Merge pull request #1644 from Hestia-Homes/fix/elmhurst-community-dhw-rate

Bill community-heating DHW at the network fuel rate (not lodged mains-gas)
This commit is contained in:
KhalimCK 2026-07-17 14:30:25 +01:00 committed by GitHub
commit bd2253ee61
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 60 additions and 3 deletions

View file

@ -7698,9 +7698,16 @@ def _fuel_cost(
if main_2_fuel_code is not None
else main_1_high_rate_gbp_per_kwh
)
# When DHW is supplied by a community heat network (WHC 901/902/914 on a
# heat-network main), the network heat — space AND water — bills at the
# main's Table-12 heat-network rate, NOT the cert-lodged HW fuel (which
# Elmhurst defaults to "Mains gas"). Mirror the .173 inherit already applied
# on the legacy/off-peak HW-rate paths (`_hot_water_fuel_cost_gbp_per_kwh`)
# into this §10a STANDARD-tariff path, which is the one that drives (255)/SAP.
water_high_rate_gbp_per_kwh = (
table_32_unit_price_p_per_kwh(water_heating_fuel_code)
* _PENCE_TO_GBP
_fuel_cost_gbp_per_kwh(main, prices)
if _is_community_heating_hw_from_main(epc)
else 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`
# when lodged (e.g. mains-gas fire SAP code 605 → fuel 26 → Table 32

View file

@ -45,7 +45,11 @@ from domain.sap10_ml.tests._fixtures import (
make_sap_heating,
make_window,
)
from domain.sap10_calculator.calculator import Sap10Calculator, SapResult
from domain.sap10_calculator.calculator import (
Sap10Calculator,
SapResult,
calculate_sap_from_inputs,
)
from domain.sap10_calculator.exceptions import (
MissingMainFuelType,
UnmappedSapCode,
@ -69,6 +73,8 @@ from domain.sap10_calculator.rdsap.cert_to_inputs import (
_TABLE_4G_DEFAULT_MEV_SFP_W_PER_L_PER_S, # pyright: ignore[reportPrivateUsage]
dimensions_from_cert,
fuel_cost_section_from_cert,
_fuel_cost_gbp_per_kwh, # pyright: ignore[reportPrivateUsage]
RDSAP_10_TABLE_32_PRICES, # pyright: ignore[reportPrivateUsage]
_table_12_factor_fuel_code, # pyright: ignore[reportPrivateUsage]
_table_12a_system_for_main, # pyright: ignore[reportPrivateUsage]
_is_electric_main, # pyright: ignore[reportPrivateUsage]
@ -252,6 +258,50 @@ def test_thermal_mass_wall_code_8_is_park_home_only_when_property_type_says_so()
assert abs(tmp_system_built - 250.0) <= 1e-9
def test_community_heating_dhw_bills_at_network_rate_not_lodged_fuel() -> None:
# Arrange — a community heat-network main (Table 4a code 301, cat 6) whose
# DHW is heated by the network (WHC 901, "from the main system") but whose
# lodged HW fuel is the Elmhurst "Mains gas" (26) placeholder. SAP 10.2 §C3
# bills network heat — space AND water — at the network's own Table-12 rate,
# so the DHW must inherit the main's heat-network rate, not the lodged fuel's.
main = MainHeatingDetail(
has_fghrs=False,
main_fuel_type=20, # mains gas (community)
heat_emitter_type=1,
emitter_temperature=1,
main_heating_control=2306,
main_heating_category=6,
sap_main_heating_code=301,
)
epc = make_minimal_sap10_epc(
total_floor_area_m2=_TYPICAL_TFA_M2,
habitable_rooms_count=4,
country_code="ENG",
sap_building_parts=[make_building_part(construction_age_band="E")],
sap_heating=make_sap_heating(
main_heating_details=[main],
water_heating_code=901, # DHW from the main (heat network)
water_heating_fuel=26, # Elmhurst "Mains gas" placeholder
),
)
# Act
section = fuel_cost_section_from_cert(epc)
# Assert — total DHW cost is priced at the main's heat-network rate, NOT the
# lodged mains-gas rate. Network fuel is single-rate, so (245)+(246)+(247) =
# HW kWh x network rate.
assert section is not None
hw_kwh = calculate_sap_from_inputs(cert_to_inputs(epc)).hot_water_kwh_per_yr
total_water_cost_gbp = (
section.water_high_rate_cost_gbp
+ section.water_low_rate_cost_gbp
+ section.water_other_fuel_cost_gbp
)
network_rate = _fuel_cost_gbp_per_kwh(main, RDSAP_10_TABLE_32_PRICES)
assert total_water_cost_gbp == pytest.approx(hw_kwh * network_rate, rel=1e-6)
def test_heat_network_main_applies_table12c_dlf_to_main_heating_efficiency() -> None:
# Arrange — heat-network main heating (Table 4a code 301 = community
# heating with CHP/boilers; main_heating_category=6). Cert age band