From d164850dd38ab3e6496a2f7331a43f5a25836a46 Mon Sep 17 00:00:00 2001 From: Khalim Conn-Kowlessar Date: Thu, 4 Jun 2026 14:37:50 +0000 Subject: [PATCH] =?UTF-8?q?S0380.220:=20map=20API=20floor=5Fconstruction?= =?UTF-8?q?=20code=200=20=E2=86=92=20None=20(unknown/N-A)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The 2026 sample's second-largest mapper raise: 37 certs lodge sap_floor_dimensions.floor_construction=0, which raised UnmappedApiCode and blocked the cert. Code 0 is the "not recorded / not applicable" sentinel — 33/37 pair it with floor_heat_loss=6 ("another dwelling below", an upper-floor flat with no ground floor to describe); the rest carry mixed Solid / unheated-space descriptions. There is no single construction to assert. Map code 0 → None, which defers to RdSAP 10 Table 19 ("where floor construction is unknown" → age-band default) — identical to how an unlodged floor_construction (the 993 None certs) is already handled, and honest about the absence (cf. the no-misleading-insulation_type rule). Empirically inert and validated: across all 37 code-0 certs the cascade floor W/K is byte-identical whether code 0 maps to None or to an explicit "Solid" string — the another-dwelling-below floors compute to 0.0 W/K (handled via floor_heat_loss + property_type=Flat + floors[].description, per the _API_FLOOR_HEAT_LOSS_TO_FLOOR_TYPE code-6 note), and the few genuine ground/unheated floors hit the same age-band default either way. All 37 now compute (were raising). Dict value type widened to Optional[str] for the None entry; helper already returns Optional[str]. §4 suite + schema-mapper tests green (pre-existing test_total_floor_area failure unrelated); mapper.py pyright unchanged at 32; new test suppresses reportPrivateUsage (net-zero). Co-Authored-By: Claude Opus 4.8 --- datatypes/epc/domain/mapper.py | 13 ++++++++++++- .../domain/tests/test_from_rdsap_schema.py | 19 +++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) 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