"""Slice 3 — `restrict_to_considered_measures`, the pure allowlist that limits a run to a chosen set of measure types (mirroring the legacy engine's `inclusions`). It filters at the Option level, so a multi-option Recommendation (e.g. Heating & Hot Water offering both HHRSH and an ASHP bundle) is kept with only its allowed Options; a Recommendation left with no allowed Option is dropped entirely. A None allowlist means "consider everything" (today's unrestricted behaviour). """ from domain.modelling.considered_measures import ( combine_considered_measures, restrict_to_considered_measures, ) from domain.modelling.measure_type import MeasureType from domain.modelling.recommendation import MeasureOption, Recommendation from domain.modelling.simulation import EpcSimulation def _option(measure_type: MeasureType) -> MeasureOption: return MeasureOption( measure_type=measure_type, description=str(measure_type), overlay=EpcSimulation() ) def _heating_rec() -> Recommendation: # Heating & Hot Water competes HHRSH against an ASHP bundle in one rec. return Recommendation( surface="Heating & Hot Water", options=( _option(MeasureType.HIGH_HEAT_RETENTION_STORAGE_HEATERS), _option(MeasureType.AIR_SOURCE_HEAT_PUMP), ), ) def _solar_rec() -> Recommendation: return Recommendation(surface="Solar PV", options=(_option(MeasureType.SOLAR_PV),)) def _wall_rec() -> Recommendation: return Recommendation( surface="Wall", options=(_option(MeasureType.CAVITY_WALL_INSULATION),) ) def test_none_allowlist_keeps_everything() -> None: # Arrange recommendations = [_heating_rec(), _solar_rec(), _wall_rec()] # Act kept = restrict_to_considered_measures(recommendations, None) # Assert assert kept == recommendations def test_drops_recommendations_with_no_allowed_option() -> None: # Arrange considered = frozenset( {MeasureType.HIGH_HEAT_RETENTION_STORAGE_HEATERS, MeasureType.SOLAR_PV} ) # Act kept = restrict_to_considered_measures( [_heating_rec(), _solar_rec(), _wall_rec()], considered ) # Assert — the wall rec is gone; heating + solar survive. surfaces = {rec.surface for rec in kept} assert surfaces == {"Heating & Hot Water", "Solar PV"} def test_combine_treats_none_as_all_and_intersects_two_allowlists() -> None: # Arrange a = frozenset({MeasureType.SOLAR_PV, MeasureType.LOFT_INSULATION}) b = frozenset({MeasureType.SOLAR_PV, MeasureType.CAVITY_WALL_INSULATION}) # Act / Assert — None means "all", so it never narrows; two sets intersect. assert combine_considered_measures(None, b) == b assert combine_considered_measures(a, None) == a assert combine_considered_measures(None, None) is None assert combine_considered_measures(a, b) == frozenset({MeasureType.SOLAR_PV}) def test_filters_options_within_a_kept_recommendation() -> None: # Arrange — HHRSH is allowed but the competing ASHP bundle is not. considered = frozenset( {MeasureType.HIGH_HEAT_RETENTION_STORAGE_HEATERS, MeasureType.SOLAR_PV} ) # Act kept = restrict_to_considered_measures([_heating_rec()], considered) # Assert — the heating rec keeps ONLY its HHRSH option, ASHP is dropped. assert len(kept) == 1 kept_types = [option.measure_type for option in kept[0].options] assert kept_types == [MeasureType.HIGH_HEAT_RETENTION_STORAGE_HEATERS]