mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-30 13:10:47 +00:00
Map non-separated conservatory on the gov-API RdSAP-19.0 path 🟩
Mirror the merged 21.0.1 fix (d501535c): declare the four glazed conservatory
fields on the 19.0 SapBuildingPart so from_dict stops dropping them, exclude the
glazed BP from the fabric loop, and carry it as EpcPropertyData.sap_conservatory
(§6.1). Fixes cert 718138 (conservatory_type=4) mis-scoring as a fabric part and
failing to persist on the NOT-NULL construction_age_band.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
f6ec96fbcd
commit
56add97bd9
3 changed files with 89 additions and 0 deletions
|
|
@ -1568,8 +1568,14 @@ class EpcPropertyDataMapper:
|
|||
else None
|
||||
),
|
||||
)
|
||||
# RdSAP 10 §6.1 — exclude the glazed conservatory BP from the
|
||||
# fabric loop; it is carried as `sap_conservatory` below and
|
||||
# billed by the §6.1 cascade (window/rooflight/floor), not as
|
||||
# a dwelling building part. Mirrors the 21.0.1 path.
|
||||
for bp in schema.sap_building_parts
|
||||
if getattr(bp, "glazed_perimeter", None) is None
|
||||
],
|
||||
sap_conservatory=_api_sap_conservatory(schema.sap_building_parts),
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
|
|
|
|||
|
|
@ -2433,3 +2433,76 @@ class TestNonSeparatedConservatoryApiMirror:
|
|||
# Assert — §6.2: disregarded; no conservatory geometry.
|
||||
assert epc.sap_conservatory is None
|
||||
assert conservatory_geometry(epc) is None
|
||||
|
||||
|
||||
class TestNonSeparatedConservatoryApiMirror19_0:
|
||||
"""RdSAP 10 §6.1 — the same glazed-BP conservatory the 21.0.1 mapper
|
||||
handles is also lodged on RdSAP-Schema-19.0 (repro cert 718138). The four
|
||||
glazed fields were undeclared on the 19.0 `SapBuildingPart`, so `from_dict`
|
||||
dropped them: the conservatory mapped to a fabric-less building part that
|
||||
mis-scored and could not persist (NOT-NULL `construction_age_band`). The
|
||||
19.0 mapper now mirrors the 21.0.1 split.
|
||||
|
||||
Unlike 21.0.1, the 19.0 mapper keeps the **lodged** `total_floor_area`
|
||||
scalar (a real 19.0 cert already lodges the non-separated conservatory in
|
||||
it — 718138: dwelling 140.23 + conservatory 9.71 = 149.94 → lodged 150), so
|
||||
appending a conservatory does NOT change `total_floor_area_m2`. The §6.1
|
||||
floor-area fold reaches the score through the calculator's dimensions, not
|
||||
through the scalar."""
|
||||
|
||||
def test_from_api_response_splits_out_conservatory_building_part(
|
||||
self,
|
||||
) -> None:
|
||||
# Arrange — the 19.0 dwelling plus a non-separated double-glazed
|
||||
# conservatory glazed BP.
|
||||
from datatypes.epc.domain.epc_property_data import SapConservatory
|
||||
from domain.sap10_calculator.worksheet.conservatory import (
|
||||
conservatory_geometry,
|
||||
)
|
||||
|
||||
dwelling_part_count = len(
|
||||
EpcPropertyDataMapper.from_api_response(load("19_0.json")).sap_building_parts
|
||||
)
|
||||
|
||||
cert = load("19_0.json")
|
||||
cert["conservatory_type"] = 4
|
||||
cert["sap_building_parts"].append(
|
||||
{
|
||||
"floor_area": 12.0,
|
||||
"room_height": 1,
|
||||
"double_glazed": "Y",
|
||||
"glazed_perimeter": 9.0,
|
||||
}
|
||||
)
|
||||
|
||||
# Act
|
||||
epc = EpcPropertyDataMapper.from_api_response(cert)
|
||||
|
||||
# Assert — conservatory split out; the glazed BP is NOT a fabric part.
|
||||
assert epc.sap_conservatory == SapConservatory(
|
||||
floor_area_m2=12.0,
|
||||
glazed_perimeter_m=9.0,
|
||||
double_glazed=True,
|
||||
thermally_separated=False,
|
||||
room_height_storeys=1.0,
|
||||
)
|
||||
assert len(epc.sap_building_parts) == dwelling_part_count
|
||||
# §6.1 fold is active (surfaces derived by the shared cascade).
|
||||
assert conservatory_geometry(epc) is not None
|
||||
|
||||
def test_separated_conservatory_lodges_no_glazed_building_part(self) -> None:
|
||||
# Arrange — a separated conservatory (type 2/3) lodges NO glazed BP;
|
||||
# the dwelling is unchanged.
|
||||
from domain.sap10_calculator.worksheet.conservatory import (
|
||||
conservatory_geometry,
|
||||
)
|
||||
|
||||
cert = load("19_0.json")
|
||||
cert["conservatory_type"] = 2
|
||||
|
||||
# Act
|
||||
epc = EpcPropertyDataMapper.from_api_response(cert)
|
||||
|
||||
# Assert — §6.2: disregarded; no conservatory geometry.
|
||||
assert epc.sap_conservatory is None
|
||||
assert conservatory_geometry(epc) is None
|
||||
|
|
|
|||
|
|
@ -122,6 +122,16 @@ class SapBuildingPart:
|
|||
wall_insulation_thickness: Optional[str] = None
|
||||
floor_insulation_thickness: Optional[str] = None
|
||||
flat_roof_insulation_thickness: Optional[Union[str, int]] = None
|
||||
# RdSAP 10 §6.1 — a NON-SEPARATED conservatory (`conservatory_type == 4`) is
|
||||
# lodged by the gov API as a glazed "building part" carrying ONLY these four
|
||||
# fields (no fabric, no floor dimensions). Previously undeclared → dropped by
|
||||
# `from_dict`, so the conservatory was silently lost on the 19.0 path. The
|
||||
# mapper splits this BP out into `EpcPropertyData.sap_conservatory`. Mirrors
|
||||
# the 21.0.1 schema declaration.
|
||||
floor_area: Optional[Union[Measurement, int, float]] = None
|
||||
room_height: Optional[Union[Measurement, int, float]] = None
|
||||
double_glazed: Optional[str] = None
|
||||
glazed_perimeter: Optional[Union[Measurement, int, float]] = None
|
||||
|
||||
|
||||
@dataclass
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue