docs: design météo + astuces (APScheduler + SQLite + tableau synthétique)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
257
docs/plans/2026-02-22-meteo-astuces-design.md
Normal file
257
docs/plans/2026-02-22-meteo-astuces-design.md
Normal file
@@ -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()`
|
||||
Reference in New Issue
Block a user