mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-08 11:17:27 +00:00
start mapping json
This commit is contained in:
parent
9e7d8c004f
commit
ff67297646
7 changed files with 137258 additions and 231 deletions
|
|
@ -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
|
||||
|
|
|
|||
136742
backend/magic_plan/magicplan_api_plan_response_example.json
Normal file
136742
backend/magic_plan/magicplan_api_plan_response_example.json
Normal file
File diff suppressed because one or more lines are too long
|
|
@ -1,18 +1,40 @@
|
|||
import re
|
||||
from dataclasses import dataclass
|
||||
from typing import Optional
|
||||
from typing import Any, Optional
|
||||
|
||||
|
||||
def _camel_to_snake(name: str) -> str:
|
||||
return re.sub(r"(?<!^)(?=[A-Z])", "_", name).lower()
|
||||
|
||||
|
||||
def parse_displayable_fields(fields: list[dict[str, Any]]) -> 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 MagicPlanRoomPoint:
|
||||
class MagicPlanXMLRoomPoint:
|
||||
snapped_x: float
|
||||
snapped_y: float
|
||||
height: float
|
||||
uid: str
|
||||
values: dict[str, str] # e.g. loadBearingWall, addElevationToReport
|
||||
values: dict[str, str]
|
||||
|
||||
|
||||
@dataclass
|
||||
class MagicPlanWallPoint:
|
||||
class MagicPlanXMLWallPoint:
|
||||
"""Point in <exploded><wall> — absolute coords, no uid or values."""
|
||||
|
||||
x: float
|
||||
|
|
@ -21,21 +43,21 @@ class MagicPlanWallPoint:
|
|||
|
||||
|
||||
@dataclass
|
||||
class MagicPlanDoor:
|
||||
class MagicPlanXMLDoor:
|
||||
"""Door in <floorRoom> context — wall-relative position."""
|
||||
|
||||
wall_point_index: int
|
||||
type: str
|
||||
u: float
|
||||
width: float # m
|
||||
depth: float # m
|
||||
height: float # m
|
||||
width: float
|
||||
depth: float
|
||||
height: float
|
||||
orientation: int
|
||||
snapped_type: str
|
||||
snapped_position: float
|
||||
snapped_width: float # m
|
||||
snapped_depth: float # m
|
||||
snapped_height: float # m
|
||||
snapped_width: float
|
||||
snapped_depth: float
|
||||
snapped_height: float
|
||||
snapped_orientation: int
|
||||
inset_x: float
|
||||
inset_y: float
|
||||
|
|
@ -45,21 +67,21 @@ class MagicPlanDoor:
|
|||
|
||||
|
||||
@dataclass
|
||||
class MagicPlanWindow:
|
||||
class MagicPlanXMLWindow:
|
||||
"""Window in <floorRoom> context — wall-relative position."""
|
||||
|
||||
wall_point_index: int
|
||||
type: str
|
||||
u: float
|
||||
width: float # m
|
||||
depth: float # m
|
||||
height: float # m
|
||||
width: float
|
||||
depth: float
|
||||
height: float
|
||||
orientation: int
|
||||
snapped_type: str
|
||||
snapped_position: float
|
||||
snapped_width: float # m
|
||||
snapped_depth: float # m
|
||||
snapped_height: float # m
|
||||
snapped_width: float
|
||||
snapped_depth: float
|
||||
snapped_height: float
|
||||
snapped_orientation: int
|
||||
inset_x: float
|
||||
inset_y: float
|
||||
|
|
@ -68,7 +90,7 @@ class MagicPlanWindow:
|
|||
|
||||
|
||||
@dataclass
|
||||
class MagicPlanExplodedOpening:
|
||||
class MagicPlanXMLExplodedOpening:
|
||||
"""Door or window in <exploded> context — absolute coords, no snapped* fields."""
|
||||
|
||||
type: str
|
||||
|
|
@ -76,9 +98,9 @@ class MagicPlanExplodedOpening:
|
|||
y1: float
|
||||
x2: float
|
||||
y2: float
|
||||
width: float # m
|
||||
depth: float # m
|
||||
height: float # m
|
||||
width: float
|
||||
depth: float
|
||||
height: float
|
||||
inset_x: float
|
||||
inset_y: float
|
||||
orientation: int
|
||||
|
|
@ -86,19 +108,19 @@ class MagicPlanExplodedOpening:
|
|||
|
||||
|
||||
@dataclass
|
||||
class MagicPlanFurniture:
|
||||
class MagicPlanXMLFurniture:
|
||||
type: str
|
||||
x: float
|
||||
y: float
|
||||
snapped_x: float
|
||||
snapped_y: float
|
||||
angle: float
|
||||
width: float # m
|
||||
depth: float # m
|
||||
height: float # m
|
||||
snapped_width: float # m
|
||||
snapped_depth: float # m
|
||||
snapped_height: float # m
|
||||
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
|
||||
|
|
@ -106,7 +128,7 @@ class MagicPlanFurniture:
|
|||
|
||||
|
||||
@dataclass
|
||||
class MagicPlanMainDimension:
|
||||
class MagicPlanXMLMainDimension:
|
||||
from_point: int
|
||||
to_point: int
|
||||
dir_x: float
|
||||
|
|
@ -117,30 +139,30 @@ class MagicPlanMainDimension:
|
|||
|
||||
|
||||
@dataclass
|
||||
class MagicPlanExplodedWall:
|
||||
wall_type: str # "exterior" | "interior"
|
||||
points: list[MagicPlanWallPoint]
|
||||
class MagicPlanXMLExplodedWall:
|
||||
wall_type: str
|
||||
points: list[MagicPlanXMLWallPoint]
|
||||
|
||||
|
||||
@dataclass
|
||||
class MagicPlanExploded:
|
||||
walls: list[MagicPlanExplodedWall]
|
||||
doors: list[MagicPlanExplodedOpening]
|
||||
windows: list[MagicPlanExplodedOpening]
|
||||
furniture: list[MagicPlanFurniture]
|
||||
class MagicPlanXMLExploded:
|
||||
walls: list[MagicPlanXMLExplodedWall]
|
||||
doors: list[MagicPlanXMLExplodedOpening]
|
||||
windows: list[MagicPlanXMLExplodedOpening]
|
||||
furniture: list[MagicPlanXMLFurniture]
|
||||
|
||||
|
||||
@dataclass
|
||||
class MagicPlanSymbolInstance:
|
||||
class MagicPlanXMLSymbolInstance:
|
||||
id: str
|
||||
uid: str
|
||||
parent_uid: str
|
||||
symbol: str
|
||||
values: dict[str, str] # ceilingHeight, width, height, depth, distanceUnit, etc.
|
||||
values: dict[str, str]
|
||||
|
||||
|
||||
@dataclass
|
||||
class MagicPlanRoom:
|
||||
class MagicPlanXMLRoom:
|
||||
uid: str
|
||||
type: str
|
||||
x: float
|
||||
|
|
@ -149,35 +171,35 @@ class MagicPlanRoom:
|
|||
was_modified: bool
|
||||
linked_room_0: str
|
||||
linked_room_1: str
|
||||
area: float # m²
|
||||
perimeter: float # m
|
||||
values: dict[str, str] # ceilingHeight, label, etc.
|
||||
points: list[MagicPlanRoomPoint]
|
||||
doors: list[MagicPlanDoor]
|
||||
windows: list[MagicPlanWindow]
|
||||
furniture: list[MagicPlanFurniture]
|
||||
main_dimensions: list[MagicPlanMainDimension]
|
||||
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 MagicPlanFloor:
|
||||
class MagicPlanXMLFloor:
|
||||
uid: str
|
||||
name: str
|
||||
floor_type: str # "0"=ground, "1"=upper, "2"=basement
|
||||
floor_type: str
|
||||
rotation: float
|
||||
compass_angle: float
|
||||
area_without_walls: float # m²
|
||||
area_with_interior_walls_only: float # m²
|
||||
area_with_walls: float # m²
|
||||
symbol_instance: MagicPlanSymbolInstance
|
||||
rooms: list[MagicPlanRoom]
|
||||
furniture: list[MagicPlanFurniture] # floor-level furniture (not inside any room)
|
||||
exploded: MagicPlanExploded
|
||||
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 MagicPlanSummary:
|
||||
"""Plan metadata returned by the list-plans API endpoint."""
|
||||
class MagicPlanXMLSummary:
|
||||
"""Plan metadata returned by the list-plans API endpoint (old string-address format)."""
|
||||
|
||||
id: str
|
||||
project_id: str
|
||||
|
|
@ -195,25 +217,201 @@ class MagicPlanSummary:
|
|||
|
||||
|
||||
@dataclass
|
||||
class MagicPlanDetail:
|
||||
class MagicPlanXMLDetail:
|
||||
"""Full plan response: summary metadata + parsed XML plan."""
|
||||
|
||||
summary: MagicPlanSummary
|
||||
plan_xml: "MagicPlanPlan"
|
||||
summary: MagicPlanXMLSummary
|
||||
plan_xml: "MagicPlanXMLPlan"
|
||||
|
||||
|
||||
@dataclass
|
||||
class MagicPlanPlan:
|
||||
class MagicPlanXMLPlan:
|
||||
id: str
|
||||
uid: str
|
||||
name: str
|
||||
type: str
|
||||
interior_wall_width: float # m
|
||||
exterior_wall_width: float # m
|
||||
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] # date, statistics.*, distanceUnit, etc.
|
||||
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]
|
||||
interior_room_floors: list[MagicPlanFloor] # from <interiorRoomPoints>
|
||||
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
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import pytest
|
|||
import requests
|
||||
|
||||
from backend.magic_plan.magic_plan_client import MagicPlanClient
|
||||
from backend.magic_plan.models import MagicPlanDetail, MagicPlanPlan, MagicPlanSummary
|
||||
from backend.magic_plan.models import MagicPlanXMLDetail, MagicPlanXMLPlan, MagicPlanXMLSummary
|
||||
|
||||
FIXTURES = Path(__file__).parent.parent
|
||||
API_KEY = "test-api-key"
|
||||
|
|
@ -107,12 +107,12 @@ class TestGetPlanXml:
|
|||
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, MagicPlanDetail)
|
||||
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, MagicPlanSummary)
|
||||
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())
|
||||
|
|
@ -127,7 +127,7 @@ class TestGetPlanXml:
|
|||
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, MagicPlanPlan)
|
||||
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())
|
||||
|
|
@ -167,7 +167,7 @@ class TestGetPlans:
|
|||
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, MagicPlanSummary) for s in result)
|
||||
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])
|
||||
|
|
|
|||
50
backend/magic_plan/tests/test_models.py
Normal file
50
backend/magic_plan/tests/test_models.py
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
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([]) == {}
|
||||
|
|
@ -5,7 +5,7 @@ from pathlib import Path
|
|||
|
||||
import pytest
|
||||
|
||||
from backend.magic_plan.models import MagicPlanPlan
|
||||
from backend.magic_plan.models import MagicPlanXMLPlan
|
||||
from backend.magic_plan.xml_parser import parse_magicplan_xml
|
||||
|
||||
FIXTURES = Path(__file__).parent.parent
|
||||
|
|
@ -21,17 +21,17 @@ def _load(filename: str) -> str:
|
|||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def plan1() -> MagicPlanPlan:
|
||||
def plan1() -> MagicPlanXMLPlan:
|
||||
return parse_magicplan_xml(_load("xml_example.xml"))
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def plan2() -> MagicPlanPlan:
|
||||
def plan2() -> MagicPlanXMLPlan:
|
||||
return parse_magicplan_xml(_load("xml_example_2.xml"))
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def plan3() -> MagicPlanPlan:
|
||||
def plan3() -> MagicPlanXMLPlan:
|
||||
return parse_magicplan_xml(_load("xml_example_3.xml"))
|
||||
|
||||
|
||||
|
|
@ -41,55 +41,55 @@ def plan3() -> MagicPlanPlan:
|
|||
|
||||
class TestPlanAttributes:
|
||||
|
||||
def test_id(self, plan1: MagicPlanPlan) -> None:
|
||||
def test_id(self, plan1: MagicPlanXMLPlan) -> None:
|
||||
assert plan1.id == "b66bb427-33fe-4865-8d57-bad7d9d3f2e5"
|
||||
|
||||
def test_uid(self, plan1: MagicPlanPlan) -> None:
|
||||
def test_uid(self, plan1: MagicPlanXMLPlan) -> None:
|
||||
assert plan1.uid == "69e5fafc.0890b3ff"
|
||||
|
||||
def test_name(self, plan1: MagicPlanPlan) -> None:
|
||||
def test_name(self, plan1: MagicPlanXMLPlan) -> None:
|
||||
assert plan1.name == "275 Carr Hill Rd NE9 5ND"
|
||||
|
||||
def test_type(self, plan1: MagicPlanPlan) -> None:
|
||||
def test_type(self, plan1: MagicPlanXMLPlan) -> None:
|
||||
assert plan1.type == "0"
|
||||
|
||||
def test_interior_wall_width(self, plan1: MagicPlanPlan) -> None:
|
||||
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: MagicPlanPlan) -> None:
|
||||
def test_exterior_wall_width(self, plan1: MagicPlanXMLPlan) -> None:
|
||||
assert math.isclose(plan1.exterior_wall_width, 0.25)
|
||||
|
||||
def test_schematic_false(self, plan1: MagicPlanPlan) -> None:
|
||||
def test_schematic_false(self, plan1: MagicPlanXMLPlan) -> None:
|
||||
assert plan1.schematic is False
|
||||
|
||||
def test_has_land_survey_address_false(self, plan1: MagicPlanPlan) -> None:
|
||||
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: MagicPlanPlan) -> None:
|
||||
def test_last_patch_identifier(self, plan1: MagicPlanXMLPlan) -> None:
|
||||
assert plan1.last_patch_identifier == "0"
|
||||
|
||||
def test_last_roll_identifier(self, plan1: MagicPlanPlan) -> None:
|
||||
def test_last_roll_identifier(self, plan1: MagicPlanXMLPlan) -> None:
|
||||
assert plan1.last_roll_identifier == "0"
|
||||
|
||||
|
||||
class TestPlanValues:
|
||||
|
||||
def test_date(self, plan1: MagicPlanPlan) -> None:
|
||||
def test_date(self, plan1: MagicPlanXMLPlan) -> None:
|
||||
assert plan1.values["date"] == "2026-04-20"
|
||||
|
||||
def test_statistics_area_of_height(self, plan1: MagicPlanPlan) -> None:
|
||||
def test_statistics_area_of_height(self, plan1: MagicPlanXMLPlan) -> None:
|
||||
assert plan1.values["statistics.areaOfHeight"] == "2.134"
|
||||
|
||||
def test_statistics_basement_account(self, plan1: MagicPlanPlan) -> None:
|
||||
def test_statistics_basement_account(self, plan1: MagicPlanXMLPlan) -> None:
|
||||
assert plan1.values["statistics.basement.account"] == "100"
|
||||
|
||||
def test_statistics_exterior_walls(self, plan1: MagicPlanPlan) -> None:
|
||||
def test_statistics_exterior_walls(self, plan1: MagicPlanXMLPlan) -> None:
|
||||
assert plan1.values["statistics.exteriorWalls"] == "0"
|
||||
|
||||
def test_statistics_interior_walls(self, plan1: MagicPlanPlan) -> None:
|
||||
def test_statistics_interior_walls(self, plan1: MagicPlanXMLPlan) -> None:
|
||||
assert plan1.values["statistics.interiorWalls"] == "0"
|
||||
|
||||
def test_plan2_date(self, plan2: MagicPlanPlan) -> None:
|
||||
def test_plan2_date(self, plan2: MagicPlanXMLPlan) -> None:
|
||||
assert plan2.values["date"] == "2026-04-16"
|
||||
|
||||
|
||||
|
|
@ -99,43 +99,43 @@ class TestPlanValues:
|
|||
|
||||
class TestFloors:
|
||||
|
||||
def test_floor_count_plan1(self, plan1: MagicPlanPlan) -> None:
|
||||
def test_floor_count_plan1(self, plan1: MagicPlanXMLPlan) -> None:
|
||||
assert len(plan1.floors) == 2
|
||||
|
||||
def test_floor_count_plan2(self, plan2: MagicPlanPlan) -> None:
|
||||
def test_floor_count_plan2(self, plan2: MagicPlanXMLPlan) -> None:
|
||||
assert len(plan2.floors) == 1
|
||||
|
||||
def test_floor_count_plan3(self, plan3: MagicPlanPlan) -> None:
|
||||
def test_floor_count_plan3(self, plan3: MagicPlanXMLPlan) -> None:
|
||||
assert len(plan3.floors) == 1
|
||||
|
||||
def test_ground_floor_name(self, plan1: MagicPlanPlan) -> None:
|
||||
def test_ground_floor_name(self, plan1: MagicPlanXMLPlan) -> None:
|
||||
assert plan1.floors[0].name == "Ground Floor"
|
||||
|
||||
def test_first_floor_name(self, plan1: MagicPlanPlan) -> None:
|
||||
def test_first_floor_name(self, plan1: MagicPlanXMLPlan) -> None:
|
||||
assert plan1.floors[1].name == "1st Floor"
|
||||
|
||||
def test_ground_floor_type(self, plan1: MagicPlanPlan) -> None:
|
||||
def test_ground_floor_type(self, plan1: MagicPlanXMLPlan) -> None:
|
||||
assert plan1.floors[0].floor_type == "0"
|
||||
|
||||
def test_upper_floor_type(self, plan1: MagicPlanPlan) -> None:
|
||||
def test_upper_floor_type(self, plan1: MagicPlanXMLPlan) -> None:
|
||||
assert plan1.floors[1].floor_type == "1"
|
||||
|
||||
def test_floor_uid(self, plan1: MagicPlanPlan) -> None:
|
||||
def test_floor_uid(self, plan1: MagicPlanXMLPlan) -> None:
|
||||
assert plan1.floors[0].uid == "69e5fb20.4feef7ff"
|
||||
|
||||
def test_ground_floor_area_without_walls(self, plan1: MagicPlanPlan) -> None:
|
||||
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: MagicPlanPlan) -> None:
|
||||
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: MagicPlanPlan) -> None:
|
||||
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: MagicPlanPlan) -> None:
|
||||
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: MagicPlanPlan) -> None:
|
||||
def test_floor_compass_angle(self, plan1: MagicPlanXMLPlan) -> None:
|
||||
assert math.isclose(plan1.floors[0].compass_angle, -1.0)
|
||||
|
||||
|
||||
|
|
@ -145,19 +145,19 @@ class TestFloors:
|
|||
|
||||
class TestSymbolInstance:
|
||||
|
||||
def test_symbol_instance_id(self, plan1: MagicPlanPlan) -> None:
|
||||
def test_symbol_instance_id(self, plan1: MagicPlanXMLPlan) -> None:
|
||||
assert plan1.floors[0].symbol_instance.id == "floor"
|
||||
|
||||
def test_symbol_instance_uid(self, plan1: MagicPlanPlan) -> None:
|
||||
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: MagicPlanPlan) -> None:
|
||||
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: MagicPlanPlan) -> None:
|
||||
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: MagicPlanPlan) -> None:
|
||||
def test_symbol_instance_ceiling_height_value(self, plan1: MagicPlanXMLPlan) -> None:
|
||||
assert plan1.floors[0].symbol_instance.values["ceilingHeight"] == "2.323164"
|
||||
|
||||
|
||||
|
|
@ -167,59 +167,59 @@ class TestSymbolInstance:
|
|||
|
||||
class TestRooms:
|
||||
|
||||
def test_ground_floor_room_count(self, plan1: MagicPlanPlan) -> None:
|
||||
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: MagicPlanPlan) -> None:
|
||||
def test_first_floor_room_count(self, plan1: MagicPlanXMLPlan) -> None:
|
||||
assert len(plan1.floors[1].rooms) == 4
|
||||
|
||||
def test_plan3_room_count(self, plan3: MagicPlanPlan) -> None:
|
||||
def test_plan3_room_count(self, plan3: MagicPlanXMLPlan) -> None:
|
||||
assert len(plan3.floors[0].rooms) == 9
|
||||
|
||||
def test_room_type(self, plan1: MagicPlanPlan) -> None:
|
||||
def test_room_type(self, plan1: MagicPlanXMLPlan) -> None:
|
||||
assert plan1.floors[0].rooms[0].type == "Kitchen"
|
||||
|
||||
def test_room_uid(self, plan1: MagicPlanPlan) -> None:
|
||||
def test_room_uid(self, plan1: MagicPlanXMLPlan) -> None:
|
||||
assert plan1.floors[0].rooms[0].uid == "69e5fbc8.71027bff"
|
||||
|
||||
def test_room_area(self, plan1: MagicPlanPlan) -> None:
|
||||
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: MagicPlanPlan) -> None:
|
||||
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: MagicPlanPlan) -> None:
|
||||
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: MagicPlanPlan) -> None:
|
||||
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: MagicPlanPlan) -> None:
|
||||
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: MagicPlanPlan) -> None:
|
||||
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: MagicPlanPlan) -> None:
|
||||
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: MagicPlanPlan) -> None:
|
||||
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: MagicPlanPlan) -> None:
|
||||
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: MagicPlanPlan) -> None:
|
||||
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: MagicPlanPlan) -> None:
|
||||
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: MagicPlanPlan) -> None:
|
||||
def test_room_no_label_when_absent(self, plan1: MagicPlanXMLPlan) -> None:
|
||||
assert "label" not in plan1.floors[0].rooms[0].values
|
||||
|
||||
|
||||
|
|
@ -229,25 +229,25 @@ class TestRooms:
|
|||
|
||||
class TestRoomPoints:
|
||||
|
||||
def test_kitchen_point_count(self, plan1: MagicPlanPlan) -> None:
|
||||
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: MagicPlanPlan) -> None:
|
||||
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: MagicPlanPlan) -> None:
|
||||
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: MagicPlanPlan) -> None:
|
||||
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: MagicPlanPlan) -> None:
|
||||
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: MagicPlanPlan) -> None:
|
||||
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: MagicPlanPlan) -> None:
|
||||
def test_point_values_empty_when_absent(self, plan1: MagicPlanXMLPlan) -> None:
|
||||
assert plan1.floors[0].rooms[0].points[0].values == {}
|
||||
|
||||
|
||||
|
|
@ -257,40 +257,40 @@ class TestRoomPoints:
|
|||
|
||||
class TestDoors:
|
||||
|
||||
def test_kitchen_door_count(self, plan1: MagicPlanPlan) -> None:
|
||||
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: MagicPlanPlan) -> None:
|
||||
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: MagicPlanPlan) -> None:
|
||||
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: MagicPlanPlan) -> None:
|
||||
def test_door_type(self, plan1: MagicPlanXMLPlan) -> None:
|
||||
assert plan1.floors[0].rooms[0].doors[0].type == "4"
|
||||
|
||||
def test_door_u(self, plan1: MagicPlanPlan) -> None:
|
||||
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: MagicPlanPlan) -> None:
|
||||
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: MagicPlanPlan) -> None:
|
||||
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: MagicPlanPlan) -> None:
|
||||
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: MagicPlanPlan) -> None:
|
||||
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: MagicPlanPlan) -> None:
|
||||
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: MagicPlanPlan) -> 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: MagicPlanPlan) -> None:
|
||||
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)
|
||||
|
||||
|
||||
|
|
@ -300,30 +300,30 @@ class TestDoors:
|
|||
|
||||
class TestWindows:
|
||||
|
||||
def test_kitchen_window_count(self, plan1: MagicPlanPlan) -> None:
|
||||
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: MagicPlanPlan) -> None:
|
||||
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: MagicPlanPlan) -> None:
|
||||
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: MagicPlanPlan) -> None:
|
||||
def test_window_type(self, plan1: MagicPlanXMLPlan) -> None:
|
||||
assert plan1.floors[0].rooms[0].windows[0].type == "1"
|
||||
|
||||
def test_window_u(self, plan1: MagicPlanPlan) -> None:
|
||||
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: MagicPlanPlan) -> None:
|
||||
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: MagicPlanPlan) -> None:
|
||||
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: MagicPlanPlan) -> None:
|
||||
def test_window_symbol_instance(self, plan1: MagicPlanXMLPlan) -> None:
|
||||
assert plan1.floors[0].rooms[0].windows[0].symbol_instance == "W-0-2"
|
||||
|
||||
|
||||
|
|
@ -333,18 +333,18 @@ class TestWindows:
|
|||
|
||||
class TestRoomFurniture:
|
||||
|
||||
def test_kitchen_furniture_count(self, plan1: MagicPlanPlan) -> None:
|
||||
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: MagicPlanPlan) -> None:
|
||||
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: MagicPlanPlan) -> None:
|
||||
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: MagicPlanPlan) -> None:
|
||||
def test_furniture_symbol_instance(self, plan1: MagicPlanXMLPlan) -> None:
|
||||
assert isinstance(plan1.floors[0].rooms[0].furniture[0].symbol_instance, str)
|
||||
|
||||
|
||||
|
|
@ -354,16 +354,16 @@ class TestRoomFurniture:
|
|||
|
||||
class TestFloorLevelFurniture:
|
||||
|
||||
def test_floor_furniture_count(self, plan3: MagicPlanPlan) -> None:
|
||||
def test_floor_furniture_count(self, plan3: MagicPlanXMLPlan) -> None:
|
||||
assert len(plan3.floors[0].furniture) == 1
|
||||
|
||||
def test_floor_furniture_x(self, plan3: MagicPlanPlan) -> None:
|
||||
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: MagicPlanPlan) -> None:
|
||||
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: MagicPlanPlan) -> None:
|
||||
def test_floor_furniture_absent_plan1(self, plan1: MagicPlanXMLPlan) -> None:
|
||||
assert len(plan1.floors[0].furniture) == 0
|
||||
|
||||
|
||||
|
|
@ -373,13 +373,13 @@ class TestFloorLevelFurniture:
|
|||
|
||||
class TestMainDimensions:
|
||||
|
||||
def test_main_dimension_count(self, plan1: MagicPlanPlan) -> None:
|
||||
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: MagicPlanPlan) -> None:
|
||||
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: MagicPlanPlan) -> None:
|
||||
def test_main_dimension_is_set(self, plan1: MagicPlanXMLPlan) -> None:
|
||||
assert isinstance(plan1.floors[0].rooms[0].main_dimensions[0].is_set, bool)
|
||||
|
||||
|
||||
|
|
@ -389,59 +389,59 @@ class TestMainDimensions:
|
|||
|
||||
class TestExploded:
|
||||
|
||||
def test_exploded_wall_count_ground_floor(self, plan1: MagicPlanPlan) -> None:
|
||||
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: MagicPlanPlan) -> None:
|
||||
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: MagicPlanPlan) -> None:
|
||||
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: MagicPlanPlan) -> None:
|
||||
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: MagicPlanPlan) -> None:
|
||||
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: MagicPlanPlan) -> None:
|
||||
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: MagicPlanPlan) -> None:
|
||||
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: MagicPlanPlan) -> None:
|
||||
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: MagicPlanPlan) -> None:
|
||||
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: MagicPlanPlan) -> None:
|
||||
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: MagicPlanPlan) -> None:
|
||||
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: MagicPlanPlan) -> None:
|
||||
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: MagicPlanPlan) -> None:
|
||||
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: MagicPlanPlan) -> None:
|
||||
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: MagicPlanPlan) -> None:
|
||||
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: MagicPlanPlan) -> None:
|
||||
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: MagicPlanPlan) -> None:
|
||||
def test_plan3_exploded_window_count(self, plan3: MagicPlanXMLPlan) -> None:
|
||||
assert len(plan3.floors[0].exploded.windows) == 5
|
||||
|
||||
|
||||
|
|
@ -451,11 +451,11 @@ class TestExploded:
|
|||
|
||||
class TestInteriorRoomPoints:
|
||||
|
||||
def test_interior_room_floor_count_plan1(self, plan1: MagicPlanPlan) -> None:
|
||||
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: MagicPlanPlan) -> None:
|
||||
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: MagicPlanPlan) -> None:
|
||||
def test_interior_room_floor_uid_matches(self, plan1: MagicPlanXMLPlan) -> None:
|
||||
assert plan1.interior_room_floors[0].uid == plan1.floors[0].uid
|
||||
|
|
|
|||
|
|
@ -1,19 +1,19 @@
|
|||
import xml.etree.ElementTree as ET
|
||||
|
||||
from backend.magic_plan.models import (
|
||||
MagicPlanDoor,
|
||||
MagicPlanExploded,
|
||||
MagicPlanExplodedOpening,
|
||||
MagicPlanExplodedWall,
|
||||
MagicPlanFloor,
|
||||
MagicPlanFurniture,
|
||||
MagicPlanMainDimension,
|
||||
MagicPlanPlan,
|
||||
MagicPlanRoom,
|
||||
MagicPlanRoomPoint,
|
||||
MagicPlanSymbolInstance,
|
||||
MagicPlanWallPoint,
|
||||
MagicPlanWindow,
|
||||
MagicPlanXMLDoor,
|
||||
MagicPlanXMLExploded,
|
||||
MagicPlanXMLExplodedOpening,
|
||||
MagicPlanXMLExplodedWall,
|
||||
MagicPlanXMLFloor,
|
||||
MagicPlanXMLFurniture,
|
||||
MagicPlanXMLMainDimension,
|
||||
MagicPlanXMLPlan,
|
||||
MagicPlanXMLRoom,
|
||||
MagicPlanXMLRoomPoint,
|
||||
MagicPlanXMLSymbolInstance,
|
||||
MagicPlanXMLWallPoint,
|
||||
MagicPlanXMLWindow,
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -21,8 +21,8 @@ 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) -> MagicPlanRoomPoint:
|
||||
return MagicPlanRoomPoint(
|
||||
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")),
|
||||
|
|
@ -31,16 +31,16 @@ def _parse_room_point(el: ET.Element) -> MagicPlanRoomPoint:
|
|||
)
|
||||
|
||||
|
||||
def _parse_wall_point(el: ET.Element) -> MagicPlanWallPoint:
|
||||
return MagicPlanWallPoint(
|
||||
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) -> MagicPlanDoor:
|
||||
return MagicPlanDoor(
|
||||
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")),
|
||||
|
|
@ -62,8 +62,8 @@ def _parse_door(el: ET.Element) -> MagicPlanDoor:
|
|||
)
|
||||
|
||||
|
||||
def _parse_window(el: ET.Element) -> MagicPlanWindow:
|
||||
return MagicPlanWindow(
|
||||
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")),
|
||||
|
|
@ -84,8 +84,8 @@ def _parse_window(el: ET.Element) -> MagicPlanWindow:
|
|||
)
|
||||
|
||||
|
||||
def _parse_exploded_opening(el: ET.Element) -> MagicPlanExplodedOpening:
|
||||
return MagicPlanExplodedOpening(
|
||||
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")),
|
||||
|
|
@ -101,8 +101,8 @@ def _parse_exploded_opening(el: ET.Element) -> MagicPlanExplodedOpening:
|
|||
)
|
||||
|
||||
|
||||
def _parse_furniture(el: ET.Element) -> MagicPlanFurniture:
|
||||
return MagicPlanFurniture(
|
||||
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")),
|
||||
|
|
@ -122,8 +122,8 @@ def _parse_furniture(el: ET.Element) -> MagicPlanFurniture:
|
|||
)
|
||||
|
||||
|
||||
def _parse_main_dimension(el: ET.Element) -> MagicPlanMainDimension:
|
||||
return MagicPlanMainDimension(
|
||||
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")),
|
||||
|
|
@ -134,16 +134,16 @@ def _parse_main_dimension(el: ET.Element) -> MagicPlanMainDimension:
|
|||
)
|
||||
|
||||
|
||||
def _parse_exploded_wall(el: ET.Element) -> MagicPlanExplodedWall:
|
||||
def _parse_exploded_wall(el: ET.Element) -> MagicPlanXMLExplodedWall:
|
||||
type_el = el.find("type")
|
||||
return MagicPlanExplodedWall(
|
||||
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) -> MagicPlanExploded:
|
||||
return MagicPlanExploded(
|
||||
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")],
|
||||
|
|
@ -151,8 +151,8 @@ def _parse_exploded(el: ET.Element) -> MagicPlanExploded:
|
|||
)
|
||||
|
||||
|
||||
def _parse_symbol_instance(el: ET.Element) -> MagicPlanSymbolInstance:
|
||||
return MagicPlanSymbolInstance(
|
||||
def _parse_symbol_instance(el: ET.Element) -> MagicPlanXMLSymbolInstance:
|
||||
return MagicPlanXMLSymbolInstance(
|
||||
id=el.get("id", ""),
|
||||
uid=el.get("uid", ""),
|
||||
parent_uid=el.get("parentUid", ""),
|
||||
|
|
@ -161,10 +161,10 @@ def _parse_symbol_instance(el: ET.Element) -> MagicPlanSymbolInstance:
|
|||
)
|
||||
|
||||
|
||||
def _parse_floor(el: ET.Element) -> MagicPlanFloor:
|
||||
def _parse_floor(el: ET.Element) -> MagicPlanXMLFloor:
|
||||
si_el = el.find("symbolInstance")
|
||||
exploded_el = el.find("exploded")
|
||||
return MagicPlanFloor(
|
||||
return MagicPlanXMLFloor(
|
||||
uid=el.get("uid", ""),
|
||||
name=el.findtext("name") or "",
|
||||
floor_type=el.get("floorType", "0"),
|
||||
|
|
@ -174,16 +174,16 @@ def _parse_floor(el: ET.Element) -> MagicPlanFloor:
|
|||
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 MagicPlanSymbolInstance(id="", uid="", parent_uid="", symbol="", values={}),
|
||||
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 MagicPlanExploded(walls=[], doors=[], windows=[], furniture=[]),
|
||||
else MagicPlanXMLExploded(walls=[], doors=[], windows=[], furniture=[]),
|
||||
)
|
||||
|
||||
|
||||
def _parse_room(el: ET.Element) -> MagicPlanRoom:
|
||||
return MagicPlanRoom(
|
||||
def _parse_room(el: ET.Element) -> MagicPlanXMLRoom:
|
||||
return MagicPlanXMLRoom(
|
||||
uid=el.get("uid", ""),
|
||||
type=el.get("type", ""),
|
||||
x=float(el.get("x", "0")),
|
||||
|
|
@ -203,10 +203,10 @@ def _parse_room(el: ET.Element) -> MagicPlanRoom:
|
|||
)
|
||||
|
||||
|
||||
def parse_magicplan_xml(xml_str: str) -> MagicPlanPlan:
|
||||
def parse_magicplan_xml(xml_str: str) -> MagicPlanXMLPlan:
|
||||
root = ET.fromstring(xml_str)
|
||||
irp_el = root.find("interiorRoomPoints")
|
||||
return MagicPlanPlan(
|
||||
return MagicPlanXMLPlan(
|
||||
id=root.get("id", ""),
|
||||
uid=root.get("uid", ""),
|
||||
name=root.get("name", ""),
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue