Files
jardin/docs/plans/2026-02-22-meteo-astuces-design.md

9.1 KiB

Météo + Astuces — Design Document

Date : 2026-02-22 Statut : Validé


Contexte

Le projet "Jardin" dispose déjà d'un service météo basique (Open-Meteo, cache JSON 3h, champs limités). Il dispose également de deux scripts d'essai exploratoires :

  • prevision meteo/open_meteo_garden_forecast.py — fetch Open-Meteo riche (sol, ETP, horaire)
  • station_meteo/local_station_weather.py — scraper WeeWX via RSS + HTML + NOAA monthly

La demande est d'intégrer ces deux sources dans l'application, avec affichage synthétique côté frontend et une nouvelle section "Astuces".


Décisions d'architecture

Question Choix retenu
Scheduler APScheduler intégré dans FastAPI
Stockage SQLite avec tables dédiées
Plage temporelle 7j passé + J0 + 7j futur
Modèle astuces Bibliothèque avec catégories + tags

Modèle de données

Table meteo_station

Données collectées depuis la station WeeWX locale (http://10.0.0.8:8081/).

CREATE TABLE meteo_station (
    date_heure  TEXT PRIMARY KEY,  -- ISO "2026-02-22T14:00" (heure arrondie)
    type        TEXT NOT NULL,     -- "current" | "veille"
    temp_ext    REAL,              -- °C
    temp_int    REAL,              -- °C (serre, si disponible)
    humidite    REAL,              -- %
    pression    REAL,              -- hPa
    pluie_mm    REAL,              -- précipitations accumulées
    vent_kmh    REAL,
    vent_dir    TEXT,              -- N/NE/E/SE/S/SO/O/NO
    uv          REAL,
    solaire     REAL,              -- W/m²
    source_url  TEXT               -- URL station au moment du fetch
);

Table meteo_open_meteo

Prévisions journalières depuis l'API Open-Meteo (gratuite, sans clé).

CREATE TABLE meteo_open_meteo (
    date         TEXT PRIMARY KEY,  -- "2026-02-22"
    t_min        REAL,
    t_max        REAL,
    pluie_mm     REAL,
    vent_kmh     REAL,
    wmo          INTEGER,           -- code WMO
    label        TEXT,              -- libellé WMO en français
    humidite_moy REAL,
    sol_0cm      REAL,              -- température sol surface
    etp_mm       REAL,              -- évapotranspiration
    fetched_at   TEXT               -- timestamp ISO du dernier fetch
);

Table astuces

Bibliothèque d'astuces jardinage.

CREATE TABLE astuces (
    id         INTEGER PRIMARY KEY AUTOINCREMENT,
    titre      TEXT NOT NULL,
    contenu    TEXT NOT NULL,
    categorie  TEXT NOT NULL,  -- "plante"|"jardin"|"tache"|"general"|"ravageur"|"maladie"
    tags       TEXT,           -- JSON array, ex: ["tomate","semis","printemps"]
    mois       TEXT,           -- JSON array, ex: [3,4,5] ou null = toute l'année
    created_at TEXT NOT NULL   -- ISO datetime
);

Scheduler APScheduler

Intégré dans backend/app/main.py via apscheduler (lifespan).

from apscheduler.schedulers.asyncio import AsyncIOScheduler

scheduler = AsyncIOScheduler()

# Job 1 — données station courantes (1x/h)
scheduler.add_job(collect_station_current, "interval", hours=1, id="station_current")

# Job 2 — résumé veille NOAA (1x/j à 06h00)
scheduler.add_job(collect_station_veille, "cron", hour=6, id="station_veille")

# Job 3 — prévisions Open-Meteo (1x/h)
scheduler.add_job(collect_open_meteo, "interval", hours=1, id="open_meteo")

Les jobs s'exécutent également au démarrage (next_run_time=datetime.now()).


Endpoints API

Météo

Méthode Route Description
GET /api/meteo/station/current Dernière mesure station (SELECT dernière ligne)
GET /api/meteo/station/history?days=7 Historique station N jours
GET /api/meteo/previsions?days=7 Prévisions Open-Meteo (SELECT depuis J+1)
GET /api/meteo/tableau Tableau synthétique 7j passé + J0 + 7j futur
POST /api/meteo/refresh Déclenche les 3 jobs manuellement (admin)

Format réponse /api/meteo/tableau

{
  "rows": [
    {
      "date": "2026-02-15",
      "type": "passe",
      "station": { "t_min": 2.1, "t_max": 8.4, "pluie_mm": 0.0, "vent_kmh": 12.0, "humidite": 78.0 },
      "open_meteo": null
    },
    {
      "date": "2026-02-22",
      "type": "aujourd_hui",
      "station": { "t_ext": 6.2, "humidite": 71.0, "pluie_mm": 0.2, "vent_kmh": 8.0 },
      "open_meteo": { "t_min": 4.0, "t_max": 9.0, "pluie_mm": 1.2, "wmo": 61, "label": "Pluie légère" }
    },
    {
      "date": "2026-02-23",
      "type": "futur",
      "station": null,
      "open_meteo": { "t_min": 3.5, "t_max": 11.2, "pluie_mm": 0.0, "wmo": 1, "label": "Plutôt clair" }
    }
  ]
}

Astuces

Méthode Route Description
GET /api/astuces?categorie=plante&mois=3&tag=tomate Liste filtrée
POST /api/astuces Créer une astuce
PUT /api/astuces/{id} Modifier
DELETE /api/astuces/{id} Supprimer

Frontend

CalendrierView.vue — onglet Météo (refonte)

L'onglet météo actuel (grille simple 7 jours) est remplacé par :

  1. Widget "Maintenant" (en haut) — depuis /api/meteo/station/current

    • Température extérieure, humidité, vent, pression
    • Icône SVG WMO si disponible
  2. Tableau synthétique — depuis /api/meteo/tableau

┌──────────┬────────────────────────┬──────────────────────┐
│  Date    │  Station WeeWX         │  Open-Meteo          │
├──────────┼────────────────────────┼──────────────────────┤
│ 15 fév ← │  2° / 8°  💧0  💨12  │  —                   │
│ ...      │  ...                   │  ...                 │
│ Auj. 🔵  │  6° act. 💧0.2 💨8   │  4/9° 🌦 1.2mm      │
│ Dem. →   │  —                     │  3/11° ☀️ 0mm       │
│ ...      │  —                     │  prévisions 7j       │
└──────────┴────────────────────────┴──────────────────────┘
  • Lignes passé : opacité réduite
  • Ligne aujourd'hui : bordure verte border-green
  • Colonne station absente pour futur →
  • Icône SVG WMO dans la colonne Open-Meteo (fichiers existants dans /icons/weather/)

AstucessView.vue — nouvelle vue

Route : /astuces Icône drawer : 💡 Astuces

Structure :

  • Tabs par catégorie (Toutes / Plante / Jardin / Tâche / Ravageur / Maladie / Général)
  • Filtre mois courant activé par défaut (badge "Ce mois" désactivable)
  • Chips de tags cliquables pour filtrer
  • Liste de cartes : titre + contenu (truncated) + tags + mois
  • Bouton "+ Ajouter"

Modal création/édition :

  • Titre (input)
  • Contenu (textarea, multiline)
  • Catégorie (select)
  • Tags (input avec séparateur virgule → chips visuels)
  • Mois pertinents (12 cases à cocher, ou "Toute l'année")

Fichiers à créer / modifier

Backend

Fichier Action
backend/app/models/meteo.py Créer : modèles SQLModel MeteoStation, MeteoOpenMeteo
backend/app/models/astuce.py Modifier : compléter le modèle existant
backend/app/services/station.py Créer : service scraping WeeWX (extrait de station_meteo/local_station_weather.py)
backend/app/services/meteo.py Modifier : enrichir le fetch Open-Meteo (champs sol, ETP, humidité)
backend/app/services/scheduler.py Créer : APScheduler + 3 jobs
backend/app/routers/meteo.py Modifier : ajouter les 4 nouveaux endpoints
backend/app/routers/astuces.py Modifier : compléter le CRUD existant
backend/app/main.py Modifier : intégrer le scheduler dans le lifespan
backend/requirements.txt Ajouter : apscheduler>=3.10

Frontend

Fichier Action
frontend/src/views/CalendrierView.vue Modifier : refonte onglet Météo
frontend/src/views/AstucessView.vue Créer : nouvelle vue astuces
frontend/src/api/astuces.ts Créer : client API astuces
frontend/src/stores/astuces.ts Créer : store Pinia astuces
frontend/src/router/index.ts Modifier : ajouter route /astuces
frontend/src/components/AppDrawer.vue Modifier : ajouter entrée 💡 Astuces

Gestion des erreurs

  • Station hors ligne : le job station_current catch l'exception, log l'erreur, ne plante pas le scheduler. L'endpoint /api/meteo/station/current retourne null si aucune donnée récente (< 2h).
  • Open-Meteo indisponible : le job open_meteo catch l'exception, conserve les données existantes en BDD. Le tableau affiche les prévisions du dernier fetch réussi.
  • Tableau sans données station : la colonne station affiche pour les jours sans mesure. Pas d'erreur côté frontend.

Tests

  • backend/tests/test_meteo.py : test endpoints tableau (mock des tables), test format réponse
  • backend/tests/test_astuces.py : test CRUD complet, test filtres catégorie + mois + tag
  • Tests unitaires pour parse_station_current() et summarize_open_meteo()