mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-30 13:10:47 +00:00
fix(repositories): fold landlord overrides when re-hydrating a Property via the UoW
PostgresUnitOfWork built its PropertyPostgresRepository without an overrides reader, so a Property re-hydrated through the unit silently dropped its Landlord Overrides (ADR-0032). The Baseline orchestrator runs through the UoW, so it scored the bare lodged EPC while the Plan modelled the override-folded Effective EPC — the two diverged (e.g. baseline effective 71/C vs plan baseline 62/D), producing "already at band C yet recommends reaching C". Wire PropertyOverridesPostgresReader into the unit's property repo (uow- independent committed reference data, read via the same session factory) so every re-hydration folds overrides, matching the live modelling path. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
e80be44fd1
commit
7a970ea393
2 changed files with 76 additions and 1 deletions
|
|
@ -14,6 +14,9 @@ from repositories.plan.plan_postgres_repository import PlanPostgresRepository
|
|||
from repositories.product.composite_product_repository import (
|
||||
catalogue_with_off_catalogue_overrides,
|
||||
)
|
||||
from repositories.property.property_overrides_postgres_reader import (
|
||||
PropertyOverridesPostgresReader,
|
||||
)
|
||||
from repositories.property.property_postgres_repository import (
|
||||
PropertyPostgresRepository,
|
||||
)
|
||||
|
|
@ -41,8 +44,14 @@ class PostgresUnitOfWork(UnitOfWork):
|
|||
self._session = self._session_factory()
|
||||
epc_repo = EpcPostgresRepository(self._session)
|
||||
spatial_repo = SpatialPostgresRepository(self._session)
|
||||
# Fold Landlord Overrides onto the Effective EPC on every re-hydration
|
||||
# (ADR-0032), so what the Baseline orchestrator scores off ``uow.property``
|
||||
# matches what the Plan was modelled from. The reader is uow-independent —
|
||||
# ``property_overrides`` is committed reference data — so it opens its own
|
||||
# short read session per call via the same session factory.
|
||||
overrides_reader = PropertyOverridesPostgresReader(self._session_factory)
|
||||
self.property = PropertyPostgresRepository(
|
||||
self._session, epc_repo, spatial_repo
|
||||
self._session, epc_repo, spatial_repo, overrides_reader
|
||||
)
|
||||
self.epc = epc_repo
|
||||
self.solar = SolarPostgresRepository(self._session)
|
||||
|
|
|
|||
|
|
@ -1,25 +1,45 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from collections.abc import Callable
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
import pytest
|
||||
from sqlalchemy import Engine
|
||||
from sqlmodel import Session
|
||||
|
||||
from datatypes.epc.domain.epc import Epc
|
||||
from datatypes.epc.domain.epc_property_data import (
|
||||
BuildingPartIdentifier,
|
||||
EpcPropertyData,
|
||||
)
|
||||
from datatypes.epc.domain.mapper import EpcPropertyDataMapper
|
||||
from domain.property_baseline.property_baseline_performance import PropertyBaselinePerformance
|
||||
from domain.property_baseline.performance import Performance
|
||||
from infrastructure.postgres.property_override_table import PropertyOverrideRow
|
||||
from infrastructure.postgres.property_table import PropertyRow
|
||||
from repositories.epc.epc_postgres_repository import EpcPostgresRepository
|
||||
from repositories.plan.plan_repository import PlanRepository
|
||||
from repositories.postgres_unit_of_work import PostgresUnitOfWork
|
||||
from repositories.product.product_repository import ProductRepository
|
||||
from repositories.scenario.scenario_repository import ScenarioRepository
|
||||
from repositories.spatial.spatial_repository import SpatialRepository
|
||||
|
||||
_JSON_SAMPLES = Path(__file__).resolve().parents[2] / "backend/epc_api/json_samples"
|
||||
|
||||
|
||||
def _session_factory(db_engine: Engine) -> Callable[[], Session]:
|
||||
return lambda: Session(db_engine)
|
||||
|
||||
|
||||
def _epc() -> EpcPropertyData:
|
||||
raw: dict[str, Any] = json.loads(
|
||||
(_JSON_SAMPLES / "RdSAP-Schema-21.0.0" / "epc.json").read_text()
|
||||
)
|
||||
return EpcPropertyDataMapper.from_api_response(raw)
|
||||
|
||||
|
||||
def _baseline() -> PropertyBaselinePerformance:
|
||||
perf = Performance(
|
||||
sap_score=72, epc_band=Epc.C, co2_emissions=1.8, primary_energy_intensity=180
|
||||
|
|
@ -86,6 +106,52 @@ def test_unit_exposes_the_spatial_cache_repo_bound_to_its_session(
|
|||
assert isinstance(uow.spatial, SpatialRepository)
|
||||
|
||||
|
||||
def test_unit_hydrates_a_property_with_its_landlord_overrides_folded(
|
||||
db_engine: Engine,
|
||||
) -> None:
|
||||
"""A Property re-hydrated through the unit folds its Landlord Overrides onto
|
||||
the Effective EPC (ADR-0032) — the same overlay the live modelling path
|
||||
applies. Without this the Baseline Performance the orchestrator scores off
|
||||
``uow.property`` diverges from the Plan, which does apply the overrides.
|
||||
"""
|
||||
# Arrange — a lodged EPC (cavity main wall, 4) plus a solid-brick / internal
|
||||
# wall override, persisted through the unit.
|
||||
with PostgresUnitOfWork(_session_factory(db_engine)) as uow:
|
||||
row = PropertyRow(portfolio_id=1, postcode="A0 0AA", address="1 St", uprn=1)
|
||||
uow._session.add(row) # pyright: ignore[reportPrivateUsage]
|
||||
uow._session.flush() # pyright: ignore[reportPrivateUsage]
|
||||
property_id = row.id
|
||||
assert property_id is not None
|
||||
EpcPostgresRepository(uow._session).save( # pyright: ignore[reportPrivateUsage]
|
||||
_epc(), property_id=property_id
|
||||
)
|
||||
uow._session.add( # pyright: ignore[reportPrivateUsage]
|
||||
PropertyOverrideRow(
|
||||
property_id=property_id,
|
||||
portfolio_id=1,
|
||||
building_part=0,
|
||||
override_component="wall_type",
|
||||
override_value="Solid brick, with internal insulation",
|
||||
original_spreadsheet_description="solid brick, insulated",
|
||||
)
|
||||
)
|
||||
uow.commit()
|
||||
|
||||
# Act — re-hydrate through a fresh unit.
|
||||
with PostgresUnitOfWork(_session_factory(db_engine)) as uow:
|
||||
prop = uow.property.get(property_id)
|
||||
|
||||
main = next(
|
||||
part
|
||||
for part in prop.effective_epc.sap_building_parts
|
||||
if part.identifier is BuildingPartIdentifier.MAIN
|
||||
)
|
||||
|
||||
# Assert — the override is folded: cavity (4) → solid brick (3) / internal (3).
|
||||
assert main.wall_construction == 3
|
||||
assert main.wall_insulation_type == 3
|
||||
|
||||
|
||||
def test_leaving_the_block_without_commit_persists_nothing(db_engine: Engine) -> None:
|
||||
# Arrange
|
||||
new_unit = lambda: PostgresUnitOfWork(_session_factory(db_engine))
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue