# 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()`
