Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions apps/api/src/planproof_api/agent/planner.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@
"If the user did not specify a duration, ask about it in questions. "
"Every task MUST have a duration of at least 5 minutes. You are forbidden "
"from creating zero-duration tasks to fit constraints. "
"If a task duration is not specified, assume a minimum of 25 minutes for "
"anything labeled \"Meeting\" and 15 minutes for chores. You are "
"FORBIDDEN from scheduling tasks shorter than 15 minutes unless the user "
"explicitly asks for a \"quick check\" or \"5-min task\". "
"Current time is provided in 12h format. Be extremely careful with AM/PM: "
"3:15 PM is 15:15. If the current time is 6 AM, a 3 PM meeting is in the "
"future and must be scheduled. "
Expand Down
8 changes: 7 additions & 1 deletion apps/api/src/planproof_api/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,10 +151,11 @@ def _validate_plan(
plan, metadata.actionable_tasks
)
missing_keywords = _missing_keywords(plan, metadata.actionable_tasks)
human_feasibility_flags = check_feasibility(plan)
human_feasibility_flags, feasibility_errors = check_feasibility(plan)
zero_duration_flags = 0

errors: list[str] = list(constraint_errors)
errors.extend(feasibility_errors)
current_dt = isoparse(current_time)
for item in plan:
start_dt = isoparse(item.start_time)
Expand Down Expand Up @@ -254,6 +255,11 @@ def _repair_plan(
"RULE 2: Do not delete tasks to fix overlaps. Shorten them instead "
"(e.g., change 60m to 15m)."
)
repair_prompt = (
f"{repair_prompt}\n\n"
"Your previous attempt used unrealistic 5-minute durations. Increase "
"durations to at least 25 minutes and shift other tasks accordingly."
)
if constraint_violation_count > 0:
repair_prompt = (
f"{repair_prompt}\n\n"
Expand Down
32 changes: 27 additions & 5 deletions apps/api/tests/test_feasibility.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,15 @@ def test_check_feasibility_no_long_blocks() -> None:
_item("2025-01-18T11:15:00-05:00", "2025-01-18T13:00:00-05:00"),
]

assert check_feasibility(items) == 0
flags, _ = check_feasibility(items)
assert flags == 0


def test_check_feasibility_single_block_over_limit() -> None:
items = [_item("2025-01-18T09:00:00-05:00", "2025-01-18T13:30:00-05:00")]

assert check_feasibility(items) == 1
flags, _ = check_feasibility(items)
assert flags == 1


def test_check_feasibility_multiple_blocks_over_limit() -> None:
Expand All @@ -36,7 +38,8 @@ def test_check_feasibility_multiple_blocks_over_limit() -> None:
_item("2025-01-18T13:45:00-05:00", "2025-01-18T18:15:00-05:00"),
]

assert check_feasibility(items) == 2
flags, _ = check_feasibility(items)
assert flags == 2


def test_check_feasibility_break_exactly_15_minutes() -> None:
Expand All @@ -45,7 +48,8 @@ def test_check_feasibility_break_exactly_15_minutes() -> None:
_item("2025-01-18T13:15:00-05:00", "2025-01-18T15:00:00-05:00"),
]

assert check_feasibility(items) == 0
flags, _ = check_feasibility(items)
assert flags == 0


def test_check_feasibility_overlapping_tasks() -> None:
Expand All @@ -54,4 +58,22 @@ def test_check_feasibility_overlapping_tasks() -> None:
_item("2025-01-18T10:30:00-05:00", "2025-01-18T15:30:00-05:00"),
]

assert check_feasibility(items) == 1
flags, _ = check_feasibility(items)
assert flags == 1


def test_check_feasibility_short_meeting() -> None:
items = [
PlanItem(
task="Team Meeting",
start_time="2025-01-18T09:00:00-05:00",
end_time="2025-01-18T09:10:00-05:00",
timebox_minutes=0,
why="Test",
)
]

flags, errors = check_feasibility(items)

assert flags == 1
assert "Meeting duration is realistically too short." in errors
17 changes: 14 additions & 3 deletions eval/feasibility.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,15 @@
from planproof_api.agent.schemas import PlanItem


def check_feasibility(plan_items: List["PlanItem"]) -> int:
def check_feasibility(plan_items: List["PlanItem"]) -> tuple[int, list[str]]:
if not plan_items:
return 0
return 0, []

items = sorted(plan_items, key=lambda item: isoparse(item.start_time))
block_start = isoparse(items[0].start_time)
block_end = isoparse(items[0].end_time)
flags = 0
errors: list[str] = []

for item in items[1:]:
start_time = isoparse(item.start_time)
Expand All @@ -32,8 +33,18 @@ def check_feasibility(plan_items: List["PlanItem"]) -> int:
if end_time > block_end:
block_end = end_time

for item in items:
label = (item.task or "").lower()
if "meeting" in label or "sync" in label:
start_time = isoparse(item.start_time)
end_time = isoparse(item.end_time)
duration_minutes = (end_time - start_time).total_seconds() / 60
if duration_minutes < 20:
flags += 1
errors.append("Meeting duration is realistically too short.")

block_minutes = (block_end - block_start).total_seconds() / 60
if block_minutes > 240:
flags += 1

return flags
return flags, errors