diff --git a/domain/sap10_calculator/rdsap/cert_to_inputs.py b/domain/sap10_calculator/rdsap/cert_to_inputs.py index be7faa47..7767c788 100644 --- a/domain/sap10_calculator/rdsap/cert_to_inputs.py +++ b/domain/sap10_calculator/rdsap/cert_to_inputs.py @@ -5196,16 +5196,23 @@ def ventilation_from_cert( # can override via a future plumbing slice; the spec default # is what every MEV / MV / MVHR cohort cert lodges today. mv_system_ach = 0.5 - # For a whole-house mechanical EXTRACT system (MEV / dMEV) the - # lodged intermittent extract-fan count (7a) is taken AS-IS — the - # Table 5 age-band default must NOT be substituted for a lodged 0. - # On a mechanically-ventilated dwelling the fan count is explicit - # (the dMEV is the ventilation), so 0 means 0, not "unknown". - # Worksheet-proven on two dMEV builds of 000565: "case 48" lodges - # (7a)=0 → SAP 57 exact, while the original 000565 fixture lodges - # (7a)=2 → unchanged. Scoped to EXTRACT_OR_PIV_OUTSIDE; balanced - # MVHR/MV kinds are left untouched pending their own worksheet. - if mv_kind is MechanicalVentilationKind.EXTRACT_OR_PIV_OUTSIDE: + # For a whole-house mechanical EXTRACT (MEV / dMEV) OR balanced- + # with-heat-recovery (MVHR) system the lodged intermittent extract- + # fan count (7a) is taken AS-IS — the Table 5 age-band default must + # NOT be substituted for a lodged 0. On such a dwelling the fan + # count is explicit (the mechanical system IS the ventilation), so + # 0 means 0, not "unknown". Worksheet-proven on three 000565 builds: + # "case 48" (dMEV) lodges (7a)=0 → SAP 57 exact; "case 49" (MVHR, + # Vent Axia 500140) lodges (7a)=0 → the worksheet (8) openings line + # is 0.0000 (our default had added 20 m³/h = 0.0723 ach, inflating + # (22b)/(25)/(38) and the demand). The original 000565 fixture + # lodges (7a)=2 → unchanged. MV-without-HR (mechanical_ventilation + # =1) is EXCLUDED: forcing its lodged 0 regressed 47 Howsman / 18 + # Jutland and is not worksheet-validated. + if mv_kind in ( + MechanicalVentilationKind.EXTRACT_OR_PIV_OUTSIDE, + MechanicalVentilationKind.MVHR, + ): intermittent_fans = vc.intermittent_fans # SAP 10.2 §2.6.6 equation (2): the (24a) MVHR effective-air-change # credit needs the in-use heat-recovery efficiency (23c) = raw PCDB diff --git a/tests/infrastructure/epc_client/test_sap_accuracy_corpus.py b/tests/infrastructure/epc_client/test_sap_accuracy_corpus.py index dd6e322c..2eb5b80a 100644 --- a/tests/infrastructure/epc_client/test_sap_accuracy_corpus.py +++ b/tests/infrastructure/epc_client/test_sap_accuracy_corpus.py @@ -210,6 +210,11 @@ _MIN_WITHIN_HALF_SAP = 0.72 # 81.9%, (230a) 415.9325, (231) 501.9325 — matching Elmhurst exactly). Corpus # MVHR certs: Flat 1 +6 -> 0, 12a Princes Gate +3 -> +1; Apartment 707's -4 -> # -6 is a separate baseline under-rate (it under-rated as natural too). +# Then 0.782 -> 0.781 via extending the dMEV "lodged 0 extract fans, no Table 5 +# default" rule to MVHR (the balanced system is the ventilation, so a lodged +# (7a)=0 is explicit): case 49's (8) openings line is 0.0000 — our default had +# added 20 m3/h (0.0723 ach), inflating (22b)/(25)/(38) and demand (SAP 70.90 +# -> 71.43; the worksheet's own continuous SAP is 71.69 -> rounds to 72). _MAX_SAP_MAE = 0.785 _MAX_CO2_MAE_TONNES = 0.09 # t CO2 / yr vs co2_emissions_current _MAX_PE_PER_M2_MAE = 3.6 # kWh / m2 / yr vs energy_consumption_current