From ff67297646a47ea02b77a3217930867237476035 Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Fri, 1 May 2026 16:20:55 +0000 Subject: [PATCH] start mapping json --- backend/magic_plan/magic_plan_client.py | 45 +- .../magicplan_api_plan_response_example.json | 136742 +++++++++++++++ backend/magic_plan/models.py | 334 +- .../tests/test_magic_plan_client.py | 10 +- backend/magic_plan/tests/test_models.py | 50 + backend/magic_plan/tests/test_xml_parser.py | 226 +- backend/magic_plan/xml_parser.py | 82 +- 7 files changed, 137258 insertions(+), 231 deletions(-) create mode 100644 backend/magic_plan/magicplan_api_plan_response_example.json create mode 100644 backend/magic_plan/tests/test_models.py diff --git a/backend/magic_plan/magic_plan_client.py b/backend/magic_plan/magic_plan_client.py index 29ad078a..9398f9b8 100644 --- a/backend/magic_plan/magic_plan_client.py +++ b/backend/magic_plan/magic_plan_client.py @@ -1,14 +1,51 @@ -from backend.magic_plan.models import MagicPlanDetail, MagicPlanSummary +import requests + +from backend.magic_plan.models import ( + MagicPlanPlan, + MagicPlanSummary, + MagicPlanXMLDetail, + MagicPlanXMLSummary, +) +from backend.magic_plan.xml_parser import parse_magicplan_xml + + +def _parse_xml_summary(data: dict[str, object]) -> MagicPlanXMLSummary: + return MagicPlanXMLSummary( + id=str(data.get("id", "")), + project_id=str(data.get("project_id", "")), + name=str(data.get("name", "")), + address=str(data.get("address", "")), + creation_date=str(data.get("creation_date", "")), + update_date=str(data.get("update_date", "")), + workgroup_id=str(data.get("workgroup_id", "")), + team_id=str(data.get("team_id", "")), + created_by=str(data.get("created_by", "")), + thumbnail_url=str(data.get("thumbnail_url", "")), + public_url=str(data.get("public_url", "")), + cloud_url=str(data.get("cloud_url", "")), + url_3d=str(data.get("3d_url", "")), + ) class MagicPlanClient: BASE_URL = "https://cloud.magicplan.app/api/v2" - def __init__(self, _api_key: str) -> None: - raise NotImplementedError + def __init__(self, api_key: str) -> None: + self._session = requests.Session() + self._session.headers.update({"Authorization": f"Bearer {api_key}"}) def get_plans(self, _project_id: str) -> list[MagicPlanSummary]: raise NotImplementedError - def get_plan_xml(self, _plan_id: str) -> MagicPlanDetail: + def get_plan_xml(self, plan_id: str) -> MagicPlanXMLDetail: + resp = self._session.get(f"{self.BASE_URL}/plans/get/{plan_id}") + resp.raise_for_status() + data: dict[str, object] = resp.json()["data"] + plan_data = data["plan"] # type: ignore[index] + detail_data = data["plan_detail"] # type: ignore[index] + summary = _parse_xml_summary(plan_data) # type: ignore[arg-type] + plan_xml = parse_magicplan_xml(detail_data["magicplan_format_xml"]) # type: ignore[index] + return MagicPlanXMLDetail(summary=summary, plan_xml=plan_xml) + + def get_plan(self, _plan_id: str) -> MagicPlanPlan: raise NotImplementedError diff --git a/backend/magic_plan/magicplan_api_plan_response_example.json b/backend/magic_plan/magicplan_api_plan_response_example.json new file mode 100644 index 00000000..d76b3540 --- /dev/null +++ b/backend/magic_plan/magicplan_api_plan_response_example.json @@ -0,0 +1,136742 @@ +{ + "message": "OK", + "data": { + "plan": { + "id": "a7285ed1-878d-47eb-8aa6-85ef9e187516", + "project_id": "9f8f3208-0f04-466f-9c4c-e776532183c8", + "name": "2, Br2 8bz", + "address": { + "street": "2 Laburnum Way", + "street_number": null, + "postal_code": "BR2 8BZ", + "city": "Bromley", + "country": "GB", + "longitude": 0.0616749, + "latitude": 51.3835182 + }, + "creation_date": "2026-04-28T08:32:58+00:00", + "update_date": "2026-04-29T14:58:54+00:00", + "thumbnail_url": "https:\/\/s3.amazonaws.com\/prod.plans.sensopia.com\/a7285ed1-878d-47eb-8aa6-85ef9e187516\/plan.thumb", + "public_url": "https:\/\/cloud.magicplan.app\/plan\/a7285ed1-878d-47eb-8aa6-85ef9e187516", + "cloud_url": "https:\/\/cloud.magicplan.app\/projects\/a7285ed1-878d-47eb-8aa6-85ef9e187516", + "3d_url": "https:\/\/3d.magicplan.app\/#embed\/?key=YzBkMTQyZDRlY2E5MmEzMWQ4NWE1NWJmMGE4OTQ5ZjMwOTNlZjcwNjhkN2U4ODg5ZDZiMDI1OTRkNWU5ZTY0N%2B9n3Xg%2FF422BetMnabb%2FwQI3XiEQbNltioOXI05WueYapFlJvuxgPLnzxjLI1eFcsii6s7vRgs71gHD1LPsSBcNGjF424hTkMCt9hxbCryf", + "workgroup_id": "677d01685458a", + "team_id": null, + "created_by": { + "id": "49c5fd0d-5031-4a7d-aa59-3cc1b64d18aa", + "firstname": null, + "lastname": null, + "email": "sebastian@osmosis-acd.com" + } + }, + "plan_detail": { + "magicplan_format_xml": "\n2026-04-24<\/value>2.134<\/value>100<\/value>0<\/value>0<\/value><\/values>Ground Floor<\/name>2.450007<\/value>Total m2 =1.196 yd\u00b2 <\/value><\/values><\/symbolInstance>m<\/value>m<\/value>m2<\/value>outdoors<\/value>m3<\/value><\/values><\/symbolInstance>annotations<\/value>3<\/value>left<\/value>M2 - 44.19\nHeight - 2.43\nHLP - 20.56\nPWL - 6.12<\/value>top<\/value><\/values><\/symbolInstance>677d01685458a<\/value><\/values><\/symbolInstance>annotations<\/value>7.22m<\/value><\/values><\/symbolInstance>annotations<\/value>6.12m<\/value><\/values><\/symbolInstance>m<\/value>0<\/value>m<\/value>m2<\/value>doors<\/value>m3<\/value>2.026217<\/value><\/values><\/symbolInstance>m<\/value>1<\/value>m<\/value>m2<\/value>doors<\/value>m3<\/value>0.905517<\/value>1.20394<\/value><\/values><\/symbolInstance>0.496099<\/value>0.241025<\/value>plumbing<\/value>0.682423<\/value>0.241025<\/value>0.454712<\/value><\/values><\/symbolInstance>m<\/value>1<\/value>m<\/value>m2<\/value>doors<\/value>m3<\/value>2.014143<\/value><\/values><\/symbolInstance>m<\/value>0<\/value>m<\/value>m2<\/value>doors<\/value>m3<\/value>1.963297<\/value><\/values><\/symbolInstance>677d01685458a<\/value><\/values><\/symbolInstance>677d01685458a<\/value><\/values><\/symbolInstance>m<\/value>m<\/value>m2<\/value>hvac<\/value>m3<\/value>0.1500<\/value><\/values><\/symbolInstance>m<\/value>1<\/value>m<\/value>m2<\/value>doors<\/value>m3<\/value>2.014739<\/value><\/values><\/symbolInstance>m<\/value>1<\/value>m<\/value>m2<\/value>doors<\/value>m3<\/value>0.985417<\/value>1.099043<\/value><\/values><\/symbolInstance>m<\/value>1<\/value>m<\/value>m2<\/value>doors<\/value>m3<\/value>0.911601<\/value>1.123649<\/value><\/values><\/symbolInstance>m<\/value>0<\/value>m<\/value>m2<\/value>doors<\/value>m3<\/value>2.057803<\/value><\/values><\/symbolInstance>677d01685458a<\/value><\/values><\/symbolInstance>m<\/value>m<\/value>m2<\/value>hvac<\/value>m3<\/value>0.1500<\/value><\/values><\/symbolInstance>677d01685458a<\/value><\/values><\/symbolInstance>677d01685458a<\/value><\/values><\/symbolInstance>m<\/value>1<\/value>m<\/value>m2<\/value>doors<\/value>m3<\/value>1.942764<\/value><\/values><\/symbolInstance>m<\/value>1<\/value>m<\/value>m2<\/value>doors<\/value>m3<\/value>0.968262<\/value>1.063659<\/value><\/values><\/symbolInstance>677d01685458a<\/value><\/values><\/symbolInstance>677d01685458a<\/value><\/values><\/symbolInstance>m<\/value>1<\/value>m<\/value>m2<\/value>doors<\/value>m3<\/value>2.014143<\/value><\/values><\/symbolInstance>m<\/value>0<\/value>m<\/value>m2<\/value>doors<\/value>m3<\/value>2.133701<\/value><\/values><\/symbolInstance>m<\/value>1<\/value>m<\/value>m2<\/value>doors<\/value>m3<\/value>2.014739<\/value><\/values><\/symbolInstance>m<\/value>1<\/value>m<\/value>m2<\/value>doors<\/value>m3<\/value>1.942764<\/value><\/values><\/symbolInstance>m<\/value>1<\/value>m<\/value>m2<\/value>doors<\/value>m3<\/value>0.867147<\/value>1.202909<\/value><\/values><\/symbolInstance>677d01685458a<\/value><\/values><\/symbolInstance>m<\/value>m<\/value>m2<\/value>hvac<\/value>m3<\/value>0.1500<\/value><\/values><\/symbolInstance>m<\/value>m<\/value>m2<\/value>electrical<\/value>m3<\/value>1.1<\/value><\/values><\/symbolInstance>structure<\/value>0<\/value>1<\/value><\/values><\/symbolInstance>m<\/value>0<\/value>m<\/value>m2<\/value>doors<\/value>m3<\/value>2.057803<\/value><\/values><\/symbolInstance>m<\/value>1<\/value>m<\/value>m2<\/value>doors<\/value>m3<\/value>1.993003<\/value><\/values><\/symbolInstance>m<\/value>1<\/value>m<\/value>m2<\/value>doors<\/value>m3<\/value>1.0<\/value><\/values><\/symbolInstance>m<\/value>1<\/value>m<\/value>m2<\/value>doors<\/value>m3<\/value>1.0<\/value><\/values><\/symbolInstance>m<\/value>0<\/value>m<\/value>m2<\/value>doors<\/value>m3<\/value>1.963297<\/value><\/values><\/symbolInstance>677d01685458a<\/value><\/values><\/symbolInstance>m<\/value>m<\/value>m2<\/value>hvac<\/value>m3<\/value>0.1500<\/value><\/values><\/symbolInstance>2.450007<\/value>0.5x1.2 (x2)\n<\/value>2500 (x2)<\/value>Between.15.and.30.degrees<\/value>0<\/value>9.X.6<\/value>0<\/value>0<\/value>9<\/value><\/values><\/floorRoom>