mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-07-27 23:35:01 +00:00
Lock the sloping-ceiling override's U-value end-to-end (col-3 as-built, col-1a by depth) 🟩
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
c01f48d4fd
commit
0625b9b179
1 changed files with 48 additions and 0 deletions
|
|
@ -12,6 +12,28 @@ import pytest
|
|||
from datatypes.epc.domain.epc_property_data import BuildingPartIdentifier
|
||||
from domain.epc.property_overlays.roof_type_overlay import roof_overlay_for
|
||||
from domain.epc.property_overrides.roof_type import RoofType
|
||||
from domain.sap10_ml.rdsap_uvalues import Country, u_roof
|
||||
|
||||
|
||||
def _resolved_roof_u(roof_type_value: str, age_band: str) -> float:
|
||||
"""Score a resolved roof override through the real `u_roof`, deriving the
|
||||
sloping-ceiling flag exactly as the calculator does from the overlay's
|
||||
`roof_construction_type` string (`heat_transmission.py`:
|
||||
`is_pitched_sloping_ceiling = "sloping ceiling" in roof_type_lower`). This
|
||||
ties the overlay's emitted string to the U it actually produces — a silent
|
||||
loft-scored roof if the string ever stops containing "sloping ceiling"."""
|
||||
simulation = roof_overlay_for(roof_type_value, 0)
|
||||
assert simulation is not None
|
||||
overlay = simulation.building_parts[BuildingPartIdentifier.MAIN]
|
||||
roof_type_lower = (overlay.roof_construction_type or "").lower()
|
||||
is_sloping = "sloping ceiling" in roof_type_lower
|
||||
return u_roof(
|
||||
country=Country.ENG,
|
||||
age_band=age_band,
|
||||
insulation_thickness_mm=overlay.roof_insulation_thickness,
|
||||
is_sloping_ceiling=is_sloping,
|
||||
is_pitched_sloping_ceiling=is_sloping,
|
||||
)
|
||||
|
||||
_FLAT_THICKNESS_MEMBERS: list[tuple[RoofType, int]] = [
|
||||
(RoofType.FLAT_12MM, 12),
|
||||
|
|
@ -218,6 +240,32 @@ def test_sloping_ceiling_as_built_defers_to_the_age_band_default() -> None:
|
|||
assert overlay.roof_insulation_thickness is None
|
||||
|
||||
|
||||
def test_as_built_sloping_ceiling_scores_the_slope_default_not_the_loft_floor() -> None:
|
||||
# ADR-0066: an as-built sloping ceiling at an old band takes Table 18 col (3)
|
||||
# (band F = 0.68 W/m²K), NOT the loft col (1) 0.40 "modern-retrofit" floor it
|
||||
# was mis-scored at when the classifier routed it onto the loft ladder. This
|
||||
# is the whole point of the family — the age-band default must be the slope
|
||||
# column, driven by the overlay's construction string.
|
||||
|
||||
# Act
|
||||
u_value = _resolved_roof_u("Pitched, sloping ceiling, as built", age_band="F")
|
||||
|
||||
# Assert — col (3), not the loft col (1) 0.40.
|
||||
assert abs(u_value - 0.68) <= 1e-4
|
||||
|
||||
|
||||
def test_known_depth_sloping_ceiling_scores_the_insulated_slope_ladder() -> None:
|
||||
# ADR-0066: a known-depth sloping ceiling scores on Table 17 col (1a)
|
||||
# "insulated slope" — 150 mm → 0.30 W/m²K (Elmhurst-confirmed, byte-identical
|
||||
# to the loft col (1) value, so it is a *deliberate* match, not a fallback).
|
||||
|
||||
# Act
|
||||
u_value = _resolved_roof_u("Pitched, sloping ceiling, 150 mm insulation", age_band="F")
|
||||
|
||||
# Assert
|
||||
assert abs(u_value - 0.30) <= 1e-4
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"roof_type_value",
|
||||
[
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue