diff --git a/datatypes/epc/domain/mapper.py b/datatypes/epc/domain/mapper.py index fe947bf9c..4143a54ae 100644 --- a/datatypes/epc/domain/mapper.py +++ b/datatypes/epc/domain/mapper.py @@ -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 diff --git a/tests/datatypes/epc/domain/test_mapper_building_part_fabric_17_0.py b/tests/datatypes/epc/domain/test_mapper_building_part_fabric_17_0.py new file mode 100644 index 000000000..1a929b28f --- /dev/null +++ b/tests/datatypes/epc/domain/test_mapper_building_part_fabric_17_0.py @@ -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