mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-08 11:17:27 +00:00
Slice S0380.46: wire β-split into CO2 cascade per SAP 10.2 Appendix M1 §7
The CO2 cascade in calculator.py had no PV credit at all
(environmental_section_from_cert had a stale `pv_credit = 0.0` with
the comment "no PV in any Elmhurst fixture", but that helper isn't
called by `calculate_sap_from_inputs` anyway). The full ASHP+PV
cluster therefore over-counted CO2 by +0.16..+0.28 t/yr — the entire
PV CO2 offset was missing.
Wiring (calculator.py):
- New fields: `pv_dwelling_co2_factor_kg_per_kwh: Optional[float]`,
`pv_exported_co2_factor_kg_per_kwh: Optional[float]`.
- CO2 cascade now subtracts:
pv_co2_credit = E_PV,dw × dwelling_CO2_factor
+ E_PV,ex × exported_CO2_factor
when the split + factors are set. None preserves the legacy
zero-credit behaviour for synthetic CalculatorInputs constructions.
Wiring (cert_to_inputs.py):
- New constant: `_PV_EXPORT_FUEL_CODE_TABLE_12 = 60` (SAP 10.2
Table 12 code 60, "electricity sold to grid, PV") — the EXPORT
factor key per Appendix M1 §6/§7/§8.
- The dwelling CO2 factor is the effective monthly Table 12d Σ
weighted by E_PV,dw,m at code 30 (Standard electricity); the
exported CO2 factor is the same Σ weighted by E_PV,ex,m at
code 60 ("Electricity sold to grid, PV"). Both reuse the
existing `_effective_monthly_co2_factor` helper.
Test impact (CO2 residual cluster, re-pinned in this slice):
Pre-Slice 46 → Post-Slice 46:
- 0330 (no PV): -0.034 → -0.034 (unchanged ✓)
- 0350 (PV + 5 kWh battery): +0.171 → -0.084
- 0380 (PV + 5 kWh battery): +0.279 → -0.054
- 2130 (PV + gas combi): +0.299 → -0.046
- 2225 (PV + 5 kWh battery): +0.263 → -0.071
- 2636 (PV + 5 kWh battery): +0.219 → -0.058
- 3800 (PV + 5 kWh battery): +0.261 → -0.014
- 9285 (PV + 5 kWh battery): +0.157 → -0.098
- 9418 (PV + 5 kWh battery): +0.232 → -0.046
- 9501 (PV, no battery): +0.202 → -0.047
Cluster magnitude dropped 3-5× — over-count flipped to slight
under-count (-0.01..-0.10 vs +0.16..+0.28). The remaining negative
residual is largely the same E_PV-magnitude bug from Slice 45 (PV
is over-credited because the cascade thinks E_PV ≈ 3× the worksheet
value for the 5-kWh-battery cohort). Slice 47 (cost cascade) + Slice
S0380.48 (E_PV magnitude audit) will close the cluster further.
Chain tests still <1e-4 — CO2 cascade isn't gated by the chain
tests' SAP-rating-vs-worksheet assertions.
Test suite: 763 pass + 0 fail. Pyright net-zero per touched file
(calculator.py 0/0; cert_to_inputs.py 34/34; test_golden_fixtures.py 1/1).
Spec citations:
- SAP 10.2 specification Appendix M1 §7 (p.94) — PV CO2 credit split.
- SAP 10.2 Table 12d (p.194) code 60 — monthly CO2 factor for
"electricity sold to grid, PV" (already in `tables/table_12.py`).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
49de18e83a
commit
5b269f23b6
3 changed files with 54 additions and 8 deletions
|
|
@ -234,6 +234,15 @@ class CalculatorInputs:
|
|||
# SAP 10.2 Table 12 code 60 ("electricity sold to grid, PV") PE
|
||||
# factor = 0.501. Applied to E_PV,ex when split is set.
|
||||
pv_export_primary_factor: float = 0.501
|
||||
# SAP 10.2 Appendix M1 §7 — per-cert CO2 factors for the PV split.
|
||||
# The dwelling factor is the effective monthly Table 12d IMPORT
|
||||
# factor (Σ(E_PV,dw,m × CO2_30,m) / Σ(E_PV,dw,m)); the exported
|
||||
# factor is the effective monthly Table 12d code-60 ("electricity
|
||||
# sold to grid, PV") factor. Both are computed in cert_to_inputs.
|
||||
# Synthetic CalculatorInputs constructions leave these None → no
|
||||
# PV CO2 credit applied (legacy behaviour).
|
||||
pv_dwelling_co2_factor_kg_per_kwh: Optional[float] = None
|
||||
pv_exported_co2_factor_kg_per_kwh: Optional[float] = None
|
||||
# Secondary heating — SAP 10.2 Table 11 routes a fraction of space
|
||||
# heating demand to a secondary system (0.10 for gas/oil/solid main
|
||||
# systems; 0.15-0.20 for electric room/storage heaters). Fraction
|
||||
|
|
@ -501,6 +510,28 @@ def calculate_sap_from_inputs(inputs: CalculatorInputs) -> SapResult:
|
|||
+ lighting_co2
|
||||
+ electric_shower_co2
|
||||
)
|
||||
# SAP 10.2 Appendix M1 §7 — subtract PV CO2 credit. Onsite consumption
|
||||
# offsets grid imports at the IMPORT CO2 factor (Table 12d weighted
|
||||
# by E_PV,dw,m); exports credit at the EXPORT CO2 factor (Table 12d
|
||||
# code 60 weighted by E_PV,ex,m). Both factors are precomputed in
|
||||
# cert_to_inputs; None preserves the legacy zero-credit behaviour
|
||||
# for synthetic CalculatorInputs constructions.
|
||||
if (
|
||||
inputs.pv_dwelling_kwh_per_yr is not None
|
||||
and inputs.pv_dwelling_co2_factor_kg_per_kwh is not None
|
||||
):
|
||||
co2 -= (
|
||||
inputs.pv_dwelling_kwh_per_yr
|
||||
* inputs.pv_dwelling_co2_factor_kg_per_kwh
|
||||
)
|
||||
if (
|
||||
inputs.pv_exported_kwh_per_yr is not None
|
||||
and inputs.pv_exported_co2_factor_kg_per_kwh is not None
|
||||
):
|
||||
co2 -= (
|
||||
inputs.pv_exported_kwh_per_yr
|
||||
* inputs.pv_exported_co2_factor_kg_per_kwh
|
||||
)
|
||||
|
||||
# Per-end-use effective PE factors. Same shape as the CO2 cascade:
|
||||
# electricity end-uses use Table 12e (p.195) monthly factors weighted
|
||||
|
|
|
|||
|
|
@ -1151,6 +1151,10 @@ def _days_in_month_proportioned(
|
|||
|
||||
_DAYS_IN_MONTH: Final[tuple[int, ...]] = (31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31)
|
||||
_STANDARD_ELECTRICITY_FUEL_CODE: Final[int] = 30
|
||||
# SAP 10.2 Table 12 code 60 — "electricity sold to grid, PV". Used as
|
||||
# the EXPORT factor key for the Appendix M1 §6/§7/§8 PV split:
|
||||
# (1-β)·E_PV credits at this code's monthly Table 12d/12e factor.
|
||||
_PV_EXPORT_FUEL_CODE_TABLE_12: Final[int] = 60
|
||||
|
||||
|
||||
def _co2_factor_kg_per_kwh(main: Optional[MainHeatingDetail]) -> float:
|
||||
|
|
@ -3201,8 +3205,19 @@ def cert_to_inputs(
|
|||
# SAP 10.2 Appendix M1 §3-4 PV split — the cascade applies
|
||||
# IMPORT PEF (Table 12) to the onsite portion and EXPORT PEF
|
||||
# (Table 12 code 60 = 0.501) to the exported portion per §8.
|
||||
# The CO2 factors per §7 are the effective monthly Table 12d
|
||||
# values weighted by the monthly E_PV,dw / E_PV,ex split:
|
||||
# dwelling uses code 30 (Standard electricity); exported uses
|
||||
# code 60 (Electricity sold to grid, PV).
|
||||
pv_dwelling_kwh_per_yr=pv_split.epv_dwelling_kwh_per_yr,
|
||||
pv_exported_kwh_per_yr=pv_split.epv_exported_kwh_per_yr,
|
||||
pv_dwelling_co2_factor_kg_per_kwh=_effective_monthly_co2_factor(
|
||||
pv_split.epv_dwelling_monthly_kwh, _STANDARD_ELECTRICITY_FUEL_CODE,
|
||||
),
|
||||
pv_exported_co2_factor_kg_per_kwh=_effective_monthly_co2_factor(
|
||||
pv_split.epv_exported_monthly_kwh,
|
||||
_PV_EXPORT_FUEL_CODE_TABLE_12,
|
||||
),
|
||||
secondary_heating_fraction=secondary_fraction_value,
|
||||
secondary_heating_efficiency=secondary_efficiency_value,
|
||||
energy_requirements=energy_requirements_result,
|
||||
|
|
|
|||
|
|
@ -201,7 +201,7 @@ _EXPECTATIONS: tuple[_GoldenExpectation, ...] = (
|
|||
actual_sap=82,
|
||||
expected_sap_resid=+1,
|
||||
expected_pe_resid_kwh_per_m2=-9.6962,
|
||||
expected_co2_resid_tonnes_per_yr=+0.2993,
|
||||
expected_co2_resid_tonnes_per_yr=-0.0456,
|
||||
notes=(
|
||||
"End-terrace + 1 extension, TFA 64, gas combi PCDB index 17505, "
|
||||
"postcode DE22 (PCDB Table 172 match), PV: 2× 2.04 kWp arrays "
|
||||
|
|
@ -257,7 +257,7 @@ _EXPECTATIONS: tuple[_GoldenExpectation, ...] = (
|
|||
actual_sap=89,
|
||||
expected_sap_resid=+0,
|
||||
expected_pe_resid_kwh_per_m2=+8.0916,
|
||||
expected_co2_resid_tonnes_per_yr=+0.2785,
|
||||
expected_co2_resid_tonnes_per_yr=-0.0540,
|
||||
notes=(
|
||||
"Mitsubishi PUZ-WM50VHA PCDB 104568, semi-detached bungalow "
|
||||
"TFA 60.43 age D, PV 3 kWp + 5 kWh battery. Worksheet SAP "
|
||||
|
|
@ -275,7 +275,7 @@ _EXPECTATIONS: tuple[_GoldenExpectation, ...] = (
|
|||
actual_sap=84,
|
||||
expected_sap_resid=+0,
|
||||
expected_pe_resid_kwh_per_m2=+2.7315,
|
||||
expected_co2_resid_tonnes_per_yr=+0.1709,
|
||||
expected_co2_resid_tonnes_per_yr=-0.0841,
|
||||
notes=(
|
||||
"Mitsubishi PUZ-WM50VHA PCDB 104568, ASHP cohort cert with "
|
||||
"PV + 5 kWh battery. Worksheet SAP 84.1367. Slice S0380.45 "
|
||||
|
|
@ -288,7 +288,7 @@ _EXPECTATIONS: tuple[_GoldenExpectation, ...] = (
|
|||
actual_sap=89,
|
||||
expected_sap_resid=+0,
|
||||
expected_pe_resid_kwh_per_m2=+4.4804,
|
||||
expected_co2_resid_tonnes_per_yr=+0.2628,
|
||||
expected_co2_resid_tonnes_per_yr=-0.0711,
|
||||
notes=(
|
||||
"Mitsubishi PUZ-WM50VHA PCDB 104568, ASHP cohort cert with "
|
||||
"PV + 5 kWh battery. Worksheet SAP 88.7921. Slice S0380.45 "
|
||||
|
|
@ -300,7 +300,7 @@ _EXPECTATIONS: tuple[_GoldenExpectation, ...] = (
|
|||
actual_sap=86,
|
||||
expected_sap_resid=+0,
|
||||
expected_pe_resid_kwh_per_m2=+3.4216,
|
||||
expected_co2_resid_tonnes_per_yr=+0.2194,
|
||||
expected_co2_resid_tonnes_per_yr=-0.0581,
|
||||
notes=(
|
||||
"Mitsubishi PUZ-WM50VHA PCDB 104568, ASHP cohort cert with "
|
||||
"PV + 5 kWh battery + 3.74 m² cantilever + 12.76 m² alt wall. "
|
||||
|
|
@ -313,7 +313,7 @@ _EXPECTATIONS: tuple[_GoldenExpectation, ...] = (
|
|||
actual_sap=86,
|
||||
expected_sap_resid=+0,
|
||||
expected_pe_resid_kwh_per_m2=+3.5809,
|
||||
expected_co2_resid_tonnes_per_yr=+0.2609,
|
||||
expected_co2_resid_tonnes_per_yr=-0.0142,
|
||||
notes=(
|
||||
"Mitsubishi PUZ-WM50VHA PCDB 104568, ASHP cohort cert with "
|
||||
"PV + 5 kWh battery. Worksheet SAP 86.1458. Slice S0380.45 "
|
||||
|
|
@ -325,7 +325,7 @@ _EXPECTATIONS: tuple[_GoldenExpectation, ...] = (
|
|||
actual_sap=84,
|
||||
expected_sap_resid=+0,
|
||||
expected_pe_resid_kwh_per_m2=+3.1982,
|
||||
expected_co2_resid_tonnes_per_yr=+0.1571,
|
||||
expected_co2_resid_tonnes_per_yr=-0.0979,
|
||||
notes=(
|
||||
"Mitsubishi PUZ-WM50VHA PCDB 104568, ASHP cohort cert with "
|
||||
"PV + 5 kWh battery. Worksheet SAP 84.1369. Slice S0380.45 "
|
||||
|
|
@ -337,7 +337,7 @@ _EXPECTATIONS: tuple[_GoldenExpectation, ...] = (
|
|||
actual_sap=85,
|
||||
expected_sap_resid=+0,
|
||||
expected_pe_resid_kwh_per_m2=+4.6681,
|
||||
expected_co2_resid_tonnes_per_yr=+0.2317,
|
||||
expected_co2_resid_tonnes_per_yr=-0.0463,
|
||||
notes=(
|
||||
"Daikin Altherma EDLQ05CAV3 PCDB 102421 (heating_duration "
|
||||
"code '24' — continuous, all days at Th) + 5 kWh battery. "
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue