"""The Simulation Overlay (`EpcSimulation`) — the change a single Measure Option makes to a Property's EpcPropertyData. An all-optional partial mirror of EpcPropertyData / SapBuildingPart, covering the retrofit-relevant surface only (wall fields first). It is *not* an EpcPropertyData — composition, not inheritance — and carries no scores. Building parts are targeted by `BuildingPartIdentifier` so a measure addresses the exact `SapBuildingPart` (the main wall vs an extension). See CONTEXT.md. """ from dataclasses import dataclass, field from typing import Mapping, Optional from datatypes.epc.domain.epc_property_data import BuildingPartIdentifier @dataclass(frozen=True) class BuildingPartOverlay: """All-optional partial of `SapBuildingPart` (wall surface first). A `None` field means "leave the baseline value unchanged". """ wall_insulation_type: Optional[int] = None # Added solid-wall insulation depth (mm) — drives the calculator's Table 6 # bucket / §5.8 documentary U-value for EWI (`wall_insulation_type=1`) and # IWI (`wall_insulation_type=3`); λ defaults to 0.04 W/m·K in the calculator. wall_insulation_thickness: Optional[int] = None roof_insulation_thickness: Optional[int] = None floor_insulation_thickness: Optional[int] = None floor_insulation_type_str: Optional[str] = None @dataclass(frozen=True) class VentilationOverlay: """All-optional partial of `SapVentilation` — the whole-dwelling ventilation change a Measure Option makes (e.g. retrofit MEV). Unlike a `BuildingPartOverlay` this targets no building part; it folds onto the dwelling's single `sap_ventilation`. `mechanical_ventilation_kind` names the SAP10.2 §2 mechanical-ventilation kind (the `MechanicalVentilationKind` enum name, e.g. ``"EXTRACT_OR_PIV_OUTSIDE"`` for decentralised MEV). A `None` field means "leave the baseline value unchanged". """ mechanical_ventilation_kind: Optional[str] = None @dataclass(frozen=True) class WindowOverlay: """All-optional partial of one `SapWindow` — the change a glazing Measure makes to a single window (ADR-0022). `glazing_type` is the SAP10.2 Table U2 code (drives only the §5 daylight factor when a per-window U is lodged). `u_value` and `solar_transmittance` are written into the window's `WindowTransmissionDetails` — where the calculator reads heat loss and solar gain from — because our calculator consumes the lodged values directly rather than deriving them from `glazing_type`. A `None` field means "leave the baseline value unchanged". """ glazing_type: Optional[int] = None u_value: Optional[float] = None solar_transmittance: Optional[float] = None @dataclass(frozen=True) class LightingOverlay: """All-optional partial of the dwelling's fixed-lighting bulb counts — the whole-dwelling lighting change a Measure Option makes (e.g. an all-LED upgrade — ADR-0023). Unlike a `BuildingPartOverlay` or `WindowOverlay` this targets no building part or window; its fields are the four **top-level** `EpcPropertyData` bulb counts, folded directly by name. The counts are absolute target states, not deltas (an all-LED upgrade sets ``led = total``, the rest 0). A `None` field means "leave the baseline value unchanged". """ led_fixed_lighting_bulbs_count: Optional[int] = None cfl_fixed_lighting_bulbs_count: Optional[int] = None incandescent_fixed_lighting_bulbs_count: Optional[int] = None low_energy_fixed_lighting_bulbs_count: Optional[int] = None def _no_building_parts() -> dict[BuildingPartIdentifier, BuildingPartOverlay]: return {} def _no_windows() -> dict[int, WindowOverlay]: return {} @dataclass(frozen=True) class EpcSimulation: """A Simulation Overlay: the per-building-part changes a Measure Option makes, keyed by `BuildingPartIdentifier`; per-window changes keyed by the `sap_windows` index; plus an optional whole-dwelling `ventilation` change (the Measure Dependency surface — ADR-0016).""" building_parts: Mapping[BuildingPartIdentifier, BuildingPartOverlay] = field( default_factory=_no_building_parts ) windows: Mapping[int, WindowOverlay] = field(default_factory=_no_windows) ventilation: Optional[VentilationOverlay] = None lighting: Optional[LightingOverlay] = None