mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-30 13:10:47 +00:00
fix(hot-water): apply Table 12c distribution loss to HW-only heat networks (whc 950/951/952)
The heat-network HW distribution-loss override fired only when the MAIN was
a heat network AND whc inherited from main ({901,902,914}). Water-heating-only
heat networks (SAP 10.2 Table 4a HW codes 950 boilers / 951 CHP / 952 heat
pump) were missed entirely: their Table 4a plant efficiency applied with NO
distribution loss, so the HW fuel was under-counted by the Table 12c DLF
(1.33-1.48x) → under-cost → over-rate.
RdSAP 10 §10 (spec p.36): a water-heating-only heat network is calculated 'for
plant efficiency, distribution loss and pumping energy - see Table 12c'. Added
a whc-gated branch (independent of the main) applying water_eff = plant_eff /
DLF — the per-kWh-generated cost model (q_generated = q_useful x DLF). Fires on
the WHC alone so a HW-only heat network with a non-network main (cert 9093, whc
950 + warm-air main 502) is covered.
The 3 corpus whc=950 certs all improve in |err|: 2153 +2.62->-0.48 (now within
0.5), 7220 +1.27->-0.97, 9093 +6.04->+3.60 (residual is its warm-air main, a
separate cause). within-0.5 56.66->56.79%, within-1.0 71.9->72.2%, mean|err|
down; only those 3 certs change. New AAA test pins the DLF scaling fires on the
WHC independent of the main. Goldens + gate green, pyright net-zero.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
a7990edb8c
commit
872bc585f7
2 changed files with 67 additions and 0 deletions
|
|
@ -3092,6 +3092,15 @@ def _pumps_fans_fuel_cost_gbp_per_kwh(
|
|||
# the SAME cascade the main heating uses, including the main_heating_
|
||||
# category fallback (e.g. heat pumps return 2.30 via category 4).
|
||||
_WATER_INHERIT_FROM_MAIN_CODES: Final[frozenset[int]] = frozenset({901, 902, 914})
|
||||
# Hot-water-only heat-network codes — SAP 10.2 Table 4a HW section (PDF
|
||||
# p.167): 950 boilers / 951 CHP / 952 heat pump. The DHW is supplied by a
|
||||
# heat network independent of the space-heating system, so RdSAP 10 §10
|
||||
# (spec p.36 "water heating only ... for plant efficiency, distribution loss
|
||||
# and pumping energy — see Table 12c") requires the Table 12c distribution
|
||||
# loss factor applied on top of the Table 4a plant efficiency. Distinct from
|
||||
# the inherit-from-main codes: the DLF fires on the WHC regardless of whether
|
||||
# the *main* is a heat network (e.g. cert 9093, whc 950 + warm-air main 502).
|
||||
_WATER_HEAT_NETWORK_ONLY_CODES: Final[frozenset[int]] = frozenset({950, 951, 952})
|
||||
# Water-heating code 901 = "From main heating system" — used by the
|
||||
# SAP 10.2 Appendix D §D2.1 (2) Equation D1 gate, which only applies
|
||||
# when "the boiler provides both space and water heating".
|
||||
|
|
@ -6776,6 +6785,14 @@ def cert_to_inputs(
|
|||
# space heating so the delivered HW kWh reflects q_useful × DLF
|
||||
# = q_generated, matching the per-kWh-generated unit price.
|
||||
water_eff = 1.0 / _heat_network_dlf(primary_age)
|
||||
elif epc.sap_heating.water_heating_code in _WATER_HEAT_NETWORK_ONLY_CODES:
|
||||
# HW-only heat network (whc 950/951/952): the Table 4a plant
|
||||
# efficiency is already in `water_eff`; apply the Table 12c
|
||||
# distribution loss on top per RdSAP 10 §10 (spec p.36 "water
|
||||
# heating only ... distribution loss"). q_generated = q_useful ×
|
||||
# DLF, so delivered-per-fuel efficiency = plant_eff / DLF. Fires
|
||||
# on the WHC alone — the HW network is independent of the main.
|
||||
water_eff = water_eff / _heat_network_dlf(primary_age)
|
||||
is_instantaneous = epc.sap_heating.water_heating_code in _INSTANTANEOUS_WATER_CODES
|
||||
# §9a Table 11 secondary fraction — pulled forward of §4 so the
|
||||
# post-§8 Equation D1 cascade can derive Q_space = (98c)m × (204)
|
||||
|
|
|
|||
|
|
@ -459,6 +459,56 @@ def test_heat_network_main_with_hw_from_main_dlf_scales_hot_water_kwh() -> None:
|
|||
assert hn_hw / gas_hw == pytest.approx(1.41 * 0.80 / 1.0, abs=0.02)
|
||||
|
||||
|
||||
def test_hot_water_only_heat_network_whc_950_applies_table12c_dlf() -> None:
|
||||
# Arrange — water_heating_code 950 = "hot-water-only heat network
|
||||
# (boilers)" (SAP 10.2 Table 4a HW section, plant eff 0.80). RdSAP 10
|
||||
# §10 (spec p.36) requires the Table 12c distribution loss applied on
|
||||
# top of the plant efficiency for water-heating-only heat networks, so
|
||||
# the delivered HW fuel = q_useful × DLF / 0.80. The DLF must fire on
|
||||
# the WHC ALONE — independent of the main, which here is an ordinary
|
||||
# gas boiler (NOT a heat network), mirroring cert 9093 (whc 950 + a
|
||||
# warm-air main). Compare against a non-heat-network baseline at the
|
||||
# same 0.80 water efficiency (whc 901 from the same gas main): the 950
|
||||
# HW fuel must exceed it by exactly the DLF (age E → 1.41).
|
||||
part = make_building_part(construction_age_band="E")
|
||||
gas_main = MainHeatingDetail(
|
||||
has_fghrs=False,
|
||||
main_fuel_type=26,
|
||||
heat_emitter_type=1,
|
||||
emitter_temperature=1,
|
||||
main_heating_control=2106,
|
||||
main_heating_category=2,
|
||||
)
|
||||
hw_network_epc = make_minimal_sap10_epc(
|
||||
total_floor_area_m2=_TYPICAL_TFA_M2,
|
||||
habitable_rooms_count=4,
|
||||
country_code="ENG",
|
||||
sap_building_parts=[part],
|
||||
sap_heating=make_sap_heating(
|
||||
main_heating_details=[gas_main],
|
||||
water_heating_code=950, # hot-water-only heat network
|
||||
),
|
||||
)
|
||||
# Baseline: HW from the same gas main (eff 0.80), no heat network → no DLF.
|
||||
baseline_epc = make_minimal_sap10_epc(
|
||||
total_floor_area_m2=_TYPICAL_TFA_M2,
|
||||
habitable_rooms_count=4,
|
||||
country_code="ENG",
|
||||
sap_building_parts=[part],
|
||||
sap_heating=make_sap_heating(
|
||||
main_heating_details=[gas_main],
|
||||
water_heating_code=901,
|
||||
),
|
||||
)
|
||||
|
||||
# Act
|
||||
hw_network: float = cert_to_inputs(hw_network_epc).hot_water_kwh_per_yr
|
||||
baseline: float = cert_to_inputs(baseline_epc).hot_water_kwh_per_yr
|
||||
|
||||
# Assert — the HW-only-heat-network fuel is scaled up by the age-E DLF.
|
||||
assert abs(hw_network / baseline - 1.41) <= 0.02
|
||||
|
||||
|
||||
def test_gas_boiler_main_efficiency_unchanged_by_dlf_override() -> None:
|
||||
# Arrange — regression check: the DLF override only fires for heat-
|
||||
# network main heating. A standard gas boiler (cat=2, code=102) must
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue