An Energy-Savings scenario prices packages at the live fuel rates 🟥

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Khalim Conn-Kowlessar 2026-07-09 12:54:46 +00:00
parent 48d54675c3
commit 6b64104dc5
2 changed files with 56 additions and 1 deletions

View file

@ -39,6 +39,7 @@ from orchestration.modelling_orchestrator import (
_candidate_recommendations, # pyright: ignore[reportPrivateUsage]
)
from orchestration.property_baseline_orchestrator import PropertyBaselineOrchestrator
from repositories.fuel_rates.fuel_rates_repository import FuelRatesRepository
from repositories.fuel_rates.fuel_rates_static_file_repository import (
FuelRatesStaticFileRepository,
)
@ -182,6 +183,7 @@ def run_modelling(
considered_measures: Optional[frozenset[MeasureType]] = None,
products: Optional[ProductRepository] = None,
scenario: Optional[Scenario] = None,
fuel_rates: Optional[FuelRatesRepository] = None,
print_table: bool = True,
) -> Plan:
"""Run ONLY the Modelling stage over ``epc`` with no database — skipping
@ -240,7 +242,7 @@ def run_modelling(
ModellingOrchestrator(
unit_of_work=lambda: unit,
calculator=Sap10Calculator(),
fuel_rates=FuelRatesStaticFileRepository(),
fuel_rates=fuel_rates or FuelRatesStaticFileRepository(),
).run(
property_ids=[_PROPERTY_ID],
scenario_ids=[scenario_id],

View file

@ -9,11 +9,19 @@ carbon-optimal packages diverge at a £16,000 budget.
from __future__ import annotations
import dataclasses
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,
)
@ -57,3 +65,48 @@ def test_reducing_co2_scenario_buys_carbon_not_sap() -> None:
)
selected = {measure.measure_type for measure in carbon_led.measures}
assert MeasureType.GAS_BOILER_UPGRADE not in selected
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,
}