Slice S0380.150: SAP 10.2 §12 / Appendix F2 — 18-hour high-rate for pumps + lighting

SAP 10.2 §12 (PDF p.45 lines 2280-2283):

  "The 18-hour tariff is only for use with electric CPSUs with
   sufficient energy storage to provide space (and possibly water)
   heating requirements for 2 hours. Electricity at the low-rate price
   is available for 18 hours per day, with interruptions totalling 6
   hours per day, with the proviso that no interruption will exceed 2
   hours. The low-rate price applies to space and water heating, while
   electricity for all other purposes is at the high-rate price."

SAP 10.2 Appendix F2 (PDF p.63 lines 3809-3812):

  "F2 Electric CPSUs using 18-hour electricity tariff. The 18-hour
   low rate applies to all space heating and water heating provided
   by the CPSU. The CPSU must have sufficient energy stored to provide
   heating during a 2-hour shut-off period. The 18-hour high rate
   applies to all other electricity uses."

Table 12a Grid 2 omits 18-hour / 24-hour from its 7-hour / 10-hour
table; pre-slice the cascade's `_other_fuel_cost_gbp_per_kwh` fell
through Grid 2's `NotImplementedError` to
`prices.standard_electricity_p_per_kwh` (Table 32 code 30 = 13.19
p/kWh). Per §12 + Appendix F2 the 18-hour rule is explicit fraction =
1.0 at the high rate — pumps, fans, and lighting bill at the 18-hour
high rate (Table 32 code 38 = 13.67 p/kWh).

All 41 heating-systems corpus variants lodge `meter_type='18 Hour'`,
so this gap was cohort-wide. Pre-slice the cascade undercounted
pumps + lighting cost by (13.67 − 13.19) × kWh on every variant:

  oil 1            Δcost -£9.31 → -£6.69   (closed £2.62, pumps 265 +
                                            lighting 282 × £0.0048)
  oil pcdb 1/2     Δcost -£8.32 → -£6.29   (closed £2.03)
  oil pcdb 3       Δcost -£8.91 → -£6.29   (closed £2.62)
  pcdb 1           Δcost -£11.10 → -£9.07  (closed £2.03)
  ashp             Δcost -£5.57 → -£4.22   (closed £1.35, lighting only)
  electric 1..9    Δcost shift ~ -£1.35..+£1.35  (lighting only;
                                                  storage / room-heater
                                                  certs carry pumps_fans
                                                  = 0)
  solid fuel 4..11 Δcost ~ -£1.55 (lighting only)
  gshp             Δcost -£26.48 → -£25.12 (closed £1.35)

Pyright net-zero (43 → 43). Extended handover suite: 892 → 893 pass.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Khalim Conn-Kowlessar 2026-06-01 09:34:09 +00:00
parent f20d96369f
commit a658f73613
3 changed files with 80 additions and 26 deletions

View file

