mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-07-27 23:35:01 +00:00
Carry the surveyed room-in-roof block onto the SAP building part 🟥
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
895df42afe
commit
9599fd8239
2 changed files with 96 additions and 0 deletions
|
|
@ -122,6 +122,75 @@ class TestPhotovoltaicArrayMapping:
|
|||
assert result.sap_energy_source.photovoltaic_arrays is None
|
||||
|
||||
|
||||
class TestRoomInRoofMapping:
|
||||
"""A surveyed room-in-roof block (RdSAP §3.10 detailed measurement) must
|
||||
reach `sap_building_parts[*].sap_room_in_roof` — left None, ~32 m² of
|
||||
poorly-insulated RIR surfaces are billed as well-insulated loft (issue
|
||||
#1590 bug 5; fixture 499617935574 over-rates by ~2-3.5 SAP). Values below
|
||||
are that survey's verbatim lodgement. Gable types code per Table 4:
|
||||
Exposed → gable_wall_external, Party → gable_wall.
|
||||
"""
|
||||
|
||||
_RIR = {
|
||||
"age_range": "B: 1900 - 1929",
|
||||
"floor_area_m2": 32.35,
|
||||
"gables": [
|
||||
{"length_m": 6.64, "height_m": 2.74, "gable_type": "Exposed"},
|
||||
{"length_m": 6.64, "height_m": 2.74, "gable_type": "Party"},
|
||||
],
|
||||
"slopes": [
|
||||
{"length_m": 4.94, "height_m": 1.73},
|
||||
{"length_m": 5.0, "height_m": 2.95},
|
||||
],
|
||||
"common_walls": [
|
||||
{"length_m": 4.94, "height_m": 1.45},
|
||||
{"length_m": 4.94, "height_m": 1.3},
|
||||
],
|
||||
"flat_ceilings": [{"length_m": 4.94, "height_m": 5.17}],
|
||||
}
|
||||
|
||||
def test_room_in_roof_reaches_building_part(self) -> None:
|
||||
# Arrange
|
||||
data = load("pashub_rdsap_site_notes_example1.json")
|
||||
data["roof_space"]["main_building"]["rooms_in_roof"] = True
|
||||
data["roof_space"]["main_building"]["room_in_roof"] = self._RIR
|
||||
survey = from_dict(PasHubRdSapSiteNotes, data)
|
||||
|
||||
# Act
|
||||
rir = EpcPropertyDataMapper.from_site_notes(survey).sap_building_parts[
|
||||
0
|
||||
].sap_room_in_roof
|
||||
|
||||
# Assert
|
||||
assert rir is not None
|
||||
assert rir.floor_area == 32.35
|
||||
assert rir.construction_age_band == "B"
|
||||
surfaces = rir.detailed_surfaces
|
||||
assert surfaces is not None
|
||||
kinds_areas = [(s.kind, round(s.area_m2, 2)) for s in surfaces]
|
||||
assert kinds_areas == [
|
||||
("gable_wall_external", round(6.64 * 2.74, 2)),
|
||||
("gable_wall", round(6.64 * 2.74, 2)),
|
||||
("slope", round(4.94 * 1.73, 2)),
|
||||
("slope", round(5.0 * 2.95, 2)),
|
||||
("common_wall", round(4.94 * 1.45, 2)),
|
||||
("common_wall", round(4.94 * 1.3, 2)),
|
||||
("flat_ceiling", round(4.94 * 5.17, 2)),
|
||||
]
|
||||
|
||||
def test_no_rooms_in_roof_stays_absent(self) -> None:
|
||||
# Arrange — the fixture as lodged (rooms_in_roof=false).
|
||||
survey = from_dict(
|
||||
PasHubRdSapSiteNotes, load("pashub_rdsap_site_notes_example1.json")
|
||||
)
|
||||
|
||||
# Act
|
||||
result = EpcPropertyDataMapper.from_site_notes(survey)
|
||||
|
||||
# Assert
|
||||
assert result.sap_building_parts[0].sap_room_in_roof is None
|
||||
|
||||
|
||||
class TestSystemBuildWallIsNotBasement:
|
||||
"""RdSAP wall-construction code 6 is overloaded: PasHub "System Build
|
||||
(i.e Any Other)" maps to 6, but 6 doubles as the gov-API basement-wall
|
||||
|
|
|
|||
|
|
@ -105,6 +105,32 @@ class BuildingMeasurements:
|
|||
extensions: Optional[List[ExtensionMeasurements]] = None
|
||||
|
||||
|
||||
@dataclass
|
||||
class RoomInRoofSurfaceDetail:
|
||||
"""One surveyed room-in-roof surface (gable / slope / common wall / flat
|
||||
ceiling): length × height, an optional measured U, and — for gables —
|
||||
the Exposed/Party type. Raw survey values; SAP coding happens at the
|
||||
mapper boundary (ADR-0015)."""
|
||||
|
||||
length_m: Optional[float] = None
|
||||
height_m: Optional[float] = None
|
||||
u_value: Optional[float] = None
|
||||
gable_type: Optional[str] = None
|
||||
|
||||
|
||||
@dataclass
|
||||
class RoomInRoofDetail:
|
||||
"""The surveyed §Roof-Space room-in-roof block (RdSAP §3.10 detailed
|
||||
measurement): age range, floor area, and the per-surface detail."""
|
||||
|
||||
age_range: Optional[str] = None
|
||||
floor_area_m2: Optional[float] = None
|
||||
gables: Optional[List[RoomInRoofSurfaceDetail]] = None
|
||||
slopes: Optional[List[RoomInRoofSurfaceDetail]] = None
|
||||
common_walls: Optional[List[RoomInRoofSurfaceDetail]] = None
|
||||
flat_ceilings: Optional[List[RoomInRoofSurfaceDetail]] = None
|
||||
|
||||
|
||||
@dataclass
|
||||
class RoofSpaceDetail:
|
||||
construction_type: str
|
||||
|
|
@ -115,6 +141,7 @@ class RoofSpaceDetail:
|
|||
# Numeric thickness (mm) when known; string (e.g. "As built") when not measured
|
||||
insulation_thickness_mm: Optional[int] = None
|
||||
insulation_thickness: Optional[str] = None
|
||||
room_in_roof: Optional[RoomInRoofDetail] = None
|
||||
|
||||
|
||||
@dataclass
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue