mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-07-12 13:29:04 +00:00
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>
77 lines
3.2 KiB
Python
77 lines
3.2 KiB
Python
"""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
|