mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-08 11:17:27 +00:00
Slice 40: room_in_roof_type_1 gable lengths flow through schema-21 to EpcPropertyData
Schema-21.0.0/0.1's SapRoomInRoof dataclass declared only floor_area and construction_age_band. Real certs lodge gable wall lengths under sap_room_in_roof.room_in_roof_type_1 (RdSAP §3.9.1 Simplified Type 1). from_dict silently dropped the whole block at deserialization, so the mapper never had a chance to surface the lengths on EpcPropertyData. Fix: add RoomInRoofType1 dataclass to both schema-21 variants; extend SapRoomInRoof with `room_in_roof_type_1: Optional[...]`; update the mapper to populate EpcPropertyData.SapRoomInRoof gable_1_length_m / gable_2_length_m from the new field. Calculator behaviour unchanged this slice: heat_transmission.py:243 requires BOTH length AND height to contribute gable area, and the cert lodges length only (RdSAP §3.9.1 uses a default 2.45 m storey height — not yet plumbed). Cert 0240's −12 SAP residual unchanged. Schema scope: both 21.0.0 and 21.0.1 schemas (identical SapBuildingPart mapper code, kept consistent). Older schemas (17/18/19/20) don't carry this RR shape on their dataclasses and are out of scope per the prior cohort scope decision. Unblocks the follow-up slices that close the RR cascade: default H_gable in calculator or mapper, parse "Roof room(s), insulated (assumed)" description for the U-value override, etc. 930/930 Elmhurst cascade green. 14/14 golden cohort green at pinned residuals (no shift, as expected). 76/76 mapper tests green. Pyright net-zero (32 errors before and after). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
1d7c13b995
commit
fb3973457a
5 changed files with 85 additions and 1 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
11
datatypes/epc/schema/tests/fixtures/21_0_1.json
vendored
11
datatypes/epc/schema/tests/fixtures/21_0_1.json
vendored
|
|
@ -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,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue