From 265be9849b1eb8b7e5393a830c87624aa87e8f07 Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Wed, 13 May 2026 10:50:28 +0000 Subject: [PATCH 1/3] =?UTF-8?q?Store=20uploaded=5Ffile=5Fid=20on=20magic?= =?UTF-8?q?=5Fplan=5Fplan=20row=20=F0=9F=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/db/functions/magic_plan_functions.py | 8 +++-- .../tests/test_magic_plan_functions.py | 34 +++++++++++++++---- backend/app/db/models/magic_plan.py | 1 + backend/magic_plan/magic_plan_service.py | 3 +- 4 files changed, 35 insertions(+), 11 deletions(-) diff --git a/backend/app/db/functions/magic_plan_functions.py b/backend/app/db/functions/magic_plan_functions.py index 9400f36f..143e4172 100644 --- a/backend/app/db/functions/magic_plan_functions.py +++ b/backend/app/db/functions/magic_plan_functions.py @@ -14,15 +14,15 @@ from backend.app.db.models.magic_plan import ( ) -def save_plan(session: Session, plan: Plan) -> None: - plan_id: int = _upsert_plan(session, plan) +def save_plan(session: Session, plan: Plan, uploaded_file_id: int) -> None: + plan_id: int = _upsert_plan(session, plan, uploaded_file_id) _delete_children(session, plan_id) floor_ids: list[int] = _insert_floors(session, plan.floors, plan_id) room_ids: list[int] = _insert_rooms(session, plan.floors, floor_ids) _insert_windows_and_doors(session, plan.floors, room_ids) -def _upsert_plan(session: Session, plan: Plan) -> int: +def _upsert_plan(session: Session, plan: Plan, uploaded_file_id: int) -> int: stmt = ( pg_insert(MagicPlanPlanModel) .values( @@ -30,6 +30,7 @@ def _upsert_plan(session: Session, plan: Plan) -> int: name=plan.name, address=plan.address, postcode=plan.postcode, + uploaded_file_id=uploaded_file_id, ) .on_conflict_do_update( index_elements=["magic_plan_uid"], @@ -37,6 +38,7 @@ def _upsert_plan(session: Session, plan: Plan) -> int: "name": plan.name, "address": plan.address, "postcode": plan.postcode, + "uploaded_file_id": uploaded_file_id, }, ) .returning(col(MagicPlanPlanModel.id)) diff --git a/backend/app/db/functions/tests/test_magic_plan_functions.py b/backend/app/db/functions/tests/test_magic_plan_functions.py index e58d0528..0b93685c 100644 --- a/backend/app/db/functions/tests/test_magic_plan_functions.py +++ b/backend/app/db/functions/tests/test_magic_plan_functions.py @@ -36,7 +36,7 @@ def _count(session: Session, model: type[SQLModel]) -> int: def test_plan_row_present_after_save(db_session: Session, domain_plan: Plan) -> None: # Act - save_plan(db_session, domain_plan) + save_plan(db_session, domain_plan, 1) # Assert assert _count(db_session, MagicPlanPlanModel) == 1 @@ -45,7 +45,7 @@ def test_floor_count_matches_domain(db_session: Session, domain_plan: Plan) -> N # Arrange expected = len(domain_plan.floors) # Act - save_plan(db_session, domain_plan) + save_plan(db_session, domain_plan, 1) # Assert assert _count(db_session, MagicPlanFloorModel) == expected @@ -54,7 +54,7 @@ def test_room_count_matches_domain(db_session: Session, domain_plan: Plan) -> No # Arrange expected = sum(len(f.rooms) for f in domain_plan.floors) # Act - save_plan(db_session, domain_plan) + save_plan(db_session, domain_plan, 1) # Assert assert _count(db_session, MagicPlanRoomModel) == expected @@ -63,7 +63,7 @@ def test_window_count_matches_domain(db_session: Session, domain_plan: Plan) -> # Arrange expected = sum(len(r.windows) for f in domain_plan.floors for r in f.rooms) # Act - save_plan(db_session, domain_plan) + save_plan(db_session, domain_plan, 1) # Assert assert _count(db_session, MagicPlanWindowModel) == expected @@ -72,15 +72,15 @@ def test_door_count_matches_domain(db_session: Session, domain_plan: Plan) -> No # Arrange expected = sum(len(r.doors) for f in domain_plan.floors for r in f.rooms) # Act - save_plan(db_session, domain_plan) + save_plan(db_session, domain_plan, 1) # Assert assert _count(db_session, MagicPlanDoorModel) == expected def test_save_plan_idempotent(db_session: Session, domain_plan: Plan) -> None: # Act — call twice within the same session - save_plan(db_session, domain_plan) - save_plan(db_session, domain_plan) + save_plan(db_session, domain_plan, 1) + save_plan(db_session, domain_plan, 1) # Assert — same row counts as a single call assert _count(db_session, MagicPlanPlanModel) == 1 assert _count(db_session, MagicPlanFloorModel) == len(domain_plan.floors) @@ -93,3 +93,23 @@ def test_save_plan_idempotent(db_session: Session, domain_plan: Plan) -> None: assert _count(db_session, MagicPlanDoorModel) == sum( len(r.doors) for f in domain_plan.floors for r in f.rooms ) + + +def test_uploaded_file_id_stored_after_save(db_session: Session, domain_plan: Plan) -> None: + # Act + save_plan(db_session, domain_plan, 1) + # Assert + row = db_session.execute(select(MagicPlanPlanModel)).scalar_one() + assert row.uploaded_file_id == 1 + + +def test_save_plan_updates_uploaded_file_id_on_reingest( + db_session: Session, domain_plan: Plan +) -> None: + # Arrange + save_plan(db_session, domain_plan, 1) + # Act + save_plan(db_session, domain_plan, 2) + # Assert + row = db_session.execute(select(MagicPlanPlanModel)).scalar_one() + assert row.uploaded_file_id == 2 diff --git a/backend/app/db/models/magic_plan.py b/backend/app/db/models/magic_plan.py index 38e9de18..77ca52fd 100644 --- a/backend/app/db/models/magic_plan.py +++ b/backend/app/db/models/magic_plan.py @@ -11,6 +11,7 @@ class MagicPlanPlanModel(SQLModel, table=True): name: Optional[str] = None address: Optional[str] = None postcode: Optional[str] = None + uploaded_file_id: Optional[int] = Field(default=None) class MagicPlanFloorModel(SQLModel, table=True): diff --git a/backend/magic_plan/magic_plan_service.py b/backend/magic_plan/magic_plan_service.py index 22e19ddf..2be3379d 100644 --- a/backend/magic_plan/magic_plan_service.py +++ b/backend/magic_plan/magic_plan_service.py @@ -55,8 +55,9 @@ class MagicPlanService: ) with db_session() as session: - save_plan(session, plan) session.add(uploaded_file) + session.flush() + save_plan(session, plan, uploaded_file.id) return plan From 509fbf2abfa3849a44782f5a9cf2f8d033157823 Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Wed, 13 May 2026 11:02:46 +0000 Subject: [PATCH 2/3] =?UTF-8?q?Store=20uploaded=5Ffile=5Fid=20on=20magic?= =?UTF-8?q?=5Fplan=5Fplan=20row=20=F0=9F=9F=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/magic_plan/magic_plan_service.py | 4 +-- .../tests/test_magic_plan_service.py | 35 +++++++++++++++++++ 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/backend/magic_plan/magic_plan_service.py b/backend/magic_plan/magic_plan_service.py index 2be3379d..8a75c716 100644 --- a/backend/magic_plan/magic_plan_service.py +++ b/backend/magic_plan/magic_plan_service.py @@ -1,7 +1,7 @@ import gzip import json from datetime import datetime, timezone -from typing import Optional +from typing import Optional, cast from datatypes.magicplan.api.response import MagicPlanPlan, PlanSummary from datatypes.magicplan.domain.mapper import map_plan @@ -57,7 +57,7 @@ class MagicPlanService: with db_session() as session: session.add(uploaded_file) session.flush() - save_plan(session, plan, uploaded_file.id) + save_plan(session, plan, cast(int, uploaded_file.id)) return plan diff --git a/backend/magic_plan/tests/test_magic_plan_service.py b/backend/magic_plan/tests/test_magic_plan_service.py index 158cf4d6..a2302ab4 100644 --- a/backend/magic_plan/tests/test_magic_plan_service.py +++ b/backend/magic_plan/tests/test_magic_plan_service.py @@ -271,3 +271,38 @@ def test_run_creates_uploaded_file_record( assert uploaded_file.s3_upload_timestamp is not None assert uploaded_file.uprn == 100023336956 assert uploaded_file.hubspot_deal_id == "deal-789" + + +def test_run_passes_flushed_uploaded_file_id_to_save_plan( + mock_client: MagicMock, + plan_summary: PlanSummary, +) -> None: + # Arrange + mock_client.get_plans.return_value = [plan_summary] + service = _make_service(mock_client) + mock_session = MagicMock() + added_objects: list = [] + + mock_session.add.side_effect = added_objects.append + + def simulate_flush() -> None: + for obj in added_objects: + if isinstance(obj, UploadedFile): + obj.id = 42 + + mock_session.flush.side_effect = simulate_flush + + with patch( + "backend.magic_plan.magic_plan_service.find_matching_plan", + return_value=plan_summary, + ), patch("backend.magic_plan.magic_plan_service.save_plan") as mock_save, patch( + "backend.magic_plan.magic_plan_service.db_session" + ) as mock_db, patch( + "backend.magic_plan.magic_plan_service.save_data_to_s3" + ): + mock_db.return_value.__enter__.return_value = mock_session + # Act + service.run(_make_request()) + + # Assert + assert mock_save.call_args[0][2] == 42 From 7635c800e6b88d65ae3ef7ddbbf7d199aaa7e64b Mon Sep 17 00:00:00 2001 From: Jun-te Kim Date: Wed, 13 May 2026 16:04:53 +0000 Subject: [PATCH 3/3] added 0.0.7 --- .devcontainer/backend/devcontainer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.devcontainer/backend/devcontainer.json b/.devcontainer/backend/devcontainer.json index 24949770..0a78dadf 100644 --- a/.devcontainer/backend/devcontainer.json +++ b/.devcontainer/backend/devcontainer.json @@ -5,7 +5,7 @@ "remoteUser": "vscode", "workspaceFolder": "/workspaces/model", "initializeCommand": "docker network create shared-dev 2>/dev/null || true; test -d \"$HOME/.config/gh\" || test -n \"$GITHUB_TOKEN\" || { echo >&2 'error: no GitHub auth found. Run `gh auth login && gh auth setup-git` on the host, or export GITHUB_TOKEN, then retry.'; exit 1; }", - "postCreateCommand": "gh repo clone Hestia-Homes/agentic-toolkit /tmp/agentic-toolkit -- --branch 0.0.5 --depth 1 && bash /tmp/agentic-toolkit/setup.sh", + "postCreateCommand": "gh repo clone Hestia-Homes/agentic-toolkit /tmp/agentic-toolkit -- --branch 0.0.7 --depth 1 && bash /tmp/agentic-toolkit/setup.sh", "postStartCommand": "bash .devcontainer/backend/post-install.sh", "mounts": [ "source=${localEnv:HOME},target=/workspaces/home,type=bind",