Model/infrastructure/postgres/modelling/installed_measure_table.py
Khalim Conn-Kowlessar c18968ba3c refactor(modelling): consolidate scenario + installed_measure into the subpackage
Move the scenario and installed_measure tables into
infrastructure/postgres/modelling/ as full-parity SQLModel definitions
(ScenarioModel, InstalledMeasureModel + MeasureType), completing the cluster
consolidation. backend/app/db/models/recommendations.py is now a pure
re-export shim.

ScenarioModel.goal is the PortfolioGoal enum (legacy planning branches on it),
sourced from domain/modelling/portfolio_goal.py; the repo's to_domain maps it to
its value string, so domain Scenario.goal is now the value ("Increasing EPC")
consistent with the orchestrator's check — fixing the latent name-vs-value
inconsistency the old str column masked (the scenario repo test stored the enum
*name*). Parity columns are nullable (mirror convention; live NOT-NULLs owned by
Drizzle).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 22:52:35 +00:00

73 lines
2.9 KiB
Python

from __future__ import annotations
import enum
from datetime import datetime
from typing import ClassVar, Optional
from sqlalchemy import Column, TIMESTAMP
from sqlalchemy import Enum as SAEnum
from sqlmodel import Field, SQLModel
class MeasureType(enum.Enum):
air_source_heat_pump = "air_source_heat_pump"
boiler_upgrade = "boiler_upgrade"
high_heat_retention_storage_heaters = "high_heat_retention_storage_heaters"
secondary_heating = "secondary_heating"
roomstat_programmer_trvs = "roomstat_programmer_trvs"
time_temperature_zone_control = "time_temperature_zone_control"
cylinder_thermostat = "cylinder_thermostat"
cavity_wall_insulation = "cavity_wall_insulation"
extension_cavity_wall_insulation = "extension_cavity_wall_insulation"
external_wall_insulation = "external_wall_insulation"
internal_wall_insulation = "internal_wall_insulation"
loft_insulation = "loft_insulation"
flat_roof_insulation = "flat_roof_insulation"
room_roof_insulation = "room_roof_insulation"
solid_floor_insulation = "solid_floor_insulation"
suspended_floor_insulation = "suspended_floor_insulation"
double_glazing = "double_glazing"
secondary_glazing = "secondary_glazing"
draught_proofing = "draught_proofing"
mechanical_ventilation = "mechanical_ventilation"
low_energy_lighting = "low_energy_lighting"
solar_pv = "solar_pv"
hot_water_tank_insulation = "hot_water_tank_insulation"
sealing_open_fireplace = "sealing_open_fireplace"
class InstalledMeasureModel(SQLModel, table=True):
"""The single SQLModel definition of the live ``installed_measure`` table
(ADR-0017 amendment). ``measure_type`` is the ``MeasureType`` Postgres enum;
the remaining NOT-NULLs are relaxed to nullable (mirror convention — the
live constraints are owned by the Drizzle schema)."""
__tablename__: ClassVar[str] = "installed_measure" # pyright: ignore[reportIncompatibleVariableOverride]
id: Optional[int] = Field(default=None, primary_key=True)
uprn: Optional[int] = Field(default=None, index=True)
measure_type: MeasureType = Field(
sa_column=Column(
SAEnum(
MeasureType,
name="measure_type",
values_callable=lambda cls: [m.value for m in cls], # pyright: ignore[reportUnknownLambdaType, reportUnknownMemberType, reportUnknownVariableType]
create_type=False,
),
nullable=False,
)
)
installed_at: Optional[datetime] = Field(
default=None, sa_column=Column(TIMESTAMP, nullable=True)
)
sap_points: Optional[float] = Field(default=None)
carbon_savings: Optional[float] = Field(default=None)
kwh_savings: Optional[float] = Field(default=None)
bill_savings: Optional[float] = Field(default=None)
heat_demand_savings: Optional[float] = Field(default=None)
source: Optional[str] = Field(default=None)
is_active: bool = True