mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-07-12 13:29:04 +00:00
Fabric unpicked in phase 1 can re-enter phase 2 on post-fabric worth 🟩
Documents behaviour already delivered: phase 2 optimises every group phase 1 did not consume, with signals re-scored against the fabric-applied dwelling, so glazing skipped on raw-baseline merit re-enters when it closes the target. Test passed on arrival. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
48ce18dbb6
commit
adf60b82b1
1 changed files with 59 additions and 0 deletions
|
|
@ -27,6 +27,7 @@ from domain.modelling.scoring.package_scorer import Score
|
|||
from domain.modelling.simulation import (
|
||||
BuildingPartOverlay,
|
||||
EpcSimulation,
|
||||
GlazingOverlay,
|
||||
HeatingOverlay,
|
||||
)
|
||||
from tests.domain.sap10_calculator.worksheet._elmhurst_worksheet_000490 import (
|
||||
|
|
@ -201,6 +202,64 @@ class _InteractionScorer:
|
|||
)
|
||||
|
||||
|
||||
_GLAZING_OVERLAY = EpcSimulation(glazing=GlazingOverlay(glazing_type=2))
|
||||
|
||||
|
||||
class _GlazingInteractionScorer:
|
||||
"""A stub where glazing is worthless on the raw dwelling (+0) but worth +4
|
||||
once the wall is insulated — so phase 1's max-gain fabric pass leaves it
|
||||
out, and only a phase 2 that re-admits unpicked fabric can close the
|
||||
target with it."""
|
||||
|
||||
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()
|
||||
)
|
||||
glazing_present = any(sim.glazing is not None for sim in simulations)
|
||||
heating_present = any(sim.heating is not None for sim in simulations)
|
||||
sap = 60.0
|
||||
sap += 5.0 if wall_present else 0.0
|
||||
sap += (4.0 if wall_present else 0.0) if glazing_present else 0.0
|
||||
sap += 10.0 if heating_present else 0.0
|
||||
return Score(
|
||||
sap_continuous=sap, co2_kg_per_yr=0.0, primary_energy_kwh_per_yr=0.0
|
||||
)
|
||||
|
||||
|
||||
def test_fabric_unpicked_in_phase_one_can_reenter_phase_two() -> None:
|
||||
# Arrange — glazing loses phase 1 on merit (it scores nothing on the raw
|
||||
# dwelling), but post-wall it is the only affordable way to the target:
|
||||
# the heat pump that could also close it does not fit the leftover budget.
|
||||
groups: list[list[ScoredOption]] = [
|
||||
[_scored("cavity_wall_insulation", gain=5.0, cost=1000.0, overlay=_WALL_OVERLAY)],
|
||||
[_scored("double_glazing", gain=0.0, cost=500.0, overlay=_GLAZING_OVERLAY)],
|
||||
[_scored("air_source_heat_pump", gain=10.0, cost=8000.0, overlay=_HEATING_OVERLAY)],
|
||||
]
|
||||
scorer = _GlazingInteractionScorer()
|
||||
|
||||
# Act — target 69 (gain 9); budget £5000 keeps the heat pump out of reach
|
||||
# after the wall's £1000.
|
||||
package: OptimisedPackage = optimise_package_fabric_first(
|
||||
groups=groups,
|
||||
scorer=scorer,
|
||||
baseline_epc=build_epc(),
|
||||
budget=5000.0,
|
||||
target_sap=69.0,
|
||||
)
|
||||
|
||||
# Assert — the skipped glazing re-enters on its post-fabric worth: 60 + 5
|
||||
# wall + 4 glazing = 69, target met.
|
||||
assert _selected_types(package) == {
|
||||
"cavity_wall_insulation",
|
||||
"double_glazing",
|
||||
}
|
||||
assert abs(package.score.sap_continuous - 69.0) <= 1e-9
|
||||
|
||||
|
||||
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
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue