S0380.220: map API floor_construction code 0 → None (unknown/N-A)

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 <noreply@anthropic.com>
This commit is contained in:
Khalim Conn-Kowlessar 2026-06-04 14:37:50 +00:00
parent c89bec42cb
commit d164850dd3
2 changed files with 31 additions and 1 deletions

View file

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

View file

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