# 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/`). ```sql 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é). ```sql 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. ```sql 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). ```python 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` ```json { "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()`