mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-07-12 13:29:04 +00:00
Phase-2 candidates are valued against the post-fabric dwelling 🟥
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
e652780873
commit
705c86d5e6
1 changed files with 65 additions and 0 deletions
|
|
@ -44,6 +44,12 @@ _ROOF_OVERLAY = EpcSimulation(
|
|||
}
|
||||
)
|
||||
_HEATING_OVERLAY = EpcSimulation(heating=HeatingOverlay(sap_main_heating_code=201))
|
||||
_BOILER_OVERLAY = EpcSimulation(heating=HeatingOverlay(sap_main_heating_code=201))
|
||||
_ASHP_OVERLAY = EpcSimulation(
|
||||
heating=HeatingOverlay(main_heating_index_number=13000)
|
||||
)
|
||||
|
||||
|
||||
def _scored(
|
||||
measure_type: str, *, gain: float, cost: float, overlay: EpcSimulation
|
||||
) -> ScoredOption:
|
||||
|
|
@ -144,3 +150,62 @@ def test_fabric_short_of_target_is_topped_up_with_non_fabric_measures() -> None:
|
|||
assert abs(package.score.sap_continuous - 85.0) <= 1e-9
|
||||
|
||||
|
||||
class _InteractionScorer:
|
||||
"""A stub whose boiler gain collapses once the wall is insulated (+10 raw,
|
||||
+3 post-fabric) while the heat pump's holds (+8 either way) — so a phase 2
|
||||
that keeps valuing candidates against the raw baseline picks the wrong
|
||||
heating system."""
|
||||
|
||||
def score(
|
||||
self, baseline: EpcPropertyData, simulations: Sequence[EpcSimulation]
|
||||
) -> Score:
|
||||
wall_present = any(
|
||||
part.wall_insulation_type is not None
|
||||
for sim in simulations
|
||||
for part in sim.building_parts.values()
|
||||
)
|
||||
sap = 60.0 + (5.0 if wall_present else 0.0)
|
||||
for sim in simulations:
|
||||
if sim.heating is None:
|
||||
continue
|
||||
if sim.heating.sap_main_heating_code is not None:
|
||||
sap += 3.0 if wall_present else 10.0
|
||||
if sim.heating.main_heating_index_number is not None:
|
||||
sap += 8.0
|
||||
return Score(
|
||||
sap_continuous=sap, co2_kg_per_yr=0.0, primary_energy_kwh_per_yr=0.0
|
||||
)
|
||||
|
||||
|
||||
def test_phase_two_values_candidates_against_the_post_fabric_dwelling() -> None:
|
||||
# Arrange — one heating Recommendation, two Options. The boiler's role-1
|
||||
# signal (vs the raw baseline, +10) beats the heat pump's (+8) and it is
|
||||
# cheaper — but on the insulated dwelling the boiler is only worth +3.
|
||||
# Only a heat pump gets the fabric-applied dwelling to the target.
|
||||
groups: list[list[ScoredOption]] = [
|
||||
[_scored("cavity_wall_insulation", gain=5.0, cost=1000.0, overlay=_WALL_OVERLAY)],
|
||||
[
|
||||
_scored("gas_boiler_upgrade", gain=10.0, cost=2000.0, overlay=_BOILER_OVERLAY),
|
||||
_scored("air_source_heat_pump", gain=8.0, cost=6000.0, overlay=_ASHP_OVERLAY),
|
||||
],
|
||||
]
|
||||
scorer = _InteractionScorer()
|
||||
|
||||
# Act — target 73: wall (65) + boiler-post-fabric (+3) = 68 misses; wall +
|
||||
# heat pump (+8) = 73 reaches. The heating group is consumed by whichever
|
||||
# option phase 2 warm-starts with, so the choice must be made on
|
||||
# post-fabric values, not raw-baseline signals.
|
||||
package: OptimisedPackage = optimise_package_fabric_first(
|
||||
groups=groups,
|
||||
scorer=scorer,
|
||||
baseline_epc=build_epc(),
|
||||
budget=20000.0,
|
||||
target_sap=73.0,
|
||||
)
|
||||
|
||||
# Assert
|
||||
assert _selected_types(package) == {
|
||||
"cavity_wall_insulation",
|
||||
"air_source_heat_pump",
|
||||
}
|
||||
assert abs(package.score.sap_continuous - 73.0) <= 1e-9
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue