mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-08 11:17:27 +00:00
Slice S0380.81: RdSAP 10 Table 32 default prices close cert 000565 sap_score 28 → 29 EXACT
Per ADR-0010 §10a amendment (2026-05-21) + RdSAP 10 Specification §19.1 (PDF page 80-81): "The SAP rating for RdSAP 10 is to be calculated using Table 32 prices (not Table 12) for section 10a and 10b." The §10a `fuel_cost` orchestrator already used RdSAP 10 Table 32 prices for STANDARD-tariff certs (via `table_32_unit_price_p_per_kwh`). The legacy off-peak fallback scalar path on `CalculatorInputs.*_fuel_cost_ gbp_per_kwh` (which fires when `tariff is not STANDARD` → `_ZERO_FUEL_COST_FOR_OFF_PEAK`) was reading from `prices.unit_price_p_ per_kwh`, which was still wired to SAP 10.2 Table 12 prices via the `SAP_10_2_SPEC_PRICES` PriceTable constant. For cert 000565 (Dual meter → TEN_HOUR tariff + mains-gas DHW via WHC 914), this leaked Table 12's 3.64 p/kWh mains gas rate (vs Table 32's 3.48) into HW cost — a £6 over-count on 3755 HW kWh that landed continuous SAP 0.041 below the 28.5 integer rounding boundary, flipping sap_score 29 → 28. Verbatim Table 32 (PDF page 95) rows touched by this slice: Mains gas 3.48 p/kWh (Table 12 was 3.64) 7-hour low tariff 5.50 p/kWh (Table 12 was 9.40) Standard electricity 13.19 p/kWh (Table 12 was 16.49) Fix is one PriceTable constant — `RDSAP_10_TABLE_32_PRICES` wires `table_32_unit_price_p_per_kwh` + the 5.50 / 13.19 scalars per the amendment. `SAP_10_2_SPEC_PRICES` becomes a back-compat alias so existing `prices=SAP_10_2_SPEC_PRICES` test imports continue working but route through Table 32 at the call site. Cert 000565 movements at HEAD (this commit): - sap_score: 28 → **29 ✓ EXACT** (was Δ−1) - sap_score_continuous: 28.4680 → 28.5355 (Δ−0.041 → Δ+0.027) - total_fuel_cost_gbp: 4683.88 → 4677.87 (Δ+3.62 → Δ−2.39) - ecf: 5.3910 → 5.3841 (Δ+0.004 → Δ−0.003) - hot_water_kwh_per_yr: 3755.03 = 3755.03 ✓ EXACT (unchanged) Cumulative cert 000565 closure across S0380.77/78/79/80/81: - hot_water_kwh: +1399 → +260 → −238 → 0 → 0 ✓ - sap_score (integer): +1 → −1 → 0 → −1 → 0 ✓ EXACT - sap_score_continuous: +0.60 → −0.035 → +0.057 → −0.041 → +0.027 Cohort impact: STANDARD-tariff certs (the 6 U985 fixtures 000474/000477/000480/000487/000490/000516 and all cohort-2/golden gas combi certs) route through the §10a orchestrator that already used Table 32 — zero shift. Off-peak certs in the test suite are cert 000565 only at this point (Dual / TEN_HOUR); golden cohort unaffected. Three existing scalar-pin tests in `test_cert_to_inputs.py` re-pinned to Table 32 values: - `test_gas_heating_with_electric_immersion_charges_hw_at_electricity_ rate` (0.0364 → 0.0348 gas; 0.1649 → 0.1319 std elec) - `test_off_peak_meter_routes_electric_costs_to_low_rate` (0.094 → 0.055 7-hour low fallback) - `test_standard_meter_keeps_electric_costs_on_standard_rate` (0.1649 → 0.1319 std elec) New test pins the rule: `test_rdsap_10_table_32_prices_charge_mains_gas_hot_water_at_3p48_per_kwh` quotes the §19.1 spec and asserts cert 000565 HW £/kWh = 0.0348. Test baseline: 553 pass + 8 expected `test_sap_result_pin[000565-*]` fails (was 551 + 9; sap_score now closes EXACT). The remaining 8 fails on cert 000565 are the documented work-queue residuals — RR fold-in (space_heating, main_heating_fuel), Table 12d/12e dual-rate blend for lighting + CO2, MEV PCDB record (pumps_fans). Pyright net-zero on touched files (45 errors, matching baseline). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
69710f5882
commit
9338914f8a
2 changed files with 84 additions and 17 deletions
|
|
@ -92,7 +92,6 @@ from domain.sap10_calculator.tables.table_12 import (
|
|||
co2_factor_kg_per_kwh,
|
||||
pe_monthly_factors_kwh_per_kwh,
|
||||
primary_energy_factor,
|
||||
unit_price_p_per_kwh,
|
||||
)
|
||||
from domain.sap10_calculator.tables.table_12a import (
|
||||
OtherUse,
|
||||
|
|
@ -471,14 +470,35 @@ _SPEC_E7_ELIGIBLE_MAIN_CODES: Final[frozenset[int]] = frozenset(
|
|||
)
|
||||
|
||||
|
||||
SAP_10_2_SPEC_PRICES: Final[PriceTable] = PriceTable(
|
||||
unit_price_p_per_kwh=unit_price_p_per_kwh,
|
||||
e7_low_rate_p_per_kwh=9.40,
|
||||
standard_electricity_p_per_kwh=16.49,
|
||||
# RdSAP 10 Table 32 (PDF page 95) — the canonical SAP-rating price set per
|
||||
# the RdSAP 10 §19.1 spec text:
|
||||
#
|
||||
# "The SAP rating for RdSAP 10 is to be calculated using Table 32 prices
|
||||
# (not Table 12) for section 10a and 10b."
|
||||
#
|
||||
# Table 32 mains gas = 3.48 p/kWh (vs SAP 10.2 Table 12 = 3.64);
|
||||
# 7-hour low = 5.50 p/kWh (vs Table 12 = 9.40);
|
||||
# standard electricity = 13.19 p/kWh (vs Table 12 = 16.49).
|
||||
#
|
||||
# Wired into `cert_to_inputs` as the default PriceTable per ADR-0010
|
||||
# §10a amendment (2026-05-21). Off-peak fallback scalars
|
||||
# (`hot_water_fuel_cost_gbp_per_kwh` etc.) read `unit_price_p_per_kwh`
|
||||
# directly so this is where the cohort-wide tariff lands.
|
||||
RDSAP_10_TABLE_32_PRICES: Final[PriceTable] = PriceTable(
|
||||
unit_price_p_per_kwh=table_32_unit_price_p_per_kwh,
|
||||
e7_low_rate_p_per_kwh=5.50, # Table 32 code 31 (7-hour low)
|
||||
standard_electricity_p_per_kwh=13.19, # Table 32 code 30
|
||||
e7_eligible_main_codes=_SPEC_E7_ELIGIBLE_MAIN_CODES,
|
||||
)
|
||||
|
||||
|
||||
# Legacy alias retained so existing imports keep working. Per ADR-0010
|
||||
# §10a amendment the SAP rating uses Table 32 prices, NOT SAP 10.2
|
||||
# Table 12 — the name is preserved for back-compat; both constants point
|
||||
# at the same Table 32 PriceTable instance.
|
||||
SAP_10_2_SPEC_PRICES: Final[PriceTable] = RDSAP_10_TABLE_32_PRICES
|
||||
|
||||
|
||||
# SAP 10.2 Table 4e (page 171) main_heating_control codes → control type
|
||||
# (1/2/3 per Table 9 "Heating control type" column). Type drives the
|
||||
# elsewhere-zone off-hours pattern in Table 9: types 1+2 use (7, 8),
|
||||
|
|
|
|||
|
|
@ -751,12 +751,14 @@ def test_gas_heating_with_electric_immersion_charges_hw_at_electricity_rate() ->
|
|||
|
||||
# Assert — gas main → space heating at gas rate; HW switches to electric
|
||||
# rate when water_heating_fuel is electric; lighting/pumps always electric.
|
||||
assert inputs_gas.space_heating_fuel_cost_gbp_per_kwh == 0.0364
|
||||
assert inputs_gas.hot_water_fuel_cost_gbp_per_kwh == 0.0364
|
||||
assert inputs_gas.other_fuel_cost_gbp_per_kwh == 0.1649
|
||||
assert inputs_hw.space_heating_fuel_cost_gbp_per_kwh == 0.0364
|
||||
assert inputs_hw.hot_water_fuel_cost_gbp_per_kwh == 0.1649
|
||||
assert inputs_hw.other_fuel_cost_gbp_per_kwh == 0.1649
|
||||
# Prices are RdSAP 10 Table 32 (PDF p.95) per the ADR-0010 §10a amendment:
|
||||
# mains gas = 3.48 p/kWh; standard electricity (code 30) = 13.19 p/kWh.
|
||||
assert inputs_gas.space_heating_fuel_cost_gbp_per_kwh == 0.0348
|
||||
assert inputs_gas.hot_water_fuel_cost_gbp_per_kwh == 0.0348
|
||||
assert inputs_gas.other_fuel_cost_gbp_per_kwh == 0.1319
|
||||
assert inputs_hw.space_heating_fuel_cost_gbp_per_kwh == 0.0348
|
||||
assert inputs_hw.hot_water_fuel_cost_gbp_per_kwh == 0.1319
|
||||
assert inputs_hw.other_fuel_cost_gbp_per_kwh == 0.1319
|
||||
|
||||
|
||||
def test_main_heating_control_code_maps_to_sap_control_type() -> None:
|
||||
|
|
@ -812,7 +814,8 @@ def test_off_peak_meter_routes_electric_costs_to_low_rate() -> None:
|
|||
# now apply Table 12a Grid 2 ALL_OTHER_USES + SEVEN_HOUR = 0.90
|
||||
# high → blended 0.90 * 15.29 + 0.10 * 5.50 = 14.311 p/kWh per
|
||||
# Slice S0380.61 (was 16.49 under the pre-Table-12a empirical
|
||||
# override).
|
||||
# override). RdSAP 10 Table 32 (PDF p.95) per ADR-0010 §10a
|
||||
# amendment: 7-hour low (code 31) = 5.50 p/kWh.
|
||||
epc = make_minimal_sap10_epc(
|
||||
total_floor_area_m2=_TYPICAL_TFA_M2,
|
||||
habitable_rooms_count=3,
|
||||
|
|
@ -846,8 +849,8 @@ def test_off_peak_meter_routes_electric_costs_to_low_rate() -> None:
|
|||
inputs = cert_to_inputs(epc)
|
||||
|
||||
# Assert
|
||||
assert inputs.space_heating_fuel_cost_gbp_per_kwh == 0.094
|
||||
assert inputs.hot_water_fuel_cost_gbp_per_kwh == 0.094
|
||||
assert inputs.space_heating_fuel_cost_gbp_per_kwh == 0.055
|
||||
assert inputs.hot_water_fuel_cost_gbp_per_kwh == 0.055
|
||||
assert abs(inputs.other_fuel_cost_gbp_per_kwh - 0.14311) < 1e-5
|
||||
|
||||
|
||||
|
|
@ -1266,9 +1269,11 @@ def test_standard_meter_keeps_electric_costs_on_standard_rate() -> None:
|
|||
inputs = cert_to_inputs(epc)
|
||||
|
||||
# Assert — no off-peak routing; all-electric dwelling pays standard rates.
|
||||
assert inputs.space_heating_fuel_cost_gbp_per_kwh == 0.1649
|
||||
assert inputs.hot_water_fuel_cost_gbp_per_kwh == 0.1649
|
||||
assert inputs.other_fuel_cost_gbp_per_kwh == 0.1649
|
||||
# RdSAP 10 Table 32 (PDF p.95) standard electricity (code 30) = 13.19
|
||||
# p/kWh per ADR-0010 §10a amendment.
|
||||
assert inputs.space_heating_fuel_cost_gbp_per_kwh == 0.1319
|
||||
assert inputs.hot_water_fuel_cost_gbp_per_kwh == 0.1319
|
||||
assert inputs.other_fuel_cost_gbp_per_kwh == 0.1319
|
||||
|
||||
|
||||
def test_mid_floor_flat_dwelling_type_zeroes_floor_and_roof_heat_transmission() -> None:
|
||||
|
|
@ -2083,3 +2088,45 @@ def test_air_source_heat_pump_main_heating_zeroes_table_3a_combi_loss_per_sap_4_
|
|||
f"month {month_idx}: combi loss {value!r} should be 0 for "
|
||||
f"non-combi main heating per SAP 10.2 §4 line 7702"
|
||||
)
|
||||
|
||||
|
||||
def test_rdsap_10_table_32_prices_charge_mains_gas_hot_water_at_3p48_per_kwh() -> None:
|
||||
"""RdSAP 10 Specification §19.1 (PDF page 80-81) — the §10a fuel-cost
|
||||
block uses RdSAP 10 Table 32 (PDF page 95) prices, NOT SAP 10.2
|
||||
Table 12 (PDF page 189):
|
||||
|
||||
"The SAP rating for RdSAP 10 is to be calculated using Table 32
|
||||
prices (not Table 12) for section 10a and 10b."
|
||||
|
||||
Table 32 row "Mains gas" = 3.48 p/kWh; the SAP 10.2 Table 12 row is
|
||||
3.64 p/kWh. Per ADR-0010 amendment (2026-05-21), the §10a orchestrator
|
||||
already targets Table 32. This pin closes the residual gap on the
|
||||
legacy off-peak scalar fallback `inputs.hot_water_fuel_cost_gbp_per_
|
||||
kwh` so it ALSO reads Table 32 — the cohort's `prices` PriceTable
|
||||
callable must return 3.48 for mains-gas DHW.
|
||||
|
||||
Cert 000565 lodges Dual meter (Tariff.TEN_HOUR) + gas-combi DHW via
|
||||
WHC 914 (mains gas Table 32 code 1). It hits the off-peak fallback
|
||||
branch in the calculator (`fuel_cost is _ZERO_FUEL_COST_FOR_OFF_PEAK`)
|
||||
so this scalar IS the consumed cost — the +£6 over-count on the
|
||||
cohort handover trace is exactly the £0.16 p/kWh × 3755 HW kWh
|
||||
delta. Closes cert 000565 sap_score 28 → 29 EXACT at the 28.5
|
||||
rounding boundary.
|
||||
"""
|
||||
# Arrange — mapper-driven cohort fixture (Summary_000565 → cert_to_
|
||||
# inputs), Dual meter / mains gas DHW.
|
||||
from domain.sap10_calculator.worksheet.tests import (
|
||||
_elmhurst_worksheet_000565 as _w000565,
|
||||
)
|
||||
epc = _w000565.build_epc()
|
||||
|
||||
# Act
|
||||
inputs = cert_to_inputs(epc)
|
||||
|
||||
# Assert — HW £/kWh equals Table 32 mains gas (3.48 p/kWh = 0.0348
|
||||
# £/kWh), NOT Table 12 (3.64 p/kWh = 0.0364 £/kWh).
|
||||
assert abs(inputs.hot_water_fuel_cost_gbp_per_kwh - 0.0348) <= 1e-6, (
|
||||
f"hot_water_fuel_cost_gbp_per_kwh = "
|
||||
f"{inputs.hot_water_fuel_cost_gbp_per_kwh!r}, expected 0.0348 per "
|
||||
f"RdSAP 10 Table 32 mains gas (§19.1 amendment, ADR-0010)"
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue