diff --git a/docs/synthese-dev.md b/docs/synthese-dev.md new file mode 100644 index 0000000..8709b49 --- /dev/null +++ b/docs/synthese-dev.md @@ -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` : 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() +" +```