Model/tests/domain/modelling/test_overlay_applicator.py
Khalim Conn-Kowlessar 84ec6da032 refactor(modelling): group domain/modelling into generators/scoring/optimisation
domain/modelling/ had grown to 15 flat modules. Group the behavioural ones into
subpackages — generators/ (wall/roof/floor Recommendation Generators), scoring/
(overlay applicator, package scorer, role-1/3 scoring), optimisation/ (optimiser
+ measure dependency) — and leave the shared value-object vocabulary
(recommendation, plan, scenario, product, contingencies, simulation) flat at the
top, since it is imported everywhere. Pure move + import-path rewrite across 89
import sites; no behaviour change. 136 pass, pyright strict clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 13:48:36 +00:00

170 lines
5.1 KiB
Python

"""Behaviour of the Overlay Applicator: folding Simulation Overlays
(EpcSimulation) onto a baseline EpcPropertyData to produce a new one for
the calculator. See ADR-0016 and the Modelling glossary in CONTEXT.md.
"""
from datatypes.epc.domain.epc_property_data import (
BuildingPartIdentifier,
EpcPropertyData,
SapBuildingPart,
SapVentilation,
)
from domain.modelling.simulation import (
BuildingPartOverlay,
EpcSimulation,
VentilationOverlay,
)
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_apply_writes_targeted_building_part_and_leaves_others_untouched() -> None:
# Arrange
baseline: EpcPropertyData = build_epc()
extension_before: int | str = _part(
baseline, BuildingPartIdentifier.EXTENSION_1
).wall_insulation_type
simulation = EpcSimulation(
building_parts={
BuildingPartIdentifier.MAIN: BuildingPartOverlay(wall_insulation_type=1)
}
)
# Act
result: EpcPropertyData = apply_simulations(baseline, [simulation])
# Assert
assert _part(result, BuildingPartIdentifier.MAIN).wall_insulation_type == 1
assert (
_part(result, BuildingPartIdentifier.EXTENSION_1).wall_insulation_type
== extension_before
)
def test_empty_simulation_is_a_no_op() -> None:
# Arrange
baseline: EpcPropertyData = build_epc()
# Act
result: EpcPropertyData = apply_simulations(baseline, [EpcSimulation()])
# Assert
assert result == baseline
def test_later_simulation_wins_on_a_shared_field() -> None:
# Arrange
baseline: EpcPropertyData = build_epc()
first = EpcSimulation(
building_parts={
BuildingPartIdentifier.MAIN: BuildingPartOverlay(wall_insulation_type=1)
}
)
second = EpcSimulation(
building_parts={
BuildingPartIdentifier.MAIN: BuildingPartOverlay(wall_insulation_type=2)
}
)
# Act
result: EpcPropertyData = apply_simulations(baseline, [first, second])
# Assert
assert _part(result, BuildingPartIdentifier.MAIN).wall_insulation_type == 2
def test_apply_writes_dwelling_ventilation_onto_sap_ventilation() -> None:
# Arrange — a Measure Dependency overlay targets the whole-dwelling
# ventilation system (no building part), e.g. retrofit MEV.
baseline: EpcPropertyData = build_epc()
simulation = EpcSimulation(
ventilation=VentilationOverlay(
mechanical_ventilation_kind="EXTRACT_OR_PIV_OUTSIDE"
)
)
# Act
result: EpcPropertyData = apply_simulations(baseline, [simulation])
# Assert
assert result.sap_ventilation is not None
assert (
result.sap_ventilation.mechanical_ventilation_kind
== "EXTRACT_OR_PIV_OUTSIDE"
)
def test_ventilation_overlay_creates_sap_ventilation_when_baseline_has_none() -> None:
# Arrange — a naturally-ventilated baseline that lodged no SapVentilation.
baseline: EpcPropertyData = build_epc()
baseline.sap_ventilation = None
simulation = EpcSimulation(
ventilation=VentilationOverlay(
mechanical_ventilation_kind="EXTRACT_OR_PIV_OUTSIDE"
)
)
# Act
result: EpcPropertyData = apply_simulations(baseline, [simulation])
# Assert
assert isinstance(result.sap_ventilation, SapVentilation)
assert (
result.sap_ventilation.mechanical_ventilation_kind
== "EXTRACT_OR_PIV_OUTSIDE"
)
def test_ventilation_overlay_leaves_building_parts_and_baseline_untouched() -> None:
# Arrange
baseline: EpcPropertyData = build_epc()
main_before: int | str = _part(
baseline, BuildingPartIdentifier.MAIN
).wall_insulation_type
simulation = EpcSimulation(
ventilation=VentilationOverlay(
mechanical_ventilation_kind="EXTRACT_OR_PIV_OUTSIDE"
)
)
# Act
result: EpcPropertyData = apply_simulations(baseline, [simulation])
# Assert — ventilation overlay touches only sap_ventilation; the baseline
# is never mutated.
assert _part(result, BuildingPartIdentifier.MAIN).wall_insulation_type == main_before
assert baseline.sap_ventilation is not None
assert baseline.sap_ventilation.mechanical_ventilation_kind is None
def test_baseline_is_not_mutated() -> None:
# Arrange
baseline: EpcPropertyData = build_epc()
original: int | str = _part(
baseline, BuildingPartIdentifier.MAIN
).wall_insulation_type
# Act
_: EpcPropertyData = apply_simulations(
baseline,
[
EpcSimulation(
building_parts={
BuildingPartIdentifier.MAIN: BuildingPartOverlay(
wall_insulation_type=1
)
}
)
],
)
# Assert
assert (
_part(baseline, BuildingPartIdentifier.MAIN).wall_insulation_type == original
)