mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-08 11:17:27 +00:00
461 lines
19 KiB
Python
461 lines
19 KiB
Python
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
|