feat(modelling): ASHP decommission cost by existing system

Slice 2 of ADR-0025 costing. _decommission maps the existing system to its
Southern Housing line: gas/oil flat 720, LPG 960 (tank+fuel removal),
electric-storage 570/840 by property-size band. Unmapped systems raise for
now -- the no-system/electric-other/other fallbacks land in the next slice.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Khalim Conn-Kowlessar 2026-06-06 20:36:12 +00:00
parent d23b84209d
commit cf7c2e017d
2 changed files with 44 additions and 5 deletions

View file

@ -29,6 +29,11 @@ _ASHP_MEASURE_TYPE = "air_source_heat_pump"
# Decommission an existing electric-storage system, by property size band. # Decommission an existing electric-storage system, by property size band.
_DECOMMISSION_ELECTRIC_STORAGE_SMALL = 570.0 _DECOMMISSION_ELECTRIC_STORAGE_SMALL = 570.0
_DECOMMISSION_ELECTRIC_STORAGE_LARGE = 840.0 _DECOMMISSION_ELECTRIC_STORAGE_LARGE = 840.0
# Decommission an existing wet (boiler) system — flat across property size for
# gas and oil; LPG carries the extra tank/fuel removal (ECOHT06-08, 03-04).
_DECOMMISSION_GAS = 720.0
_DECOMMISSION_OIL = 720.0
_DECOMMISSION_LPG = 960.0
# Heat-pump install (MONOBLOC, brand-neutral), by kW size band — design heat # Heat-pump install (MONOBLOC, brand-neutral), by kW size band — design heat
# loss is rounded up to the next band (ECOHT09-13). # loss is rounded up to the next band (ECOHT09-13).
@ -112,11 +117,19 @@ class Products:
return _PUMP_TOP_PRICE return _PUMP_TOP_PRICE
def _decommission(self, inputs: AshpCostInputs) -> float: def _decommission(self, inputs: AshpCostInputs) -> float:
return ( if inputs.existing_system is AshpExistingSystem.ELECTRIC_STORAGE:
_DECOMMISSION_ELECTRIC_STORAGE_SMALL return (
if inputs.is_small_property _DECOMMISSION_ELECTRIC_STORAGE_SMALL
else _DECOMMISSION_ELECTRIC_STORAGE_LARGE if inputs.is_small_property
) else _DECOMMISSION_ELECTRIC_STORAGE_LARGE
)
if inputs.existing_system is AshpExistingSystem.GAS:
return _DECOMMISSION_GAS
if inputs.existing_system is AshpExistingSystem.OIL:
return _DECOMMISSION_OIL
if inputs.existing_system is AshpExistingSystem.LPG:
return _DECOMMISSION_LPG
raise ValueError(f"no decommission rate for {inputs.existing_system}")
def _distribution(self, inputs: AshpCostInputs) -> float: def _distribution(self, inputs: AshpCostInputs) -> float:
radiators: int = max(_MIN_RADIATORS, min(_MAX_RADIATORS, inputs.radiator_count)) radiators: int = max(_MIN_RADIATORS, min(_MAX_RADIATORS, inputs.radiator_count))

View file

@ -37,3 +37,29 @@ def test_ashp_bundle_cost_composes_an_electric_storage_full_distribution_dwellin
# (7 rads) 3618 = 16290.60, with the separate 25% ASHP contingency. # (7 rads) 3618 = 16290.60, with the separate 25% ASHP contingency.
assert abs(cost.total - 16290.60) <= 1e-9 assert abs(cost.total - 16290.60) <= 1e-9
assert abs(cost.contingency_rate - 0.25) <= 1e-9 assert abs(cost.contingency_rate - 0.25) <= 1e-9
def _large_no_reuse(system: AshpExistingSystem) -> AshpCostInputs:
"""A large dwelling, 8 kW band, 8 radiators, no reusable wet system — so the
only thing varying with ``system`` is the decommission line."""
return AshpCostInputs(
existing_system=system,
is_small_property=False,
design_heat_loss_kw=8.0,
radiator_count=8,
has_reusable_wet_system=False,
)
def test_decommission_cost_varies_by_existing_system() -> None:
# Arrange — common: pump (8 kW) 9840 + cylinder 2382.60 + distribution (8
# rads) 4152 = 16374.60; only decommission differs by system.
products = Products()
common = 16374.60
# Act / Assert — gas and oil are flat 720; LPG 960; electric-storage large
# 840 (small 570 is pinned by the tracer above).
assert abs(products.ashp_bundle_cost(_large_no_reuse(AshpExistingSystem.GAS)).total - (common + 720.0)) <= 1e-9
assert abs(products.ashp_bundle_cost(_large_no_reuse(AshpExistingSystem.OIL)).total - (common + 720.0)) <= 1e-9
assert abs(products.ashp_bundle_cost(_large_no_reuse(AshpExistingSystem.LPG)).total - (common + 960.0)) <= 1e-9
assert abs(products.ashp_bundle_cost(_large_no_reuse(AshpExistingSystem.ELECTRIC_STORAGE)).total - (common + 840.0)) <= 1e-9