feat(modelling): cost data for secondary-heating-removal (ADR-0028)

Flat per-dwelling decommission price (sample_catalogue \£250) + 0.25 contingency
(covers unknown heater count / hard-wired-vs-plugged / repaint extent). The JSON
repo joins the contingency from config, proven by the new repo test. No composite
Products machinery — a lodged secondary is one roughly-fixed job, not room-scaled.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Khalim Conn-Kowlessar 2026-06-11 13:51:16 +00:00
parent ae7959f57c
commit 0d1ec2228d
3 changed files with 24 additions and 1 deletions

View file

@ -24,6 +24,10 @@ _CONTINGENCY_RATES: dict[str, float] = {
"system_tune_up": 0.10,
"system_tune_up_zoned": 0.10,
"solar_pv": 0.15,
# Decommissioning a fixed secondary heater + localised making-good is small,
# uncertain work: the rate covers the unknown heater count / hard-wired vs
# plugged status / repaint extent (ADR-0028).
"secondary_heating_removal": 0.25,
}

View file

@ -16,5 +16,6 @@
"gas_boiler_upgrade": { "unit_cost_per_m2": 3000.0 },
"system_tune_up": { "unit_cost_per_m2": 500.0 },
"system_tune_up_zoned": { "unit_cost_per_m2": 900.0 },
"solar_pv": { "unit_cost_per_m2": 0.0 }
"solar_pv": { "unit_cost_per_m2": 0.0 },
"secondary_heating_removal": { "unit_cost_per_m2": 250.0 }
}

View file

@ -50,6 +50,24 @@ def test_get_raises_when_measure_type_absent(tmp_path: Path) -> None:
ProductJsonRepository(catalogue).get("cavity_wall_insulation")
def test_get_joins_the_secondary_heating_removal_contingency(tmp_path: Path) -> None:
# Arrange — a flat per-dwelling decommission price for the removal measure
# (ADR-0028); the 0.25 contingency is joined from config, not the file.
catalogue: Path = _write_catalogue(
tmp_path, {"secondary_heating_removal": {"unit_cost_per_m2": 250.0}}
)
# Act
product: Product = ProductJsonRepository(catalogue).get(
"secondary_heating_removal"
)
# Assert
assert product.measure_type == "secondary_heating_removal"
assert abs(product.unit_cost_per_m2 - 250.0) <= 1e-9
assert abs(product.contingency_rate - 0.25) <= 1e-9
def test_get_raises_when_entry_lacks_unit_cost(tmp_path: Path) -> None:
# Arrange
catalogue: Path = _write_catalogue(