From e0bca4c3cd9f072f8396099e47094cf4d294bb84 Mon Sep 17 00:00:00 2001 From: Khalim Conn-Kowlessar Date: Thu, 28 May 2026 22:45:00 +0000 Subject: [PATCH] Slice S0380.56: cascade WHC 914 routing extended to fuel cost / CO2 / PE MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .../sap10_calculator/rdsap/cert_to_inputs.py | 29 +++++++++++++++---- 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/domain/sap10_calculator/rdsap/cert_to_inputs.py b/domain/sap10_calculator/rdsap/cert_to_inputs.py index dd32a9d3..a873e5f9 100644 --- a/domain/sap10_calculator/rdsap/cert_to_inputs.py +++ b/domain/sap10_calculator/rdsap/cert_to_inputs.py @@ -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