Model/domain/modelling/recommendation.py
Khalim Conn-Kowlessar 214b38ff78 feat(modelling): wall Recommendation Generator — cavity-fill detection + overlay
recommend_cavity_wall(epc) detects an uninsulated main cavity wall
(wall_construction=4, wall_insulation_type=4) and emits a Recommendation
whose single Measure Option carries the Simulation Overlay setting MAIN
wall_insulation_type=2 (Table 6 'Filled cavity'; cf. domain/sap10_ml/
rdsap_uvalues.py u_wall). Returns None for already-insulated or
non-cavity main walls.

Recommendation/MeasureOption reshaped per design review: the target is
encoded in the Option's overlay (addresses a building part / window /
system), not a typed key on Recommendation — generalises to glazing and
heating without changing the type. CONTEXT partition wording generalised
to match.

Three behaviour tests (hand-built EPD, no PDF). Cost (behaviour 4 of
#1155) outstanding — needs net heat-loss wall area + ProductRepository.
WIP on #1155. pyright strict clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-02 22:49:33 +00:00

44 lines
1.4 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
@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, ...]