From 3d93c7b7d5294c538fd3d73f61db2239f38aabf0 Mon Sep 17 00:00:00 2001 From: Khalim Conn-Kowlessar Date: Mon, 29 Jun 2026 15:08:15 +0000 Subject: [PATCH 1/6] =?UTF-8?q?test(pcdb):=20PSR-extension=20toward=20100%?= =?UTF-8?q?=20above=20largest=20/=20below=20smallest=20=F0=9F=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit SAP 10.2 Appendix N2 (PDF p.101, footnote 44/45): for an air/ground/water source heat pump whose plant size ratio exceeds the record's largest PSR, the efficiency is reciprocal-interpolated between the largest-PSR value and 100% at twice the largest PSR (100% beyond that); below the smallest PSR the efficiency is 100%. Our interpolator instead clamps to the top/bottom row. Anchored to the accredited Elmhurst worksheet for cert 100110101713 (golden fixture case 56, PCDB 100061): PSR 3.10665 over the record's largest 2.0 gives eta_space,1 = 147.011 -> (206) = 0.95 x 147.011 = 139.660, vs the clamped 352.0 -> 334.4% that over-rates the dwelling by +18 SAP. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../test_pcdb_table_362_lookup.py | 85 +++++++++++++++++++ 1 file changed, 85 insertions(+) diff --git a/tests/domain/sap10_calculator/test_pcdb_table_362_lookup.py b/tests/domain/sap10_calculator/test_pcdb_table_362_lookup.py index fe3d40b6..58e97446 100644 --- a/tests/domain/sap10_calculator/test_pcdb_table_362_lookup.py +++ b/tests/domain/sap10_calculator/test_pcdb_table_362_lookup.py @@ -180,3 +180,88 @@ def test_interpolate_heat_pump_efficiency_at_cert_0380_psr_per_sap_app_n() -> No # ≈ 0.0035077 → eta_water_3 ≈ 285.0861 assert abs(eta_space_1 - 234.5235) < 1e-3 assert abs(eta_water_3 - 285.0861) < 1e-3 + + +def test_interpolate_extends_above_largest_psr_toward_100pct_per_app_n() -> None: + """SAP 10.2 Appendix N2 (PDF p.101, footnote 44/45) — PSR above the + record's largest value extends the efficiency toward 100%, it is NOT + clamped to the top-of-table value. + + "in the case of a heat pump (ground, water or air source), where + the PSR is greater than the largest value in the data record, an + efficiency may be obtained from linear interpolation between that + at the largest PSR in the data record and efficiency 100% at PSR + two times the largest PSR in the data record. If the PSR is + greater than two times the largest PSR in the data record an + efficiency of 100% should be used." + + Interpolation is reciprocal-linear (footnote 43). Accredited anchor: + Elmhurst worksheet for cert 100110101713 / "golden fixture debugging" + case 56 (PCDB 100061, ECODAN 8.5 kW, largest PSR row η_space,1=352.0). + The dwelling HLC (39) = 107.8199 W/K and max output 8.106 kW give + + PSR = 8.106 × 1000 / (107.8199 × 24.2) = 3.106650 + + which exceeds the record's largest PSR (2.0). The spec extension to + 100% at 2 × 2.0 = 4.0 yields, at t = (3.106650 − 2.0)/(4.0 − 2.0): + + 1/η = (1 − t)/352.0 + t/100.0 → η_space,1 = 147.011 + + so that (206) = 0.95 × 147.011 = 139.660 — matching the accredited + worksheet exactly. The previous top-of-table clamp returned 352.0 + (→ 334.4%), over-rating the dwelling by +18 SAP. + """ + from domain.sap10_calculator.tables.pcdb.parser import ( + interpolate_heat_pump_efficiency_at_psr, + ) + + record = heat_pump_record(100061) + assert record is not None + assert record.psr_groups[-1].psr == 2.0 + assert record.psr_groups[-1].eta_space_1_pct == 352.0 + + eta_space_1, _eta_water_3 = interpolate_heat_pump_efficiency_at_psr( + record.psr_groups, target_psr=3.106649864134083, + ) + + assert abs(eta_space_1 - 147.011) < 1e-2 + assert abs(0.95 * eta_space_1 - 139.6604) < 1e-2 + + +def test_interpolate_above_twice_largest_psr_is_100pct_per_app_n() -> None: + """SAP 10.2 Appendix N2 — beyond twice the largest PSR the efficiency + is exactly 100% (the upper terminus of the extension), for both space + and water heating PSR-dependent results.""" + from domain.sap10_calculator.tables.pcdb.parser import ( + interpolate_heat_pump_efficiency_at_psr, + ) + + record = heat_pump_record(100061) + assert record is not None + + eta_space_1, eta_water_3 = interpolate_heat_pump_efficiency_at_psr( + record.psr_groups, target_psr=9.0, # > 2 × 2.0 + ) + + assert eta_space_1 == 100.0 + assert eta_water_3 == 100.0 + + +def test_interpolate_below_smallest_psr_is_100pct_per_app_n() -> None: + """SAP 10.2 Appendix N2 (PDF p.101) — "For all heat pumps, an + efficiency of 100% should be used if the PSR is less than the smallest + value in the database record." (Not clamped to the smallest row.)""" + from domain.sap10_calculator.tables.pcdb.parser import ( + interpolate_heat_pump_efficiency_at_psr, + ) + + record = heat_pump_record(100061) + assert record is not None + assert record.psr_groups[0].psr == 0.2 + + eta_space_1, eta_water_3 = interpolate_heat_pump_efficiency_at_psr( + record.psr_groups, target_psr=0.1, # < 0.2 + ) + + assert eta_space_1 == 100.0 + assert eta_water_3 == 100.0 From 51d8f65aac70c51de2de9b086dd71f9ad18bf528 Mon Sep 17 00:00:00 2001 From: Khalim Conn-Kowlessar Date: Mon, 29 Jun 2026 15:13:16 +0000 Subject: [PATCH 2/6] =?UTF-8?q?fix(pcdb):=20extend=20heat-pump=20efficienc?= =?UTF-8?q?y=20toward=20100%=20beyond=20table=20PSR=20range=20=F0=9F=9F=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit interpolate_heat_pump_efficiency_at_psr clamped to the smallest/largest PSR row when the dwelling's plant size ratio fell outside the record's range. That is the SAP 10.2 Appendix N rule for *combined heat-pump-and-boiler* packages, not for a plain air/ground/water source heat pump. Per Appendix N2 (PDF p.101, footnotes 44/45) a source heat pump whose PSR exceeds the record's largest value takes a reciprocal-linear interpolation between the largest-PSR efficiency and 100% at twice that PSR (100% beyond), and 100% when the PSR is below the record's smallest value. Both the space- and water-heating PSR-dependent efficiencies extend this way. Effect: an oversized heat pump in a small dwelling is no longer credited the full top-of-table COP. Accredited Elmhurst worksheet for cert 100110101713 (golden fixture case 56, PCDB 100061, PSR 3.107 over largest 2.0): (206) 334.4% -> 139.66% = Elmhurst exact. Corpus (RdSAP-21.0.1, n=1000) MAE 0.7397 -> 0.7258, within-0.5 0.7410 held; only two certs move (both oversized-PSR heat pumps), 100110101713 +18.32 -> -4.97. Exhaust-air and combined heat-pump-and-boiler packages have different boundary rules (straight-to-100% / clamp-to-edge) but are not distinguished by the current PCDB parse; the air/ground/water rule is applied uniformly, a documented limitation noted in the function docstring. Co-Authored-By: Claude Opus 4.8 (1M context) --- domain/sap10_calculator/tables/pcdb/parser.py | 64 +++++++++++++++---- 1 file changed, 52 insertions(+), 12 deletions(-) diff --git a/domain/sap10_calculator/tables/pcdb/parser.py b/domain/sap10_calculator/tables/pcdb/parser.py index 4e34195f..27e1b3d1 100644 --- a/domain/sap10_calculator/tables/pcdb/parser.py +++ b/domain/sap10_calculator/tables/pcdb/parser.py @@ -263,6 +263,14 @@ _HP_PSR_GROUP_OFFSET_PSR: Final[int] = 0 _HP_PSR_GROUP_OFFSET_ETA_SPACE_1: Final[int] = 2 _HP_PSR_GROUP_OFFSET_ETA_WATER_3: Final[int] = 6 +# SAP 10.2 Appendix N2 (PDF p.101, footnotes 44/45): out-of-range PSR +# extension for air/ground/water source heat pumps. Above the record's +# largest PSR the efficiency is reciprocal-interpolated toward 100% at +# `_EXTENSION_PSR_MULTIPLE` × the largest PSR; below the smallest PSR, and +# beyond that multiple, the efficiency is the terminal 100%. +_EXTENSION_TERMINAL_EFFICIENCY_PCT: Final[float] = 100.0 +_EXTENSION_PSR_MULTIPLE: Final[float] = 2.0 + def _parse_psr_groups(raw: tuple[str, ...]) -> tuple[PsrEfficiencyGroup, ...]: """Decode the variable-length PSR-dependent block of a format-465 @@ -317,28 +325,60 @@ def interpolate_heat_pump_efficiency_at_psr( (not their reciprocals taken from PCDB), so the η_*_pct values must be strictly positive — every PCDB row in the cohort satisfies this. - Per spec PDF p.100 lines 7039-7072: clamp to the smallest PSR in - the record when `target_psr` is below it, and to the largest when - above ("if the PSR is greater than the largest PSR in the database - record then the heat pump space and water heating fractions for the - largest PSR should be used, and if the PSR is less than the - smallest PSR in the database record then the heat pump space and - water heating fractions for the smallest PSR should be used"). + Out-of-range PSR (spec PDF p.101, footnotes 44/45 — air/ground/water + source heat pumps): + + - Below the smallest PSR in the record: "an efficiency of 100% + should be used if the PSR is less than the smallest value in the + database record." + - Above the largest PSR in the record: "an efficiency may be + obtained from linear interpolation between that at the largest + PSR in the data record and efficiency 100% at PSR two times the + largest PSR in the data record. If the PSR is greater than two + times the largest PSR in the data record an efficiency of 100% + should be used." The interpolation is reciprocal-linear too + (footnote 43), with 100% as the upper anchor. + + Both space- and water-heating PSR-dependent efficiencies extend the + same way. (Exhaust-air heat pumps and combined heat-pump-and-boiler + packages instead use 100% directly above the largest PSR, and combined + packages clamp to the edge rows; neither is distinguished by the + current PCDB parse, so the air/ground/water rule is applied uniformly + — a documented limitation. The dominant RdSAP cohort is air source.) Cohort fixture: cert 3336-2825-9400-0512-8292 (Mitsubishi PUZ-WM50VHA, PCDB 104568) — PSR 1.40151 brackets PCDB rows PSR 1.2 (η_space_1 = 253.9) and PSR 1.5 (η_space_1 = 229.2). Linear (pre-slice): 237.31; reciprocal (spec-faithful): 236.74 — matches worksheet (206)/(210) at 1e-4 once the 0.95 in-use factor is applied. + + Out-of-range anchor: PCDB 100061 (golden fixture case 56), largest PSR + 2.0 (η_space_1=352.0). At dwelling PSR 3.10665 the extension to 100% + at PSR 4.0 gives η_space_1 = 147.011 → (206) = 139.660, matching the + accredited Elmhurst worksheet (vs the old clamp's 352.0 → 334.4%). """ if not psr_groups: raise ValueError("PSR groups required for interpolation") - if target_psr <= psr_groups[0].psr: - first = psr_groups[0] - return (first.eta_space_1_pct, first.eta_water_3_pct) - if target_psr >= psr_groups[-1].psr: + if target_psr < psr_groups[0].psr: + return (_EXTENSION_TERMINAL_EFFICIENCY_PCT, _EXTENSION_TERMINAL_EFFICIENCY_PCT) + if target_psr > psr_groups[-1].psr: last = psr_groups[-1] - return (last.eta_space_1_pct, last.eta_water_3_pct) + upper_psr = _EXTENSION_PSR_MULTIPLE * last.psr + if target_psr >= upper_psr: + return ( + _EXTENSION_TERMINAL_EFFICIENCY_PCT, + _EXTENSION_TERMINAL_EFFICIENCY_PCT, + ) + t = (target_psr - last.psr) / (upper_psr - last.psr) + eta_space_1 = 1.0 / ( + (1.0 - t) / last.eta_space_1_pct + + t / _EXTENSION_TERMINAL_EFFICIENCY_PCT + ) + eta_water_3 = 1.0 / ( + (1.0 - t) / last.eta_water_3_pct + + t / _EXTENSION_TERMINAL_EFFICIENCY_PCT + ) + return (eta_space_1, eta_water_3) for low_group, high_group in zip(psr_groups, psr_groups[1:]): if low_group.psr <= target_psr <= high_group.psr: span = high_group.psr - low_group.psr From 2d6479536c8feb2f3f8d920aae054902ac05416f Mon Sep 17 00:00:00 2001 From: Khalim Conn-Kowlessar Date: Mon, 29 Jun 2026 15:16:07 +0000 Subject: [PATCH 3/6] =?UTF-8?q?test(accuracy):=20ratchet=20SAP=20MAE=200.7?= =?UTF-8?q?40=20->=200.726=20after=20HP=20PSR-extension=20=F0=9F=9F=AA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The heat-pump PSR-extension fix (SAP 10.2 Appendix N2) drops corpus MAE from 0.740 to 0.726 on the 1000-cert RdSAP-21.0.1 sample; within-0.5 holds at 74.1%. Tighten the ceiling and log the slice. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../epc_client/test_sap_accuracy_corpus.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/tests/infrastructure/epc_client/test_sap_accuracy_corpus.py b/tests/infrastructure/epc_client/test_sap_accuracy_corpus.py index cc57d28d..c489e1ef 100644 --- a/tests/infrastructure/epc_client/test_sap_accuracy_corpus.py +++ b/tests/infrastructure/epc_client/test_sap_accuracy_corpus.py @@ -248,7 +248,16 @@ _MIN_WITHIN_HALF_SAP = 0.74 # an identical dwelling rates SAP 87 with "Connected to Dwelling = Yes" (credit # -£167) vs SAP 74 with "No" (credit £0). Enum decoded empirically: 0 = no PV, # 1 = not connected, 2 = connected (the gov-API does not expose it elsewhere). -_MAX_SAP_MAE = 0.740 +# Then 0.740 -> 0.726 via the heat-pump PSR-extension fix (SAP 10.2 Appendix N2, +# PDF p.101 footnotes 44/45): an air/ground/water source heat pump whose plant +# size ratio exceeds the PCDB record's largest PSR is no longer clamped to the +# top-of-table COP — its efficiency is reciprocal-interpolated toward 100% at +# twice the largest PSR (and 100% below the smallest PSR). Accredited Elmhurst +# worksheet for cert 100110101713 (golden fixture case 56, PCDB 100061, PSR +# 3.107 over largest 2.0): (206) 334.4% -> 139.66% = Elmhurst exact. Only two +# certs move (both oversized-PSR heat pumps): 100110101713 +18.32 -> -4.97 and +# 4510053280 -0.61; within-0.5 holds at 74.1%. +_MAX_SAP_MAE = 0.726 _MAX_CO2_MAE_TONNES = 0.09 # t CO2 / yr vs co2_emissions_current _MAX_PE_PER_M2_MAE = 3.5 # kWh / m2 / yr vs energy_consumption_current From 672e6679c850ab3909ae5f4865c07e6231540740 Mon Sep 17 00:00:00 2001 From: Khalim Conn-Kowlessar Date: Mon, 29 Jun 2026 15:33:04 +0000 Subject: [PATCH 4/6] =?UTF-8?q?test(hp):=20floor=20heat-pump=20water-heati?= =?UTF-8?q?ng=20efficiency=20at=20100%=20(App=20N3.7)=20=F0=9F=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit SAP 10.2 Appendix N3.7 ("Thermal efficiency for water heating - heat pumps", PDF p.109): "multiply the thermal efficiency for water heating by the in-use factor in Table N8; subject to a minimum efficiency of 100%." Our _heat_pump_apm_efficiencies applies the in-use factor but omits the floor. Anchored to golden fixture case 56 (PCDB 100061, cert 100110101713): an oversized HP (PSR 3.107) extends water,3 198.9% -> 128.55%, x 0.60 in-use = 77.13% < 100% -> the accredited Elmhurst worksheet (216) reads 100.0000, we read 77.13%. In-range PSR keeps 0.60 x 198.9 = 119.34% (above the floor). Co-Authored-By: Claude Opus 4.8 (1M context) --- .../rdsap/test_cert_to_inputs.py | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) 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 b643bf0d..6b9daf5a 100644 --- a/tests/domain/sap10_calculator/rdsap/test_cert_to_inputs.py +++ b/tests/domain/sap10_calculator/rdsap/test_cert_to_inputs.py @@ -54,6 +54,7 @@ from domain.sap10_calculator.rdsap.cert_to_inputs import ( _apply_heat_network_hiu_default_store, # pyright: ignore[reportPrivateUsage] _cylinder_thermostat_present, # pyright: ignore[reportPrivateUsage] _has_suspended_timber_floor_per_spec, # pyright: ignore[reportPrivateUsage] + _heat_pump_apm_efficiencies, # pyright: ignore[reportPrivateUsage] _heat_network_code_302_effective_factor, # pyright: ignore[reportPrivateUsage] _heat_network_community_fuel_code, # pyright: ignore[reportPrivateUsage] _heat_network_distribution_electricity, # pyright: ignore[reportPrivateUsage] @@ -4841,6 +4842,57 @@ def test_hot_water_from_pcdb_heat_pump_bills_at_app_n_wh_high_rate() -> None: assert abs(rate_immersion - 0.0750) <= 1e-6 +def test_heat_pump_water_efficiency_is_floored_at_100pct_per_app_n3_7() -> None: + # Arrange — SAP 10.2 Appendix N3.7 ("Thermal efficiency for water + # heating – heat pumps", PDF p.109): "multiply the thermal efficiency + # (ηwater) for water heating by the in-use factor in Table N8; subject + # to a MINIMUM EFFICIENCY OF 100%." Our `_heat_pump_apm_efficiencies` + # applied the in-use factor but omitted the floor, so an oversized heat + # pump whose PSR-extended ηwater × 0.60 in-use fell below 100% billed + # water heating at that sub-100% efficiency (over-counting HW fuel). + # + # Accredited anchor: golden fixture case 56 (PCDB 100061, the config of + # cert 100110101713). At HLC 107.82 W/K the PSR is 3.107, above the + # record's largest PSR 2.0, so the Appendix N2 extension takes ηwater,3 + # from 198.9% toward 100% at 2 x 2.0 = 4.0 → 128.55%; × the 0.60 in-use + # factor (Open-EPC certs never lodge cylinder HX area → criteria fail) + # = 77.13% < 100% → the worksheet (216) reads 100.0000. In-range PSR + # (case 54, HLC large) keeps 0.60 × 198.9 = 119.34% (worksheet case 54 + # (216) = 112.5% for its 187.5% record — both above the floor, unchanged). + from domain.sap10_calculator.tables.pcdb import heat_pump_record + + record = heat_pump_record(100061) + assert record is not None + hp_main = MainHeatingDetail( + has_fghrs=False, + main_fuel_type=29, # electricity (heat pump) + heat_emitter_type=1, # radiators + emitter_temperature=0, + main_heating_control=2210, + main_heating_category=4, + sap_main_heating_code=None, + main_heating_index_number=100061, + ) + epc = _typical_semi_detached_epc() # no specified cylinder → in-use 0.60 + + # Act — oversized PSR (extension region) vs an in-range PSR. + _space_ext, water_ext = _heat_pump_apm_efficiencies( + main=hp_main, hp_record=record, + hlc_annual_avg_w_per_k=107.82, # PSR 3.107 > largest 2.0 + epc=epc, + ) or (None, None) + _space_in, water_in = _heat_pump_apm_efficiencies( + main=hp_main, hp_record=record, + hlc_annual_avg_w_per_k=400.0, # PSR 0.837, in range + epc=epc, + ) or (None, None) + + # Assert — extended HP water efficiency is floored at 100% (1.0); the + # in-range PSR keeps the un-floored 0.60 × 198.9% = 119.34%. + assert water_ext is not None and abs(water_ext - 1.0) < 1e-9 + assert water_in is not None and abs(water_in - 0.60 * 198.9 / 100.0) < 1e-9 + + def test_hot_water_immersion_off_peak_bills_at_table_13_blend() -> None: # Arrange — SAP 10.2 Table 12a (PDF p.191) "Immersion water heater" # row routes the WH column to Table 13 (PDF p.197). For an electric From dbaf3c49e20dbe64d8a8509d51918ede20267e76 Mon Sep 17 00:00:00 2001 From: Khalim Conn-Kowlessar Date: Mon, 29 Jun 2026 15:38:31 +0000 Subject: [PATCH 5/6] =?UTF-8?q?fix(hp):=20floor=20heat-pump=20water-heatin?= =?UTF-8?q?g=20efficiency=20at=20100%=20(App=20N3.7)=20=F0=9F=9F=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit _heat_pump_apm_efficiencies applied the Table N8 in-use factor to the PCDB water-heating efficiency but omitted the spec's "subject to a minimum efficiency of 100%" clause (SAP 10.2 Appendix N3.7, PDF p.109). An oversized heat pump whose PSR-extended water,3 x the 0.60 in-use factor fell below 100% therefore billed water heating at that sub-100% efficiency, over- counting hot-water fuel. Apply max(in_use x eta_water, 100%). Accredited Elmhurst worksheet for cert 100110101713 (golden fixture case 56, PCDB 100061): water (216) = 100.0000, which we now match (was 77.13%). Combined with the space-heating PSR- extension fix the cert lands 72.51 vs lodged 73 (|err| 18.32 -> 0.49). Data-driven: only the single oversized-PSR cert moves (in-range heat pumps keep their > 100% water COP, e.g. case 54 112.5%, case 55 179.6%). Corpus (RdSAP-21.0.1 n=1000) MAE 0.726 -> 0.721, within-0.5 74.1% -> 74.2%. Co-Authored-By: Claude Opus 4.8 (1M context) --- domain/sap10_calculator/rdsap/cert_to_inputs.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/domain/sap10_calculator/rdsap/cert_to_inputs.py b/domain/sap10_calculator/rdsap/cert_to_inputs.py index 1cdfd98e..13bf41ee 100644 --- a/domain/sap10_calculator/rdsap/cert_to_inputs.py +++ b/domain/sap10_calculator/rdsap/cert_to_inputs.py @@ -6234,6 +6234,10 @@ _SAP_DESIGN_HEAT_LOSS_DELTA_T_K: Final[float] = 24.2 _HP_SPACE_HEATING_IN_USE_FACTOR_N3_6: Final[float] = 0.95 _HP_IN_USE_FACTOR_CRITERIA_MET: Final[float] = 0.95 _HP_IN_USE_FACTOR_CRITERIA_FAIL: Final[float] = 0.60 +# SAP 10.2 Appendix N3.7 (PDF p.109): the heat-pump water-heating efficiency +# (in-use factor × η_water) is "subject to a minimum efficiency of 100%" — +# below that the direct-electric backup governs. +_HP_WATER_HEATING_MIN_EFFICIENCY: Final[float] = 1.0 def _heat_pump_cylinder_meets_pcdb_criteria( @@ -6325,7 +6329,12 @@ def _heat_pump_apm_efficiencies( main_heating_efficiency = ( _HP_SPACE_HEATING_IN_USE_FACTOR_N3_6 * eta_space_1_pct / 100.0 ) - water_efficiency_pct = in_use_water * eta_water_3_pct / 100.0 + # N3.7: in-use factor × η_water, subject to a minimum efficiency of 100% + # (the direct-electric backup floors the heat pump's water heating). + water_efficiency_pct = max( + in_use_water * eta_water_3_pct / 100.0, + _HP_WATER_HEATING_MIN_EFFICIENCY, + ) return (main_heating_efficiency, water_efficiency_pct) From 8914496456e9fa8465fdcbbb7689bb234560094d Mon Sep 17 00:00:00 2001 From: Khalim Conn-Kowlessar Date: Mon, 29 Jun 2026 15:40:53 +0000 Subject: [PATCH 6/6] =?UTF-8?q?test(accuracy):=20ratchet=20after=20HP=20wa?= =?UTF-8?q?ter-heating=20100%=20floor=20=F0=9F=9F=AA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Appendix N3.7 water-heating 100% floor drops corpus MAE 0.726 -> 0.721 and lifts within-0.5 74.1% -> 74.2% on the 1000-cert RdSAP-21.0.1 sample (cert 100110101713 moves inside +-0.5). Tighten the MAE ceiling to 0.722 and the within-0.5 floor to 0.742, and log the slice. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../epc_client/test_sap_accuracy_corpus.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/tests/infrastructure/epc_client/test_sap_accuracy_corpus.py b/tests/infrastructure/epc_client/test_sap_accuracy_corpus.py index c489e1ef..20a0eeae 100644 --- a/tests/infrastructure/epc_client/test_sap_accuracy_corpus.py +++ b/tests/infrastructure/epc_client/test_sap_accuracy_corpus.py @@ -193,7 +193,10 @@ _CORPUS = Path( # within-0.5 71.6% -> 72.5%, MAE 0.819 -> 0.815. Surfaced by Khalim's Elmhurst # stress worksheet (simulated case 46): closed its last ventilation residual # (our Jan ACH 9.14 -> 9.0748 exact; SAP 29 -> 30 = accredited Elmhurst). -_MIN_WITHIN_HALF_SAP = 0.74 +# 0.74 -> 0.742 via the heat-pump water-heating 100% floor (App N3.7): cert +# 100110101713 moves inside +-0.5 (|err| 4.97 -> 0.49). See the _MAX_SAP_MAE +# log below for the paired space-heating PSR-extension + water-floor slices. +_MIN_WITHIN_HALF_SAP = 0.742 # 0.793 -> 0.789 via the §12 Unknown-meter + dual-electric-immersion off-peak # trigger (RdSAP 10 PDF p.62): Apartment 241 (main 691 + 903 dual immersion) # -5.38 -> -1.05. Worksheet-validated on "simulated case 48" (Elmhurst SAP 57, @@ -257,7 +260,13 @@ _MIN_WITHIN_HALF_SAP = 0.74 # 3.107 over largest 2.0): (206) 334.4% -> 139.66% = Elmhurst exact. Only two # certs move (both oversized-PSR heat pumps): 100110101713 +18.32 -> -4.97 and # 4510053280 -0.61; within-0.5 holds at 74.1%. -_MAX_SAP_MAE = 0.726 +# Then 0.726 -> 0.722 (within-0.5 74.1% -> 74.2%) via the heat-pump water- +# heating 100% floor (SAP 10.2 Appendix N3.7, PDF p.109: in-use x eta_water +# subject to a minimum efficiency of 100%). Only 100110101713 moves: its +# oversized-PSR water eff 0.60 x 128.55% = 77.13% is floored to 100% (= +# accredited Elmhurst (216)), taking the cert 68.03 -> 72.51 (|err| 4.97 -> +# 0.49, now inside +-0.5). In-range heat pumps keep their > 100% water COP. +_MAX_SAP_MAE = 0.722 _MAX_CO2_MAE_TONNES = 0.09 # t CO2 / yr vs co2_emissions_current _MAX_PE_PER_M2_MAE = 3.5 # kWh / m2 / yr vs energy_consumption_current