From 1acfc08fce26477b47fc91fd2bb7d9855645638b Mon Sep 17 00:00:00 2001 From: Khalim Conn-Kowlessar Date: Wed, 24 Jun 2026 17:20:19 +0000 Subject: [PATCH] =?UTF-8?q?Load=20the=20off-peak=20day/night=20rate=20from?= =?UTF-8?q?=20the=20committed=20snapshot=20=F0=9F=9F=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- .../fuel_rates_static_file_repository.py | 23 ++++++++++++++----- .../test_fuel_rates_static_file_repository.py | 17 ++++++++++++-- 2 files changed, 32 insertions(+), 8 deletions(-) diff --git a/repositories/fuel_rates/fuel_rates_static_file_repository.py b/repositories/fuel_rates/fuel_rates_static_file_repository.py index 1f53617d..e8057b39 100644 --- a/repositories/fuel_rates/fuel_rates_static_file_repository.py +++ b/repositories/fuel_rates/fuel_rates_static_file_repository.py @@ -5,7 +5,7 @@ from pathlib import Path from typing import Any, Optional from domain.fuel_rates.fuel import Fuel -from domain.fuel_rates.fuel_rates import FuelRate, FuelRates +from domain.fuel_rates.fuel_rates import FuelRate, FuelRates, OffPeakRate from repositories.fuel_rates.fuel_rates_repository import FuelRatesRepository _DEFAULT_SNAPSHOT = Path(__file__).parent / "data" / "fuel_rates_2026_q2.json" @@ -14,10 +14,12 @@ _DEFAULT_SNAPSHOT = Path(__file__).parent / "data" / "fuel_rates_2026_q2.json" class FuelRatesStaticFileRepository(FuelRatesRepository): """Reads Fuel Rates from a committed JSON snapshot (ADR-0014). - Only **single-rate** fuels (those lodging a ``unit_rate_p_per_kwh``) are - exposed. Off-peak (day/night) and the unpriced gaps (null entries — house - coal, heat network) are skipped, so pricing them raises ``UnpricedFuel``. - The day/night accessor for off-peak lands in a later slice. + **Single-rate** fuels (those lodging a ``unit_rate_p_per_kwh``) populate the + ``rates`` map. The **off-peak** entry — a dual-rate meter lodging + ``day_p_per_kwh`` / ``night_p_per_kwh`` — loads as an ``OffPeakRate`` (each + end use blends day/night by its own High-Rate Fraction). The unpriced gaps + (null entries — house coal, heat network) are skipped, so pricing them + raises ``UnpricedFuel``. """ def __init__(self, snapshot_path: Optional[Path] = None) -> None: @@ -27,11 +29,19 @@ class FuelRatesStaticFileRepository(FuelRatesRepository): payload: dict[str, Any] = json.loads(self._snapshot_path.read_text()) fuels: dict[str, Any] = payload["fuels"] rates: dict[Fuel, FuelRate] = {} + off_peak: Optional[OffPeakRate] = None for name, entry in fuels.items(): if entry is None: continue # an unpriced gap (house coal / heat network) + if name == Fuel.ELECTRICITY_OFF_PEAK.name: + off_peak = OffPeakRate( + day_p_per_kwh=float(entry["day_p_per_kwh"]), + night_p_per_kwh=float(entry["night_p_per_kwh"]), + standing_charge_p_per_day=float(entry["standing_charge_p_per_day"]), + ) + continue if "unit_rate_p_per_kwh" not in entry: - continue # off-peak day/night — priced in a later slice + continue # an unpriced gap with no single rate rates[Fuel[name]] = FuelRate( unit_rate_p_per_kwh=float(entry["unit_rate_p_per_kwh"]), standing_charge_p_per_day=float(entry["standing_charge_p_per_day"]), @@ -40,4 +50,5 @@ class FuelRatesStaticFileRepository(FuelRatesRepository): period=str(payload["period"]), seg_export_p_per_kwh=float(payload["seg_export_p_per_kwh"]), rates=rates, + off_peak=off_peak, ) diff --git a/tests/repositories/fuel_rates/test_fuel_rates_static_file_repository.py b/tests/repositories/fuel_rates/test_fuel_rates_static_file_repository.py index 1bce0362..fd5f038f 100644 --- a/tests/repositories/fuel_rates/test_fuel_rates_static_file_repository.py +++ b/tests/repositories/fuel_rates/test_fuel_rates_static_file_repository.py @@ -55,8 +55,21 @@ def test_dual_fuel_carries_a_derived_midpoint_rate() -> None: assert rates.standing_charge_p_per_day(Fuel.DUAL_FUEL_MINERAL_AND_WOOD) == 0.0 -def test_off_peak_remains_unpriced_pending_the_day_night_accessor() -> None: - # Arrange — off-peak still needs the day/night split a later slice adds (ADR-0014). +def test_off_peak_meter_prices_day_night_from_the_snapshot() -> None: + # Arrange — the committed snapshot's off-peak entry carries a day/night + # split (ADR-0014 2026-06-24 amendment); the repo loads it as an OffPeakRate. + rates = FuelRatesStaticFileRepository().get_current() + + # Act / Assert — an all-night load bills at the night rate, an all-day load + # at the day rate, and the off-peak meter carries its own standing charge. + assert rates.off_peak_blended_p_per_kwh(high_rate_fraction=0.0) == 13.89 + assert rates.off_peak_blended_p_per_kwh(high_rate_fraction=1.0) == 29.73 + assert rates.standing_charge_p_per_day(Fuel.ELECTRICITY_OFF_PEAK) == 56.99 + + +def test_off_peak_has_no_single_unit_rate() -> None: + # Arrange — off-peak has no single blended unit rate; callers must price it + # day/night via a High-Rate Fraction, so unit_rate_p_per_kwh still raises. rates = FuelRatesStaticFileRepository().get_current() # Act / Assert