From d3ca6bc7ffb40afc23aa4bd697ebf40e7ff8015c Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Tue, 5 May 2026 15:45:36 +0000 Subject: [PATCH] delete historic mapping and client --- backend/magic_plan/__init__.py | 0 backend/magic_plan/magic_plan_client.py | 51 - backend/magic_plan/models.py | 417 -------- backend/magic_plan/tests/__init__.py | 0 .../tests/test_magic_plan_client.py | 195 ---- backend/magic_plan/tests/test_models.py | 50 - backend/magic_plan/tests/test_xml_parser.py | 461 -------- backend/magic_plan/xml_example.xml | 985 ------------------ backend/magic_plan/xml_example_2.xml | 1 - backend/magic_plan/xml_example_3.xml | 1 - backend/magic_plan/xml_parser.py | 224 ---- backend/magic_plan/xml_schema.py | 48 - 12 files changed, 2433 deletions(-) delete mode 100644 backend/magic_plan/__init__.py delete mode 100644 backend/magic_plan/magic_plan_client.py delete mode 100644 backend/magic_plan/models.py delete mode 100644 backend/magic_plan/tests/__init__.py delete mode 100644 backend/magic_plan/tests/test_magic_plan_client.py delete mode 100644 backend/magic_plan/tests/test_models.py delete mode 100644 backend/magic_plan/tests/test_xml_parser.py delete mode 100644 backend/magic_plan/xml_example.xml delete mode 100644 backend/magic_plan/xml_example_2.xml delete mode 100644 backend/magic_plan/xml_example_3.xml delete mode 100644 backend/magic_plan/xml_parser.py delete mode 100644 backend/magic_plan/xml_schema.py diff --git a/backend/magic_plan/__init__.py b/backend/magic_plan/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/backend/magic_plan/magic_plan_client.py b/backend/magic_plan/magic_plan_client.py deleted file mode 100644 index 9398f9b8..00000000 --- a/backend/magic_plan/magic_plan_client.py +++ /dev/null @@ -1,51 +0,0 @@ -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: - 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) -> 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/models.py b/backend/magic_plan/models.py deleted file mode 100644 index df64fb5c..00000000 --- a/backend/magic_plan/models.py +++ /dev/null @@ -1,417 +0,0 @@ -import re -from dataclasses import dataclass -from typing import Any, Optional - - -def _camel_to_snake(name: str) -> str: - return re.sub(r"(? dict[str, str]: - result: dict[str, str] = {} - for f in fields: - if f.get("type_as_string") == "sectionTitle": - continue - v: dict[str, Any] = f.get("value") or {} - if not v.get("has_value"): - continue - result[_camel_to_snake(str(f["id"]))] = str(v["value"]) - return result - - -# --------------------------------------------------------------------------- -# XML dataclasses (sourced from MagicPlan Exchange XML format) -# --------------------------------------------------------------------------- - - -@dataclass -class MagicPlanXMLRoomPoint: - snapped_x: float - snapped_y: float - height: float - uid: str - values: dict[str, str] - - -@dataclass -class MagicPlanXMLWallPoint: - """Point in — absolute coords, no uid or values.""" - - x: float - y: float - height: float - - -@dataclass -class MagicPlanXMLDoor: - """Door in context — wall-relative position.""" - - wall_point_index: int - type: str - u: float - width: float - depth: float - height: float - orientation: int - snapped_type: str - snapped_position: float - snapped_width: float - snapped_depth: float - snapped_height: float - snapped_orientation: int - inset_x: float - inset_y: float - inset_z: float - symbol_instance: str - twin_wall_item_uid: Optional[str] = None - - -@dataclass -class MagicPlanXMLWindow: - """Window in context — wall-relative position.""" - - wall_point_index: int - type: str - u: float - width: float - depth: float - height: float - orientation: int - snapped_type: str - snapped_position: float - snapped_width: float - snapped_depth: float - snapped_height: float - snapped_orientation: int - inset_x: float - inset_y: float - inset_z: float - symbol_instance: str - - -@dataclass -class MagicPlanXMLExplodedOpening: - """Door or window in context — absolute coords, no snapped* fields.""" - - type: str - x1: float - y1: float - x2: float - y2: float - width: float - depth: float - height: float - inset_x: float - inset_y: float - orientation: int - symbol_instance: str - - -@dataclass -class MagicPlanXMLFurniture: - type: str - x: float - y: float - snapped_x: float - snapped_y: float - angle: float - width: float - depth: float - height: float - snapped_width: float - snapped_depth: float - snapped_height: float - size_lock_0: str - size_lock_1: str - size_lock_2: str - symbol_instance: str - - -@dataclass -class MagicPlanXMLMainDimension: - from_point: int - to_point: int - dir_x: float - dir_y: float - value: float - actual_value: float - is_set: bool - - -@dataclass -class MagicPlanXMLExplodedWall: - wall_type: str - points: list[MagicPlanXMLWallPoint] - - -@dataclass -class MagicPlanXMLExploded: - walls: list[MagicPlanXMLExplodedWall] - doors: list[MagicPlanXMLExplodedOpening] - windows: list[MagicPlanXMLExplodedOpening] - furniture: list[MagicPlanXMLFurniture] - - -@dataclass -class MagicPlanXMLSymbolInstance: - id: str - uid: str - parent_uid: str - symbol: str - values: dict[str, str] - - -@dataclass -class MagicPlanXMLRoom: - uid: str - type: str - x: float - y: float - rotation: float - was_modified: bool - linked_room_0: str - linked_room_1: str - area: float - perimeter: float - values: dict[str, str] - points: list[MagicPlanXMLRoomPoint] - doors: list[MagicPlanXMLDoor] - windows: list[MagicPlanXMLWindow] - furniture: list[MagicPlanXMLFurniture] - main_dimensions: list[MagicPlanXMLMainDimension] - - -@dataclass -class MagicPlanXMLFloor: - uid: str - name: str - floor_type: str - rotation: float - compass_angle: float - area_without_walls: float - area_with_interior_walls_only: float - area_with_walls: float - symbol_instance: MagicPlanXMLSymbolInstance - rooms: list[MagicPlanXMLRoom] - furniture: list[MagicPlanXMLFurniture] - exploded: MagicPlanXMLExploded - - -@dataclass -class MagicPlanXMLSummary: - """Plan metadata returned by the list-plans API endpoint (old string-address format).""" - - id: str - project_id: str - name: str - address: str - creation_date: str - update_date: str - workgroup_id: str - team_id: str - created_by: str - thumbnail_url: str - public_url: str - cloud_url: str - url_3d: str - - -@dataclass -class MagicPlanXMLDetail: - """Full plan response: summary metadata + parsed XML plan.""" - - summary: MagicPlanXMLSummary - plan_xml: "MagicPlanXMLPlan" - - -@dataclass -class MagicPlanXMLPlan: - id: str - uid: str - name: str - type: str - interior_wall_width: float - exterior_wall_width: float - schematic: bool - has_land_survey_address: bool - last_patch_identifier: str - last_roll_identifier: str - values: dict[str, str] - floors: list[MagicPlanXMLFloor] - interior_room_floors: list[MagicPlanXMLFloor] - - -# --------------------------------------------------------------------------- -# JSON dataclasses (sourced from GET Plan API JSON response) -# --------------------------------------------------------------------------- - - -@dataclass -class MagicPlanWall: - uid: str - symbol_id: str - length: float - fields: dict[str, str] - custom_fields: dict[str, str] - - -@dataclass -class MagicPlanWallItem: - """Door or window — distinguished by symbol_id.""" - - uid: str - symbol_id: str - symbol_name: str - width: float - depth: float - height: float - pos_x: float - pos_y: float - pos_z: float - rotation_z: float - fields: dict[str, str] - custom_fields: dict[str, str] - - -@dataclass -class MagicPlanFurniture: - uid: str - symbol_id: str - symbol_name: str - width: float - depth: float - height: float - pos_x: float - pos_y: float - pos_z: float - rotation_z: float - fields: dict[str, str] - custom_fields: dict[str, str] - - -@dataclass -class MagicPlanRoom: - uid: str - name: str - area: float - perimeter: float - height: float - width: float - volume: float - dimensions: str - door_count: int - window_count: int - walls_surface: float - walls_surface_without_openings: float - doors_surface: float - windows_surface: float - wall_items: list[MagicPlanWallItem] - furnitures: list[MagicPlanFurniture] - walls: list[MagicPlanWall] - fields: dict[str, str] - custom_fields: dict[str, str] - - -@dataclass -class MagicPlanFloor: - uid: str - name: str - floor_type: str - area: float - perimeter: float - area_without_walls: float - area_with_interior_walls_only: float - area_with_walls: float - wall_count: int - wall_count_with_interior_walls: int - door_count: int - window_count: int - room_count: int - furniture_count: int - doors_surface: float - walls_surface: float - walls_surface_without_openings: float - windows_surface: float - volume: float - rooms: list[MagicPlanRoom] - furnitures: list[MagicPlanFurniture] - symbol_instances: list[MagicPlanFurniture] - fields: dict[str, str] - custom_fields: dict[str, str] - - -@dataclass -class MagicPlanPlan: - """GET /plans/{id} — merged data.plan + data.plan_detail.plan.""" - - id: str - project_id: str - uid: str - name: str - area: float - perimeter: float - area_without_walls: float - area_with_interior_walls_only: float - area_with_walls: float - wall_count: int - door_count: int - window_count: int - room_count: int - furniture_count: int - floor_count: int - doors_surface: float - walls_surface: float - walls_surface_without_openings: float - windows_surface: float - volume: float - living_area: float - below_grade_living_area: float - above_grade_living_area: float - address_street: Optional[str] - address_postal_code: Optional[str] - address_city: Optional[str] - address_country: Optional[str] - address_longitude: Optional[float] - address_latitude: Optional[float] - creation_date: str - update_date: str - workgroup_id: str - team_id: str - created_by_id: str - created_by_firstname: Optional[str] - created_by_lastname: Optional[str] - created_by_email: str - thumbnail_url: str - public_url: str - cloud_url: str - url_3d: str - floors: list[MagicPlanFloor] - fields: dict[str, str] - custom_fields: dict[str, str] - - -@dataclass -class MagicPlanSummary: - """GET /plans list — lightweight, flat address and creator fields.""" - - id: str - project_id: str - name: str - address_street: Optional[str] - address_postal_code: Optional[str] - address_city: Optional[str] - address_country: Optional[str] - address_longitude: Optional[float] - address_latitude: Optional[float] - creation_date: str - update_date: str - workgroup_id: str - team_id: str - created_by_id: str - created_by_firstname: Optional[str] - created_by_lastname: Optional[str] - created_by_email: str - thumbnail_url: str - public_url: str - cloud_url: str - url_3d: str diff --git a/backend/magic_plan/tests/__init__.py b/backend/magic_plan/tests/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/backend/magic_plan/tests/test_magic_plan_client.py b/backend/magic_plan/tests/test_magic_plan_client.py deleted file mode 100644 index 63eaaa7c..00000000 --- a/backend/magic_plan/tests/test_magic_plan_client.py +++ /dev/null @@ -1,195 +0,0 @@ -import json -import xml.etree.ElementTree as ET -from pathlib import Path -from unittest.mock import MagicMock, patch - -import pytest -import requests - -from backend.magic_plan.magic_plan_client import MagicPlanClient -from backend.magic_plan.models import MagicPlanXMLDetail, MagicPlanXMLPlan, MagicPlanXMLSummary - -FIXTURES = Path(__file__).parent.parent -API_KEY = "test-api-key" -PLAN_ID = "b66bb427-33fe-4865-8d57-bad7d9d3f2e5" -PROJECT_ID = "test-project-123" -BASE_URL = "https://cloud.magicplan.app/api/v2" - - -def _load_xml() -> str: - content = (FIXTURES / "xml_example.xml").read_text().strip() - try: - ET.fromstring(content) - return content - except ET.ParseError: - return json.loads(content) # type: ignore[return-value] - - -def _summary_payload(plan_id: str = PLAN_ID, index: int = 0) -> dict[str, str]: - return { - "id": plan_id, - "project_id": PROJECT_ID, - "name": f"Plan {index}", - "address": f"Address {index}", - "creation_date": "2026-04-20", - "update_date": "2026-04-20", - "workgroup_id": "wg-123", - "team_id": "team-123", - "created_by": "user-123", - "thumbnail_url": "https://example.com/thumb.jpg", - "public_url": "https://example.com/plan", - "cloud_url": "https://example.com/cloud", - "3d_url": "https://example.com/3d", - } - - -def _get_plan_response(xml_str: str) -> dict: # type: ignore[type-arg] - return { - "message": "success", - "data": { - "plan": _summary_payload(), - "plan_detail": { - "magicplan_format_xml": xml_str, - }, - }, - } - - -def _get_plans_response(plan_ids: list[str]) -> dict: # type: ignore[type-arg] - return { - "message": "success", - "data": [_summary_payload(pid, i) for i, pid in enumerate(plan_ids)], - } - - -@pytest.fixture -def mock_session() -> MagicMock: - with patch("backend.magic_plan.magic_plan_client.requests.Session") as MockSession: - session: MagicMock = MagicMock() - MockSession.return_value = session - yield session - - -@pytest.fixture -def client(mock_session: MagicMock) -> MagicPlanClient: - return MagicPlanClient(api_key=API_KEY) - - -# --------------------------------------------------------------------------- -# Initialisation -# --------------------------------------------------------------------------- - -class TestInit: - - def test_sets_bearer_auth_header(self, mock_session: MagicMock) -> None: - MagicPlanClient(api_key=API_KEY) - mock_session.headers.update.assert_called_once_with( - {"Authorization": f"Bearer {API_KEY}"} - ) - - -# --------------------------------------------------------------------------- -# get_plan_xml -# --------------------------------------------------------------------------- - -class TestGetPlanXml: - - def test_calls_correct_url(self, client: MagicPlanClient, mock_session: MagicMock) -> None: - mock_session.get.return_value.json.return_value = _get_plan_response(_load_xml()) - client.get_plan_xml(PLAN_ID) - mock_session.get.assert_called_once_with(f"{BASE_URL}/plans/get/{PLAN_ID}") - - def test_calls_raise_for_status(self, client: MagicPlanClient, mock_session: MagicMock) -> None: - mock_session.get.return_value.json.return_value = _get_plan_response(_load_xml()) - client.get_plan_xml(PLAN_ID) - mock_session.get.return_value.raise_for_status.assert_called_once() - - def test_returns_magic_plan_detail(self, client: MagicPlanClient, mock_session: MagicMock) -> None: - mock_session.get.return_value.json.return_value = _get_plan_response(_load_xml()) - result = client.get_plan_xml(PLAN_ID) - assert isinstance(result, MagicPlanXMLDetail) - - def test_detail_contains_summary(self, client: MagicPlanClient, mock_session: MagicMock) -> None: - mock_session.get.return_value.json.return_value = _get_plan_response(_load_xml()) - result = client.get_plan_xml(PLAN_ID) - assert isinstance(result.summary, MagicPlanXMLSummary) - - def test_detail_summary_id(self, client: MagicPlanClient, mock_session: MagicMock) -> None: - mock_session.get.return_value.json.return_value = _get_plan_response(_load_xml()) - result = client.get_plan_xml(PLAN_ID) - assert result.summary.id == PLAN_ID - - def test_detail_summary_project_id(self, client: MagicPlanClient, mock_session: MagicMock) -> None: - mock_session.get.return_value.json.return_value = _get_plan_response(_load_xml()) - result = client.get_plan_xml(PLAN_ID) - assert result.summary.project_id == PROJECT_ID - - def test_detail_contains_parsed_plan(self, client: MagicPlanClient, mock_session: MagicMock) -> None: - mock_session.get.return_value.json.return_value = _get_plan_response(_load_xml()) - result = client.get_plan_xml(PLAN_ID) - assert isinstance(result.plan_xml, MagicPlanXMLPlan) - - def test_plan_xml_id_matches_xml(self, client: MagicPlanClient, mock_session: MagicMock) -> None: - mock_session.get.return_value.json.return_value = _get_plan_response(_load_xml()) - result = client.get_plan_xml(PLAN_ID) - assert result.plan_xml.id == PLAN_ID - - def test_plan_xml_floor_count(self, client: MagicPlanClient, mock_session: MagicMock) -> None: - mock_session.get.return_value.json.return_value = _get_plan_response(_load_xml()) - result = client.get_plan_xml(PLAN_ID) - assert len(result.plan_xml.floors) == 2 - - def test_raises_on_http_error(self, client: MagicPlanClient, mock_session: MagicMock) -> None: - mock_session.get.return_value.raise_for_status.side_effect = requests.HTTPError("404") - with pytest.raises(requests.HTTPError): - client.get_plan_xml(PLAN_ID) - - -# --------------------------------------------------------------------------- -# get_plans -# --------------------------------------------------------------------------- - -class TestGetPlans: - - def test_calls_correct_url(self, client: MagicPlanClient, mock_session: MagicMock) -> None: - mock_session.get.return_value.json.return_value = _get_plans_response([]) - client.get_plans(PROJECT_ID) - mock_session.get.assert_called_once_with( - f"{BASE_URL}/plans", params={"project_id": PROJECT_ID} - ) - - def test_calls_raise_for_status(self, client: MagicPlanClient, mock_session: MagicMock) -> None: - mock_session.get.return_value.json.return_value = _get_plans_response([]) - client.get_plans(PROJECT_ID) - mock_session.get.return_value.raise_for_status.assert_called_once() - - def test_returns_list_of_summaries(self, client: MagicPlanClient, mock_session: MagicMock) -> None: - mock_session.get.return_value.json.return_value = _get_plans_response([PLAN_ID, "other-id"]) - result = client.get_plans(PROJECT_ID) - assert len(result) == 2 - assert all(isinstance(s, MagicPlanXMLSummary) for s in result) - - def test_summary_id(self, client: MagicPlanClient, mock_session: MagicMock) -> None: - mock_session.get.return_value.json.return_value = _get_plans_response([PLAN_ID]) - result = client.get_plans(PROJECT_ID) - assert result[0].id == PLAN_ID - - def test_summary_project_id(self, client: MagicPlanClient, mock_session: MagicMock) -> None: - mock_session.get.return_value.json.return_value = _get_plans_response([PLAN_ID]) - result = client.get_plans(PROJECT_ID) - assert result[0].project_id == PROJECT_ID - - def test_summary_name(self, client: MagicPlanClient, mock_session: MagicMock) -> None: - mock_session.get.return_value.json.return_value = _get_plans_response([PLAN_ID]) - result = client.get_plans(PROJECT_ID) - assert result[0].name == "Plan 0" - - def test_returns_empty_list_when_no_plans(self, client: MagicPlanClient, mock_session: MagicMock) -> None: - mock_session.get.return_value.json.return_value = _get_plans_response([]) - result = client.get_plans(PROJECT_ID) - assert result == [] - - def test_raises_on_http_error(self, client: MagicPlanClient, mock_session: MagicMock) -> None: - mock_session.get.return_value.raise_for_status.side_effect = requests.HTTPError("401") - with pytest.raises(requests.HTTPError): - client.get_plans(PROJECT_ID) diff --git a/backend/magic_plan/tests/test_models.py b/backend/magic_plan/tests/test_models.py deleted file mode 100644 index 11163064..00000000 --- a/backend/magic_plan/tests/test_models.py +++ /dev/null @@ -1,50 +0,0 @@ -import pytest - -from backend.magic_plan.models import parse_displayable_fields - - -SECTION_TITLE_FIELD = { - "id": "measures.section.title", - "type_as_string": "sectionTitle", - "value": {"has_value": False, "value": ""}, -} - -CEILING_HEIGHT_FIELD = { - "id": "ceilingHeight", - "type_as_string": "distance", - "value": {"has_value": True, "value": "2.45"}, -} - -NO_VALUE_FIELD = { - "id": "label", - "type_as_string": "text", - "value": {"has_value": False, "value": ""}, -} - - -class TestParseDisplayableFields: - - def test_excludes_section_titles(self) -> None: - result = parse_displayable_fields([SECTION_TITLE_FIELD]) - assert result == {} - - def test_excludes_fields_without_value(self) -> None: - result = parse_displayable_fields([NO_VALUE_FIELD]) - assert result == {} - - def test_includes_field_with_value(self) -> None: - result = parse_displayable_fields([CEILING_HEIGHT_FIELD]) - assert "ceiling_height" in result - assert result["ceiling_height"] == "2.45" - - def test_snake_cases_camel_key(self) -> None: - result = parse_displayable_fields([CEILING_HEIGHT_FIELD]) - assert "ceiling_height" in result - assert "ceilingHeight" not in result - - def test_mixed_fields(self) -> None: - result = parse_displayable_fields([SECTION_TITLE_FIELD, CEILING_HEIGHT_FIELD, NO_VALUE_FIELD]) - assert result == {"ceiling_height": "2.45"} - - def test_empty_list(self) -> None: - assert parse_displayable_fields([]) == {} diff --git a/backend/magic_plan/tests/test_xml_parser.py b/backend/magic_plan/tests/test_xml_parser.py deleted file mode 100644 index 006d6918..00000000 --- a/backend/magic_plan/tests/test_xml_parser.py +++ /dev/null @@ -1,461 +0,0 @@ -import json -import math -import xml.etree.ElementTree as ET -from pathlib import Path - -import pytest - -from backend.magic_plan.models import MagicPlanXMLPlan -from backend.magic_plan.xml_parser import parse_magicplan_xml - -FIXTURES = Path(__file__).parent.parent - - -def _load(filename: str) -> str: - content = (FIXTURES / filename).read_text().strip() - try: - ET.fromstring(content) - return content - except ET.ParseError: - return json.loads(content) # type: ignore[return-value] - - -@pytest.fixture(scope="module") -def plan1() -> MagicPlanXMLPlan: - return parse_magicplan_xml(_load("xml_example.xml")) - - -@pytest.fixture(scope="module") -def plan2() -> MagicPlanXMLPlan: - return parse_magicplan_xml(_load("xml_example_2.xml")) - - -@pytest.fixture(scope="module") -def plan3() -> MagicPlanXMLPlan: - return parse_magicplan_xml(_load("xml_example_3.xml")) - - -# --------------------------------------------------------------------------- -# Plan-level attributes -# --------------------------------------------------------------------------- - -class TestPlanAttributes: - - def test_id(self, plan1: MagicPlanXMLPlan) -> None: - assert plan1.id == "b66bb427-33fe-4865-8d57-bad7d9d3f2e5" - - def test_uid(self, plan1: MagicPlanXMLPlan) -> None: - assert plan1.uid == "69e5fafc.0890b3ff" - - def test_name(self, plan1: MagicPlanXMLPlan) -> None: - assert plan1.name == "275 Carr Hill Rd NE9 5ND" - - def test_type(self, plan1: MagicPlanXMLPlan) -> None: - assert plan1.type == "0" - - def test_interior_wall_width(self, plan1: MagicPlanXMLPlan) -> None: - assert math.isclose(plan1.interior_wall_width, 0.12) - - def test_exterior_wall_width(self, plan1: MagicPlanXMLPlan) -> None: - assert math.isclose(plan1.exterior_wall_width, 0.25) - - def test_schematic_false(self, plan1: MagicPlanXMLPlan) -> None: - assert plan1.schematic is False - - def test_has_land_survey_address_false(self, plan1: MagicPlanXMLPlan) -> None: - assert plan1.has_land_survey_address is False - - def test_last_patch_identifier(self, plan1: MagicPlanXMLPlan) -> None: - assert plan1.last_patch_identifier == "0" - - def test_last_roll_identifier(self, plan1: MagicPlanXMLPlan) -> None: - assert plan1.last_roll_identifier == "0" - - -class TestPlanValues: - - def test_date(self, plan1: MagicPlanXMLPlan) -> None: - assert plan1.values["date"] == "2026-04-20" - - def test_statistics_area_of_height(self, plan1: MagicPlanXMLPlan) -> None: - assert plan1.values["statistics.areaOfHeight"] == "2.134" - - def test_statistics_basement_account(self, plan1: MagicPlanXMLPlan) -> None: - assert plan1.values["statistics.basement.account"] == "100" - - def test_statistics_exterior_walls(self, plan1: MagicPlanXMLPlan) -> None: - assert plan1.values["statistics.exteriorWalls"] == "0" - - def test_statistics_interior_walls(self, plan1: MagicPlanXMLPlan) -> None: - assert plan1.values["statistics.interiorWalls"] == "0" - - def test_plan2_date(self, plan2: MagicPlanXMLPlan) -> None: - assert plan2.values["date"] == "2026-04-16" - - -# --------------------------------------------------------------------------- -# Floors -# --------------------------------------------------------------------------- - -class TestFloors: - - def test_floor_count_plan1(self, plan1: MagicPlanXMLPlan) -> None: - assert len(plan1.floors) == 2 - - def test_floor_count_plan2(self, plan2: MagicPlanXMLPlan) -> None: - assert len(plan2.floors) == 1 - - def test_floor_count_plan3(self, plan3: MagicPlanXMLPlan) -> None: - assert len(plan3.floors) == 1 - - def test_ground_floor_name(self, plan1: MagicPlanXMLPlan) -> None: - assert plan1.floors[0].name == "Ground Floor" - - def test_first_floor_name(self, plan1: MagicPlanXMLPlan) -> None: - assert plan1.floors[1].name == "1st Floor" - - def test_ground_floor_type(self, plan1: MagicPlanXMLPlan) -> None: - assert plan1.floors[0].floor_type == "0" - - def test_upper_floor_type(self, plan1: MagicPlanXMLPlan) -> None: - assert plan1.floors[1].floor_type == "1" - - def test_floor_uid(self, plan1: MagicPlanXMLPlan) -> None: - assert plan1.floors[0].uid == "69e5fb20.4feef7ff" - - def test_ground_floor_area_without_walls(self, plan1: MagicPlanXMLPlan) -> None: - assert math.isclose(plan1.floors[0].area_without_walls, 40.20736) - - def test_ground_floor_area_with_walls(self, plan1: MagicPlanXMLPlan) -> None: - assert math.isclose(plan1.floors[0].area_with_walls, 48.40593) - - def test_ground_floor_area_with_interior_walls_only(self, plan1: MagicPlanXMLPlan) -> None: - assert math.isclose(plan1.floors[0].area_with_interior_walls_only, 40.67878) - - def test_floor_rotation(self, plan1: MagicPlanXMLPlan) -> None: - assert math.isclose(plan1.floors[0].rotation, 0.0, abs_tol=1e-9) - - def test_floor_compass_angle(self, plan1: MagicPlanXMLPlan) -> None: - assert math.isclose(plan1.floors[0].compass_angle, -1.0) - - -# --------------------------------------------------------------------------- -# Symbol instance -# --------------------------------------------------------------------------- - -class TestSymbolInstance: - - def test_symbol_instance_id(self, plan1: MagicPlanXMLPlan) -> None: - assert plan1.floors[0].symbol_instance.id == "floor" - - def test_symbol_instance_uid(self, plan1: MagicPlanXMLPlan) -> None: - assert plan1.floors[0].symbol_instance.uid == "69e5fb20.4feef7ff" - - def test_symbol_instance_symbol(self, plan1: MagicPlanXMLPlan) -> None: - assert plan1.floors[0].symbol_instance.symbol == "floor" - - def test_symbol_instance_parent_uid(self, plan1: MagicPlanXMLPlan) -> None: - assert plan1.floors[0].symbol_instance.parent_uid == "" - - def test_symbol_instance_ceiling_height_value(self, plan1: MagicPlanXMLPlan) -> None: - assert plan1.floors[0].symbol_instance.values["ceilingHeight"] == "2.323164" - - -# --------------------------------------------------------------------------- -# Rooms -# --------------------------------------------------------------------------- - -class TestRooms: - - def test_ground_floor_room_count(self, plan1: MagicPlanXMLPlan) -> None: - assert len(plan1.floors[0].rooms) == 2 - - def test_first_floor_room_count(self, plan1: MagicPlanXMLPlan) -> None: - assert len(plan1.floors[1].rooms) == 4 - - def test_plan3_room_count(self, plan3: MagicPlanXMLPlan) -> None: - assert len(plan3.floors[0].rooms) == 9 - - def test_room_type(self, plan1: MagicPlanXMLPlan) -> None: - assert plan1.floors[0].rooms[0].type == "Kitchen" - - def test_room_uid(self, plan1: MagicPlanXMLPlan) -> None: - assert plan1.floors[0].rooms[0].uid == "69e5fbc8.71027bff" - - def test_room_area(self, plan1: MagicPlanXMLPlan) -> None: - assert math.isclose(plan1.floors[0].rooms[0].area, 10.78332) - - def test_room_perimeter(self, plan1: MagicPlanXMLPlan) -> None: - assert math.isclose(plan1.floors[0].rooms[0].perimeter, 13.32812) - - def test_room_x(self, plan1: MagicPlanXMLPlan) -> None: - assert math.isclose(plan1.floors[0].rooms[0].x, 3.80616) - - def test_room_y(self, plan1: MagicPlanXMLPlan) -> None: - assert math.isclose(plan1.floors[0].rooms[0].y, 0.23162) - - def test_room_rotation(self, plan1: MagicPlanXMLPlan) -> None: - assert math.isclose(plan1.floors[0].rooms[0].rotation, 0.0, abs_tol=1e-9) - - def test_room_was_modified_false(self, plan1: MagicPlanXMLPlan) -> None: - assert plan1.floors[0].rooms[0].was_modified is False - - def test_room_was_modified_true(self, plan1: MagicPlanXMLPlan) -> None: - closet = plan1.floors[1].rooms[1] - assert closet.type == "Closet" - assert closet.was_modified is True - - def test_room_linked_room_0(self, plan1: MagicPlanXMLPlan) -> None: - assert plan1.floors[0].rooms[0].linked_room_0 == "-1" - - def test_room_linked_room_1(self, plan1: MagicPlanXMLPlan) -> None: - assert plan1.floors[0].rooms[0].linked_room_1 == "-1" - - def test_room_ceiling_height_value(self, plan1: MagicPlanXMLPlan) -> None: - assert plan1.floors[0].rooms[0].values["ceilingHeight"] == "2.323164" - - def test_room_label_value(self, plan2: MagicPlanXMLPlan) -> None: - dining = plan2.floors[0].rooms[1] - assert dining.type == "Dining Room" - assert dining.values["label"] == "Room" - - def test_room_no_label_when_absent(self, plan1: MagicPlanXMLPlan) -> None: - assert "label" not in plan1.floors[0].rooms[0].values - - -# --------------------------------------------------------------------------- -# Room points -# --------------------------------------------------------------------------- - -class TestRoomPoints: - - def test_kitchen_point_count(self, plan1: MagicPlanXMLPlan) -> None: - assert len(plan1.floors[0].rooms[0].points) == 4 - - def test_living_room_point_count(self, plan1: MagicPlanXMLPlan) -> None: - assert len(plan1.floors[0].rooms[1].points) == 8 - - def test_point_snapped_x(self, plan1: MagicPlanXMLPlan) -> None: - assert math.isclose(plan1.floors[0].rooms[0].points[0].snapped_x, -1.44357) - - def test_point_snapped_y(self, plan1: MagicPlanXMLPlan) -> None: - assert math.isclose(plan1.floors[0].rooms[0].points[0].snapped_y, -2.00846) - - def test_point_height(self, plan1: MagicPlanXMLPlan) -> None: - assert math.isclose(plan1.floors[0].rooms[0].points[0].height, 2.323164) - - def test_point_uid(self, plan1: MagicPlanXMLPlan) -> None: - assert plan1.floors[0].rooms[0].points[0].uid == "69e5fbc8.710363ff" - - def test_point_values_empty_when_absent(self, plan1: MagicPlanXMLPlan) -> None: - assert plan1.floors[0].rooms[0].points[0].values == {} - - -# --------------------------------------------------------------------------- -# Doors (floorRoom context) -# --------------------------------------------------------------------------- - -class TestDoors: - - def test_kitchen_door_count(self, plan1: MagicPlanXMLPlan) -> None: - assert len(plan1.floors[0].rooms[0].doors) == 5 - - def test_corridor_door_count(self, plan3: MagicPlanXMLPlan) -> None: - assert len(plan3.floors[0].rooms[0].doors) == 9 - - def test_door_wall_point_index(self, plan1: MagicPlanXMLPlan) -> None: - assert plan1.floors[0].rooms[0].doors[0].wall_point_index == 3 - - def test_door_type(self, plan1: MagicPlanXMLPlan) -> None: - assert plan1.floors[0].rooms[0].doors[0].type == "4" - - def test_door_u(self, plan1: MagicPlanXMLPlan) -> None: - assert math.isclose(plan1.floors[0].rooms[0].doors[0].u, 0.47585) - - def test_door_snapped_width(self, plan1: MagicPlanXMLPlan) -> None: - assert math.isclose(plan1.floors[0].rooms[0].doors[0].snapped_width, 1.82394) - - def test_door_snapped_height(self, plan1: MagicPlanXMLPlan) -> None: - assert math.isclose(plan1.floors[0].rooms[0].doors[0].snapped_height, 2.08411) - - def test_door_snapped_orientation(self, plan1: MagicPlanXMLPlan) -> None: - assert plan1.floors[0].rooms[0].doors[0].snapped_orientation == 3 - - def test_door_twin_wall_item_uid_present(self, plan1: MagicPlanXMLPlan) -> None: - assert plan1.floors[0].rooms[0].doors[0].twin_wall_item_uid == "69e5fbc8.74614fff" - - def test_door_twin_wall_item_uid_absent(self, plan2: MagicPlanXMLPlan) -> None: - assert plan2.floors[0].rooms[0].doors[0].twin_wall_item_uid is None - - def test_door_symbol_instance(self, plan1: MagicPlanXMLPlan) -> None: - assert plan1.floors[0].rooms[0].doors[0].symbol_instance == "W-0-0" - - def test_door_inset_x(self, plan1: MagicPlanXMLPlan) -> None: - assert math.isclose(plan1.floors[0].rooms[0].doors[0].inset_x, 0.0, abs_tol=1e-9) - - -# --------------------------------------------------------------------------- -# Windows (floorRoom context) -# --------------------------------------------------------------------------- - -class TestWindows: - - def test_kitchen_window_count(self, plan1: MagicPlanXMLPlan) -> None: - assert len(plan1.floors[0].rooms[0].windows) == 1 - - def test_corridor_window_count_zero(self, plan3: MagicPlanXMLPlan) -> None: - corridor = plan3.floors[0].rooms[0] - assert corridor.type == "Corridor" - assert len(corridor.windows) == 0 - - def test_window_wall_point_index(self, plan1: MagicPlanXMLPlan) -> None: - assert plan1.floors[0].rooms[0].windows[0].wall_point_index == 1 - - def test_window_type(self, plan1: MagicPlanXMLPlan) -> None: - assert plan1.floors[0].rooms[0].windows[0].type == "1" - - def test_window_u(self, plan1: MagicPlanXMLPlan) -> None: - assert math.isclose(plan1.floors[0].rooms[0].windows[0].u, 0.68463) - - def test_window_snapped_width(self, plan1: MagicPlanXMLPlan) -> None: - assert math.isclose(plan1.floors[0].rooms[0].windows[0].snapped_width, 1.71972) - - def test_window_snapped_height(self, plan1: MagicPlanXMLPlan) -> None: - assert math.isclose(plan1.floors[0].rooms[0].windows[0].snapped_height, 0.94940) - - def test_window_symbol_instance(self, plan1: MagicPlanXMLPlan) -> None: - assert plan1.floors[0].rooms[0].windows[0].symbol_instance == "W-0-2" - - -# --------------------------------------------------------------------------- -# Furniture (room-level) -# --------------------------------------------------------------------------- - -class TestRoomFurniture: - - def test_kitchen_furniture_count(self, plan1: MagicPlanXMLPlan) -> None: - assert len(plan1.floors[0].rooms[0].furniture) == 5 - - def test_bathroom_furniture_count_zero(self, plan1: MagicPlanXMLPlan) -> None: - bathroom = plan1.floors[1].rooms[0] - assert bathroom.type == "Bathroom" - assert len(bathroom.furniture) == 0 - - def test_furniture_type(self, plan1: MagicPlanXMLPlan) -> None: - assert isinstance(plan1.floors[0].rooms[0].furniture[0].type, str) - - def test_furniture_symbol_instance(self, plan1: MagicPlanXMLPlan) -> None: - assert isinstance(plan1.floors[0].rooms[0].furniture[0].symbol_instance, str) - - -# --------------------------------------------------------------------------- -# Floor-level furniture (example 3) -# --------------------------------------------------------------------------- - -class TestFloorLevelFurniture: - - def test_floor_furniture_count(self, plan3: MagicPlanXMLPlan) -> None: - assert len(plan3.floors[0].furniture) == 1 - - def test_floor_furniture_x(self, plan3: MagicPlanXMLPlan) -> None: - assert math.isclose(plan3.floors[0].furniture[0].x, -2.09376) - - def test_floor_furniture_y(self, plan3: MagicPlanXMLPlan) -> None: - assert math.isclose(plan3.floors[0].furniture[0].y, 3.13664) - - def test_floor_furniture_absent_plan1(self, plan1: MagicPlanXMLPlan) -> None: - assert len(plan1.floors[0].furniture) == 0 - - -# --------------------------------------------------------------------------- -# Main dimensions -# --------------------------------------------------------------------------- - -class TestMainDimensions: - - def test_main_dimension_count(self, plan1: MagicPlanXMLPlan) -> None: - assert len(plan1.floors[0].rooms[0].main_dimensions) == 2 - - def test_main_dimension_from_point(self, plan1: MagicPlanXMLPlan) -> None: - assert isinstance(plan1.floors[0].rooms[0].main_dimensions[0].from_point, int) - - def test_main_dimension_is_set(self, plan1: MagicPlanXMLPlan) -> None: - assert isinstance(plan1.floors[0].rooms[0].main_dimensions[0].is_set, bool) - - -# --------------------------------------------------------------------------- -# Exploded section -# --------------------------------------------------------------------------- - -class TestExploded: - - def test_exploded_wall_count_ground_floor(self, plan1: MagicPlanXMLPlan) -> None: - assert len(plan1.floors[0].exploded.walls) == 11 - - def test_exploded_door_count_ground_floor(self, plan1: MagicPlanXMLPlan) -> None: - assert len(plan1.floors[0].exploded.doors) == 6 - - def test_exploded_window_count_ground_floor(self, plan1: MagicPlanXMLPlan) -> None: - assert len(plan1.floors[0].exploded.windows) == 2 - - def test_exploded_wall_type(self, plan1: MagicPlanXMLPlan) -> None: - assert plan1.floors[0].exploded.walls[0].wall_type == "exterior" - - def test_exploded_wall_points(self, plan1: MagicPlanXMLPlan) -> None: - pts = plan1.floors[0].exploded.walls[0].points - assert len(pts) == 2 - assert math.isclose(pts[0].x, 2.363) - assert math.isclose(pts[0].y, -1.778) - assert math.isclose(pts[0].height, 2.323164) - - def test_exploded_door_x1(self, plan1: MagicPlanXMLPlan) -> None: - assert math.isclose(plan1.floors[0].exploded.doors[0].x1, 5.24973) - - def test_exploded_door_y1(self, plan1: MagicPlanXMLPlan) -> None: - assert math.isclose(plan1.floors[0].exploded.doors[0].y1, -1.333153) - - def test_exploded_door_width(self, plan1: MagicPlanXMLPlan) -> None: - assert math.isclose(plan1.floors[0].exploded.doors[0].width, 0.773) - - def test_exploded_door_height(self, plan1: MagicPlanXMLPlan) -> None: - assert math.isclose(plan1.floors[0].exploded.doors[0].height, 2.02225) - - def test_exploded_door_symbol_instance(self, plan1: MagicPlanXMLPlan) -> None: - assert plan1.floors[0].exploded.doors[0].symbol_instance == "W-0-1" - - def test_exploded_window_x1(self, plan1: MagicPlanXMLPlan) -> None: - assert math.isclose(plan1.floors[0].exploded.windows[0].x1, 5.24973) - - def test_exploded_window_width(self, plan1: MagicPlanXMLPlan) -> None: - assert math.isclose(plan1.floors[0].exploded.windows[0].width, 1.71972) - - def test_exploded_window_height(self, plan1: MagicPlanXMLPlan) -> None: - assert math.isclose(plan1.floors[0].exploded.windows[0].height, 0.949396) - - def test_exploded_window_symbol_instance(self, plan1: MagicPlanXMLPlan) -> None: - assert plan1.floors[0].exploded.windows[0].symbol_instance == "W-0-2" - - def test_plan3_exploded_wall_count(self, plan3: MagicPlanXMLPlan) -> None: - assert len(plan3.floors[0].exploded.walls) == 33 - - def test_plan3_exploded_door_count(self, plan3: MagicPlanXMLPlan) -> None: - assert len(plan3.floors[0].exploded.doors) == 12 - - def test_plan3_exploded_window_count(self, plan3: MagicPlanXMLPlan) -> None: - assert len(plan3.floors[0].exploded.windows) == 5 - - -# --------------------------------------------------------------------------- -# Interior room points -# --------------------------------------------------------------------------- - -class TestInteriorRoomPoints: - - def test_interior_room_floor_count_plan1(self, plan1: MagicPlanXMLPlan) -> None: - assert len(plan1.interior_room_floors) == 2 - - def test_interior_room_floor_count_plan2(self, plan2: MagicPlanXMLPlan) -> None: - assert len(plan2.interior_room_floors) == 1 - - def test_interior_room_floor_uid_matches(self, plan1: MagicPlanXMLPlan) -> None: - assert plan1.interior_room_floors[0].uid == plan1.floors[0].uid diff --git a/backend/magic_plan/xml_example.xml b/backend/magic_plan/xml_example.xml deleted file mode 100644 index 28088fc8..00000000 --- a/backend/magic_plan/xml_example.xml +++ /dev/null @@ -1,985 +0,0 @@ - - - 2026-04-20 - 2.134 - 100 - 0 - 0 - - - Ground Floor - - - 2.323164 - - - - - doors - 2.084107 - - - - - doors - 2.02225 - - - - - doors - 1.124639 - 0.949396 - - - - - 0.397258 - 0.746826 - furniture - 1.279293 - 0.746826 - 0.677733 - - - - - 0.368615 - 0.723145 - furniture - 1.311519 - 0.723145 - 0.398439 - - - - - 0.604444 - 0.1 - furniture - 0.851311 - 0.1 - 0.601075 - - - - - furniture - - - - - appliances - - - - - appliances - - - - - appliances - - - - - hvac - 0.1500 - - - - - doors - 2.084107 - - - - - doors - 2.006357 - - - - - doors - 1.00597 - 1.026259 - - - - - appliances - - - - - furniture - - - - - hvac - 0.0000 - - - - - hvac - 0.1500 - - - - - hvac - 0.1500 - - - - - 2.323164 - - - - - - - - - - - - - - - - - - - - - - 3.285283 - - - - - - - - - - - - - - - - - - - - - - - - - exterior - - - - - exterior - - - - - exterior - - - - - exterior - - - - - exterior - - - - - exterior - - - - - interior - - - - - exterior - - - - - exterior - - - - - exterior - - - - - exterior - - - - - - - - - - - - - - - - - - - - - - - 1st Floor - - - 2.382467 - - - - - doors - 2.074757 - - - - - plumbing - 1.5000 - - - - - doors - 2.081704 - - - - - doors - 1.227592 - 0.88319 - - - - - hvac - 0.1500 - - - - - 677d01685458a - - - - - doors - 2.081704 - - - - - doors - 2.074757 - - - - - doors - 2.195494 - - - - - doors - 2.195494 - - - - - doors - 0.915306 - 1.180143 - - - - - 0.1 - 0.676758 - appliances - 1.399472 - 0.676758 - 1.253952 - - - - - hvac - 0.1500 - - - - - 2.382467 - - - - - - - - - - - - - 2.382467 - - - - - - - - - - - - - - - 2.382467 - - - - - - - - - - - - - - 2.382467 - - - - - - - - - - - - - - - - - - - exterior - - - - - exterior - - - - - exterior - - - - - exterior - - - - - interior - - - - - interior - - - - - interior - - - - - interior - - - - - exterior - - - - - exterior - - - - - exterior - - - - - exterior - - - - - exterior - - - - - interior - - - - - exterior - - - - - - - - - - - - - - - - Ground Floor - - - 2.323164 - - - - - doors - 2.084107 - - - - - doors - 2.02225 - - - - - doors - 1.124639 - 0.949396 - - - - - 0.397258 - 0.746826 - furniture - 1.279293 - 0.746826 - 0.677733 - - - - - 0.368615 - 0.723145 - furniture - 1.311519 - 0.723145 - 0.398439 - - - - - 0.604444 - 0.1 - furniture - 0.851311 - 0.1 - 0.601075 - - - - - furniture - - - - - appliances - - - - - appliances - - - - - appliances - - - - - hvac - 0.1500 - - - - - doors - 2.084107 - - - - - doors - 2.006357 - - - - - doors - 1.00597 - 1.026259 - - - - - appliances - - - - - furniture - - - - - hvac - 0.0000 - - - - - hvac - 0.1500 - - - - - hvac - 0.1500 - - - - - 2.323164 - - - - - - - - - - - - - - - - - - - - - - 3.285283 - - - - - - - - - - - - - - - - - - - - - - - - - exterior - - - - - exterior - - - - - exterior - - - - - exterior - - - - - exterior - - - - - exterior - - - - - interior - - - - - exterior - - - - - exterior - - - - - exterior - - - - - exterior - - - - - - - - - - - - - - - - - - - - - - - 1st Floor - - - 2.382467 - - - - - doors - 2.074757 - - - - - plumbing - 1.5000 - - - - - doors - 2.081704 - - - - - doors - 1.227592 - 0.88319 - - - - - hvac - 0.1500 - - - - - 677d01685458a - - - - - doors - 2.081704 - - - - - doors - 2.074757 - - - - - doors - 2.195494 - - - - - doors - 2.195494 - - - - - doors - 0.915306 - 1.180143 - - - - - 0.1 - 0.676758 - appliances - 1.399472 - 0.676758 - 1.253952 - - - - - hvac - 0.1500 - - - - - 2.382467 - - - - - - - - - - - - - 2.382467 - - - - - - - - - - - - - - - 2.382467 - - - - - - - - - - - - - - 2.382467 - - - - - - - - - - - - - - - - - - - exterior - - - - - exterior - - - - - exterior - - - - - exterior - - - - - interior - - - - - interior - - - - - interior - - - - - interior - - - - - exterior - - - - - exterior - - - - - exterior - - - - - exterior - - - - - exterior - - - - - interior - - - - - exterior - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/backend/magic_plan/xml_example_2.xml b/backend/magic_plan/xml_example_2.xml deleted file mode 100644 index b4014e86..00000000 --- a/backend/magic_plan/xml_example_2.xml +++ /dev/null @@ -1 +0,0 @@ -"\n2026-04-162.13410000Ground Floor2.423333doors2.095132doors2.116748doors2.07892doors1.0413441.129445appliancesappliancesdoors1.868133doors0.8742110.939139doors0.7959061.0349912.4233332.415RoomexteriorexteriorexteriorexteriorexteriorexteriorexteriorexteriorexteriorexteriorexteriorexteriorGround Floor2.423333doors2.095132doors2.116748doors2.07892doors1.0413441.129445appliancesappliancesdoors1.868133doors0.8742110.939139doors0.7959061.0349912.4233332.415Roomexteriorexteriorexteriorexteriorexteriorexteriorexteriorexteriorexteriorexteriorexteriorexterior\n" \ No newline at end of file diff --git a/backend/magic_plan/xml_example_3.xml b/backend/magic_plan/xml_example_3.xml deleted file mode 100644 index 22dcd696..00000000 --- a/backend/magic_plan/xml_example_3.xml +++ /dev/null @@ -1 +0,0 @@ -"\n2026-04-162.13410000Ground Floor2.366039677d01685458am1mm2doorsm32.046953m1mm2doorsm32.049024mmm2doorsm32.08851m1mm2doorsm31.998668m1mm2doorsm32.099428m1mm2doorsm32.086379m1mm2doorsm32.118055m1mm2doorsm32.123336m1mm2doorsm31.986039677d01685458a677d01685458ammm2doorsm32.08851m1mm2doorsm30.9267931.19895677d01685458ammm2hvacm30.0000m1mm2doorsm31.998668m1mm2doorsm30.9400381.190655677d01685458ammm2hvacm30.0000m1mm2doorsm32.020562m1mm2doorsm32.086379677d01685458adoors2.01773m1mm2doorsm32.020562m1mm2doorsm32.004822677d01685458adoors2.01773m1mm2doorsm32.046953m1mm2doorsm30.8718441.200662677d01685458ammm2hvacm30.0000m1mm2doorsm32.099428m1mm2doorsm31.1390810.932646677d01685458a677d01685458am1mm2doorsm32.118055m1mm2doorsm32.123336677d01685458am1mm2doorsm32.049024m1mm2doorsm31.0796020.985677677d01685458ammm2hvacm30.0000677d01685458a2.3660392.366039112.366039112.3660392.3660392.3660392.3660392.3660392.366039exteriorexteriorexteriorexteriorexteriorexteriorexteriorinteriorinteriorinteriorinteriorinteriorexteriorexteriorexteriorexteriorexteriorexteriorinteriorinteriorinteriorinteriorinteriorinteriorinteriorinteriorinteriorinteriorinteriorinteriorinteriorinteriorinteriorGround Floor2.366039677d01685458am1mm2doorsm32.046953m1mm2doorsm32.049024mmm2doorsm32.08851m1mm2doorsm31.998668m1mm2doorsm32.099428m1mm2doorsm32.086379m1mm2doorsm32.118055m1mm2doorsm32.123336m1mm2doorsm31.986039677d01685458a677d01685458ammm2doorsm32.08851m1mm2doorsm30.9267931.19895677d01685458ammm2hvacm30.0000m1mm2doorsm31.998668m1mm2doorsm30.9400381.190655677d01685458ammm2hvacm30.0000m1mm2doorsm32.020562m1mm2doorsm32.086379677d01685458adoors2.01773m1mm2doorsm32.020562m1mm2doorsm32.004822677d01685458adoors2.01773m1mm2doorsm32.046953m1mm2doorsm30.8718441.200662677d01685458ammm2hvacm30.0000m1mm2doorsm32.099428m1mm2doorsm31.1390810.932646677d01685458a677d01685458am1mm2doorsm32.118055m1mm2doorsm32.123336677d01685458am1mm2doorsm32.049024m1mm2doorsm31.0796020.985677677d01685458ammm2hvacm30.0000677d01685458a2.3660392.366039112.366039112.3660392.3660392.3660392.3660392.3660392.366039exteriorexteriorexteriorexteriorexteriorexteriorexteriorinteriorinteriorinteriorinteriorinteriorexteriorexteriorexteriorexteriorexteriorexteriorinteriorinteriorinteriorinteriorinteriorinteriorinteriorinteriorinteriorinteriorinteriorinteriorinteriorinteriorinterior\n" \ No newline at end of file diff --git a/backend/magic_plan/xml_parser.py b/backend/magic_plan/xml_parser.py deleted file mode 100644 index bd99c913..00000000 --- a/backend/magic_plan/xml_parser.py +++ /dev/null @@ -1,224 +0,0 @@ -import xml.etree.ElementTree as ET - -from backend.magic_plan.models import ( - MagicPlanXMLDoor, - MagicPlanXMLExploded, - MagicPlanXMLExplodedOpening, - MagicPlanXMLExplodedWall, - MagicPlanXMLFloor, - MagicPlanXMLFurniture, - MagicPlanXMLMainDimension, - MagicPlanXMLPlan, - MagicPlanXMLRoom, - MagicPlanXMLRoomPoint, - MagicPlanXMLSymbolInstance, - MagicPlanXMLWallPoint, - MagicPlanXMLWindow, -) - - -def _values(el: ET.Element) -> dict[str, str]: - return {v.get("key", ""): (v.text or "") for v in el.findall("values/value")} - - -def _parse_room_point(el: ET.Element) -> MagicPlanXMLRoomPoint: - return MagicPlanXMLRoomPoint( - snapped_x=float(el.get("snappedX", "0")), - snapped_y=float(el.get("snappedY", "0")), - height=float(el.get("height", "0")), - uid=el.get("uid", ""), - values=_values(el), - ) - - -def _parse_wall_point(el: ET.Element) -> MagicPlanXMLWallPoint: - return MagicPlanXMLWallPoint( - x=float(el.get("x", "0")), - y=float(el.get("y", "0")), - height=float(el.get("height", "0")), - ) - - -def _parse_door(el: ET.Element) -> MagicPlanXMLDoor: - return MagicPlanXMLDoor( - wall_point_index=int(el.get("point", "0")), - type=el.get("type", ""), - u=float(el.get("u", "0")), - width=float(el.get("width", "0")), - depth=float(el.get("depth", "0")), - height=float(el.get("height", "0")), - orientation=int(el.get("orientation", "0")), - snapped_type=el.get("snappedType", ""), - snapped_position=float(el.get("snappedPosition", "0")), - snapped_width=float(el.get("snappedWidth", "0")), - snapped_depth=float(el.get("snappedDepth", "0")), - snapped_height=float(el.get("snappedHeight", "0")), - snapped_orientation=int(el.get("snappedOrientation", "0")), - inset_x=float(el.get("insetX", "0")), - inset_y=float(el.get("insetY", "0")), - inset_z=float(el.get("insetZ", "0")), - symbol_instance=el.get("symbolInstance", ""), - twin_wall_item_uid=el.get("twinWallItemUid"), - ) - - -def _parse_window(el: ET.Element) -> MagicPlanXMLWindow: - return MagicPlanXMLWindow( - wall_point_index=int(el.get("point", "0")), - type=el.get("type", ""), - u=float(el.get("u", "0")), - width=float(el.get("width", "0")), - depth=float(el.get("depth", "0")), - height=float(el.get("height", "0")), - orientation=int(el.get("orientation", "0")), - snapped_type=el.get("snappedType", ""), - snapped_position=float(el.get("snappedPosition", "0")), - snapped_width=float(el.get("snappedWidth", "0")), - snapped_depth=float(el.get("snappedDepth", "0")), - snapped_height=float(el.get("snappedHeight", "0")), - snapped_orientation=int(el.get("snappedOrientation", "0")), - inset_x=float(el.get("insetX", "0")), - inset_y=float(el.get("insetY", "0")), - inset_z=float(el.get("insetZ", "0")), - symbol_instance=el.get("symbolInstance", ""), - ) - - -def _parse_exploded_opening(el: ET.Element) -> MagicPlanXMLExplodedOpening: - return MagicPlanXMLExplodedOpening( - type=el.get("type", ""), - x1=float(el.get("x1", "0")), - y1=float(el.get("y1", "0")), - x2=float(el.get("x2", "0")), - y2=float(el.get("y2", "0")), - width=float(el.get("width", "0")), - depth=float(el.get("depth", "0")), - height=float(el.get("height", "0")), - inset_x=float(el.get("insetX", "0")), - inset_y=float(el.get("insetY", "0")), - orientation=int(el.get("orientation", "0")), - symbol_instance=el.get("symbolInstance", ""), - ) - - -def _parse_furniture(el: ET.Element) -> MagicPlanXMLFurniture: - return MagicPlanXMLFurniture( - type=el.get("type", ""), - x=float(el.get("x", "0")), - y=float(el.get("y", "0")), - snapped_x=float(el.get("snappedX", "0")), - snapped_y=float(el.get("snappedY", "0")), - angle=float(el.get("angle", "0")), - width=float(el.get("width", "0")), - depth=float(el.get("depth", "0")), - height=float(el.get("height", "0")), - snapped_width=float(el.get("snappedWidth", "0")), - snapped_depth=float(el.get("snappedDepth", "0")), - snapped_height=float(el.get("snappedHeight", "0")), - size_lock_0=el.get("sizeLock0", ""), - size_lock_1=el.get("sizeLock1", ""), - size_lock_2=el.get("sizeLock2", ""), - symbol_instance=el.get("symbolInstance", ""), - ) - - -def _parse_main_dimension(el: ET.Element) -> MagicPlanXMLMainDimension: - return MagicPlanXMLMainDimension( - from_point=int(el.get("from", "0")), - to_point=int(el.get("to", "0")), - dir_x=float(el.get("dir.x", "0")), - dir_y=float(el.get("dir.y", "0")), - value=float(el.get("value", "0")), - actual_value=float(el.get("actualValue", "0")), - is_set=el.get("isSet", "0") == "1", - ) - - -def _parse_exploded_wall(el: ET.Element) -> MagicPlanXMLExplodedWall: - type_el = el.find("type") - return MagicPlanXMLExplodedWall( - wall_type=(type_el.text or "") if type_el is not None else "", - points=[_parse_wall_point(p) for p in el.findall("point")], - ) - - -def _parse_exploded(el: ET.Element) -> MagicPlanXMLExploded: - return MagicPlanXMLExploded( - walls=[_parse_exploded_wall(w) for w in el.findall("wall")], - doors=[_parse_exploded_opening(d) for d in el.findall("door")], - windows=[_parse_exploded_opening(w) for w in el.findall("window")], - furniture=[_parse_furniture(f) for f in el.findall("furniture")], - ) - - -def _parse_symbol_instance(el: ET.Element) -> MagicPlanXMLSymbolInstance: - return MagicPlanXMLSymbolInstance( - id=el.get("id", ""), - uid=el.get("uid", ""), - parent_uid=el.get("parentUid", ""), - symbol=el.get("symbol", ""), - values=_values(el), - ) - - -def _parse_floor(el: ET.Element) -> MagicPlanXMLFloor: - si_el = el.find("symbolInstance") - exploded_el = el.find("exploded") - return MagicPlanXMLFloor( - uid=el.get("uid", ""), - name=el.findtext("name") or "", - floor_type=el.get("floorType", "0"), - rotation=float(el.get("rotation", "0")), - compass_angle=float(el.get("compassAngle", "0")), - area_without_walls=float(el.get("areaWithoutWalls", "0")), - area_with_interior_walls_only=float(el.get("areaWithInteriorWallsOnly", "0")), - area_with_walls=float(el.get("areaWithWalls", "0")), - symbol_instance=_parse_symbol_instance(si_el) if si_el is not None - else MagicPlanXMLSymbolInstance(id="", uid="", parent_uid="", symbol="", values={}), - rooms=[_parse_room(r) for r in el.findall("floorRoom")], - furniture=[_parse_furniture(f) for f in el.findall("furniture")], - exploded=_parse_exploded(exploded_el) if exploded_el is not None - else MagicPlanXMLExploded(walls=[], doors=[], windows=[], furniture=[]), - ) - - -def _parse_room(el: ET.Element) -> MagicPlanXMLRoom: - return MagicPlanXMLRoom( - uid=el.get("uid", ""), - type=el.get("type", ""), - x=float(el.get("x", "0")), - y=float(el.get("y", "0")), - rotation=float(el.get("rotation", "0")), - was_modified=el.get("wasModified", "0") == "1", - linked_room_0=el.get("linkedRoom0", "-1"), - linked_room_1=el.get("linkedRoom1", "-1"), - area=float(el.get("area", "0")), - perimeter=float(el.get("perimeter", "0")), - values=_values(el), - points=[_parse_room_point(p) for p in el.findall("point")], - doors=[_parse_door(d) for d in el.findall("door")], - windows=[_parse_window(w) for w in el.findall("window")], - furniture=[_parse_furniture(f) for f in el.findall("furniture")], - main_dimensions=[_parse_main_dimension(d) for d in el.findall("mainDimension")], - ) - - -def parse_magicplan_xml(xml_str: str) -> MagicPlanXMLPlan: - root = ET.fromstring(xml_str) - irp_el = root.find("interiorRoomPoints") - return MagicPlanXMLPlan( - id=root.get("id", ""), - uid=root.get("uid", ""), - name=root.get("name", ""), - type=root.get("type", ""), - interior_wall_width=float(root.get("interiorWallWidth", "0")), - exterior_wall_width=float(root.get("exteriorWallWidth", "0")), - schematic=root.get("schematic", "0") == "1", - has_land_survey_address=root.get("hasLandSurveyAddress", "0") == "1", - last_patch_identifier=root.get("lastPatchIdentifier", ""), - last_roll_identifier=root.get("lastRollIdentifier", ""), - values=_values(root), - floors=[_parse_floor(f) for f in root.findall("floor")], - interior_room_floors=[_parse_floor(f) for f in irp_el.findall("floor")] - if irp_el is not None else [], - ) diff --git a/backend/magic_plan/xml_schema.py b/backend/magic_plan/xml_schema.py deleted file mode 100644 index add2292b..00000000 --- a/backend/magic_plan/xml_schema.py +++ /dev/null @@ -1,48 +0,0 @@ -# MagicPlan Exchange XML Schema Reference -# Derived from xml_example.xml and https://apidocs.magicplan.app/guide/basic-concepts/plan-exchange-xml-format -# -# -# attrs: name, id, uid, type, interiorWallWidth (m), exteriorWallWidth (m), -# schematic, hasLandSurveyAddress, lastPatchIdentifier, lastRollIdentifier -# children: , +, -# -# (plan-level metadata) -# children: text -# known keys: date, statistics.areaOfHeight, statistics.basement.account, -# statistics.exteriorWalls, statistics.interiorWalls -# -# -# attrs: uid, floorType (0=ground 1=upper), rotation, compassAngle, -# areaWithoutWalls (m²), areaWithInteriorWallsOnly (m²), areaWithWalls (m²) -# children: , , +, -# -# -# attrs: type (room label e.g. "Kitchen"), uid, x, y, rotation, -# wasModified, linkedRoom0, linkedRoom1, area (m²), perimeter (m) -# children: (key: ceilingHeight m), +, *, *, *, * -# -# (room corner polygon) -# attrs: snappedX (m), snappedY (m), height (m), uid -# -# -# attrs: point, type, u, width (m), depth (m), height (m), orientation, -# snappedType, snappedPosition, snappedWidth, snappedDepth, snappedHeight, snappedOrientation, -# insetX, insetY, insetZ, twinWallItemUid, symbolInstance -# -# -# attrs: point, type, u, width (m), depth (m), height (m), orientation, -# snappedType, snappedPosition, snappedWidth, snappedDepth, snappedHeight, snappedOrientation, -# insetX, insetY, insetZ, symbolInstance -# -# (wall geometry per floor) -# children: , , , -# children: , (text: "exterior" | "interior") -# -# -# children: (same structure as top-level floor, interior wall room shapes) -# -# All distances in metres. All areas in m². - -FLOOR_TYPE_GROUND = "0" -FLOOR_TYPE_UPPER = "1" -FLOOR_TYPE_BASEMENT = "2"