"""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