Model/infrastructure/postgres/property_table.py
2026-06-04 11:47:42 +00:00

50 lines
2.3 KiB
Python

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)