mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-08 11:17:27 +00:00
Expand half of the recommendation_materials retirement (ADR-0017). A Plan Measure installs a single Product, so thread its catalogue id end to end — Product.id -> MeasureOption.material_id -> PlanMeasure.material_id -> recommendation.material_id — replacing the per-material BOM child table with one nullable column on the row. ProductPostgresRepository reads the id from MaterialRow; the four fabric generators set it on their Option; the orchestrator carries it onto the Plan Measure; the mirror declares + maps the column. Optional throughout (the JSON stopgap catalogue carries no ids -> NULL). The multi-measure integration test now pins each persisted measure's material_id to its seeded MaterialRow id. Migration spec (live column must be added before this deploys; contraction is the owner's next step) in docs/migrations/recommendation-material-id.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
48 lines
1.6 KiB
Python
48 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.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: str
|
|
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, ...]
|