From a05ecacd678439cfcfea3d5c8f7c312da82d8149 Mon Sep 17 00:00:00 2001 From: Khalim Conn-Kowlessar Date: Sun, 24 May 2026 15:06:32 +0000 Subject: [PATCH] =?UTF-8?q?Slice=2043:=20percent=5Fdraughtproofed=20mapper?= =?UTF-8?q?=20fix=20=E2=80=94=20surface=20lodged=20value=20on=20EpcPropert?= =?UTF-8?q?yData?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Mapper-drop audit across the 9-fixture cohort: `percent_draughtproofed` is lodged on 9/9 certs (raw values 85-100) but the schema-21.0.1 mapper never set it on EpcPropertyData. The site-notes mappers always have (line 312 of mapper.py); only the API path was missing. cert_to_inputs reads `epc.percent_draughtproofed` for the §2 ventilation cascade (window draught loss); with None → 0 default, the calc was treating every API-routed cert as fully draughty — over-counting draught infiltration on every fixture in the cohort. Fix: `percent_draughtproofed=schema.percent_draughtproofed` in `from_rdsap_schema_21_0_1`. Cohort SAP / PE / CO2 shifts (all 9 fixtures move; many shift one SAP point because the continuous SAP was near a rounding boundary): cert old SAP new SAP PE shift CO2 shift 0240-0200-5706-2365-8010 -12 -10 -7.63 -0.39 0300-2747-7640-2526-2135 -9 -7 -6.36 -0.55 0390-2254-6420-2126-5561 (LN12) 0 +1 -9.10 -0.13 0390-2954-3640-2196-4175 -7 -4 -4.87 -0.44 2130-1033-4050-5007-8395 (DE22) +8 +9 -3.67 -0.04 6035-7729-2309-0879-2296 -6 -5 -8.90 -0.21 7536-3827-0600-0600-0276 +3 +4 -9.19 -0.24 8135-1728-8500-0511-3296 +1 +1 (cont -7.48 -0.14 72.7→73.5) 9390-2722-3520-2105-8715 +2 +3 -7.32 -0.01 LN12 lost its exact-SAP-match (0 → +1, continuous 65.47 → 66.28); the other fixtures' rounded SAP residuals tightened or worsened by 1 depending on which side of the rounding boundary they sit. This is spec-correctness over residual-tightness: the lodged value is correct, our calc now reads it. 930/930 Elmhurst cascade green. 78/78 mapper tests + 14/14 golden cohort + PCDB chain green. Pyright net-zero. Co-Authored-By: Claude Opus 4.7 --- datatypes/epc/domain/mapper.py | 4 ++ .../domain/tests/test_from_rdsap_schema.py | 15 ++++++ .../sap/rdsap/tests/test_golden_fixtures.py | 46 +++++++++---------- 3 files changed, 42 insertions(+), 23 deletions(-) diff --git a/datatypes/epc/domain/mapper.py b/datatypes/epc/domain/mapper.py index 722e92c2..14ac33ec 100644 --- a/datatypes/epc/domain/mapper.py +++ b/datatypes/epc/domain/mapper.py @@ -1564,6 +1564,10 @@ class EpcPropertyDataMapper: multiple_glazed_proportion=schema.multiple_glazed_proportion, extract_fans_count=schema.extract_fans_count, blocked_chimneys_count=schema.blocked_chimneys_count, + # cert_to_inputs reads epc.percent_draughtproofed for the §2 + # ventilation cascade (window draught loss). Without this the + # cascade defaults to 0 — treats every cert as fully draughty. + percent_draughtproofed=schema.percent_draughtproofed, insulated_door_u_value=schema.insulated_door_u_value, mechanical_vent_duct_placement=schema.mechanical_vent_duct_placement, mechanical_vent_duct_insulation=schema.mechanical_vent_duct_insulation, diff --git a/datatypes/epc/domain/tests/test_from_rdsap_schema.py b/datatypes/epc/domain/tests/test_from_rdsap_schema.py index ce83ddce..428f4b8a 100644 --- a/datatypes/epc/domain/tests/test_from_rdsap_schema.py +++ b/datatypes/epc/domain/tests/test_from_rdsap_schema.py @@ -622,6 +622,21 @@ class TestFromRdSapSchema21_0_1: assert sv is not None assert sv.extract_fans_count == 2 + def test_percent_draughtproofed_flows_through_to_calculator_input( + self, result: EpcPropertyData + ) -> None: + # Arrange — fixture lodges `percent_draughtproofed: 100` at the + # cert root. cert_to_inputs reads it via epc.percent_draughtproofed + # for the §2 ventilation cascade (window draught loss). Without + # this the cascade defaults to 0 — treats every cert as fully + # draughty, over-counting infiltration. + + # Act + v = result.percent_draughtproofed + + # Assert + assert v == 100 + def test_ventilation_completeness_all_seven_vent_fields_flow_through( self, result: EpcPropertyData ) -> None: diff --git a/packages/domain/src/domain/sap/rdsap/tests/test_golden_fixtures.py b/packages/domain/src/domain/sap/rdsap/tests/test_golden_fixtures.py index 83bd83a2..7c7ccb17 100644 --- a/packages/domain/src/domain/sap/rdsap/tests/test_golden_fixtures.py +++ b/packages/domain/src/domain/sap/rdsap/tests/test_golden_fixtures.py @@ -74,9 +74,9 @@ _EXPECTATIONS: tuple[_GoldenExpectation, ...] = ( _GoldenExpectation( cert_number="0240-0200-5706-2365-8010", actual_sap=73, - expected_sap_resid=-12, - expected_pe_resid_kwh_per_m2=+5.5809, - expected_co2_resid_tonnes_per_yr=+0.3436, + expected_sap_resid=-10, + expected_pe_resid_kwh_per_m2=-2.0499, + expected_co2_resid_tonnes_per_yr=-0.0447, notes=( "Detached house, TFA 202, age J, oil boiler, Table 4b code 130. " "API response lodges sap_room_in_roof.room_in_roof_type_1 with " @@ -94,9 +94,9 @@ _EXPECTATIONS: tuple[_GoldenExpectation, ...] = ( _GoldenExpectation( cert_number="0300-2747-7640-2526-2135", actual_sap=78, - expected_sap_resid=-9, - expected_pe_resid_kwh_per_m2=+4.4546, - expected_co2_resid_tonnes_per_yr=-0.5359, + expected_sap_resid=-7, + expected_pe_resid_kwh_per_m2=-1.9082, + expected_co2_resid_tonnes_per_yr=-1.0829, notes=( "Large semi-detached, TFA 526, age D, gas boiler PCDB-listed " "(no Table 4b code). Cert lodges open_flues_count=1 + " @@ -106,41 +106,41 @@ _EXPECTATIONS: tuple[_GoldenExpectation, ...] = ( _GoldenExpectation( cert_number="0390-2954-3640-2196-4175", actual_sap=60, - expected_sap_resid=-7, - expected_pe_resid_kwh_per_m2=-26.6760, - expected_co2_resid_tonnes_per_yr=-2.5816, + expected_sap_resid=-4, + expected_pe_resid_kwh_per_m2=-31.5482, + expected_co2_resid_tonnes_per_yr=-3.0251, notes="Large detached, TFA 360, age F, oil PCDB-listed. Cert lodges has_draught_lobby=true.", ), _GoldenExpectation( cert_number="6035-7729-2309-0879-2296", actual_sap=70, - expected_sap_resid=-6, - expected_pe_resid_kwh_per_m2=+45.0454, - expected_co2_resid_tonnes_per_yr=+1.0245, + expected_sap_resid=-5, + expected_pe_resid_kwh_per_m2=+36.1487, + expected_co2_resid_tonnes_per_yr=+0.8134, notes="Mid-terrace, TFA 128, age A, gas combi Table 4b code 104.", ), _GoldenExpectation( cert_number="7536-3827-0600-0600-0276", actual_sap=68, - expected_sap_resid=+3, - expected_pe_resid_kwh_per_m2=-17.9833, - expected_co2_resid_tonnes_per_yr=-0.4781, + expected_sap_resid=+4, + expected_pe_resid_kwh_per_m2=-27.1721, + expected_co2_resid_tonnes_per_yr=-0.7230, notes="Detached + 2 extensions, TFA 152, age D, gas PCDB.", ), _GoldenExpectation( cert_number="8135-1728-8500-0511-3296", actual_sap=72, expected_sap_resid=+1, - expected_pe_resid_kwh_per_m2=-9.5017, - expected_co2_resid_tonnes_per_yr=-0.1537, + expected_pe_resid_kwh_per_m2=-16.9775, + expected_co2_resid_tonnes_per_yr=-0.2951, notes="Semi-detached, TFA 102, age C, gas PCDB-listed. Cert lodges blocked_chimneys_count=1.", ), _GoldenExpectation( cert_number="2130-1033-4050-5007-8395", actual_sap=82, - expected_sap_resid=+8, - expected_pe_resid_kwh_per_m2=-65.8945, - expected_co2_resid_tonnes_per_yr=+0.1856, + expected_sap_resid=+9, + expected_pe_resid_kwh_per_m2=-69.5678, + expected_co2_resid_tonnes_per_yr=+0.1422, notes=( "End-terrace + 1 extension, TFA 64, gas combi PCDB index 17505, " "postcode DE22 (PCDB Table 172 match), PV: 2× 2.04 kWp arrays " @@ -158,9 +158,9 @@ _EXPECTATIONS: tuple[_GoldenExpectation, ...] = ( _GoldenExpectation( cert_number="0390-2254-6420-2126-5561", actual_sap=65, - expected_sap_resid=0, - expected_pe_resid_kwh_per_m2=+0.1797, - expected_co2_resid_tonnes_per_yr=+0.0413, + expected_sap_resid=+1, + expected_pe_resid_kwh_per_m2=-8.9202, + expected_co2_resid_tonnes_per_yr=-0.0942, notes=( "End-terrace + 1 extension, TFA 80, gas combi PCDB index 18119, " "no PV, no secondary, postcode LN12 (PCDB Table 172 match). "