From 58e218650666766f7d9695e2bfdad6ab6571908b Mon Sep 17 00:00:00 2001 From: Khalim Conn-Kowlessar Date: Fri, 24 Jul 2026 09:18:56 +0000 Subject: [PATCH] =?UTF-8?q?Re-point=20a=20fuel-agnostic=20gas=20boiler=20t?= =?UTF-8?q?o=20oil=20or=20solid=20fuel=20by=20main=5Ffuel=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) --- .../property/landlord_override_overlays.py | 49 ++++++++++++++++++- 1 file changed, 47 insertions(+), 2 deletions(-) diff --git a/repositories/property/landlord_override_overlays.py b/repositories/property/landlord_override_overlays.py index 3d1fcdce5..e987f7b26 100644 --- a/repositories/property/landlord_override_overlays.py +++ b/repositories/property/landlord_override_overlays.py @@ -106,12 +106,57 @@ _FUEL_FAMILY: dict[int, str] = { 27: "lpg", 3: "lpg", 17: "lpg", 28: "oil", 29: "electricity", 25: "electricity", - 33: "solid", 15: "solid", 10: "solid", 31: "solid", + # Solid fuels — house coal 33, smokeless 15, dual mineral/wood 10, plus wood + # logs (main_fuel_overlay code 6): omitting 6 left a wood-logs dwelling with no + # fuel family, so `resolve_boiler_archetype` never re-pointed it and + # `flag_fuel_mismatch` never checked it (ADR-0067). + 33: "solid", 15: "solid", 10: "solid", 31: "solid", 6: "solid", } +# A fuel-agnostic gas wet-boiler archetype re-points to the oil / solid-fuel +# member matching the property's `main_fuel` family (ADR-0067). Oil preserves the +# regular/combi split; any solid fuel (coal / wood / dual) collapses to the single +# `Solid fuel boiler` member. Gas/LPG (RdSAP models LPG on gas), electric, and a +# missing fuel leave the archetype unchanged, as does any non-gas-boiler archetype. +_GAS_BOILER_REPOINT: dict[tuple[str, str], str] = { + ("Gas boiler, regular", "oil"): "Oil boiler, regular", + ("Gas boiler, combi", "oil"): "Oil boiler, combi", + ("Gas boiler, regular", "solid"): "Solid fuel boiler", + ("Gas boiler, combi", "solid"): "Solid fuel boiler", +} + + +def _fuel_family_of(fuel_value: Optional[str]) -> Optional[str]: + """The coarse fuel family a landlord `main_fuel` value resolves to (via the + fuel overlay's RdSAP code), or None when absent / unmapped.""" + if fuel_value is None: + return None + fuel_overlay = fuel_overlay_for(fuel_value, 0) + code = ( + fuel_overlay.heating.main_fuel_type + if fuel_overlay is not None and fuel_overlay.heating is not None + else None + ) + if not isinstance(code, int): + return None + return _FUEL_FAMILY.get(code) + + def resolve_boiler_archetype(heating_value: str, fuel_value: Optional[str]) -> str: - raise NotImplementedError + """Re-point a fuel-agnostic gas wet-boiler archetype to the oil / solid-fuel + member matching the property's `main_fuel` (ADR-0067). + + The landlord's boiler description cannot name its fuel, so the LLM defaults a + plain "Boiler … Regular/Combi" to the gas archetype; this join corrects it at + resolution using the sibling `main_fuel` override — the same rule as the + per-property backfill. Non-gas-boiler archetypes, gas/LPG fuels, and a missing + fuel are returned unchanged. Extensible to a future `Boiler, unspecified fuel` + member (fuel override → EPC → default gas).""" + family = _fuel_family_of(fuel_value) + if family is None: + return heating_value + return _GAS_BOILER_REPOINT.get((heating_value, family), heating_value) def _override_value(overrides: ResolvedPropertyOverrides, component: str) -> Optional[str]: