P2.4: correct table_12 CO2 factors to SAP 10.2 (14-03-2025); P2 complete

ADR-0010 §1: the file was a SAP 10.2 prices + SAP 10.3 CO2 hybrid,
incorrectly labelled "SAP 10.3" throughout. Realigns the CO2 column
to SAP 10.2 PDF page 189 — the table the calculator's Validation
Cohort certs were emitted against.

CO2 corrections (kg CO2e per kWh delivered):
  - Mains gas:               0.214 → 0.210
  - LPG (2, 3, 5, 9):        0.24  → 0.241 (precision restore)
  - Biogas (7):              0.029 → 0.024
  - HVO (71):                0.041 → 0.036
  - FAME (73):               0.058 → 0.018
  - B30K (75):               0.226 → 0.214
  - Bioethanol (76):         0.072 → 0.105
  - Coal / anthracite (11, 15): 0.398 → 0.395
  - Smokeless (12):          0.398 → 0.366
  - Wood logs (20):          0.023 → 0.028
  - Wood pellets (22, 23):   0.048 → 0.053
  - Wood chips (21):         0.018 → 0.023
  - Dual fuel (10):          0.084 → 0.087
  - Standard electricity (all grid tariffs):
                             0.086 → 0.136 (biggest swing — the
                             annual-average factor changes between
                             SAP 10.2 and 10.3 by -37%)
  - Heat-network variants realigned to match their parent fuels
  - _DEFAULT_CO2_KG_PER_KWH: 0.214 → 0.210

Header docstring rewritten:
  - Re-labelled "SAP 10.2 (14-03-2025 amendment)"
  - Dropped the misleading "+25% shift from SAP 10.2" block — those
    13.19 → 16.49 figures were SAP 10.1 → SAP 10.2, not 10.2 → 10.3
  - Notes the SAP 10.3 re-pointing trigger (corpus migration)

New test file packages/domain/src/domain/sap/tests/test_table_12.py
locks SAP 10.2 values for mains gas, standard electricity, 7h low,
24h heating, bulk LPG, heating oil, default, plus sanity checks
on the unchanged unit price + PE factor columns.

All 161 SAP + ml_training_data tests pass. CO2 corrections don't
affect SAP score (cost-driven) or PEUI (PEF-driven), so golden
fixtures and probe pinned values remain green.

P2 complete:
  P2.1 (ac1aa56a) — probe swap to spec prices
  P2.2 (28e9dd38) — golden fixtures migrated to loose smoke test
  P2.3 (cd6ac9b1) — cert-cal file deleted
  P2.4 (this)     — CO2 factors corrected

Next: P1 (parquet re-extract with inspection_date) + P3 (Validation
Cohort filter) unblock the cohort-clean probe baseline.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Khalim Conn-Kowlessar 2026-05-19 10:10:04 +00:00
parent cd6ac9b16d
commit 62289ec6f6
2 changed files with 112 additions and 29 deletions

View file

@ -1,17 +1,17 @@
"""SAP 10.3 Table 12 — fuel prices, CO2 emission factors, primary energy
factors.
"""SAP 10.2 (14-03-2025 amendment) Table 12 — fuel prices, CO2 emission
factors, primary energy factors.
Sourced verbatim from BRE, *The Government's Standard Assessment
Procedure for Energy Rating of Dwellings, SAP 10.3* (13-01-2026), page
190 (Table 12). Keys are the SAP 10.2/10.3 fuel code numbers they
remained stable across the 10.2 10.3 jump, only the values changed.
Procedure for Energy Rating of Dwellings, SAP 10.2* (14-03-2025), page
189 (Table 12). Keys are the SAP 10.2/10.3 fuel code numbers they
remained stable across the 10.2 10.3 jump.
Notable shifts from SAP 10.2 (used by `domain.ml.sap_efficiencies`):
- Standard electricity: 13.19 16.49 p/kWh (+25%)
- 7h low (off-peak): 5.50 9.40 p/kWh (+71%)
- 24h heating: 6.61 14.04 p/kWh (+112%)
- Mains gas: 3.48 3.64 p/kWh (+5%)
- Grid electricity CO2: 0.136 0.086 kg/kWh (-37%)
The calculator targets SAP 10.2 per ADR-0010 because no SAP-10.3-lodged
certs exist in the corpus to validate against. SAP 10.3 differs from
SAP 10.2 mainly on CO2 factors (grid electricity 0.136 0.086 kg/kWh,
37%; mains gas 0.210 0.214 kg/kWh, +2%); prices and primary energy
factors are largely unchanged. When the corpus migrates to SAP 10.3
this module re-points to those values.
The Energy Cost Deflator stays at 0.36 (used in ECF see
`domain.sap.worksheet.rating`).
@ -77,33 +77,34 @@ UNIT_PRICE_P_PER_KWH: Final[dict[int, float]] = {
_DEFAULT_P_PER_KWH: Final[float] = 3.64 # fall back to mains gas
# SAP 10.3 Table 12 — CO2 emission factor in kg CO2-equivalent per kWh
# of delivered energy. Grid electricity uses the annual-average 0.086;
# the monthly factors in Table 12d are for comparison only per note (s).
# SAP 10.2 Table 12 — CO2 emission factor in kg CO2-equivalent per kWh
# of delivered energy. Grid electricity uses the annual-average 0.136
# kg/kWh; the monthly factors in Table 12d are for comparison only per
# note (s).
CO2_KG_PER_KWH: Final[dict[int, float]] = {
# Gas fuels
1: 0.214,
2: 0.24, 3: 0.24, 5: 0.24, 9: 0.24,
7: 0.029,
1: 0.210,
2: 0.241, 3: 0.241, 5: 0.241, 9: 0.241,
7: 0.024,
# Liquid fuels
4: 0.298,
71: 0.041, 73: 0.058,
75: 0.226, 76: 0.072,
71: 0.036, 73: 0.018,
75: 0.214, 76: 0.105,
# Solid fuels
11: 0.398, 15: 0.398, 12: 0.398,
20: 0.023, 22: 0.048, 23: 0.048, 21: 0.018,
10: 0.084,
11: 0.395, 15: 0.395, 12: 0.366,
20: 0.028, 22: 0.053, 23: 0.053, 21: 0.023,
10: 0.087,
# Electricity — all grid tariffs use the same annual-average CO2 factor.
30: 0.086, 31: 0.086, 32: 0.086, 33: 0.086, 34: 0.086, 35: 0.086,
38: 0.086, 40: 0.086, 39: 0.086, 60: 0.086, 36: 0.086,
30: 0.136, 31: 0.136, 32: 0.136, 33: 0.136, 34: 0.136, 35: 0.136,
38: 0.136, 40: 0.136, 39: 0.136, 60: 0.136, 36: 0.136,
# Heat networks
51: 0.214, 52: 0.24, 53: 0.298, 54: 0.398, 55: 0.298,
56: 0.298, 57: 0.041, 58: 0.058,
41: 0.086, 42: 0.010, 43: 0.029, 44: 0.029,
45: 0.007, 46: 0.007, 47: 0.010, 48: 0.086, 49: 0.086,
51: 0.210, 52: 0.241, 53: 0.298, 54: 0.375, 55: 0.269,
56: 0.298, 57: 0.036, 58: 0.018,
41: 0.136, 42: 0.015, 43: 0.029, 44: 0.024,
45: 0.015, 46: 0.011, 47: 0.011, 48: 0.136, 49: 0.136,
50: 0.0,
}
_DEFAULT_CO2_KG_PER_KWH: Final[float] = 0.214 # mains gas baseline
_DEFAULT_CO2_KG_PER_KWH: Final[float] = 0.210 # mains gas baseline
# Gov EPC API main_fuel_type → SAP 10.3 Table 12 fuel code. Lifted from

View file

@ -0,0 +1,82 @@
"""SAP 10.2 (14-03-2025 amendment) Table 12 value-correctness tests.
Locks the CO2 emission factors and primary energy factors against the
published SAP 10.2 specification at
`docs/sap-spec/sap-10-2-full-specification-2025-03-14.pdf`, page 189.
The price column (`UNIT_PRICE_P_PER_KWH`) was already SAP 10.2-correct
when the calculator code was authored; the CO2 column was authored
against SAP 10.3 (13-01-2026) values by mistake. ADR-0010 retargets the
calculator to SAP 10.2 (14-03-2025) until the corpus migrates, so the
CO2 column was corrected during P2.4. These tests lock the corrected
values.
"""
from __future__ import annotations
import pytest
from domain.sap.tables.table_12 import (
co2_factor_kg_per_kwh,
primary_energy_factor,
unit_price_p_per_kwh,
)
@pytest.mark.parametrize(
"fuel_code, expected_co2_kg_per_kwh, fuel_name",
[
# Most-common cases first — gas + electricity dominate the corpus.
(1, 0.210, "mains gas"),
(30, 0.136, "standard tariff electricity"),
# Sanity: heating oil is unchanged between SAP 10.2 and SAP 10.3.
(4, 0.298, "heating oil"),
# Off-peak electricity tariffs all share the annual-average factor.
(31, 0.136, "7-hour low rate electricity"),
(35, 0.136, "24-hour heating tariff"),
# Bulk LPG — SAP 10.2 says 0.241 (file had 0.24 rounded).
(2, 0.241, "bulk LPG"),
],
)
def test_co2_factor_matches_sap_10_2_table_12(
fuel_code: int, expected_co2_kg_per_kwh: float, fuel_name: str
) -> None:
# Arrange — table_12.co2_factor_kg_per_kwh is the only CO2 source
# in the calculator pipeline (see cert_to_inputs._co2_factor_kg_per_kwh).
# Act
actual = co2_factor_kg_per_kwh(fuel_code)
# Assert
assert actual == pytest.approx(expected_co2_kg_per_kwh, abs=1e-6), (
f"{fuel_name} (code {fuel_code}): expected SAP 10.2 CO2 factor "
f"{expected_co2_kg_per_kwh}, got {actual}. See SAP 10.2 PDF p.189."
)
def test_default_co2_factor_is_mains_gas_baseline() -> None:
# Arrange — unknown fuel codes fall back to mains gas (the SAP 10.2
# convention; see table_12._DEFAULT_CO2_KG_PER_KWH).
# Act
actual = co2_factor_kg_per_kwh(None)
# Assert
assert actual == pytest.approx(0.210, abs=1e-6)
def test_mains_gas_unit_price_unchanged_at_sap_10_2_value() -> None:
# Arrange — sanity: prices were already SAP 10.2-correct before P2.4.
# This locks that we didn't accidentally regress them while fixing CO2.
# Act
actual = unit_price_p_per_kwh(1)
# Assert
assert actual == pytest.approx(3.64, abs=1e-6)
def test_standard_electricity_primary_energy_factor_unchanged() -> None:
# Arrange — sanity: PE factor for electricity is 1.501 in both SAP
# 10.2 and SAP 10.3; locks that P2.4 didn't touch the PEF column.
# Act
actual = primary_energy_factor(30)
# Assert
assert actual == pytest.approx(1.501, abs=1e-6)