mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-30 13:10:47 +00:00
recommend_loft_insulation(epc, products) detects an uninsulated main loft
(SapBuildingPart.roof_insulation_thickness == 0) and emits a
Recommendation("Roof") with one loft_insulation Option carrying the overlay
(roof_insulation_thickness = 270 mm, the recommended top-up) and a priced
Cost (roof area x the Product's fully-loaded unit cost + contingency).
- building_geometry.roof_area(epc, identifier): the part's greatest
per-storey floor area (RdSAP 10 §3.8). Pinned 14.85 m^2 on 000490 MAIN.
- BuildingPartOverlay gains roof_insulation_thickness; the generic Overlay
Applicator writes it with NO change (validated by the tracer) — the
deep-module field-fold paying off.
- loft_insulation contingency (0.10) added.
Progress on #1158 (generator + geometry); end-to-end + Elmhurst pin pending
the orchestrator (#1157) and the parser fix. Four behaviour tests
(geometry pin; detect / none / cost). pyright strict clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
22 lines
771 B
Python
22 lines
771 B
Python
"""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,
|
|
}
|
|
|
|
|
|
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
|