Slice S0380.56: cascade WHC 914 routing extended to fuel cost / CO2 / PE

Slice S0380.55 routed water-heating EFFICIENCY to Main 2 for WHC
914. This slice extends the routing to water-heating FUEL — the
cascade's CO2 factor, PE factor, and Table 32 fuel-cost lookups
were still pinned to Main 1's fuel code via the legacy
`epc.sap_heating.water_heating_fuel or main_fuel` pattern.

For cert 000565 (WHC 914 + HP Main 1 + gas combi Main 2):
- `epc.sap_heating.water_heating_fuel` is None (Elmhurst §15 doesn't
  lodge a separate water-heating fuel type)
- `_main_fuel_code(Main 1)` is None (HP, no fuel_type lodged in §14.0
  — Elmhurst convention for heat pump certs)
- Old pattern: water_fuel = None → `co2_factor_kg_per_kwh(None) = 0`
  → HW CO2 silently 0 (off by ~833 kg/yr vs gas combi truth)

New helper `_water_heating_fuel_code(epc)` mirrors `_water_heating_
main(epc)`: prefers the explicitly-lodged `water_heating_fuel`,
otherwise falls back to `_main_fuel_code` of whichever main system
services DHW per WHC. Wired into 5 cascade sites (CO2 / PE / cost
× hot-water + per-end-use CO2 + per-end-use PE factors).

Cert 000565 cascade impact:
- hot_water_co2 (kg/yr): factor=0 → 0.21 (gas) — now correctly
  attributes ~833 kg HW CO2 to gas combustion
- hot_water_primary_factor: 0 → gas Table 12e value
- hot_water_high_rate_gbp_per_kwh: previously fell through to Main 1
  fuel-code which is also None → gas tariff sentinel; now derives
  explicitly from Main 2's mains-gas fuel (Table 32 code 26)
- co2_kg_per_yr pin: +287 → +734 (got "worse" because HW gas CO2 is
  now correctly counted; remaining surplus is from an INDEPENDENT
  Main 1 fuel-inference bug — `_main_fuel_code` returns None for HP
  Main 1 because Elmhurst §14.0 leaves `Fuel Type` empty for heat
  pumps, so the cost/CO2 cascade defaults Main 1 to the gas tariff)

The Main-1 HP fuel-inference bug is the next slice. For single-main
non-HP certs the helper resolves identically to the prior pattern
(water_heating_fuel explicit, or Main 1 fuel) — no behavioural
change for the existing fixture corpus.

Pyright net-zero (34 / 34).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Khalim Conn-Kowlessar 2026-05-28 22:45:00 +00:00
parent 1eff5cf4a1
commit e0bca4c3cd

View file

@ -588,6 +588,23 @@ def _water_heating_main(
return details[0]
def _water_heating_fuel_code(epc: EpcPropertyData) -> Optional[int]:
"""Fuel code for water heating per the cert's WHC routing. Prefers
an explicitly-lodged `water_heating_fuel`; otherwise falls back to
the fuel of whichever main system services DHW (Main 2 for WHC
914, Main 1 otherwise see `_water_heating_main`).
Replaces the pattern `epc.sap_heating.water_heating_fuel or
main_fuel` that defaulted to Main 1 unconditionally; for cert
000565 the explicit fuel is None and Main 1 is a heat pump with
no fuel_type lodged, so the old fallback resolved to None and CO2/
PE/cost lookups returned defaults instead of the gas-combi values.
"""
if epc.sap_heating.water_heating_fuel:
return epc.sap_heating.water_heating_fuel
return _main_fuel_code(_water_heating_main(epc))
def _main_heating_efficiency(epc: EpcPropertyData) -> float:
"""SAP 10.2 (206) main system 1 efficiency as a 0..1 fraction.
@ -1605,7 +1622,7 @@ def environmental_section_from_cert(
main = _first_main_heating(epc)
main_fuel = _main_fuel_code(main)
main_factor = co2_factor_kg_per_kwh(main_fuel)
water_fuel = epc.sap_heating.water_heating_fuel or main_fuel
water_fuel = _water_heating_fuel_code(epc)
water_factor = co2_factor_kg_per_kwh(water_fuel)
# Compute per-end-use CO2. For electricity end-uses, monthly Table 12d
@ -1712,7 +1729,7 @@ def primary_energy_section_from_cert(
main = _first_main_heating(epc)
main_fuel = _main_fuel_code(main)
main_pe = primary_energy_factor(main_fuel)
water_fuel = epc.sap_heating.water_heating_fuel or main_fuel
water_fuel = _water_heating_fuel_code(epc)
water_pe = primary_energy_factor(water_fuel)
main_1 = er.main_1_fuel_kwh_per_yr * main_pe
main_2 = er.main_2_fuel_kwh_per_yr * main_pe
@ -2765,7 +2782,7 @@ def _fuel_cost(
return _ZERO_FUEL_COST_FOR_OFF_PEAK
main_fuel_code = _main_fuel_code(main)
water_heating_fuel_code = epc.sap_heating.water_heating_fuel
water_heating_fuel_code = _water_heating_fuel_code(epc)
# Std electricity for all single-row end-uses (pumps/fans, lighting,
# cooling). Table 32 code 30.
@ -2776,7 +2793,7 @@ def _fuel_cost(
table_32_unit_price_p_per_kwh(main_fuel_code) * _PENCE_TO_GBP
)
water_high_rate_gbp_per_kwh = (
table_32_unit_price_p_per_kwh(water_heating_fuel_code or main_fuel_code)
table_32_unit_price_p_per_kwh(water_heating_fuel_code)
* _PENCE_TO_GBP
)
# Secondary fuel cost: route through the cert's `secondary_fuel_type`
@ -3286,7 +3303,7 @@ def cert_to_inputs(
_STANDARD_ELECTRICITY_FUEL_CODE,
),
hot_water_co2_factor_kg_per_kwh=co2_factor_kg_per_kwh(
epc.sap_heating.water_heating_fuel or main_fuel
_water_heating_fuel_code(epc)
),
pumps_fans_co2_factor_kg_per_kwh=_effective_monthly_co2_factor(
_days_in_month_proportioned(pumps_fans_kwh, _DAYS_IN_MONTH),
@ -3346,7 +3363,7 @@ def cert_to_inputs(
),
space_heating_primary_factor=primary_energy_factor(main_fuel),
hot_water_primary_factor=primary_energy_factor(
epc.sap_heating.water_heating_fuel or main_fuel
_water_heating_fuel_code(epc)
),
other_primary_factor=primary_energy_factor(30), # standard electricity
# SAP 10.2 Table 12e (p.195) per-end-use effective PE factors. Same