mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-07-12 13:29:04 +00:00
Bill / EnergyBreakdown / BillDerivation / sap_fuel were under domain/property_baseline/ only because Baseline was built first. The Modelling stage now needs them too, so move them (and their tests) to a neutral domain/billing/ — Fuel/FuelRates already live in the shared domain/fuel_rates/. Avoids a modelling -> property_baseline cross-stage import and a package name that wrongly implies ownership (ADR-0011, ADR-0014 amendment). Pure git mv + import rewrite across 10 files; 40 billing/baseline/repo tests pass, pyright strict clean. CONTEXT.md Bill Derivation location updated. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
54 lines
2.8 KiB
Python
54 lines
2.8 KiB
Python
from __future__ import annotations
|
|
|
|
from typing import Final
|
|
|
|
from domain.fuel_rates.fuel import Fuel
|
|
from domain.sap10_calculator.exceptions import UnmappedSapCode
|
|
from domain.sap10_calculator.tables.table_32 import to_table_32_code
|
|
|
|
# Table 32 fuel code -> canonical billing Fuel (ADR-0014). Bounded to the ~47
|
|
# Table 32 fuel codes (the keys of `UNIT_PRICE_P_PER_KWH`) — the carrier, NOT the
|
|
# PCDB product, so a thousand PCDB heat pumps all share one code. 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 one of the calculator's per-end-use fuel codes to its billing Fuel.
|
|
|
|
The code may be a raw gov-API `main_fuel_type` enum or an already-Table-32
|
|
code depending on the source mapper (until [[adr-0015]] normalizes the cert),
|
|
so it is first run through the calculator's own ``to_table_32_code`` —
|
|
T32-first, then API-translate — the **same** normalization the calculator's
|
|
pricing/CO2 helpers use, so the bill's carrier matches what the calculator
|
|
billed. The normalized Table-32 code is then dispatched to a 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.
|
|
"""
|
|
# Normalize to a Table-32 code; fall back to the raw code for billing fuels
|
|
# the price table does not carry (the 41-58 heat-network range — `to_table_32_
|
|
# code` returns None there, but they still resolve to HEAT_NETWORK and so to
|
|
# UnpricedFuel, which is stricter — and correct — than the calculator's
|
|
# lossy default-to-mains-gas for an unpriced code).
|
|
normalized = to_table_32_code(code)
|
|
fuel = _CODE_TO_FUEL.get(normalized if normalized is not None else code)
|
|
if fuel is None:
|
|
raise UnmappedSapCode("fuel_code", code)
|
|
return fuel
|