feat(modelling): price sloping-ceiling + flat-roof measures in the catalogue

Slice 4 (ADR-0021). The roof dispatcher can now emit sloping_ceiling_insulation
and flat_roof_insulation, so wire both into contingencies and the sample
catalogue; the forcing-function test now asserts every generator measure type
is both priced and has a contingency rate, so an offline/live run over a
sloping or flat roof never dies on a missing entry.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Khalim Conn-Kowlessar 2026-06-04 21:20:38 +00:00
parent 13b18ce9fb
commit 1a807a4c4c
3 changed files with 11 additions and 2 deletions

View file

@ -8,6 +8,8 @@ extended as each measure type lands.
_CONTINGENCY_RATES: dict[str, float] = {
"cavity_wall_insulation": 0.10,
"loft_insulation": 0.10,
"sloping_ceiling_insulation": 0.10,
"flat_roof_insulation": 0.10,
"suspended_floor_insulation": 0.20,
"solid_floor_insulation": 0.26,
"mechanical_ventilation": 0.26,

View file

@ -1,6 +1,8 @@
{
"cavity_wall_insulation": { "unit_cost_per_m2": 18.5 },
"loft_insulation": { "unit_cost_per_m2": 12.0 },
"sloping_ceiling_insulation": { "unit_cost_per_m2": 40.0 },
"flat_roof_insulation": { "unit_cost_per_m2": 60.0 },
"suspended_floor_insulation": { "unit_cost_per_m2": 25.0 },
"solid_floor_insulation": { "unit_cost_per_m2": 45.0 },
"mechanical_ventilation": { "unit_cost_per_m2": 450.0 },

View file

@ -9,6 +9,7 @@ import pytest
from datatypes.epc.domain.epc import Epc
from datatypes.epc.domain.epc_property_data import EpcPropertyData
from domain.geospatial.planning_restrictions import PlanningRestrictions
from domain.modelling.contingencies import contingency_rate
from harness.console import DEFAULT_CATALOGUE, run_modelling, run_one
from repositories.product.product_json_repository import ProductJsonRepository
from tests.domain.modelling._elmhurst_recommendation import (
@ -25,6 +26,8 @@ _GENERATOR_MEASURE_TYPES = (
"external_wall_insulation",
"internal_wall_insulation",
"loft_insulation",
"sloping_ceiling_insulation",
"flat_roof_insulation",
"suspended_floor_insulation",
"solid_floor_insulation",
"mechanical_ventilation",
@ -113,10 +116,12 @@ def test_sample_catalogue_prices_every_generator_measure_type() -> None:
# Arrange — the default offline catalogue.
products: ProductJsonRepository = ProductJsonRepository(DEFAULT_CATALOGUE)
# Act / Assert — get() raises if a Measure Type is unpriced, so an offline
# run over arbitrary EPCs never dies on a missing catalogue entry.
# Act / Assert — get() and contingency_rate() each raise on a missing
# Measure Type, so an offline run over arbitrary EPCs never dies on a
# missing catalogue or contingency entry.
for measure_type in _GENERATOR_MEASURE_TYPES:
products.get(measure_type)
contingency_rate(measure_type)
def test_run_one_threads_a_current_market_value_onto_the_plan() -> None: