mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-08 11:17:27 +00:00
Standardise the modelling persistence classes on the …Model suffix (PlanModel, RecommendationModel, RecommendationMaterialModel) — matching the epc_property precedent and the legacy names the rest of backend/ already imports, so the shim's plan re-export becomes literal (no alias) and the eventual shim deletion needs zero renames. The …Row→…Model sweep for the non-cluster tables (Property/Task/Material/…) waits until their live legacy …Model counterparts are retired, to avoid reintroducing dual-definition collisions. No behaviour change. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
170 lines
6.8 KiB
Python
170 lines
6.8 KiB
Python
"""Re-export shim + remaining legacy models (ADR-0017 amendment).
|
|
|
|
`plan`, `recommendation`, `recommendation_materials` and the retiring
|
|
`plan_recommendations` moved to `infrastructure/postgres/modelling/` as single
|
|
SQLModel definitions (the `epc_property` pattern). This module re-exports them
|
|
under their legacy names so the dying `backend/` callers keep working; new code
|
|
imports from `infrastructure.postgres.modelling` directly. `ScenarioModel` and
|
|
`InstalledMeasure` are not yet migrated and stay here for now.
|
|
"""
|
|
|
|
import enum
|
|
from typing import Iterable, List, NamedTuple, Optional, Type
|
|
from sqlalchemy import (
|
|
BigInteger,
|
|
String,
|
|
Float,
|
|
Boolean,
|
|
TIMESTAMP,
|
|
ForeignKey,
|
|
Column,
|
|
Enum,
|
|
)
|
|
from sqlalchemy.orm import Mapped, mapped_column
|
|
from sqlalchemy.sql import func
|
|
from datetime import datetime
|
|
|
|
from backend.app.db.base import Base
|
|
from backend.app.db.models.portfolio import Portfolio, PortfolioGoal, PropertyModel
|
|
|
|
from infrastructure.postgres.modelling import (
|
|
PlanModel,
|
|
PlanType,
|
|
RecommendationMaterialModel,
|
|
RecommendationModel,
|
|
)
|
|
|
|
# Legacy names → the single SQLModel definitions now in
|
|
# `infrastructure/postgres/modelling/`. The `plan_recommendations` m2m is
|
|
# retired (ADR-0017 amendment) — measures link to their Plan via
|
|
# `recommendation.plan_id`.
|
|
Recommendation = RecommendationModel
|
|
RecommendationMaterials = RecommendationMaterialModel
|
|
PlanTypeEnum = PlanType
|
|
|
|
|
|
def portfolio_goal_values(enum_cls: Type[PortfolioGoal]) -> List[str]:
|
|
return [e.value for e in enum_cls]
|
|
|
|
|
|
class ScenarioModel(Base):
|
|
__tablename__ = "scenario"
|
|
|
|
id: Mapped[int] = mapped_column(BigInteger, primary_key=True, autoincrement=True)
|
|
name: Mapped[str] = mapped_column(String, nullable=False)
|
|
created_at: Mapped[datetime] = mapped_column(
|
|
TIMESTAMP, nullable=False, server_default=func.now()
|
|
)
|
|
budget: Mapped[Optional[float]] = mapped_column(Float)
|
|
portfolio_id: Mapped[int] = mapped_column(
|
|
BigInteger, ForeignKey(Portfolio.id), nullable=False
|
|
)
|
|
housing_type: Mapped[str] = mapped_column(String, nullable=False)
|
|
goal: Mapped[PortfolioGoal] = mapped_column(
|
|
Enum(PortfolioGoal, values_callable=portfolio_goal_values, name="goal"),
|
|
nullable=False,
|
|
)
|
|
goal_value: Mapped[str] = mapped_column(String, nullable=False)
|
|
trigger_file_path: Mapped[str] = mapped_column(String, nullable=False)
|
|
already_installed_file_path: Mapped[Optional[str]] = mapped_column(String)
|
|
patches_file_path: Mapped[Optional[str]] = mapped_column(String)
|
|
non_invasive_recommendations_file_path: Mapped[Optional[str]] = mapped_column(
|
|
String
|
|
)
|
|
exclusions: Mapped[Optional[str]] = mapped_column(String)
|
|
multi_plan: Mapped[bool] = mapped_column(Boolean, default=False)
|
|
is_default: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
|
|
|
|
# Add in the fields we need, which were previously sitting at the portfolio level
|
|
cost: Mapped[Optional[float]] = mapped_column(Float)
|
|
contingency: Mapped[Optional[float]] = mapped_column(Float)
|
|
funding: Mapped[Optional[float]] = mapped_column(Float)
|
|
total_work_hours: Mapped[Optional[float]] = mapped_column(Float)
|
|
energy_savings: Mapped[Optional[float]] = mapped_column(Float)
|
|
co2_equivalent_savings: Mapped[Optional[float]] = mapped_column(Float)
|
|
energy_cost_savings: Mapped[Optional[float]] = mapped_column(Float)
|
|
epc_breakdown_pre_retrofit: Mapped[Optional[str]] = mapped_column(String)
|
|
epc_breakdown_post_retrofit: Mapped[Optional[str]] = mapped_column(String)
|
|
number_of_properties: Mapped[Optional[int]] = mapped_column(BigInteger)
|
|
n_units_to_retrofit: Mapped[Optional[int]] = mapped_column(BigInteger)
|
|
co2_per_unit_pre_retrofit: Mapped[Optional[str]] = mapped_column(String)
|
|
co2_per_unit_post_retrofit: Mapped[Optional[str]] = mapped_column(String)
|
|
energy_bill_per_unit_pre_retrofit: Mapped[Optional[str]] = mapped_column(String)
|
|
energy_bill_per_unit_post_retrofit: Mapped[Optional[str]] = mapped_column(String)
|
|
energy_consumption_per_unit_pre_retrofit: Mapped[Optional[str]] = mapped_column(
|
|
String
|
|
)
|
|
energy_consumption_per_unit_post_retrofit: Mapped[Optional[str]] = mapped_column(
|
|
String
|
|
)
|
|
valuation_improvement_per_unit: Mapped[Optional[str]] = mapped_column(String)
|
|
cost_per_unit: Mapped[Optional[str]] = mapped_column(String)
|
|
cost_per_co2_saved: Mapped[Optional[str]] = mapped_column(String)
|
|
cost_per_sap_point: Mapped[Optional[str]] = mapped_column(String)
|
|
valuation_return_on_investment: Mapped[Optional[str]] = mapped_column(String)
|
|
property_valuation_increase: Mapped[Optional[float]] = mapped_column(Float)
|
|
labour_days: Mapped[Optional[float]] = mapped_column(Float)
|
|
|
|
|
|
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 InstalledMeasure(Base):
|
|
__tablename__ = "installed_measure"
|
|
|
|
id = Column(BigInteger, primary_key=True, autoincrement=True)
|
|
uprn = Column(BigInteger, nullable=False)
|
|
measure_type = Column(
|
|
Enum(
|
|
MeasureType,
|
|
name="measure_type",
|
|
values_callable=lambda e: [m.value for m in e],
|
|
create_type=False, # <-- critical
|
|
),
|
|
nullable=False,
|
|
)
|
|
installed_at = Column(TIMESTAMP)
|
|
sap_points = Column(Float)
|
|
carbon_savings = Column(Float)
|
|
kwh_savings = Column(Float)
|
|
bill_savings = Column(Float)
|
|
heat_demand_savings = Column(Float)
|
|
source = Column(String)
|
|
is_active = Column(Boolean, nullable=False, default=True)
|
|
|
|
|
|
def enum_values(e: Iterable[PlanType]) -> list[str]:
|
|
return [m.value for m in e]
|
|
|
|
|
|
class PlanPersistence(NamedTuple):
|
|
plan: PlanModel
|
|
scenario: ScenarioModel
|