"""The ModellingOrchestrator aligns the Optimiser's objective with the Scenario's goal (ADR-0062): Reducing CO2 emissions maximises the carbon reduction the budget buys, Energy Savings maximises the annual bill saving, and Increasing EPC keeps its SAP target semantics. End-to-end through ``run_modelling`` (no database) with the real calculator, against the uninsulated solid-brick 001431 dwelling where the SAP-optimal and carbon-optimal packages diverge at a £16,000 budget. """ from __future__ import annotations import dataclasses import pytest from datatypes.epc.domain.epc_property_data import EpcPropertyData from domain.fuel_rates.fuel import Fuel from domain.fuel_rates.fuel_rates import FuelRate, FuelRates from domain.modelling.measure_type import MeasureType from domain.modelling.plan import Plan from domain.modelling.scenario import Scenario from harness.console import run_modelling from repositories.fuel_rates.fuel_rates_repository import FuelRatesRepository from repositories.fuel_rates.fuel_rates_static_file_repository import ( FuelRatesStaticFileRepository, ) from tests.domain.modelling._elmhurst_recommendation import ( parse_recommendation_summary, ) def _solid_brick_dwelling() -> EpcPropertyData: return parse_recommendation_summary("solid_brick_ewi_001431_before.pdf") def _scenario(goal: str, *, budget: float) -> Scenario: return Scenario( id=999, goal=goal, goal_value="", budget=budget, is_default=True ) def test_reducing_co2_scenario_buys_carbon_not_sap() -> None: # Arrange — at £16,000 the SAP objective buys the wall + floor + £3,200 # gas boiler package (~2,069 kg CO2/yr, SAP 72.9). The carbon objective # swaps the boiler for electric storage heaters (~1,098 kg/yr) — a lower # SAP, but ~970 kg/yr less carbon on the low-carbon grid. epc = _solid_brick_dwelling() # Act — the same dwelling and budget under each goal. sap_led: Plan = run_modelling( epc, scenario=_scenario("Valuation Improvement", budget=16000.0), print_table=False, ) carbon_led: Plan = run_modelling( epc, scenario=_scenario("Reducing CO2 emissions", budget=16000.0), print_table=False, ) # Assert — the goal changes the outcome in the goal's favour: the carbon # plan cuts materially more CO2 than the SAP plan buys with the same # money, and the gas boiler that wins on SAP-per-£ is rejected. assert ( carbon_led.post_retrofit.co2_kg_per_yr < sap_led.post_retrofit.co2_kg_per_yr - 500.0 ) selected = {measure.measure_type for measure in carbon_led.measures} assert MeasureType.GAS_BOILER_UPGRADE not in selected def test_a_goal_aligned_scenario_without_a_budget_fails_loudly() -> None: # Arrange — 'reduce as much as possible within this budget' is undefined # without a budget: unconstrained it would recommend every beneficial # measure. A budget-less goal-aligned Scenario is a misconfiguration and # must fail visibly, not produce a maximal plan. epc = _solid_brick_dwelling() budgetless = Scenario( id=999, goal="Reducing CO2 emissions", goal_value="", budget=None, is_default=True, ) # Act / Assert with pytest.raises(ValueError, match="budget"): run_modelling(epc, scenario=budgetless, print_table=False) class _FixedFuelRates(FuelRatesRepository): def __init__(self, rates: FuelRates) -> None: self._rates = rates def get_current(self) -> FuelRates: return self._rates def _cheap_electricity_snapshot() -> FuelRates: """The committed snapshot with electricity at 1p/kWh — a world where any electric heating out-bills gas, while SAP's internal price book (which the calculator rates against) is unmoved.""" base = FuelRatesStaticFileRepository().get_current() rates = dict(base.rates) rates[Fuel.ELECTRICITY] = FuelRate( unit_rate_p_per_kwh=1.0, standing_charge_p_per_day=rates[Fuel.ELECTRICITY].standing_charge_p_per_day, ) return dataclasses.replace(base, rates=rates) def test_energy_savings_scenario_prices_packages_at_the_live_fuel_rates() -> None: # Arrange — SAP is itself a cost metric, but it prices energy from its # internal tariff book. The Energy Savings goal must price at the *live* # Fuel Rates snapshot: with 1p/kWh electricity, electric heating slashes # the bill even though SAP still scores the gas boiler package higher. epc = _solid_brick_dwelling() # Act plan: Plan = run_modelling( epc, scenario=_scenario("Energy Savings", budget=16000.0), fuel_rates=_FixedFuelRates(_cheap_electricity_snapshot()), print_table=False, ) # Assert — the bill objective abandons the boiler for electric heating. selected = {measure.measure_type for measure in plan.measures} assert MeasureType.GAS_BOILER_UPGRADE not in selected assert selected & { MeasureType.AIR_SOURCE_HEAT_PUMP, MeasureType.HIGH_HEAT_RETENTION_STORAGE_HEATERS, }