mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-08 11:17:27 +00:00
EpcSimulation is the Simulation Overlay — a narrow all-optional partial mirror of EpcPropertyData/SapBuildingPart (wall surface first), targeting building parts by BuildingPartIdentifier (composition, not inheritance). apply_simulations(baseline, simulations) deep-copies the baseline, folds overlays in order (later wins on a shared field) via a generic non-None field write, and returns a throwaway EpcPropertyData for the calculator; the baseline is never mutated. Four behaviour tests (hand-built EPD from the 000490 fixture, no PDF): targeted-write-leaves-others-untouched, empty-overlay no-op, sequential last-wins, baseline-immutability. pyright strict clean. Slice 1 of the Modelling stage rebuild (ADR-0016). Closes #1153. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
101 lines
2.9 KiB
Python
101 lines
2.9 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,
|
|
)
|
|
from domain.modelling.simulation import BuildingPartOverlay, EpcSimulation
|
|
from domain.modelling.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_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
|
|
)
|