mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-30 13:10:47 +00:00
fix(fuel): raise UnpricedFuelCode for unrecognised fuels instead of silently defaulting to mains gas
`table_32.unit_price_p_per_kwh` silently returned the mains-gas default (3.48 p/kWh) for any fuel code it could not resolve to a Table 32 price or a translatable gov-API enum. An unhandled fuel billed at the gas rate mis-costs the dwelling (same failure mode as the dual-main wood-vs-electric over-cost). Raise `UnpricedFuelCode` (new, mirrors MissingMainFuelType / UnmappedSapCode) so the gap surfaces at the price boundary. `None` (no fuel lodged) still defaults — callers resolve "no system" upstream. 0 corpus impact: all 1000 certs compute (every lodged fuel resolves), so this is a forward guard against future/unmapped fuels. Unit pin added; existing None-default test docstring tightened. pyright not installed locally — strict type gate not run. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
702150002f
commit
44fff76722
3 changed files with 53 additions and 6 deletions
|
|
@ -61,3 +61,26 @@ class MissingMainFuelType(ValueError):
|
|||
)
|
||||
self.value = value
|
||||
self.sap_main_heating_code = sap_main_heating_code
|
||||
|
||||
|
||||
class UnpricedFuelCode(ValueError):
|
||||
"""A concrete (non-None) fuel code reached the Table 32 unit-price
|
||||
lookup but resolves to neither a Table 32 price code nor a gov-API
|
||||
enum that translates to one.
|
||||
|
||||
Raised instead of silently falling back to the mains-gas price
|
||||
(3.48 p/kWh): an unrecognised fuel billed at the gas rate produces a
|
||||
misleading cost (e.g. the dual-main wood-vs-electric over-cost on
|
||||
cert 10032957680). Surface the gap at the price boundary so the fuel
|
||||
is either added to `UNIT_PRICE_P_PER_KWH` / `API_FUEL_TO_TABLE_32`
|
||||
or canonicalised upstream. A `None` code (no fuel lodged) is NOT an
|
||||
error — callers resolve "no system" before pricing.
|
||||
"""
|
||||
|
||||
def __init__(self, fuel_code: object) -> None:
|
||||
super().__init__(
|
||||
f"no Table 32 unit price for fuel code {fuel_code!r}; add it to "
|
||||
f"UNIT_PRICE_P_PER_KWH or API_FUEL_TO_TABLE_32, or canonicalise "
|
||||
f"the code upstream before pricing"
|
||||
)
|
||||
self.fuel_code = fuel_code
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ from __future__ import annotations
|
|||
|
||||
from typing import Final, Optional
|
||||
|
||||
from domain.sap10_calculator.exceptions import UnpricedFuelCode
|
||||
from domain.sap10_calculator.tables.table_12a import Tariff
|
||||
|
||||
|
||||
|
|
@ -184,8 +185,16 @@ STANDING_CHARGE_GBP_PER_YR: Final[dict[int, float]] = {
|
|||
def unit_price_p_per_kwh(fuel_code: Optional[int]) -> float:
|
||||
"""Unit price (p/kWh) for the given fuel code. Accepts either a
|
||||
Table 32 code or a gov API `main_fuel_type` / `water_heating_fuel`
|
||||
enum; translates the latter via `API_FUEL_TO_TABLE_32`. Unknown →
|
||||
mains gas (3.48 p/kWh)."""
|
||||
enum; translates the latter via `API_FUEL_TO_TABLE_32`.
|
||||
|
||||
`None` (no fuel lodged) → mains-gas default; callers resolve a
|
||||
"no system" before pricing. A concrete but UNRECOGNISED code raises
|
||||
`UnpricedFuelCode` rather than silently defaulting to the gas price —
|
||||
an unhandled fuel billed at 3.48 p/kWh mis-costs the dwelling (the
|
||||
same failure mode as the dual-main wood-vs-electric over-cost). The
|
||||
strict-raise surfaces the gap at the price boundary; 0 corpus certs
|
||||
hit it today (every lodged fuel resolves), so the raise is a guard
|
||||
against future / unmapped fuels, mirroring `MissingMainFuelType`."""
|
||||
if fuel_code is None:
|
||||
return _DEFAULT_P_PER_KWH
|
||||
if fuel_code in UNIT_PRICE_P_PER_KWH:
|
||||
|
|
@ -193,7 +202,7 @@ def unit_price_p_per_kwh(fuel_code: Optional[int]) -> float:
|
|||
translated = API_FUEL_TO_TABLE_32.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
|
||||
raise UnpricedFuelCode(fuel_code)
|
||||
|
||||
|
||||
def standing_charge_gbp(fuel_code: Optional[int]) -> float:
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ from __future__ import annotations
|
|||
|
||||
import pytest
|
||||
|
||||
from domain.sap10_calculator.exceptions import UnpricedFuelCode
|
||||
from domain.sap10_calculator.tables.table_12a import Tariff
|
||||
from domain.sap10_calculator.tables.table_32 import (
|
||||
additional_standing_charges_gbp,
|
||||
|
|
@ -143,9 +144,9 @@ def test_unit_price_translates_api_fuel_enum_via_api_fuel_to_table_32() -> None:
|
|||
|
||||
|
||||
def test_unit_price_defaults_to_mains_gas_when_code_is_none() -> None:
|
||||
"""Mirrors `table_12.unit_price_p_per_kwh` behaviour: unknown / missing
|
||||
fuel codes fall back to mains gas. cert_to_inputs occasionally has to
|
||||
resolve a price for a cert with a missing main_fuel_type."""
|
||||
"""A `None` fuel code (no fuel lodged) falls back to mains gas — callers
|
||||
resolve a "no system" before pricing, so None is not an error. A concrete
|
||||
UNRECOGNISED code raises instead (see the next test)."""
|
||||
# Arrange
|
||||
fuel_code = None
|
||||
|
||||
|
|
@ -156,6 +157,20 @@ def test_unit_price_defaults_to_mains_gas_when_code_is_none() -> None:
|
|||
assert price == 3.48
|
||||
|
||||
|
||||
def test_unit_price_raises_on_unrecognised_fuel_code() -> None:
|
||||
"""A concrete (non-None) fuel code that resolves to neither a Table 32
|
||||
price nor a translatable gov-API enum raises `UnpricedFuelCode` rather
|
||||
than silently defaulting to the mains-gas price — an unhandled fuel
|
||||
billed at 3.48 p/kWh mis-costs the dwelling (the dual-main wood-vs-
|
||||
electric failure mode). Guards against future/unmapped fuels."""
|
||||
# Arrange — 999 is not a Table 32 code nor a gov-API fuel enum.
|
||||
fuel_code = 999
|
||||
|
||||
# Act / Assert
|
||||
with pytest.raises(UnpricedFuelCode):
|
||||
unit_price_p_per_kwh(fuel_code)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"fuel_code, expected_standing_gbp, fuel_name",
|
||||
[
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue