diff --git a/domain/fuel_rates/fuel_rates.py b/domain/fuel_rates/fuel_rates.py index a5b2eb73..569c6f17 100644 --- a/domain/fuel_rates/fuel_rates.py +++ b/domain/fuel_rates/fuel_rates.py @@ -2,6 +2,7 @@ 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 @@ -18,6 +19,25 @@ class FuelRate: 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.""" + raise NotImplementedError + + @dataclass(frozen=True) class FuelRates: """A current Fuel Rates snapshot — the rate per billing Fuel plus the SEG @@ -32,6 +52,13 @@ class FuelRates: 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.""" + raise NotImplementedError def unit_rate_p_per_kwh(self, fuel: Fuel) -> float: return self._rate(fuel).unit_rate_p_per_kwh diff --git a/tests/domain/fuel_rates/test_fuel_rates.py b/tests/domain/fuel_rates/test_fuel_rates.py index a7319274..432fcb63 100644 --- a/tests/domain/fuel_rates/test_fuel_rates.py +++ b/tests/domain/fuel_rates/test_fuel_rates.py @@ -3,7 +3,7 @@ from __future__ import annotations import pytest from domain.fuel_rates.fuel import Fuel, UnpricedFuel -from domain.fuel_rates.fuel_rates import FuelRate, FuelRates +from domain.fuel_rates.fuel_rates import FuelRate, FuelRates, OffPeakRate def _rates() -> FuelRates: @@ -14,6 +14,28 @@ def _rates() -> FuelRates: ) +def _off_peak_rates() -> FuelRates: + return FuelRates( + period="test", + seg_export_p_per_kwh=15.0, + rates={}, + off_peak=OffPeakRate( + day_p_per_kwh=29.73, night_p_per_kwh=13.89, standing_charge_p_per_day=56.99 + ), + ) + + +def test_off_peak_blended_rate_at_zero_high_fraction_is_the_night_rate() -> None: + # Arrange — an all-night load (storage heating, high-rate fraction 0.0). + rates = _off_peak_rates() + + # Act + blended = rates.off_peak_blended_p_per_kwh(high_rate_fraction=0.0) + + # Assert — every kWh bills at the cheap night rate. + assert blended == 13.89 + + def test_unit_rate_and_standing_charge_read_back_for_a_priced_fuel() -> None: # Arrange rates = _rates()