Slice 43: percent_draughtproofed mapper fix — surface lodged value on EpcPropertyData

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 <noreply@anthropic.com>
This commit is contained in:
Khalim Conn-Kowlessar 2026-05-24 15:06:32 +00:00
parent 6836aed004
commit a05ecacd67
3 changed files with 42 additions and 23 deletions

View file

@ -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,

View file

@ -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:

View file

@ -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). "