mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-08 11:17:27 +00:00
Slice 2 of #1157. The per-Property output of one Scenario's modelling run, per ADR-0017. - PlanMeasure: a selected Measure Option frozen with its installed Cost and role-3 (final-package cascade) attributed MeasureImpact — the output counterpart of a Recommendation's candidate Option. - Plan: the selected Plan Measures + baseline/post-retrofit Scores. Single-phase (ADR-0005); derives the persisted headline figures — cost_of_works, contingency_cost, co2_savings_kg_per_yr (kg; the mapper converts to tonnes), post_sap_continuous, and post_epc_rating (band from the rounded SAP via Epc.from_sap_score). 1 unit test, pyright strict clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
73 lines
2.6 KiB
Python
73 lines
2.6 KiB
Python
"""Plan and Plan Measure — the Modelling stage's persisted output (ADR-0017).
|
||
|
||
A **Plan** is the per-Property output of one Scenario's modelling run: the
|
||
selected **Optimised Package** (its **Plan Measures**) plus the Property's
|
||
post-retrofit figures. It is single-phase — multi-phase is deferred
|
||
(ADR-0005) — so the headline figures are flat on the Plan.
|
||
|
||
A **Plan Measure** is the *output* counterpart of a Recommendation's candidate
|
||
Option: the one Option the Optimiser kept, frozen with its installed **Cost**
|
||
and its final-package (role-3) attributed **impact**. See CONTEXT.md.
|
||
"""
|
||
|
||
from dataclasses import dataclass
|
||
|
||
from datatypes.epc.domain.epc import Epc
|
||
from domain.modelling.package_scorer import Score
|
||
from domain.modelling.recommendation import Cost
|
||
from domain.modelling.scoring import MeasureImpact
|
||
|
||
|
||
@dataclass(frozen=True)
|
||
class PlanMeasure:
|
||
"""One selected Measure Option as it lands in a Plan: the measure, its
|
||
installed Cost, and its role-3 (final-package cascade) attributed impact."""
|
||
|
||
measure_type: str
|
||
description: str
|
||
cost: Cost
|
||
impact: MeasureImpact
|
||
|
||
|
||
@dataclass(frozen=True)
|
||
class Plan:
|
||
"""A Property's Plan for one Scenario: the selected Plan Measures and the
|
||
baseline / post-retrofit whole-package Scores. The persisted headline
|
||
figures are derived from these (cost aggregates, CO₂ saving, post band)."""
|
||
|
||
measures: tuple[PlanMeasure, ...]
|
||
baseline: Score
|
||
post_retrofit: Score
|
||
|
||
@property
|
||
def cost_of_works(self) -> float:
|
||
"""Sum of the Plan Measures' fully-loaded Costs."""
|
||
return sum((measure.cost.total for measure in self.measures), 0.0)
|
||
|
||
@property
|
||
def contingency_cost(self) -> float:
|
||
"""Sum of each Plan Measure's contingency (its Cost total × its
|
||
per-Measure-Type contingency rate)."""
|
||
return sum(
|
||
(
|
||
measure.cost.total * measure.cost.contingency_rate
|
||
for measure in self.measures
|
||
),
|
||
0.0,
|
||
)
|
||
|
||
@property
|
||
def post_sap_continuous(self) -> float:
|
||
"""The whole-package re-score's un-rounded SAP rating."""
|
||
return self.post_retrofit.sap_continuous
|
||
|
||
@property
|
||
def post_epc_rating(self) -> Epc:
|
||
"""The post-retrofit EPC band, from the rounded SAP rating."""
|
||
return Epc.from_sap_score(round(self.post_retrofit.sap_continuous))
|
||
|
||
@property
|
||
def co2_savings_kg_per_yr(self) -> float:
|
||
"""Whole-package CO₂ reduction (kg/yr) vs the baseline re-score. The
|
||
persistence mapper converts to tonnes for the live column contract."""
|
||
return self.baseline.co2_kg_per_yr - self.post_retrofit.co2_kg_per_yr
|