mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-08 11:17:27 +00:00
Expand half of the recommendation_materials retirement (ADR-0017). A Plan Measure installs a single Product, so thread its catalogue id end to end — Product.id -> MeasureOption.material_id -> PlanMeasure.material_id -> recommendation.material_id — replacing the per-material BOM child table with one nullable column on the row. ProductPostgresRepository reads the id from MaterialRow; the four fabric generators set it on their Option; the orchestrator carries it onto the Plan Measure; the mirror declares + maps the column. Optional throughout (the JSON stopgap catalogue carries no ids -> NULL). The multi-measure integration test now pins each persisted measure's material_id to its seeded MaterialRow id. Migration spec (live column must be added before this deploys; contraction is the owner's next step) in docs/migrations/recommendation-material-id.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
68 lines
2.4 KiB
Python
68 lines
2.4 KiB
Python
"""The wall Recommendation Generator.
|
|
|
|
Detects a treatable main wall on an EpcPropertyData and emits a Recommendation
|
|
whose Measure Option carries the Simulation Overlay for the intervention. No
|
|
scoring, no persistence — impact is produced later by scoring (ADR-0016).
|
|
"""
|
|
|
|
from typing import Optional
|
|
|
|
from datatypes.epc.domain.epc_property_data import (
|
|
BuildingPartIdentifier,
|
|
EpcPropertyData,
|
|
)
|
|
from domain.building_geometry import gross_heat_loss_wall_area
|
|
from domain.modelling.recommendation import Cost, MeasureOption, Recommendation
|
|
from domain.modelling.simulation import BuildingPartOverlay, EpcSimulation
|
|
from repositories.product.product_repository import ProductRepository
|
|
|
|
_CAVITY_MEASURE_TYPE = "cavity_wall_insulation"
|
|
|
|
# RdSAP 10 Table 5 wall_construction: 4 = "Cavity". Table 6
|
|
# wall_insulation_type: 4 = "as-built / assumed" (uninsulated), 2 = "Filled
|
|
# cavity" (the calculator's dedicated filled-cavity U row — see
|
|
# domain/sap10_ml/rdsap_uvalues.py u_wall).
|
|
_CAVITY_WALL_CONSTRUCTION = 4
|
|
_WALL_UNINSULATED = 4
|
|
_FILLED_CAVITY = 2
|
|
|
|
|
|
def recommend_cavity_wall(
|
|
epc: EpcPropertyData, products: ProductRepository
|
|
) -> Optional[Recommendation]:
|
|
"""Return a cavity-fill Recommendation for the main wall when it is an
|
|
uninsulated cavity wall, else None. The Option's cost is the heat-loss wall
|
|
area priced at the Product's fully-loaded unit cost, with its contingency."""
|
|
main = next(
|
|
part
|
|
for part in epc.sap_building_parts
|
|
if part.identifier is BuildingPartIdentifier.MAIN
|
|
)
|
|
|
|
if (
|
|
main.wall_construction != _CAVITY_WALL_CONSTRUCTION
|
|
or main.wall_insulation_type != _WALL_UNINSULATED
|
|
):
|
|
return None
|
|
|
|
product = products.get(_CAVITY_MEASURE_TYPE)
|
|
wall_area: float = gross_heat_loss_wall_area(epc, BuildingPartIdentifier.MAIN)
|
|
cost = Cost(
|
|
total=wall_area * product.unit_cost_per_m2,
|
|
contingency_rate=product.contingency_rate,
|
|
)
|
|
|
|
option = MeasureOption(
|
|
measure_type=_CAVITY_MEASURE_TYPE,
|
|
description="Cavity wall insulation (fill the existing cavity)",
|
|
overlay=EpcSimulation(
|
|
building_parts={
|
|
BuildingPartIdentifier.MAIN: BuildingPartOverlay(
|
|
wall_insulation_type=_FILLED_CAVITY
|
|
)
|
|
}
|
|
),
|
|
cost=cost,
|
|
material_id=product.id,
|
|
)
|
|
return Recommendation(surface="Main wall", options=(option,))
|