maj
This commit is contained in:
363
docs/synthese-dev.md
Normal file
363
docs/synthese-dev.md
Normal 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()
|
||||
"
|
||||
```
|
||||
Reference in New Issue
Block a user