diff --git a/datatypes/epc/domain/mapper.py b/datatypes/epc/domain/mapper.py index 98feac78..d09a46e6 100644 --- a/datatypes/epc/domain/mapper.py +++ b/datatypes/epc/domain/mapper.py @@ -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 ( details.flat_ceiling_length_1 is not None and details.flat_ceiling_height_1 is not None diff --git a/datatypes/epc/domain/tests/test_from_rdsap_schema.py b/datatypes/epc/domain/tests/test_from_rdsap_schema.py index 5d29893a..054e9864 100644 --- a/datatypes/epc/domain/tests/test_from_rdsap_schema.py +++ b/datatypes/epc/domain/tests/test_from_rdsap_schema.py @@ -2202,22 +2202,29 @@ class TestRoomInRoofDetailedSlopeAndStudWall: "stud_wall_length_2": 7.0, "stud_wall_height_2": 1.03, "stud_wall_insulation_thickness_1": "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 result = EpcPropertyDataMapper.from_api_response(cert) # 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 assert rir_part is not None surfaces = rir_part.detailed_surfaces assert surfaces is not None slopes = [s for s in surfaces if s.kind == "slope"] 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(studs) == 2 + assert len(commons) == 2 assert abs(slopes[0].area_m2 - 9.8) <= 1e-9 assert slopes[0].insulation_thickness_mm == 100 assert abs(studs[0].area_m2 - 7.21) <= 1e-9 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 diff --git a/datatypes/epc/schema/rdsap_schema_21_0_0.py b/datatypes/epc/schema/rdsap_schema_21_0_0.py index fdb0d17c..4f7e7e40 100644 --- a/datatypes/epc/schema/rdsap_schema_21_0_0.py +++ b/datatypes/epc/schema/rdsap_schema_21_0_0.py @@ -228,6 +228,12 @@ class RoomInRoofDetails: stud_wall_insulation_type_2: Optional[int] = None stud_wall_insulation_thickness_1: 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 diff --git a/datatypes/epc/schema/rdsap_schema_21_0_1.py b/datatypes/epc/schema/rdsap_schema_21_0_1.py index db6d4c1a..37714034 100644 --- a/datatypes/epc/schema/rdsap_schema_21_0_1.py +++ b/datatypes/epc/schema/rdsap_schema_21_0_1.py @@ -267,6 +267,15 @@ class RoomInRoofDetails: stud_wall_insulation_type_2: Optional[int] = None stud_wall_insulation_thickness_1: 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