From 02ad8701d38275daa0dd9dac087452fa7bcd8cef Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Tue, 14 Jul 2026 14:29:26 +0000 Subject: [PATCH] =?UTF-8?q?Normalize=20PasHub=20main-heating=20Mains=20gas?= =?UTF-8?q?=20to=20SAP=20fuel=20code=2026=20=F0=9F=9F=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- datatypes/epc/domain/mapper.py | 24 ++++++++++++++++++- .../epc/domain/tests/test_from_site_notes.py | 2 +- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/datatypes/epc/domain/mapper.py b/datatypes/epc/domain/mapper.py index ad9883bc3..e9e72ab6c 100644 --- a/datatypes/epc/domain/mapper.py +++ b/datatypes/epc/domain/mapper.py @@ -6027,7 +6027,7 @@ def _map_sap_heating( main_heating_details=[ MainHeatingDetail( has_fghrs=main.flue_gas_heat_recovery_system, - main_fuel_type=fuel_type, + main_fuel_type=_pashub_main_fuel_code(fuel_type), heat_emitter_type=main.emitter, emitter_temperature=main.emitter_temperature, fan_flue_present=main.fan_assist, @@ -7030,6 +7030,28 @@ def _elmhurst_main_fuel_int(fuel_type: str) -> Optional[int]: return _ELMHURST_MAIN_FUEL_TO_SAP10.get(fuel_type) +# PasHub Site-Notes surveyed main-heating "Fuel" labels mapped to SAP10 fuel +# codes (the epc-codes `main_fuel` enum the calculator's resolver routes via +# its API→Table-12/32 translation). Seeded only with labels confirmed from +# live PasHub fixtures; a dedicated lookup rather than reuse of the Elmhurst +# map, which carries legacy label oddities PasHub must not inherit. New labels +# are a one-line addition — until then they strict-raise at the boundary +# (ADR-0015) rather than being silently mis-billed downstream. +_PASHUB_MAIN_FUEL_TO_SAP10: Dict[str, int] = { + "Mains gas": 26, # matches the Elmhurst mapper's mains-gas code + "Electricity": 30, # the standard-electricity fuel code +} + + +def _pashub_main_fuel_code(fuel_label: str) -> Union[int, str]: + """Resolve a PasHub surveyed main-heating Fuel label to a SAP10 fuel code + at the mapper boundary (ADR-0015). A genuinely blank label is the "no main + heating" shape and passes through unchanged.""" + if not fuel_label: + return fuel_label + return _PASHUB_MAIN_FUEL_TO_SAP10.get(fuel_label, fuel_label) + + def _resolve_elmhurst_underfloor_subtype( main_floor: ElmhurstFloorDetails, main_age_band: str, diff --git a/datatypes/epc/domain/tests/test_from_site_notes.py b/datatypes/epc/domain/tests/test_from_site_notes.py index ad18a969e..81670e239 100644 --- a/datatypes/epc/domain/tests/test_from_site_notes.py +++ b/datatypes/epc/domain/tests/test_from_site_notes.py @@ -379,7 +379,7 @@ class TestFromSiteNotesExample1: main_heating_details=[ MainHeatingDetail( has_fghrs=False, - main_fuel_type="Mains gas", + main_fuel_type=26, heat_emitter_type="Radiators", emitter_temperature="Unknown", fan_flue_present=True,