Carry full-SAP measured fabric U-value descriptions into the domain model 🟩

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jun-te Kim 2026-06-15 13:10:05 +00:00
parent c3fd9a6872
commit 0eaf87b106
3 changed files with 62 additions and 5 deletions

View file

@ -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]:

View file

@ -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

View file

@ -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]