feat: vue détail d'un site (#51)

Ajoute la page de détail d'un site (fiche, mesure instantanée, jauge de
consommation, historique) en remplacement du placeholder. Les champs
null sont affichés explicitement avec leur raison plutôt que masqués.

Ajoute l'endpoint GET /sites/{id}/current côté backend, qui retourne la
dernière mesure connue d'un site sans filtre temporel, conformément au
contrat de l'issue.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012i5NteMLRZgfTAKB5GD37X
This commit is contained in:
Johan LEROY
2026-09-17 16:43:15 +02:00
co-authored by Claude Sonnet 5
parent 56c134beb0
commit 5d921a9b1e
25 changed files with 1067 additions and 114 deletions
+21 -1
View File
@@ -1,8 +1,9 @@
from fastapi import APIRouter, HTTPException, status
from app.api.deps import LecteurDep, SiteServiceDep
from app.api.deps import LecteurDep, ReadingServiceDep, SiteServiceDep
from app.api.openapi import REPONSE_VALIDATION, Reponses
from app.schemas.errors import ErrorResponse
from app.schemas.reading import ReadingResponse
from app.schemas.site import SiteResponse
from app.services.site import SiteNotFoundError
@@ -34,3 +35,22 @@ async def get_site(site_id: str, _: LecteurDep, service: SiteServiceDep) -> Site
status_code=status.HTTP_404_NOT_FOUND, detail="Site introuvable"
) from erreur
return SiteResponse.model_validate(site)
@router.get(
"/{site_id}/current",
response_model=ReadingResponse | None,
summary="Dernière mesure connue d'un site",
responses=REPONSES_INTROUVABLE,
)
async def get_current(
site_id: str, _: LecteurDep, sites: SiteServiceDep, readings: ReadingServiceDep
) -> ReadingResponse | None:
try:
await sites.get_by_id(site_id)
except SiteNotFoundError as erreur:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND, detail="Site introuvable"
) from erreur
derniere = await readings.get_latest(site_id)
return ReadingResponse.model_validate(derniere) if derniere is not None else None
+9
View File
@@ -21,6 +21,15 @@ class ReadingRepository:
)
return (await self._session.execute(requete)).scalars().all()
async def latest_for_site(self, site_id: str) -> Reading | None:
requete = (
select(Reading)
.where(Reading.site_id == site_id)
.order_by(Reading.timestamp.desc(), Reading.reading_id.desc())
.limit(1)
)
return (await self._session.scalars(requete)).first()
async def list_history(
self,
*,
+3
View File
@@ -20,6 +20,9 @@ class ReadingService:
def __init__(self, *, readings: ReadingRepository) -> None:
self._readings = readings
async def get_latest(self, site_id: str) -> Reading | None:
return await self._readings.latest_for_site(site_id)
async def list_history(
self,
*,