mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-30 13:10:47 +00:00
fix(fuel): cost main heating system 2 at its own fuel price, not main 1's (SAP 10.2 §10a worksheet line 213)
Main heating system 2's space-heating fuel cost (worksheet (213)) was billed at main system 1's Table 32 unit price (`main_2_high_rate_gbp_per_kwh` reused `main_1_high_rate_gbp_per_kwh`). For a dual-FUEL pair this grossly mis-costs the second main: cert 10032957680 "Copse Cottage" (main 1 electric room heaters fuel 30, main 2 wood logs fuel 6) charged its 9481 kWh of wood at 13.19 p/kWh instead of 4.23 p/kWh — +£850/yr → SAP 21.75 vs lodged 45. Route main 2 through its own fuel code (`_main_fuel_code(details[1])`), mirroring the existing secondary-fuel handling. Copse Cottage 21.75 -> 45.94. Corpus within-0.5 holds 72.5%, SAP MAE 0.815 -> 0.793 (ratcheted ceiling 0.82 -> 0.80); CO2/PE unchanged. Same-fuel dual mains (gas+gas) unaffected. Off-peak-tariff dual-fuel mains still defer to the legacy scalar path (separate slice). Spec-cited unit pin added (AAA). pyright not installed locally — strict type gate not run. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
a9632937d5
commit
702150002f
3 changed files with 76 additions and 2 deletions
|
|
@ -6927,6 +6927,26 @@ def _fuel_cost(
|
|||
main_1_high_rate_gbp_per_kwh = (
|
||||
table_32_unit_price_p_per_kwh(main_fuel_code) * _PENCE_TO_GBP
|
||||
)
|
||||
# Main heating system 2 is costed at ITS OWN fuel price (SAP 10.2 §10a
|
||||
# worksheet line (213) bills main system 2's fuel separately from main 1's
|
||||
# (211)) — Table 32 unit price keyed on main 2's fuel code. Pre-fix this
|
||||
# column reused `main_1_high_rate_gbp_per_kwh`, charging a dual-fuel second
|
||||
# main (e.g. wood logs SAP code 633, fuel 6 @ 4.23 p/kWh) at the main-1
|
||||
# electric rate (13.19 p/kWh), grossly over-costing electric+wood
|
||||
# room-heater dwellings (cert 10032957680 "Copse Cottage" +£850/yr ->
|
||||
# SAP -23). Falls back to main 1's price only when no second main is lodged.
|
||||
main_2_detail = (
|
||||
epc.sap_heating.main_heating_details[1]
|
||||
if epc.sap_heating
|
||||
and len(epc.sap_heating.main_heating_details or []) >= 2
|
||||
else None
|
||||
)
|
||||
main_2_fuel_code = _main_fuel_code(main_2_detail)
|
||||
main_2_high_rate_gbp_per_kwh = (
|
||||
table_32_unit_price_p_per_kwh(main_2_fuel_code) * _PENCE_TO_GBP
|
||||
if main_2_fuel_code is not None
|
||||
else main_1_high_rate_gbp_per_kwh
|
||||
)
|
||||
water_high_rate_gbp_per_kwh = (
|
||||
table_32_unit_price_p_per_kwh(water_heating_fuel_code)
|
||||
* _PENCE_TO_GBP
|
||||
|
|
@ -6976,7 +6996,7 @@ def _fuel_cost(
|
|||
main_1_low_rate_gbp_per_kwh=0.0,
|
||||
main_1_high_rate_fraction=1.0,
|
||||
main_2_kwh_per_yr=main_2_kwh,
|
||||
main_2_high_rate_gbp_per_kwh=main_1_high_rate_gbp_per_kwh,
|
||||
main_2_high_rate_gbp_per_kwh=main_2_high_rate_gbp_per_kwh,
|
||||
main_2_low_rate_gbp_per_kwh=0.0,
|
||||
main_2_high_rate_fraction=1.0 if main_2_kwh > 0.0 else 0.0,
|
||||
secondary_kwh_per_yr=secondary_kwh,
|
||||
|
|
|
|||
|
|
@ -473,6 +473,60 @@ 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_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
|
||||
# main system 1's (211). A dwelling with main-1 = electric room heaters
|
||||
# (SAP 691, fuel 30 standard electricity @ 13.19 p/kWh) and main-2 =
|
||||
# wood-log room heaters (SAP 633, fuel 6 @ 4.23 p/kWh), 50/50 split, on a
|
||||
# single-rate meter (so the §10a standard path runs). Pre-fix the main-2
|
||||
# column reused main-1's electric price, charging the wood at 13.19 p/kWh
|
||||
# and grossly over-costing electric+wood dwellings (cert 10032957680
|
||||
# "Copse Cottage" SAP 21.75 -> 45.94 vs lodged 45).
|
||||
main_1_electric = MainHeatingDetail(
|
||||
has_fghrs=False,
|
||||
main_fuel_type=30, # standard electricity
|
||||
heat_emitter_type=0,
|
||||
emitter_temperature="NA",
|
||||
main_heating_control=2106,
|
||||
main_heating_category=10,
|
||||
sap_main_heating_code=691,
|
||||
main_heating_fraction=50,
|
||||
)
|
||||
main_2_wood = MainHeatingDetail(
|
||||
has_fghrs=False,
|
||||
main_fuel_type=6, # wood logs — Table 32 = 4.23 p/kWh
|
||||
heat_emitter_type=0,
|
||||
emitter_temperature="NA",
|
||||
main_heating_control=2106,
|
||||
main_heating_category=10,
|
||||
sap_main_heating_code=633,
|
||||
main_heating_fraction=50,
|
||||
)
|
||||
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="D")],
|
||||
sap_heating=make_sap_heating(
|
||||
main_heating_details=[main_1_electric, main_2_wood],
|
||||
),
|
||||
)
|
||||
|
||||
# Act
|
||||
inputs = cert_to_inputs(epc)
|
||||
result = Sap10Calculator().calculate(epc)
|
||||
fc = inputs.fuel_cost
|
||||
main_2_kwh = result.main_2_heating_fuel_kwh_per_yr
|
||||
|
||||
# Assert — main-2 wood billed at 4.23 p/kWh (0.0423 £/kWh), NOT main-1's
|
||||
# electric 13.19 p/kWh. (213) main_2_total_cost = kWh × wood price.
|
||||
assert main_2_kwh > 0.0
|
||||
assert abs(fc.main_2_total_cost_gbp - main_2_kwh * 0.0423) <= 1e-6
|
||||
# And explicitly NOT the electric rate (the pre-fix value).
|
||||
assert abs(fc.main_2_total_cost_gbp - main_2_kwh * 0.1319) > 1.0
|
||||
|
||||
|
||||
def test_cylinder_thermostat_assumed_present_per_sap_9_4_9() -> None:
|
||||
# Arrange — SAP 10.2 §9.4.9 (PDF p.32): "A cylinder thermostat should be
|
||||
# assumed to be present when the domestic hot water is obtained from a
|
||||
|
|
|
|||
|
|
@ -194,7 +194,7 @@ _CORPUS = Path(
|
|||
# stress worksheet (simulated case 46): closed its last ventilation residual
|
||||
# (our Jan ACH 9.14 -> 9.0748 exact; SAP 29 -> 30 = accredited Elmhurst).
|
||||
_MIN_WITHIN_HALF_SAP = 0.72
|
||||
_MAX_SAP_MAE = 0.82
|
||||
_MAX_SAP_MAE = 0.80
|
||||
_MAX_CO2_MAE_TONNES = 0.09 # t CO2 / yr vs co2_emissions_current
|
||||
_MAX_PE_PER_M2_MAE = 3.7 # kWh / m2 / yr vs energy_consumption_current
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue