diff --git a/datatypes/epc/domain/mapper.py b/datatypes/epc/domain/mapper.py index c79bb76b..f8a31ae4 100644 --- a/datatypes/epc/domain/mapper.py +++ b/datatypes/epc/domain/mapper.py @@ -2385,7 +2385,18 @@ def _api_party_wall_construction_int(value: Union[int, str, None]) -> Optional[i # takes the suspended U-value branch via the "Suspended" prefix yet # correctly fails the exact-match timber gate. Observed on 53/1000 of a # random 2026 API sample (was raising UnmappedApiCode, blocking the cert). -_API_FLOOR_CONSTRUCTION_TO_STR: Dict[int, str] = { +# +# Code 0 = "not recorded / not applicable" → None. It pairs +# overwhelmingly with floor_heat_loss=6 ("another dwelling below" — an +# upper-floor flat with no ground floor to describe) but also appears +# with mixed Solid / unheated-space descriptions, so there is no single +# construction to assert. None defers to RdSAP 10 Table 19 ("where floor +# construction is unknown" → age-band default), exactly as an unlodged +# floor_construction does. Empirically inert: floor W/K is identical to +# any explicit construction across all 37 code-0 certs in the 2026 +# sample (the heat loss is governed by floor_heat_loss, not this field). +_API_FLOOR_CONSTRUCTION_TO_STR: Dict[int, Optional[str]] = { + 0: None, 1: "Solid", 2: "Suspended timber", 3: "Suspended, not timber", diff --git a/datatypes/epc/domain/tests/test_from_rdsap_schema.py b/datatypes/epc/domain/tests/test_from_rdsap_schema.py index c3d59891..4f0b80ee 100644 --- a/datatypes/epc/domain/tests/test_from_rdsap_schema.py +++ b/datatypes/epc/domain/tests/test_from_rdsap_schema.py @@ -830,3 +830,22 @@ class TestApiFloorConstructionCode: # Assert assert result == "Suspended timber" + + def test_code_0_maps_to_none_unknown_construction(self) -> None: + # Arrange — code 0 is the "not recorded / not applicable" + # sentinel: it pairs overwhelmingly with floor_heat_loss=6 + # ("another dwelling below", an upper-floor flat with no ground + # floor to describe), but also appears with mixed Solid / unheated + # descriptions. There is no single construction to assert, so it + # maps to None — RdSAP 10 Table 19 ("where floor construction is + # unknown" → age-band default), the same treatment as an unlodged + # floor_construction. Empirically inert: floor W/K is identical to + # any explicit construction string across all observed code-0 + # certs (the heat loss is governed by floor_heat_loss, not this). + from datatypes.epc.domain.mapper import _api_floor_construction_str # pyright: ignore[reportPrivateUsage] + + # Act + result = _api_floor_construction_str(0) + + # Assert — no raise; None defers to the cascade's Table 19 default. + assert result is None