"""ChatPlan mapper and import/export round-trip tests.""" from __future__ import annotations from datetime import date from app.chat_plan import ( ChatPlanError, chat_plan_to_day_put, plan_and_feedback_to_chat_plan, ) from app.schedule_store import DayPlan, DayReview, ScheduledBlock SAMPLE = { "schema_version": 1, "date": "2026-07-24", "title": "Spain pack day", "intention": "AXA + stay proof + hold PDF. No embassy. No flight pay.", "constraints": ["wallet home on walk", "Weeknd only after proof"], "blocks": [ { "start": "08:00", "end": "08:25", "title": "Interrupt walk (no headphones)", "priority": "P0", "kind": "body_care", "intent": "measure", "notes": "Eyes outside.", "status": "planned", "review": { "did": None, "minutes": None, "comment": "", "emotions": [], "fse_event": "", "intensity": None, }, }, { "start": "09:00", "end": "10:30", "title": "Fix AXA residence Tanzania", "priority": "P0", "kind": "admin_spain", "intent": "duty", "notes": "26 Aug–5 Sep 2026; PDF", "locked": True, "status": "planned", "review": { "did": None, "minutes": None, "comment": "", "emotions": [], "fse_event": "", "intensity": None, }, }, ], "day_review": { "comment": "", "emotions": [], "fse_events": "", "what_moved": "", "what_avoided": "", "tomorrow_change": "", }, } def test_chat_to_put_basic() -> None: put, fbs, warnings = chat_plan_to_day_put(SAMPLE, "2026-07-24") assert put.title == "Spain pack day" assert put.intention.startswith("AXA") assert put.constraints == ["wallet home on walk", "Weeknd only after proof"] assert len(put.blocks) == 2 assert put.blocks[0].planned_min == 25 assert put.blocks[1].kind == "admin_spain" assert put.source == "chat" assert fbs == [] assert isinstance(warnings, list) def test_date_mismatch() -> None: try: chat_plan_to_day_put(SAMPLE, "2026-07-25") assert False, "expected error" except ChatPlanError as exc: assert any("date mismatch" in e for e in exc.errors) def test_empty_blocks() -> None: try: chat_plan_to_day_put({"blocks": []}, "2026-07-24") assert False except ChatPlanError: pass def test_unknown_kind_coerced() -> None: chat = { "blocks": [ { "start": "10:00", "end": "10:30", "title": "Weird", "kind": "embassy_run", "priority": "P1", } ] } put, _, warnings = chat_plan_to_day_put(chat, "2026-07-24") assert put.blocks[0].kind == "other" assert "[kind:embassy_run]" in put.blocks[0].notes assert any("embassy_run" in w for w in warnings) def test_overlap_validation() -> None: chat = { "blocks": [ {"start": "09:00", "end": "10:00", "title": "A", "kind": "other"}, {"start": "09:30", "end": "10:30", "title": "B", "kind": "other"}, ] } try: chat_plan_to_day_put(chat, "2026-07-24") assert False except ChatPlanError as exc: assert any("overlap" in e for e in exc.errors) def test_export_round_trip_fields() -> None: put, _, _ = chat_plan_to_day_put(SAMPLE, "2026-07-24") plan = DayPlan( date="2026-07-24", blocks=put.blocks, title=put.title or "", intention=put.intention or "", constraints=put.constraints or [], day_review=put.day_review or DayReview(), source="chat", ) # Annotate first block fb = { put.blocks[0].id: { "block_id": put.blocks[0].id, "did": "done", "actual_min": 20, "note": "walked", "emotions": ["calm"], "fse_event": "", "intensity": 3, } } plan.blocks[0] = plan.blocks[0].model_copy(update={"status": "done"}) out = plan_and_feedback_to_chat_plan(plan, fb) assert out["title"] == "Spain pack day" assert out["intention"].startswith("AXA") assert out["blocks"][0]["review"]["comment"] == "walked" assert out["blocks"][0]["review"]["emotions"] == ["calm"] assert out["summary"]["blocks_total"] == 2 assert out["summary"]["blocks_done"] == 1 assert out["summary"]["p0_total"] == 2 assert out["summary"]["p0_done"] == 1 assert out["summary"]["actual_min_sum"] == 20 def test_old_plan_without_day_review_loads() -> None: plan = DayPlan.model_validate( { "date": "2026-01-01", "blocks": [], "source": "user", } ) assert plan.day_review.comment == "" assert plan.title == "" assert plan.constraints == [] def test_review_did_sets_status() -> None: chat = { "blocks": [ { "start": "11:00", "end": "11:30", "title": "Done already", "kind": "other", "status": "planned", "review": {"did": "done", "minutes": 25, "comment": "ok", "emotions": ["hope"]}, } ] } put, fbs, _ = chat_plan_to_day_put(chat, "2026-07-24") assert put.blocks[0].status == "done" assert len(fbs) == 1 assert fbs[0].emotions == ["hope"] assert fbs[0].actual_min == 25