diff --git a/datatypes/epc/domain/mapper.py b/datatypes/epc/domain/mapper.py index 902b05a4..5c54b9fc 100644 --- a/datatypes/epc/domain/mapper.py +++ b/datatypes/epc/domain/mapper.py @@ -43,7 +43,10 @@ from datatypes.epc.schema.rdsap_schema_17_1 import ( RdSapSchema17_1, EnergyElement as EnergyElement_17_1, ) -from datatypes.epc.schema.sap_schema_17_1 import SapSchema17_1 +from datatypes.epc.schema.sap_schema_17_1 import ( + SapSchema17_1, + EnergyElement as EnergyElement_SAP_17_1, +) from datatypes.epc.schema.rdsap_schema_18_0 import ( RdSapSchema18_0, EnergyElement as EnergyElement_18_0, @@ -654,9 +657,14 @@ class EpcPropertyDataMapper: cfl_fixed_lighting_bulbs_count=0, led_fixed_lighting_bulbs_count=0, incandescent_fixed_lighting_bulbs_count=0, - roofs=[], - walls=[], - floors=[], + # D4: full SAP lodges the measured U as text in the element + # description ("Average thermal transmittance X W/m²K"); carry it + # through so u_wall/u_floor/u_roof parse it instead of re-deriving + # from a fabricated age band. "(other premises above/below)" + # sentinels survive untouched (bordering dwelling → no heat loss). + roofs=EpcPropertyDataMapper._map_energy_elements(schema.roofs), + walls=EpcPropertyDataMapper._map_energy_elements(schema.walls), + floors=EpcPropertyDataMapper._map_energy_elements(schema.floors), main_heating=[], sap_windows=[], sap_building_parts=[], @@ -2219,6 +2227,7 @@ class EpcPropertyDataMapper: EnergyElement_20_0, EnergyElement_21_0, EnergyElement_21_0_1, + EnergyElement_SAP_17_1, ], ) -> EnergyElement: description = ( @@ -2243,6 +2252,7 @@ class EpcPropertyDataMapper: EnergyElement_20_0, EnergyElement_21_0, EnergyElement_21_0_1, + EnergyElement_SAP_17_1, ] ], ) -> List[EnergyElement]: diff --git a/datatypes/epc/domain/tests/test_from_sap_schema.py b/datatypes/epc/domain/tests/test_from_sap_schema.py index 715040c1..fe31b8e3 100644 --- a/datatypes/epc/domain/tests/test_from_sap_schema.py +++ b/datatypes/epc/domain/tests/test_from_sap_schema.py @@ -75,3 +75,34 @@ class TestFromSapSchema17_1Tracer: # Assert assert result.uprn == 10092973954 assert result.total_floor_area_m2 == 68.0 + + +class TestFromSapSchema17_1FabricDescriptions: + """Slice 3 (D4): the measured-U fabric descriptions flow through to + epc.walls/floors/roofs so the engine's u_wall/u_floor/u_roof can parse + 'Average thermal transmittance X W/m²K' instead of re-deriving from a band.""" + + @pytest.fixture + def sample(self) -> EpcPropertyData: + schema = from_dict(SapSchema17_1, load("sap_17_1.json")) + return EpcPropertyDataMapper.from_sap_schema_17_1(schema) + + def test_wall_description_carries_measured_u(self, sample: EpcPropertyData) -> None: + assert sample.walls[0].description == "Average thermal transmittance 0.17 W/m²K" + + def test_floor_description_carries_measured_u(self, sample: EpcPropertyData) -> None: + assert sample.floors[0].description == "Average thermal transmittance 0.13 W/m²K" + + def test_roof_description_carries_other_premises( + self, sample: EpcPropertyData + ) -> None: + # A ground-floor flat with premises above → no roof heat loss; the + # "(other premises above)" sentinel must survive so the engine treats + # it as internal rather than parsing a measured U. + assert sample.roofs[0].description == "(other premises above)" + + @pytest.mark.parametrize("fixture", _ALL_FIXTURES) + def test_every_fixture_has_wall_descriptions(self, fixture: str) -> None: + schema = from_dict(SapSchema17_1, load(fixture)) + result = EpcPropertyDataMapper.from_sap_schema_17_1(schema) + assert result.walls and result.walls[0].description diff --git a/datatypes/epc/schema/sap_schema_17_1.py b/datatypes/epc/schema/sap_schema_17_1.py index 3b8995cf..bc332a78 100644 --- a/datatypes/epc/schema/sap_schema_17_1.py +++ b/datatypes/epc/schema/sap_schema_17_1.py @@ -10,11 +10,24 @@ raises only on a missing *required* field. """ from dataclasses import dataclass -from typing import Union +from typing import List, Union from .common import DescriptionV1 +@dataclass +class EnergyElement: + """A fabric/system element with its lodged description. On full SAP the + `description` carries the measured U-value as text, e.g. "Average thermal + transmittance 0.17 W/m²K" (parsed downstream by u_wall/u_floor/u_roof), + or "(other premises above/below)" for an element bordering another + dwelling (no heat loss).""" + + description: Union[str, DescriptionV1] + energy_efficiency_rating: int + environmental_efficiency_rating: int + + @dataclass class SapSchema17_1: uprn: int @@ -30,3 +43,6 @@ class SapSchema17_1: inspection_date: str has_hot_water_cylinder: str has_fixed_air_conditioning: str + roofs: List[EnergyElement] + walls: List[EnergyElement] + floors: List[EnergyElement]