diff --git a/docs/plans/2026-02-22-meteo-astuces-design.md b/docs/plans/2026-02-22-meteo-astuces-design.md new file mode 100644 index 0000000..017e4c9 --- /dev/null +++ b/docs/plans/2026-02-22-meteo-astuces-design.md @@ -0,0 +1,257 @@ +# 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()`