mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-07-12 13:29:04 +00:00
Vaulted ND roof on a pre-1950 dwelling gets sloping-ceiling insulation 🟩
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
0d465346c9
commit
f3bdf0e884
1 changed files with 38 additions and 3 deletions
|
|
@ -10,7 +10,7 @@ Flat-roof and room-in-roof branches land in later slices. No scoring, no
|
|||
persistence — impact is produced later by scoring (ADR-0016).
|
||||
"""
|
||||
|
||||
from typing import Optional
|
||||
from typing import Final, Optional, Union
|
||||
|
||||
from datatypes.epc.domain.epc_property_data import (
|
||||
BuildingPartIdentifier,
|
||||
|
|
@ -33,6 +33,36 @@ _ROOF_UNINSULATED_MM = 0
|
|||
_RECOMMENDED_LOFT_THICKNESS_MM = 300
|
||||
# Recommended sloping-ceiling depth (mm); Elmhurst re-lodges 100 mm (ADR-0021).
|
||||
_RECOMMENDED_SLOPING_CEILING_THICKNESS_MM = 100
|
||||
# Age bands whose as-built pitched roof is uninsulated (ADR-0047: A-D
|
||||
# uninsulated, E-F limited insulation, G+ insulated) — the resolution for a
|
||||
# cert lodging the "ND" (Not Defined) sentinel or no thickness at all.
|
||||
_AS_BUILT_UNINSULATED_AGE_BANDS: Final[frozenset[str]] = frozenset(
|
||||
{"A", "B", "C", "D"}
|
||||
)
|
||||
|
||||
|
||||
def _pitched_roof_is_uninsulated(
|
||||
thickness: Union[str, int, None], age_band: Optional[str]
|
||||
) -> bool:
|
||||
"""The pitched-roof uninsulated trigger, sentinel-aware (ADR-0047).
|
||||
|
||||
`roof_insulation_thickness` lodges numerics AND string sentinels; the
|
||||
sentinels are data, not noise, and must never make an undefined cert MORE
|
||||
eligible than an explicitly-lodged one. An explicit 0 is uninsulated; the
|
||||
"ND" (Not Defined) sentinel — or nothing lodged — resolves to the age
|
||||
band's as-built state (only A-D built without pitched-roof insulation).
|
||||
Anything else ("NI" = insulation present at unknown thickness per RdSAP 10
|
||||
§5.11.4, or a measured depth) means insulation is there. Eligibility only
|
||||
— the calculator resolves the same sentinels separately for the U-cascade
|
||||
(deliberate two-home split, ADR-0047)."""
|
||||
if isinstance(thickness, int):
|
||||
return thickness == 0
|
||||
if thickness is None or thickness == "ND":
|
||||
return (
|
||||
age_band is not None
|
||||
and age_band.upper() in _AS_BUILT_UNINSULATED_AGE_BANDS
|
||||
)
|
||||
return False
|
||||
_FLAT_ROOF_MEASURE_TYPE = MeasureType.FLAT_ROOF_INSULATION
|
||||
# Recommended flat-roof depth (mm); Elmhurst re-lodges 200 mm (ADR-0021).
|
||||
_RECOMMENDED_FLAT_ROOF_THICKNESS_MM = 200
|
||||
|
|
@ -68,8 +98,13 @@ def recommend_roof_insulation(
|
|||
# plain pitched loft, a thatched roof (the covering doesn't block insulating
|
||||
# the loft floor), and an unlodged roof type (the modal UK case), matching
|
||||
# the pre-dispatcher behaviour of firing on `roof_insulation_thickness == 0`.
|
||||
if "sloping ceiling" in roof_type:
|
||||
if main.roof_insulation_thickness != _ROOF_UNINSULATED_MM:
|
||||
# A vaulted ceiling follows the slope like a sloping ceiling (no loft
|
||||
# void) — same disjunction the calculator's `is_sloping_ceiling` uses
|
||||
# (heat_transmission.py), so generator and scorer read one story (ADR-0047).
|
||||
if "sloping ceiling" in roof_type or "vaulted" in roof_type:
|
||||
if not _pitched_roof_is_uninsulated(
|
||||
main.roof_insulation_thickness, main.construction_age_band
|
||||
):
|
||||
return None
|
||||
return _roof_recommendation(
|
||||
epc,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue