mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-30 13:10:47 +00:00
Price an off-peak meter line day/night by its high-rate fraction 🟩
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
1acfc08fce
commit
dc55d3b899
3 changed files with 46 additions and 5 deletions
|
|
@ -28,11 +28,17 @@ class BillSection(Enum):
|
|||
@dataclass(frozen=True)
|
||||
class EnergyLine:
|
||||
"""One section's delivered energy on one fuel. A section may have more than
|
||||
one line (e.g. gas main heating + electric secondary heating)."""
|
||||
one line (e.g. gas main heating + electric secondary heating).
|
||||
|
||||
``high_rate_fraction`` is the calculator's High-Rate Fraction for this end
|
||||
use — the share of its kWh billed at the day (high) rate on an Off-Peak
|
||||
Meter — set only on ``ELECTRICITY_OFF_PEAK`` lines; ``None`` for single-rate
|
||||
fuels, which bill at their flat unit rate."""
|
||||
|
||||
section: BillSection
|
||||
fuel: Fuel
|
||||
kwh: float
|
||||
high_rate_fraction: Optional[float] = None
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ from domain.billing.bill import (
|
|||
BillSection,
|
||||
BillSectionCost,
|
||||
EnergyBreakdown,
|
||||
EnergyLine,
|
||||
)
|
||||
|
||||
_DAYS_PER_YEAR: Final[float] = 365.0
|
||||
|
|
@ -36,9 +37,7 @@ class BillDerivation:
|
|||
fuels_used: set[Fuel] = set()
|
||||
for line in breakdown.lines:
|
||||
section_kwh[line.section] += line.kwh
|
||||
section_cost_p[line.section] += (
|
||||
line.kwh * self._rates.unit_rate_p_per_kwh(line.fuel)
|
||||
)
|
||||
section_cost_p[line.section] += line.kwh * self._unit_rate_p_per_kwh(line)
|
||||
if line.kwh > 0:
|
||||
fuels_used.add(line.fuel)
|
||||
|
||||
|
|
@ -69,3 +68,15 @@ class BillDerivation:
|
|||
seg_credit_gbp=seg_credit_gbp,
|
||||
total_gbp=total_gbp,
|
||||
)
|
||||
|
||||
def _unit_rate_p_per_kwh(self, line: EnergyLine) -> float:
|
||||
"""Price one line's fuel (p/kWh). An Off-Peak Meter line blends day/night
|
||||
by its High-Rate Fraction; every other fuel bills at its flat unit rate."""
|
||||
if line.fuel is Fuel.ELECTRICITY_OFF_PEAK:
|
||||
if line.high_rate_fraction is None:
|
||||
raise ValueError(
|
||||
f"{line.section.value} bills on an off-peak meter but carries "
|
||||
"no high-rate fraction; cannot split day/night"
|
||||
)
|
||||
return self._rates.off_peak_blended_p_per_kwh(line.high_rate_fraction)
|
||||
return self._rates.unit_rate_p_per_kwh(line.fuel)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
from domain.billing.bill import BillSection, EnergyBreakdown, EnergyLine
|
||||
from domain.billing.bill_derivation import BillDerivation
|
||||
|
||||
|
|
@ -17,9 +17,33 @@ def _rates() -> FuelRates:
|
|||
Fuel.ELECTRICITY: FuelRate(unit_rate_p_per_kwh=24.67, standing_charge_p_per_day=57.21),
|
||||
Fuel.OIL: FuelRate(unit_rate_p_per_kwh=9.16, standing_charge_p_per_day=0.0),
|
||||
},
|
||||
off_peak=OffPeakRate(
|
||||
day_p_per_kwh=29.73, night_p_per_kwh=13.89, standing_charge_p_per_day=56.99
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def test_an_all_night_off_peak_heating_line_bills_at_the_night_rate() -> None:
|
||||
# Arrange — 10,000 kWh of electric storage heating on an Off-Peak Meter,
|
||||
# charged wholly overnight (high-rate fraction 0.0).
|
||||
breakdown = EnergyBreakdown(
|
||||
lines=[
|
||||
EnergyLine(
|
||||
section=BillSection.HEATING,
|
||||
fuel=Fuel.ELECTRICITY_OFF_PEAK,
|
||||
kwh=10000.0,
|
||||
high_rate_fraction=0.0,
|
||||
)
|
||||
]
|
||||
)
|
||||
|
||||
# Act
|
||||
bill = BillDerivation(_rates()).derive(breakdown)
|
||||
|
||||
# Assert — every kWh at the night rate: 10000 × 13.89p = £1389.
|
||||
assert bill.sections[BillSection.HEATING].cost_gbp == pytest.approx(1389.0)
|
||||
|
||||
|
||||
def test_derive_prices_a_single_gas_heating_line_with_its_standing_charge() -> None:
|
||||
# Arrange — 10,000 kWh of mains-gas heating.
|
||||
breakdown = EnergyBreakdown(
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue