From 0d1ec2228d243096856f33cee93ae89fd6050437 Mon Sep 17 00:00:00 2001 From: Khalim Conn-Kowlessar Date: Thu, 11 Jun 2026 13:51:16 +0000 Subject: [PATCH] feat(modelling): cost data for secondary-heating-removal (ADR-0028) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- domain/modelling/contingencies.py | 4 ++++ harness/sample_catalogue.json | 3 ++- .../product/test_product_json_repository.py | 18 ++++++++++++++++++ 3 files changed, 24 insertions(+), 1 deletion(-) diff --git a/domain/modelling/contingencies.py b/domain/modelling/contingencies.py index 0483ebe2..4163b9ec 100644 --- a/domain/modelling/contingencies.py +++ b/domain/modelling/contingencies.py @@ -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, } diff --git a/harness/sample_catalogue.json b/harness/sample_catalogue.json index 02eb24eb..1eb5ad62 100644 --- a/harness/sample_catalogue.json +++ b/harness/sample_catalogue.json @@ -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 } } diff --git a/tests/repositories/product/test_product_json_repository.py b/tests/repositories/product/test_product_json_repository.py index a991f2a6..30ebfc94 100644 --- a/tests/repositories/product/test_product_json_repository.py +++ b/tests/repositories/product/test_product_json_repository.py @@ -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(