"""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.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. 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.sap10_calculator.worksheet.rating`). """ from __future__ import annotations from typing import Final, Optional # SAP 10.3 Table 12 — unit price in pence per kWh. UNIT_PRICE_P_PER_KWH: Final[dict[int, float]] = { # Gas fuels 1: 3.64, # mains gas 2: 6.74, # bulk LPG 3: 9.46, # bottled LPG (main heating) 5: 11.20, # bottled LPG (secondary) 9: 3.64, # LPG SC11F 7: 6.74, # biogas (including anaerobic digestion) # Liquid fuels 4: 4.94, # heating oil 71: 6.79, # bio-liquid HVO 73: 6.79, # bio-liquid FAME 75: 5.49, # B30K 76: 47.0, # bioethanol # Solid fuels 11: 5.58, # house coal 15: 4.19, # anthracite 12: 5.91, # manufactured smokeless fuel 20: 5.12, # wood logs 22: 6.91, # wood pellets (secondary) 23: 6.25, # wood pellets (main) 21: 3.72, # wood chips 10: 4.77, # dual fuel # Electricity 30: 16.49, # standard tariff 32: 19.60, # 7-hour tariff (high rate) 31: 9.40, # 7-hour tariff (low rate / off-peak) 34: 20.54, # 10-hour tariff (high rate) 33: 12.27, # 10-hour tariff (low rate) 38: 17.41, # 18-hour tariff (high rate) 40: 14.17, # 18-hour tariff (low rate) 35: 14.04, # 24-hour heating tariff 60: 5.59, # electricity sold to grid, PV 36: 5.59, # electricity sold to grid, other # 39 "electricity, any tariff" carries N/A unit price — used only to # identify the fuel for a system; cost data comes from a paired # standard / off-peak code. # Heat networks 51: 4.44, 52: 4.44, 53: 4.44, 54: 4.44, 55: 4.44, 56: 4.44, 57: 4.44, 58: 4.44, 41: 4.44, # heat from electric heat pump 42: 4.44, # heat recovered from waste combustion 43: 4.44, # heat from boilers - biomass 44: 4.44, # heat from boilers - biogas 45: 3.11, # high grade heat recovered from process 46: 3.11, # heat recovered from geothermal / natural processes 48: 3.11, # heat from CHP 49: 3.11, # low grade heat recovered from process 50: 0.0, # electricity for pumping in distribution network 47: 3.11, # heat recovered from power station } _DEFAULT_P_PER_KWH: Final[float] = 3.64 # fall back to mains gas # SAP 10.2 Table 12 — annual-average CO2 emission factor in kg CO2- # equivalent per kWh of delivered energy. For ELECTRICITY end-uses, # Table 12d (above) overrides this annual factor with monthly values per # the spec text on p.194; the value here is the legacy fallback when # monthly distribution isn't available. # SAP 10.2 Table 12d (p.194) — monthly variation in CO2 emission factors # for electricity. The spec text: "Where electricity is the fuel used, the # relevant set of factors in the table below should be used to calculate # the monthly CO2 emissions INSTEAD of the annual average factor given in # Table 12." So for ratings, electricity end-uses use Σ(kWh_m × CO2_m) # rather than annual_kwh × annual_factor. CO2_KG_PER_KWH_MONTHLY: Final[dict[int, tuple[float, ...]]] = { # Standard tariff (default electricity) 30: (0.163, 0.160, 0.153, 0.143, 0.132, 0.120, 0.111, 0.112, 0.122, 0.136, 0.151, 0.163), # 7-hour tariff 32: (0.171, 0.168, 0.161, 0.150, 0.138, 0.125, 0.117, 0.118, 0.128, 0.143, 0.158, 0.171), 31: (0.143, 0.141, 0.135, 0.126, 0.116, 0.105, 0.098, 0.099, 0.107, 0.120, 0.133, 0.144), # 10-hour tariff 34: (0.168, 0.165, 0.159, 0.148, 0.136, 0.124, 0.115, 0.116, 0.126, 0.141, 0.156, 0.168), 33: (0.155, 0.153, 0.146, 0.137, 0.126, 0.114, 0.106, 0.107, 0.116, 0.130, 0.144, 0.155), # 18-hour tariff (matches standard tariff) 38: (0.163, 0.160, 0.153, 0.143, 0.132, 0.120, 0.111, 0.112, 0.122, 0.136, 0.151, 0.163), 40: (0.163, 0.160, 0.153, 0.143, 0.132, 0.120, 0.111, 0.112, 0.122, 0.136, 0.151, 0.163), # 24-hour heating tariff 35: (0.163, 0.160, 0.153, 0.143, 0.132, 0.120, 0.111, 0.112, 0.122, 0.136, 0.151, 0.163), # Electricity sold to grid (PV) 60: (0.196, 0.190, 0.175, 0.153, 0.129, 0.106, 0.092, 0.093, 0.110, 0.138, 0.169, 0.197), # Electricity sold to grid, other 36: (0.163, 0.160, 0.153, 0.143, 0.132, 0.120, 0.111, 0.112, 0.122, 0.136, 0.151, 0.163), # Electricity, any tariff 39: (0.163, 0.160, 0.153, 0.143, 0.132, 0.120, 0.111, 0.112, 0.122, 0.136, 0.151, 0.163), # Heat from electric heat pump 41: (0.163, 0.160, 0.153, 0.143, 0.132, 0.120, 0.111, 0.112, 0.122, 0.136, 0.151, 0.163), # Low-grade heat recovered from process 49: (0.163, 0.160, 0.153, 0.143, 0.132, 0.120, 0.111, 0.112, 0.122, 0.136, 0.151, 0.163), # Electricity for pumping in distribution network 50: (0.163, 0.160, 0.153, 0.143, 0.132, 0.120, 0.111, 0.112, 0.122, 0.136, 0.151, 0.163), } def co2_monthly_factors_kg_per_kwh(fuel_code: int | None) -> Optional[tuple[float, ...]]: """SAP 10.2 Table 12d (p.194) monthly CO2 factors for electricity. Returns None for non-electricity fuels (use the annual `co2_factor_kg_per_kwh`).""" if fuel_code is None: return None if fuel_code in CO2_KG_PER_KWH_MONTHLY: return CO2_KG_PER_KWH_MONTHLY[fuel_code] return None # SAP 10.2 Table 12e (p.195) — monthly variation in PE (primary energy) # emission factors for electricity. Spec text: "Where electricity is the # fuel used, the relevant set of factors in the table below should be # used to calculate the monthly primary energy instead the annual average # factor given in Table 12." Same shape as Table 12d (CO2): electricity # end-uses use Σ(kWh_m × PE_m); gas/non-electricity fuels keep the # annual Table 12 PE factor. PE_FACTOR_MONTHLY: Final[dict[int, tuple[float, ...]]] = { # Standard tariff 30: (1.602, 1.593, 1.568, 1.530, 1.487, 1.441, 1.410, 1.413, 1.449, 1.504, 1.558, 1.604), # 7-hour tariff 32: (1.635, 1.626, 1.600, 1.562, 1.518, 1.471, 1.440, 1.443, 1.479, 1.535, 1.591, 1.637), 31: (1.521, 1.512, 1.488, 1.453, 1.411, 1.368, 1.339, 1.342, 1.376, 1.428, 1.480, 1.522), # 10-hour tariff 34: (1.625, 1.615, 1.590, 1.552, 1.507, 1.462, 1.430, 1.433, 1.470, 1.525, 1.580, 1.626), 33: (1.571, 1.561, 1.537, 1.500, 1.457, 1.413, 1.382, 1.386, 1.421, 1.474, 1.528, 1.572), # 18-hour tariff (matches standard tariff) 38: (1.602, 1.593, 1.568, 1.530, 1.487, 1.441, 1.410, 1.413, 1.449, 1.504, 1.558, 1.604), 40: (1.602, 1.593, 1.568, 1.530, 1.487, 1.441, 1.410, 1.413, 1.449, 1.504, 1.558, 1.604), # 24-hour heating tariff 35: (1.602, 1.593, 1.568, 1.530, 1.487, 1.441, 1.410, 1.413, 1.449, 1.504, 1.558, 1.604), # Electricity sold to grid (PV) — note (i): deducted, low PE factor 60: (0.715, 0.697, 0.645, 0.567, 0.478, 0.389, 0.330, 0.336, 0.405, 0.513, 0.623, 0.718), # Electricity sold to grid, other 36: (0.602, 0.593, 0.568, 0.530, 0.487, 0.441, 0.410, 0.413, 0.449, 0.504, 0.558, 0.604), # Electricity, any tariff 39: (1.602, 1.593, 1.568, 1.530, 1.487, 1.441, 1.410, 1.413, 1.449, 1.504, 1.558, 1.604), # Heat from electric heat pump 41: (1.602, 1.593, 1.568, 1.530, 1.487, 1.441, 1.410, 1.413, 1.449, 1.504, 1.558, 1.604), # Low-grade heat recovered from process 49: (1.602, 1.593, 1.568, 1.530, 1.487, 1.441, 1.410, 1.413, 1.449, 1.504, 1.558, 1.604), # Electricity for pumping in distribution network 50: (1.602, 1.593, 1.568, 1.530, 1.487, 1.441, 1.410, 1.413, 1.449, 1.504, 1.558, 1.604), } def pe_monthly_factors_kwh_per_kwh( fuel_code: int | None, ) -> Optional[tuple[float, ...]]: """SAP 10.2 Table 12e (p.195) monthly PE factors for electricity. Returns None for non-electricity fuels (use the annual `primary_energy_factor`).""" if fuel_code is None: return None if fuel_code in PE_FACTOR_MONTHLY: return PE_FACTOR_MONTHLY[fuel_code] return None CO2_KG_PER_KWH: Final[dict[int, float]] = { # Gas fuels 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.036, 73: 0.018, 75: 0.214, 76: 0.105, # Solid fuels 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.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.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.210 # mains gas baseline # Gov EPC API main_fuel_type → SAP 10.3 Table 12 fuel code. Lifted from # the SAP 10.2 mapper (`domain.sap10_ml.sap_efficiencies._API_TO_TABLE32`) — # the API enum and Table 32/12 codes are unchanged across spec versions. API_FUEL_TO_TABLE_12: Final[dict[int, int]] = { 0: 30, 1: 1, 2: 2, 3: 3, 4: 4, 5: 15, 6: 20, 7: 23, 8: 21, 9: 10, 10: 30, 11: 42, 12: 43, 13: 44, 14: 11, 15: 12, 16: 22, 17: 9, 18: 75, 19: 76, 20: 51, 21: 52, 22: 53, 23: 55, 24: 54, 25: 41, 26: 1, 27: 2, 28: 4, 29: 30, } def unit_price_p_per_kwh(fuel_code: int | None) -> float: """Unit price (p/kWh) for the given fuel code. Accepts either a Table 12 code or a gov API main_fuel_type / water_heating_fuel enum; translates the latter via `API_FUEL_TO_TABLE_12`. Unknown → mains gas (3.64 p/kWh).""" if fuel_code is None: return _DEFAULT_P_PER_KWH if fuel_code in UNIT_PRICE_P_PER_KWH: return UNIT_PRICE_P_PER_KWH[fuel_code] translated = API_FUEL_TO_TABLE_12.get(fuel_code) if translated is not None and translated in UNIT_PRICE_P_PER_KWH: return UNIT_PRICE_P_PER_KWH[translated] return _DEFAULT_P_PER_KWH # SAP 10.2 Table 12 "Primary energy factor" column. The cert's # `energy_consumption_current` field (PEUI) is delivered energy times # this factor per fuel, summed across end-uses, divided by TFA. PRIMARY_ENERGY_FACTOR: Final[dict[int, float]] = { # Gas 1: 1.130, 2: 1.141, 3: 1.141, 5: 1.133, 9: 1.163, 7: 1.286, # Liquid 4: 1.180, 71: 1.180, 73: 1.180, 75: 1.136, 76: 1.472, # Solid 11: 1.064, 15: 1.064, 12: 1.261, 20: 1.046, 22: 1.325, 23: 1.325, 21: 1.046, 10: 1.049, # Electricity — all grid tariffs same PEF. 30: 1.501, 31: 1.501, 32: 1.501, 33: 1.501, 34: 1.501, 35: 1.501, 38: 1.501, 40: 1.501, 39: 1.501, 60: 0.501, 36: 0.501, # Heat networks (sample — main values; less common) 51: 1.130, 52: 1.141, 53: 1.180, 54: 1.064, 55: 1.180, 56: 1.180, 57: 1.180, 58: 1.180, 41: 1.501, 42: 0.063, 43: 1.037, 44: 1.286, 45: 0.051, 46: 0.051, 47: 0.063, 48: 1.501, 49: 1.501, 50: 0.0, } _DEFAULT_PEF: Final[float] = 1.130 # mains gas baseline def primary_energy_factor(fuel_code: int | None) -> float: """Primary energy factor for the given fuel code, accepting either Table 12 code or gov API enum (translated). Unknown → mains gas (1.13).""" if fuel_code is None: return _DEFAULT_PEF if fuel_code in PRIMARY_ENERGY_FACTOR: return PRIMARY_ENERGY_FACTOR[fuel_code] translated = API_FUEL_TO_TABLE_12.get(fuel_code) if translated is not None and translated in PRIMARY_ENERGY_FACTOR: return PRIMARY_ENERGY_FACTOR[translated] return _DEFAULT_PEF def co2_factor_kg_per_kwh(fuel_code: int | None) -> float: """CO2 emission factor (kg CO2e/kWh) for the given fuel code, with the same accept-either-API-or-Table-12-code translation as `unit_price_p_per_kwh`. Unknown → mains gas (0.214).""" if fuel_code is None: return _DEFAULT_CO2_KG_PER_KWH if fuel_code in CO2_KG_PER_KWH: return CO2_KG_PER_KWH[fuel_code] translated = API_FUEL_TO_TABLE_12.get(fuel_code) if translated is not None and translated in CO2_KG_PER_KWH: return CO2_KG_PER_KWH[translated] return _DEFAULT_CO2_KG_PER_KWH