Model/domain/property_baseline/sap_fuel.py
Khalim Conn-Kowlessar 5f65b9be62 feat(baseline): SAP fuel-code -> Fuel mapping for billing (ADR-0014)
Slice 3 of Bill Derivation. sap_code_to_fuel(code) maps a SAP 10.2 / Table 32
fuel code to the canonical billing Fuel — bounded to the ~47 Table 32 codes (the
carrier, orthogonal to the PCDB product index, so all PCDB heat pumps share one
electricity code). Mains gas / LPG / oil+bioliquids / coal / smokeless / wood /
electricity (standard + off-peak) / heat-network groupings; an unmapped code
(dual fuel, grid-export) raises UnmappedSapCode rather than guessing.

Also: ADR-0014 deferred/TODO section records the stubbed appliances+cooking
(pending the SapResult fields), the off-peak day/night split, the heat-network
rate gap, and regional rates / ETL.

The SapResult -> EnergyBreakdown adapter (next slice) is gated on the
appliances/cooking fields landing on SapResult.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-02 09:50:10 +00:00

41 lines
1.9 KiB
Python

from __future__ import annotations
from typing import Final
from domain.fuel_rates.fuel import Fuel
from domain.sap10_calculator.exceptions import UnmappedSapCode
# SAP 10.2 / Table 32 fuel code -> canonical billing Fuel (ADR-0014). Bounded to
# the ~47 Table 32 fuel codes (the keys of `table_12.UNIT_PRICE_P_PER_KWH`) — the
# carrier, NOT the PCDB product, so a thousand PCDB heat pumps all share one code.
# Input is a normalised Table 32 fuel code (the calculator sets `main_fuel_type`
# to Table 32 codes); an unmapped code raises `UnmappedSapCode` rather than
# guessing — a bounded, self-surfacing backlog [[reference-unmapped-sap-code]].
_CODE_TO_FUEL: Final[dict[int, Fuel]] = {
**dict.fromkeys([1, 7], Fuel.MAINS_GAS), # mains gas, grid biogas
**dict.fromkeys([2, 3, 5, 9], Fuel.LPG),
**dict.fromkeys([4, 71, 73, 75, 76], Fuel.OIL), # heating oil + bio-liquids
**dict.fromkeys([11, 15], Fuel.COAL), # house coal, anthracite
**dict.fromkeys([12], Fuel.SMOKELESS),
**dict.fromkeys([20, 21], Fuel.WOOD_LOGS), # logs, chips
**dict.fromkeys([22, 23], Fuel.WOOD_PELLETS),
**dict.fromkeys([30], Fuel.ELECTRICITY), # standard tariff
# 7/10/18-hour off-peak tariffs + 24-hour heating tariff — priced once the
# off-peak day/night slice lands; ELECTRICITY_OFF_PEAK is unpriced until then.
**dict.fromkeys([31, 32, 33, 34, 35, 38, 40], Fuel.ELECTRICITY_OFF_PEAK),
# "heat from ..." community/heat-network + distribution codes (41-58).
**dict.fromkeys(range(41, 59), Fuel.HEAT_NETWORK),
}
def sap_code_to_fuel(code: int) -> Fuel:
"""Map a SAP 10.2 / Table 32 fuel code to its canonical billing Fuel.
Raises ``UnmappedSapCode`` on a code with no single billing carrier — e.g.
dual fuel (10) or the grid-export codes (36/60), which are not an end use's
input fuel.
"""
fuel = _CODE_TO_FUEL.get(code)
if fuel is None:
raise UnmappedSapCode("fuel_code", code)
return fuel