mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-08 11:17:27 +00:00
Slice S0380.28: SAP 10.2 Appendix N footnote 43 reciprocal η interpolation — closes the +0.03..+0.06 ASHP precision-floor cluster
Per SAP 10.2 Appendix N, PDF p.101 footnote 43 (line 7053):
"For the efficiency values, the interpolated efficiency is the
reciprocal of linear interpolation between the reciprocals of the
efficiencies."
i.e. 1/η_interp = (1 − t)·1/η_low + t·1/η_high, the weighted harmonic
mean at t = (PSR − PSR_low) / (PSR_high − PSR_low). Cascade was using
**linear** interpolation directly on η — a +0.15..+0.25% over-estimate
in the typical PSR range (1.2..1.5) for ASHPs in the cohort.
Cohort fixture: cert 3336-2825-9400-0512-8292 (Mitsubishi PUZ-WM50VHA,
PCDB 104568). MIT/η-zone cascade matches worksheet EXACTLY (every line
86..92, every month), but η_main_heating cascade 225.443 vs worksheet
224.923 → main_heating_fuel +5.24 kWh/yr too high → ECF 1.5474 vs ws
1.5503 → SAP +0.04 vs worksheet 78.3739. Back-solving the worksheet's
η_main implies η_space_1 = 224.923 / 0.95 ≈ 236.76.
Closed form at PSR=1.40151, bracketing PCDB rows PSR 1.2
(η_space_1=253.9) and PSR 1.5 (η_space_1=229.2):
Linear (pre-slice): 253.9 + (229.2 − 253.9) × 0.6717 = 237.31 ✗
Reciprocal (footnote 43): 1 / ((1 − 0.6717)/253.9 + 0.6717/229.2)
= 1 / 0.004224 = 236.74 ✓
The harmonic mean is curvature-aware: linear interpolation under-
penalises efficiency drops at higher PSR (η typically falls off as
PSR increases past the system's design point) by averaging on η
rather than 1/η. SAP 10.2 footnote 43 is explicit about which side
of the reciprocal the interpolation sits.
Outcome:
Cohort-2 Summary path (38 certs):
exact (<1e-4): 23 → **33** (+10)
≤±0.07: 15 → **5** (-10: HP certs close to exact)
±0.07..0.5: 0 → 0
±0.5..1: 0 → 0
±1+: 0 → 0
RAISES: 0 → 0
Cohort-2 HP cluster post-slice:
0100 +0.00003 ← was +0.00283
0320 -0.00001 ← was +0.01801
0330 -0.00004 ← was +0.01772
2336 +0.00003 ← was +0.01778
3336 +0.00001 ← was +0.04005 (worst residual closes exact)
4536 -0.00002 ← was +0.01312
9036 -0.00003 ← was +0.02159
9796 +0.00000 ← was +0.00174 (post-S0380.27)
2536 +0.00072 ← was +0.00163
2800 +0.00068 ← was +0.00436
4800 +0.00068 ← was +0.02939
9370 +0.00002 ← was +0.00174
9421 +0.00001 ← was +0.00117
Cohort-1 ASHP cohort (7-cert cohort + new chain test certs):
cert 0380: +1e-6 ← was +0.034 (Mitsubishi PUZ-WM50VHA, the
canonical first-HP cohort cert)
cert 3800: -2e-5 ← was +0.021
cert 9418: -3e-7 ← was +0.00004
cert 9285: -3e-5 ← was +0.021
cert 2636: -0.015 ← was +0.003 (cantilever fixture; remaining
residual is non-η in nature)
5 of 7 cohort-1 ASHP certs now hit delta < 1e-4 vs worksheet — the
+0.04 spec-precision-floor cluster diagnosed in
HANDOVER_CERT_0380_MIT_CASCADE.md is the linear-vs-reciprocal η
interpolation bug, not a spec-floor at all. The handover doc's "no
public spec or BRE data field would distinguish these" claim was
incorrect — SAP 10.2 footnote 43 is the resolution.
API path (golden fixtures): 6 ASHP cohort residuals updated to reflect
the cascade closure:
cert 0380 PE: -14.7865 → -14.6848 kWh/m²; CO2: +0.2774 → +0.2780 t/yr
cert 0350 PE: -7.9281 → -7.8741; CO2: +0.1697 → +0.1701
cert 2225 PE: -11.9175 → -11.8557; CO2: +0.2617 → +0.2621
cert 2636 PE: -9.7153 → -9.6692; CO2: +0.2189 → +0.2193
cert 3800 PE: -9.7551 → -9.6838; CO2: +0.2598 → +0.2603
cert 9285 PE: -8.1110 → -8.0466; CO2: +0.1559 → +0.1564
All SAP integer residuals unchanged (cascade tracks the EPC integer
SAP at residual 0 across the cohort).
PSR interpolation unit test (`test_interpolate_heat_pump_efficiency_at
_cert_0380_psr_per_sap_app_n`) updated to reflect the reciprocal
formula with the SAP-10.2-footnote-43 spec citation and closed-form
asserts (η_space_1 ≈ 234.5235; η_water_3 ≈ 285.0861 at PSR=1.43).
Pyright net-zero (1 → 1 across touched files: pcdb/parser.py,
tests/test_pcdb_table_362_lookup.py, rdsap/tests/test_golden_fixtures.py).
Tests: 710 pass (was 710 pre-slice with linear interp + un-updated
pins; net-zero because the 6 golden pin updates + 1 interp test update
exactly offset the 6 + 1 failures the formula change introduced), 10
expected fails unchanged.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
012cbd183f
commit
081bb8fd7e
3 changed files with 60 additions and 42 deletions
|
|
@ -258,8 +258,8 @@ _EXPECTATIONS: tuple[_GoldenExpectation, ...] = (
|
|||
cert_number="0380-2471-3250-2596-8761",
|
||||
actual_sap=89,
|
||||
expected_sap_resid=+0,
|
||||
expected_pe_resid_kwh_per_m2=-14.7865,
|
||||
expected_co2_resid_tonnes_per_yr=+0.2774,
|
||||
expected_pe_resid_kwh_per_m2=-14.6848,
|
||||
expected_co2_resid_tonnes_per_yr=+0.2780,
|
||||
notes=(
|
||||
"Mitsubishi PUZ-WM50VHA PCDB 104568, semi-detached bungalow "
|
||||
"TFA 60.43 age D, PV 3 kWp. Worksheet SAP 88.5104 — slice "
|
||||
|
|
@ -275,8 +275,8 @@ _EXPECTATIONS: tuple[_GoldenExpectation, ...] = (
|
|||
cert_number="0350-2968-2650-2796-5255",
|
||||
actual_sap=84,
|
||||
expected_sap_resid=+0,
|
||||
expected_pe_resid_kwh_per_m2=-7.9281,
|
||||
expected_co2_resid_tonnes_per_yr=+0.1697,
|
||||
expected_pe_resid_kwh_per_m2=-7.8741,
|
||||
expected_co2_resid_tonnes_per_yr=+0.1701,
|
||||
notes=(
|
||||
"Mitsubishi PUZ-WM50VHA PCDB 104568, ASHP cohort cert. "
|
||||
"Worksheet SAP 84.1367 — cascade integer matches lodged."
|
||||
|
|
@ -286,8 +286,8 @@ _EXPECTATIONS: tuple[_GoldenExpectation, ...] = (
|
|||
cert_number="2225-3062-8205-2856-7204",
|
||||
actual_sap=89,
|
||||
expected_sap_resid=+0,
|
||||
expected_pe_resid_kwh_per_m2=-11.9175,
|
||||
expected_co2_resid_tonnes_per_yr=+0.2617,
|
||||
expected_pe_resid_kwh_per_m2=-11.8557,
|
||||
expected_co2_resid_tonnes_per_yr=+0.2621,
|
||||
notes=(
|
||||
"Mitsubishi PUZ-WM50VHA PCDB 104568, ASHP cohort cert with "
|
||||
"PV. Worksheet SAP 88.7921. Slice 102f-prep.8 closed the "
|
||||
|
|
@ -298,8 +298,8 @@ _EXPECTATIONS: tuple[_GoldenExpectation, ...] = (
|
|||
cert_number="2636-0525-2600-0401-2296",
|
||||
actual_sap=86,
|
||||
expected_sap_resid=+0,
|
||||
expected_pe_resid_kwh_per_m2=-9.7153,
|
||||
expected_co2_resid_tonnes_per_yr=+0.2189,
|
||||
expected_pe_resid_kwh_per_m2=-9.6692,
|
||||
expected_co2_resid_tonnes_per_yr=+0.2193,
|
||||
notes=(
|
||||
"Mitsubishi PUZ-WM50VHA PCDB 104568, ASHP cohort cert with "
|
||||
"PV + 3.74 m² cantilever exposed floor + 12.76 m² alt wall. "
|
||||
|
|
@ -313,8 +313,8 @@ _EXPECTATIONS: tuple[_GoldenExpectation, ...] = (
|
|||
cert_number="3800-8515-0922-3398-3563",
|
||||
actual_sap=86,
|
||||
expected_sap_resid=+0,
|
||||
expected_pe_resid_kwh_per_m2=-9.7551,
|
||||
expected_co2_resid_tonnes_per_yr=+0.2598,
|
||||
expected_pe_resid_kwh_per_m2=-9.6838,
|
||||
expected_co2_resid_tonnes_per_yr=+0.2603,
|
||||
notes=(
|
||||
"Mitsubishi PUZ-WM50VHA PCDB 104568, ASHP cohort cert. "
|
||||
"Worksheet SAP 86.1458."
|
||||
|
|
@ -324,8 +324,8 @@ _EXPECTATIONS: tuple[_GoldenExpectation, ...] = (
|
|||
cert_number="9285-3062-0205-7766-7200",
|
||||
actual_sap=84,
|
||||
expected_sap_resid=+0,
|
||||
expected_pe_resid_kwh_per_m2=-8.1110,
|
||||
expected_co2_resid_tonnes_per_yr=+0.1559,
|
||||
expected_pe_resid_kwh_per_m2=-8.0466,
|
||||
expected_co2_resid_tonnes_per_yr=+0.1564,
|
||||
notes=(
|
||||
"Mitsubishi PUZ-WM50VHA PCDB 104568, ASHP cohort cert. "
|
||||
"Worksheet SAP 84.1369."
|
||||
|
|
|
|||
|
|
@ -304,18 +304,32 @@ def interpolate_heat_pump_efficiency_at_psr(
|
|||
*,
|
||||
target_psr: float,
|
||||
) -> tuple[float, float]:
|
||||
"""SAP 10.2 PDF p.100 line 5957 — linear interpolation between the
|
||||
two PSR rows enclosing `target_psr`. Returns `(eta_space_1_pct,
|
||||
eta_water_3_pct)` at the dwelling's PSR.
|
||||
"""SAP 10.2 PDF p.101 footnote 43 (line 7053) — reciprocal-linear
|
||||
interpolation between the two PSR rows enclosing `target_psr`:
|
||||
|
||||
Per spec PDF p.101 lines 6007-6008: 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").
|
||||
"For the efficiency values, the interpolated efficiency is the
|
||||
reciprocal of linear interpolation between the reciprocals of
|
||||
the efficiencies."
|
||||
|
||||
i.e. 1/η_interp = (1 − t)·1/η_low + t·1/η_high, which is the harmonic
|
||||
mean weighted at t. Returns `(eta_space_1_pct, eta_water_3_pct)` at
|
||||
the dwelling's PSR. The interpolation is on the η values themselves
|
||||
(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").
|
||||
|
||||
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.
|
||||
"""
|
||||
if not psr_groups:
|
||||
raise ValueError("PSR groups required for interpolation")
|
||||
|
|
@ -329,13 +343,13 @@ def interpolate_heat_pump_efficiency_at_psr(
|
|||
if low_group.psr <= target_psr <= high_group.psr:
|
||||
span = high_group.psr - low_group.psr
|
||||
t = (target_psr - low_group.psr) / span if span > 0 else 0.0
|
||||
eta_space_1 = (
|
||||
low_group.eta_space_1_pct
|
||||
+ (high_group.eta_space_1_pct - low_group.eta_space_1_pct) * t
|
||||
eta_space_1 = 1.0 / (
|
||||
(1.0 - t) / low_group.eta_space_1_pct
|
||||
+ t / high_group.eta_space_1_pct
|
||||
)
|
||||
eta_water_3 = (
|
||||
low_group.eta_water_3_pct
|
||||
+ (high_group.eta_water_3_pct - low_group.eta_water_3_pct) * t
|
||||
eta_water_3 = 1.0 / (
|
||||
(1.0 - t) / low_group.eta_water_3_pct
|
||||
+ t / high_group.eta_water_3_pct
|
||||
)
|
||||
return (eta_space_1, eta_water_3)
|
||||
# Unreachable: target_psr is between min and max so a bracket exists.
|
||||
|
|
|
|||
|
|
@ -130,9 +130,9 @@ def test_heat_pump_record_psr_groups_for_104568_decoded_per_format_465() -> None
|
|||
|
||||
|
||||
def test_interpolate_heat_pump_efficiency_at_cert_0380_psr_per_sap_app_n() -> None:
|
||||
"""SAP 10.2 PDF p.100 line 5957: "The PSR-dependent results applicable
|
||||
to the dwelling are then obtained by linear interpolation between
|
||||
the two datasets whose PSRs enclose that of the dwelling."
|
||||
"""SAP 10.2 PDF p.101 footnote 43 (line 7053): "For the efficiency
|
||||
values, the interpolated efficiency is the reciprocal of linear
|
||||
interpolation between the reciprocals of the efficiencies."
|
||||
|
||||
Cert 0380's worksheet pins η_space (206) = 223.0480 and η_water (217)
|
||||
= 171.0746 via the SAP 10.2 cascade:
|
||||
|
|
@ -151,7 +151,10 @@ def test_interpolate_heat_pump_efficiency_at_cert_0380_psr_per_sap_app_n() -> No
|
|||
Table 362 record's PSR 1.2 (η_space,1=253.9, η_water,3=287.7) and
|
||||
PSR 1.5 (η_space,1=229.2, η_water,3=284.3) rows. This test exercises
|
||||
the interpolation primitive at PSR=1.43 (close to the worksheet-
|
||||
implied value); slice 102e.0 pins the exact PSR-formula result.
|
||||
implied value); slice S0380.28 swapped the linear formula for the
|
||||
spec-correct reciprocal-linear (footnote 43) and closed the cohort-1
|
||||
ASHP residual cluster from +0.03..+0.06 SAP to delta < 1e-4 vs
|
||||
worksheet on 4 of 5 cohort certs.
|
||||
"""
|
||||
# Arrange
|
||||
from domain.sap10_calculator.tables.pcdb.parser import (
|
||||
|
|
@ -167,12 +170,13 @@ def test_interpolate_heat_pump_efficiency_at_cert_0380_psr_per_sap_app_n() -> No
|
|||
record.psr_groups, target_psr=1.43,
|
||||
)
|
||||
|
||||
# Assert — closed-form linear interpolation between PSR 1.2 and 1.5:
|
||||
# eta_space_1 = 253.9 + (229.2 - 253.9) × (1.43 - 1.2) / (1.5 - 1.2)
|
||||
# = 253.9 - 24.7 × 0.7666667
|
||||
# = 234.9633
|
||||
# eta_water_3 = 287.7 + (284.3 - 287.7) × (1.43 - 1.2) / (1.5 - 1.2)
|
||||
# = 287.7 - 3.4 × 0.7666667
|
||||
# = 285.0933
|
||||
assert abs(eta_space_1 - 234.9633) < 1e-3
|
||||
assert abs(eta_water_3 - 285.0933) < 1e-3
|
||||
# Assert — closed-form reciprocal interpolation between PSR 1.2 and 1.5
|
||||
# at t = (1.43 − 1.2) / (1.5 − 1.2) = 23/30 ≈ 0.7666667:
|
||||
# 1/eta_space_1 = (7/30)/253.9 + (23/30)/229.2
|
||||
# = 7/7617 + 23/6876
|
||||
# ≈ 0.0042641 → eta_space_1 ≈ 234.5235
|
||||
# 1/eta_water_3 = (7/30)/287.7 + (23/30)/284.3
|
||||
# = 7/8631 + 23/8529
|
||||
# ≈ 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
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue