A Fabric First Scenario spends the budget on the envelope before heating 🟥

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Khalim Conn-Kowlessar 2026-07-09 11:44:31 +00:00
parent e75798b281
commit a9173bd492

View file

@ -0,0 +1,60 @@
"""The ModellingOrchestrator honours a Fabric First Scenario: when
``scenario.fabric_first`` is set, the Optimiser treats the building envelope
with the full budget before heating / renewables are considered, so a budget
the plain optimiser would spend on a heating system is spent on fabric
instead. End-to-end through ``run_modelling`` (no database) with the real
calculator, against the uninsulated solid-brick 001431 dwelling whose plain
plan is heating-led.
"""
from __future__ import annotations
from datatypes.epc.domain.epc_property_data import EpcPropertyData
from domain.modelling.measure_type import FABRIC_MEASURE_TYPES, MeasureType
from domain.modelling.plan import Plan
from domain.modelling.scenario import Scenario
from harness.console import run_modelling
from tests.domain.modelling._elmhurst_recommendation import (
parse_recommendation_summary,
)
_HEATING_MEASURES: frozenset[MeasureType] = frozenset(
{
MeasureType.GAS_BOILER_UPGRADE,
MeasureType.AIR_SOURCE_HEAT_PUMP,
MeasureType.HIGH_HEAT_RETENTION_STORAGE_HEATERS,
MeasureType.SOLAR_PV,
}
)
def _fabric_first_scenario(*, budget: float) -> Scenario:
return Scenario(
id=999,
goal="Increasing EPC",
goal_value="C",
budget=budget,
is_default=True,
fabric_first=True,
)
def test_fabric_first_scenario_spends_the_budget_on_fabric_before_heating() -> None:
# Arrange — uninsulated solid brick: at a £4000 budget the plain optimiser
# buys the £3200 gas boiler (the cheapest route toward band C). Fabric
# first must commit the envelope work first, after which the boiler no
# longer fits the leftover budget.
epc: EpcPropertyData = parse_recommendation_summary(
"solid_brick_ewi_001431_before.pdf"
)
# Act
plan: Plan = run_modelling(
epc, scenario=_fabric_first_scenario(budget=4000.0), print_table=False
)
# Assert — the plan is fabric-led: at least one envelope measure, and none
# of the budget leaked to a heating system or renewables.
selected = {measure.measure_type for measure in plan.measures}
assert selected & FABRIC_MEASURE_TYPES
assert not selected & _HEATING_MEASURES