mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-07-12 13:29:04 +00:00
Fabric-first phase 2 re-scores candidates in the goal objective's currency 🟥
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
6296b2d8fc
commit
ffaf89935b
1 changed files with 74 additions and 0 deletions
|
|
@ -26,6 +26,7 @@ from domain.modelling.scoring.package_scorer import Score
|
|||
from domain.modelling.simulation import (
|
||||
BuildingPartOverlay,
|
||||
EpcSimulation,
|
||||
HeatingOverlay,
|
||||
VentilationOverlay,
|
||||
)
|
||||
from tests.domain.sap10_calculator.worksheet._elmhurst_worksheet_000490 import (
|
||||
|
|
@ -121,3 +122,76 @@ def test_dependency_signals_are_priced_in_the_objective_currency() -> None:
|
|||
"mechanical_ventilation",
|
||||
}
|
||||
assert abs(package.score.co2_kg_per_yr - 480.0) <= 1e-9
|
||||
|
||||
|
||||
_IWI_OVERLAY = EpcSimulation(
|
||||
building_parts={
|
||||
BuildingPartIdentifier.MAIN: BuildingPartOverlay(wall_insulation_type=3)
|
||||
}
|
||||
)
|
||||
_BOILER_OVERLAY = EpcSimulation(heating=HeatingOverlay(sap_main_heating_code=201))
|
||||
_ASHP_OVERLAY = EpcSimulation(
|
||||
heating=HeatingOverlay(main_heating_index_number=13000)
|
||||
)
|
||||
|
||||
|
||||
class _CarbonHeatingScorer:
|
||||
"""A stub where the boiler wins on SAP (+10 vs +2) but the heat pump wins
|
||||
on carbon (−50 vs −5 kg/yr): a fabric-first phase 2 that re-scores its
|
||||
candidates in SAP picks the wrong heating for a Reducing-CO2 brief."""
|
||||
|
||||
def score(
|
||||
self, baseline: EpcPropertyData, simulations: Sequence[EpcSimulation]
|
||||
) -> Score:
|
||||
sap, co2 = 60.0, 500.0
|
||||
for sim in simulations:
|
||||
for part in sim.building_parts.values():
|
||||
if part.wall_insulation_type is not None:
|
||||
sap += 5.0
|
||||
co2 -= 10.0
|
||||
if sim.heating is None:
|
||||
continue
|
||||
if sim.heating.sap_main_heating_code is not None:
|
||||
sap += 10.0
|
||||
co2 -= 5.0
|
||||
if sim.heating.main_heating_index_number is not None:
|
||||
sap += 2.0
|
||||
co2 -= 50.0
|
||||
return Score(
|
||||
sap_continuous=sap, co2_kg_per_yr=co2, primary_energy_kwh_per_yr=0.0
|
||||
)
|
||||
|
||||
|
||||
def test_fabric_first_phase_two_rescores_in_the_objective_currency() -> None:
|
||||
# Arrange — a fabric-first Reducing-CO2 brief. Phase 1 commits the wall;
|
||||
# phase 2 must choose the heating on its post-fabric *carbon* worth, not
|
||||
# its SAP worth. Signals are supplied in kg CO2 saved (the caller's job).
|
||||
from domain.modelling.optimisation.optimiser import (
|
||||
optimise_package_fabric_first,
|
||||
)
|
||||
|
||||
groups: list[list[ScoredOption]] = [
|
||||
[_scored("internal_wall_insulation", gain=10.0, cost=1000.0, overlay=_IWI_OVERLAY)],
|
||||
[
|
||||
_scored("gas_boiler_upgrade", gain=5.0, cost=2000.0, overlay=_BOILER_OVERLAY),
|
||||
_scored("air_source_heat_pump", gain=50.0, cost=6000.0, overlay=_ASHP_OVERLAY),
|
||||
],
|
||||
]
|
||||
|
||||
# Act — no target (goal-aligned briefs have none), generous budget.
|
||||
package: OptimisedPackage = optimise_package_fabric_first(
|
||||
groups=groups,
|
||||
scorer=_CarbonHeatingScorer(),
|
||||
baseline_epc=build_epc(),
|
||||
budget=10000.0,
|
||||
target_sap=None,
|
||||
objective=_carbon_reduction,
|
||||
)
|
||||
|
||||
# Assert — the wall plus the heat pump (−50 kg), not the SAP-favoured
|
||||
# boiler; the truthful package carbon is 500 − 10 − 50 = 440.
|
||||
assert {s.option.measure_type for s in package.selected} == {
|
||||
"internal_wall_insulation",
|
||||
"air_source_heat_pump",
|
||||
}
|
||||
assert abs(package.score.co2_kg_per_yr - 440.0) <= 1e-9
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue