Merge pull request #1292 from Hestia-Homes/fix/sap-opening-type-6

Map full-SAP opening-type 6 (rooflight) as a roof window (5 cohort certs)
This commit is contained in:
Jun-te Kim 2026-06-24 11:58:53 +01:00 committed by GitHub
commit e4a79648ab
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
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,