mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-30 13:10:47 +00:00
Back-solve habitable-room count from full-SAP measured living area 🟩
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
af26688846
commit
5ebeb71090
3 changed files with 54 additions and 1 deletions
|
|
@ -678,7 +678,10 @@ class EpcPropertyDataMapper:
|
|||
extensions_count=0,
|
||||
heated_rooms_count=0,
|
||||
open_chimneys_count=0,
|
||||
habitable_rooms_count=0,
|
||||
# D3: full SAP measures living_area; the engine reads it only via
|
||||
# habitable_rooms_count (Table 27). Back-solve the count whose
|
||||
# Table-27 fraction best matches the measured living_area/TFA.
|
||||
habitable_rooms_count=_sap_back_solved_habitable_rooms(schema),
|
||||
# D2: door openings (1/2/3) → counts + area-weighted U. New-build
|
||||
# doors are treated insulated, so insulated_door_count == door_count.
|
||||
door_count=door_count,
|
||||
|
|
@ -2455,6 +2458,30 @@ class EpcPropertyDataMapper:
|
|||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
# RdSAP 10 Table 27 (p.52) living-area fraction by habitable-room count.
|
||||
# Mirrored here read-only to back-solve a room count from full SAP's measured
|
||||
# living_area (single home is the calculator; this is the inverse lookup).
|
||||
_SAP_LIVING_AREA_FRACTION_BY_ROOMS: Final[Dict[int, float]] = {
|
||||
1: 0.75, 2: 0.50, 3: 0.30, 4: 0.25, 5: 0.21, 6: 0.18, 7: 0.16, 8: 0.14,
|
||||
}
|
||||
|
||||
|
||||
def _sap_back_solved_habitable_rooms(schema: SapSchema17_1) -> int:
|
||||
"""D3: pick the habitable-room count whose Table 27 fraction is closest to
|
||||
the measured living_area/total_floor_area, so the engine's Table-27 path
|
||||
reproduces the measured living-area fraction. Falls back to 0 (engine's
|
||||
0.21 SAP-convention default) when living_area or floor area is absent."""
|
||||
if not schema.living_area or not schema.total_floor_area:
|
||||
return 0
|
||||
measured_fraction = float(schema.living_area) / float(schema.total_floor_area)
|
||||
return min(
|
||||
_SAP_LIVING_AREA_FRACTION_BY_ROOMS,
|
||||
key=lambda rooms: abs(
|
||||
_SAP_LIVING_AREA_FRACTION_BY_ROOMS[rooms] - measured_fraction
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def _sap_17_1_building_part(
|
||||
bp: SapBuildingPart_SAP_17_1, index: int
|
||||
) -> SapBuildingPart:
|
||||
|
|
|
|||
|
|
@ -209,6 +209,29 @@ class TestFromSapSchema17_1UnknownOpeningType:
|
|||
EpcPropertyDataMapper.from_sap_schema_17_1(schema)
|
||||
|
||||
|
||||
class TestFromSapSchema17_1LivingArea:
|
||||
"""Slice 6 (D3): full SAP measures living_area but the engine only reads it
|
||||
via habitable_rooms_count (Table 27). Back-solve the room count whose Table
|
||||
27 fraction is closest to the measured living_area/TFA so the existing
|
||||
engine path reproduces the measured living-area fraction."""
|
||||
|
||||
def _map(self, fixture: str) -> EpcPropertyData:
|
||||
schema = from_dict(SapSchema17_1, load(fixture))
|
||||
return EpcPropertyDataMapper.from_sap_schema_17_1(schema)
|
||||
|
||||
def test_sample_back_solves_rooms(self) -> None:
|
||||
# 23.45 / 68 = 0.345 → closest Table 27 fraction is 3 rooms (0.30).
|
||||
assert self._map("sap_17_1.json").habitable_rooms_count == 3
|
||||
|
||||
def test_low_living_fraction_back_solves_high_room_count(self) -> None:
|
||||
# 15.19 / 114 = 0.133 → bottom of Table 27 → 8 rooms.
|
||||
assert self._map("sap_17_1_house.json").habitable_rooms_count == 8
|
||||
|
||||
def test_flat_back_solves_rooms(self) -> None:
|
||||
# 14.87 / 41 = 0.363 → 3 rooms (0.30 closest).
|
||||
assert self._map("sap_17_1_flat.json").habitable_rooms_count == 3
|
||||
|
||||
|
||||
class TestFromSapSchema17_1Perimeter:
|
||||
"""Slice 5 (D1): full SAP lodges no heat-loss perimeter; derive it from the
|
||||
measured exposed-wall areas (wall_type 1/2/3) ÷ Σ storey-heights, with party
|
||||
|
|
|
|||
|
|
@ -121,3 +121,6 @@ class SapSchema17_1:
|
|||
floors: List[EnergyElement]
|
||||
sap_opening_types: List[SapOpeningType]
|
||||
sap_building_parts: List[SapBuildingPart]
|
||||
# measured living-room area (m²); the engine consumes it via a back-solved
|
||||
# habitable_rooms_count (Table 27). Optional — 100% present in the corpus.
|
||||
living_area: Optional[Union[int, float]] = None
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue