mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-30 13:10:47 +00:00
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:
parent
dde98fb684
commit
8746eabb70
2 changed files with 35 additions and 1 deletions
|
|
@ -53,6 +53,7 @@ from datatypes.epc.schema.sap_schema_17_1 import (
|
||||||
_SAP_OPENING_TYPE_WINDOW: Final[int] = 4
|
_SAP_OPENING_TYPE_WINDOW: Final[int] = 4
|
||||||
_SAP_OPENING_TYPE_ROOF_WINDOW: Final[int] = 5
|
_SAP_OPENING_TYPE_ROOF_WINDOW: Final[int] = 5
|
||||||
_SAP_OPENING_TYPE_DOORS: Final[frozenset[int]] = frozenset({1, 2, 3})
|
_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-typical glazing solar transmittance when an opening-type omits it.
|
||||||
_SAP_DEFAULT_SOLAR_TRANSMITTANCE: Final[float] = 0.63
|
_SAP_DEFAULT_SOLAR_TRANSMITTANCE: Final[float] = 0.63
|
||||||
# SAP-typical window frame factor when an opening-type omits it.
|
# 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
|
# Identity + scalars + fabric descriptions (D4) + openings (D2) are
|
||||||
# mapped; sap_building_parts (D1 perimeter) and habitable_rooms_count
|
# mapped; sap_building_parts (D1 perimeter) and habitable_rooms_count
|
||||||
# (D3 living-area) are still filled by later slices.
|
# (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)
|
door_count, insulated_door_u = _sap_door_aggregates(schema)
|
||||||
return EpcPropertyData(
|
return EpcPropertyData(
|
||||||
uprn=schema.uprn,
|
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]]:
|
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
|
"""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
|
model — a count and a single U-value. When a cert lodges doors with
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ from typing import Any, Dict
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from datatypes.epc.domain.epc_property_data import EpcPropertyData
|
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.sap_schema_17_1 import SapSchema17_1
|
||||||
from datatypes.epc.schema.tests.helpers import from_dict
|
from datatypes.epc.schema.tests.helpers import from_dict
|
||||||
|
|
||||||
|
|
@ -192,3 +192,18 @@ class TestFromSapSchema17_1RoofWindows:
|
||||||
# A ground-floor flat lodges no roof windows.
|
# A ground-floor flat lodges no roof windows.
|
||||||
result = self._map("sap_17_1.json")
|
result = self._map("sap_17_1.json")
|
||||||
assert not result.sap_roof_windows
|
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)
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue