Slice S0380.50: §4 seasonal monthly HW fuel for PV β cascade

The PV β-factor cascade was prorating the annual hot-water fuel kWh
uniformly by days when feeding D_PV,m per Appendix M1 footnote 32.
The worksheet uses §4 (219)m = (62)m / efficiency monthly — which is
seasonal (peaks in Jan when cold-mains-inlet drives energy content,
troughs in Jul/Aug). For cert 0380: worksheet Jul (219) = 68.30 kWh
vs cascade days-prorated 74.60 kWh — over-counted summer D_PV by
~6 kWh/month.

Per Appendix M1 footnote 32: "D_PV,m = ... + E_water,m" where
"E_water,m = (219)_m if water heating fuel code applied in Section
10a of the SAP worksheet is 30". (219)_m is the §4 fuel kWh per
month, not annual / 12.

Fix: scale `wh_result.output_monthly_kwh` to sum to the annual fuel
`hw_kwh` (equivalent to dividing each month by the annual-average
efficiency — exact for single-COP HP water heaters; close enough
for PCDB combi winter/summer-split efficiencies because the annual
total already accounts for the seasonal-efficiency mix). None fall-
back to the legacy days-proration when wh_result is absent
(TFA-missing certs).

Cohort PE residual closure (kWh/m²):

| Cert | Post-S0380.49 | Post-S0380.50 |
|---|---:|---:|
| 0350 | -2.96 | **-2.90** |
| 0380 | -3.06 | **-2.96** |
| 2225 | -3.73 | **-3.54** |
| 2636 | -3.44 | **-3.28** |
| 3800 | -3.25 | **-3.16** |
| 9285 | -2.81 | **-2.74** |
| 9418 | -3.01 | **-2.89** |

Modest but real cohort closure (~0.1 kWh/m² each). The remaining
~3 kWh/m² traces to a small cascade β over-count (0.751 vs worksheet
0.739) — likely Appendix L monthly-weighting details for appliances/
cooking/electric-shower in D_PV; deferred to a follow-up slice.

Cert 9501 (PV no battery) unchanged at +0.65 PE.
CO2 cohort: <0.11 t/yr (within tolerance, re-pinned in same slice).
SAP scores all exact. 763 pass + 0 fail. Pyright net-zero.
This commit is contained in:
Khalim Conn-Kowlessar 2026-05-28 19:56:57 +00:00
parent 33edff136a
commit 3d1e6f103a
2 changed files with 58 additions and 35 deletions

View file

@ -3128,6 +3128,28 @@ def cert_to_inputs(
# = electricity → Table 12 code 30) per the Appendix M1 §3a fuel # = electricity → Table 12 code 30) per the Appendix M1 §3a fuel
# inclusion list. # inclusion list.
pv_monthly_kwh = _pv_monthly_generation_kwh(epc, climate) pv_monthly_kwh = _pv_monthly_generation_kwh(epc, climate)
# SAP 10.2 Appendix M1 footnote 32 D_PV,m uses §4 (219)m monthly
# water-heating fuel kWh — which is the (62)m output divided by the
# water-heater efficiency. Uniform days-proration over the annual
# `hw_kwh` over-counts D_PV in summer and under-counts in winter
# (the (45)m hot-water energy content is seasonal, peaking in Jan).
# Scale `wh_output_monthly_kwh` to sum to the annual fuel `hw_kwh`
# — equivalent to dividing each month by the annual-average
# efficiency, which matches the worksheet's (219)m for HP / single-
# efficiency water heaters. For PCDB combis with distinct winter /
# summer efficiencies, `_apply_water_efficiency` already accounted
# for the seasonal split in the annual total; preserving the §4
# monthly shape here keeps the per-month distribution faithful.
if wh_result is not None and sum(wh_result.output_monthly_kwh) > 0:
output_total = sum(wh_result.output_monthly_kwh)
hot_water_monthly_kwh_for_pv = tuple(
wh_result.output_monthly_kwh[m] / output_total * hw_kwh
for m in range(12)
)
else:
hot_water_monthly_kwh_for_pv = _days_in_month_proportioned(
hw_kwh, _DAYS_IN_MONTH,
)
pv_eligible_demand_monthly_kwh = _pv_eligible_demand_monthly_kwh( pv_eligible_demand_monthly_kwh = _pv_eligible_demand_monthly_kwh(
lighting_monthly_kwh=lighting_monthly_kwh, lighting_monthly_kwh=lighting_monthly_kwh,
appliances_monthly_kwh=appliances_monthly_kwh, appliances_monthly_kwh=appliances_monthly_kwh,
@ -3140,7 +3162,7 @@ def cert_to_inputs(
pumps_fans_kwh, _DAYS_IN_MONTH, pumps_fans_kwh, _DAYS_IN_MONTH,
), ),
main_1_fuel_monthly_kwh=energy_requirements_result.main_1_fuel_monthly_kwh, main_1_fuel_monthly_kwh=energy_requirements_result.main_1_fuel_monthly_kwh,
hot_water_monthly_kwh=_days_in_month_proportioned(hw_kwh, _DAYS_IN_MONTH), hot_water_monthly_kwh=hot_water_monthly_kwh_for_pv,
main_fuel_code_table_12=( main_fuel_code_table_12=(
API_FUEL_TO_TABLE_12.get(main_fuel, main_fuel) API_FUEL_TO_TABLE_12.get(main_fuel, main_fuel)
if main_fuel is not None else None if main_fuel is not None else None

View file

@ -257,93 +257,94 @@ _EXPECTATIONS: tuple[_GoldenExpectation, ...] = (
cert_number="0380-2471-3250-2596-8761", cert_number="0380-2471-3250-2596-8761",
actual_sap=89, actual_sap=89,
expected_sap_resid=+0, expected_sap_resid=+0,
expected_pe_resid_kwh_per_m2=-3.0557, expected_pe_resid_kwh_per_m2=-2.9633,
expected_co2_resid_tonnes_per_yr=-0.0549, expected_co2_resid_tonnes_per_yr=-0.0548,
notes=( notes=(
"Mitsubishi PUZ-WM50VHA PCDB 104568, semi-detached bungalow " "Mitsubishi PUZ-WM50VHA PCDB 104568, semi-detached bungalow "
"TFA 60.43 age D, PV 3 kWp + 5 kWh battery. Worksheet SAP " "TFA 60.43 age D, PV 3 kWp + 5 kWh battery. Worksheet SAP "
"88.5104. Slice S0380.48 surfaced the 5-kWh battery from " "88.5104. Slice S0380.48 surfaced the 5-kWh battery (PE "
"the flat-shape real-API JSON (PE +8.09 → -4.01). Slice " "+8.09 → -4.01); .49 wired Table 12e effective-monthly PE "
"S0380.49 then wired effective-monthly Table 12e PE factor " "factor (PE -4.01 → -3.06); Slice S0380.50 replaced "
"into the PV split — dwelling 1.4952 (vs worksheet 1.4960), " "days-prorated hot-water demand in the PV β cascade with "
"exported 0.4283 (vs worksheet 0.4268) — closing PE -4.01 " "the §4 (62)m seasonal output scaled to annual fuel (PE "
"→ -3.06. The residual -3 traces to a small β fine-tuning " "-3.06 → -2.96). Remaining ~3 kWh/m² traces to a small "
"gap (cascade 0.751 vs worksheet 0.7426) — monthly D_PV " "cascade β over-count (0.751 vs worksheet 0.739) — "
"distribution detail." "monthly D_PV distribution for appliances/cooking/electric-"
"shower likely needs Appendix L-aligned monthly weights."
), ),
), ),
_GoldenExpectation( _GoldenExpectation(
cert_number="0350-2968-2650-2796-5255", cert_number="0350-2968-2650-2796-5255",
actual_sap=84, actual_sap=84,
expected_sap_resid=+0, expected_sap_resid=+0,
expected_pe_resid_kwh_per_m2=-2.9642, expected_pe_resid_kwh_per_m2=-2.8973,
expected_co2_resid_tonnes_per_yr=-0.0865, expected_co2_resid_tonnes_per_yr=-0.0864,
notes=( notes=(
"Mitsubishi PUZ-WM50VHA PCDB 104568, ASHP cohort cert with " "Mitsubishi PUZ-WM50VHA PCDB 104568, ASHP cohort cert with "
"PV + 5 kWh battery. Worksheet SAP 84.1367. Slice S0380.49 " "PV + 5 kWh battery. Worksheet SAP 84.1367. Slice S0380.50 "
"Table 12e PE factor wiring closed PE -3.58 → -2.96." "HW seasonal-monthly fix closed PE -2.96 → -2.90."
), ),
), ),
_GoldenExpectation( _GoldenExpectation(
cert_number="2225-3062-8205-2856-7204", cert_number="2225-3062-8205-2856-7204",
actual_sap=89, actual_sap=89,
expected_sap_resid=+0, expected_sap_resid=+0,
expected_pe_resid_kwh_per_m2=-3.7259, expected_pe_resid_kwh_per_m2=-3.5393,
expected_co2_resid_tonnes_per_yr=-0.0729, expected_co2_resid_tonnes_per_yr=-0.0726,
notes=( notes=(
"Mitsubishi PUZ-WM50VHA PCDB 104568, ASHP cohort cert with " "Mitsubishi PUZ-WM50VHA PCDB 104568, ASHP cohort cert with "
"PV + 5 kWh battery. Worksheet SAP 88.7921. Slice S0380.49 " "PV + 5 kWh battery. Worksheet SAP 88.7921. Slice S0380.50 "
"Table 12e PE factor wiring closed PE -4.50 → -3.73." "HW seasonal-monthly fix closed PE -3.73 → -3.54."
), ),
), ),
_GoldenExpectation( _GoldenExpectation(
cert_number="2636-0525-2600-0401-2296", cert_number="2636-0525-2600-0401-2296",
actual_sap=86, actual_sap=86,
expected_sap_resid=+0, expected_sap_resid=+0,
expected_pe_resid_kwh_per_m2=-3.4364, expected_pe_resid_kwh_per_m2=-3.2775,
expected_co2_resid_tonnes_per_yr=-0.0603, expected_co2_resid_tonnes_per_yr=-0.0601,
notes=( notes=(
"Mitsubishi PUZ-WM50VHA PCDB 104568, ASHP cohort cert with " "Mitsubishi PUZ-WM50VHA PCDB 104568, ASHP cohort cert with "
"PV + 5 kWh battery + 3.74 m² cantilever + 12.76 m² alt wall. " "PV + 5 kWh battery + 3.74 m² cantilever + 12.76 m² alt wall. "
"Worksheet SAP 86.2641. Slice S0380.49 Table 12e PE factor " "Worksheet SAP 86.2641. Slice S0380.50 HW seasonal-monthly "
"wiring closed PE -4.14 → -3.44." "fix closed PE -3.44 → -3.28."
), ),
), ),
_GoldenExpectation( _GoldenExpectation(
cert_number="3800-8515-0922-3398-3563", cert_number="3800-8515-0922-3398-3563",
actual_sap=86, actual_sap=86,
expected_sap_resid=+0, expected_sap_resid=+0,
expected_pe_resid_kwh_per_m2=-3.2511, expected_pe_resid_kwh_per_m2=-3.1623,
expected_co2_resid_tonnes_per_yr=-0.0166, expected_co2_resid_tonnes_per_yr=-0.0165,
notes=( notes=(
"Mitsubishi PUZ-WM50VHA PCDB 104568, ASHP cohort cert with " "Mitsubishi PUZ-WM50VHA PCDB 104568, ASHP cohort cert with "
"PV + 5 kWh battery. Worksheet SAP 86.1458. Slice S0380.49 " "PV + 5 kWh battery. Worksheet SAP 86.1458. Slice S0380.50 "
"Table 12e PE factor wiring closed PE -4.01 → -3.25." "HW seasonal-monthly fix closed PE -3.25 → -3.16."
), ),
), ),
_GoldenExpectation( _GoldenExpectation(
cert_number="9285-3062-0205-7766-7200", cert_number="9285-3062-0205-7766-7200",
actual_sap=84, actual_sap=84,
expected_sap_resid=+0, expected_sap_resid=+0,
expected_pe_resid_kwh_per_m2=-2.8078, expected_pe_resid_kwh_per_m2=-2.7361,
expected_co2_resid_tonnes_per_yr=-0.1003, expected_co2_resid_tonnes_per_yr=-0.1002,
notes=( notes=(
"Mitsubishi PUZ-WM50VHA PCDB 104568, ASHP cohort cert with " "Mitsubishi PUZ-WM50VHA PCDB 104568, ASHP cohort cert with "
"PV + 5 kWh battery. Worksheet SAP 84.1369. Slice S0380.49 " "PV + 5 kWh battery. Worksheet SAP 84.1369. Slice S0380.50 "
"Table 12e PE factor wiring closed PE -3.46 → -2.81." "HW seasonal-monthly fix closed PE -2.81 → -2.74."
), ),
), ),
_GoldenExpectation( _GoldenExpectation(
cert_number="9418-3062-8205-3566-7200", cert_number="9418-3062-8205-3566-7200",
actual_sap=85, actual_sap=85,
expected_sap_resid=+0, expected_sap_resid=+0,
expected_pe_resid_kwh_per_m2=-3.0084, expected_pe_resid_kwh_per_m2=-2.8890,
expected_co2_resid_tonnes_per_yr=-0.0485, expected_co2_resid_tonnes_per_yr=-0.0483,
notes=( notes=(
"Daikin Altherma EDLQ05CAV3 PCDB 102421 (heating_duration " "Daikin Altherma EDLQ05CAV3 PCDB 102421 (heating_duration "
"code '24' — continuous, all days at Th) + 5 kWh battery. " "code '24' — continuous, all days at Th) + 5 kWh battery. "
"Worksheet SAP 84.6305. Slice S0380.49 Table 12e PE factor " "Worksheet SAP 84.6305. Slice S0380.50 HW seasonal-monthly "
"wiring closed PE -3.76 → -3.01." "fix closed PE -3.01 → -2.89."
), ),
), ),
) )