Model/tests/domain/epc/test_construction_age_band_overlay.py
Jun-te Kim 3a0a122b7f Group landlord property-override enums under domain/epc/property_overrides 🟪
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 14:49:58 +00:00

109 lines
3.5 KiB
Python

"""The Landlord-Override construction-age-band → fabric Simulation Overlay.
An age-band value resolves to the RdSAP letter code the calculator's U-value
cascades read from `SapBuildingPart.construction_age_band`; the overlay targets
the override's building part.
"""
from __future__ import annotations
import pytest
from datatypes.epc.domain.epc_property_data import (
BuildingPartIdentifier,
EpcPropertyData,
SapBuildingPart,
)
from domain.epc.property_overrides.construction_age_band import ConstructionAgeBand
from domain.epc.property_overlays.construction_age_band_overlay import (
age_band_overlay_for,
)
from domain.modelling.scoring.overlay_applicator import apply_simulations
from tests.domain.sap10_calculator.worksheet._elmhurst_worksheet_000490 import (
build_epc,
)
def _part(
epc: EpcPropertyData, identifier: BuildingPartIdentifier
) -> SapBuildingPart:
return next(p for p in epc.sap_building_parts if p.identifier is identifier)
def test_age_band_overlays_the_main_building_part() -> None:
# Act — band B (1900-1929) on the main building part.
simulation = age_band_overlay_for("B", 0)
# Assert
assert simulation is not None
overlay = simulation.building_parts[BuildingPartIdentifier.MAIN]
assert overlay.construction_age_band == "B"
def test_age_band_overlay_targets_the_extension_building_part() -> None:
# Act — building_part 1 is the first extension.
simulation = age_band_overlay_for("L", 1)
# Assert
assert simulation is not None
assert BuildingPartIdentifier.EXTENSION_1 in simulation.building_parts
assert (
simulation.building_parts[BuildingPartIdentifier.EXTENSION_1]
.construction_age_band
== "L"
)
def test_lowercase_age_band_is_normalised_to_its_letter_code() -> None:
# Act
simulation = age_band_overlay_for("d", 0)
# Assert — the calculator upper-cases the band; the overlay stores it upper.
assert simulation is not None
assert (
simulation.building_parts[BuildingPartIdentifier.MAIN].construction_age_band
== "D"
)
@pytest.mark.parametrize("age_band_value", ["Z", "", "1900-1929", "Unknown"])
def test_unrecognised_age_band_produces_no_overlay(age_band_value: str) -> None:
# Act
simulation = age_band_overlay_for(age_band_value, 0)
# Assert
assert simulation is None
def test_age_band_override_re_dates_the_main_part_only() -> None:
# Arrange — baseline main + extension are both band B; the landlord corrects
# the main building's age band to F (1976-1982).
baseline = build_epc()
overlay = age_band_overlay_for("F", 0)
assert overlay is not None
# Act
result = apply_simulations(baseline, [overlay])
# Assert — the main part is re-dated (its U-value cascade now keys on F); the
# extension is left untouched.
assert _part(result, BuildingPartIdentifier.MAIN).construction_age_band == "F"
assert (
_part(result, BuildingPartIdentifier.EXTENSION_1).construction_age_band == "B"
)
@pytest.mark.parametrize(
"member", [m for m in ConstructionAgeBand if m is not ConstructionAgeBand.UNKNOWN]
)
def test_every_resolvable_age_band_value_decodes_to_an_overlay(
member: ConstructionAgeBand,
) -> None:
# A classifier emits a ConstructionAgeBand value; if the overlay can't decode
# it the override silently no-ops. Every non-UNKNOWN member must resolve.
# Act
simulation = age_band_overlay_for(member.value, 0)
# Assert
assert simulation is not None