From 8746eabb70397bcdf6d1e606330cfd11cd185b8d Mon Sep 17 00:00:00 2001 From: Jun-te Kim Date: Mon, 15 Jun 2026 13:48:14 +0000 Subject: [PATCH] =?UTF-8?q?Fail=20loud=20on=20unmapped=20full-SAP=20openin?= =?UTF-8?q?g-type=20codes=20=F0=9F=9F=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- datatypes/epc/domain/mapper.py | 19 +++++++++++++++++++ .../epc/domain/tests/test_from_sap_schema.py | 17 ++++++++++++++++- 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/datatypes/epc/domain/mapper.py b/datatypes/epc/domain/mapper.py index ce4ed2dc..712cccb9 100644 --- a/datatypes/epc/domain/mapper.py +++ b/datatypes/epc/domain/mapper.py @@ -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 diff --git a/datatypes/epc/domain/tests/test_from_sap_schema.py b/datatypes/epc/domain/tests/test_from_sap_schema.py index 066be8e0..51332a39 100644 --- a/datatypes/epc/domain/tests/test_from_sap_schema.py +++ b/datatypes/epc/domain/tests/test_from_sap_schema.py @@ -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)