mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-07-12 13:29:04 +00:00
The measures a run considers should come from the Scenario, not a CLI flag.
The live scenario table persists exclusions only (no inclusions column), as a
Postgres text-array of exact MeasureType values.
- Scenario gains `exclusions: frozenset[MeasureType]` + `considered_measures()`
(all measures minus the excluded ones, or None when none are excluded).
- ScenarioModel.to_domain parses the `{a,b,c}` exclusions array into
MeasureTypes, raising on a token that is not an exact MeasureType value
(no high-level category expansion), per the strict-enum convention.
- ModellingOrchestrator._plan_for derives the allowlist from the Scenario's
exclusions, combined (intersection) with any explicit considered_measures
override via the new `combine_considered_measures`.
- run_modelling_e2e sources the allowlist from the Scenario; --measures /
--exclude-measures become optional overlays (e.g. the technical
secondary_heating_removal exclusion the catalogue cannot yet stock).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
44 lines
1.7 KiB
Python
44 lines
1.7 KiB
Python
"""Scenario — the named retrofit brief the Modelling stage scores against.
|
|
|
|
Built by a user in the scenario-builder UI and persisted before any modelling
|
|
fires; the pipeline is handed only its id and reads it back via a
|
|
`ScenarioRepository`. This is the thin slice the Modelling stage uses today:
|
|
the goal + budget that the Optimiser will consume (#1160) and `is_default`
|
|
(which drives `plan.is_default`). The legacy file-path / portfolio-aggregate
|
|
columns are not modelled. Carries no phases — multi-phase is deferred
|
|
(ADR-0005). See CONTEXT.md.
|
|
"""
|
|
|
|
from dataclasses import dataclass
|
|
from typing import Optional
|
|
|
|
from domain.modelling.measure_type import MeasureType
|
|
|
|
_NO_EXCLUSIONS: frozenset[MeasureType] = frozenset()
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class Scenario:
|
|
"""A retrofit brief: its goal, optional budget, and whether it is the
|
|
Property's default Scenario. `goal` / `goal_value` are the lodged target
|
|
(e.g. "INCREASING_EPC" → band "C"); carried for the Optimiser, not yet
|
|
enforced.
|
|
|
|
`exclusions` are the measure types the brief bars from the run (the only
|
|
measure-scoping the live ``scenario`` table persists — there is no
|
|
inclusions column). Empty means nothing is barred."""
|
|
|
|
id: int
|
|
goal: str
|
|
goal_value: str
|
|
budget: Optional[float]
|
|
is_default: bool
|
|
exclusions: frozenset[MeasureType] = _NO_EXCLUSIONS
|
|
|
|
def considered_measures(self) -> Optional[frozenset[MeasureType]]:
|
|
"""The measure-type allowlist the Scenario's exclusions imply: every
|
|
modelled measure minus the excluded ones, or None (consider every
|
|
measure) when nothing is excluded."""
|
|
if not self.exclusions:
|
|
return None
|
|
return frozenset(MeasureType) - self.exclusions
|