Fail loud on unmapped full-SAP opening-type codes 🟩

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jun-te Kim 2026-06-15 13:48:14 +00:00
parent dde98fb684
commit 8746eabb70
2 changed files with 35 additions and 1 deletions

View file

@ -53,6 +53,7 @@ from datatypes.epc.schema.sap_schema_17_1 import (
_SAP_OPENING_TYPE_WINDOW: Final[int] = 4
_SAP_OPENING_TYPE_ROOF_WINDOW: Final[int] = 5
_SAP_OPENING_TYPE_DOORS: Final[frozenset[int]] = frozenset({1, 2, 3})
_SAP_KNOWN_OPENING_TYPES: Final[frozenset[int]] = frozenset({1, 2, 3, 4, 5})
# SAP-typical glazing solar transmittance when an opening-type omits it.
_SAP_DEFAULT_SOLAR_TRANSMITTANCE: Final[float] = 0.63
# SAP-typical window frame factor when an opening-type omits it.
@ -639,6 +640,10 @@ class EpcPropertyDataMapper:
# Identity + scalars + fabric descriptions (D4) + openings (D2) are
# mapped; sap_building_parts (D1 perimeter) and habitable_rooms_count
# (D3 living-area) are still filled by later slices.
# D2: fail loud on any placed opening whose joined type is outside the
# known taxonomy {1/2/3 door, 4 window, 5 roof window}, rather than
# silently dropping it from the envelope.
_sap_assert_known_opening_types(schema)
door_count, insulated_door_u = _sap_door_aggregates(schema)
return EpcPropertyData(
uprn=schema.uprn,
@ -2433,6 +2438,20 @@ class EpcPropertyDataMapper:
# ---------------------------------------------------------------------------
def _sap_assert_known_opening_types(schema: SapSchema17_1) -> None:
"""D2: raise `UnmappedApiCode` if any placed opening joins to an
opening-type whose `type` code is outside the known taxonomy. Keeps a
new opening variant from silently vanishing from the envelope."""
types: Dict[Union[str, int], SapOpeningType_SAP_17_1] = {
ot.name: ot for ot in schema.sap_opening_types
}
for bp in schema.sap_building_parts:
for op in bp.sap_openings:
ot = types.get(op.type)
if ot is not None and ot.type not in _SAP_KNOWN_OPENING_TYPES:
raise UnmappedApiCode("sap_opening_type", ot.type)
def _sap_door_aggregates(schema: SapSchema17_1) -> Tuple[int, Optional[float]]:
"""D2: collapse door openings (opening-type 1/2/3) onto the engine's door
model a count and a single U-value. When a cert lodges doors with

View file

@ -17,7 +17,7 @@ from typing import Any, Dict
import pytest
from datatypes.epc.domain.epc_property_data import EpcPropertyData
from datatypes.epc.domain.mapper import EpcPropertyDataMapper
from datatypes.epc.domain.mapper import EpcPropertyDataMapper, UnmappedApiCode
from datatypes.epc.schema.sap_schema_17_1 import SapSchema17_1
from datatypes.epc.schema.tests.helpers import from_dict
@ -192,3 +192,18 @@ class TestFromSapSchema17_1RoofWindows:
# A ground-floor flat lodges no roof windows.
result = self._map("sap_17_1.json")
assert not result.sap_roof_windows
class TestFromSapSchema17_1UnknownOpeningType:
"""Slice 4d (D2): an opening-type code outside {1,2,3 door, 4 window,
5 roof window} must fail loud rather than silently drop the opening."""
def test_unmapped_opening_type_raises(self) -> None:
# Arrange — corrupt the placed window opening-type to an unknown code
data = load("sap_17_1.json")
data["sap_opening_types"][1]["type"] = 99 # was 4 (window), now unmapped
schema = from_dict(SapSchema17_1, data)
# Act / Assert
with pytest.raises(UnmappedApiCode):
EpcPropertyDataMapper.from_sap_schema_17_1(schema)