refactor(modelling): rename the cluster SQLModel classes …Row → …Model

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>
This commit is contained in:
Khalim Conn-Kowlessar 2026-06-03 22:42:21 +00:00
parent b97d06882f
commit 01c2c3910e
7 changed files with 37 additions and 38 deletions

View file

@ -28,19 +28,18 @@ from backend.app.db.base import Base
from backend.app.db.models.portfolio import Portfolio, PortfolioGoal, PropertyModel from backend.app.db.models.portfolio import Portfolio, PortfolioGoal, PropertyModel
from infrastructure.postgres.modelling import ( from infrastructure.postgres.modelling import (
PlanRow, PlanModel,
PlanType, PlanType,
RecommendationMaterialRow, RecommendationMaterialModel,
RecommendationRow, RecommendationModel,
) )
# Legacy names → the single SQLModel definitions now in # Legacy names → the single SQLModel definitions now in
# `infrastructure/postgres/modelling/`. The `plan_recommendations` m2m is # `infrastructure/postgres/modelling/`. The `plan_recommendations` m2m is
# retired (ADR-0017 amendment) — measures link to their Plan via # retired (ADR-0017 amendment) — measures link to their Plan via
# `recommendation.plan_id`. # `recommendation.plan_id`.
Recommendation = RecommendationRow Recommendation = RecommendationModel
RecommendationMaterials = RecommendationMaterialRow RecommendationMaterials = RecommendationMaterialModel
PlanModel = PlanRow
PlanTypeEnum = PlanType PlanTypeEnum = PlanType
@ -167,5 +166,5 @@ def enum_values(e: Iterable[PlanType]) -> list[str]:
class PlanPersistence(NamedTuple): class PlanPersistence(NamedTuple):
plan: PlanRow plan: PlanModel
scenario: ScenarioModel scenario: ScenarioModel

View file

@ -8,15 +8,15 @@ One canonical SQLModel per physical table — `plan`, `recommendation`,
`plan_recommendations` m2m is retired. `plan_recommendations` m2m is retired.
""" """
from infrastructure.postgres.modelling.plan_table import PlanRow, PlanType from infrastructure.postgres.modelling.plan_table import PlanModel, PlanType
from infrastructure.postgres.modelling.recommendation_table import ( from infrastructure.postgres.modelling.recommendation_table import (
RecommendationMaterialRow, RecommendationMaterialModel,
RecommendationRow, RecommendationModel,
) )
__all__ = [ __all__ = [
"PlanRow", "PlanModel",
"PlanType", "PlanType",
"RecommendationRow", "RecommendationModel",
"RecommendationMaterialRow", "RecommendationMaterialModel",
] ]

View file

