mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-07-12 13:29:04 +00:00
Tighten the recommendation/plan vocabulary off generic str: MeasureOption.measure_type and PlanMeasure.measure_type are now MeasureType (also _GlazingTarget.measure_type, MeasureDependency.triggers -> frozenset[MeasureType], and the optimiser's chosen/required-type locals). Because MeasureType is a StrEnum the change is transparent to persistence (the `recommendation` varchar column), the optimiser group-by key, and every `== "solar_pv"`-style comparison — so pyright now enforces the enum at every construction site with no runtime behaviour change. The catalogue boundary stays str: ProductRepository.get(measure_type: str) and Product.measure_type are unchanged (they map arbitrary DB/JSON rows), so the fake product repos in tests need no edit. Test construction helpers coerce their str arg via MeasureType(...); direct constructions use members. Suite green: tests/domain/modelling + orchestration + harness 253 pass + 3 xfail; pyright clean on production + tests (pre-existing moto + property- override-rowcount baselines untouched). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
49 lines
1.6 KiB
Python
49 lines
1.6 KiB
Python
"""Recommendation and Measure Option — the Modelling stage's proposal types.
|
|
|
|
A Recommendation is a labelled group of mutually-exclusive Measure Options for
|
|
one target surface; the Optimiser selects at most one. The target itself is
|
|
encoded entirely in each Option's Simulation Overlay (which addresses building
|
|
parts, windows, or systems), so this type stays stable as new surfaces land.
|
|
Impact is never stored here — it is cascade-conditional (ADR-0016). See
|
|
CONTEXT.md.
|
|
"""
|
|
|
|
from dataclasses import dataclass
|
|
from typing import Optional
|
|
|
|
from domain.modelling.measure_type import MeasureType
|
|
from domain.modelling.simulation import EpcSimulation
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class Cost:
|
|
"""A Measure Option's cost: a single fully-loaded total (labour + VAT +
|
|
preliminaries + margin rolled in) plus a separately-carried per-Measure-Type
|
|
contingency rate."""
|
|
|
|
total: float
|
|
contingency_rate: float
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class MeasureOption:
|
|
"""One mutually-exclusive way to treat a Recommendation's surface."""
|
|
|
|
measure_type: MeasureType
|
|
description: str
|
|
overlay: EpcSimulation
|
|
cost: Optional[Cost] = None
|
|
# The catalogue id of the Product this Option installs (Product.id), carried
|
|
# through to the persisted Plan Measure's ``material_id``. None when priced
|
|
# from a catalogue with no ids.
|
|
material_id: Optional[int] = None
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class Recommendation:
|
|
"""A target surface and the mutually-exclusive Measure Options that treat
|
|
it. `surface` is a human label for display/grouping; the actual target is
|
|
in each Option's overlay."""
|
|
|
|
surface: str
|
|
options: tuple[MeasureOption, ...]
|