184 lines
6.0 KiB
Python
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é"}
|