mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-08 11:17:27 +00:00
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>
109 lines
3.8 KiB
Python
109 lines
3.8 KiB
Python
"""Behaviour of the ventilation Measure Dependency builder: the data-declared
|
|
"fabric insulation requires adequate ventilation" edge, guarded by the
|
|
dwelling's existing ventilation. See CONTEXT.md (Measure Dependency) / ADR-0016.
|
|
"""
|
|
|
|
from typing import Optional
|
|
|
|
from datatypes.epc.domain.epc_property_data import EpcPropertyData
|
|
from domain.modelling.optimisation.measure_dependency import (
|
|
MEASURES_NEEDING_VENTILATION,
|
|
ventilation_dependency,
|
|
)
|
|
from domain.modelling.optimisation.optimiser import MeasureDependency
|
|
from domain.modelling.product import Product
|
|
from domain.modelling.simulation import EpcSimulation, VentilationOverlay
|
|
from repositories.product.product_repository import ProductRepository
|
|
from tests.domain.sap10_calculator.worksheet._elmhurst_worksheet_000490 import (
|
|
build_epc,
|
|
)
|
|
|
|
|
|
class _StubProducts(ProductRepository):
|
|
"""In-memory ProductRepository returning a fixed per-unit ventilation cost."""
|
|
|
|
def get(self, measure_type: str) -> Product:
|
|
# unit_cost_per_m2 carries the catalogue row's fully-loaded total cost;
|
|
# for ventilation that total is per installed unit.
|
|
return Product(
|
|
measure_type=measure_type, unit_cost_per_m2=450.0, contingency_rate=0.10
|
|
)
|
|
|
|
|
|
def test_triggers_are_the_fabric_wall_measures() -> None:
|
|
# Arrange / Act / Assert — the data-held trigger set (cf. legacy
|
|
# assumptions.measures_needing_ventilation).
|
|
assert MEASURES_NEEDING_VENTILATION == frozenset(
|
|
{
|
|
"cavity_wall_insulation",
|
|
"internal_wall_insulation",
|
|
"external_wall_insulation",
|
|
}
|
|
)
|
|
|
|
|
|
def test_builds_a_ventilation_dependency_for_a_naturally_ventilated_dwelling() -> None:
|
|
# Arrange — 000490 lodges no mechanical ventilation kind.
|
|
baseline: EpcPropertyData = build_epc()
|
|
|
|
# Act
|
|
dependency: Optional[MeasureDependency] = ventilation_dependency(
|
|
baseline, _StubProducts()
|
|
)
|
|
|
|
# Assert — a forced edge whose required Option installs MEV.
|
|
assert dependency is not None
|
|
assert dependency.triggers == MEASURES_NEEDING_VENTILATION
|
|
option = dependency.required.option
|
|
assert option.measure_type == "mechanical_ventilation"
|
|
assert isinstance(option.overlay, EpcSimulation)
|
|
assert option.overlay.ventilation == VentilationOverlay(
|
|
mechanical_ventilation_kind="EXTRACT_OR_PIV_OUTSIDE"
|
|
)
|
|
|
|
|
|
def test_dependency_costs_two_installed_units_with_contingency() -> None:
|
|
# Arrange
|
|
baseline: EpcPropertyData = build_epc()
|
|
|
|
# Act
|
|
dependency: Optional[MeasureDependency] = ventilation_dependency(
|
|
baseline, _StubProducts()
|
|
)
|
|
|
|
# Assert — two MEV units at £450 each, carrying the product's contingency.
|
|
assert dependency is not None
|
|
cost = dependency.required.option.cost
|
|
assert cost is not None
|
|
assert abs(cost.total - 900.0) <= 1e-9
|
|
assert abs(cost.contingency_rate - 0.10) <= 1e-9
|
|
|
|
|
|
def test_no_dependency_when_already_mechanically_ventilated() -> None:
|
|
# Arrange — the dwelling already has a mechanical ventilation kind, so MEV
|
|
# must not be forced on (legacy has_ventilation guard).
|
|
baseline: EpcPropertyData = build_epc()
|
|
assert baseline.sap_ventilation is not None
|
|
baseline.sap_ventilation.mechanical_ventilation_kind = "EXTRACT_OR_PIV_OUTSIDE"
|
|
|
|
# Act
|
|
dependency: Optional[MeasureDependency] = ventilation_dependency(
|
|
baseline, _StubProducts()
|
|
)
|
|
|
|
# Assert
|
|
assert dependency is None
|
|
|
|
|
|
def test_builds_a_dependency_when_the_dwelling_lodged_no_ventilation() -> None:
|
|
# Arrange — no SapVentilation at all counts as not mechanically ventilated.
|
|
baseline: EpcPropertyData = build_epc()
|
|
baseline.sap_ventilation = None
|
|
|
|
# Act
|
|
dependency: Optional[MeasureDependency] = ventilation_dependency(
|
|
baseline, _StubProducts()
|
|
)
|
|
|
|
# Assert
|
|
assert dependency is not None
|