"""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