This commit is contained in:
2026-03-09 18:26:04 +01:00
parent 2d5e5a05a2
commit 2043a1b8b5

363
docs/synthese-dev.md Normal file
View File

@@ -0,0 +1,363 @@
# Jardin App — Synthèse de développement
> Application web de gestion de jardins (potager, serre, plein air), self-hosted, mobile-first.
> Thème visuel : **Gruvbox Dark "seventies"** — vintage, chaleureux, contrasté.
> Date de dernière mise à jour : **2026-03-09**
---
## Stack technique
| Couche | Technologie |
|--------|-------------|
| Backend | Python 3.13 · FastAPI · SQLModel · SQLite |
| Frontend | Vue 3 · Vite · TypeScript · Tailwind CSS · Pinia |
| Tests | pytest (49 tests) |
| Déploiement | Docker Compose (backend + frontend + volumes db/uploads) |
**Ports locaux :** backend `8060`, frontend `5173` (dev) / nginx (prod)
---
## Architecture générale
```
jardin/
├── backend/
│ ├── app/
│ │ ├── main.py — lifespan, routers, migration auto, seed
│ │ ├── migrate.py — ALTER TABLE pour colonnes manquantes (sans perte)
│ │ ├── seed.py — données de démo (plantes, outils, dictons, astuces)
│ │ ├── config.py — DATABASE_URL, STATION_URL, METEO_LAT/LON
│ │ ├── database.py — engine SQLite + PRAGMA foreign_keys=ON
│ │ ├── models/ — SQLModel tables
│ │ └── routers/ — endpoints FastAPI par domaine
│ ├── scripts/ — scripts one-shot (migration, import)
│ └── tests/ — pytest (49 tests)
├── frontend/
│ ├── src/
│ │ ├── api/ — clients Axios par domaine (*.ts + *.js)
│ │ ├── stores/ — stores Pinia
│ │ ├── views/ — pages Vue (16 vues)
│ │ └── utils/ — helpers partagés
│ └── tailwind.config.js — palette Gruvbox Dark personnalisée
├── data/
│ ├── jardin.db — base SQLite de production
│ └── uploads/ — photos uploadées (UUID.jpg)
└── docs/ — plans, designs, données source
```
---
## Modèle de données (tables actives)
| Table | Rôle |
|-------|------|
| `plant` | Plantes (nom commun, famille, caractéristiques culture) |
| `plant_variety` | Variétés d'une plante (variete, boutique, prix, poids, DLUO) |
| `plant_image` | Photos associées à une plante |
| `garden` | Jardins (nom, type, sol, exposition, dimensions) |
| `garden_cell` | Cases de la grille 2D du jardin |
| `planting` | Instance : plante X dans jardin Y |
| `planting_event` | Arrosage / taille / traitement / observation |
| `task` | Tâches (ponctuelles ou récurrentes avec fréquence_jours) |
| `tool` | Outils de jardinage |
| `media` | Photos (upload, identification PlantNet/YOLO) |
| `recolte` | Récoltes (quantité, unité, date) |
| `meteostation` | Données station météo locale (WeeWX) |
| `meteoopenmeteo` | Données open-meteo.com (prévisions) |
| `lunar_calendar_entry` | Calendrier lunaire (phases, types de jours) |
| `astuce` | Astuces jardinage (catégorie, mois, tags) |
| `dicton` | Dictons saisonniers |
| `saint_du_jour` | Saints du calendrier (366 entrées) |
| `achat_intrant` | Achats d'intrants (semences, engrais…) |
| `fabrication` | Fabrications maison (compost, préparations…) |
| `user_settings` | Préférences interface (tailles, URL station…) |
---
## Pages de l'interface (16 vues)
| Route | Vue | Description |
|-------|-----|-------------|
| `/` | DashboardView | Tâches du jour, météo résumée, plantations actives |
| `/jardins` | JardinsView | Liste et création de jardins |
| `/jardins/:id` | JardinDetailView | Grille 2D, cases, plantations par case |
| `/plantes` | PlantesView | Catalogue plantes + variétés, popup détail/édition |
| `/varietes` | VarietesView | Vue dédiée variétés (ancienne) |
| `/plantations` | PlantationsView | Liste filtrable, création, fiche plantation |
| `/taches` | TachesView | Kanban / liste des tâches |
| `/planning` | PlanningView | Calendrier 4 semaines |
| `/meteo` | CalendrierView | Tableau météo + lunaire + dictons + navigation |
| `/lunaire` | LunaireView | Calendrier lunaire détaillé |
| `/astuces` | AstucesView | Astuces filtrables par catégorie/mois/tag |
| `/bibliotheque` | BibliothequeView | Galerie photos + identification PlantNet/YOLO |
| `/outils` | OutilsView | Outils de jardinage (CRUD) |
| `/intrants` | IntratsView | Onglets Achats + Fabrications |
| `/reglages` | ReglagesView | URL station, backup ZIP, tailles UI |
---
## Historique du développement
### Phase 1 — Fondations (commit initial → `4787f04`)
- Scaffold projet Docker Compose (backend FastAPI + frontend Vue 3)
- 10 tables SQLModel, CRUD complet jardins / variétés / plantations / tâches
- Seed données démo : 1 jardin, variétés courantes, tâches types
- Frontend : layout header + drawer + router 9 routes, API layer + stores Pinia
- Vues MVP : Dashboard, Jardins, Grille, Variétés, Tâches, Plantations
### Phase 2 — Photos & identification (`2d8b1b4` → `326158f`)
- Service YOLO (container FastAPI dédié) pour détection locale de plantes
- Service PlantNet API pour identification par photo
- Cache Redis pour les identifications
- Endpoint `POST /api/identify` : PlantNet en premier, YOLO en fallback
- Vue BibliothequeView : galerie lightbox + modal d'upload + identification
- Fix : noms PlantNet en français (`lang=fr`), nginx `client_max_body_size 20M`
### Phase 3 — Météo & calendrier (`3032751` → `0d3bf20`)
- Tables `MeteoStation` + `MeteoOpenMeteo` (SQLModel)
- APScheduler 3 jobs : station WeeWX (1×/heure), open-meteo (1×/heure), cleanup
- Scraper WeeWX RSS pour données actuelles + NOAA pour données de la veille
- Service open-meteo enrichi (sol, ETP, past_days, humidité)
- 6 endpoints météo : tableau synthétique, station, prévisions, navigation
- CalendrierView : tableau météo + lunaire + dictons + navigation Prev/Today/Next
- Import calendrier lunaire (phases + types jours : racine/feuille/fleur/fruit)
- Table `saint_du_jour` : 366 entrées importées
### Phase 4 — Astuces & réglages UI (`cc69d0d` → `fb33540`)
- Table `astuce` : colonnes catégorie/tags/mois, CRUD + filtres
- AstucesView avec filtres catégorie / mois / tag
- Réglages interface : sliders taille texte/menu/icônes/miniatures (CSS vars globales)
- Export `UI_SIZE_DEFAULTS` partagé entre composants
### Phase 5 — Intrants (`faa469e` → `f8e64d6`)
- Tables `AchatIntrant` + `Fabrication` (SQLModel)
- Migration automatique via `migrate.py`
- Routers CRUD : achats, fabrications
- Frontend : stores Pinia + clients API + IntratsView avec onglets Achats / Fabrications
### Phase 6 — Plantes & Variétés — restructuration complète (`e40351e` → `4c279c3`)
> **Moteur principal de cette session (2026-03-08/09)**
Voir détail complet ci-dessous.
---
## Dernières modifications — Phase 6 : Plantes & Variétés
### Contexte
La gestion des plantes reposait sur une table unique où chaque entrée mêlait
les caractéristiques de l'espèce (nom commun, famille, culture) et les données
commerciales d'une variété particulière (boutique, prix, DLUO). Cette structure
rendait impossible d'associer plusieurs variétés à une même plante.
**Décision architecturale :** scission en 2 tables distinctes.
---
### Nouveau modèle de données
#### Table `plant` (espèce / nom commun)
```
id, nom_commun, nom_botanique, famille, type_plante, categorie
besoin_eau, besoin_soleil, espacement_cm, hauteur_cm, temp_min_c
temp_germination, temps_levee_j ← nouveaux champs
duree_culture_j, profondeur_semis_cm, sol_conseille
semis_interieur_mois, semis_exterieur_mois, repiquage_mois
plantation_mois, recolte_mois
maladies_courantes, astuces_culture, url_reference, notes
associations_favorables (JSON), associations_defavorables (JSON)
created_at
```
#### Table `plant_variety` (variété commerciale)
```
id, plant_id (FK → plant.id), variete, tags, notes_variete
boutique_nom, boutique_url, prix_achat, date_achat, poids, dluo
created_at
```
#### Schéma de réponse API `PlantWithVarieties`
Objet non persisté retourné par tous les `GET /api/plants*` :
```
= tous les champs de Plant + varieties: List[PlantVariety]
```
---
### Fichiers modifiés — Backend
| Fichier | Modification |
|---------|-------------|
| `backend/app/models/plant.py` | Modèle `Plant` épuré + `PlantVariety` + `PlantWithVarieties` + `PlantImage` |
| `backend/app/models/__init__.py` | Export `PlantVariety`, `PlantWithVarieties` |
| `backend/app/database.py` | Activation `PRAGMA foreign_keys=ON` (event listener SQLAlchemy) |
| `backend/app/migrate.py` | Ajout section `plant_variety` + colonnes `temp_germination`/`temps_levee_j` sur `plant` — suppression colonnes boutique/dluo de la section `plant` |
| `backend/app/seed.py` | Extraction du champ `variete` avant création `Plant` + création `PlantVariety` associée |
| `backend/app/routers/plants.py` | Remplacement complet : helper `_with_varieties()`, GET retourne `PlantWithVarieties`, CASCADE sur DELETE, 4 endpoints CRUD `/plants/{id}/varieties` |
| `backend/tests/test_plants.py` | Réécriture du test variétés pour la nouvelle architecture |
#### Nouveaux endpoints variétés
```
GET /api/plants → List[PlantWithVarieties]
GET /api/plants/{id} → PlantWithVarieties
POST /api/plants/{id}/varieties → PlantVariety (201)
GET /api/plants/{id}/varieties → List[PlantVariety]
PUT /api/plants/{id}/varieties/{vid} → PlantVariety
DELETE /api/plants/{id}/varieties/{vid} → 204
```
---
### Scripts one-shot (déjà exécutés)
#### `backend/scripts/migrate_plant_varieties.py`
Migration de la base existante :
- Crée la table `plant_variety`
- Migre `variete`/`tags`/`boutique_*` de chaque plant → une entrée `plant_variety`
- **Fusionne** le plant "haricot grimpant" (id=21) comme variété "Grimpant Neckarkönigin" sous "Haricot" (id=7) → supprime plant id=21
- Ajoute colonnes `temp_germination` et `temps_levee_j` à `plant`
Résultat : **20 plants · 21 variétés**
#### `backend/scripts/import_graines.py`
Import des données documentaires :
- `docs/graine/caracteristiques_plantation.json` : 14 sachets de graines
- `docs/arbustre/caracteristiques_arbustre.json` : 4 arbustes/fruitiers
- Mapping intelligent nom sachet → nom commun BDD (`NOM_MAP`)
- Conversion mois romains → CSV (`roman_to_csv` : "III-IV" → "3,4")
- Enrichissement des champs `plant` (semis, récolte, profondeur, espacement, T° germination)
- Création `PlantVariety` avec nom de variété extrait
- Copie des **36 photos de sachets** dans `data/uploads/` + entrées `media`
- Script idempotent (guard `SELECT` avant chaque `INSERT`)
Résultat : **25 plants · 39 variétés · 36 photos importées**
---
### Fichiers modifiés — Frontend
| Fichier | Modification |
|---------|-------------|
| `frontend/src/api/plants.ts` | Interface `PlantVariety` + interface `Plant` mise à jour (sans boutique/variete, avec `varieties?`, `temp_germination?`, `temps_levee_j?`) + méthodes `createVariety`, `updateVariety`, `deleteVariety` |
| `frontend/src/stores/plants.ts` | Actions `update`, `createVariety`, `updateVariety`, `removeVariety` avec synchronisation du store local |
| `frontend/src/views/PlantesView.vue` | Voir détail ci-dessous |
| `frontend/src/views/TachesView.vue` | Adaptation `plant.varieties?.[0]?.variete` (champ déplacé) |
| `frontend/src/utils/plants.ts` | `formatPlantLabel` utilise `varieties?.[0]?.variete` au lieu de `plant.variete` |
#### PlantesView.vue — nouvelles fonctionnalités
**Script setup :**
- `detailPlantObj = ref<Plant | null>` : référence à la plante affichée
- `detailVarieties = computed(() => detailPlantObj.value?.varieties ?? [])` : liste des variétés
- Refs formulaire variété : `showFormVariety`, `editVariety`, `formVariety`
- Fonctions : `openAddVariety`, `openEditVariety`, `closeFormVariety`, `submitVariety`, `deleteVariety`
- `submitPlant` : extrait les champs variété du form avant envoi au backend, puis crée/modifie la `PlantVariety` séparément
**Template :**
- Bouton **" Variété"** dans le footer du popup détail plante
- Champs `temp_germination` et `temps_levee_j` dans la section caractéristiques (conditionnels)
- Liste des variétés dans le popup détail avec boutons ✏️ et ✕, affichage DLUO expirée
- **Popup formulaire variété** (`z-[70]`) : nom variété, tags, enseigne (select BOUTIQUES), prix, poids, date achat, DLUO, URL, notes
---
### État de la base de production après la phase 6
```
plant : 25 entrées (20 potager + 5 arbustes)
plant_variety : 39 entrées
media : 86 entrées (dont 36 photos sachets)
```
Répartition par catégorie :
- **Potager** : Tomate, Carotte, Courgette, Laitue, Ail, Oignon, Haricot (×2 variétés), Pois (×2), Poireau, Pomme de terre, Fraise, Framboise, Persil, Échalote, Chou-fleur, Chou, Betterave, Radis, Épinard, Basilic, Courge
- **Arbuste** : Vigne, Cassissier, Framboisier
---
## État des tests
```
49 tests passed (pytest)
Couverture par fichier de test :
test_gardens.py — CRUD jardins + filtres
test_plants.py — CRUD plantes + test_plant_variety_crud (nouvelle architecture)
test_plantings.py — CRUD plantations
test_tasks.py — CRUD tâches + filtres statut/planting
test_tools.py — CRUD outils
test_varieties.py — CRUD variétés (ancienne table)
```
---
## Points en cours / backlog
### Fonctionnalités planifiées (amelioration.md)
- [ ] Photos sachet recto/verso dans le popup variété (upload + compression WebP)
- [ ] Page 404 catch-all (route Vue manquante)
- [ ] Export/import JSON complet
- [ ] Observations dans PlantationsView (backend prêt, UI manquante)
- [ ] Restauration depuis ZIP (upload + restore)
- [ ] Réglages par sections (interface, jardin, plante, tâches, calendrier)
- [ ] Capteurs IoT : ensoleillement, température sol/air, humidité, pH (MQTT)
- [ ] "Astuce du jour" dans le Dashboard
- [ ] Vue Gantt dans PlanningView
- [ ] Satisfaction plante (étoiles 1-5)
### Dette technique connue
- **Colonnes orphelines dans `plant`** : `variete`, `tags`, `boutique_*`, `prix_achat`, `date_achat`, `poids`, `dluo` toujours présentes en SQLite (impossibilité de DROP COLUMN) — SQLModel les ignore, pas d'impact fonctionnel
- **`PlantCreate` manquant** : les endpoints `POST/PUT /api/plants` acceptent le modèle `Plant` (table=True) directement au lieu d'un schéma `PlantCreate` dédié
- **N+1 queries sur `GET /api/plants`** : une query par plante pour charger ses variétés — acceptable jusqu'à ~200 plantes
- **`planting.variety_id` FK mal nommée** : pointe vers `plant.id` et non `plant_variety.id` — à corriger avant d'implémenter le choix de variété lors d'une plantation
- **Données orphelines BDD** : jardin id=1 supprimé mais 24 `garden_cells` + 1 `planting` + 1 `measurement` subsistent
---
## Commandes utiles
```bash
# Lancer l'environnement complet
docker compose up --build
# Backend seul (développement)
cd backend && uvicorn app.main:app --reload --port 8060
# Frontend seul (développement)
cd frontend && npm run dev
# Tests backend
cd backend && python3 -m pytest tests/ -v
# Build frontend
cd frontend && npm run build
# Réexécuter l'import graines (idempotent)
python3 backend/scripts/import_graines.py
# Vérifier état BDD
python3 -c "
import sqlite3; conn = sqlite3.connect('data/jardin.db')
print('plant:', conn.execute('SELECT COUNT(*) FROM plant').fetchone()[0])
print('plant_variety:', conn.execute('SELECT COUNT(*) FROM plant_variety').fetchone()[0])
print('media:', conn.execute('SELECT COUNT(*) FROM media').fetchone()[0])
conn.close()
"
```