diff --git a/tests/domain/sap10_calculator/rdsap/test_cert_to_inputs.py b/tests/domain/sap10_calculator/rdsap/test_cert_to_inputs.py index 17edfabd..8aa84a85 100644 --- a/tests/domain/sap10_calculator/rdsap/test_cert_to_inputs.py +++ b/tests/domain/sap10_calculator/rdsap/test_cert_to_inputs.py @@ -5690,7 +5690,10 @@ def test_mid_floor_flat_dwelling_type_zeroes_floor_and_roof_heat_transmission() # Arrange — A "Mid-floor flat" has party floor (downstairs flat) and # party ceiling (upstairs flat). The mapper must wire DwellingExposure # to suppress both channels so the HLC matches what RdSAP-driven - # assessor software produces. + # assessor software produces. A genuine mid-floor flat lodges + # roof_construction=7 ("dwelling above") — the party-ceiling code — + # NOT an external-roof construction (the default 4 = exposed pitched + # roof, which `_cert_lodges_exposed_roof` now reads as top-floor). epc = make_minimal_sap10_epc( total_floor_area_m2=_TYPICAL_TFA_M2, habitable_rooms_count=4, @@ -5698,6 +5701,7 @@ def test_mid_floor_flat_dwelling_type_zeroes_floor_and_roof_heat_transmission() dwelling_type="Mid-floor flat", sap_building_parts=[ make_building_part( + roof_construction=7, # "dwelling above" — party ceiling floor_dimensions=[ make_floor_dimension(total_floor_area_m2=_TYPICAL_TFA_M2, floor=0), ], @@ -5718,6 +5722,49 @@ def test_mid_floor_flat_dwelling_type_zeroes_floor_and_roof_heat_transmission() assert inputs.heat_transmission.walls_w_per_k > 0 +def test_mid_floor_label_with_exposed_roof_element_exposes_roof() -> None: + # Arrange — gov-API certs lodge `dwelling_type` as the raw assessor + # label, which can contradict the lodged fabric. Property 715363 + # (uprn 6027561) and its sibling 715395 (6027563) lodge + # dwelling_type="Mid-floor flat" yet carry a real exposed flat roof + # (roof_construction=1, "Flat", with lodged insulation) over a + # "(another dwelling below)" floor — i.e. they are TOP-floor flats + # mislabelled mid-floor. Keying roof exposure on the label alone + # dropped the roof heat-loss term, under-reading space-heating demand + # ~32% and over-reading SAP +7 (calc 81 vs lodged 74). The correctly + # labelled top-floor sibling 715871 (6027574), same block + same flat + # roof, already computes the lodged 74 — the ground truth here. + # + # The lodged roof element is the authoritative signal: an assessor + # only lodges a non-party roof_construction (anything but 7 = "dwelling + # above") when the roof is a heat-loss surface. + epc = make_minimal_sap10_epc( + total_floor_area_m2=_TYPICAL_TFA_M2, + habitable_rooms_count=4, + region_code="1", + dwelling_type="Mid-floor flat", + sap_building_parts=[ + make_building_part( + roof_construction=1, # Flat — a genuine exposed roof + floor_dimensions=[ + make_floor_dimension(total_floor_area_m2=_TYPICAL_TFA_M2, floor=0), + ], + ), + ], + sap_heating=make_sap_heating( + main_heating_details=[_gas_boiler_detail(sap_main_heating_code=102)], + ), + ) + + # Act + inputs = cert_to_inputs(epc) + + # Assert — the floor stays party (dwelling below) but the lodged + # exposed roof is honoured, matching the top-floor sibling. + assert inputs.heat_transmission.floor_w_per_k == 0.0 + assert inputs.heat_transmission.roof_w_per_k > 0 + + def test_top_floor_flat_keeps_roof_drops_floor() -> None: # Arrange — Top-floor flat: party floor, external roof. epc = make_minimal_sap10_epc(