From 471728db0a070323a2fc2e1df4d1bd53ceff9c30 Mon Sep 17 00:00:00 2001 From: Khalim Conn-Kowlessar Date: Thu, 9 Jul 2026 11:42:54 +0000 Subject: [PATCH] =?UTF-8?q?Forced=20ventilation=20is=20injected=20once=20a?= =?UTF-8?q?cross=20both=20fabric-first=20phases=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 | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/domain/modelling/optimisation/optimiser.py b/domain/modelling/optimisation/optimiser.py index 43a87dfce..4a5edd28e 100644 --- a/domain/modelling/optimisation/optimiser.py +++ b/domain/modelling/optimisation/optimiser.py @@ -271,13 +271,23 @@ def optimise_package_fabric_first( leftover_budget: Optional[float] = ( None if budget is None else budget - _package_cost(fabric_package.selected) ) + # A dependency phase 1 already injected (e.g. the wall's ventilation) is + # satisfied for the whole package: phase 2 must not force it in again. + fabric_types: set[MeasureType] = { + scored.option.measure_type for scored in fabric_package.selected + } + outstanding_dependencies: list[MeasureDependency] = [ + dependency + for dependency in dependencies + if dependency.required.option.measure_type not in fabric_types + ] top_up: OptimisedPackage = optimise_package( groups=_rescored_groups(remaining_groups, post_fabric_scorer, baseline_epc), scorer=post_fabric_scorer, baseline_epc=baseline_epc, budget=leftover_budget, target_sap=target_sap, - dependencies=dependencies, + dependencies=outstanding_dependencies, ) return OptimisedPackage( selected=[*fabric_package.selected, *top_up.selected],