mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-08 11:17:27 +00:00
Completes #1159 end-to-end with solid and suspended-floor before/after cascade pins on cert 001431, both closing at delta 0.000000. Adds floor_insulation_type_str to BuildingPartOverlay (the generic field-fold applicator picks it up with no change) and has recommend_floor_insulation set it to "Retro-fitted". Insulating an as-built floor re-lodges its insulation as retro-fitted; the calculator keys on this for a suspended timber floor's sealed/unsealed determination (cert_to_inputs.py: "retro" + no U-value supplied → sealed). Without it the suspended-floor cascade left a +1.40 SAP gap (the floor stayed "unsealed", wrong U-value); with it the cascade closes exactly. Solid floors are unaffected by the seal logic and stay at delta 0; both Elmhurst after-certs lodge "Retro-fitted", so setting it uniformly is faithful. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
91 lines
3.4 KiB
Python
91 lines
3.4 KiB
Python
"""The floor Recommendation Generator.
|
|
|
|
Detects an uninsulated ground floor and its construction (suspended timber vs
|
|
solid) and emits a Recommendation whose single Measure Option carries the
|
|
matching insulation Simulation Overlay and a priced Cost. A floor is one
|
|
construction, so — like a cavity wall — there is one Option, chosen by
|
|
detection. No scoring, no persistence (ADR-0016).
|
|
"""
|
|
|
|
from typing import Optional, Union
|
|
|
|
from datatypes.epc.domain.epc_property_data import (
|
|
BuildingPartIdentifier,
|
|
EpcPropertyData,
|
|
SapBuildingPart,
|
|
)
|
|
from domain.building_geometry import ground_floor_area
|
|
from domain.modelling.recommendation import Cost, MeasureOption, Recommendation
|
|
from domain.modelling.simulation import BuildingPartOverlay, EpcSimulation
|
|
from repositories.product.product_repository import ProductRepository
|
|
|
|
# Recommended ground-floor insulation depth (mm).
|
|
_RECOMMENDED_FLOOR_THICKNESS_MM = 100
|
|
# Insulating an as-built floor re-lodges its insulation as retro-fitted. The
|
|
# calculator keys on this for a suspended timber floor's sealed/unsealed
|
|
# determination (cert_to_inputs: "retro" + no U-value → sealed), so the
|
|
# overlay must set it or the suspended-floor cascade leaves a ~1.4 SAP gap
|
|
# (see test_elmhurst_cascade_pins).
|
|
_RETROFITTED_INSULATION = "Retro-fitted"
|
|
|
|
|
|
def _is_uninsulated(thickness: Optional[Union[str, int]]) -> bool:
|
|
"""A lodged floor-insulation thickness of nothing / blank / zero is an
|
|
uninsulated floor; any positive thickness is already insulated."""
|
|
if thickness is None:
|
|
return True
|
|
if isinstance(thickness, int):
|
|
return thickness == 0
|
|
return thickness.strip() in ("", "0")
|
|
|
|
|
|
def _floor_measure_type(construction_type: Optional[str]) -> Optional[str]:
|
|
"""Map the lodged floor construction to the insulation Measure Type, or
|
|
None when the construction is not a treatable suspended/solid floor."""
|
|
text = (construction_type or "").lower()
|
|
if "suspended" in text:
|
|
return "suspended_floor_insulation"
|
|
if "solid" in text:
|
|
return "solid_floor_insulation"
|
|
return None
|
|
|
|
|
|
def recommend_floor_insulation(
|
|
epc: EpcPropertyData, products: ProductRepository
|
|
) -> Optional[Recommendation]:
|
|
"""Return a ground-floor insulation Recommendation for the main part's
|
|
uninsulated ground floor, else None."""
|
|
main: SapBuildingPart = next(
|
|
part
|
|
for part in epc.sap_building_parts
|
|
if part.identifier is BuildingPartIdentifier.MAIN
|
|
)
|
|
|
|
if not _is_uninsulated(main.floor_insulation_thickness):
|
|
return None
|
|
|
|
measure_type = _floor_measure_type(main.floor_construction_type)
|
|
if measure_type is None:
|
|
return None
|
|
|
|
product = products.get(measure_type)
|
|
area: float = ground_floor_area(epc, BuildingPartIdentifier.MAIN)
|
|
cost = Cost(
|
|
total=area * product.unit_cost_per_m2,
|
|
contingency_rate=product.contingency_rate,
|
|
)
|
|
|
|
option = MeasureOption(
|
|
measure_type=measure_type,
|
|
description="Ground-floor insulation",
|
|
overlay=EpcSimulation(
|
|
building_parts={
|
|
BuildingPartIdentifier.MAIN: BuildingPartOverlay(
|
|
floor_insulation_thickness=_RECOMMENDED_FLOOR_THICKNESS_MM,
|
|
floor_insulation_type_str=_RETROFITTED_INSULATION,
|
|
)
|
|
}
|
|
),
|
|
cost=cost,
|
|
)
|
|
return Recommendation(surface="Ground floor", options=(option,))
|