mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-07-12 13:29:04 +00:00
82 lines
2.9 KiB
Python
82 lines
2.9 KiB
Python
from __future__ import annotations
|
|
|
|
from collections.abc import Mapping
|
|
from dataclasses import dataclass
|
|
from typing import Optional
|
|
|
|
from domain.fuel_rates.fuel import Fuel, UnpricedFuel
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class FuelRate:
|
|
"""One fuel's current tariff: unit price + daily standing charge.
|
|
|
|
Off-gas fuels (oil / LPG / solid / wood) carry a ``0.0`` standing charge —
|
|
they are delivered, not metered, so there is no daily charge.
|
|
"""
|
|
|
|
unit_rate_p_per_kwh: float
|
|
standing_charge_p_per_day: float
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class OffPeakRate:
|
|
"""An Off-Peak Meter's dual-rate tariff — a cheaper ``night`` (low) rate and
|
|
a dearer ``day`` (high) rate, plus the daily standing charge (ADR-0014).
|
|
|
|
Off-peak electricity has no single unit rate, so it does not live in the
|
|
single-rate ``FuelRates.rates`` map; each end use blends day/night by its own
|
|
High-Rate Fraction (a calculator output)."""
|
|
|
|
day_p_per_kwh: float
|
|
night_p_per_kwh: float
|
|
standing_charge_p_per_day: float
|
|
|
|
def blended_p_per_kwh(self, high_rate_fraction: float) -> float:
|
|
"""Effective p/kWh for an end use billing ``high_rate_fraction`` of its
|
|
kWh at the day (high) rate and the remainder at the night (low) rate."""
|
|
return (
|
|
high_rate_fraction * self.day_p_per_kwh
|
|
+ (1.0 - high_rate_fraction) * self.night_p_per_kwh
|
|
)
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class FuelRates:
|
|
"""A current Fuel Rates snapshot — the rate per billing Fuel plus the SEG
|
|
export credit (ADR-0014). ``period`` records which window it is for, since
|
|
a committed snapshot goes stale on the Ofgem-cap (quarterly) cadence.
|
|
|
|
Pricing a fuel the snapshot does not carry raises ``UnpricedFuel`` rather
|
|
than defaulting — see [[reference-unmapped-sap-code]] for the same strict
|
|
discipline on the calculator side.
|
|
"""
|
|
|
|
period: str
|
|
seg_export_p_per_kwh: float
|
|
rates: Mapping[Fuel, FuelRate]
|
|
off_peak: Optional[OffPeakRate] = None
|
|
|
|
def off_peak_blended_p_per_kwh(self, high_rate_fraction: float) -> float:
|
|
"""Blended day/night p/kWh for an Off-Peak Meter end use at the given
|
|
High-Rate Fraction. Raises ``UnpricedFuel`` when the snapshot carries no
|
|
off-peak entry."""
|
|
if self.off_peak is None:
|
|
raise UnpricedFuel(Fuel.ELECTRICITY_OFF_PEAK)
|
|
return self.off_peak.blended_p_per_kwh(high_rate_fraction)
|
|
|
|
def unit_rate_p_per_kwh(self, fuel: Fuel) -> float:
|
|
return self._rate(fuel).unit_rate_p_per_kwh
|
|
|
|
def standing_charge_p_per_day(self, fuel: Fuel) -> float:
|
|
if fuel is Fuel.ELECTRICITY_OFF_PEAK:
|
|
if self.off_peak is None:
|
|
raise UnpricedFuel(fuel)
|
|
return self.off_peak.standing_charge_p_per_day
|
|
return self._rate(fuel).standing_charge_p_per_day
|
|
|
|
def _rate(self, fuel: Fuel) -> FuelRate:
|
|
rate = self.rates.get(fuel)
|
|
if rate is None:
|
|
raise UnpricedFuel(fuel)
|
|
return rate
|