From b433e01fa8cb763d4b97ecedb37b3161e3618983 Mon Sep 17 00:00:00 2001 From: Johan LEROY Date: Fri, 18 Sep 2026 10:26:18 +0200 Subject: [PATCH] =?UTF-8?q?fix(backend):=20d=C3=A9partage=20aussi=20les=20?= =?UTF-8?q?=C3=A9galit=C3=A9s=20de=20timestamp=20dans=20latest=5Fby=5Fsite?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `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. --- apps/backend/app/repositories/reading.py | 5 +++-- .../tests/repositories/test_reading.py | 20 +++++++++++++++++++ 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/apps/backend/app/repositories/reading.py b/apps/backend/app/repositories/reading.py index d251f55..d005d16 100644 --- a/apps/backend/app/repositories/reading.py +++ b/apps/backend/app/repositories/reading.py @@ -13,11 +13,12 @@ class ReadingRepository: async def latest_by_site(self) -> Sequence[Reading]: # `.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 = ( select(Reading) .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() diff --git a/apps/backend/tests/repositories/test_reading.py b/apps/backend/tests/repositories/test_reading.py index dd2f2df..4f12df0 100644 --- a/apps/backend/tests/repositories/test_reading.py +++ b/apps/backend/tests/repositories/test_reading.py @@ -88,6 +88,26 @@ async def test_latest_by_site_returns_one_row_per_site(session: AsyncSession) -> 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: site = await creer_site(session) depot = ReadingRepository(session)