fix(backend): départage aussi les égalités de timestamp dans latest_by_site
Backend / Lint, typage et tests (push) Successful in 1m18s
Backend / Lint, typage et tests (push) Successful in 1m18s
`latest_by_site` portait le même défaut que `latest_for_site` : `DISTINCT ON (site_id)` ordonné sur `site_id, timestamp DESC` sans départage, alors que `uq_reading_source` autorise deux lignes au même `site_id`+`timestamp` quand la `source` diffère. `/stats/summary` pouvait donc afficher une consommation différente d'un appel à l'autre pour un site alimenté par un backfill CSV et une écriture live. Test `integration` dédié, qui échoue sans le correctif.
This commit is contained in:
@@ -13,11 +13,12 @@ class ReadingRepository:
|
|||||||
|
|
||||||
async def latest_by_site(self) -> Sequence[Reading]:
|
async def latest_by_site(self) -> Sequence[Reading]:
|
||||||
# `.distinct(site_id)` compile en `DISTINCT ON (site_id)` sous PostgreSQL : une seule
|
# `.distinct(site_id)` compile en `DISTINCT ON (site_id)` sous PostgreSQL : une seule
|
||||||
# ligne par site, la plus récente grâce à l'ordre composite qui suit.
|
# ligne par site, la plus récente grâce à l'ordre composite qui suit. `reading_id` départage
|
||||||
|
# les égalités de timestamp, que `uq_reading_source` autorise à `source` différente.
|
||||||
requete = (
|
requete = (
|
||||||
select(Reading)
|
select(Reading)
|
||||||
.distinct(Reading.site_id)
|
.distinct(Reading.site_id)
|
||||||
.order_by(Reading.site_id, Reading.timestamp.desc())
|
.order_by(Reading.site_id, Reading.timestamp.desc(), Reading.reading_id.desc())
|
||||||
)
|
)
|
||||||
return (await self._session.execute(requete)).scalars().all()
|
return (await self._session.execute(requete)).scalars().all()
|
||||||
|
|
||||||
|
|||||||
@@ -88,6 +88,26 @@ async def test_latest_by_site_returns_one_row_per_site(session: AsyncSession) ->
|
|||||||
assert identifiants == {premier, second}
|
assert identifiants == {premier, second}
|
||||||
|
|
||||||
|
|
||||||
|
async def test_latest_by_site_breaks_a_timestamp_tie_on_the_last_written_reading(
|
||||||
|
session: AsyncSession,
|
||||||
|
) -> None:
|
||||||
|
site = await creer_site(session)
|
||||||
|
depot = ReadingRepository(session)
|
||||||
|
horodatage = datetime(2026, 9, 15, tzinfo=UTC)
|
||||||
|
await creer_lecture(
|
||||||
|
session, site_id=site.site_id, timestamp=horodatage, source="api_history", consumption_kw=10
|
||||||
|
)
|
||||||
|
derniere = await creer_lecture(
|
||||||
|
session, site_id=site.site_id, timestamp=horodatage, source="api_current", consumption_kw=42
|
||||||
|
)
|
||||||
|
|
||||||
|
resultats = await depot.latest_by_site()
|
||||||
|
retenues = [r.reading_id for r in resultats if r.site_id == site.site_id]
|
||||||
|
await session.rollback()
|
||||||
|
|
||||||
|
assert retenues == [derniere.reading_id]
|
||||||
|
|
||||||
|
|
||||||
async def test_latest_for_site_returns_the_most_recent_reading(session: AsyncSession) -> None:
|
async def test_latest_for_site_returns_the_most_recent_reading(session: AsyncSession) -> None:
|
||||||
site = await creer_site(session)
|
site = await creer_site(session)
|
||||||
depot = ReadingRepository(session)
|
depot = ReadingRepository(session)
|
||||||
|
|||||||
Reference in New Issue
Block a user