mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-30 13:10:47 +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(
|
SapRoomInRoof(
|
||||||
floor_area=_measurement_value(bp.sap_room_in_roof.floor_area),
|
floor_area=_measurement_value(bp.sap_room_in_roof.floor_area),
|
||||||
construction_age_band=bp.sap_room_in_roof.construction_age_band,
|
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
|
if bp.sap_room_in_roof
|
||||||
else None
|
else None
|
||||||
|
|
@ -1478,6 +1491,19 @@ class EpcPropertyDataMapper:
|
||||||
SapRoomInRoof(
|
SapRoomInRoof(
|
||||||
floor_area=_measurement_value(bp.sap_room_in_roof.floor_area),
|
floor_area=_measurement_value(bp.sap_room_in_roof.floor_area),
|
||||||
construction_age_band=bp.sap_room_in_roof.construction_age_band,
|
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
|
if bp.sap_room_in_roof
|
||||||
else None
|
else None
|
||||||
|
|
|
||||||
|
|
@ -587,6 +587,25 @@ class TestFromRdSapSchema21_0_1:
|
||||||
def test_party_wall_length(self, result: EpcPropertyData) -> None:
|
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
|
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) ---
|
# --- ventilation (sap_ventilation) ---
|
||||||
|
|
||||||
def test_sap_ventilation_extract_fans_count_flows_through_to_calculator_input(
|
def test_sap_ventilation_extract_fans_count_flows_through_to_calculator_input(
|
||||||
|
|
|
||||||
|
|
@ -166,11 +166,26 @@ class SapFloorDimension:
|
||||||
floor_construction: Optional[int] = None
|
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
|
@dataclass
|
||||||
class SapRoomInRoof:
|
class SapRoomInRoof:
|
||||||
"""Room-in-roof details. insulation and roof_room_connected removed in schema 21.0.0."""
|
"""Room-in-roof details. insulation and roof_room_connected removed in schema 21.0.0."""
|
||||||
floor_area: Union[int, float]
|
floor_area: Union[int, float]
|
||||||
construction_age_band: str
|
construction_age_band: str
|
||||||
|
room_in_roof_type_1: Optional[RoomInRoofType1] = None
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
|
|
|
||||||
|
|
@ -176,10 +176,25 @@ class SapFloorDimension:
|
||||||
floor_construction: Optional[int] = None
|
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
|
@dataclass
|
||||||
class SapRoomInRoof:
|
class SapRoomInRoof:
|
||||||
floor_area: Union[int, float]
|
floor_area: Union[int, float]
|
||||||
construction_age_band: str
|
construction_age_band: str
|
||||||
|
room_in_roof_type_1: Optional[RoomInRoofType1] = None
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@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",
|
"identifier": "Main Dwelling",
|
||||||
"wall_dry_lined": "N",
|
"wall_dry_lined": "N",
|
||||||
"floor_heat_loss": 7,
|
"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,
|
"roof_construction": 4,
|
||||||
"wall_construction": 4,
|
"wall_construction": 4,
|
||||||
"building_part_number": 1,
|
"building_part_number": 1,
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue