From 9ad2c2edfdb860cf8da867ae1e3057b3c8b9f69a Mon Sep 17 00:00:00 2001 From: Khalim Conn-Kowlessar Date: Thu, 9 Jul 2026 11:36:27 +0000 Subject: [PATCH] =?UTF-8?q?Fabric-first=20scenario=20stops=20at=20fabric?= =?UTF-8?q?=20when=20the=20target=20is=20met=20=F0=9F=9F=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Fable 5 --- domain/modelling/optimisation/optimiser.py | 23 ++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/domain/modelling/optimisation/optimiser.py b/domain/modelling/optimisation/optimiser.py index 9b076ec4a..b47495f00 100644 --- a/domain/modelling/optimisation/optimiser.py +++ b/domain/modelling/optimisation/optimiser.py @@ -24,7 +24,7 @@ from dataclasses import dataclass from typing import Optional, Protocol, Sequence from datatypes.epc.domain.epc_property_data import EpcPropertyData -from domain.modelling.measure_type import MeasureType +from domain.modelling.measure_type import FABRIC_MEASURE_TYPES, MeasureType from domain.modelling.scoring.package_scorer import Score from domain.modelling.recommendation import MeasureOption from domain.modelling.simulation import EpcSimulation @@ -237,7 +237,26 @@ def optimise_package_fabric_first( if the truthful post-fabric score meets ``target_sap``, stop there. Otherwise optimise the remaining groups on top — the starting point for phase 2 is the dwelling with the phase-1 fabric applied — within the leftover budget.""" - raise NotImplementedError + fabric_groups: list[list[ScoredOption]] = [ + group for group in groups if _is_fabric_group(group) + ] + fabric_package: OptimisedPackage = optimise_package( + groups=fabric_groups, + scorer=scorer, + baseline_epc=baseline_epc, + budget=budget, + target_sap=target_sap, + dependencies=dependencies, + ) + return fabric_package + + +def _is_fabric_group(group: list[ScoredOption]) -> bool: + """A group belongs to phase 1 when every Option in it is a fabric measure + (groups are one Recommendation each, so they are homogeneous in kind).""" + return all( + scored.option.measure_type in FABRIC_MEASURE_TYPES for scored in group + ) def _with_role1_signals(