Model/domain/epc/property_overlays/main_heating_system_overlay.py
Jun-te Kim 2dbbcf0c7b Decode old/slimline/convector storage-heater heating overrides 🟩
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 14:21:01 +00:00

46 lines
2 KiB
Python

"""Map a Landlord-Override main-heating-system value to a heating Simulation Overlay.
A main-heating-system value is one canonical system archetype ("Gas boiler,
combi", "Electric storage heaters, fan"). The calculator reads the primary
system's `sap_main_heating_code` (SAP Table 4a/4b), so the overlay maps the
archetype to a representative code and emits a whole-dwelling `HeatingOverlay`
targeting `main_heating_details[0]` (`building_part` is ignored). It composes
field-wise with the main_fuel / water_heating overlays.
The SEDBUK A-G efficiency band the Hyde "Heating" column carries is NOT honoured
yet (no efficiency slot on the overlay/MainHeatingDetail) -- archetypes map to
their modern/condensing Table 4b code, so an old low-rated boiler is currently
modelled at the condensing efficiency. Heat pumps and community heating (which
resolve via main_heating_index_number / community codes, not a Table 4b code)
are left UNKNOWN until modelled. Unresolvable values produce no overlay.
"""
from __future__ import annotations
from typing import Optional
from domain.modelling.simulation import EpcSimulation, HeatingOverlay
# Canonical system archetype → representative `sap_main_heating_code` (SAP Table
# 4b boiler rows / Table 4a). Codes map to the modern/condensing variant (A-G
# efficiency deferred): 102 regular condensing, 104 condensing combi, 120 CPSU,
# 404 fan storage heaters, 191 direct-acting electric boiler.
_MAIN_HEATING_CODES: dict[str, int] = {
"Gas boiler, combi": 104,
"Gas boiler, regular": 102,
"Gas CPSU": 120,
"Electric storage heaters, old": 401,
"Electric storage heaters, slimline": 402,
"Electric storage heaters, convector": 403,
"Electric storage heaters, fan": 404,
"Direct-acting electric": 191,
}
def main_heating_overlay_for(
main_heating_value: str, building_part: int
) -> Optional[EpcSimulation]:
code = _MAIN_HEATING_CODES.get(main_heating_value)
if code is None:
return None
return EpcSimulation(heating=HeatingOverlay(sap_main_heating_code=code))