"""Per-Measure-Type contingency rates. The one cost component carried separately from a Product's fully-loaded total (CONTEXT.md). Mirrors the legacy `recommendations/Costs.py::Costs.CONTINGENCIES`; 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, "external_wall_insulation": 0.26, "internal_wall_insulation": 0.26, "double_glazing": 0.15, "secondary_glazing": 0.15, "low_energy_lighting": 0.26, "high_heat_retention_storage_heaters": 0.10, "air_source_heat_pump": 0.25, "gas_boiler_upgrade": 0.26, "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, } def contingency_rate(measure_type: str) -> float: """Return the contingency rate for a Measure Type, raising if unknown (strict — do not silently default, per the repo's strict-raise convention).""" try: return _CONTINGENCY_RATES[measure_type] except KeyError as exc: raise ValueError( f"no contingency rate configured for measure type {measure_type!r}" ) from exc