"""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