mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-08 11:17:27 +00:00
Slice S0380.173: Community heating HW path routes through main fuel
Closes CH1 (boilers) + CH3 (HP) HW CO2 / PE residuals by routing
the HW cost / CO2 / PE factor lookups through the heat-network main
when WHC ∈ {901, 902, 914} ("HW from main heating system"). Pre-
slice the cascade honoured Elmhurst Summary §15.0's
`water_heating_fuel_type = "Mains gas"` placeholder on community-
heated certs, mis-routing HW through Table 12 code 1 (mains gas,
3.48 p/kWh / 0.21 CO2 / 1.13 PE) instead of the heat-network code
(4.24 p/kWh + Table 12 code 41 / 51 / 53 / 54 with Table 4a heat-
source-eff scaling per S0380.172).
Per SAP 10.2 §C1 + RdSAP 10 §C (PDF p.49 + p.58) the HW heat
delivered by a heat-network main is supplied through the same
network as SH: spec block 10b (342a)/(342b) computes HW cost as
`(310a) × CHP_price + (310b) × boiler_price`, mirroring SH's
(340a)/(340b) split. Block 12b (365)/(366) and 13a (465)/(466)
likewise apply the heat-source-eff division on HW.
Three layers wired:
1. New `_is_community_heating_hw_from_main(epc)` predicate. Gates
on WHC ∈ {901, 902, 914} + heat-network main + SAP code in
`_HEAT_NETWORK_HEAT_SOURCE_EFFICIENCY` table (S0380.172 — only
301 boilers + 304 HP). SAP 302 (CHP+boilers) is excluded
because the 35%/65% split needs the displaced-electricity
credit cascade per spec block 13b (464)/(466) on BOTH SH and HW
paths — both converge in a single follow-up slice.
2. `_hot_water_fuel_cost_gbp_per_kwh` gains a keyword-only
`inherit_main_for_community_heating: bool = False` parameter.
When True, returns `_fuel_cost_gbp_per_kwh(main, prices)` —
same helper that already applies the S0380.171 CHP blend +
heat-network rate. The orchestrator passes
`inherit_main_for_community_heating=_is_community_heating_hw_
from_main(epc)` at the cost-rate construction site.
3. `_hot_water_co2_factor_kg_per_kwh` and `_hot_water_primary_
factor` get top-level branches: when the predicate fires, return
`Table_12_factor × _heat_network_heat_source_efficiency_scaling
(main)` — same scaled-factor return as the SH path in S0380.172.
Closures (heating-systems corpus block 11b):
CH1 (Boilers/Gas) ΔPE −967 → −9 (essentially closed)
CH1 ΔCO2 −126 → +52 (shifted across worksheet)
CH3 (HP/Elec) ΔPE +1749 → −387 (~78% closure)
CH3 ΔCO2 +473 → −86 (~82% closure)
Cost / SAP signs flip on CH1 / CH3 (was −£14 / +0.59 SAP, now
+£12 / −0.53 SAP) — HW cost now matches the worksheet's (342) line
exactly, exposing a +£12 lighting / standing overage that was
previously masked by the HW under-charge. Per [[feedback-software-
no-special-handling]] the pre-slice near-zero on CH1 / CH3 cost was
an offsetting-bugs artifact; the spec-correct fix surfaces the real
lighting / standing gap as the next forcing function.
CH2 / CH4 / CH6 (SAP 302) unchanged from S0380.171 / S0380.172 pins
— gated out per the heat-source-eff-table membership check.
Test baseline at HEAD: 926 pass + 1 skipped (was 926 + 1 at
predecessor 36d4bf87). Pyright net-zero on affected files
(cert_to_inputs.py, test_heating_systems_corpus.py): 32 → 32.
Per [[feedback-spec-citation-in-commits]] the rule cites SAP 10.2
§C1 verbatim ("heat from CHP + back-up boilers, via a heat main")
and RdSAP 10 §C defaults (PDF p.58).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
a5eda92a90
commit
c17330b319
2 changed files with 97 additions and 3 deletions
|
|
@ -562,9 +562,38 @@ _EXPECTATIONS: tuple[_CorpusExpectation, ...] = (
|
|||
# the WHC=901 HW path (cascade reads cert-lodged "Mains gas" as
|
||||
# HW fuel; should fall through to main fuel for community heating)
|
||||
# + the Elmhurst 0.8523 multiplier on heat-network energy column.
|
||||
_CorpusExpectation(variant='community heating 1', block='11b', expected_sap_resid=+0.5915, expected_cost_resid_gbp=-13.6289, expected_co2_resid_kg=-126.4571, expected_pe_resid_kwh=-967.3648),
|
||||
#
|
||||
# Slice S0380.173 routed CH1 + CH3 HW cost / CO2 / PE through the
|
||||
# main heat-network fuel + Table 4a heat-source-eff scaling via a
|
||||
# new `_is_community_heating_hw_from_main(epc)` predicate (WHC ∈
|
||||
# {901, 902, 914} + heat-network main + SAP code in
|
||||
# `_HEAT_NETWORK_HEAT_SOURCE_EFFICIENCY` table from S0380.172).
|
||||
# Pre-slice the cascade honoured Elmhurst's §15.0 placeholder
|
||||
# `water_heating_fuel_type = "Mains gas"` for community-heated
|
||||
# certs, mis-routing HW through the Mains-gas Table 12 code
|
||||
# (3.48 p/kWh / 0.21 CO2 / 1.13 PE) instead of the heat-network
|
||||
# code (4.24 p/kWh + scaled factors). Closures:
|
||||
#
|
||||
# CH1 (Boilers/Gas) ΔPE −967 → −9 (essentially closed)
|
||||
# CH1 ΔCO2 −126 → +52 (shift)
|
||||
# CH3 (HP/Elec) ΔPE +1749 → −387 (~78% closed)
|
||||
# CH3 ΔCO2 +473 → −86 (~82% closed)
|
||||
#
|
||||
# Cost / SAP signs flip on CH1 / CH3 (was −£14 / +0.59 SAP, now
|
||||
# +£12 / −0.53 SAP) — HW cost now matches the worksheet exactly,
|
||||
# exposing a +£12 lighting / standing overage that was previously
|
||||
# masked by the HW under-charge. The exposed lighting / standing
|
||||
# gap is the next closure front (likely the £120 heat-network
|
||||
# standing charge being applied to lighting kWh instead).
|
||||
#
|
||||
# SAP 302 (CHP+boilers) gated out per `_is_community_heating_hw_
|
||||
# from_main`'s `_HEAT_NETWORK_HEAT_SOURCE_EFFICIENCY` check — the
|
||||
# 35%/65% split + displaced-electricity credit must converge on
|
||||
# both SH and HW in a single follow-up slice. CH2 / CH4 / CH6
|
||||
# residuals unchanged from S0380.172 / S0380.171 pins.
|
||||
_CorpusExpectation(variant='community heating 1', block='11b', expected_sap_resid=-0.5273, expected_cost_resid_gbp=+12.1495, expected_co2_resid_kg=+51.6176, expected_pe_resid_kwh=-9.1529),
|
||||
_CorpusExpectation(variant='community heating 2', block='11b', expected_sap_resid=-0.0076, expected_cost_resid_gbp=+0.1744, expected_co2_resid_kg=-1430.3212, expected_pe_resid_kwh=+1506.0355),
|
||||
_CorpusExpectation(variant='community heating 3', block='11b', expected_sap_resid=+0.5915, expected_cost_resid_gbp=-13.6289, expected_co2_resid_kg=+472.5996, expected_pe_resid_kwh=+1748.7395),
|
||||
_CorpusExpectation(variant='community heating 3', block='11b', expected_sap_resid=-0.5273, expected_cost_resid_gbp=+12.1495, expected_co2_resid_kg=-85.9334, expected_pe_resid_kwh=-387.0272),
|
||||
_CorpusExpectation(variant='community heating 4', block='11b', expected_sap_resid=-0.0076, expected_cost_resid_gbp=+0.1744, expected_co2_resid_kg=-4397.0794, expected_pe_resid_kwh=+494.6090),
|
||||
_CorpusExpectation(variant='community heating 6', block='11b', expected_sap_resid=-8.0295, expected_cost_resid_gbp=+185.0120, expected_co2_resid_kg=-2934.9021, expected_pe_resid_kwh=+7864.5950),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1298,6 +1298,36 @@ def _water_heating_fuel_code(epc: EpcPropertyData) -> Optional[int]:
|
|||
return _main_fuel_code(_water_heating_main(epc))
|
||||
|
||||
|
||||
def _is_community_heating_hw_from_main(epc: EpcPropertyData) -> bool:
|
||||
"""True iff the cert's WHC routes HW from the main heating system
|
||||
(codes 901 / 902 / 914) AND the main is a single-source heat
|
||||
network with a registered heat-source efficiency
|
||||
(`_HEAT_NETWORK_HEAT_SOURCE_EFFICIENCY` — currently SAP code 301
|
||||
boilers and 304 HP).
|
||||
|
||||
Elmhurst Summary §15.0 lodges `water_heating_fuel_type = "Mains gas"`
|
||||
on community-heating certs regardless of the actual heat-network
|
||||
source — without this guard the HW cost / CO2 / PE bills via the
|
||||
Mains-gas Table 12 code (3.48 p/kWh / 0.21 / 1.13) instead of the
|
||||
heat-network code (4.24 p/kWh / Table 12 code 41 / 51).
|
||||
|
||||
SAP code 302 (CHP+boilers) is excluded because the 35%/65% split
|
||||
requires the displaced-electricity credit line per spec block 13b
|
||||
(464)/(466) on the HW side — same constraint as `_main_heating_
|
||||
co2_factor_kg_per_kwh` (S0380.172). Routing HW through main for
|
||||
SAP 302 without the credit cascade would regress CO2 / PE; both
|
||||
the SH and HW paths converge in a single follow-up slice that
|
||||
wires the CHP credit + boiler-side factor split.
|
||||
"""
|
||||
if epc.sap_heating.water_heating_code not in _WATER_INHERIT_FROM_MAIN_CODES:
|
||||
return False
|
||||
main = _water_heating_main(epc)
|
||||
if not _is_heat_network_main(main):
|
||||
return False
|
||||
code = main.sap_main_heating_code if main is not None else None
|
||||
return isinstance(code, int) and code in _HEAT_NETWORK_HEAT_SOURCE_EFFICIENCY
|
||||
|
||||
|
||||
def _main_heating_efficiency(epc: EpcPropertyData) -> float:
|
||||
"""SAP 10.2 (206) main system 1 efficiency as a 0..1 fraction.
|
||||
|
||||
|
|
@ -1793,6 +1823,8 @@ def _hot_water_fuel_cost_gbp_per_kwh(
|
|||
main: Optional[MainHeatingDetail],
|
||||
tariff: Tariff,
|
||||
prices: PriceTable,
|
||||
*,
|
||||
inherit_main_for_community_heating: bool = False,
|
||||
) -> float:
|
||||
"""Hot water bills at the *water-heating* fuel's rate. When the
|
||||
water-heating fuel is electric AND tariff is off-peak, bill at the
|
||||
|
|
@ -1801,7 +1833,17 @@ def _hot_water_fuel_cost_gbp_per_kwh(
|
|||
not consulted — those fuels are single-rate per Table 32. For
|
||||
cert 000565 HW routes to gas combi via WHC 914 → tariff branch
|
||||
not taken. TODO: Table 12a Grid 1 WH high-rate-fraction split for
|
||||
electric WH on off-peak (currently uses 100% low rate)."""
|
||||
electric WH on off-peak (currently uses 100% low rate).
|
||||
|
||||
`inherit_main_for_community_heating`: per S0380.173, when WHC
|
||||
∈ {901, 902, 914} AND main is a heat network, ignore the cert-
|
||||
lodged HW fuel (which Elmhurst defaults to "Mains gas") and route
|
||||
HW cost through `_fuel_cost_gbp_per_kwh(main, prices)` — same
|
||||
helper that applies the .171 CHP heat-fraction blend for SAP 302
|
||||
+ heat-network rate for code 41 / 51 / 53 / 54.
|
||||
"""
|
||||
if inherit_main_for_community_heating:
|
||||
return _fuel_cost_gbp_per_kwh(main, prices)
|
||||
water_electric = _is_electric_water(water_heating_fuel)
|
||||
if water_electric and tariff is not Tariff.STANDARD:
|
||||
return _off_peak_low_rate_gbp_per_kwh(tariff)
|
||||
|
|
@ -2782,6 +2824,17 @@ def _hot_water_co2_factor_kg_per_kwh(
|
|||
monthly HW fuel kWh — the calculator uses an annual-flat HW
|
||||
efficiency so the SHAPE of fuel monthly is identical to demand
|
||||
monthly, and `_effective_monthly_co2_factor` is shape-only)."""
|
||||
# Community heating + WHC ∈ {901, 902, 914}: HW heat is delivered
|
||||
# through the heat-network main, so HW CO2 must read the same
|
||||
# Table 12 heat-network code factor as SH, scaled by 1/heat_source_
|
||||
# eff per spec block 12b (363)/(367). Cert-lodged HW fuel "Mains
|
||||
# gas" is an Elmhurst placeholder that mis-routes the lookup.
|
||||
if _is_community_heating_hw_from_main(epc):
|
||||
main = _water_heating_main(epc)
|
||||
return (
|
||||
_co2_factor_kg_per_kwh(main)
|
||||
* _heat_network_heat_source_efficiency_scaling(main)
|
||||
)
|
||||
fuel = _water_heating_fuel_code(epc)
|
||||
if fuel is None:
|
||||
return _DEFAULT_CO2_KG_PER_KWH
|
||||
|
|
@ -2819,6 +2872,16 @@ def _hot_water_primary_factor(
|
|||
exactly to match the Elmhurst worksheet's (278) annual factor.
|
||||
The 41-variant heating-systems corpus closes its HW PE residual
|
||||
+25/+48 → 0 with this gate."""
|
||||
# Mirror of `_hot_water_co2_factor_kg_per_kwh` community-heating
|
||||
# branch (S0380.173): WHC ∈ {901, 902, 914} on a heat-network main
|
||||
# routes HW PE through the same Table 12 heat-network code as SH,
|
||||
# scaled by 1/heat_source_eff per spec block 13a (463)/(467).
|
||||
if _is_community_heating_hw_from_main(epc):
|
||||
main = _water_heating_main(epc)
|
||||
return (
|
||||
primary_energy_factor(_main_fuel_code(main))
|
||||
* _heat_network_heat_source_efficiency_scaling(main)
|
||||
)
|
||||
fuel = _water_heating_fuel_code(epc)
|
||||
if fuel is None:
|
||||
return _DEFAULT_PEF
|
||||
|
|
@ -5818,11 +5881,13 @@ def cert_to_inputs(
|
|||
hw_co2_factor = _hw_co2_factor
|
||||
hw_pe_factor = _hw_pe_factor
|
||||
else:
|
||||
_community_hw_inherit = _is_community_heating_hw_from_main(epc)
|
||||
hw_cost_rate = _hot_water_fuel_cost_gbp_per_kwh(
|
||||
_water_heating_fuel_code(epc),
|
||||
_water_heating_main(epc),
|
||||
_rdsap_tariff(epc),
|
||||
prices,
|
||||
inherit_main_for_community_heating=_community_hw_inherit,
|
||||
)
|
||||
hw_co2_factor = _hot_water_co2_factor_kg_per_kwh(
|
||||
epc, hw_monthly_kwh_for_factors, _rdsap_tariff(epc),
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue