from __future__ import annotations from typing import ClassVar, Optional from sqlalchemy import Column from sqlalchemy import Enum as SAEnum from sqlmodel import Field, SQLModel # Mirror of the FE-owned `creation_status` pgEnum (property.ts: # propertyCreationStatusEnum = {LOADING, READY, ERROR}). A single SAEnum instance # so test-schema create_all emits one CREATE TYPE; prod owns the type via Drizzle. property_creation_status_sa_enum = SAEnum( "LOADING", "READY", "ERROR", name="creation_status" ) class PropertyRow(SQLModel, table=True): """Mirror of the FE-owned ``property`` table. The schema and migrations for ``property`` are owned by the front-end Next.js repo (``src/app/db/schema/property.ts``); this declares the identity columns the modelling backend reads, plus the subset the ``bulk_upload_finaliser`` Lambda **inserts** at Finalise (ADR-0013). It is no longer read-only — the finaliser is the one backend caller that inserts. Columns not declared here are still owned by FE migrations and don't ripple into us. """ __tablename__: ClassVar[str] = "property" # pyright: ignore[reportIncompatibleVariableOverride] # bigserial in the FE schema — DB-assigned on insert, so Optional/None on the # way in and always populated on the way out. id: Optional[int] = Field(default=None, primary_key=True) portfolio_id: int # Nullable in the FE schema. The finaliser writes `matched ?? user-inputted`, # which is absent for fully-unmatched rows. postcode: Optional[str] = Field(default=None) address: Optional[str] = Field(default=None) uprn: Optional[int] = Field(default=None) landlord_property_id: Optional[str] = Field(default=None) # Insertable columns the finaliser writes (ADR-0013). All nullable in the FE # schema except `creation_status` (NOT NULL); the finaliser always sets it to # 'READY', so a nullable mirror is safe — the real column enforces NOT NULL. creation_status: Optional[str] = Field( default=None, sa_column=Column(property_creation_status_sa_enum, nullable=True), ) user_inputted_address: Optional[str] = Field(default=None) user_inputted_postcode: Optional[str] = Field(default=None) lexiscore: Optional[float] = Field(default=None)