# Plant Associations Implementation Plan > **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. **Goal:** Ajouter des associations favorables/défavorables (plantes amies/ennemies) à chaque plante, éditables depuis la popup d'édition. **Architecture:** Deux colonnes JSON (`associations_favorables`, `associations_defavorables`) dans le modèle `Plant` (List[str] de noms communs). Migration via migrate.py. UI tag-based avec autocomplete et validation croisée dans PlantesView.vue. **Tech Stack:** FastAPI + SQLModel + SQLAlchemy JSON column (backend) · Vue 3 + TypeScript (frontend) --- ### Task 1: Backend — modèle Plant **Files:** - Modify: `backend/app/models/plant.py` **Step 1: Ajouter les imports nécessaires** ```python from typing import List, Optional from sqlalchemy import Column from sqlalchemy import JSON as SA_JSON ``` **Step 2: Ajouter les 2 champs dans la classe `Plant`** Après le champ `notes`, avant `created_at` : ```python associations_favorables: Optional[List[str]] = Field( default=None, sa_column=Column("associations_favorables", SA_JSON, nullable=True), ) associations_defavorables: Optional[List[str]] = Field( default=None, sa_column=Column("associations_defavorables", SA_JSON, nullable=True), ) ``` --- ### Task 2: Backend — migration **Files:** - Modify: `backend/app/migrate.py` **Step 1: Ajouter les 2 colonnes dans `EXPECTED_COLUMNS["plant"]`** ```python "plant": [ ("categorie", "TEXT", None), ("hauteur_cm", "INTEGER", None), ("maladies_courantes", "TEXT", None), ("astuces_culture", "TEXT", None), ("url_reference", "TEXT", None), ("associations_favorables", "TEXT", None), # JSON list[str] ("associations_defavorables", "TEXT", None), # JSON list[str] ], ``` --- ### Task 3: Frontend — interface TypeScript **Files:** - Modify: `frontend/src/api/plants.ts` **Step 1: Ajouter les 2 champs à l'interface `Plant`** ```typescript associations_favorables?: string[] associations_defavorables?: string[] ``` --- ### Task 4: Frontend — PlantesView.vue (formulaire + détail) **Files:** - Modify: `frontend/src/views/PlantesView.vue` **Step 1: Étendre `form` reactive** Dans `const form = reactive({...})` ajouter : ```typescript associations_favorables: [] as string[], associations_defavorables: [] as string[], ``` **Step 2: Ajouter ref pour l'autocomplete** ```typescript const assocInput = reactive({ fav: '', def: '' }) ``` **Step 3: Computed — noms de plantes disponibles pour l'autocomplete** ```typescript const allPlantNames = computed(() => plantsStore.plants .map(p => p.nom_commun) .filter(n => n && n !== form.nom_commun) .sort() ) function filteredAssocSuggestions(type: 'fav' | 'def') { const query = assocInput[type].toLowerCase() const excluded = type === 'fav' ? new Set([...form.associations_favorables, ...form.associations_defavorables]) : new Set([...form.associations_defavorables, ...form.associations_favorables]) return allPlantNames.value .filter(n => !excluded.has(n) && n.toLowerCase().includes(query)) .slice(0, 8) } function addAssoc(type: 'fav' | 'def', name: string) { const list = type === 'fav' ? form.associations_favorables : form.associations_defavorables const other = type === 'fav' ? form.associations_defavorables : form.associations_favorables if (!name.trim() || list.includes(name) || other.includes(name)) return list.push(name) assocInput[type] = '' } function removeAssoc(type: 'fav' | 'def', name: string) { const list = type === 'fav' ? form.associations_favorables : form.associations_defavorables const idx = list.indexOf(name) if (idx !== -1) list.splice(idx, 1) } ``` **Step 4: `startEdit` — peupler les nouvelles listes** ```typescript associations_favorables: [...(p.associations_favorables ?? [])], associations_defavorables: [...(p.associations_defavorables ?? [])], ``` **Step 5: `submitPlant` — inclure les listes dans le payload** Le spread `{ ...form }` les inclut automatiquement — rien à changer. **Step 6: Ajouter le bloc UI dans le formulaire (pleine largeur, après les 2 colonnes existantes)** ```html
{{ n }}
  • {{ s }}
{{ n }}
  • {{ s }}
``` **Step 7: Ajouter le bloc lecture dans la modale détail (après la section Notes)** ```html

Associations

🤝 Favorables
{{ n }}
⚡ À éviter
{{ n }}
```