Slice S0380.131: flip Table 32 heating-oil price 7.64 → 5.44 (empirical)

The published RdSAP 10 Specification 10-06-2025 PDF Table 32 (p.95)
lists heating oil at 7.64 p/kWh. Two independent operational sources
both use 5.44 p/kWh for the same fuel:

  - Elmhurst P960 worksheets across all five oil-fired variants in
    `sap worksheets/heating systems examples/` (oil 1, oil pcdb 1/2/3,
    pcdb 1) lodge 5.4400 p/kWh on (240) "Space heating - main system 1"
    and (247) "Water heating (other fuel)" for every "FuelType: Heating
    oil" worksheet.
  - The gov.uk EPC register's lodging software back-solves to ~5.48
    p/kWh from cert 0240-0200-5706-2365-8010's lodged SAP 73 (oil + PV
    detached, age J). With heating-oil at 5.44 in the cascade this cert
    closes to ΔSAP = 0 exactly against its lodged value.

The BRE technical papers (`docs/specs/sap10 technical papers/`) carry
no Table 32 errata or fuel-price update, so the change is grounded in
empirical cross-source evidence rather than a spec citation — the
worksheet PDF is the source of truth per the project convention.

Post-flip residuals:

  Heating-systems corpus (cascade − worksheet ΔSAP_c):
    oil 1        −9.7030 → +2.6578
    oil pcdb 1  −11.6343 → +0.4239  ← within 1 SAP of closure
    oil pcdb 2  −11.6343 → +0.4239
    oil pcdb 3  −10.8674 → +1.1597
    pcdb 1       −9.4083 → +6.9521  ← largest remaining oil-cohort gap

  Golden fixtures (cascade − lodged SAP):
    0240-0200-5706-2365-8010  resid −10 → +0  ← EXACT closure
    0390-2954-3640-2196-4175  resid  −6 → +7  ← oil-price bug was
                                                 masking +13 SAP of
                                                 opposite-direction
                                                 cascade gaps; now
                                                 exposed for follow-up

PE / CO2 residuals are unaffected by the unit-price flip (cost-only
change). The 41-variant corpus regression guard (S0380.129) holds; all
other golden cohorts pass unchanged. Extended handover suite: 874 pass.

Bio-FAME (code 73) shows the inverse divergence on oil 3/4 worksheets
(worksheet 7.64 vs spec 5.44 — possible row-swap typo in the spec PDF)
but flipping it has no measurable cascade effect today, so deferred
until a cert that exercises it surfaces.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Khalim Conn-Kowlessar 2026-05-31 09:21:29 +00:00 committed by Jun-te Kim
parent 2339f8bac3
commit 65e2025f96
3 changed files with 51 additions and 14 deletions

View file

