"""The ventilation Recommendation Generator. Detects a dwelling that lacks adequate mechanical ventilation and emits a Recommendation whose single Measure Option installs decentralised mechanical extract ventilation (MEV), priced per installed unit. Like the wall/roof/floor generators it does detection + pricing and carries no scores (ADR-0016). Unlike them it is **not** run by the candidate-pool runner: ventilation is a forced Measure Dependency of fabric insulation (it only ever costs SAP, so the Optimiser would never choose it), so this Recommendation is consumed by ``optimisation.measure_dependency`` and injected into the package, never freely selected. The legacy intervention was "mechanical, extract only"; the guard mirrors legacy ``Property.has_ventilation``. """ from typing import Optional from datatypes.epc.domain.epc_property_data import EpcPropertyData from domain.modelling.measure_type import MeasureType from domain.modelling.recommendation import Cost, MeasureOption, Recommendation from domain.modelling.simulation import EpcSimulation, VentilationOverlay from repositories.product.product_repository import ProductRepository _VENTILATION_MEASURE_TYPE = MeasureType.MECHANICAL_VENTILATION # The SAP10.2 §2 mechanical-ventilation kind installed: decentralised MEV # ("mechanical extract, decentralised (MEV dc)" → MechanicalVentilationKind # name), the legacy "mechanical, extract only" intervention. _MEV_KIND = "EXTRACT_OR_PIV_OUTSIDE" # Best practice installs one MEV unit per wet zone; the legacy recommendation # fits two units per dwelling. _VENTILATION_UNIT_COUNT = 2 def recommend_ventilation( epc: EpcPropertyData, products: ProductRepository ) -> Optional[Recommendation]: """Return a mechanical-ventilation Recommendation for a dwelling that is not already mechanically ventilated, else None. The single Option installs MEV and is priced at two fully-loaded units.""" if _already_mechanically_ventilated(epc): return None product = products.get(_VENTILATION_MEASURE_TYPE) cost = Cost( total=product.unit_cost_per_m2 * _VENTILATION_UNIT_COUNT, contingency_rate=product.contingency_rate, ) option = MeasureOption( measure_type=_VENTILATION_MEASURE_TYPE, description=f"Install {_VENTILATION_UNIT_COUNT} mechanical extract ventilation units", overlay=EpcSimulation( ventilation=VentilationOverlay(mechanical_ventilation_kind=_MEV_KIND) ), cost=cost, material_id=product.id, ) return Recommendation(surface="Ventilation", options=(option,)) def _already_mechanically_ventilated(epc: EpcPropertyData) -> bool: """True when the dwelling already lodges a mechanical ventilation kind (MEV/MVHR) — the legacy `has_ventilation` guard.""" return ( epc.sap_ventilation is not None and epc.sap_ventilation.mechanical_ventilation_kind is not None )