mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-30 13:10:47 +00:00
feat(modelling): classify ASHP existing system by fuel code
Slice 8 of ADR-0025 costing. _existing_system keys on the heating fuel code, not the mains_gas flag -- the 001431 electric fixtures all lodge mains_gas=True (gas available at the property) while heating electrically (fuel 30), which the flag-based check misread as gas (and would have wrongly reused a non-existent wet system). Electric/gas/oil/LPG map to their categories; empty details -> NONE; unrecognised -> OTHER (gas-line fallback). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
f182f36802
commit
6f136a8d6a
2 changed files with 72 additions and 15 deletions
|
|
@ -125,9 +125,11 @@ _KW_PER_M2 = 0.05
|
|||
# latter three from habitable rooms); fallback ~1 radiator per 13 m2.
|
||||
_RADIATOR_ROOM_OFFSET = 3
|
||||
_RADIATOR_M2_PER_RADIATOR = 13.0
|
||||
# SAP main-heating code lodged when a dwelling has no heating system.
|
||||
_NO_SYSTEM_SAP_CODE = 999
|
||||
# main_fuel_type codes (gov API enum and/or Table 12) for off-gas wet fuels.
|
||||
# main_fuel_type codes (gov API enum and/or Table 12) by fuel. Classification
|
||||
# keys on the heating *fuel*, NOT the `mains_gas` flag — that flag means gas is
|
||||
# available at the property, which is True even for electrically-heated dwellings
|
||||
# on a gas street (every 001431 electric fixture lodges mains_gas=True).
|
||||
_GAS_FUEL_CODES = frozenset({26, 1})
|
||||
_OIL_FUEL_CODES = frozenset({28, 4, 71, 73, 75, 76})
|
||||
_LPG_FUEL_CODES = frozenset({27, 2, 3, 5, 9})
|
||||
|
||||
|
|
@ -149,21 +151,23 @@ def ashp_cost_inputs(epc: EpcPropertyData) -> AshpCostInputs:
|
|||
|
||||
|
||||
def _existing_system(epc: EpcPropertyData) -> AshpExistingSystem:
|
||||
"""Classify the dwelling's pre-retrofit system for decommission + reuse.
|
||||
Mains gas is the most reliable signal (`mains_gas`); electricity keys on the
|
||||
fuel code; oil/LPG on their fuel codes; an absent system on SAP code 999.
|
||||
The storage-vs-other-electric split is deliberately not made — both price
|
||||
the same decommission line (ADR-0025)."""
|
||||
main: MainHeatingDetail = epc.sap_heating.main_heating_details[0]
|
||||
if main.sap_main_heating_code == _NO_SYSTEM_SAP_CODE:
|
||||
"""Classify the dwelling's pre-retrofit system for decommission + reuse,
|
||||
keyed on the heating *fuel code* (not the misleading `mains_gas` flag).
|
||||
Electricity, gas, oil and LPG map to their categories; a dwelling with no
|
||||
lodged main system to NONE; anything unrecognised to OTHER (which prices the
|
||||
gas-line decommission fallback). The storage-vs-other-electric split is
|
||||
deliberately not made — both price the same decommission line (ADR-0025)."""
|
||||
details: list[MainHeatingDetail] = epc.sap_heating.main_heating_details
|
||||
if not details:
|
||||
return AshpExistingSystem.NONE
|
||||
if epc.sap_energy_source.mains_gas:
|
||||
return AshpExistingSystem.GAS
|
||||
if main.main_fuel_type == _ELECTRICITY_FUEL:
|
||||
fuel = details[0].main_fuel_type
|
||||
if fuel == _ELECTRICITY_FUEL:
|
||||
return AshpExistingSystem.ELECTRIC_STORAGE
|
||||
if main.main_fuel_type in _OIL_FUEL_CODES:
|
||||
if fuel in _GAS_FUEL_CODES:
|
||||
return AshpExistingSystem.GAS
|
||||
if fuel in _OIL_FUEL_CODES:
|
||||
return AshpExistingSystem.OIL
|
||||
if main.main_fuel_type in _LPG_FUEL_CODES:
|
||||
if fuel in _LPG_FUEL_CODES:
|
||||
return AshpExistingSystem.LPG
|
||||
return AshpExistingSystem.OTHER
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,9 @@ size band, design heat loss (floor-area proxy), radiator count, and whether a
|
|||
wet system can be reused — the catalogue math (Products) stays EPC-free.
|
||||
"""
|
||||
|
||||
import copy
|
||||
|
||||
from datatypes.epc.domain.epc_property_data import EpcPropertyData
|
||||
from domain.modelling.generators.heating_recommendation import ashp_cost_inputs
|
||||
from domain.modelling.products import AshpCostInputs, AshpExistingSystem
|
||||
from tests.domain.modelling._elmhurst_recommendation import (
|
||||
|
|
@ -29,3 +32,53 @@ def test_mains_gas_dwelling_maps_to_a_reusable_wet_gas_system() -> None:
|
|||
assert abs(inputs.design_heat_loss_kw - 4.5) <= 1e-9
|
||||
assert inputs.radiator_count == 10
|
||||
assert inputs.has_reusable_wet_system is True
|
||||
|
||||
|
||||
def test_electric_dwelling_has_no_reusable_wet_system() -> None:
|
||||
# Arrange — an electric storage-heater dwelling (no wet system).
|
||||
epc = parse_recommendation_summary(
|
||||
"hhr_storage_from_electric_storage_001431_before.pdf"
|
||||
)
|
||||
|
||||
# Act
|
||||
inputs: AshpCostInputs = ashp_cost_inputs(epc)
|
||||
|
||||
# Assert — electric, so a full new wet distribution is needed.
|
||||
assert inputs.existing_system is AshpExistingSystem.ELECTRIC_STORAGE
|
||||
assert inputs.has_reusable_wet_system is False
|
||||
|
||||
|
||||
def test_classification_keys_on_fuel_not_the_mains_gas_flag() -> None:
|
||||
# Arrange — the 001431 electric fixtures all lodge mains_gas=True (gas is
|
||||
# available at the property) while heating electrically (fuel 30). The
|
||||
# classifier must key on the fuel, not the flag, or it would misread these
|
||||
# as gas and wrongly reuse a non-existent wet system.
|
||||
epc = parse_recommendation_summary(
|
||||
"hhr_storage_from_electric_storage_001431_before.pdf"
|
||||
)
|
||||
|
||||
# Act / Assert
|
||||
assert epc.sap_energy_source.mains_gas is True
|
||||
inputs: AshpCostInputs = ashp_cost_inputs(epc)
|
||||
assert inputs.existing_system is AshpExistingSystem.ELECTRIC_STORAGE
|
||||
assert inputs.has_reusable_wet_system is False
|
||||
|
||||
|
||||
def test_oil_and_lpg_dwellings_are_reusable_wet_systems() -> None:
|
||||
# Arrange — no oil/LPG fixture exists, so mutate an off-gas dwelling's main
|
||||
# fuel to oil (28) and LPG (27); both are wet boiler systems.
|
||||
base: EpcPropertyData = parse_recommendation_summary(
|
||||
"hhr_storage_from_electric_storage_001431_before.pdf"
|
||||
)
|
||||
|
||||
def _with_fuel(code: int) -> EpcPropertyData:
|
||||
clone: EpcPropertyData = copy.deepcopy(base)
|
||||
clone.sap_energy_source.mains_gas = False
|
||||
clone.sap_heating.main_heating_details[0].main_fuel_type = code
|
||||
clone.sap_heating.main_heating_details[0].sap_main_heating_code = 199
|
||||
return clone
|
||||
|
||||
# Act / Assert
|
||||
assert ashp_cost_inputs(_with_fuel(28)).existing_system is AshpExistingSystem.OIL
|
||||
assert ashp_cost_inputs(_with_fuel(27)).existing_system is AshpExistingSystem.LPG
|
||||
assert ashp_cost_inputs(_with_fuel(28)).has_reusable_wet_system is True
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue