diff --git a/packages/domain/src/domain/sap/tables/table_12.py b/packages/domain/src/domain/sap/tables/table_12.py index 32d5acb1..f4f72fb1 100644 --- a/packages/domain/src/domain/sap/tables/table_12.py +++ b/packages/domain/src/domain/sap/tables/table_12.py @@ -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 diff --git a/packages/domain/src/domain/sap/tests/test_table_12.py b/packages/domain/src/domain/sap/tests/test_table_12.py new file mode 100644 index 00000000..5f133c5a --- /dev/null +++ b/packages/domain/src/domain/sap/tests/test_table_12.py @@ -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)