@ -219,21 +219,21 @@ class _CorpusExpectation:
# the SH+Sec demand mismatch for electric 3/6/7 (Table 11 fraction
# for codes 401/402) remains the open driver of those SAP residuals.
_EXPECTATIONS: tuple[_CorpusExpectation, ...] = (
_CorpusExpectation(variant='ashp', block='11a', expected_sap_resid=+0.2418, expected_cost_resid_gbp=-5.5706, expected_co2_resid_kg=-1.4283, expected_pe_resid_kwh=-11.8017),
_CorpusExpectation(variant='electric 1', block='11a', expected_sap_resid=+0.4522, expected_cost_resid_gbp=-10.4203, expected_co2_resid_kg=-4.3334, expected_pe_resid_kwh=-40.1603),
_CorpusExpectation(variant='electric 2', block='11a', expected_sap_resid=-0.1842, expected_cost_resid_gbp=+4.2439, expected_co2_resid_kg=+38.7768, expected_pe_resid_kwh=+392.8379),
_CorpusExpectation(variant='electric 3', block='11a', expected_sap_resid=+0.6568, expected_cost_resid_gbp=-15.1334, expected_co2_resid_kg=-13.8238, expected_pe_resid_kwh=-114.7533),
_CorpusExpectation(variant='electric 5', block='11a', expected_sap_resid=-0.6813, expected_cost_resid_gbp=+15.6982, expected_co2_resid_kg=+43.9325, expected_pe_resid_kwh=+338.5315),
_CorpusExpectation(variant='electric 6', block='11a', expected_sap_resid=+0.5744, expected_cost_resid_gbp=-13.2352, expected_co2_resid_kg=-10.1354, expected_pe_resid_kwh=-93.1997),
_CorpusExpectation(variant='electric 7', block='11a', expected_sap_resid=+0.5398, expected_cost_resid_gbp=-12.4372, expected_co2_resid_kg=-8.3964, expected_pe_resid_kwh=-83.9576),
_CorpusExpectation(variant='electric 8', block='11a', expected_sap_resid=+0.4874, expected_cost_resid_gbp=-11.2307, expected_co2_resid_kg=-6.4095, expected_pe_resid_kwh=-70.5744),
_CorpusExpectation(variant='electric 9', block='11a', expected_sap_resid=+0.6261, expected_cost_resid_gbp=-14.4253, expected_co2_resid_kg=-12.3507, expected_pe_resid_kwh=-105.2495),
_CorpusExpectation(variant='gshp', block='11a', expected_sap_resid=+1.1491, expected_cost_resid_gbp=-26.4775, expected_co2_resid_kg=-41.4461, expected_pe_resid_kwh=-454.5023),
_CorpusExpectation(variant='oil 1', block='11a', expected_sap_resid=+0.4042, expected_cost_resid_gbp=-9.3142, expected_co2_resid_kg=-36.6371, expected_pe_resid_kwh=-71.2875),
_CorpusExpectation(variant='oil pcdb 1', block='11a', expected_sap_resid=+0.3609, expected_cost_resid_gbp=-8.3159, expected_co2_resid_kg=-34.4292, expected_pe_resid_kwh=-67.1831),
_CorpusExpectation(variant='oil pcdb 2', block='11a', expected_sap_resid=+0.3609, expected_cost_resid_gbp=-8.3159, expected_co2_resid_kg=-34.4292, expected_pe_resid_kwh=-67.1831),
_CorpusExpectation(variant='oil pcdb 3', block='11a', expected_sap_resid=+0.3869, expected_cost_resid_gbp=-8.9139, expected_co2_resid_kg=-34.4447, expected_pe_resid_kwh=-67.2071),
_CorpusExpectation(variant='pcdb 1', block='11a', expected_sap_resid=+0.5018, expected_cost_resid_gbp=-11.0973, expected_co2_resid_kg=-49.6654, expected_pe_resid_kwh=-92.8147),
_CorpusExpectation(variant='ashp', block='11a', expected_sap_resid=+0.1830, expected_cost_resid_gbp=-4.2166, expected_co2_resid_kg=-1.4283, expected_pe_resid_kwh=-11.8017),
_CorpusExpectation(variant='electric 1', block='11a', expected_sap_resid=+0.3849, expected_cost_resid_gbp=-8.8694, expected_co2_resid_kg=-4.3334, expected_pe_resid_kwh=-40.1603),
_CorpusExpectation(variant='electric 2', block='11a', expected_sap_resid=-0.2430, expected_cost_resid_gbp=+5.5979, expected_co2_resid_kg=+38.7768, expected_pe_resid_kwh=+392.8379),
_CorpusExpectation(variant='electric 3', block='11a', expected_sap_resid=+0.5980, expected_cost_resid_gbp=-13.7793, expected_co2_resid_kg=-13.8238, expected_pe_resid_kwh=-114.7533),
_CorpusExpectation(variant='electric 5', block='11a', expected_sap_resid=-0.7401, expected_cost_resid_gbp=+17.0523, expected_co2_resid_kg=+43.9325, expected_pe_resid_kwh=+338.5315),
_CorpusExpectation(variant='electric 6', block='11a', expected_sap_resid=+0.5156, expected_cost_resid_gbp=-11.8811, expected_co2_resid_kg=-10.1354, expected_pe_resid_kwh=-93.1997),
_CorpusExpectation(variant='electric 7', block='11a', expected_sap_resid=+0.4810, expected_cost_resid_gbp=-11.0832, expected_co2_resid_kg=-8.3964, expected_pe_resid_kwh=-83.9576),
_CorpusExpectation(variant='electric 8', block='11a', expected_sap_resid=+0.4286, expected_cost_resid_gbp=-9.8766, expected_co2_resid_kg=-6.4095, expected_pe_resid_kwh=-70.5744),
_CorpusExpectation(variant='electric 9', block='11a', expected_sap_resid=+0.5673, expected_cost_resid_gbp=-13.0713, expected_co2_resid_kg=-12.3507, expected_pe_resid_kwh=-105.2495),
_CorpusExpectation(variant='gshp', block='11a', expected_sap_resid=+1.0903, expected_cost_resid_gbp=-25.1234, expected_co2_resid_kg=-41.4461, expected_pe_resid_kwh=-454.5023),
_CorpusExpectation(variant='oil 1', block='11a', expected_sap_resid=+0.2902, expected_cost_resid_gbp=-6.6882, expected_co2_resid_kg=-36.6371, expected_pe_resid_kwh=-71.2875),
_CorpusExpectation(variant='oil pcdb 1', block='11a', expected_sap_resid=+0.2728, expected_cost_resid_gbp=-6.2850, expected_co2_resid_kg=-34.4292, expected_pe_resid_kwh=-67.1831),
_CorpusExpectation(variant='oil pcdb 2', block='11a', expected_sap_resid=+0.2728, expected_cost_resid_gbp=-6.2850, expected_co2_resid_kg=-34.4292, expected_pe_resid_kwh=-67.1831),
_CorpusExpectation(variant='oil pcdb 3', block='11a', expected_sap_resid=+0.2729, expected_cost_resid_gbp=-6.2879, expected_co2_resid_kg=-34.4447, expected_pe_resid_kwh=-67.2071),
_CorpusExpectation(variant='pcdb 1', block='11a', expected_sap_resid=+0.4096, expected_cost_resid_gbp=-9.0664, expected_co2_resid_kg=-49.6654, expected_pe_resid_kwh=-92.8147),
# Slice S0380.133 unblocked 10 solid-fuel variants by routing the
# Elmhurst §14.0 "Main Heating EES Code" through the new
# `_ELMHURST_MAIN_HEATING_EES_TO_FUEL_CODE` dict. Pre-slice the
@ -241,16 +241,16 @@ _EXPECTATIONS: tuple[_CorpusExpectation, ...] = (
# cost / CO2 / PE all route via the correct Table 32 fuel code.
# Remaining residuals are likely heating-system efficiency or
# control-type gaps — separate slices.
_CorpusExpectation(variant='solid fuel 2', block='11a', expected_sap_resid=+3.1478, expected_cost_resid_gbp=-72.5305, expected_co2_resid_kg=+41.5584, expected_pe_resid_kwh=-1346.0016),
_CorpusExpectation(variant='solid fuel 3', block='11a', expected_sap_resid=+1.8310, expected_cost_resid_gbp=-42.1903, expected_co2_resid_kg=-441.0048, expected_pe_resid_kwh=-1069.2375),
_CorpusExpectation(variant='solid fuel 4', block='11a', expected_sap_resid=+0.4523, expected_cost_resid_gbp=-10.4208, expected_co2_resid_kg=-86.4442, expected_pe_resid_kwh=-106.8858),
_CorpusExpectation(variant='solid fuel 5', block='11a', expected_sap_resid=+0.3440, expected_cost_resid_gbp=-7.9255, expected_co2_resid_kg=-56.6651, expected_pe_resid_kwh=-41.8008),
_CorpusExpectation(variant='solid fuel 6', block='11a', expected_sap_resid=+0.5376, expected_cost_resid_gbp=-12.3864, expected_co2_resid_kg=-11.6812, expected_pe_resid_kwh=-89.8541),
_CorpusExpectation(variant='solid fuel 7', block='11a', expected_sap_resid=+0.6029, expected_cost_resid_gbp=-14.0701, expected_co2_resid_kg=-87.4488, expected_pe_resid_kwh=-117.8475),
_CorpusExpectation(variant='solid fuel 8', block='11a', expected_sap_resid=+0.4291, expected_cost_resid_gbp=-9.8880, expected_co2_resid_kg=+5.6990, expected_pe_resid_kwh=-89.4580),
_CorpusExpectation(variant='solid fuel 9', block='11a', expected_sap_resid=+0.5486, expected_cost_resid_gbp=-12.6405, expected_co2_resid_kg=+1.6494, expected_pe_resid_kwh=-103.7659),
_CorpusExpectation(variant='solid fuel 10', block='11a', expected_sap_resid=+0.5837, expected_cost_resid_gbp=-13.4482, expected_co2_resid_kg=-0.2410, expected_pe_resid_kwh=-130.1413),
_CorpusExpectation(variant='solid fuel 11', block='11a', expected_sap_resid=+0.4809, expected_cost_resid_gbp=-11.0799, expected_co2_resid_kg=+5.5072, expected_pe_resid_kwh=-92.4917),
_CorpusExpectation(variant='solid fuel 2', block='11a', expected_sap_resid=+3.0805, expected_cost_resid_gbp=-70.9797, expected_co2_resid_kg=+41.5584, expected_pe_resid_kwh=-1346.0016),
_CorpusExpectation(variant='solid fuel 3', block='11a', expected_sap_resid=+1.7637, expected_cost_resid_gbp=-40.6395, expected_co2_resid_kg=-441.0048, expected_pe_resid_kwh=-1069.2375),
_CorpusExpectation(variant='solid fuel 4', block='11a', expected_sap_resid=+0.3935, expected_cost_resid_gbp=-9.0668, expected_co2_resid_kg=-86.4442, expected_pe_resid_kwh=-106.8858),
_CorpusExpectation(variant='solid fuel 5', block='11a', expected_sap_resid=+0.2767, expected_cost_resid_gbp=-6.3746, expected_co2_resid_kg=-56.6651, expected_pe_resid_kwh=-41.8008),
_CorpusExpectation(variant='solid fuel 6', block='11a', expected_sap_resid=+0.4703, expected_cost_resid_gbp=-10.8355, expected_co2_resid_kg=-11.6812, expected_pe_resid_kwh=-89.8541),
_CorpusExpectation(variant='solid fuel 7', block='11a', expected_sap_resid=+0.5361, expected_cost_resid_gbp=-12.5193, expected_co2_resid_kg=-87.4488, expected_pe_resid_kwh=-117.8475),
_CorpusExpectation(variant='solid fuel 8', block='11a', expected_sap_resid=+0.3618, expected_cost_resid_gbp=-8.3371, expected_co2_resid_kg=+5.6990, expected_pe_resid_kwh=-89.4580),
_CorpusExpectation(variant='solid fuel 9', block='11a', expected_sap_resid=+0.4898, expected_cost_resid_gbp=-11.2865, expected_co2_resid_kg=+1.6494, expected_pe_resid_kwh=-103.7659),
_CorpusExpectation(variant='solid fuel 10', block='11a', expected_sap_resid=+0.5249, expected_cost_resid_gbp=-12.0942, expected_co2_resid_kg=-0.2410, expected_pe_resid_kwh=-130.1413),
_CorpusExpectation(variant='solid fuel 11', block='11a', expected_sap_resid=+0.4221, expected_cost_resid_gbp=-9.7259, expected_co2_resid_kg=+5.5072, expected_pe_resid_kwh=-92.4917),
)

View file

@ -1985,7 +1985,17 @@ def _other_fuel_cost_gbp_per_kwh(
is on an off-peak tariff, applies the Table 12a Grid 2
ALL_OTHER_USES high-rate fraction blended Table 32 rate. Standard
tariff bypasses to the prices table's flat scalar (preserves the
cohort fixture cost cascade at 1e-4)."""
cohort fixture cost cascade at 1e-4).
SAP 10.2 §12 (PDF p.45) + Appendix F2 (PDF p.63) for the 18-hour
tariff, "the 18-hour high rate applies to all other electricity
uses" (i.e. fraction = 1.0 at the high rate). Table 12a Grid 2 omits
18-hour and 24-hour from its 7-hour/10-hour table; for 18-hour the
spec rule is explicit (fraction 1.0 at the high rate per Appendix
F2), so route directly to the 18-hour high rate (Table 32 code 38 =
13.67 p/kWh). 24-hour heating tariff is a heating-only single-rate
tariff (Table 32 code 35 = 6.61 p/kWh) non-heating uses fall back
to the standard electricity rate."""
if tariff is Tariff.STANDARD:
return prices.standard_electricity_p_per_kwh * _PENCE_TO_GBP
try:
@ -1993,6 +2003,9 @@ def _other_fuel_cost_gbp_per_kwh(
OtherUse.ALL_OTHER_USES, tariff,
)
except NotImplementedError:
if tariff is Tariff.EIGHTEEN_HOUR:
high_rate, _low = _tariff_high_low_rates_p_per_kwh(tariff)
return high_rate * _PENCE_TO_GBP
return prices.standard_electricity_p_per_kwh * _PENCE_TO_GBP
high_rate, low_rate = _tariff_high_low_rates_p_per_kwh(tariff)
blended = high_frac * high_rate + (1.0 - high_frac) * low_rate

View file

@ -45,6 +45,7 @@ from domain.sap10_calculator.rdsap.cert_to_inputs import (
_is_electric_water, # pyright: ignore[reportPrivateUsage]
_is_off_peak_meter, # pyright: ignore[reportPrivateUsage]
_main_floor_u_value, # pyright: ignore[reportPrivateUsage]
_other_fuel_cost_gbp_per_kwh, # pyright: ignore[reportPrivateUsage]
_pv_overshading_factor, # pyright: ignore[reportPrivateUsage]
_pv_pitch_deg, # pyright: ignore[reportPrivateUsage]
_responsiveness, # pyright: ignore[reportPrivateUsage]
@ -1398,6 +1399,46 @@ def test_tariff_high_low_rates_full_dispatch_coverage() -> None:
assert excinfo.value.field == "tariff_high_low_rates"
def test_other_fuel_cost_for_18_hour_tariff_uses_18_hour_high_rate() -> None:
# Arrange — SAP 10.2 §12 (PDF p.45) and Appendix F2 (PDF p.63) both
# specify that for the 18-hour tariff "the 18-hour high rate applies
# to all other electricity uses" (pumps, fans, lighting). Table 12a
# Grid 2 only lists 7-hour / 10-hour fractions; for 18-hour the spec
# rule is implicit fraction = 1.0 at the high rate per Appendix F2.
#
# Pre-slice the cascade fell through Grid 2's NotImplementedError
# to `prices.standard_electricity_p_per_kwh` (Table 32 code 30 =
# 13.19 p/kWh), under-counting pumps + lighting cost on every
# 18-hour cert by (13.67 13.19) × kWh. The 41-variant heating-
# systems corpus all lodges `meter_type='18 Hour'`, so this gap is
# cohort-wide.
#
# Spec verbatim, SAP 10.2 §12 (lines 2280-2283):
# "The 18-hour tariff is only for use with electric CPSUs ...
# The low-rate price applies to space and water heating, while
# electricity for all other purposes is at the high-rate price."
#
# Spec verbatim, SAP 10.2 Appendix F2 (lines 3809-3812):
# "F2 Electric CPSUs using 18-hour electricity tariff. The 18-hour
# low rate applies to all space heating and water heating
# provided by the CPSU. ... The 18-hour high rate applies to all
# other electricity uses."
#
# Table 32 code 38 (18-hour high rate) = 13.67 p/kWh =
# 0.1367 £/kWh.
from domain.sap10_calculator.tables.table_12a import Tariff
# Act
rate_18h = _other_fuel_cost_gbp_per_kwh(Tariff.EIGHTEEN_HOUR, SAP_10_2_SPEC_PRICES)
# Assert
assert abs(rate_18h - 0.1367) <= 1e-6, (
f"18-hour tariff other-uses rate {rate_18h:.6f} £/kWh "
f"should equal Table 32 code 38 high rate 0.1367 £/kWh per "
f"SAP 10.2 §12 / Appendix F2"
)
def test_is_off_peak_meter_recognises_bare_18_hour_lodging() -> None:
# Arrange — RdSAP 10 §17 page 85 row 10-2 lodges 18-hour meter
# as the bare "18-hour" or "18 Hour" form (Elmhurst Summary §14.2