mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-07-27 23:35:01 +00:00
Revert the water-only heat-network calculator changes (tracked in #1592)
Backs out the WHC-950/951/952 primary-loss (d3c0b9e4) and flat-rate-charging (fecf4f62) changes to cert_to_inputs.py: they fire on ANY cert with those water-heating codes, regressing the gov-API RdSAP corpus (test_api_path_sap_accuracy_on_rdsap_21_0_1_corpus MAE 0.627 -> 0.630), and an Opus audit found the water-only branch has a pre-existing plant-efficiency double count they flow more kWh through. The calculator work + the correct fix (water_eff = 1.0/(DLF*1.05)) + an Elmhurst-worksheet regression are tracked in #1592, isolated from the extractor accuracy PR. The cylinder- insulation MAPPER fix (f5e4814b) is retained. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
21674d5ed0
commit
dc271e959a
2 changed files with 6 additions and 94 deletions
|
|
@ -1187,11 +1187,6 @@ _HEAT_NETWORK_DHW_CHARGING_FACTOR_BY_CODE: Final[dict[int, float]] = {
|
|||
2306: 1.00, 2308: 1.00, 2309: 1.00, 2310: 1.00, 2312: 1.00, 2314: 1.00,
|
||||
}
|
||||
|
||||
# SAP 10.2 Table 4c(3) (PDF p.169) worksheet (305a) flat-rate-charging
|
||||
# factor — the RdSAP 10 (spec p.59) default for a water-ONLY heat network
|
||||
# (WHC 950/951/952), whose own charging method is never lodged.
|
||||
_TABLE_4C3_FLAT_RATE_CHARGING_FACTOR: Final[float] = 1.05
|
||||
|
||||
|
||||
def _heat_network_space_charging_factor(main: Optional[MainHeatingDetail]) -> float:
|
||||
"""SAP 10.2 Table 4c(3) (PDF p.169) worksheet (305) — heat-network
|
||||
|
|
@ -6590,16 +6585,6 @@ def _primary_loss_applies(
|
|||
# kWh/yr — zero before this branch.
|
||||
if water_heating_code in _WATER_HEATING_BOILER_CIRCULATOR_CODES:
|
||||
return True
|
||||
# SAP 10.2 §4 "Heat networks" (PDF p.17 line 1482): "Primary circuit
|
||||
# loss for insulated pipework and cylinderstat should be included (see
|
||||
# Table 3)." A water-ONLY community scheme (WHC 950/951/952) feeds the
|
||||
# dwelling-side cylinder via primary pipework from the network
|
||||
# connection whatever the space main is, so the loss applies exactly
|
||||
# as for heat-network mains with WHC 901/902/914 (below). Checked off
|
||||
# `water_heating_code` before the main-keyed branches so a non-network
|
||||
# space main (HP, direct-acting electric) can't zero it out.
|
||||
if water_heating_code in _WATER_HEAT_NETWORK_ONLY_CODES:
|
||||
return True
|
||||
# SAP 10.2 Table 3 (PDF p.160) zero-loss list names "Direct-acting
|
||||
# electric boiler" verbatim. RdSAP 10 §12 (p.62) classifies SAP code
|
||||
# 191 as the direct-acting electric boiler: its cylinder is immersion-
|
||||
|
|
@ -7104,20 +7089,13 @@ def _primary_loss_override(
|
|||
water_heating_code=epc.sap_heating.water_heating_code,
|
||||
):
|
||||
return None
|
||||
# Heat-network DHW: either the space main is a network (WHC 901/902/914
|
||||
# inherit its DHW) or the DHW is its own water-only community scheme
|
||||
# (WHC 950/951/952) — the Table 3 heat-network row keys on the DHW
|
||||
# source, not on the space main.
|
||||
heat_network_dhw = is_heat_network_main(main) or (
|
||||
epc.sap_heating.water_heating_code in _WATER_HEAT_NETWORK_ONLY_CODES
|
||||
)
|
||||
# SAP 10.2 §4 "Heat networks" (PDF p.17 line 1482) pins community-
|
||||
# heating primary pipework to "insulated" (p=1.0), overriding the
|
||||
# RdSAP §3 age-band default which would otherwise return 0 for
|
||||
# pre-2007 stock. See `_HEAT_NETWORK_PIPEWORK_INSULATION_FRACTION`.
|
||||
pipework_p = (
|
||||
_HEAT_NETWORK_PIPEWORK_INSULATION_FRACTION
|
||||
if heat_network_dhw
|
||||
if is_heat_network_main(main)
|
||||
else _pipework_insulation_fraction_table_3(primary_age)
|
||||
)
|
||||
base = primary_loss_monthly_kwh(
|
||||
|
|
@ -7129,7 +7107,7 @@ def _primary_loss_override(
|
|||
# row applies regardless of the thermostat / separate-timing
|
||||
# lodgement (and so is robust to the community-fuel-as-electric
|
||||
# collision that would otherwise route DHW to the h=5 row).
|
||||
heat_network=heat_network_dhw,
|
||||
heat_network=is_heat_network_main(main),
|
||||
)
|
||||
# SAP 10.2 §12.4.4 (PDF p.36-37): for back-boiler combos summer DHW
|
||||
# comes from an electric immersion, not from the boiler — the boiler
|
||||
|
|
@ -8029,16 +8007,7 @@ def cert_to_inputs(
|
|||
# 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.
|
||||
# RdSAP 10 (spec p.59, "Heat Network (HN) supplying — water
|
||||
# heating only"): a non-PCDB water-only network defaults to
|
||||
# "flat-rate charging", so the Table 4c(3) worksheet (305a)
|
||||
# factor 1.05 always applies — the scheme's own charging method
|
||||
# is never lodged (the dwelling's heating-control code describes
|
||||
# the SPACE network's charging, not this one's).
|
||||
water_eff = water_eff / (
|
||||
_heat_network_dlf(primary_age)
|
||||
* _TABLE_4C3_FLAT_RATE_CHARGING_FACTOR
|
||||
)
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -478,57 +478,6 @@ def test_heat_network_primary_loss_uses_p1_h3_all_months_per_table_3() -> None:
|
|||
assert abs(sum(wh_result.primary_loss_monthly_kwh) - expected_primary_kwh) <= 1e-6
|
||||
|
||||
|
||||
def test_water_only_heat_network_cylinder_accrues_primary_loss() -> None:
|
||||
# Arrange — DHW from a water-ONLY community scheme (WHC 950) with a
|
||||
# lodged 210 L cylinder, on a heat-network space main (Table 4a 301,
|
||||
# cat 6) — fixture 507644414148's shape. SAP 10.2 §4 "Heat networks"
|
||||
# (PDF p.17 line 1482): "Primary circuit loss for insulated pipework
|
||||
# and cylinderstat should be included (see Table 3)" — the network
|
||||
# feeds the dwelling-side cylinder via primary pipework whichever WHC
|
||||
# lodges it, so the loss applies to 950/951/952 exactly as to
|
||||
# 901/902/914 (pre-fix these fell through to zero). Table 3 heat-
|
||||
# network row: p = 1.0 and h = 3 for all months.
|
||||
main = MainHeatingDetail(
|
||||
has_fghrs=False,
|
||||
main_fuel_type=51, # Table 12 community heat (from boilers)
|
||||
heat_emitter_type=1,
|
||||
emitter_temperature=1,
|
||||
main_heating_control=2306,
|
||||
main_heating_category=6,
|
||||
sap_main_heating_code=301,
|
||||
)
|
||||
part = make_building_part(construction_age_band="F")
|
||||
epc = make_minimal_sap10_epc(
|
||||
total_floor_area_m2=_TYPICAL_TFA_M2,
|
||||
habitable_rooms_count=4,
|
||||
country_code="ENG",
|
||||
has_hot_water_cylinder=True,
|
||||
sap_building_parts=[part],
|
||||
sap_heating=make_sap_heating(
|
||||
main_heating_details=[main],
|
||||
water_heating_code=950,
|
||||
water_heating_fuel=51,
|
||||
cylinder_size=4, # Large (>170 litres) → 210 L
|
||||
cylinder_insulation_type=1,
|
||||
cylinder_insulation_thickness_mm=50,
|
||||
),
|
||||
)
|
||||
|
||||
# Act
|
||||
wh_result, _ = _water_heating_worksheet_and_gains(
|
||||
epc=epc,
|
||||
water_efficiency_pct=100.0,
|
||||
is_instantaneous=False,
|
||||
primary_age="F",
|
||||
pcdb_record=None,
|
||||
)
|
||||
|
||||
# Assert — p=1.0, h=3 all months → 365 × 14 × (0.0091×3 + 0.0263).
|
||||
assert wh_result is not None
|
||||
expected_primary_kwh = 365.0 * 14.0 * (0.0091 * 3.0 + 0.0263)
|
||||
assert abs(sum(wh_result.primary_loss_monthly_kwh) - expected_primary_kwh) <= 1e-6
|
||||
|
||||
|
||||
def test_dual_main_system_2_costed_at_its_own_fuel_price() -> None:
|
||||
# Arrange — SAP 10.2 §10a: main heating system 2's fuel cost (worksheet
|
||||
# line (213)) is billed at ITS OWN Table 32 fuel price, separately from
|
||||
|
|
@ -1460,12 +1409,7 @@ def test_hot_water_only_heat_network_whc_950_applies_table12c_dlf() -> None:
|
|||
# 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 the DLF (age E → 1.41) × the Table 4c(3)
|
||||
# charging factor. RdSAP 10 (spec p.59, "Heat Network (HN) supplying
|
||||
# — water heating only"): a non-PCDB water-only network defaults to
|
||||
# "flat-rate charging" — worksheet (305a) factor 1.05 — since the
|
||||
# scheme's own charging method is never lodged (the dwelling's
|
||||
# heating-control code describes the SPACE network, not this one).
|
||||
# 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,
|
||||
|
|
@ -1501,9 +1445,8 @@ def test_hot_water_only_heat_network_whc_950_applies_table12c_dlf() -> None:
|
|||
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
|
||||
# × the flat-rate-charging default (1.41 × 1.05 = 1.4805).
|
||||
assert abs(hw_network / baseline - 1.41 * 1.05) <= 0.02
|
||||
# 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:
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue