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:
co-authored by
Claude Sonnet 5
parent
56c134beb0
commit
5d921a9b1e
@@ -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
|
||||
|
||||
@@ -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,
|
||||
*,
|
||||
|
||||
@@ -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,
|
||||
*,
|
||||
|
||||
Reference in New Issue
Block a user