from __future__ import annotations from datetime import datetime from typing import ClassVar, Optional from sqlalchemy import BigInteger, Column, ForeignKey, TIMESTAMP from sqlalchemy import Enum as SAEnum from sqlalchemy.sql import func from sqlmodel import Field, SQLModel from datatypes.enums import QuantityUnits from domain.modelling.plan import PlanMeasure # Calculator metrics are in kg CO₂/yr; the live ``recommendation`` column is # tonnes (legacy ``emissions_kg / 1000``). Convert on the way in. _KG_PER_TONNE = 1000.0 class RecommendationModel(SQLModel, table=True): """The single SQLModel definition of the live ``recommendation`` table (ADR-0017 amendment) — one row per persisted Plan Measure. Carries full legacy column parity (the readers iterate the columns / sum them) **plus** ``plan_id``, the FK that links a measure to its Plan and replaces the retired ``plan_recommendations`` m2m. Out-of-cluster columns (``property_id``) are plain indexed ints, not FK constraints, matching the mirror convention so ``SQLModel.metadata.create_all`` needs no foreign table to exist (the live FKs are owned by the Drizzle schema). """ __tablename__: ClassVar[str] = "recommendation" # pyright: ignore[reportIncompatibleVariableOverride] id: Optional[int] = Field(default=None, primary_key=True) property_id: int = Field(index=True) plan_id: Optional[int] = Field( default=None, sa_column=Column( BigInteger, ForeignKey("plan.id", ondelete="CASCADE"), nullable=True, index=True, ), ) created_at: Optional[datetime] = Field( default=None, sa_column=Column(TIMESTAMP, nullable=False, server_default=func.now()), ) type: str measure_type: Optional[str] = Field(default=None) description: str # The single Product this measure installs — the live ``material_id`` column # that replaces the retired ``recommendation_materials`` BOM (one material # per Plan Measure). Plain int, out-of-cluster (mirror convention). material_id: Optional[int] = Field(default=None, index=True) estimated_cost: Optional[float] = Field(default=None) starting_u_value: Optional[float] = Field(default=None) new_u_value: Optional[float] = Field(default=None) sap_points: Optional[float] = Field(default=None) heat_demand: Optional[float] = Field(default=None) kwh_savings: Optional[float] = Field(default=None) # delivered kWh/yr co2_equivalent_savings: Optional[float] = Field(default=None) # tonnes/yr energy_savings: Optional[float] = Field(default=None) energy_cost_savings: Optional[float] = Field(default=None) # £/yr property_valuation_increase: Optional[float] = Field(default=None) rental_yield_increase: Optional[float] = Field(default=None) total_work_hours: Optional[float] = Field(default=None) labour_days: Optional[float] = Field(default=None) default: bool = True already_installed: bool = False @classmethod def from_domain( cls, measure: PlanMeasure, *, property_id: int, plan_id: int ) -> "RecommendationModel": return cls( property_id=property_id, plan_id=plan_id, type=measure.measure_type, measure_type=measure.measure_type, description=measure.description, material_id=measure.material_id, estimated_cost=measure.cost.total, sap_points=measure.impact.sap_points, co2_equivalent_savings=( measure.impact.co2_savings_kg_per_yr / _KG_PER_TONNE ), kwh_savings=measure.kwh_savings, energy_cost_savings=measure.energy_cost_savings, default=True, already_installed=False, ) class RecommendationMaterialModel(SQLModel, table=True): """The live ``recommendation_materials`` table — one row per material used by a Recommendation. ``recommendation_id`` is an intra-cluster FK; ``material_id`` is a plain int (out-of-cluster, mirror convention).""" __tablename__: ClassVar[str] = "recommendation_materials" # pyright: ignore[reportIncompatibleVariableOverride] id: Optional[int] = Field(default=None, primary_key=True) recommendation_id: int = Field( sa_column=Column( BigInteger, ForeignKey("recommendation.id"), nullable=False ) ) material_id: int = Field(index=True) created_at: Optional[datetime] = Field( default=None, sa_column=Column(TIMESTAMP, nullable=False, server_default=func.now()), ) depth: Optional[float] = Field(default=None) quantity: Optional[float] = Field(default=None) quantity_unit: Optional[QuantityUnits] = Field( default=None, sa_column=Column( SAEnum( QuantityUnits, values_callable=lambda cls: [m.value for m in cls], # pyright: ignore[reportUnknownLambdaType, reportUnknownMemberType, reportUnknownVariableType] ), nullable=True, ), ) estimated_cost: Optional[float] = Field(default=None)