Bill water-only heat-network DHW identically whatever plant is behind it 🟩

950 boilers / 951 CHP / 952 community heat pump differ only in plant
efficiency, which SAP 10.2 §4.3 puts in the Table 12 heat price — so the
dwelling's delivered-heat demand must not vary with it. Passes on the
previous commit's expression; verified RED against the prior impl, where
951 leaked 0.80/0.75 and 952 under-billed its DHW fuel by 3.15×.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Daniel Roth 2026-07-15 10:25:59 +00:00
parent 353ebe5556
commit 5f3e9f893f

View file

@ -1510,6 +1510,57 @@ def test_water_only_heat_network_dhw_prices_heat_without_plant_efficiency() -> N
assert abs(hw_fuel / wh_result.output_kwh_per_yr - 1.41 * 1.05) <= 1e-6
def test_water_only_heat_network_dhw_fuel_independent_of_plant_type() -> None:
# Arrange — the three water-only heat-network codes differ ONLY in the
# plant behind the network: 950 boilers (Table 4a eff 0.80), 951 CHP
# (0.75), 952 community heat pump (3.00). Because SAP 10.2 §4.3 (p.24)
# puts that efficiency in the Table 12 price of delivered heat, the
# DWELLING's delivered-heat demand cannot depend on it: worksheet (310)
# is "(64) × (305a) × (306)" whatever the plant is. C6 (p.54) likewise
# gives one rule for a DHW-only network, and the Table 4c(3) (p.169)
# "DHW-only system" rows make no 950/951/952 distinction.
#
# So all three must bill identical HW fuel. The plant efficiency still
# matters — but at (342)/(373)/(473), the network's CO2/PE factors (C4,
# p.53) — never as a dwelling-side divisor.
#
# This is the sharpest statement of the double-count fix: under the old
# `water_eff = plant_eff / DLF` the three diverged by their plant
# efficiencies, and 952 under-billed its DHW fuel by ~3× (0.47 × q_useful
# against the spec's 1.4805 × q_useful).
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,
)
def hw_fuel_for(water_heating_code: int) -> float:
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=water_heating_code,
),
)
return cert_to_inputs(epc).hot_water_kwh_per_yr
# Act — boilers / CHP / community heat pump behind the same network.
boilers = hw_fuel_for(950)
chp = hw_fuel_for(951)
heat_pump = hw_fuel_for(952)
# Assert — the plant behind the network never reaches the dwelling.
assert chp == pytest.approx(boilers, abs=1e-6)
assert heat_pump == pytest.approx(boilers, abs=1e-6)
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