mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-08 11:17:27 +00:00
Slice S0380.105: MEV fans CO2 split via Table 12a Grid 2 + Table 12d (SAP 10.2 §10a / §10b)
Mirror of S0380.103 for the CO2 cascade. Cert 000565 worksheet line
(267):
Pumps, fans and electric keep-hot 252.5159 0.1412 35.3349 (267)
The displayed factor (0.1412) is the ALL_OTHER_USES Table 12d Σ
days-weighted blend; the displayed product (35.3349) is the kWh-
weighted blend across the two Grid 2 categories:
F_FANS = 0.58 × F_code34 + 0.42 × F_code33 = 0.13872 kg/kWh
F_OTHER = 0.80 × F_code34 + 0.20 × F_code33 = 0.14116 kg/kWh
F_eff = (127.5159 × 0.13872 + 125.0 × 0.14116) / 252.5159
= 0.13993 kg/kWh
CO2 = 252.5159 × 0.13993 = 35.3349 kg/yr ✓
Pre-slice the cascade applied 0.14116 to ALL 252.5159 kWh →
35.6457 → +0.31 over ws.
SAP 10.2 Table 12a Grid 2 (PDF p.191) verbatim header:
"Fractions of electricity used at the higher rate, for use in
off-peak tariff calculations
...
Fans for mechanical ventilation systems 10-hour: 0.58
All other uses, and locally generated 10-hour: 0.80
electricity"
SAP 10.2 Table 12d (PDF p.194) verbatim header:
"Where electricity is the fuel used, the relevant set of factors
in the table below should be used to calculate the monthly CO2
emissions INSTEAD of the annual average factor given in Table
12."
The Grid 2 high-rate fraction blends Table 12d high-rate × low-
rate codes per `F_blended = high_frac × F_high + (1 − high_frac)
× F_low`. MEV fans bill at the lower 0.58 high_frac → lower CO2
factor on the higher-carbon high-rate code 34. Cost-side S0380.103
landed the same split for tariff prices; this slice mirrors it
for the CO2 factor.
3-layer fix:
1. New helper `_pumps_fans_co2_factor_kg_per_kwh` returns the
kWh-weighted blend across `FANS_FOR_MECH_VENT` + `ALL_OTHER_USES`
factors. Falls back to the existing `ALL_OTHER_USES` rate on
STANDARD tariff and no-MEV certs (cohort-safe).
2. cert_to_inputs.py wires `mev_kwh_for_cost_split` +
`pumps_fans_kwh` through to the new helper.
3. Field `CalculatorInputs.pumps_fans_co2_factor_kg_per_kwh`
already exists from S0380.65; calculator legacy path unchanged.
Movement at HEAD `7df3fef8` → post-slice (cert 000565):
| Pin | Pre | Post | Δ vs ws |
|------------------------------|-----------:|-----------:|---------:|
| pumps_fans_co2_kg_per_yr | 35.6457 | 35.3349 | ✓ 0 |
| co2_kg_per_yr (TOTAL) | 6445.8198 | 6445.5090 | −2.1173 |
The total CO2 residual moves -1.81 → -2.12 (sign-flip pattern of
S0380.103): the previously-cancelling pumps_fans CO2 over-count
masked the main-heating-fuel CO2 under-count (downstream of the
§3-§8 SH cascade -16 kWh fuel residual). Per user direction
(NEXT_AGENT_PROMPT) transient continuous-SAP / TOTAL drift is OK
while closing a true spec-correct intermediate-value bug; the SH
cascade closure is a separate slice.
Cohort safety: STANDARD-tariff certs return the existing
ALL_OTHER_USES rate (helper falls through). No-MEV certs return
the same rate (mev_kwh_per_yr=0 short-circuit).
Test count: 604 pass + 7 expected 000565 fails → **605 pass + 7
expected 000565 fails** (new
test_summary_000565_mev_fans_co2_factor_uses_table_12a_grid_2_
fans_for_mech_vent_split GREEN). Pyright net-zero per touched
file (45 baseline → 45 post-change).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
7df3fef8bb
commit
8a3aaf7ae6
2 changed files with 117 additions and 3 deletions
|
|
@ -1892,6 +1892,57 @@ def test_summary_000565_mev_fans_cost_uses_table_12a_grid_2_fans_for_mech_vent_r
|
|||
)
|
||||
|
||||
|
||||
def test_summary_000565_mev_fans_co2_factor_uses_table_12a_grid_2_fans_for_mech_vent_split() -> None:
|
||||
# Arrange — SAP 10.2 Table 12a Grid 2 (PDF p.191) + Table 12d
|
||||
# (PDF p.194) — CO2-side mirror of the cost split landed in
|
||||
# S0380.103. The Table 12a Grid 2 high-rate fractions on TEN_HOUR
|
||||
# are:
|
||||
#
|
||||
# Fans for mechanical ventilation systems high_frac = 0.58
|
||||
# All other uses, and locally generated high_frac = 0.80
|
||||
# electricity
|
||||
#
|
||||
# Table 12d codes for TEN_HOUR are 34 (high) + 33 (low). Days-
|
||||
# weighted Σ(F_m × N_m) / Σ N_m over the 12 months of code 30
|
||||
# uniform-per-day proxy yields:
|
||||
#
|
||||
# F_FANS = 0.58 × F_code34 + 0.42 × F_code33 = 0.13872 kg/kWh
|
||||
# F_OTHER = 0.80 × F_code34 + 0.20 × F_code33 = 0.14116 kg/kWh
|
||||
#
|
||||
# Cert 000565 splits pumps_fans into 127.5159 kWh MEV + 125 kWh
|
||||
# non-MEV (45 flue fan + 80 solar HW pump). kWh-weighted blend:
|
||||
#
|
||||
# F_eff = (127.5159 × 0.13872 + 125 × 0.14116) / 252.5159
|
||||
# = 0.13993 kg/kWh
|
||||
#
|
||||
# Worksheet line (267) verifies the split:
|
||||
# Pumps, fans and electric keep-hot 252.5159 × 0.1412 = 35.3349
|
||||
# (display rounds factor to 0.1412 but the product is the
|
||||
# kWh-weighted MEV-split total of 35.3349)
|
||||
#
|
||||
# Pre-slice the cascade applied 0.14116 to ALL 252.5159 kWh →
|
||||
# 35.6457 kg/yr → +0.31 over ws. With the MEV-aware split the
|
||||
# cascade lands on 35.3349 kg/yr.
|
||||
pages = _summary_pdf_to_textract_style_pages(_SUMMARY_000565_PDF)
|
||||
site_notes = ElmhurstSiteNotesExtractor(pages).extract()
|
||||
epc = EpcPropertyDataMapper.from_elmhurst_site_notes(site_notes)
|
||||
from domain.sap10_calculator.rdsap.cert_to_inputs import cert_to_inputs
|
||||
|
||||
# Act
|
||||
inputs = cert_to_inputs(epc)
|
||||
|
||||
# Assert — pumps_fans CO2 factor equals the worksheet line (267)
|
||||
# effective rate (within 1e-4 / kWh).
|
||||
expected_factor = 35.3349 / 252.5159
|
||||
actual = inputs.pumps_fans_co2_factor_kg_per_kwh
|
||||
assert actual is not None
|
||||
assert abs(actual - expected_factor) <= 1e-4, (
|
||||
f"cascade pumps_fans_co2_factor={actual:.6f}; "
|
||||
f"ws (267) effective={expected_factor:.6f}; Δ={actual - expected_factor:+.6f} "
|
||||
f"(expected MEV-split kWh-weighted blend post-S0380.105)"
|
||||
)
|
||||
|
||||
|
||||
def test_summary_000565_main_1_ashp_sap_code_224_routes_to_main_heating_category_4_per_sap_table_4a() -> None:
|
||||
# Arrange — SAP 10.2 Table 4a (PDF p.165) "Main heating systems":
|
||||
# the category column lists "Heat pumps" as category 4. Codes in
|
||||
|
|
|
|||
|
|
@ -1872,6 +1872,61 @@ def _main_heating_primary_factor(
|
|||
return high_frac * high_factor + (1.0 - high_frac) * low_factor
|
||||
|
||||
|
||||
def _pumps_fans_co2_factor_kg_per_kwh(
|
||||
*,
|
||||
tariff: Tariff,
|
||||
mev_kwh_per_yr: float,
|
||||
total_pumps_fans_kwh_per_yr: float,
|
||||
monthly_kwh: tuple[float, ...],
|
||||
) -> Optional[float]:
|
||||
"""SAP 10.2 Table 12a Grid 2 (PDF p.191) + Table 12d (PDF p.194) —
|
||||
CO2-side mirror of `_pumps_fans_fuel_cost_gbp_per_kwh` (Slice
|
||||
S0380.103).
|
||||
|
||||
MEV/MVHR-fan electricity bills at the `FANS_FOR_MECH_VENT` high-rate
|
||||
fraction (10-hour: 0.58; 7-hour: 0.71) on dual-rate tariffs, while
|
||||
the remaining pumps_fans portion (central-heating circulation
|
||||
pumps, flue fans, solar HW pumps, electric keep-hot) bills at
|
||||
`ALL_OTHER_USES` (10-hour: 0.80; 7-hour: 0.90). The two Grid 2
|
||||
categories blend Table 12d high/low-rate codes at different ratios
|
||||
→ two distinct effective CO2 factors. Returns the kWh-weighted
|
||||
blend across the two streams.
|
||||
|
||||
Returns the existing `_other_use_co2_factor_kg_per_kwh(
|
||||
ALL_OTHER_USES, ...)` rate on STANDARD tariff (no Grid 2 split
|
||||
applies — Table 12d code 30 monthly cascade only), and when no MEV
|
||||
is lodged (no split needed).
|
||||
|
||||
Worksheet pin for cert 000565 (TEN_HOUR + MEV 127.5159 kWh + 125
|
||||
kWh other pumps/fans):
|
||||
F_FANS = 0.58 × F_code34 + 0.42 × F_code33 = 0.13872 kg/kWh
|
||||
F_OTHER = 0.80 × F_code34 + 0.20 × F_code33 = 0.14116 kg/kWh
|
||||
F_eff = (127.5159 × 0.13872 + 125.0 × 0.14116) / 252.5159
|
||||
= 0.13993 kg/kWh
|
||||
Worksheet line (267): 252.5159 × 0.13993 = 35.3349 kg/yr; pre-slice
|
||||
the cascade applied 0.14116 to all pumps_fans → 35.6457 → +0.31
|
||||
over ws.
|
||||
"""
|
||||
other_factor = _other_use_co2_factor_kg_per_kwh(
|
||||
OtherUse.ALL_OTHER_USES, tariff, monthly_kwh,
|
||||
)
|
||||
if (
|
||||
tariff is Tariff.STANDARD
|
||||
or mev_kwh_per_yr <= 0.0
|
||||
or total_pumps_fans_kwh_per_yr <= 0.0
|
||||
):
|
||||
return other_factor
|
||||
fans_factor = _other_use_co2_factor_kg_per_kwh(
|
||||
OtherUse.FANS_FOR_MECH_VENT, tariff, monthly_kwh,
|
||||
)
|
||||
if fans_factor is None or other_factor is None:
|
||||
return other_factor
|
||||
non_mev_kwh = max(0.0, total_pumps_fans_kwh_per_yr - mev_kwh_per_yr)
|
||||
return (
|
||||
mev_kwh_per_yr * fans_factor + non_mev_kwh * other_factor
|
||||
) / total_pumps_fans_kwh_per_yr
|
||||
|
||||
|
||||
def _other_use_co2_factor_kg_per_kwh(
|
||||
other_use: OtherUse,
|
||||
tariff: Tariff,
|
||||
|
|
@ -4499,9 +4554,17 @@ def cert_to_inputs(
|
|||
# low Table 12d codes per the Grid 2 fraction. STANDARD tariff
|
||||
# passes through to single-code-30 monthly. Mirrors the main-
|
||||
# heating Grid 1 split landed in S0380.65.
|
||||
pumps_fans_co2_factor_kg_per_kwh=_other_use_co2_factor_kg_per_kwh(
|
||||
OtherUse.ALL_OTHER_USES, _rdsap_tariff(epc),
|
||||
_days_in_month_proportioned(pumps_fans_kwh, _DAYS_IN_MONTH),
|
||||
#
|
||||
# MEV/MVHR-fan kWh route through `FANS_FOR_MECH_VENT` (lower
|
||||
# high-rate fraction → lower CO2 factor on a high-carbon high-
|
||||
# rate code) instead of `ALL_OTHER_USES`. Slice S0380.105
|
||||
# weights the two streams by their lodged kWh portions —
|
||||
# mirror of the cost-side S0380.103 split.
|
||||
pumps_fans_co2_factor_kg_per_kwh=_pumps_fans_co2_factor_kg_per_kwh(
|
||||
tariff=_rdsap_tariff(epc),
|
||||
mev_kwh_per_yr=mev_kwh_for_cost_split,
|
||||
total_pumps_fans_kwh_per_yr=pumps_fans_kwh,
|
||||
monthly_kwh=_days_in_month_proportioned(pumps_fans_kwh, _DAYS_IN_MONTH),
|
||||
),
|
||||
lighting_co2_factor_kg_per_kwh=_other_use_co2_factor_kg_per_kwh(
|
||||
OtherUse.ALL_OTHER_USES, _rdsap_tariff(epc), lighting_monthly_kwh,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue