Files
jardin/backend/app/routers/meteo.py
2026-02-22 18:34:50 +01:00

184 lines
6.0 KiB
Python

"""Router météo — station WeeWX + Open-Meteo + tableau synthétique."""
from datetime import date, timedelta
from typing import Any, Optional
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy import text
from sqlmodel import Session
from app.database import get_session
router = APIRouter(tags=["météo"])
def _station_daily_summary(session: Session, iso_date: str) -> Optional[dict]:
"""Agrège les mesures horaires d'une journée en résumé."""
rows = session.exec(
text(
"SELECT temp_ext, t_min, t_max, pluie_mm, vent_kmh, humidite "
"FROM meteostation WHERE substr(date_heure, 1, 10) = :d"
),
params={"d": iso_date},
).fetchall()
if not rows:
return None
temps = [r[0] for r in rows if r[0] is not None]
t_mins = [r[1] for r in rows if r[1] is not None]
t_maxs = [r[2] for r in rows if r[2] is not None]
pluies = [r[3] for r in rows if r[3] is not None]
vents = [r[4] for r in rows if r[4] is not None]
hums = [r[5] for r in rows if r[5] is not None]
min_candidates = temps + t_mins
max_candidates = temps + t_maxs
return {
"t_min": round(min(min_candidates), 1) if min_candidates else None,
"t_max": round(max(max_candidates), 1) if max_candidates else None,
# WeeWX RSS expose souvent une pluie cumulée journalière.
"pluie_mm": round(max(pluies), 1) if pluies else 0.0,
"vent_kmh": round(max(vents), 1) if vents else None,
"humidite": round(sum(hums) / len(hums), 0) if hums else None,
}
def _station_current_row(session: Session) -> Optional[dict]:
"""Dernière mesure station (max 2h d'ancienneté)."""
row = session.exec(
text(
"SELECT temp_ext, humidite, pression, pluie_mm, vent_kmh, vent_dir, uv, solaire, date_heure "
"FROM meteostation WHERE type='current' ORDER BY date_heure DESC LIMIT 1"
)
).fetchone()
if not row:
return None
return {
"temp_ext": row[0], "humidite": row[1], "pression": row[2],
"pluie_mm": row[3], "vent_kmh": row[4], "vent_dir": row[5],
"uv": row[6], "solaire": row[7], "date_heure": row[8],
}
def _open_meteo_day(session: Session, iso_date: str) -> Optional[dict]:
row = session.exec(
text(
"SELECT t_min, t_max, pluie_mm, vent_kmh, wmo, label, humidite_moy, sol_0cm, etp_mm "
"FROM meteoopenmeteo WHERE date = :d"
),
params={"d": iso_date},
).fetchone()
if not row:
return None
return {
"t_min": row[0], "t_max": row[1], "pluie_mm": row[2],
"vent_kmh": row[3], "wmo": row[4], "label": row[5],
"humidite_moy": row[6], "sol_0cm": row[7], "etp_mm": row[8],
}
@router.get("/meteo/tableau")
def get_tableau(
center_date: Optional[str] = Query(None, description="Date centrale YYYY-MM-DD"),
span: int = Query(7, ge=1, le=31, description="Nombre de jours avant/après la date centrale"),
session: Session = Depends(get_session),
) -> dict[str, Any]:
"""Tableau synthétique centré sur une date, avec historique + prévision."""
today = date.today()
center = today
if center_date:
try:
center = date.fromisoformat(center_date)
except ValueError as exc:
raise HTTPException(status_code=400, detail="center_date invalide (format YYYY-MM-DD)") from exc
rows = []
for delta in range(-span, span + 1):
d = center + timedelta(days=delta)
iso = d.isoformat()
delta_today = (d - today).days
if delta_today < 0:
row_type = "passe"
station = _station_daily_summary(session, iso)
om = _open_meteo_day(session, iso)
elif delta_today == 0:
row_type = "aujourd_hui"
station_current = _station_current_row(session) or {}
station_daily = _station_daily_summary(session, iso) or {}
station = {**station_daily, **station_current} or None
om = _open_meteo_day(session, iso)
else:
row_type = "futur"
station = None
om = _open_meteo_day(session, iso)
rows.append({"date": iso, "type": row_type, "station": station, "open_meteo": om})
return {"rows": rows}
@router.get("/meteo/station/current")
def get_station_current(session: Session = Depends(get_session)) -> Optional[dict]:
return _station_current_row(session)
@router.get("/meteo/station/history")
def get_station_history(
days: int = Query(7, ge=1, le=30),
session: Session = Depends(get_session),
) -> dict[str, Any]:
today = date.today()
result = []
for delta in range(-days, 0):
d = today + timedelta(days=delta)
iso = d.isoformat()
summary = _station_daily_summary(session, iso)
result.append({"date": iso, "station": summary})
return {"days": result}
@router.get("/meteo/previsions")
def get_previsions(
days: int = Query(7, ge=1, le=14),
session: Session = Depends(get_session),
) -> dict[str, Any]:
today = date.today()
result = []
for delta in range(0, days + 1):
d = today + timedelta(days=delta)
iso = d.isoformat()
om = _open_meteo_day(session, iso)
if om:
result.append({"date": iso, **om})
return {"days": result}
@router.get("/meteo")
def get_meteo_legacy(
days: int = Query(14, ge=1, le=16),
lat: float = Query(45.14),
lon: float = Query(4.12),
):
"""Compatibilité ascendante avec l'ancien endpoint."""
from app.services.meteo import fetch_forecast
return fetch_forecast(lat=lat, lon=lon, days=days)
@router.post("/meteo/refresh")
def refresh_meteo() -> dict[str, str]:
"""Force le rafraîchissement immédiat des 3 jobs."""
from app.services.scheduler import scheduler
import datetime as dt
for job_id in ["station_current", "station_veille", "open_meteo"]:
job = scheduler.get_job(job_id)
if job:
job.modify(next_run_time=dt.datetime.now())
return {"status": "refresh planifié"}