Model/tests/domain/modelling/test_plan.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

47 lines
1.9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""Behaviour of the Plan / PlanMeasure domain types: the per-Property output
of one Scenario's modelling run. A Plan carries its selected Plan Measures
(the Optimised Package) plus the baseline/post-retrofit Scores, and derives
the persisted headline figures (cost aggregates, CO₂ saving, post-retrofit
band). Single-phase, flat post-retrofit figures (ADR-0005 / ADR-0017).
"""
from __future__ import annotations
from datatypes.epc.domain.epc import Epc
from domain.modelling.scoring.package_scorer import Score
from domain.modelling.plan import Plan, PlanMeasure
from domain.modelling.recommendation import Cost
from domain.modelling.scoring.scoring import MeasureImpact
def _measure(measure_type: str, total: float, rate: float) -> PlanMeasure:
return PlanMeasure(
measure_type=measure_type,
description=measure_type.replace("_", " "),
cost=Cost(total=total, contingency_rate=rate),
impact=MeasureImpact(
sap_points=1.0, co2_savings_kg_per_yr=1.0, energy_savings_kwh_per_yr=1.0
),
)
def test_plan_aggregates_cost_and_savings_and_bands_the_post_sap() -> None:
# Arrange
measures: tuple[PlanMeasure, ...] = (
_measure("cavity_wall_insulation", total=1000.0, rate=0.10),
_measure("loft_insulation", total=500.0, rate=0.20),
)
baseline = Score(
sap_continuous=40.0, co2_kg_per_yr=4000.0, primary_energy_kwh_per_yr=20000.0
)
post = Score(
sap_continuous=70.4, co2_kg_per_yr=3600.0, primary_energy_kwh_per_yr=18000.0
)
plan = Plan(measures=measures, baseline=baseline, post_retrofit=post)
# Act / Assert
assert abs(plan.cost_of_works - 1500.0) <= 1e-9
assert abs(plan.contingency_cost - 200.0) <= 1e-9 # 1000*0.10 + 500*0.20
assert abs(plan.co2_savings_kg_per_yr - 400.0) <= 1e-9 # baseline - post
assert abs(plan.post_sap_continuous - 70.4) <= 1e-9
assert plan.post_epc_rating is Epc.C # round(70.4) = 70 → band C (6980)