diff --git a/datatypes/epc/domain/mapper.py b/datatypes/epc/domain/mapper.py index 708ad571..8068485f 100644 --- a/datatypes/epc/domain/mapper.py +++ b/datatypes/epc/domain/mapper.py @@ -1233,6 +1233,19 @@ class EpcPropertyDataMapper: SapRoomInRoof( floor_area=_measurement_value(bp.sap_room_in_roof.floor_area), construction_age_band=bp.sap_room_in_roof.construction_age_band, + # RdSAP §3.9.1 Simplified Type 1: gable lengths + # only (no heights — the cascade applies the + # 2.45 m default storey height per §3.9.1). + gable_1_length_m=( + bp.sap_room_in_roof.room_in_roof_type_1.gable_wall_length_1 + if bp.sap_room_in_roof.room_in_roof_type_1 is not None + else None + ), + gable_2_length_m=( + bp.sap_room_in_roof.room_in_roof_type_1.gable_wall_length_2 + if bp.sap_room_in_roof.room_in_roof_type_1 is not None + else None + ), ) if bp.sap_room_in_roof else None @@ -1478,6 +1491,19 @@ class EpcPropertyDataMapper: SapRoomInRoof( floor_area=_measurement_value(bp.sap_room_in_roof.floor_area), construction_age_band=bp.sap_room_in_roof.construction_age_band, + # RdSAP §3.9.1 Simplified Type 1: gable lengths + # only (no heights — the cascade applies the + # 2.45 m default storey height per §3.9.1). + gable_1_length_m=( + bp.sap_room_in_roof.room_in_roof_type_1.gable_wall_length_1 + if bp.sap_room_in_roof.room_in_roof_type_1 is not None + else None + ), + gable_2_length_m=( + bp.sap_room_in_roof.room_in_roof_type_1.gable_wall_length_2 + if bp.sap_room_in_roof.room_in_roof_type_1 is not None + else None + ), ) if bp.sap_room_in_roof else None diff --git a/datatypes/epc/domain/tests/test_from_rdsap_schema.py b/datatypes/epc/domain/tests/test_from_rdsap_schema.py index f6551429..cebad47c 100644 --- a/datatypes/epc/domain/tests/test_from_rdsap_schema.py +++ b/datatypes/epc/domain/tests/test_from_rdsap_schema.py @@ -587,6 +587,25 @@ class TestFromRdSapSchema21_0_1: def test_party_wall_length(self, result: EpcPropertyData) -> None: assert result.sap_building_parts[0].sap_floor_dimensions[0].party_wall_length_m == 7.9 + # --- room-in-roof (sap_room_in_roof.room_in_roof_type_1) --- + + def test_sap_room_in_roof_gable_lengths_extracted_from_room_in_roof_type_1( + self, result: EpcPropertyData + ) -> None: + # Arrange — schema-21.0.1 lodges Simplified Type 1 gable lengths + # under sap_room_in_roof.room_in_roof_type_1. The cascade requires + # them on EpcPropertyData.SapRoomInRoof.gable_1_length_m / + # gable_2_length_m for the §3.9.2 area cascade. Without this the + # length data is silently dropped at deserialization. + + # Act + rir = result.sap_building_parts[0].sap_room_in_roof + + # Assert + assert rir is not None + assert rir.gable_1_length_m == 6.4 + assert rir.gable_2_length_m == 6.4 + # --- ventilation (sap_ventilation) --- def test_sap_ventilation_extract_fans_count_flows_through_to_calculator_input( diff --git a/datatypes/epc/schema/rdsap_schema_21_0_0.py b/datatypes/epc/schema/rdsap_schema_21_0_0.py index 54077b20..279c35b9 100644 --- a/datatypes/epc/schema/rdsap_schema_21_0_0.py +++ b/datatypes/epc/schema/rdsap_schema_21_0_0.py @@ -166,11 +166,26 @@ class SapFloorDimension: floor_construction: Optional[int] = None +@dataclass +class RoomInRoofType1: + """RdSAP §3.9.1 Simplified Type 1 RR — gable lengths only. + + `gable_wall_type_*` is the Table 4 gable variant (0 = external, etc.; + full enum not yet mapped). `gable_wall_length_*` is the run of the + external gable in metres. Heights are NOT lodged here — the cascade + applies the §3.9.1 default storey height (2.45 m).""" + gable_wall_type_1: Optional[int] = None + gable_wall_type_2: Optional[int] = None + gable_wall_length_1: Optional[float] = None + gable_wall_length_2: Optional[float] = None + + @dataclass class SapRoomInRoof: """Room-in-roof details. insulation and roof_room_connected removed in schema 21.0.0.""" floor_area: Union[int, float] construction_age_band: str + room_in_roof_type_1: Optional[RoomInRoofType1] = 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 f9c6125b..db89194b 100644 --- a/datatypes/epc/schema/rdsap_schema_21_0_1.py +++ b/datatypes/epc/schema/rdsap_schema_21_0_1.py @@ -176,10 +176,25 @@ class SapFloorDimension: floor_construction: Optional[int] = None +@dataclass +class RoomInRoofType1: + """RdSAP §3.9.1 Simplified Type 1 RR — gable lengths only. + + `gable_wall_type_*` is the Table 4 gable variant (0 = external, etc.; + full enum not yet mapped). `gable_wall_length_*` is the run of the + external gable in metres. Heights are NOT lodged here — the cascade + applies the §3.9.1 default storey height (2.45 m).""" + gable_wall_type_1: Optional[int] = None + gable_wall_type_2: Optional[int] = None + gable_wall_length_1: Optional[float] = None + gable_wall_length_2: Optional[float] = None + + @dataclass class SapRoomInRoof: floor_area: Union[int, float] construction_age_band: str + room_in_roof_type_1: Optional[RoomInRoofType1] = None @dataclass diff --git a/datatypes/epc/schema/tests/fixtures/21_0_1.json b/datatypes/epc/schema/tests/fixtures/21_0_1.json index ff332801..a8c8d645 100644 --- a/datatypes/epc/schema/tests/fixtures/21_0_1.json +++ b/datatypes/epc/schema/tests/fixtures/21_0_1.json @@ -126,7 +126,16 @@ "identifier": "Main Dwelling", "wall_dry_lined": "N", "floor_heat_loss": 7, - "sap_room_in_roof": {"floor_area": 100, "construction_age_band": "B"}, + "sap_room_in_roof": { + "floor_area": 100, + "construction_age_band": "B", + "room_in_roof_type_1": { + "gable_wall_type_1": 0, + "gable_wall_type_2": 0, + "gable_wall_length_1": 6.4, + "gable_wall_length_2": 6.4 + } + }, "roof_construction": 4, "wall_construction": 4, "building_part_number": 1,