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.property_baseline.bill import BillSection, EnergyBreakdown, EnergyLine from domain.property_baseline.bill_derivation import BillDerivation def _rates() -> FuelRates: return FuelRates( period="test", seg_export_p_per_kwh=15.0, rates={ Fuel.MAINS_GAS: FuelRate(unit_rate_p_per_kwh=5.74, standing_charge_p_per_day=29.09), 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), }, ) def test_derive_prices_a_single_gas_heating_line_with_its_standing_charge() -> None: # Arrange — 10,000 kWh of mains-gas heating. breakdown = EnergyBreakdown( lines=[EnergyLine(section=BillSection.HEATING, fuel=Fuel.MAINS_GAS, kwh=10000.0)] ) derivation = BillDerivation(_rates()) # Act bill = derivation.derive(breakdown) # Assert — heating = 10000 × 5.74p = £574; standing = 29.09p × 365 = £106.1785. assert abs(bill.sections[BillSection.HEATING].cost_gbp - 574.0) <= 1e-9 assert abs(bill.standing_charges_gbp - 106.1785) <= 1e-9 assert abs(bill.total_gbp - 680.1785) <= 1e-9 def test_two_sections_on_the_same_fuel_share_one_standing_charge() -> None: # Arrange — gas heating + gas hot water are one meter, not two. breakdown = EnergyBreakdown( lines=[ EnergyLine(section=BillSection.HEATING, fuel=Fuel.MAINS_GAS, kwh=8000.0), EnergyLine(section=BillSection.HOT_WATER, fuel=Fuel.MAINS_GAS, kwh=2000.0), ] ) # Act bill = BillDerivation(_rates()).derive(breakdown) # Assert — one gas standing charge (29.09p × 365 = £106.1785), not two. assert abs(bill.standing_charges_gbp - 106.1785) <= 1e-9 assert abs(bill.sections[BillSection.HOT_WATER].cost_gbp - 114.8) <= 1e-9 def test_distinct_fuels_each_add_their_own_standing_charge() -> None: # Arrange — gas heating + electric lighting: two meters. breakdown = EnergyBreakdown( lines=[ EnergyLine(section=BillSection.HEATING, fuel=Fuel.MAINS_GAS, kwh=8000.0), EnergyLine(section=BillSection.LIGHTING, fuel=Fuel.ELECTRICITY, kwh=500.0), ] ) # Act bill = BillDerivation(_rates()).derive(breakdown) # Assert — gas 29.09 + elec 57.21 = 86.30 p/day × 365 = £314.995. assert abs(bill.standing_charges_gbp - 314.995) <= 1e-9 def test_exported_pv_is_credited_at_the_seg_rate() -> None: # Arrange — 1000 kWh exported at 15p, against a single gas heating line. breakdown = EnergyBreakdown( lines=[EnergyLine(section=BillSection.HEATING, fuel=Fuel.MAINS_GAS, kwh=10000.0)], exported_kwh=1000.0, ) # Act bill = BillDerivation(_rates()).derive(breakdown) # Assert — SEG credit £150 subtracted from the £680.1785 gross. assert abs(bill.seg_credit_gbp - 150.0) <= 1e-9 assert abs(bill.total_gbp - 530.1785) <= 1e-9 def test_an_unpriced_fuel_in_a_line_raises() -> None: # Arrange — a heat-network line; the snapshot prices no heat network. breakdown = EnergyBreakdown( lines=[EnergyLine(section=BillSection.HEATING, fuel=Fuel.HEAT_NETWORK, kwh=5000.0)] ) # Act / Assert with pytest.raises(UnpricedFuel): BillDerivation(_rates()).derive(breakdown)