@ -76,6 +76,12 @@ class _CorpusExpectation:
# fixtures cascade-execute; the residuals below are the current
# cascade-vs-worksheet diff per variant. Closures land by re-pinning
# the smaller expected residual.
#
# Slice S0380.131 re-pinned the 5 heating-oil variants (oil 1, oil pcdb
# 1/2/3, pcdb 1) after `tables/table_32.py` flipped the heating-oil unit
# price from RdSAP 10 Table 32's published 7.64 p/kWh to the Elmhurst-
# worksheet-canonical 5.44 p/kWh. Worst-residual oil ΔSAP 11.63 → +0.42;
# pcdb 1 9.41 → +6.95 (largest remaining oil-cohort gap).
_EXPECTATIONS: tuple[_CorpusExpectation, ...] = (
_CorpusExpectation(variant='ashp', block='11a', expected_sap_resid=+5.6680, expected_cost_resid_gbp=-130.5995, expected_co2_resid_kg=-1.4283, expected_pe_resid_kwh=+1467.8983),
_CorpusExpectation(variant='community heating 1', block='11b', expected_sap_resid=+4.1830, expected_cost_resid_gbp=-96.3816, expected_co2_resid_kg=-786.6453, expected_pe_resid_kwh=-940.7364),
@ -97,16 +103,16 @@ _EXPECTATIONS: tuple[_CorpusExpectation, ...] = (
_CorpusExpectation(variant='electric 9', block='11a', expected_sap_resid=+12.0340, expected_cost_resid_gbp=-277.2813, expected_co2_resid_kg=-255.6076, expected_pe_resid_kwh=+362.4518),
_CorpusExpectation(variant='gshp', block='11a', expected_sap_resid=+5.1598, expected_cost_resid_gbp=-118.8901, expected_co2_resid_kg=-41.4461, expected_pe_resid_kwh=+639.1890),
_CorpusExpectation(variant='no system', block='11a', expected_sap_resid=+21.9350, expected_cost_resid_gbp=-505.4134, expected_co2_resid_kg=+689.2188, expected_pe_resid_kwh=-2454.8193),
_CorpusExpectation(variant='oil 1', block='11a', expected_sap_resid=-9.7030, expected_cost_resid_gbp=+223.5710, expected_co2_resid_kg=-242.2677, expected_pe_resid_kwh=+1259.6587),
_CorpusExpectation(variant='oil 1', block='11a', expected_sap_resid=+2.6578, expected_cost_resid_gbp=-61.2402, expected_co2_resid_kg=-242.2677, expected_pe_resid_kwh=+1259.6587),
_CorpusExpectation(variant='oil 2', block='11a', expected_sap_resid=+26.0712, expected_cost_resid_gbp=-600.7179, expected_co2_resid_kg=+2230.1071, expected_pe_resid_kwh=+801.2920),
_CorpusExpectation(variant='oil 3', block='11a', expected_sap_resid=+30.9500, expected_cost_resid_gbp=-712.1785, expected_co2_resid_kg=+2859.5796, expected_pe_resid_kwh=+738.4592),
_CorpusExpectation(variant='oil 4', block='11a', expected_sap_resid=+28.5927, expected_cost_resid_gbp=-655.6129, expected_co2_resid_kg=+2636.9526, expected_pe_resid_kwh=+701.8340),
_CorpusExpectation(variant='oil 5', block='11a', expected_sap_resid=+120.7457, expected_cost_resid_gbp=-6312.0020, expected_co2_resid_kg=+1345.3630, expected_pe_resid_kwh=-2780.6222),
_CorpusExpectation(variant='oil 6', block='11a', expected_sap_resid=+24.4087, expected_cost_resid_gbp=-561.8886, expected_co2_resid_kg=-658.8928, expected_pe_resid_kwh=-478.5733),
_CorpusExpectation(variant='oil pcdb 1', block='11a', expected_sap_resid=-11.6343, expected_cost_resid_gbp=+268.0722, expected_co2_resid_kg=-35.9551, expected_pe_resid_kwh=+2086.7505),
_CorpusExpectation(variant='oil pcdb 2', block='11a', expected_sap_resid=-11.6343, expected_cost_resid_gbp=+268.0722, expected_co2_resid_kg=-35.9551, expected_pe_resid_kwh=+2086.7505),
_CorpusExpectation(variant='oil pcdb 3', block='11a', expected_sap_resid=-10.8674, expected_cost_resid_gbp=+250.4014, expected_co2_resid_kg=-53.1709, expected_pe_resid_kwh=+1897.4341),
_CorpusExpectation(variant='pcdb 1', block='11a', expected_sap_resid=-9.4083, expected_cost_resid_gbp=+228.9812, expected_co2_resid_kg=-845.8065, expected_pe_resid_kwh=-171.6971),
_CorpusExpectation(variant='oil pcdb 1', block='11a', expected_sap_resid=+0.4239, expected_cost_resid_gbp=-9.7668, expected_co2_resid_kg=-35.9551, expected_pe_resid_kwh=+2086.7505),
_CorpusExpectation(variant='oil pcdb 2', block='11a', expected_sap_resid=+0.4239, expected_cost_resid_gbp=-9.7668, expected_co2_resid_kg=-35.9551, expected_pe_resid_kwh=+2086.7505),
_CorpusExpectation(variant='oil pcdb 3', block='11a', expected_sap_resid=+1.1597, expected_cost_resid_gbp=-26.7204, expected_co2_resid_kg=-53.1709, expected_pe_resid_kwh=+1897.4341),
_CorpusExpectation(variant='pcdb 1', block='11a', expected_sap_resid=+6.9521, expected_cost_resid_gbp=-157.6055, expected_co2_resid_kg=-845.8065, expected_pe_resid_kwh=-171.6971),
_CorpusExpectation(variant='pcdb 3', block='11a', expected_sap_resid=+27.7563, expected_cost_resid_gbp=-637.0435, expected_co2_resid_kg=-446.3815, expected_pe_resid_kwh=+2097.4553),
_CorpusExpectation(variant='solid fuel 10', block='11a', expected_sap_resid=+14.7769, expected_cost_resid_gbp=-340.4814, expected_co2_resid_kg=+1906.2620, expected_pe_resid_kwh=-584.5284),
_CorpusExpectation(variant='solid fuel 11', block='11a', expected_sap_resid=+8.4098, expected_cost_resid_gbp=-193.7739, expected_co2_resid_kg=+2262.3481, expected_pe_resid_kwh=+2583.7764),

View file

@ -74,7 +74,7 @@ _EXPECTATIONS: tuple[_GoldenExpectation, ...] = (
_GoldenExpectation(
cert_number="0240-0200-5706-2365-8010",
actual_sap=73,
expected_sap_resid=-10,
expected_sap_resid=+0,
expected_pe_resid_kwh_per_m2=+0.0542,
expected_co2_resid_tonnes_per_yr=+0.0626,
notes=(
@ -88,9 +88,11 @@ _EXPECTATIONS: tuple[_GoldenExpectation, ...] = (
"(total HLC 221.6 W/K). Slice 97 added glazing_type=2 "
"(RdSAP 10 Table 24 DG England/Wales 2002+, U=2.0, g=0.72) — "
"PE residual +17.85 → +15.69 and CO2 +1.01 → +0.90. SAP "
"residual still -15: the residual sits in subsystems other than "
"the windows lookup (PV cascade, RR description-implied "
"insulation nuance, possibly oil-tariff secondary)."
"residual was -10 throughout the Slice 97..130 range; "
"Slice S0380.131 flipped table_32.py heating-oil price 7.64 → "
"5.44 per Elmhurst worksheet evidence + this cert's gov.uk "
"back-solve, closing SAP residual -10 → +0 exactly. PE / CO2 "
"residuals are unaffected by the unit-price flip."
),
),
_GoldenExpectation(
@ -124,7 +126,7 @@ _EXPECTATIONS: tuple[_GoldenExpectation, ...] = (
_GoldenExpectation(
cert_number="0390-2954-3640-2196-4175",
actual_sap=60,
expected_sap_resid=-6,
expected_sap_resid=+7,
expected_pe_resid_kwh_per_m2=-26.3749,
expected_co2_resid_tonnes_per_yr=-2.5544,
notes=(
@ -136,9 +138,14 @@ _EXPECTATIONS: tuple[_GoldenExpectation, ...] = (
"(_separately_timed_dhw=True when cylinder lodged per "
"SAP 10.2 Table 2b note b) + RdSAP §3 default) closed a "
"10% storage-loss over-count via TF 0.60 → 0.54, lifting SAP "
"53 → 54 (resid -7 → -6). Remaining -6 traces to fabric heat-"
"loss / oil-fuel cost cascade (oil tariff + age-F masonry on "
"a 360 m² detached typically lands -5..-10 SAP)."
"53 → 54 (resid -7 → -6). Slice S0380.131 flipped heating-oil "
"tariff 7.64 → 5.44 (cert 0240 closed exactly), exposing this "
"cert's previously-masked +13 SAP of cascade gaps: residual "
"swung -6 → +7. The oil-price bug was netting against an "
"opposite-direction gap (cert lodges age F + 360 m² detached "
"+ Firebird PCDF — likely fabric or hot-water cascade). PE / "
"CO2 residuals unchanged by the unit-price flip; remaining "
"SAP residual is a follow-up slice candidate."
),
),
_GoldenExpectation(

View file

@ -8,6 +8,9 @@ targets RdSAP10 cost per ADR-0010 amendment.
CO2 emission factors and primary energy factors are unchanged from
SAP10.2 Table 12 (RdSAP10 §19.2), so they continue to live in
`domain.sap10_calculator.tables.table_12` rather than being duplicated here.
Heating-oil (code 4) is a documented divergence from the published spec
PDF see the note on the dict entry below.
"""
from __future__ import annotations
@ -31,7 +34,28 @@ UNIT_PRICE_P_PER_KWH: Final[dict[int, float]] = {
9: 3.48, # LPG SC11F
7: 7.60, # biogas (including anaerobic digestion)
# Liquid fuels
4: 7.64, # heating oil
#
# Slice S0380.131 — heating oil (code 4): operationally-canonical
# 5.44 p/kWh, not the 7.64 published in the RdSAP 10 Specification
# 10-06-2025 PDF Table 32 (p.95). The spec PDF value is the outlier;
# two independent implementations agree on 5.44:
# - Elmhurst P960 worksheets (fuel cost row, line ref (240) "Space
# heating - main system 1") for variants oil 1, oil pcdb 1/2/3,
# pcdb 1 in `sap worksheets/heating systems examples/` — every
# "FuelType: Heating oil" worksheet lodges 5.4400 p/kWh.
# - The gov.uk EPC register's lodging software back-solves to
# ~5.48 p/kWh from cert 0240-0200-5706-2365-8010's lodged SAP
# 73 (an oil + PV detached at age J), and with 5.44 in the
# cascade this cert closes to ΔSAP = 0 exactly against its
# lodged value.
# BRE technical papers (`docs/specs/sap10 technical papers/`) carry
# no Table 32 errata or fuel-price update, so the change is grounded
# in empirical cross-source evidence rather than a spec citation.
# FAME (code 73) shows the inverse pattern on oil 3/4 worksheets
# (worksheet 7.64 vs spec 5.44) but flipping it has no measurable
# cascade effect today — deferred until a cert that exercises it
# surfaces.
4: 5.44, # heating oil — see comment above (Slice S0380.131)
71: 7.64, # bio-liquid HVO
73: 5.44, # bio-liquid FAME
75: 6.10, # B30K