Model/tests/repositories/property/test_property_repository.py
Khalim Conn-Kowlessar 694cdd9c23 feat(modelling): mark a Property as run via has_recommendations + updated_at
The new pipeline left no per-Property record of a run (the old engine set
property.has_recommendations and populated property_details_epc). Restore the
marker: PropertyRepository.mark_modelled sets has_recommendations (true when the
Plan carries measures, mirroring the old engine) and bumps updated_at, so a
first-run under the new process is identifiable as updated_at >= 2026-06-01.

ModellingOrchestrator marks each Property after its Scenarios (true if any
Scenario yielded a measure); run_modelling_e2e's --persist path marks it too
(its compute runs on in-memory fakes, so the DB UoW sets it directly). Adds the
has_recommendations/updated_at columns to the PropertyRow mirror.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 23:34:33 +00:00

142 lines
4.9 KiB
Python

"""PropertyRepository hydrates the aggregate whole from the property row + EPC slice."""
from __future__ import annotations
import json
from pathlib import Path
from typing import Any
from sqlalchemy import Engine
from sqlmodel import Session
from datatypes.epc.domain.mapper import EpcPropertyDataMapper
from domain.geospatial.planning_restrictions import PlanningRestrictions
from domain.geospatial.spatial_reference import SpatialReference
from infrastructure.postgres.property_table import PropertyRow
from repositories.epc.epc_postgres_repository import EpcPostgresRepository
from repositories.property.property_postgres_repository import (
PropertyPostgresRepository,
)
from repositories.spatial.spatial_postgres_repository import SpatialPostgresRepository
_JSON_SAMPLES = Path(__file__).resolve().parents[3] / "backend/epc_api/json_samples"
def test_get_hydrates_identity_and_epc_slice(db_engine: Engine) -> None:
# Arrange
raw: dict[str, Any] = json.loads(
(_JSON_SAMPLES / "RdSAP-Schema-21.0.0" / "epc.json").read_text()
)
epc = EpcPropertyDataMapper.from_api_response(raw)
with Session(db_engine) as session:
row = PropertyRow(
portfolio_id=7, postcode="A0 0AA", address="1 Some Street", uprn=12345
)
session.add(row)
session.commit()
property_id = row.id
assert property_id is not None
EpcPostgresRepository(session).save(epc, property_id=property_id)
session.commit()
# Act
with Session(db_engine) as session:
repo = PropertyPostgresRepository(
session, EpcPostgresRepository(session), SpatialPostgresRepository(session)
)
prop = repo.get(property_id)
# Assert
assert prop.identity.portfolio_id == 7
assert prop.identity.uprn == 12345
assert prop.epc == epc
assert prop.source_path == "epc_with_overlay"
assert prop.effective_epc == epc
def test_get_many_hydrates_planning_restrictions_from_the_spatial_cache(
db_engine: Engine,
) -> None:
# Arrange — a property whose UPRN has a cached listed-building flag.
with Session(db_engine) as session:
row = PropertyRow(
portfolio_id=7, postcode="A0 0AA", address="1 Some Street", uprn=12345
)
session.add(row)
session.commit()
property_id = row.id
assert property_id is not None
SpatialPostgresRepository(session).save(
uprn=12345,
reference=SpatialReference(
coordinates=None,
restrictions=PlanningRestrictions(is_listed=True),
),
)
session.commit()
# Act
with Session(db_engine) as session:
repo = PropertyPostgresRepository(
session, EpcPostgresRepository(session), SpatialPostgresRepository(session)
)
properties = repo.get_many([property_id])
# Assert — the protections are hydrated onto the Property (ADR-0020).
assert properties.items[0].planning_restrictions == PlanningRestrictions(
is_listed=True
)
def test_get_many_defaults_to_unrestricted_when_uprn_has_no_spatial_row(
db_engine: Engine,
) -> None:
# Arrange — a property whose UPRN is not in the spatial cache.
with Session(db_engine) as session:
row = PropertyRow(
portfolio_id=7, postcode="A0 0AA", address="1 Some Street", uprn=999
)
session.add(row)
session.commit()
property_id = row.id
assert property_id is not None
# Act
with Session(db_engine) as session:
repo = PropertyPostgresRepository(
session, EpcPostgresRepository(session), SpatialPostgresRepository(session)
)
properties = repo.get_many([property_id])
# Assert — an uncovered UPRN means unrestricted, not blocked (per legacy
# `empty_spatial_df`; ADR-0020).
assert properties.items[0].planning_restrictions == PlanningRestrictions()
def test_mark_modelled_sets_has_recommendations_and_bumps_updated_at(
db_engine: Engine,
) -> None:
# Arrange — a freshly-inserted property with no run recorded yet.
with Session(db_engine) as session:
row = PropertyRow(portfolio_id=7, uprn=12345)
session.add(row)
session.commit()
property_id = row.id
assert property_id is not None
assert row.has_recommendations is None
assert row.updated_at is None
# Act — record a run that produced recommendations.
with Session(db_engine) as session:
PropertyPostgresRepository(session).mark_modelled(
property_id, has_recommendations=True
)
session.commit()
# Assert — the marker is set and updated_at is stamped, so the run is datable
# against the 2026-06-01 new-process cutoff.
with Session(db_engine) as session:
refreshed = session.get(PropertyRow, property_id)
assert refreshed is not None
assert refreshed.has_recommendations is True
assert refreshed.updated_at is not None