mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-07-12 13:29:04 +00:00
Normalize RdSAP building-part fabric mapping on the degraded 17.0 path
PRD #1385, building-part fabric family. The 17.0 mapper hardcoded floor_insulation_thickness / sap_room_in_roof to None and omitted flat_roof_insulation_thickness, all of which the 17.0 schema lodges and the 19.0/20.0/21.x paths read: * floor_insulation_thickness: "NI" (no measured thickness) -> None so u_floor reaches its age-band default, else pass through (mirrors the 19.0 note). * flat_roof_insulation_thickness: pass through (was dropped). * sap_room_in_roof: map to SapRoomInRoof(floor_area, construction_age_band), None if absent (mirrors 19.0). 17.0-only: the 17.1 SapBuildingPart schema has none of these fields, so its None there is correct (verified against the dataclass). roof_insulation_thickness is intentionally left raw -- the sloping-ceiling resolve helper moves roof U-values and is a separate follow-up. Verification: new test 3 passed; test_mapper_corpus 6002 passed; SAP-accuracy / RealCertExpectation regressions pass; pyright unchanged (39 -> 39, pre-existing). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
366adaf8c8
commit
7bb89a36f1
2 changed files with 89 additions and 2 deletions
|
|
@ -741,11 +741,32 @@ class EpcPropertyDataMapper:
|
|||
bp, "wall_insulation_thermal_conductivity", None
|
||||
),
|
||||
floor_heat_loss=bp.floor_heat_loss,
|
||||
floor_insulation_thickness=None,
|
||||
# 17.0 lodges these three building-part fabric fields but the
|
||||
# mapper hardcoded them to None / omitted them, unlike the
|
||||
# 19.0/20.0/21.x paths. "NI" (no measured thickness) → None so
|
||||
# u_floor reaches its age-band default rather than the
|
||||
# explicit-zero path (see the 19.0 note). roof_insulation_
|
||||
# thickness is left raw here (the sloping-ceiling resolve
|
||||
# helper is a separate, roof-U-moving follow-up).
|
||||
floor_insulation_thickness=(
|
||||
None
|
||||
if bp.floor_insulation_thickness == "NI"
|
||||
else bp.floor_insulation_thickness
|
||||
),
|
||||
flat_roof_insulation_thickness=bp.flat_roof_insulation_thickness,
|
||||
roof_construction=bp.roof_construction,
|
||||
roof_insulation_location=bp.roof_insulation_location,
|
||||
roof_insulation_thickness=bp.roof_insulation_thickness,
|
||||
sap_room_in_roof=None,
|
||||
sap_room_in_roof=(
|
||||
SapRoomInRoof(
|
||||
floor_area=_measurement_value(
|
||||
bp.sap_room_in_roof.floor_area
|
||||
),
|
||||
construction_age_band=bp.sap_room_in_roof.construction_age_band,
|
||||
)
|
||||
if bp.sap_room_in_roof
|
||||
else None
|
||||
),
|
||||
)
|
||||
# RdSAP 10 §6.1 — exclude the glazed conservatory BP from the
|
||||
# fabric loop; carried as `sap_conservatory` below. Mirrors the
|
||||
|
|
|
|||
|
|
@ -0,0 +1,66 @@
|
|||
"""Regression: the RdSAP-Schema-17.0 mapper path must carry the lodged
|
||||
building-part fabric fields, like the 19.0/20.0/21.x paths.
|
||||
|
||||
PRD #1385 (mapper normalization), building-part fabric family. 17.0 hardcoded
|
||||
`floor_insulation_thickness` / `sap_room_in_roof` to None and omitted
|
||||
`flat_roof_insulation_thickness`, all of which the 17.0 schema lodges and the
|
||||
sibling paths read. (17.1 is NOT affected — its SapBuildingPart schema has none
|
||||
of these fields, so its None there is correct.) `roof_insulation_thickness` is
|
||||
intentionally still passed raw; the sloping-ceiling resolve helper is a separate
|
||||
roof-U-moving follow-up.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from datatypes.epc.domain.mapper import EpcPropertyDataMapper
|
||||
|
||||
# Lodges a room-in-roof (floor_area 48.83, age band K).
|
||||
_FIXTURE_RIR = Path("tests/fixtures/epc_prediction/BD24JG/cert-75aa70bdd22a.json")
|
||||
# Lodges a flat-roof insulation thickness code ("AB").
|
||||
_FIXTURE_FLAT_ROOF = Path("tests/fixtures/epc_prediction/BD24JG/cert-01f1488000e8.json")
|
||||
|
||||
|
||||
def _load(path: Path) -> dict[str, Any]:
|
||||
data: dict[str, Any] = json.loads(path.read_text())
|
||||
assert data["schema_type"] == "RdSAP-Schema-17.0"
|
||||
return data
|
||||
|
||||
|
||||
def test_17_0_maps_lodged_room_in_roof() -> None:
|
||||
epc = EpcPropertyDataMapper.from_api_response(_load(_FIXTURE_RIR))
|
||||
|
||||
rirs = [bp.sap_room_in_roof for bp in epc.sap_building_parts if bp.sap_room_in_roof]
|
||||
assert rirs, "room-in-roof was dropped (mapped to None)"
|
||||
assert rirs[0].floor_area == 48.83
|
||||
assert rirs[0].construction_age_band == "K"
|
||||
|
||||
|
||||
def test_17_0_maps_lodged_flat_roof_insulation_thickness() -> None:
|
||||
epc = EpcPropertyDataMapper.from_api_response(_load(_FIXTURE_FLAT_ROOF))
|
||||
|
||||
vals = [
|
||||
bp.flat_roof_insulation_thickness
|
||||
for bp in epc.sap_building_parts
|
||||
if bp.flat_roof_insulation_thickness is not None
|
||||
]
|
||||
assert "AB" in vals
|
||||
|
||||
|
||||
def test_17_0_floor_insulation_thickness_flows_and_ni_maps_to_none() -> None:
|
||||
# A measured thickness carries through (schema lodges None on the real certs;
|
||||
# inject to prove the field is no longer hardcoded to None).
|
||||
data = _load(_FIXTURE_RIR)
|
||||
data["sap_building_parts"][0]["floor_insulation_thickness"] = "150"
|
||||
epc = EpcPropertyDataMapper.from_api_response(data)
|
||||
assert epc.sap_building_parts[0].floor_insulation_thickness == "150"
|
||||
|
||||
# The "NI" sentinel (no measured thickness) maps to None so u_floor reaches
|
||||
# its age-band default, matching the 19.0 path.
|
||||
data_ni = _load(_FIXTURE_RIR)
|
||||
data_ni["sap_building_parts"][0]["floor_insulation_thickness"] = "NI"
|
||||
epc_ni = EpcPropertyDataMapper.from_api_response(data_ni)
|
||||
assert epc_ni.sap_building_parts[0].floor_insulation_thickness is None
|
||||
Loading…
Add table
Reference in a new issue