Map full-SAP opening-type 6 (rooflight) as a roof window

5 modelling_e2e cohort certs (full-SAP 19.1.0) were skipped with
"unmapped API sap_opening_type code: 6". Code 6 is "Ext Rooflight"; SAP 10.2
treats roof windows and rooflights as the same inclined-glazing family.

Fix: add 6 to the known opening-type taxonomy and route it onto the roof-window
path (`sap_roof_windows`) alongside code 5, via a `_SAP_ROOF_WINDOW_TYPES`
{5, 6} set. Genuinely-unknown codes (e.g. 99) still raise.

Cert 9878-3908-6309-6714-8200 now maps + calculates (sap 81). Regression test:
a type-6 opening maps onto sap_roof_windows.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jun-te Kim 2026-06-24 10:47:33 +00:00
parent 22cb47a280
commit 789103af27
2 changed files with 22 additions and 2 deletions

View file

@ -56,8 +56,12 @@ from datatypes.epc.schema.sap_schema_17_1 import (
# full-SAP opening-type codes: 1/2/3 = door, 4 = window, 5 = roof window.
_SAP_OPENING_TYPE_WINDOW: Final[int] = 4
_SAP_OPENING_TYPE_ROOF_WINDOW: Final[int] = 5
# 6 = "Ext Rooflight" (seen on full-SAP 19.1.0 certs) — SAP 10.2 treats roof
# windows and rooflights as the same inclined-glazing family, so model it on the
# roof-window path alongside code 5.
_SAP_ROOF_WINDOW_TYPES: Final[frozenset[int]] = frozenset({5, 6})
_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_KNOWN_OPENING_TYPES: Final[frozenset[int]] = frozenset({1, 2, 3, 4, 5, 6})
# full-SAP wall_type codes: 1/2/3 = external (exposed), 4 = party, 5 = internal.
_SAP_WALL_TYPES_EXPOSED: Final[frozenset[int]] = frozenset({1, 2, 3})
_SAP_WALL_TYPE_PARTY: Final[int] = 4
@ -864,7 +868,7 @@ class EpcPropertyDataMapper:
for bp in schema.sap_building_parts:
for op in bp.sap_openings:
ot = types.get(op.type)
if ot is None or ot.type != _SAP_OPENING_TYPE_ROOF_WINDOW:
if ot is None or ot.type not in _SAP_ROOF_WINDOW_TYPES:
continue
roof_windows.append(
SapRoofWindow(

View file

@ -245,6 +245,22 @@ class TestFromSapSchema17_1RoofWindows:
result = self._map("sap_17_1.json")
assert not result.sap_roof_windows
def test_rooflight_opening_type_6_maps_as_roof_window(self) -> None:
# SAP-19.1.0 certs lodge rooflights as opening-type 6 ("Ext Rooflight").
# SAP 10.2 treats roof windows and rooflights as the same inclined-glazing
# family, so code 6 maps onto sap_roof_windows like code 5 — previously it
# raised UnmappedApiCode and skipped the whole cert.
data = load("sap_17_1_house.json")
for ot in data["sap_opening_types"]:
if ot["type"] == 5:
ot["type"] = 6
schema = from_dict(SapSchema17_1, data)
result = EpcPropertyDataMapper.from_sap_schema_17_1(schema)
assert result.sap_roof_windows is not None
assert len(result.sap_roof_windows) == 2
class TestFromSapSchema17_1UnknownOpeningType:
"""Slice 4d (D2): an opening-type code outside {1,2,3 door, 4 window,