"""Shared fixtures for the Optimiser test files: distinguishable Simulation Overlays (so a stub scorer can attribute a true gain per measure kind), the ScoredOption builder, the additive per-kind stub scorer, and the forced ventilation Measure Dependency edge. Fixture values stay domain-plausible: overlay heating codes are real SAP Table 4a codes (104 = mains-gas combi boiler, heat pumps carry a PCDF index), and tests price measures at realistic magnitudes (a CWI around £1,000, an ASHP around £8,000).""" from __future__ import annotations from typing import Optional, Sequence from datatypes.epc.domain.epc_property_data import ( BuildingPartIdentifier, EpcPropertyData, ) from domain.modelling.measure_type import MeasureType from domain.modelling.optimisation.optimiser import MeasureDependency, ScoredOption from domain.modelling.recommendation import Cost, MeasureOption from domain.modelling.scoring.package_scorer import Score from domain.modelling.simulation import ( BuildingPartOverlay, EpcSimulation, GlazingOverlay, HeatingOverlay, VentilationOverlay, ) WALL_OVERLAY = EpcSimulation( building_parts={ BuildingPartIdentifier.MAIN: BuildingPartOverlay(wall_insulation_type=2) } ) ROOF_OVERLAY = EpcSimulation( building_parts={ BuildingPartIdentifier.MAIN: BuildingPartOverlay(roof_insulation_thickness=300) } ) FLOOR_OVERLAY = EpcSimulation( building_parts={ BuildingPartIdentifier.MAIN: BuildingPartOverlay(floor_insulation_thickness=100) } ) GLAZING_OVERLAY = EpcSimulation(glazing=GlazingOverlay(glazing_type=2)) VENT_OVERLAY = EpcSimulation( ventilation=VentilationOverlay(mechanical_ventilation_kind="EXTRACT_OR_PIV_OUTSIDE") ) # SAP Table 4a code 104: a mains-gas combi boiler. BOILER_OVERLAY = EpcSimulation(heating=HeatingOverlay(sap_main_heating_code=104)) # Heat pumps are expressed as a PCDF product index, as the generator emits them. ASHP_OVERLAY = EpcSimulation(heating=HeatingOverlay(main_heating_index_number=13000)) _WALL_TRIGGERS: frozenset[MeasureType] = frozenset( {MeasureType.CAVITY_WALL_INSULATION, MeasureType.EXTERNAL_WALL_INSULATION} ) def scored_option( measure_type: str, *, gain: float, cost: float, overlay: Optional[EpcSimulation] = None, ) -> ScoredOption: """A one-Option fixture: ``gain`` is the role-1 warm-start signal, ``cost`` the total install cost. Omit ``overlay`` where the test never re-scores.""" return ScoredOption( option=MeasureOption( measure_type=MeasureType(measure_type), description=measure_type, overlay=overlay if overlay is not None else EpcSimulation(), cost=Cost(total=cost, contingency_rate=0.0), ), sap_gain=gain, ) class StubScorer: """A deterministic stand-in for PackageScorer: the package SAP is a base plus a fixed *true* gain per measure kind present (detected by overlay field), decoupled from the role-1 signal — so selection, repair and the two-phase split are exercised without the calculator. Kinds a test does not use default to 0.""" def __init__( self, *, base: float, wall: float = 0.0, roof: float = 0.0, floor: float = 0.0, heating: float = 0.0, vent: float = 0.0, ) -> None: self._base = base self._wall = wall self._roof = roof self._floor = floor self._heating = heating self._vent = vent def score( self, baseline: EpcPropertyData, simulations: Sequence[EpcSimulation] ) -> Score: sap = self._base for sim in simulations: if sim.heating is not None: sap += self._heating if sim.ventilation is not None: sap += self._vent for part in sim.building_parts.values(): if part.wall_insulation_type is not None: sap += self._wall if part.roof_insulation_thickness is not None: sap += self._roof if part.floor_insulation_thickness is not None: sap += self._floor return Score( sap_continuous=sap, co2_kg_per_yr=0.0, primary_energy_kwh_per_yr=0.0 ) def selected_types(selection: Sequence[ScoredOption]) -> set[str]: return {scored.option.measure_type for scored in selection} def ventilation_dependency( *, cost: float, triggers: frozenset[MeasureType] = _WALL_TRIGGERS ) -> MeasureDependency: """A forced 'airtightness requires ventilation' edge for the tests.""" return MeasureDependency( triggers=triggers, required=ScoredOption( option=MeasureOption( measure_type=MeasureType.MECHANICAL_VENTILATION, description="mechanical_ventilation", overlay=VENT_OVERLAY, cost=Cost(total=cost, contingency_rate=0.0), ), sap_gain=0.0, # placeholder; optimise_package scores the real signal ), )