diff --git a/backend/app/routers/meteo.py b/backend/app/routers/meteo.py new file mode 100644 index 0000000..e339b0d --- /dev/null +++ b/backend/app/routers/meteo.py @@ -0,0 +1,163 @@ +"""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, 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, 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] + pluies = [r[1] for r in rows if r[1] is not None] + vents = [r[2] for r in rows if r[2] is not None] + hums = [r[3] for r in rows if r[3] is not None] + + return { + "t_min": round(min(temps), 1) if temps else None, + "t_max": round(max(temps), 1) if temps else None, + "pluie_mm": round(sum(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(session: Session = Depends(get_session)) -> dict[str, Any]: + """Tableau synthétique : 7j passé + J0 + 7j futur.""" + today = date.today() + rows = [] + + for delta in range(-7, 8): + d = today + timedelta(days=delta) + iso = d.isoformat() + + if delta < 0: + row_type = "passe" + station = _station_daily_summary(session, iso) + om = None # Pas de prévision pour le passé + elif delta == 0: + row_type = "aujourd_hui" + station = _station_current_row(session) + 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é"}