Model/tests/domain/modelling/test_measure_dependency.py
Khalim Conn-Kowlessar 02afc04ce2 refactor(modelling): ventilation_dependency delegates to the generator + wraps
measure_dependency.py now owns only the selection semantics: the trigger set and
the forced-edge wrapping. It delegates production (detection + pricing) to
recommend_ventilation and wraps the returned Recommendation into the
MeasureDependency, picking the cheapest Option (one MEV today; readies the seam
for MEV-c / MVHR). The orchestrator's _measure_dependencies call is unchanged.
Trimmed the now-redundant option-detail assertions — those live in
test_ventilation_recommendation. 138 pass, behaviour-preserving.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 14:04:17 +00:00

79 lines
2.9 KiB
Python

"""Behaviour of the ventilation Measure Dependency: the data-declared "fabric
insulation requires adequate ventilation" edge. Production (detection + pricing)
is the ventilation Recommendation Generator's job and is tested in
test_ventilation_recommendation; here we test the forced-edge wrapping and the
trigger set. 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 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:
return Product(
measure_type=measure_type, unit_cost_per_m2=450.0, contingency_rate=0.26
)
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_wraps_the_priced_recommendation_into_a_forced_edge() -> None:
# Arrange — 000490 needs ventilation, so the generator produces a priced MEV
# Recommendation that the dependency wraps.
baseline: EpcPropertyData = build_epc()
# Act
dependency: Optional[MeasureDependency] = ventilation_dependency(
baseline, _StubProducts()
)
# Assert — a forced edge triggered by the fabric measures; the required
# Option carries the generator's price and no role-1 signal (it is never
# freely scored).
assert dependency is not None
assert dependency.triggers == MEASURES_NEEDING_VENTILATION
assert dependency.required.option.measure_type == "mechanical_ventilation"
assert dependency.required.sap_gain == 0.0
cost = dependency.required.option.cost
assert cost is not None
assert abs(cost.total - 900.0) <= 1e-9
def test_no_dependency_when_the_dwelling_needs_no_ventilation() -> None:
# Arrange — already mechanically ventilated, so the generator returns None
# and there is no edge to force.
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