@ -25,7 +25,7 @@ class PlanType(enum.Enum):
EXTRACTION_ECO = "extraction_eco" EXTRACTION_ECO = "extraction_eco"
class PlanRow(SQLModel, table=True): class PlanModel(SQLModel, table=True):
"""The single SQLModel definition of the live ``plan`` table (ADR-0017 """The single SQLModel definition of the live ``plan`` table (ADR-0017
amendment). Full legacy column parity; out-of-cluster references amendment). Full legacy column parity; out-of-cluster references
(``portfolio_id`` / ``property_id`` / ``scenario_id``) are plain indexed (``portfolio_id`` / ``property_id`` / ``scenario_id``) are plain indexed
@ -87,7 +87,7 @@ class PlanRow(SQLModel, table=True):
scenario_id: int, scenario_id: int,
portfolio_id: int, portfolio_id: int,
is_default: bool, is_default: bool,
) -> "PlanRow": ) -> "PlanModel":
return cls( return cls(
portfolio_id=portfolio_id, portfolio_id=portfolio_id,
property_id=property_id, property_id=property_id,

View file

@ -16,7 +16,7 @@ from domain.modelling.plan import PlanMeasure
_KG_PER_TONNE = 1000.0 _KG_PER_TONNE = 1000.0
class RecommendationRow(SQLModel, table=True): class RecommendationModel(SQLModel, table=True):
"""The single SQLModel definition of the live ``recommendation`` table """The single SQLModel definition of the live ``recommendation`` table
(ADR-0017 amendment) one row per persisted Plan Measure. (ADR-0017 amendment) one row per persisted Plan Measure.
@ -68,7 +68,7 @@ class RecommendationRow(SQLModel, table=True):
@classmethod @classmethod
def from_domain( def from_domain(
cls, measure: PlanMeasure, *, property_id: int, plan_id: int cls, measure: PlanMeasure, *, property_id: int, plan_id: int
) -> "RecommendationRow": ) -> "RecommendationModel":
return cls( return cls(
property_id=property_id, property_id=property_id,
plan_id=plan_id, plan_id=plan_id,
@ -87,7 +87,7 @@ class RecommendationRow(SQLModel, table=True):
) )
class RecommendationMaterialRow(SQLModel, table=True): class RecommendationMaterialModel(SQLModel, table=True):
"""The live ``recommendation_materials`` table — one row per material used """The live ``recommendation_materials`` table — one row per material used
by a Recommendation. ``recommendation_id`` is an intra-cluster FK; by a Recommendation. ``recommendation_id`` is an intra-cluster FK;
``material_id`` is a plain int (out-of-cluster, mirror convention).""" ``material_id`` is a plain int (out-of-cluster, mirror convention)."""

View file

@ -3,7 +3,7 @@ from __future__ import annotations
from sqlmodel import Session, col, delete from sqlmodel import Session, col, delete
from domain.modelling.plan import Plan from domain.modelling.plan import Plan
from infrastructure.postgres.modelling import PlanRow, RecommendationRow from infrastructure.postgres.modelling import PlanModel, RecommendationModel
from repositories.plan.plan_repository import PlanRepository from repositories.plan.plan_repository import PlanRepository
@ -28,13 +28,13 @@ class PlanPostgresRepository(PlanRepository):
# cascades to its recommendation rows via the plan_id FK (ON DELETE # cascades to its recommendation rows via the plan_id FK (ON DELETE
# CASCADE), so a re-run overwrites rather than duplicating (ADR-0012). # CASCADE), so a re-run overwrites rather than duplicating (ADR-0012).
self._session.exec( # type: ignore[call-overload] self._session.exec( # type: ignore[call-overload]
delete(PlanRow).where( delete(PlanModel).where(
col(PlanRow.property_id) == property_id, col(PlanModel.property_id) == property_id,
col(PlanRow.scenario_id) == scenario_id, col(PlanModel.scenario_id) == scenario_id,
) )
) )
plan_row = PlanRow.from_domain( plan_row = PlanModel.from_domain(
plan, plan,
property_id=property_id, property_id=property_id,
scenario_id=scenario_id, scenario_id=scenario_id,
@ -48,7 +48,7 @@ class PlanPostgresRepository(PlanRepository):
for measure in plan.measures: for measure in plan.measures:
self._session.add( self._session.add(
RecommendationRow.from_domain( RecommendationModel.from_domain(
measure, property_id=property_id, plan_id=plan_row.id measure, property_id=property_id, plan_id=plan_row.id
) )
) )

View file

@ -26,7 +26,7 @@ from infrastructure.postgres.property_baseline_performance_table import (
PropertyBaselinePerformanceModel, PropertyBaselinePerformanceModel,
) )
from infrastructure.postgres.epc_property_table import EpcPropertyModel from infrastructure.postgres.epc_property_table import EpcPropertyModel
from infrastructure.postgres.modelling import PlanRow, RecommendationRow from infrastructure.postgres.modelling import PlanModel, RecommendationModel
from infrastructure.postgres.product_table import MaterialRow from infrastructure.postgres.product_table import MaterialRow
from infrastructure.postgres.property_table import PropertyRow from infrastructure.postgres.property_table import PropertyRow
from tests.domain.sap10_calculator.worksheet._elmhurst_worksheet_000490 import ( from tests.domain.sap10_calculator.worksheet._elmhurst_worksheet_000490 import (
@ -269,12 +269,12 @@ def test_modelling_optimises_and_persists_a_multi_measure_plan(
# (ADR-0016). Each is priced and attributed, linked by plan_id. # (ADR-0016). Each is priced and attributed, linked by plan_id.
with Session(db_engine) as session: with Session(db_engine) as session:
plan = session.exec( plan = session.exec(
select(PlanRow).where(col(PlanRow.property_id) == 30) select(PlanModel).where(col(PlanModel.property_id) == 30)
).first() ).first()
assert plan is not None assert plan is not None
rec_rows = session.exec( rec_rows = session.exec(
select(RecommendationRow).where( select(RecommendationModel).where(
col(RecommendationRow.plan_id) == plan.id col(RecommendationModel.plan_id) == plan.id
) )
).all() ).all()
@ -413,12 +413,12 @@ def test_modelling_recommends_nothing_when_already_at_the_target_band(
# post-retrofit figure is the unchanged baseline (still band D). # post-retrofit figure is the unchanged baseline (still band D).
with Session(db_engine) as session: with Session(db_engine) as session:
plan = session.exec( plan = session.exec(
select(PlanRow).where(col(PlanRow.property_id) == 31) select(PlanModel).where(col(PlanModel.property_id) == 31)
).first() ).first()
assert plan is not None assert plan is not None
rec_rows = session.exec( rec_rows = session.exec(
select(RecommendationRow).where( select(RecommendationModel).where(
col(RecommendationRow.plan_id) == plan.id col(RecommendationModel.plan_id) == plan.id
) )
).all() ).all()

View file

@ -18,7 +18,7 @@ from domain.modelling.scoring.package_scorer import Score
from domain.modelling.plan import Plan, PlanMeasure from domain.modelling.plan import Plan, PlanMeasure
from domain.modelling.recommendation import Cost from domain.modelling.recommendation import Cost
from domain.modelling.scoring.scoring import MeasureImpact from domain.modelling.scoring.scoring import MeasureImpact
from infrastructure.postgres.modelling import PlanRow, RecommendationRow from infrastructure.postgres.modelling import PlanModel, RecommendationModel
from repositories.plan.plan_postgres_repository import PlanPostgresRepository from repositories.plan.plan_postgres_repository import PlanPostgresRepository
@ -64,10 +64,10 @@ def test_save_persists_plan_and_its_measures_with_tonnes_and_band(
# Assert # Assert
with Session(db_engine) as session: with Session(db_engine) as session:
plan_row = session.get(PlanRow, plan_id) plan_row = session.get(PlanModel, plan_id)
rec_rows = session.exec( rec_rows = session.exec(
select(RecommendationRow).where( select(RecommendationModel).where(
col(RecommendationRow.plan_id) == plan_id col(RecommendationModel.plan_id) == plan_id
) )
).all() ).all()
@ -139,7 +139,7 @@ def test_save_persists_null_per_measure_savings_when_unbilled(
# Assert — the savings columns persist as NULL (ADR-0014 amendment) # Assert — the savings columns persist as NULL (ADR-0014 amendment)
with Session(db_engine) as session: with Session(db_engine) as session:
rec_rows = session.exec( rec_rows = session.exec(
select(RecommendationRow).where(col(RecommendationRow.plan_id) == plan_id) select(RecommendationModel).where(col(RecommendationModel.plan_id) == plan_id)
).all() ).all()
assert len(rec_rows) == 1 assert len(rec_rows) == 1
assert rec_rows[0].kwh_savings is None assert rec_rows[0].kwh_savings is None
@ -166,10 +166,10 @@ def test_save_is_idempotent_on_rerun_for_the_same_property_and_scenario(
# Assert — replaced, not duplicated (cascade removed the old measures) # Assert — replaced, not duplicated (cascade removed the old measures)
with Session(db_engine) as session: with Session(db_engine) as session:
plan_rows = session.exec( plan_rows = session.exec(
select(PlanRow).where(col(PlanRow.property_id) == 10) select(PlanModel).where(col(PlanModel.property_id) == 10)
).all() ).all()
rec_rows = session.exec( rec_rows = session.exec(
select(RecommendationRow).where(col(RecommendationRow.property_id) == 10) select(RecommendationModel).where(col(RecommendationModel.property_id) == 10)
).all() ).all()
assert len(plan_rows) == 1 assert len(plan_rows) == 1