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