"""The Landlord-Override glazing → glazing Simulation Overlay mapping. A glazing value resolves to the SAP10 `glazing_type` code the calculator's Table-24 cascade reads; the overlay is whole-dwelling (expanded across every window by `_fold_glazing`). """ from __future__ import annotations import pytest from domain.epc.property_overrides.glazing_type import GlazingType from domain.epc.property_overlays.glazing_overlay import glazing_overlay_for from domain.modelling.scoring.overlay_applicator import apply_simulations from tests.domain.sap10_calculator.worksheet._elmhurst_worksheet_000490 import ( build_epc, ) def test_double_glazing_post_2002_overlays_its_glazing_code() -> None: # Act simulation = glazing_overlay_for("Double glazing, 2002 or later", 0) # Assert — double glazing 2002-2021 is SAP10 glazing_type code 2. assert simulation is not None assert simulation.glazing is not None assert simulation.glazing.glazing_type == 2 @pytest.mark.parametrize( ("glazing_value", "code"), [ ("Single glazing", 1), ("Double glazing, pre-2002", 3), ("Triple glazing, 2002 or later", 9), ("Triple glazing, pre-2002", 6), ], ) def test_glazing_types_decode_to_their_sap_codes( glazing_value: str, code: int ) -> None: # Act simulation = glazing_overlay_for(glazing_value, 0) # Assert assert simulation is not None assert simulation.glazing is not None assert simulation.glazing.glazing_type == code def test_secondary_glazing_overlays_its_glazing_code() -> None: # Hyde-796 landlords assert "100% secondary glazing (sap 9.94)"; flattening it # to Single applies U = 4.8 instead of secondary's 2.9 (Model#1416). The # calculator already understands secondary glazing — SAP10 glazing_type code 5 # (`_GLAZING_CODE_TO_UWINDOW` in heat_transmission). # Act simulation = glazing_overlay_for("Secondary glazing", 0) # Assert assert simulation is not None assert simulation.glazing is not None assert simulation.glazing.glazing_type == 5 @pytest.mark.parametrize("glazing_value", ["Unknown", ""]) def test_unresolvable_glazing_produces_no_overlay(glazing_value: str) -> None: # Act simulation = glazing_overlay_for(glazing_value, 0) # Assert assert simulation is None def test_a_glazing_mix_produces_no_overlay_so_the_cert_is_kept() -> None: # A MIXED aggregate resolves to no overlay: a whole-dwelling proportion cannot # say which windows are which, so the cert's per-window `sap_windows` are kept # rather than flattened to one type (ADR-0042 amendment, #1376). # Act simulation = glazing_overlay_for(GlazingType.MIXED.value, 0) # Assert assert simulation is None def test_glazing_override_remaps_every_window_and_clears_lodged_u() -> None: # Arrange — baseline windows are double glazed (code 2, lodged U 2.8); the # landlord corrects the whole dwelling to single glazing. baseline = build_epc() assert len(baseline.sap_windows) > 1 overlay = glazing_overlay_for("Single glazing", 0) assert overlay is not None # Act result = apply_simulations(baseline, [overlay]) # Assert — every window flips to single (code 1) and its lodged transmission # U is cleared so the Table-24 cascade re-derives U from the new type. assert all(w.glazing_type == 1 for w in result.sap_windows) assert all(w.window_transmission_details is None for w in result.sap_windows) _NO_OVERLAY_SENTINELS = {GlazingType.UNKNOWN, GlazingType.MIXED} @pytest.mark.parametrize( "member", [m for m in GlazingType if m not in _NO_OVERLAY_SENTINELS] ) def test_every_resolvable_glazing_value_decodes_to_a_code( member: GlazingType, ) -> None: # A classifier emits a GlazingType value; if the overlay can't decode it the # override silently no-ops. Every member except the deliberate no-overlay # sentinels (UNKNOWN, MIXED) must resolve to a code. # Act simulation = glazing_overlay_for(member.value, 0) # Assert assert simulation is not None