Model/tests/domain/billing/test_off_peak_bill_integration.py
Khalim Conn-Kowlessar 1da86a6366 Pin end-to-end off-peak day/night bill regression 🟩
An Economy-7 storage dwelling now prices heating at the 0.20-day/0.80-
night blend through cert -> calculator -> bill, instead of raising
UnpricedFuel and aborting the modelling_e2e batch.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 18:05:33 +00:00

77 lines
3.2 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""End-to-end regression for the Off-Peak Meter day/night bill split (ADR-0014
2026-06-24 amendment).
Drives a real off-peak (Economy-7) storage-heating cert through the whole chain
— `cert_to_inputs` → `calculate_sap_from_inputs` → `EnergyBreakdown.from_sap_result`
→ `BillDerivation.derive` against the committed Fuel Rates snapshot. Before the
amendment this raised `UnpricedFuel` ("no rate for fuel ELECTRICITY_OFF_PEAK"),
which aborted the `modelling_e2e` batch (e.g. property 717572). It must now price
the dwelling day/night instead of crashing.
"""
from __future__ import annotations
from dataclasses import replace
from domain.sap10_ml.tests._fixtures import (
make_building_part,
make_minimal_sap10_epc,
make_sap_heating,
)
from datatypes.epc.domain.epc_property_data import EpcPropertyData, MainHeatingDetail
from domain.sap10_calculator.calculator import calculate_sap_from_inputs
from domain.sap10_calculator.rdsap.cert_to_inputs import cert_to_inputs
from domain.fuel_rates.fuel import Fuel
from domain.billing.bill import BillSection, EnergyBreakdown
from domain.billing.bill_derivation import BillDerivation
from repositories.fuel_rates.fuel_rates_static_file_repository import (
FuelRatesStaticFileRepository,
)
_TYPICAL_TFA_M2 = 60.0
def _off_peak_storage_epc() -> EpcPropertyData:
# Integrated storage heaters (SAP code 408, Table 12a Grid 1 high-rate
# fraction 0.20) on a Dual / Economy-7 (7-hour) meter.
main = MainHeatingDetail(
has_fghrs=False,
main_fuel_type=29, # electricity
heat_emitter_type=1,
emitter_temperature=1,
main_heating_control=2401,
main_heating_category=7, # electric storage heaters
sap_main_heating_code=408,
)
base = make_minimal_sap10_epc(
total_floor_area_m2=_TYPICAL_TFA_M2,
habitable_rooms_count=4,
country_code="ENG",
sap_building_parts=[make_building_part(construction_age_band="E")],
sap_heating=make_sap_heating(main_heating_details=[main]),
)
return replace(
base,
sap_energy_source=replace(base.sap_energy_source, meter_type="1"),
)
def test_off_peak_storage_dwelling_bills_heating_day_night_not_crashes() -> None:
# Arrange — score the off-peak cert and price it at the committed snapshot.
result = calculate_sap_from_inputs(cert_to_inputs(_off_peak_storage_epc()))
rates = FuelRatesStaticFileRepository().get_current()
# Act — the previously-crashing path: off-peak electricity reaches the bill.
breakdown = EnergyBreakdown.from_sap_result(result)
bill = BillDerivation(rates).derive(breakdown)
# Assert — heating bills on the off-peak carrier at the 0.20-day/0.80-night
# blend (0.20×29.73 + 0.80×13.89 = 17.058 p/kWh), not a crash and not the
# flat standard rate (24.67 p) or pure night rate (13.89 p).
heating = bill.sections[BillSection.HEATING]
assert heating.kwh > 0.0
implied_rate_p = heating.cost_gbp / heating.kwh * 100.0
assert abs(implied_rate_p - 17.058) <= 1e-6
# The off-peak meter contributes one standing charge (56.99 p/day).
assert any(line.fuel is Fuel.ELECTRICITY_OFF_PEAK for line in breakdown.lines)
assert bill.total_gbp > 0.0