mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-07-12 13:29:04 +00:00
Solid-wall insulation is withheld when it cannot lower the derived wall U 🟩
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
574e58a5c8
commit
7c8b161653
2 changed files with 90 additions and 0 deletions
|
|
@ -26,6 +26,7 @@ from domain.building_geometry import gross_heat_loss_wall_area
|
|||
from domain.geospatial.planning_restrictions import PlanningRestrictions
|
||||
from domain.modelling.measure_type import MeasureType
|
||||
from domain.modelling.recommendation import Cost, MeasureOption, Recommendation
|
||||
from domain.modelling.generators.wall_u_value_gate import wall_insulation_lowers_u
|
||||
from domain.modelling.simulation import BuildingPartOverlay, EpcSimulation
|
||||
from repositories.product.product_repository import ProductRepository
|
||||
|
||||
|
|
@ -158,6 +159,15 @@ def recommend_solid_wall(
|
|||
measure_type
|
||||
for measure_type in measure_types
|
||||
if _allowed(measure_type, restrictions, is_flat)
|
||||
# Wall U-Value Gate (ADR-0051): withhold an Option that cannot lower
|
||||
# the wall's derived U — e.g. an at-regs modern age band, where the
|
||||
# as-built and insulated cascades resolve to the same value.
|
||||
and wall_insulation_lowers_u(
|
||||
main,
|
||||
epc.country_code,
|
||||
insulation_type=_INSULATION_TYPE[measure_type],
|
||||
insulation_thickness_mm=_SOLID_WALL_INSULATION_MM,
|
||||
)
|
||||
)
|
||||
if not allowed:
|
||||
return None
|
||||
|
|
|
|||
80
domain/modelling/generators/wall_u_value_gate.py
Normal file
80
domain/modelling/generators/wall_u_value_gate.py
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
"""The Wall U-Value Gate (ADR-0051).
|
||||
|
||||
A wall insulation Option is offered only when it would actually lower the main
|
||||
wall's derived U-value: on a modern construction age band RdSAP's Table 6-10
|
||||
cascade assigns an as-built wall its at-regs U-value, and the post-measure
|
||||
state resolves to the *same* value — the measure costs thousands for +0.00 SAP
|
||||
(property 711795, PRD #1435 WS2: timber frame at age band L, 0.280 → 0.280).
|
||||
|
||||
The comparison uses a deliberately thin `u_wall` parameter mapping (the
|
||||
`envelope.py` style — construction, age band, insulation type/thickness,
|
||||
documentary wall thickness), omitting the calculator's rich extras (surveyor
|
||||
description, dry-lining, lodged `wall_u_value`). The omissions bias the gate
|
||||
toward OFFERING — both sides are computed identically, so a genuine no-op still
|
||||
compares equal — never toward suppressing a real measure.
|
||||
"""
|
||||
|
||||
from typing import Any, Final, Optional
|
||||
|
||||
from datatypes.epc.domain.epc_property_data import SapBuildingPart
|
||||
from domain.sap10_ml.rdsap_uvalues import WALL_UNKNOWN, Country, u_wall
|
||||
|
||||
# Improvements below this are float noise, not a physical change. NOT a
|
||||
# materiality threshold — how much improvement is worth the cost stays with the
|
||||
# Optimiser (ADR-0016/0024).
|
||||
_NOISE_FLOOR_W_PER_M2K: Final[float] = 0.01
|
||||
|
||||
# RdSAP `wall_insulation_type` 4 = as-built / assumed (no added insulation).
|
||||
_WALL_INSULATION_NONE: Final[int] = 4
|
||||
|
||||
|
||||
def _int_or_none(value: Any) -> Optional[int]:
|
||||
return value if isinstance(value, int) else None
|
||||
|
||||
|
||||
def _parse_thickness_mm(value: Any) -> Optional[int]:
|
||||
if value is None or isinstance(value, int):
|
||||
return value
|
||||
if isinstance(value, str) and value.strip().isdigit():
|
||||
return int(value.strip())
|
||||
return None # "NI" / free text — thickness not known
|
||||
|
||||
|
||||
def wall_insulation_lowers_u(
|
||||
main: SapBuildingPart,
|
||||
country_code: Optional[str],
|
||||
*,
|
||||
insulation_type: int,
|
||||
insulation_thickness_mm: Optional[int] = None,
|
||||
) -> bool:
|
||||
"""Whether insulating the main wall to ``insulation_type`` (+ optional
|
||||
thickness) lowers its derived U-value by more than float noise — the Wall
|
||||
U-Value Gate (ADR-0051)."""
|
||||
country: Country = Country.from_code(country_code)
|
||||
age_band = main.construction_age_band
|
||||
construction: Optional[int] = _int_or_none(main.wall_construction)
|
||||
if construction == WALL_UNKNOWN:
|
||||
construction = None
|
||||
wall_thickness_mm = main.wall_thickness_mm
|
||||
current_type: Optional[int] = _int_or_none(main.wall_insulation_type)
|
||||
current_u: float = u_wall(
|
||||
country,
|
||||
age_band,
|
||||
construction,
|
||||
_parse_thickness_mm(main.wall_insulation_thickness),
|
||||
insulation_present=(
|
||||
current_type is not None and current_type != _WALL_INSULATION_NONE
|
||||
),
|
||||
wall_insulation_type=current_type,
|
||||
wall_thickness_mm=wall_thickness_mm,
|
||||
)
|
||||
post_u: float = u_wall(
|
||||
country,
|
||||
age_band,
|
||||
construction,
|
||||
insulation_thickness_mm,
|
||||
insulation_present=True,
|
||||
wall_insulation_type=insulation_type,
|
||||
wall_thickness_mm=wall_thickness_mm,
|
||||
)
|
||||
return current_u - post_u > _NOISE_FLOOR_W_PER_M2K
|
||||
Loading…
Add table
Reference in a new issue