test(hp): floor heat-pump water-heating efficiency at 100% (App N3.7) 🟥

SAP 10.2 Appendix N3.7 ("Thermal efficiency for water heating - heat pumps",
PDF p.109): "multiply the thermal efficiency for water heating by the in-use
factor in Table N8; subject to a minimum efficiency of 100%." Our
_heat_pump_apm_efficiencies applies the in-use factor but omits the floor.

Anchored to golden fixture case 56 (PCDB 100061, cert 100110101713): an
oversized HP (PSR 3.107) extends water,3 198.9% -> 128.55%, x 0.60 in-use =
77.13% < 100% -> the accredited Elmhurst worksheet (216) reads 100.0000, we
read 77.13%. In-range PSR keeps 0.60 x 198.9 = 119.34% (above the floor).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Khalim Conn-Kowlessar 2026-06-29 15:33:04 +00:00
parent 235c2ca466
commit 672e6679c8

View file

@ -54,6 +54,7 @@ from domain.sap10_calculator.rdsap.cert_to_inputs import (
_apply_heat_network_hiu_default_store, # pyright: ignore[reportPrivateUsage]
_cylinder_thermostat_present, # pyright: ignore[reportPrivateUsage]
_has_suspended_timber_floor_per_spec, # pyright: ignore[reportPrivateUsage]
_heat_pump_apm_efficiencies, # pyright: ignore[reportPrivateUsage]
_heat_network_code_302_effective_factor, # pyright: ignore[reportPrivateUsage]
_heat_network_community_fuel_code, # pyright: ignore[reportPrivateUsage]
_heat_network_distribution_electricity, # pyright: ignore[reportPrivateUsage]
@ -4841,6 +4842,57 @@ def test_hot_water_from_pcdb_heat_pump_bills_at_app_n_wh_high_rate() -> None:
assert abs(rate_immersion - 0.0750) <= 1e-6
def test_heat_pump_water_efficiency_is_floored_at_100pct_per_app_n3_7() -> None:
# Arrange — SAP 10.2 Appendix N3.7 ("Thermal efficiency for water
# heating heat pumps", PDF p.109): "multiply the thermal efficiency
# (ηwater) for water heating by the in-use factor in Table N8; subject
# to a MINIMUM EFFICIENCY OF 100%." Our `_heat_pump_apm_efficiencies`
# applied the in-use factor but omitted the floor, so an oversized heat
# pump whose PSR-extended ηwater × 0.60 in-use fell below 100% billed
# water heating at that sub-100% efficiency (over-counting HW fuel).
#
# Accredited anchor: golden fixture case 56 (PCDB 100061, the config of
# cert 100110101713). At HLC 107.82 W/K the PSR is 3.107, above the
# record's largest PSR 2.0, so the Appendix N2 extension takes ηwater,3
# from 198.9% toward 100% at 2 x 2.0 = 4.0 → 128.55%; × the 0.60 in-use
# factor (Open-EPC certs never lodge cylinder HX area → criteria fail)
# = 77.13% < 100% → the worksheet (216) reads 100.0000. In-range PSR
# (case 54, HLC large) keeps 0.60 × 198.9 = 119.34% (worksheet case 54
# (216) = 112.5% for its 187.5% record — both above the floor, unchanged).
from domain.sap10_calculator.tables.pcdb import heat_pump_record
record = heat_pump_record(100061)
assert record is not None
hp_main = MainHeatingDetail(
has_fghrs=False,
main_fuel_type=29, # electricity (heat pump)
heat_emitter_type=1, # radiators
emitter_temperature=0,
main_heating_control=2210,
main_heating_category=4,
sap_main_heating_code=None,
main_heating_index_number=100061,
)
epc = _typical_semi_detached_epc() # no specified cylinder → in-use 0.60
# Act — oversized PSR (extension region) vs an in-range PSR.
_space_ext, water_ext = _heat_pump_apm_efficiencies(
main=hp_main, hp_record=record,
hlc_annual_avg_w_per_k=107.82, # PSR 3.107 > largest 2.0
epc=epc,
) or (None, None)
_space_in, water_in = _heat_pump_apm_efficiencies(
main=hp_main, hp_record=record,
hlc_annual_avg_w_per_k=400.0, # PSR 0.837, in range
epc=epc,
) or (None, None)
# Assert — extended HP water efficiency is floored at 100% (1.0); the
# in-range PSR keeps the un-floored 0.60 × 198.9% = 119.34%.
assert water_ext is not None and abs(water_ext - 1.0) < 1e-9
assert water_in is not None and abs(water_in - 0.60 * 198.9 / 100.0) < 1e-9
def test_hot_water_immersion_off_peak_bills_at_table_13_blend() -> None:
# Arrange — SAP 10.2 Table 12a (PDF p.191) "Immersion water heater"
# row routes the WH column to Table 13 (PDF p.197). For an electric