From 18a58486af59b1c8afaa3bedf994436802c1e54b Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Tue, 5 May 2026 10:28:07 +0000 Subject: [PATCH] =?UTF-8?q?Define=20dataclasses=20to=20represent=20API=20j?= =?UTF-8?q?son=20=F0=9F=9F=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- datatypes/magicplan/__init__.py | 0 datatypes/magicplan/api/__init__.py | 0 datatypes/magicplan/api/response.py | 279 ++++++++++++++++++++++ datatypes/magicplan/api/tests/__init__.py | 0 pytest.ini | 2 +- 5 files changed, 280 insertions(+), 1 deletion(-) create mode 100644 datatypes/magicplan/__init__.py create mode 100644 datatypes/magicplan/api/__init__.py create mode 100644 datatypes/magicplan/api/response.py create mode 100644 datatypes/magicplan/api/tests/__init__.py diff --git a/datatypes/magicplan/__init__.py b/datatypes/magicplan/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/datatypes/magicplan/api/__init__.py b/datatypes/magicplan/api/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/datatypes/magicplan/api/response.py b/datatypes/magicplan/api/response.py new file mode 100644 index 00000000..b81e02d5 --- /dev/null +++ b/datatypes/magicplan/api/response.py @@ -0,0 +1,279 @@ +from typing import Any, Optional, Union + +from pydantic import BaseModel, ConfigDict, Field + + +_IGNORE = ConfigDict(extra="ignore") +_IGNORE_POPULATE = ConfigDict(extra="ignore", populate_by_name=True) + + +class Vec3(BaseModel): + model_config = _IGNORE + x: float + y: float + z: float + + +class Symbol(BaseModel): + model_config = _IGNORE + id: str + name: str + description: Optional[str] = None + valid: bool + + +class FieldValue(BaseModel): + model_config = _IGNORE + index: int + has_value: bool + is_array: bool + value: Union[str, list[str]] + + +class SurveyField(BaseModel): + model_config = _IGNORE + id: str + type: int + type_as_string: str + is_required: bool + label: str + description: Optional[str] = None + list_values: list[str] = [] + value: FieldValue + + +class ImageMapEntry(BaseModel): + model_config = _IGNORE + symbol_id: str + uid: str + owner_uid: str + type: int + coordinates: list[int] + + +class FormattedDimensions(BaseModel): + model_config = _IGNORE + width: Optional[str] = None + depth: Optional[str] = None + height: Optional[str] = None + + +class FormattedMeasures(BaseModel): + model_config = _IGNORE + width: Optional[str] = None + depth: Optional[str] = None + height: Optional[str] = None + area: Optional[str] = None + area_without_walls: Optional[str] = None + area_with_interior_walls_only: Optional[str] = None + area_with_walls: Optional[str] = None + doors_surface: Optional[str] = None + walls_surface: Optional[str] = None + walls_surface_without_openings: Optional[str] = None + windows_surface: Optional[str] = None + perimeter: Optional[str] = None + ground_perimeter: Optional[str] = None + living_area: Optional[str] = None + below_grade_living_area: Optional[str] = None + above_grade_living_area: Optional[str] = None + exterior_perimeter: Optional[str] = None + volume: Optional[str] = None + + +class Address(BaseModel): + model_config = _IGNORE + street: Optional[str] = None + street_number: Optional[str] = None + postal_code: Optional[str] = None + city: Optional[str] = None + country: Optional[str] = None + longitude: Optional[float] = None + latitude: Optional[float] = None + + +class CreatedBy(BaseModel): + model_config = _IGNORE + id: str + email: str + firstname: Optional[str] = None + lastname: Optional[str] = None + + +class Location(BaseModel): + model_config = _IGNORE + valid: bool + longitude: float + latitude: float + altitude: float + + +class ItemBase(BaseModel): + model_config = _IGNORE + uid: str + symbol: Symbol + size: Vec3 + position: Vec3 + rotation: Vec3 + formatted: Optional[FormattedDimensions] = None + images: list[Any] = [] + notes: Optional[str] = None + displayable_fields: list[SurveyField] = [] + custom_displayable_fields: list[SurveyField] = [] + + +class WallItem(ItemBase): + pass + + +class Furniture(ItemBase): + pass + + +class SymbolInstance(ItemBase): + pass + + +class Wall(BaseModel): + model_config = _IGNORE + uid: str + symbol: Symbol + length: float + images: list[Any] = [] + notes: Optional[str] = None + displayable_fields: list[SurveyField] = [] + custom_displayable_fields: list[SurveyField] = [] + + +class Room(BaseModel): + model_config = _IGNORE + name: str + uid: str + symbol: Optional[Symbol] = None + size: Vec3 + position: Vec3 + rotation: Vec3 + area: float + perimeter: Optional[float] = None + ground_perimeter: Optional[float] = None + area_without_walls: Optional[float] = None + area_with_interior_walls_only: Optional[float] = None + area_with_walls: Optional[float] = None + wall_count: Optional[int] = None + wall_count_with_interior_walls: Optional[int] = None + corner_count_with_interior_walls: Optional[int] = None + door_count: Optional[int] = None + window_count: Optional[int] = None + height: Optional[float] = None + volume: Optional[float] = None + width: Optional[float] = None + doors_surface: Optional[float] = None + walls_surface: Optional[float] = None + walls_surface_without_openings: Optional[float] = None + windows_surface: Optional[float] = None + room_type: Optional[str] = None + furniture_count: Optional[int] = None + image: Optional[str] = None + image_map: list[ImageMapEntry] = [] + images: list[Any] = [] + notes: Optional[str] = None + formatted: Optional[FormattedMeasures] = None + displayable_fields: list[SurveyField] = [] + custom_displayable_fields: list[SurveyField] = [] + wall_items: list[WallItem] = [] + furnitures: list[Furniture] = [] + walls: list[Wall] = [] + + +class Floor(BaseModel): + model_config = _IGNORE + uid: str + symbol: Optional[Symbol] = None + size: Vec3 + position: Vec3 + rotation: Vec3 + name: Optional[str] = None + area: Optional[float] = None + perimeter: Optional[float] = None + ground_perimeter: Optional[float] = None + area_without_walls: Optional[float] = None + area_with_interior_walls_only: Optional[float] = None + area_with_walls: Optional[float] = None + wall_count: Optional[int] = None + wall_count_with_interior_walls: Optional[int] = None + corner_count_with_interior_walls: Optional[int] = None + door_count: Optional[int] = None + window_count: Optional[int] = None + bathrooms_count: Optional[int] = None + bedrooms_count: Optional[int] = None + doors_surface: Optional[float] = None + floor_type: Optional[Union[int, str]] = None + furniture_count: Optional[int] = None + height: Optional[float] = None + level: Optional[int] = None + room_count: Optional[int] = None + volume: Optional[float] = None + walls_surface: Optional[float] = None + walls_surface_without_openings: Optional[float] = None + windows_surface: Optional[float] = None + image: Optional[str] = None + image_map: list[ImageMapEntry] = [] + images: list[Any] = [] + notes: Optional[str] = None + formatted: Optional[FormattedMeasures] = None + displayable_fields: list[SurveyField] = [] + custom_displayable_fields: list[SurveyField] = [] + rooms: list[Room] = [] + furnitures: list[Furniture] = [] + symbol_instances: list[SymbolInstance] = [] + + +class PlanBody(BaseModel): + model_config = _IGNORE + uid: str + name: Optional[str] = None + symbol: Optional[Symbol] = None + size: Vec3 + position: Vec3 + rotation: Vec3 + area: Optional[float] = None + location: Location + floors: list[Floor] = [] + images: list[Any] = [] + notes: Optional[str] = None + formatted: Optional[FormattedMeasures] = None + displayable_fields: list[SurveyField] = [] + custom_displayable_fields: list[SurveyField] = [] + customer: list[Any] = [] + custom_attributes: list[Any] = [] + + +class PlanDetail(BaseModel): + model_config = _IGNORE + extension_version: Optional[str] = None + wrapper_version: Optional[str] = None + document_version: Optional[str] = None + last_modification_date: Optional[Union[int, str]] = None + plan: PlanBody + + +class PlanSummary(BaseModel): + model_config = _IGNORE_POPULATE + id: str + project_id: Optional[str] = None + name: str + address: Optional[Address] = None + creation_date: Optional[str] = None + update_date: Optional[str] = None + thumbnail_url: Optional[str] = None + public_url: Optional[str] = None + cloud_url: Optional[str] = None + url_3d: Optional[str] = Field(default=None, alias="3d_url") + workgroup_id: Optional[str] = None + team_id: Optional[str] = None + created_by: Optional[CreatedBy] = None + + +class MagicPlan(BaseModel): + model_config = _IGNORE + plan: PlanSummary + plan_detail: PlanDetail diff --git a/datatypes/magicplan/api/tests/__init__.py b/datatypes/magicplan/api/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/pytest.ini b/pytest.ini index 5311d3ff..2032ba6c 100644 --- a/pytest.ini +++ b/pytest.ini @@ -3,6 +3,6 @@ pythonpath = . log_cli = true log_cli_level = INFO addopts = --cov-report term-missing --cov=etl/epc --cov=recommendations --cov=backend --cov=etl/epc_clean --cov=etl/spatial -testpaths = recommendations/tests backend/tests etl/epc/tests etl/epc_clean/tests etl/spatial/tests backend/condition/tests backend/address2UPRN/tests backend/onboarders/tests backend/categorisation/tests backend/export/tests etl/hubspot/tests backend/hubspot_trigger_orchestrator/tests datatypes/epc/schema/tests datatypes/epc/surveys/tests datatypes/epc/domain/tests backend/ecmk_fetcher/tests/ backend/pashub_fetcher/tests backend/documents_parser/tests backend/magic_plan/tests +testpaths = recommendations/tests backend/tests etl/epc/tests etl/epc_clean/tests etl/spatial/tests backend/condition/tests backend/address2UPRN/tests backend/onboarders/tests backend/categorisation/tests backend/export/tests etl/hubspot/tests backend/hubspot_trigger_orchestrator/tests datatypes/epc/schema/tests datatypes/epc/surveys/tests datatypes/epc/domain/tests backend/ecmk_fetcher/tests/ backend/pashub_fetcher/tests backend/documents_parser/tests backend/magic_plan/tests datatypes/magicplan/api/tests markers = integration: mark a test as an integration test