Tri non déterministe : `latest_for_site` départage désormais les égalités de timestamp par `reading_id` décroissant, comme `list_history`. `uq_reading_source` autorise deux lignes au même `site_id`+`timestamp` quand la `source` diffère, donc le `LIMIT 1` pouvait renvoyer l'une ou l'autre d'un appel à l'autre. Tests : trois tests `integration` sur `latest_for_site` (plus récente, égalité de timestamp, isolation par site). Le test d'égalité échoue sans le correctif ci-dessus. Duplication : `DataQuality` et le repli vers `critical` sortent dans `app/services/data_quality.py`, partagé par `stats.py`, `site.py` et `sensor.py`, qui en portaient trois copies indépendantes. Supprime au passage deux `# type: ignore[assignment]`.
55 lines
1.9 KiB
Python
55 lines
1.9 KiB
Python
from collections.abc import Sequence
|
|
from datetime import datetime
|
|
|
|
from sqlalchemy import select
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
from app.models.energy import Reading
|
|
|
|
|
|
class ReadingRepository:
|
|
def __init__(self, session: AsyncSession) -> None:
|
|
self._session = session
|
|
|
|
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.
|
|
requete = (
|
|
select(Reading)
|
|
.distinct(Reading.site_id)
|
|
.order_by(Reading.site_id, Reading.timestamp.desc())
|
|
)
|
|
return (await self._session.execute(requete)).scalars().all()
|
|
|
|
async def latest_for_site(self, site_id: str) -> Reading | None:
|
|
# Piège : `uq_reading_source` autorise deux lignes au même `site_id`+`timestamp` quand la
|
|
# `source` diffère. Sans `reading_id` en départage, le `LIMIT 1` renverrait au hasard.
|
|
requete = (
|
|
select(Reading)
|
|
.where(Reading.site_id == site_id)
|
|
.order_by(Reading.timestamp.desc(), Reading.reading_id.desc())
|
|
.limit(1)
|
|
)
|
|
lecture: Reading | None = await self._session.scalar(requete)
|
|
return lecture
|
|
|
|
async def list_history(
|
|
self,
|
|
*,
|
|
start: datetime,
|
|
end: datetime,
|
|
site_id: str | None = None,
|
|
limit: int,
|
|
offset: int,
|
|
) -> Sequence[Reading]:
|
|
requete = (
|
|
select(Reading)
|
|
.where(Reading.timestamp >= start, Reading.timestamp < end)
|
|
.order_by(Reading.timestamp.desc(), Reading.reading_id.desc())
|
|
.limit(limit)
|
|
.offset(offset)
|
|
)
|
|
if site_id is not None:
|
|
requete = requete.where(Reading.site_id == site_id)
|
|
return (await self._session.scalars(requete)).all()
|