From 99280b6a64f747dce58b873b2e02a55938f040ee Mon Sep 17 00:00:00 2001 From: Khalim Conn-Kowlessar Date: Mon, 13 Jul 2026 14:56:07 +0000 Subject: [PATCH] =?UTF-8?q?Map=20a=20Calico=20roof-covering=20row=20to=20a?= =?UTF-8?q?=20renewal=20year=20=F0=9F=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- domain/condition/mapping/calico/__init__.py | 0 .../mapping/calico/calico_element_map.py | 12 +++++++ .../condition/mapping/calico/calico_mapper.py | 11 ++++++ domain/condition/records/calico/__init__.py | 0 .../records/calico/calico_asset_condition.py | 20 +++++++++++ .../records/calico/calico_property.py | 10 ++++++ tests/condition/mapping/test_calico_mapper.py | 36 +++++++++++++++++++ 7 files changed, 89 insertions(+) create mode 100644 domain/condition/mapping/calico/__init__.py create mode 100644 domain/condition/mapping/calico/calico_element_map.py create mode 100644 domain/condition/mapping/calico/calico_mapper.py create mode 100644 domain/condition/records/calico/__init__.py create mode 100644 domain/condition/records/calico/calico_asset_condition.py create mode 100644 domain/condition/records/calico/calico_property.py create mode 100644 tests/condition/mapping/test_calico_mapper.py diff --git a/domain/condition/mapping/calico/__init__.py b/domain/condition/mapping/calico/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/domain/condition/mapping/calico/calico_element_map.py b/domain/condition/mapping/calico/calico_element_map.py new file mode 100644 index 000000000..36f8431ef --- /dev/null +++ b/domain/condition/mapping/calico/calico_element_map.py @@ -0,0 +1,12 @@ +from domain.condition.aspect_type import AspectType +from domain.condition.element_type import ElementType +from domain.condition.mapping.element_mapping import ElementMapping + +# Calico's component path -> the shared taxonomy. Held as data (like the LBWF / +# Peabody element maps) so extending Calico to further components is a data edit. +CALICO_ELEMENT_MAP: dict[str, ElementMapping] = { + "Roof Covering": ElementMapping( + elementType=ElementType.ROOF, + aspect_type=AspectType.MATERIAL, + ), +} diff --git a/domain/condition/mapping/calico/calico_mapper.py b/domain/condition/mapping/calico/calico_mapper.py new file mode 100644 index 000000000..446d8b8cb --- /dev/null +++ b/domain/condition/mapping/calico/calico_mapper.py @@ -0,0 +1,11 @@ +from typing import Any, Optional + +from domain.condition.mapping.mapper import Mapper +from domain.condition.property_condition_survey import PropertyConditionSurvey + + +class CalicoMapper(Mapper): + def map_asset_conditions_for_property( + self, client_property_data: Any, survey_year: Optional[int] = None + ) -> PropertyConditionSurvey: + raise NotImplementedError diff --git a/domain/condition/records/calico/__init__.py b/domain/condition/records/calico/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/domain/condition/records/calico/calico_asset_condition.py b/domain/condition/records/calico/calico_asset_condition.py new file mode 100644 index 000000000..0673fbba0 --- /dev/null +++ b/domain/condition/records/calico/calico_asset_condition.py @@ -0,0 +1,20 @@ +from dataclasses import dataclass +from datetime import date +from typing import Optional + + +@dataclass +class CalicoAssetCondition: + """One component row from a Calico stock-condition export. + + Calico's current export carries a single component (``path == "Roof + Covering"``); ``covering_type`` is the material (e.g. "Concrete Tiles") and + ``remaining_life`` is Calico's Decent-Homes-derived years-remaining figure. + """ + + asset_reference: int + path: str + covering_type: Optional[str] = None + fitted_date: Optional[date] = None + quantity: Optional[int] = None + remaining_life: Optional[int] = None diff --git a/domain/condition/records/calico/calico_property.py b/domain/condition/records/calico/calico_property.py new file mode 100644 index 000000000..ba0597d5f --- /dev/null +++ b/domain/condition/records/calico/calico_property.py @@ -0,0 +1,10 @@ +from dataclasses import dataclass +from typing import List + +from domain.condition.records.calico.calico_asset_condition import CalicoAssetCondition + + +@dataclass +class CalicoProperty: + uprn: int + assets: List[CalicoAssetCondition] diff --git a/tests/condition/mapping/test_calico_mapper.py b/tests/condition/mapping/test_calico_mapper.py new file mode 100644 index 000000000..c9e15c2e7 --- /dev/null +++ b/tests/condition/mapping/test_calico_mapper.py @@ -0,0 +1,36 @@ +from datetime import date + +from domain.condition.aspect_type import AspectType +from domain.condition.element_type import ElementType +from domain.condition.mapping.calico.calico_mapper import CalicoMapper +from domain.condition.records.calico.calico_asset_condition import CalicoAssetCondition +from domain.condition.records.calico.calico_property import CalicoProperty + + +def test_calico_mapper_sets_renewal_year_to_export_year_plus_remaining_life(): + # Arrange + calico_property = CalicoProperty( + uprn=100093305101, + assets=[ + CalicoAssetCondition( + asset_reference=443, + path="Roof Covering", + covering_type="Concrete Tiles", + fitted_date=date(1998, 4, 1), + quantity=55, + remaining_life=36, + ) + ], + ) + export_year = 2026 + + # Act + survey = CalicoMapper().map_asset_conditions_for_property( + calico_property, export_year + ) + + # Assert + roof_material = survey.elements[0].aspect_conditions[0] + assert roof_material.aspect_type is AspectType.MATERIAL + assert survey.elements[0].element_type is ElementType.ROOF + assert roof_material.renewal_year == export_year + 36