diff --git a/datatypes/epc/domain/mapper.py b/datatypes/epc/domain/mapper.py index 037bf833a..74d08e4cb 100644 --- a/datatypes/epc/domain/mapper.py +++ b/datatypes/epc/domain/mapper.py @@ -6260,7 +6260,9 @@ def _map_sap_heating( if cylinder_present else None ), - cylinder_insulation_type=heating.water_heating.insulation_type, + cylinder_insulation_type=_pashub_cylinder_insulation_type_code( + heating.water_heating.insulation_type, cylinder_present + ), cylinder_insulation_thickness_mm=heating.water_heating.insulation_thickness_mm, water_heating_code=water_heating_code, water_heating_fuel=water_heating_fuel, @@ -6370,6 +6372,31 @@ def _pashub_cylinder_size_code( return code +# PasHub surveyed §Water-Heating cylinder "Insulation Type:" label → SAP10 +# `cylinder_insulation_type` cascade code (foam=1 / jacket=2, mirroring +# `_ELMHURST_CYLINDER_INSULATION_LABEL_TO_SAP10`). Passed through raw, the +# storage-loss resolver's `_int_or_none` yields None and a real lodged +# cylinder is billed ZERO storage loss. +_PASHUB_CYLINDER_INSULATION_TO_SAP10: Dict[str, int] = { + "Factory fitted": 1, # factory-applied foam +} + + +def _pashub_cylinder_insulation_type_code( + insulation_label: Optional[str], cylinder_present: bool +) -> Optional[int]: + """Resolve a PasHub surveyed cylinder insulation label to the SAP10 code + at the mapper boundary (ADR-0015). Returns None when no cylinder is + present or the label is genuinely absent; any uncovered non-empty label + strict-raises (mirroring `_pashub_cylinder_size_code`).""" + if not cylinder_present or not insulation_label: + return None + code = _PASHUB_CYLINDER_INSULATION_TO_SAP10.get(insulation_label) + if code is None: + raise UnmappedPasHubLabel("cylinder insulation", insulation_label) + return code + + def _map_sap_ventilation(ventilation: Ventilation) -> SapVentilation: return SapVentilation( ventilation_type=ventilation.ventilation_type,