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 infrastructure.postgres.modelling import (
PlanRow,
PlanModel,
PlanType,
RecommendationMaterialRow,
RecommendationRow,
RecommendationMaterialModel,
RecommendationModel,
)
# Legacy names → the single SQLModel definitions now in
# `infrastructure/postgres/modelling/`. The `plan_recommendations` m2m is
# retired (ADR-0017 amendment) — measures link to their Plan via
# `recommendation.plan_id`.
Recommendation = RecommendationRow
RecommendationMaterials = RecommendationMaterialRow
PlanModel = PlanRow
Recommendation = RecommendationModel
RecommendationMaterials = RecommendationMaterialModel
PlanTypeEnum = PlanType
@ -167,5 +166,5 @@ def enum_values(e: Iterable[PlanType]) -> list[str]:
class PlanPersistence(NamedTuple):
plan: PlanRow
plan: PlanModel
scenario: ScenarioModel

View file

@ -8,15 +8,15 @@ One canonical SQLModel per physical table — `plan`, `recommendation`,
`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 (
RecommendationMaterialRow,
RecommendationRow,
RecommendationMaterialModel,
RecommendationModel,
)
__all__ = [
"PlanRow",
"PlanModel",
"PlanType",
"RecommendationRow",
"RecommendationMaterialRow",
"RecommendationModel",
"RecommendationMaterialModel",
]

View file

@ -25,7 +25,7 @@ class PlanType(enum.Enum):
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
amendment). Full legacy column parity; out-of-cluster references
(``portfolio_id`` / ``property_id`` / ``scenario_id``) are plain indexed
@ -87,7 +87,7 @@ class PlanRow(SQLModel, table=True):
scenario_id: int,
portfolio_id: int,
is_default: bool,
) -> "PlanRow":
) -> "PlanModel":
return cls(
portfolio_id=portfolio_id,
property_id=property_id,

View file

@ -16,7 +16,7 @@ from domain.modelling.plan import PlanMeasure
_KG_PER_TONNE = 1000.0
class RecommendationRow(SQLModel, table=True):
class RecommendationModel(SQLModel, table=True):
"""The single SQLModel definition of the live ``recommendation`` table
(ADR-0017 amendment) one row per persisted Plan Measure.
@ -68,7 +68,7 @@ class RecommendationRow(SQLModel, table=True):
@classmethod
def from_domain(
cls, measure: PlanMeasure, *, property_id: int, plan_id: int
) -> "RecommendationRow":
) -> "RecommendationModel":
return cls(
property_id=property_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
by a Recommendation. ``recommendation_id`` is an intra-cluster FK;
``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 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
@ -28,13 +28,13 @@ class PlanPostgresRepository(PlanRepository):
# cascades to its recommendation rows via the plan_id FK (ON DELETE
# CASCADE), so a re-run overwrites rather than duplicating (ADR-0012).
self._session.exec( # type: ignore[call-overload]
delete(PlanRow).where(
col(PlanRow.property_id) == property_id,
col(PlanRow.scenario_id) == scenario_id,
delete(PlanModel).where(
col(PlanModel.property_id) == property_id,
col(PlanModel.scenario_id) == scenario_id,
)
)
plan_row = PlanRow.from_domain(
plan_row = PlanModel.from_domain(
plan,
property_id=property_id,
scenario_id=scenario_id,
@ -48,7 +48,7 @@ class PlanPostgresRepository(PlanRepository):
for measure in plan.measures:
self._session.add(
RecommendationRow.from_domain(
RecommendationModel.from_domain(
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,
)
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.property_table import PropertyRow
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.
with Session(db_engine) as session:
plan = session.exec(
select(PlanRow).where(col(PlanRow.property_id) == 30)
select(PlanModel).where(col(PlanModel.property_id) == 30)
).first()
assert plan is not None
rec_rows = session.exec(
select(RecommendationRow).where(
col(RecommendationRow.plan_id) == plan.id
select(RecommendationModel).where(
col(RecommendationModel.plan_id) == plan.id
)
).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).
with Session(db_engine) as session:
plan = session.exec(
select(PlanRow).where(col(PlanRow.property_id) == 31)
select(PlanModel).where(col(PlanModel.property_id) == 31)
).first()
assert plan is not None
rec_rows = session.exec(
select(RecommendationRow).where(
col(RecommendationRow.plan_id) == plan.id
select(RecommendationModel).where(
col(RecommendationModel.plan_id) == plan.id
)
).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.recommendation import Cost
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
@ -64,10 +64,10 @@ def test_save_persists_plan_and_its_measures_with_tonnes_and_band(
# Assert
with Session(db_engine) as session:
plan_row = session.get(PlanRow, plan_id)
plan_row = session.get(PlanModel, plan_id)
rec_rows = session.exec(
select(RecommendationRow).where(
col(RecommendationRow.plan_id) == plan_id
select(RecommendationModel).where(
col(RecommendationModel.plan_id) == plan_id
)
).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)
with Session(db_engine) as session:
rec_rows = session.exec(
select(RecommendationRow).where(col(RecommendationRow.plan_id) == plan_id)
select(RecommendationModel).where(col(RecommendationModel.plan_id) == plan_id)
).all()
assert len(rec_rows) == 1
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)
with Session(db_engine) as session:
plan_rows = session.exec(
select(PlanRow).where(col(PlanRow.property_id) == 10)
select(PlanModel).where(col(PlanModel.property_id) == 10)
).all()
rec_rows = session.exec(
select(RecommendationRow).where(col(RecommendationRow.property_id) == 10)
select(RecommendationModel).where(col(RecommendationModel.property_id) == 10)
).all()
assert len(plan_rows) == 1