Model/tests/repositories/epc/test_epc_idempotent_save.py
Khalim Conn-Kowlessar 559ae1b4ec feat(repos): idempotent EPC + Baseline writes (replace by property_id) (#1138)
Re-runs of a First Run batch re-save a property's data; that must replace,
not duplicate (ADR-0012 idempotent batch writes).

- `EpcPostgresRepository.save` deletes the property's existing EPC graph
  (parent + all child tables, floor-dims via their building parts) before
  inserting, when a `property_id` is given. Anonymous saves still insert.
- `BaselinePostgresRepository.save` deletes the existing row for the
  `property_id` before inserting — no more unique-constraint violation on
  re-save; also what the re-score-on-override path needs.
- Solar already upserts, so it's unchanged.

The #1129 round-trip fidelity test stays green (delete-first is a no-op on
a first save). 2 new tests (re-save replaces, not duplicates). pyright
strict clean; AAA.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-31 09:41:39 +00:00

52 lines
1.8 KiB
Python

"""A re-run of First Run re-saves a property's EPC; that must replace the prior
row, not duplicate it (ADR-0012 idempotent batch writes, #1138)."""
from __future__ import annotations
import dataclasses
import json
from pathlib import Path
from typing import Any
from sqlalchemy import Engine
from sqlmodel import Session, select
from datatypes.epc.domain.epc_property_data import EpcPropertyData
from datatypes.epc.domain.mapper import EpcPropertyDataMapper
from infrastructure.postgres.epc_property_table import EpcPropertyModel
from repositories.epc.epc_postgres_repository import EpcPostgresRepository
_JSON_SAMPLES = Path(__file__).resolve().parents[3] / "backend/epc_api/json_samples"
def _load_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 test_resaving_an_epc_for_a_property_replaces_rather_than_duplicates(
db_engine: Engine,
) -> None:
# Arrange — same property re-ingested with a changed field.
original = _load_epc()
updated = dataclasses.replace(original, status="re-run-sentinel")
# Act — save twice for the same property_id (a re-run).
with Session(db_engine) as session:
repo = EpcPostgresRepository(session)
repo.save(original, property_id=10)
repo.save(updated, property_id=10)
session.commit()
# Assert — exactly one EPC row for the property, holding the latest data.
with Session(db_engine) as session:
rows = session.exec(
select(EpcPropertyModel).where(EpcPropertyModel.property_id == 10)
).all()
reloaded = EpcPostgresRepository(session).get_for_property(10)
assert len(rows) == 1
assert reloaded is not None
assert reloaded.status == "re-run-sentinel"