mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-30 13:10:47 +00:00
fix(mapper): read dropped detailed room-in-roof common-wall surfaces
Follow-on to the slope/stud slice. A Detailed RR (RdSAP 10 §3.9.2) can also lodge `common_wall_*` — the wall separating the room-in-roof from the rest of the cold roof void. Those fields were undeclared → `from_dict` dropped them → `_api_rir_detailed_surfaces` omitted the common walls → the RR undercounted wall heat loss → over-rate. Fix: declare `common_wall_length/height_1/2` on `RoomInRoofDetails` (21_0_0 + 21_0_1) and build `kind="common_wall"` surfaces (raw L × H area to 2 d.p.). The cascade's Detailed-RR branch already bills common walls at the storey-below main-wall U (Table 4 p.22 "Common wall") and deducts their area from the §3.10.1 residual roof — no calculator change. No insulation thickness is read: common walls take the main-wall U, not a Table 17 RR-element U. 6 /tmp certs carry detailed `common_wall_length_1`: cohort mean|err| 2.43 -> 1.25 (all were over-rating; e.g. 2877-3059 +4.55 -> +2.79). Gauges: corpus within-0.5 67.5% -> 67.6% (MAE 0.987 -> 0.979); /tmp 71.6% -> 71.7% (MAE 0.846 -> 0.838). Harness 47/47 0 raised; regression = the 3 pre-existing fails; pyright net-zero (65=65). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
e4adab0e88
commit
26998152a7
4 changed files with 43 additions and 1 deletions
|
|
@ -3993,6 +3993,26 @@ def _api_rir_detailed_surfaces(
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
# Common walls — billed as external wall at the storey-below main-wall U
|
||||||
|
# (cascade `kind="common_wall"`), so no insulation thickness is read.
|
||||||
|
# Detailed BPs use the raw L × H area (RdSAP 10 §3.9.2; the cascade's
|
||||||
|
# common_wall branch applies the L × (0.25 + H) form only to Simplified
|
||||||
|
# BPs). The cascade deducts this area from the §3.10.1 residual roof.
|
||||||
|
common_wall_specs = (
|
||||||
|
(details.common_wall_length_1, details.common_wall_height_1),
|
||||||
|
(details.common_wall_length_2, details.common_wall_height_2),
|
||||||
|
)
|
||||||
|
for length, height in common_wall_specs:
|
||||||
|
if (
|
||||||
|
length is not None and height is not None
|
||||||
|
and length > 0 and height > 0
|
||||||
|
):
|
||||||
|
surfaces.append(
|
||||||
|
SapRoomInRoofSurface(
|
||||||
|
kind="common_wall",
|
||||||
|
area_m2=_round_half_up_2dp(float(length), float(height)),
|
||||||
|
)
|
||||||
|
)
|
||||||
if (
|
if (
|
||||||
details.flat_ceiling_length_1 is not None
|
details.flat_ceiling_length_1 is not None
|
||||||
and details.flat_ceiling_height_1 is not None
|
and details.flat_ceiling_height_1 is not None
|
||||||
|
|
|
||||||
|
|
@ -2202,22 +2202,29 @@ class TestRoomInRoofDetailedSlopeAndStudWall:
|
||||||
"stud_wall_length_2": 7.0, "stud_wall_height_2": 1.03,
|
"stud_wall_length_2": 7.0, "stud_wall_height_2": 1.03,
|
||||||
"stud_wall_insulation_thickness_1": "75mm",
|
"stud_wall_insulation_thickness_1": "75mm",
|
||||||
"stud_wall_insulation_thickness_2": "75mm",
|
"stud_wall_insulation_thickness_2": "75mm",
|
||||||
|
"common_wall_length_1": 8.6, "common_wall_height_1": 1.2,
|
||||||
|
"common_wall_length_2": 8.6, "common_wall_height_2": 1.2,
|
||||||
}
|
}
|
||||||
|
|
||||||
# Act
|
# Act
|
||||||
result = EpcPropertyDataMapper.from_api_response(cert)
|
result = EpcPropertyDataMapper.from_api_response(cert)
|
||||||
|
|
||||||
# Assert — both slopes + both stud walls reach the cascade, with the
|
# Assert — both slopes + both stud walls reach the cascade, with the
|
||||||
# lodged thickness parsed and the L × H area to 2 d.p.
|
# lodged thickness parsed and the L × H area to 2 d.p. Common walls
|
||||||
|
# route to the `common_wall` kind (raw L × H, billed at main-wall U).
|
||||||
rir_part = result.sap_building_parts[0].sap_room_in_roof
|
rir_part = result.sap_building_parts[0].sap_room_in_roof
|
||||||
assert rir_part is not None
|
assert rir_part is not None
|
||||||
surfaces = rir_part.detailed_surfaces
|
surfaces = rir_part.detailed_surfaces
|
||||||
assert surfaces is not None
|
assert surfaces is not None
|
||||||
slopes = [s for s in surfaces if s.kind == "slope"]
|
slopes = [s for s in surfaces if s.kind == "slope"]
|
||||||
studs = [s for s in surfaces if s.kind == "stud_wall"]
|
studs = [s for s in surfaces if s.kind == "stud_wall"]
|
||||||
|
commons = [s for s in surfaces if s.kind == "common_wall"]
|
||||||
assert len(slopes) == 2
|
assert len(slopes) == 2
|
||||||
assert len(studs) == 2
|
assert len(studs) == 2
|
||||||
|
assert len(commons) == 2
|
||||||
assert abs(slopes[0].area_m2 - 9.8) <= 1e-9
|
assert abs(slopes[0].area_m2 - 9.8) <= 1e-9
|
||||||
assert slopes[0].insulation_thickness_mm == 100
|
assert slopes[0].insulation_thickness_mm == 100
|
||||||
assert abs(studs[0].area_m2 - 7.21) <= 1e-9
|
assert abs(studs[0].area_m2 - 7.21) <= 1e-9
|
||||||
assert studs[0].insulation_thickness_mm == 75
|
assert studs[0].insulation_thickness_mm == 75
|
||||||
|
assert abs(commons[0].area_m2 - 10.32) <= 1e-9
|
||||||
|
assert commons[0].insulation_thickness_mm is None
|
||||||
|
|
|
||||||
|
|
@ -228,6 +228,12 @@ class RoomInRoofDetails:
|
||||||
stud_wall_insulation_type_2: Optional[int] = None
|
stud_wall_insulation_type_2: Optional[int] = None
|
||||||
stud_wall_insulation_thickness_1: Optional[str] = None
|
stud_wall_insulation_thickness_1: Optional[str] = None
|
||||||
stud_wall_insulation_thickness_2: Optional[str] = None
|
stud_wall_insulation_thickness_2: Optional[str] = None
|
||||||
|
# §3.9.2 common walls of a Detailed RR — see
|
||||||
|
# `rdsap_schema_21_0_1.RoomInRoofDetails`. Previously dropped.
|
||||||
|
common_wall_length_1: Optional[float] = None
|
||||||
|
common_wall_length_2: Optional[float] = None
|
||||||
|
common_wall_height_1: Optional[float] = None
|
||||||
|
common_wall_height_2: Optional[float] = None
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
|
|
|
||||||
|
|
@ -267,6 +267,15 @@ class RoomInRoofDetails:
|
||||||
stud_wall_insulation_type_2: Optional[int] = None
|
stud_wall_insulation_type_2: Optional[int] = None
|
||||||
stud_wall_insulation_thickness_1: Optional[str] = None
|
stud_wall_insulation_thickness_1: Optional[str] = None
|
||||||
stud_wall_insulation_thickness_2: Optional[str] = None
|
stud_wall_insulation_thickness_2: Optional[str] = None
|
||||||
|
# The §3.9.2 common walls of a Detailed RR (the wall separating the RR
|
||||||
|
# from the rest of the cold roof void). Billed as external wall at the
|
||||||
|
# storey-below main-wall U (cascade `kind="common_wall"`). Detailed BPs
|
||||||
|
# use the raw L × H area (Simplified Type-2 BPs use L × (0.25 + H)).
|
||||||
|
# Previously undeclared → dropped → the RR undercounted wall loss.
|
||||||
|
common_wall_length_1: Optional[float] = None
|
||||||
|
common_wall_length_2: Optional[float] = None
|
||||||
|
common_wall_height_1: Optional[float] = None
|
||||||
|
common_wall_height_2: Optional[float] = None
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue