diff --git a/datatypes/epc/domain/mapper.py b/datatypes/epc/domain/mapper.py index b992254a..de7297a4 100644 --- a/datatypes/epc/domain/mapper.py +++ b/datatypes/epc/domain/mapper.py @@ -4868,6 +4868,16 @@ _ELMHURST_CYLINDER_INSULATION_LABEL_TO_SAP10: Dict[str, int] = { "Jacket": 2, } +# Elmhurst §15.1 "Insulated" labels for an uninsulated cylinder. These are +# lodged (not absent), but an uninsulated cylinder has no insulation *type* — +# per the no-misleading-insulation convention it maps to +# `cylinder_insulation_type = None` rather than naming a material. The lodged +# §15.1 "Insulation Thickness" (0 mm) carries the storage-loss signal the +# cascade's SAP 10.2 Table 2 dispatch needs. +_ELMHURST_CYLINDER_NO_INSULATION_LABELS: frozenset[str] = frozenset({ + "No Insulation", +}) + # Elmhurst §15.0 "Water Heating Fuel Type" labels that route to solid- # fuel Table 32 codes (Anthracite, House coal, Wood logs/pellets, etc.). @@ -4978,6 +4988,8 @@ def _elmhurst_cylinder_insulation_code( mapping dict — see `_elmhurst_cylinder_size_code` rationale.""" if not cylinder_present or cylinder_insulation_label is None: return None + if cylinder_insulation_label in _ELMHURST_CYLINDER_NO_INSULATION_LABELS: + return None code = _ELMHURST_CYLINDER_INSULATION_LABEL_TO_SAP10.get(cylinder_insulation_label) if code is None: raise UnmappedElmhurstLabel("cylinder_insulation", cylinder_insulation_label) diff --git a/tests/datatypes/epc/domain/test_mapper_cylinder_insulation.py b/tests/datatypes/epc/domain/test_mapper_cylinder_insulation.py new file mode 100644 index 00000000..01869baa --- /dev/null +++ b/tests/datatypes/epc/domain/test_mapper_cylinder_insulation.py @@ -0,0 +1,49 @@ +"""Mapper boundary: the Elmhurst §15.1 "Insulated" cylinder label. + +A cylinder lodged "No Insulation" is an uninsulated cylinder, not a mapper +gap. Per the no-misleading-insulation convention it maps to +`cylinder_insulation_type = None` (don't name an insulation material on an +uninsulated surface) rather than raising `UnmappedElmhurstLabel`. This +unblocks parsing every solar example cert (the solar `before` cert lodges +"No Insulation"). +""" + +from datatypes.epc.domain.mapper import ( + UnmappedElmhurstLabel, + _elmhurst_cylinder_insulation_code, # pyright: ignore[reportPrivateUsage] +) + + +def test_no_insulation_label_maps_to_none() -> None: + # Arrange + label = "No Insulation" + + # Act + code = _elmhurst_cylinder_insulation_code(label, cylinder_present=True) + + # Assert + assert code is None + + +def test_foam_label_still_maps_to_factory_code() -> None: + # Arrange + label = "Foam" + + # Act + code = _elmhurst_cylinder_insulation_code(label, cylinder_present=True) + + # Assert + assert code == 1 + + +def test_unknown_label_still_raises() -> None: + # Arrange + label = "Spray-on unicorn felt" + + # Act / Assert + try: + _elmhurst_cylinder_insulation_code(label, cylinder_present=True) + raised = False + except UnmappedElmhurstLabel: + raised = True + assert raised