173 lines
5.9 KiB
Python
173 lines
5.9 KiB
Python
"""Scheduler APScheduler — 3 jobs de collecte météo."""
|
|
import logging
|
|
from datetime import datetime
|
|
|
|
from apscheduler.schedulers.asyncio import AsyncIOScheduler
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
scheduler = AsyncIOScheduler(timezone="Europe/Paris")
|
|
|
|
|
|
def _store_station_current() -> None:
|
|
"""Collecte et stocke les données actuelles de la station."""
|
|
from app.services.station import fetch_current
|
|
from app.models.meteo import MeteoStation
|
|
from app.database import engine
|
|
from sqlmodel import Session
|
|
|
|
data = fetch_current()
|
|
if not data:
|
|
logger.warning("Station current: aucune donnée collectée")
|
|
return
|
|
|
|
now_str = datetime.now().strftime("%Y-%m-%dT%H:00")
|
|
entry = MeteoStation(date_heure=now_str, type="current", **data)
|
|
|
|
with Session(engine) as session:
|
|
existing = session.get(MeteoStation, now_str)
|
|
if existing:
|
|
for k, v in data.items():
|
|
setattr(existing, k, v)
|
|
session.add(existing)
|
|
else:
|
|
session.add(entry)
|
|
session.commit()
|
|
logger.info(f"Station current stockée : {now_str}")
|
|
|
|
|
|
def _store_station_veille() -> None:
|
|
"""Collecte et stocke le résumé de la veille (NOAA)."""
|
|
from datetime import timedelta
|
|
from app.services.station import fetch_yesterday_summary
|
|
from app.models.meteo import MeteoStation
|
|
from app.database import engine
|
|
from sqlmodel import Session
|
|
|
|
data = fetch_yesterday_summary()
|
|
if not data:
|
|
logger.warning("Station veille: aucune donnée collectée")
|
|
return
|
|
|
|
yesterday = (datetime.now() - timedelta(days=1)).strftime("%Y-%m-%dT00:00")
|
|
entry = MeteoStation(date_heure=yesterday, type="veille", **data)
|
|
|
|
with Session(engine) as session:
|
|
existing = session.get(MeteoStation, yesterday)
|
|
if existing:
|
|
for k, v in data.items():
|
|
setattr(existing, k, v)
|
|
session.add(existing)
|
|
else:
|
|
session.add(entry)
|
|
session.commit()
|
|
logger.info(f"Station veille stockée : {yesterday}")
|
|
|
|
|
|
def _store_open_meteo() -> None:
|
|
"""Collecte et stocke les prévisions Open-Meteo."""
|
|
from app.services.meteo import fetch_and_store_forecast
|
|
from app.models.meteo import MeteoOpenMeteo
|
|
from app.database import engine
|
|
from sqlmodel import Session
|
|
|
|
rows = fetch_and_store_forecast()
|
|
if not rows:
|
|
logger.warning("Open-Meteo: aucune donnée collectée")
|
|
return
|
|
|
|
with Session(engine) as session:
|
|
for row in rows:
|
|
existing = session.get(MeteoOpenMeteo, row["date"])
|
|
if existing:
|
|
for k, v in row.items():
|
|
if k != "date":
|
|
setattr(existing, k, v)
|
|
session.add(existing)
|
|
else:
|
|
session.add(MeteoOpenMeteo(**row))
|
|
session.commit()
|
|
logger.info(f"Open-Meteo stocké : {len(rows)} jours")
|
|
|
|
|
|
def backfill_station_missing_dates(max_days_back: int = 365) -> None:
|
|
"""Remplit les dates manquantes de la station météo au démarrage.
|
|
|
|
Cherche toutes les dates sans entrée « veille » dans meteostation
|
|
depuis max_days_back jours en arrière jusqu'à hier (excl. aujourd'hui),
|
|
puis télécharge les fichiers NOAA mois par mois pour remplir les trous.
|
|
Un seul appel HTTP par mois manquant.
|
|
"""
|
|
from datetime import date, timedelta
|
|
from itertools import groupby
|
|
from app.services.station import fetch_month_summaries
|
|
from app.models.meteo import MeteoStation
|
|
from app.database import engine
|
|
from sqlmodel import Session, select
|
|
|
|
today = date.today()
|
|
start_date = today - timedelta(days=max_days_back)
|
|
|
|
# 1. Dates « veille » déjà présentes en BDD
|
|
with Session(engine) as session:
|
|
rows = session.exec(
|
|
select(MeteoStation.date_heure).where(MeteoStation.type == "veille")
|
|
).all()
|
|
existing_dates: set[str] = {dh[:10] for dh in rows}
|
|
|
|
# 2. Dates manquantes entre start_date et hier (aujourd'hui exclu)
|
|
missing: list[date] = []
|
|
cursor = start_date
|
|
while cursor < today:
|
|
if cursor.isoformat() not in existing_dates:
|
|
missing.append(cursor)
|
|
cursor += timedelta(days=1)
|
|
|
|
if not missing:
|
|
logger.info("Backfill station : aucune date manquante")
|
|
return
|
|
|
|
logger.info(f"Backfill station : {len(missing)} date(s) manquante(s) à récupérer")
|
|
|
|
# 3. Grouper par (année, mois) → 1 requête HTTP par mois
|
|
def month_key(d: date) -> tuple[int, int]:
|
|
return (d.year, d.month)
|
|
|
|
filled = 0
|
|
for (year, month), group_iter in groupby(sorted(missing), key=month_key):
|
|
month_data = fetch_month_summaries(year, month)
|
|
if not month_data:
|
|
logger.debug(f"Backfill station : pas de données NOAA pour {year}-{month:02d}")
|
|
continue
|
|
|
|
with Session(engine) as session:
|
|
for d in group_iter:
|
|
data = month_data.get(d.day)
|
|
if not data:
|
|
continue
|
|
date_heure = f"{d.isoformat()}T00:00"
|
|
if not session.get(MeteoStation, date_heure):
|
|
session.add(MeteoStation(date_heure=date_heure, type="veille", **data))
|
|
filled += 1
|
|
session.commit()
|
|
|
|
logger.info(f"Backfill station terminé : {filled} date(s) insérée(s)")
|
|
|
|
|
|
def setup_scheduler() -> None:
|
|
"""Configure et démarre le scheduler."""
|
|
scheduler.add_job(
|
|
_store_station_current, "interval", hours=1,
|
|
next_run_time=datetime.now(), id="station_current", replace_existing=True,
|
|
)
|
|
scheduler.add_job(
|
|
_store_station_veille, "cron", hour=6, minute=0,
|
|
next_run_time=datetime.now(), id="station_veille", replace_existing=True,
|
|
)
|
|
scheduler.add_job(
|
|
_store_open_meteo, "interval", hours=1,
|
|
next_run_time=datetime.now(), id="open_meteo", replace_existing=True,
|
|
)
|
|
scheduler.start()
|
|
logger.info("Scheduler météo démarré (3 jobs)")
|