Bill Simplified RIR common walls as common_wall at the main-wall U 🟥

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Khalim Conn-Kowlessar 2026-07-15 08:28:59 +00:00
parent 8a753822c8
commit 27158d623d

View file

@ -240,6 +240,80 @@ class TestRoomInRoofMapping:
assert by_kind["flat_ceiling"] == 100
assert by_kind["gable_wall"] is None
# A Simplified-approach lodgement ("Roof room type: RR type 2", details
# NOT known): gables + common walls only, no slopes/flat ceilings.
# Verbatim from fixture 499538003156 (78 North Road).
_RIR_SIMPLIFIED = {
"age_range": "C: 1930 - 1949",
"floor_area_m2": 25.43,
"roof_room_type": "RR type 2 (With the Accessible areas of continuous common walls)",
"gables": [
{"length_m": 5.82, "height_m": 2.72, "gable_type": "Party"},
{"length_m": 5.82, "height_m": 2.72, "gable_type": "Party"},
],
"common_walls": [
{"length_m": 4.37, "height_m": 2.34},
{"length_m": 4.37, "height_m": 2.34},
],
}
def test_simplified_common_walls_bill_as_common_wall(self) -> None:
"""On the Simplified approach (a "Roof room type" is lodged), a
"common wall" is a §3.9.2 Type-2 concept billed as an external wall
at the main-wall U SAP kind `common_wall`, spec area L × (0.25 + H)
(RdSAP 10 §3.9.2 / heat_transmission's Simplified-BP area rule) — NOT
a Detailed §3.10 `stud_wall` roof surface, which would drop to the
Table 18 col(4) "all elements uninsulated" default (2.30 W/m²K) over
~20 on the no-loft-access North Road pair (deals
499538003156/499627236560, over-rate ~5 SAP before this fix)."""
# Arrange
data = load("pashub_rdsap_site_notes_example1.json")
data["roof_space"]["main_building"]["rooms_in_roof"] = True
data["roof_space"]["main_building"]["room_in_roof"] = self._RIR_SIMPLIFIED
survey = from_dict(PasHubRdSapSiteNotes, data)
# Act
rir = EpcPropertyDataMapper.from_site_notes(survey).sap_building_parts[
0
].sap_room_in_roof
# Assert
assert rir is not None
surfaces = rir.detailed_surfaces
assert surfaces is not None
cw_area = round(4.37 * (0.25 + 2.34), 2) # L × (0.25 + H)
kinds_areas = [(s.kind, round(s.area_m2, 2)) for s in surfaces]
assert kinds_areas == [
("gable_wall", round(5.82 * 2.72, 2)),
("gable_wall", round(5.82 * 2.72, 2)),
("common_wall", cw_area),
("common_wall", cw_area),
]
def test_detailed_common_walls_stay_stud_wall(self) -> None:
"""Regression: a Detailed §3.10 lodgement (no "Roof room type", per-
surface slopes lodged) keeps common walls as `stud_wall` pinned
alongside `test_room_in_roof_reaches_building_part`."""
# Arrange — the Detailed fixture has no roof_room_type.
data = load("pashub_rdsap_site_notes_example1.json")
data["roof_space"]["main_building"]["rooms_in_roof"] = True
data["roof_space"]["main_building"]["room_in_roof"] = self._RIR
survey = from_dict(PasHubRdSapSiteNotes, data)
# Act
rir = EpcPropertyDataMapper.from_site_notes(survey).sap_building_parts[
0
].sap_room_in_roof
# Assert
assert rir is not None
assert rir.detailed_surfaces is not None
assert {s.kind for s in rir.detailed_surfaces if "wall" in s.kind} == {
"gable_wall_external",
"gable_wall",
"stud_wall",
}
def test_no_rooms_in_roof_stays_absent(self) -> None:
# Arrange — the fixture as lodged (rooms_in_roof=false).
survey = from_dict(