Classify the landlord Age column into a construction-age-band category 🟩

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jun-te Kim 2026-06-19 13:44:40 +00:00
parent fc591c6550
commit cb6a335382
3 changed files with 114 additions and 0 deletions

View file

@ -8,6 +8,7 @@ from applications.landlord_description_overrides.landlord_description_overrides_
LandlordDescriptionOverridesTriggerBody,
)
from domain.epc.built_form_type import BuiltFormType
from domain.epc.construction_age_band import ConstructionAgeBand
from domain.epc.glazing_type import GlazingType
from domain.epc.main_fuel_type import MainFuelType
from domain.epc.property_type import PropertyType
@ -26,6 +27,9 @@ from infrastructure.postgres.engine import commit_scope, make_engine, make_sessi
from infrastructure.postgres.landlord_built_form_type_override_table import (
LandlordBuiltFormTypeOverrideRow,
)
from infrastructure.postgres.landlord_construction_age_band_override_table import (
LandlordConstructionAgeBandOverrideRow,
)
from infrastructure.postgres.landlord_glazing_override_table import (
LandlordGlazingOverrideRow,
)
@ -130,6 +134,16 @@ def _build_columns(
session, LandlordGlazingOverrideRow
),
),
"construction_age_band": lambda src: ClassifiableColumn(
name="construction_age_band",
source_column=src,
classifier=ChatGptColumnClassifier(
chat_gpt, ConstructionAgeBand, ConstructionAgeBand.UNKNOWN
),
repo=LandlordOverridesRepository[ConstructionAgeBand](
session, LandlordConstructionAgeBandOverrideRow
),
),
}
columns: list[ClassifiableColumn[Any]] = []

View file

@ -0,0 +1,31 @@
from enum import Enum
class ConstructionAgeBand(Enum):
"""A landlord-supplied construction age band, as resolved by the
landlord-description-overrides context.
Each member's value is the RdSAP England-&-Wales age-band **letter code**
(A..M) the calculator's U-value cascades read from
`SapBuildingPart.construction_age_band` the same representation the gov-EPC
API lodges. The construction-age-band Simulation Overlay
(``domain/epc/property_overlays/construction_age_band_overlay.py``) sets the
letter directly, so these values MUST stay the bare letter codes. Member
names carry the year ranges for readability. ``UNKNOWN`` covers values the
classifier cannot resolve (it leaves the lodged cert's age band untouched).
"""
A_BEFORE_1900 = "A"
B_1900_1929 = "B"
C_1930_1949 = "C"
D_1950_1966 = "D"
E_1967_1975 = "E"
F_1976_1982 = "F"
G_1983_1990 = "G"
H_1991_1995 = "H"
I_1996_2002 = "I"
J_2003_2006 = "J"
K_2007_2011 = "K"
L_2012_2022 = "L"
M_2023_ONWARDS = "M"
UNKNOWN = "Unknown"

View file

@ -0,0 +1,69 @@
"""SQLModel mirror of the ``landlord_construction_age_band_overrides`` Drizzle table.
The schema source of truth lives in the ``assessment-model`` TS repo
(`src/app/db/schema/landlord_overrides.ts`). The migrations are owned there;
this row class only mirrors the columns so the Python lambda can read/write.
See ADR-0003. Shape mirrors ``LandlordWallTypeOverrideRow`` -- the only
differences are the table name, the ``construction_age_band`` pgEnum on
``value``, and the unique-constraint name.
"""
from datetime import datetime, timezone
from typing import ClassVar
from uuid import UUID, uuid4
from sqlalchemy import BigInteger, Column, UniqueConstraint
from sqlalchemy import Enum as SAEnum
from sqlmodel import Field, SQLModel
from domain.epc.construction_age_band import ConstructionAgeBand
from infrastructure.postgres.landlord_override_enums import override_source_sa_enum
class LandlordConstructionAgeBandOverrideRow(SQLModel, table=True):
__tablename__: ClassVar[str] = "landlord_construction_age_band_overrides" # pyright: ignore[reportIncompatibleVariableOverride]
__table_args__: ClassVar[tuple[UniqueConstraint, ...]] = ( # pyright: ignore[reportIncompatibleVariableOverride]
UniqueConstraint(
"portfolio_id",
"description",
name="landlord_construction_age_band_overrides_portfolio_description_unique",
),
)
id: UUID = Field(default_factory=uuid4, primary_key=True)
# bigint to match the Drizzle ``portfolio_id`` FK; SQLModel's default int
# mapping is 32-bit Integer and would overflow once portfolio IDs exceed
# 2^31. The FK to ``portfolio.id`` is enforced by the Drizzle migration,
# not declared here -- the ``portfolio`` table is not modelled in Python.
portfolio_id: int = Field(
sa_column=Column(BigInteger, nullable=False, index=True),
)
description: str = Field(nullable=False)
value: ConstructionAgeBand = Field(
sa_column=Column(
SAEnum(
ConstructionAgeBand,
name="construction_age_band",
values_callable=lambda cls: [m.value for m in cls], # pyright: ignore[reportUnknownLambdaType, reportUnknownMemberType, reportUnknownVariableType]
),
nullable=False,
),
)
# Shared SAEnum -- see ``landlord_override_enums`` for why this single
# instance is reused by every ``landlord_*_overrides`` row class.
source: str = Field(
sa_column=Column(override_source_sa_enum, nullable=False),
)
created_at: datetime = Field(
default_factory=lambda: datetime.now(timezone.utc),
nullable=False,
)
updated_at: datetime = Field(
default_factory=lambda: datetime.now(timezone.utc),
nullable=False,
)