From 6f1ba4be25595646f14a55328a68449306fc2856 Mon Sep 17 00:00:00 2001 From: Khalim Conn-Kowlessar Date: Tue, 14 Jul 2026 22:29:20 +0000 Subject: [PATCH] =?UTF-8?q?Bill=20room-in-roof=20surfaces=20with=20the=20s?= =?UTF-8?q?urveyed=20roof=20insulation,=20stud-wall=20commons=20?= =?UTF-8?q?=F0=9F=9F=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two dig-confirmed defects in the fresh RIR mapping (499617935574 overshot -9): the roof-space-level insulation (Joists/200mm) was never threaded into the RIR surfaces, so slopes/commons/flat ceilings fell to the Table 18 "unknown" full-uninsulated 2.30 default; and common walls were mislabeled "common_wall" (a Simplified-Type-2-only concept) instead of the Detailed-mode stud_wall every worksheet-validated fixture uses. 44.0 -> ~50.3 on the target fixture; harness MAE 1.748 -> 1.726. Co-Authored-By: Claude Opus 4.8 (1M context) --- datatypes/epc/domain/mapper.py | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/datatypes/epc/domain/mapper.py b/datatypes/epc/domain/mapper.py index 7a20c567a..efdf45e4a 100644 --- a/datatypes/epc/domain/mapper.py +++ b/datatypes/epc/domain/mapper.py @@ -6003,9 +6003,22 @@ def _pashub_room_in_roof( if not roof.rooms_in_roof or rir is None: return None surfaces: List[SapRoomInRoofSurface] = [] + # PasHub lodges roof insulation ONCE at roof-space level, not per RIR + # surface. Thread it into the roof-going surfaces (slope / stud_wall / + # flat_ceiling — Table 17) so they aren't billed at the Table 18 col(4) + # "unknown" full-uninsulated default while the same survey records an + # insulated roof. Gables keep None — they are walls, priced by wall + # fields, and their consumer branches ignore the thickness anyway. + roof_thickness = ( + roof.insulation_thickness_mm + if isinstance(roof.insulation_thickness_mm, int) + else None + ) def _add( - details: Optional[List[RoomInRoofSurfaceDetail]], kind: Optional[str] + details: Optional[List[RoomInRoofSurfaceDetail]], + kind: Optional[str], + insulation_thickness_mm: Optional[int] = None, ) -> None: for d in details or []: if d.length_m is None or d.height_m is None: @@ -6022,13 +6035,19 @@ def _pashub_room_in_roof( kind=surface_kind, area_m2=d.length_m * d.height_m, u_value=d.u_value, + insulation_thickness_mm=insulation_thickness_mm, ) ) _add(rir.gables, None) - _add(rir.slopes, "slope") - _add(rir.common_walls, "common_wall") - _add(rir.flat_ceilings, "flat_ceiling") + _add(rir.slopes, "slope", roof_thickness) + # "Common wall" lodges as a Detailed-mode stud wall (Table 17 col 3): + # kind="common_wall" is a §3.9.2 Simplified-Type-2-only concept (different + # area formula, billed at the main-wall U); every worksheet-validated + # Detailed §3.10 fixture (000516/000487/000477/000480) uses stud_wall for + # this low internal wall. + _add(rir.common_walls, "stud_wall", roof_thickness) + _add(rir.flat_ceilings, "flat_ceiling", roof_thickness) if rir.floor_area_m2 is None: return None return SapRoomInRoof(