961 lines
47 KiB
Markdown
961 lines
47 KiB
Markdown
---
|
||
type: journal
|
||
project: NAV V2
|
||
created: 2026-04-14
|
||
status: actif
|
||
---
|
||
|
||
# NAV V2 — Journal de développement
|
||
|
||
Journal technique de la V2. Décisions, anomalies, points bloquants, TODOs.
|
||
|
||
---
|
||
|
||
## 2026-04-15 — Session 5 : Corrections post Phase 2 (11 retours Jules)
|
||
|
||
**Exécutant :** Sonnet (agent autonome full auto)
|
||
**Durée :** ~45min
|
||
**Commits :** 7 commits atomiques (8ae4be3 → d30ee2c)
|
||
|
||
### Retours implémentés
|
||
|
||
**#1 — Barre de recherche** ✅
|
||
- Desktop : supprimée du header (doublon avec sidebar `NavSidebar.vue`)
|
||
- Mobile : ajoutée dans le header (`app.vue`, visible `<lg`)
|
||
|
||
**#2 — Sheet swipable mobile** ✅
|
||
- Nouveau composant `MobileSheet.vue` — 3 états : collapsed (56px) / half (50dvh) / full (92dvh)
|
||
- Touch events natifs (pas de `@vueuse/core` — implémentation vanilla pour 0 dépendance)
|
||
- Drag handle + cycle d'états au clic header + snap-back si delta < 60px
|
||
- Vue mobile index.vue : carte pleine hauteur en fond + sheet en overlay
|
||
- Décision d'exécution : vanilla touch events choisis vs useSwipe (pas installé)
|
||
|
||
**#3 — Onglets header desktop** ✅
|
||
- 3 onglets centrés remplacent la barre de recherche desktop : Écosystème / Agences Inspirantes / RAG
|
||
- Active state via `route.path` + underline `--nav-primary-solid`
|
||
- Badge "en construction" sous les 2 onglets inactifs
|
||
|
||
**#4 — Supprimer "+ Ajouter carte"** ✅
|
||
- Bouton retiré du header `app.vue`
|
||
- Route `pages/ajouter-carte.vue` supprimée
|
||
|
||
**#5 — Report/modif participatif via Resend** ✅
|
||
- `server/api/report.post.ts` : rate limit 5/IP/jour, validation email + message, envoi Resend API
|
||
- `FicheDetail.vue` : bouton "Signaler une erreur" → form inline dépliable (message + email, compteur 500 chars)
|
||
- Email envoyé à `jules@trans-former.fr` (fallback Resend, pas NocoDB)
|
||
|
||
**#6 — Texte intro commentaires** ✅
|
||
- `CommentSection.vue` : intro italique avant la liste
|
||
|
||
**#7 — DOM-TOM row horizontale pleine largeur** ✅ (Option A)
|
||
- `OutremerMap.vue` : layout row flex 5 colonnes égales, hauteur 140px
|
||
- Desktop `index.vue` : layout vertical (Métropole flex-1 + DOM-TOM row 140px fixe en bas)
|
||
- Suppression de l'encart 1/3 droit
|
||
|
||
**#8 — Supprimer layer control carte** ✅
|
||
- `NavMap.vue` : `L.control.layers(...)` supprimé, seul CartoDB Positron reste
|
||
|
||
**#9 — Dark mode switch tuile** ✅
|
||
- `NavMap.vue` + `OutremerMap.vue` : `MutationObserver` sur `html.classList` → `setUrl()` light_all/dark_all
|
||
- Initialisation correcte dès le premier rendu (lire `document.documentElement.classList`)
|
||
|
||
**#10 — Bandeau bas : logique inversée** ✅
|
||
- `BandeauBas.vue` : `isCollapsed = ref(true)` par défaut
|
||
- `onMouseLeave` : repli immédiat (suppression du timer 3s)
|
||
- Opacité bandeau déployé : 70%
|
||
|
||
**#11 — Bouton Soutenir recentré** ✅
|
||
- `bandeau-col--center` : `padding-top: 8px` pour décaler vers le bas dans la hauteur du bandeau
|
||
|
||
### Cleanup
|
||
- Supprimés : `TerritoireTabs.vue`, `TerritoireToggle.vue` (composants morts)
|
||
- Supprimée : `pages/ajouter-carte.vue`
|
||
- Créées : `pages/agences.vue`, `pages/rag.vue` (placeholders)
|
||
- Créé : `components/MobileSheet.vue`
|
||
|
||
### Décisions d'exécution S5
|
||
- **#2 useSwipe** : `@vueuse/core` absent du `package.json` — vanilla touch events choisis pour éviter d'ajouter une dépendance. Même résultat, 0 dép supplémentaire.
|
||
- **#5 NocoDB vs Resend** : fallback Resend appliqué conformément aux décisions Jules pré-tranchées. Pas de table NocoDB créée.
|
||
- **DOM-TOM desktop** : layout du `index.vue` passé de `flex row (2/3 + 1/3)` à `flex col (Métropole flex-1 + DOM-TOM row 140px)` pour intégrer l'Option A cohéremment.
|
||
|
||
### Build & deploy
|
||
- Build : `npx nuxt build` ✅ (223 modules client, 134 modules serveur)
|
||
- Deploy : tar → SSH → `systemctl restart nav-carte` ✅
|
||
- Vérif : `curl https://aep.trans-former.fr/ → 200`, service `active`
|
||
|
||
---
|
||
|
||
## 2026-04-15 — Session 4 : Phase 2 UX (10 features)
|
||
|
||
**Exécutant :** Sonnet (agent délégué Opus)
|
||
**Durée :** ~1h30
|
||
|
||
### Réalisé
|
||
|
||
**Feature 1 — Modal fiche sidebar desktop** ✅
|
||
- Composant `FicheModal.vue` : overlay centré max-w-3xl (768px), 90vh scroll interne
|
||
- Backdrop semi-transparent, fermeture Esc / clic backdrop / bouton croix
|
||
- Lien "Ouvrir" → `/fiche/[id]` (URL partageable préservée)
|
||
- Fetch `$fetch('/api/fiche/X')` avec watch sur orgId
|
||
- `onSelectOrg` desktop (≥1024px) → ouvre modal
|
||
- Mobile : comportement inchangé (navigation vers `/fiche/[id]`)
|
||
|
||
**Feature 2 — Fusion Outre-mer (suppression TerritoireTabs)** ✅
|
||
- Desktop : layout flex `flex:2` Métropole + `flex:1` bandeau DOM-TOM (max 340px, OutremerMap réutilisé)
|
||
- Mobile : section DOM-TOM scroll horizontal avec compteurs par territoire
|
||
- `TerritoireTabs` retiré du template (composant conservé, non utilisé)
|
||
|
||
**Feature 3 — CartoDB Positron + layer control + maxBounds** ✅
|
||
- `NavMap.vue` : fond par défaut CartoDB Positron (`light_all`), layer control 3 fonds (Clair / Schématique Stamen / Standard OSM)
|
||
- `maxBounds` France métr. `[41-51.5°N, -5.5-10°E]`, `minZoom: 5`, `maxZoom: 18`
|
||
- `OutremerMap.vue` : fond CartoDB Positron par défaut (sans layer control)
|
||
|
||
**Feature 4 — Bandeau bas rétractable desktop** ✅
|
||
- `bg-opacity-80` (rgba 0.8)
|
||
- Auto-collapse après 3s, déploie au hover ou mouvement souris < 80px du bas
|
||
- Barre fine `32px` en état rétracté avec label "AEP · Transparence IA"
|
||
|
||
**Features 5+6 — Textes bandeau corrigés + lien Liberapay** ✅
|
||
- "Coût IA ce mois : X.XX €" + "Tokens : X" (sans ratio budget)
|
||
- Tooltip "1 € = 30 fiches mises en ligne" au hover bouton Soutenir
|
||
- Lien Liberapay → `https://liberapay.com/trans-former.fr/donate`
|
||
- Mobile : BandeauBas absent, remplacé par FAB coeur (gauche) → bottom sheet
|
||
|
||
**Feature 7 — Logo AEP tooltip** ✅
|
||
- Sous-titre "Architecture d'Écologie Politique" visible en `lg:`
|
||
- Tooltip au hover pour `sm:` (sans le sous-titre)
|
||
- Attribut `title` sur le lien pour accessibilité
|
||
|
||
**Feature 8 — Header desktop refonte + barre de recherche** ✅
|
||
- Barre de recherche ~500px centrée dans le header desktop (≥1024px)
|
||
- Sync URL `?q=` via watch, fonctionne depuis toutes les pages
|
||
- Boutons "+ Proposer" et "+ Ajouter carte" en haut à droite
|
||
- Route `/ajouter-carte` créée (placeholder)
|
||
|
||
**Feature 9 — Dark mode** ✅
|
||
- Toggle soleil/lune en top nav, persistance `localStorage` clé `aep_theme`
|
||
- Variables CSS `.dark` dans `main.css` : fonds sombres, texte clair
|
||
- Overrides Leaflet (popup, control layers)
|
||
- Note : tuile CartoDB dark (`dark_all`) non switchée automatiquement — à améliorer en S5
|
||
|
||
**Feature 10 — /a-propos CTA Contribuer + lien Liberapay** ✅
|
||
- Section "Contribuer" existante améliorée (lien `/donate`)
|
||
- Liens Liberapay unifiés sur `/donate`
|
||
|
||
### Commits
|
||
|
||
| Hash | Message |
|
||
|------|---------|
|
||
| `46b6051` | feat(aep-s4): CartoDB Positron + layer control + maxBounds France |
|
||
| `3c036db` | feat(aep-s4): logo AEP + tooltip nom complet + dark mode toggle |
|
||
| `775ec64` | feat(aep-s4): dark mode CSS variables + Leaflet overrides |
|
||
| `9736ba7` | feat(aep-s4): bandeau bas — rétractable desktop + FAB mobile + textes corrigés |
|
||
| `d092fd0` | feat(aep-s4): /a-propos — liens Liberapay corrigés (/donate) |
|
||
| `955a561` | feat(aep-s4): page /ajouter-carte (placeholder bientôt disponible) |
|
||
| `b406cc9` | feat(aep-s4): fusion outremer — suppression TerritoireTabs + vue 2/3+1/3 |
|
||
| `2b43e90` | feat(aep-s4): modal fiche sidebar desktop |
|
||
|
||
### Build & Deploy
|
||
- `npm run build` : ✓ 2.84 MB bundle
|
||
- Deploy VPS : ✓ `systemctl is-active = active`
|
||
- `curl -sI https://aep.trans-former.fr/` : HTTP/2 200
|
||
|
||
### Points d'attention pour S5
|
||
|
||
- Dark mode tuile Leaflet : switcher automatiquement vers `dark_all` CartoDB quand `.dark` est actif
|
||
- BandeauBas détection mobile avec `isMobile` ref côté client : SSR safe (montage) mais risque blink. Envisager `useWindowSize` ou classe CSS média
|
||
- `TerritoireTabs` et `TerritoireToggle` non utilisés → à archiver ou supprimer en S5
|
||
- Barre de recherche header : sur les pages `/fiche/[id]` et `/a-propos`, la recherche navigue vers `/?q=X` — comportement acceptable mais pas élégant
|
||
- Modal fiche : pas de CommentForm affiché côté SSR (correctement client-only via watch)
|
||
|
||
---
|
||
|
||
## 2026-04-14 — Session 3a : Chatbot API (Étape 5bis)
|
||
|
||
**Exécutant :** Sonnet (agent chatbot)
|
||
**Durée :** ~30 min
|
||
|
||
### Réalisé
|
||
|
||
**ChatbotSheet.vue** — Déjà complet (S2) : onboarding exact E-spec §Détails chatbot, bulles user/assistant, fiches recommandées avec lien, erreurs 429/503, animation slide-up mobile.
|
||
|
||
**API POST /api/chatbot** (nouveau) :
|
||
- Rate limit : 10 req/IP/jour via `rateLimitJson.ts` (JSON + SHA-256 RGPD, spec F §8)
|
||
- Circuit breaker : vérification budget 20€/mois via `circuitBreaker.ts` → HTTP 503 si dépassé
|
||
- Keyword scoring : extraction mots-clés question → score sur nom+description+tags → top 20 fiches
|
||
- Filtrage optionnel par `fonction` et `echelle` si fournis dans le body
|
||
- Appel Mistral Small (mistral-small-latest, temp 0.3, max 600 tokens, json_object)
|
||
- Parse JSON → `{ reponse_texte, fiches_recommandees: [{ id, nom, explication }] }`
|
||
- Log asynchrone `stats_usage` (non bloquant)
|
||
|
||
**Helpers nouveaux** :
|
||
- `server/utils/rateLimitJson.ts` — fichier JSON par IP hashée SHA-256 dans `/tmp/nav-ratelimit/`
|
||
- `server/utils/circuitBreaker.ts` — budget check NocoDB stats_usage + calcul coût Mistral Small/Nemo
|
||
- `nuxt.config.ts` — ajout `statsTableId` (défaut `mbbq7n47ixy19mc`)
|
||
|
||
### Décisions
|
||
|
||
| Décision | Détail |
|
||
|----------|--------|
|
||
| rateLimitJson vs Redis existant | JSON fichier créé séparément (spec F §8 + RGPD SHA-256). Redis existant conservé pour submit/comment. Les deux coexistent. |
|
||
| Prompt Mistral Small | Construit depuis E-spec-frontend.md §Détails chatbot — pas de prompt verbatim dans F-spec §3 (qui est Nemo enrichissement). **Checkpoint Jules requis avant deploy prod.** |
|
||
| circuitBreaker.ts | Worker absent → créé dans server/utils/ (source unique, réutilisable par le futur worker) |
|
||
| statsTableId | Défaut hardcodé `mbbq7n47ixy19mc` + env var `STATS_TABLE_ID` en override |
|
||
|
||
### Commits
|
||
|
||
- `718e9f6` — feat(chatbot): API /api/chatbot + rate limit JSON SHA-256 + circuit breaker
|
||
|
||
### Build
|
||
|
||
- `npm run build` : ✓ sans erreur, 2.78 MB bundle
|
||
|
||
### TODO avant deploy (S3b)
|
||
|
||
- [ ] `STATS_TABLE_ID=mbbq7n47ixy19mc` → `/opt/nav-carte/.env` (confirmer ou corriger l'ID)
|
||
- [ ] Vérifier que `/tmp/nav-ratelimit/` est accessible en écriture sur VPS (droits node)
|
||
- [ ] Cron journalier reset `/tmp/nav-ratelimit/` (0h UTC) → à ajouter en S3b
|
||
- [ ] Tester chatbot 2-3 requêtes réelles sur VPS après deploy
|
||
- [ ] **Checkpoint Jules** : valider prompt Mistral Small (construit, pas copié-collé de F-spec)
|
||
|
||
---
|
||
|
||
## 2026-04-14 — Session 2 Ajustements UX v3
|
||
|
||
**Pilote :** Sonnet
|
||
**Durée :** ~30 min
|
||
|
||
### Réalisé
|
||
|
||
**A) Search desktop déplacé top nav → sidebar (haut)**
|
||
- `TopSearchBar` retiré du header `app.vue` — top nav épuré (logo + Contribuer + Aléatoire)
|
||
- Barre recherche inline ajoutée en haut de `NavSidebar.vue` : toujours visible, pleine largeur sidebar, styles scoped `sidebar-search-*`
|
||
- Focus ring `var(--nav-primary)`, bouton clear intégré
|
||
- Mobile inchangé (search sticky bandeau entre filtres et liste)
|
||
- URL sync `?q=` conservé via `pages/index.vue`
|
||
- Commit : `3f88e86`
|
||
|
||
**B) Chatbot desktop cliquable + expand/collapse**
|
||
- `ChatbotPlaceholder.vue` refactorisé : état replié (56px) → étendu (45vh) au clic
|
||
- Header entier cliquable + chevron dédié avec rotation SVG 180° animée
|
||
- `transition: max-height 0.3s ease` — animation fluide
|
||
- Zone étendue : placeholder conversation S3, overflow-y auto
|
||
- Mobile `ChatbotSheet.vue` inchangé
|
||
- Commit : `88d0319`
|
||
|
||
### Build
|
||
- `npm run build` clean (0 erreurs, 0 warnings TS)
|
||
|
||
---
|
||
|
||
## 2026-04-14 — Session 1 : Setup + Fondations
|
||
|
||
**Pilote :** Opus
|
||
**Exécutants :** Sonnet-1 (VPS/NocoDB), Sonnet-2 (seed parsing)
|
||
**Durée estimée session :** 4-5h
|
||
|
||
### Décisions validées
|
||
|
||
| Décision | Détail | Source |
|
||
|----------|--------|--------|
|
||
| Palette | A (sobre institutionnel) + bleu nuit #1a2238 à 60% opacité **partout** (bandeau, pins, chips, surlignages) | Jules 2026-04-14 |
|
||
| Texte | Bleu plein pour titres/courant (lisibilité > esthétique) | Déduction palette |
|
||
| Liberapay | `liberapay.com/trans-former.fr` (pas `nav-archi`) | Jules |
|
||
| Seed | Bypass modération pour 94 fiches, 2-3 réservées pour test pipe IA (doc de la pipe → skill Mistral Nemo futur) | Jules |
|
||
| Liberapay transparence | Section dédiée dans `/a-propos` (étape 8), pas dans le bandeau | Jules |
|
||
| Circuit breaker dépassé | Bandeau "manque de fonds" + CTA Liberapay + pédagogie "1€ = N requêtes" + transparence origine Liberapay | Jules |
|
||
| From email modération | `contact@trans-former.fr` (existant Resend) | Jules |
|
||
| Session stratégie | 3 sessions dédiées (S1 fondations, S2 front, S3 IA+deploy) — préserve jauge Opus, use Sonnet pour exécution | Jules |
|
||
| Tokens | Mistral key stockée dans `/opt/nav-carte/.env`, jamais committée | Standard |
|
||
|
||
### Réalisé
|
||
|
||
**Sonnet-1 — Setup VPS + schéma NocoDB :**
|
||
- Token NocoDB `nav-v2-worker` créé : `R-Yhd_0KgfW0ZjFxIl5iNyLS1ca7VpP8dNbo4OOa`
|
||
- `/opt/nav-carte/.env` créé (chmod 600) avec `MISTRAL_API_KEY`, `NOCODB_TOKEN`, `NOCODB_BASE`, `NOCODB_TABLE_*`, `RESEND_FROM`, `NOCODB_URL`
|
||
- crawl4ai 0.8.6 installé via pip (`--ignore-installed` pour conflit lib `rich`)
|
||
- Table `organisations` (`m08t7g5v4wch6wb`) étendue : 19 champs ajoutés
|
||
- Contenus : url, description_user, description_enrichie, points_cles, localisation_ville, submitted_by_email, moderator_note, ai_raw_output, scrape_content
|
||
- Taxonomie : echelle (SingleSelect), territoire (SingleSelect), tags_fonction (MultiSelect)
|
||
- État : scrape_status, moderation_status, ai_processed (Checkbox)
|
||
- Géo : latitude, longitude
|
||
- Meta : submitted_at, moderated_at
|
||
- Table `stats_usage` créée : `mbbq7n47ixy19mc` (model, endpoint, tokens_in/out, cout_eur, timestamp, orga_id)
|
||
- Table `scrape_queue` skippée : F§2 n'en définit pas, la pipe utilise `scrape_status` sur `organisations`
|
||
- Tests insert/update OK sur les deux tables avec nouveau token
|
||
|
||
**Sonnet-2 — Parsing biblio + géocodage seed :**
|
||
- 93 entités parsées (A-biblio annonçait 94, erreur de comptage source)
|
||
- 72 géocodées via Nominatim, 21 sans ville (lat/lon null, pas de fallback centre France pour éviter bruit carte)
|
||
- Correction manuelle Saint-Ouen (93) vs Saint-Ouen-l'Aumône (Picardie)
|
||
- 3 fiches réservées test pipe IA : CNOA, Archireport, Collectif Fil
|
||
|
||
**Sonnet-3 — Re-tag + CROA + import NocoDB :**
|
||
- Re-tag : 32 Prospection → Développement, 17 RH → Gestion d'agence
|
||
- Formation détecté et ajouté à 9 fiches (Réseau des MA, Cité de l'Archi, MAJ, CFAA, REFC'A, DU Dauphine, UNAID, FFP, Cité Archi Formation continue)
|
||
- 13 CROA régionaux : 6 créés + 7 existants enrichis (tous avec "Gestion d'agence")
|
||
- CNOA National isolé et réservé aux 3 fiches test pipe IA
|
||
- **96 fiches importées** dans NocoDB (99 v2 − 3 test pipe)
|
||
- Apostrophe typographique U+2019 appliquée pour compat NocoDB
|
||
- Anomalie : 8 anciens records V1 présents (IDs 1-8, `moderation_status null`) → **à purger avant mise en prod**
|
||
|
||
### Anomalies & points à surveiller
|
||
|
||
1. **Taxonomie `moderation_status` — divergence F/G**
|
||
- F§2 définit : `approved` / `rejected`
|
||
- G mentionne : `published` / `approved`
|
||
- Front V1 existant utilise : `approved`
|
||
- **Décision Session 2 :** rester sur `approved`/`rejected` partout, retirer `published` du prompt G si la doc est mise à jour. Le statut `pending` (avant modération) existe de fait comme absence de validation.
|
||
|
||
2. **Endpoint NocoDB 0.301.5**
|
||
- L'endpoint correct pour ajouter des colonnes est `/api/v1/db/meta/tables/{id}/columns`
|
||
- L'endpoint `/api/v2/meta/tables/{id}/fields` n'existe PAS dans cette version
|
||
- Le MCP `nocodb` n'était pas configuré avec la bonne URL de base → contournement direct par SSH + curl
|
||
- **TODO :** reconfigurer le MCP nocodb pour qu'il pointe correctement (utile pour sessions futures)
|
||
|
||
3. **Disque VPS : 72,4%** (était 69,8% avant install crawl4ai)
|
||
- crawl4ai a pris ~2,6 GB
|
||
- Hypothèse : Playwright + Chromium embarqué (crawl4ai utilise Playwright pour les pages JS)
|
||
- **Marge :** OK pour Sessions 2-3. À surveiller quand on commencera à scraper et stocker du contenu dans `scrape_content`
|
||
- **Alternative si problème disque :** remplacer crawl4ai par `httpx + BeautifulSoup` pour le scraping statique (bien plus léger, mais perd le rendu JS)
|
||
- **TODO :** vérifier via `du -sh` le poids exact de crawl4ai et ses deps Playwright, décider si on garde ou on allège
|
||
|
||
4. **Conflit lib Python `rich`**
|
||
- Résolu avec `pip install --ignore-installed`
|
||
- Risque : peut casser un autre outil Python VPS qui utilise `rich` (lightrag, formations, etc.)
|
||
- **TODO :** vérifier que lightrag et autres services Python tournent toujours
|
||
|
||
### Fichiers produits
|
||
|
||
- `/opt/nav-carte/.env` (VPS, chmod 600)
|
||
- Tables NocoDB : `organisations` étendue + `stats_usage` nouvelle
|
||
- `0 INBOX/NAV-V2-recherches/palette-nav-v2.md` — palette finale CSS
|
||
- `0 INBOX/NAV-V2-recherches/palettes-preview.html` — mockup validé
|
||
- `0 INBOX/NAV-V2-recherches/seed-94-fiches.json` *(à produire par Sonnet-2)*
|
||
- `0 INBOX/NAV-V2-recherches/seed-94-rapport.md` *(à produire par Sonnet-2)*
|
||
|
||
### Prochaines étapes
|
||
|
||
- Sonnet-2 finit le parsing + géocoding
|
||
- Import seed NocoDB (bypass modération, 2-3 fiches réservées test pipe IA)
|
||
- Rédaction des prompts Sessions 2 et 3 (livrable fin Session 1)
|
||
|
||
---
|
||
|
||
## Taxonomie finale NAV V2 (validée 2026-04-14)
|
||
|
||
**Échelle** (3 niveaux) : `National` / `Régional` (inclut Départemental) / `Local`
|
||
|
||
**Territoire** : `Métropole` + 5 DOM-TOM (Guadeloupe, Martinique, Guyane, La Réunion, Mayotte)
|
||
|
||
**Fonctions** (10 tags, multi 1-5, ordre = priorité) :
|
||
1. Juridique
|
||
2. Technique
|
||
3. Économique *(strictement finance : aides, fiscalité, rentabilité, tarification)*
|
||
4. Administratif *(démarches externes : permis, OA, déclarations)*
|
||
5. Chantier
|
||
6. Comptabilité
|
||
7. **Développement** *(ex-Prospection — AO, concours, réseaux pro, acquisition clients)*
|
||
8. **Formation** *(NOUVEAU — école, MOOC, organisme, formation continue)*
|
||
9. **Gestion d'agence** *(ex-RH — élargi : RH + management + pilotage + orga interne)*
|
||
10. Santé mentale
|
||
|
||
**Renommages appliqués :**
|
||
- Prospection → Développement
|
||
- RH → Gestion d'agence (élargi)
|
||
- Ajout : Formation
|
||
|
||
**Cas particuliers :**
|
||
- CNOA → éclaté en 1 fiche National (siège Paris) + 13 fiches CROA Régional (préfectures de région)
|
||
- Option 3 (antennes pins secondaires via champ multi-coords) → backlog V3
|
||
|
||
## Points ouverts (à trancher en amont des prochaines sessions)
|
||
|
||
- [ ] Session 2 : choix rate limit chatbot (fichier JSON vs Redis)
|
||
- [ ] Session 3 : seuil email modération (N fiches en attente → envoi)
|
||
- [ ] Facteur CO2eq : confirmer `0.052 kg CO2/kWh` (RTE FR) ou utiliser valeur plus récente (ADEME 2024 ≈ 0.055)
|
||
- [ ] Nominatim policy : user-agent `NAV-V2/1.0 (contact@trans-former.fr)` → OK ?
|
||
- [ ] UX Session 2 : options échelle vides (Local) + onglet Outre-mer vide → afficher avec compteur "0" pour inviter à contribuer (cohérent esprit collaboratif)
|
||
|
||
## Enrichissement DOM-TOM (post V2)
|
||
|
||
Session d'enrichissement à lancer après déploiement V2 :
|
||
- Recherche ciblée par territoire (Guadeloupe, Martinique, Guyane, La Réunion, Mayotte)
|
||
- Sources : CROA locaux, maisons de l'archi outre-mer, réseaux pro insulaires
|
||
- Agent Sonnet-research 1-2h → complément seed
|
||
|
||
## Piste externe : team.archi
|
||
|
||
Contacter le fondateur de team.archi (forum entraide archi) pour :
|
||
- Lui présenter NAV V2 (esprit, périmètre)
|
||
- Demander si team.archi documente d'autres réseaux d'entraide qu'on pourrait intégrer
|
||
- Proposer un échange / listing mutuel
|
||
|
||
Brouillon email à préparer (requiert nom + email fondateur de Jules).
|
||
|
||
---
|
||
|
||
---
|
||
|
||
## 2026-04-14 — Session 2 : Front — Carte + Sidebar Filtres
|
||
|
||
**Exécutant :** Sonnet (agent)
|
||
**Durée :** ~1h
|
||
|
||
### Décisions prises
|
||
|
||
| Décision | Détail |
|
||
|----------|--------|
|
||
| Leaflet sans plugin Nuxt | `@nuxtjs/leaflet` a une compat instable avec Nuxt 3.15 — import dynamique direct dans `onMounted()` + `<ClientOnly>` wrapper. Plus fiable, zéro config SSR à gérer. |
|
||
| Cluster seuil 15 (spec dit 15+) | `disableClusteringAtZoom: 14` — au-delà de zoom 14 les pins se défiltrent, cohérent avec la granularité ville/rue |
|
||
| `@headlessui/vue` installé mais non utilisé pour le drawer | Le drawer implémenté en Vue natif (Teleport + transition) est plus léger et sans dépendance. `@headlessui/vue` reste disponible pour l'étape 3 (modales commentaires). |
|
||
| Seed JSON : Id fictif 1000+ | Pour éviter collision avec les IDs NocoDB réels (1-96+). Les URLs `/fiche/1000+` ne seront pas résolues en prod — comportement attendu en dev. |
|
||
| `moderation_status=approved` | Confirmé — retirer `published` du filtre comme résolu en Session 1 |
|
||
| Compteurs calculés sur orgs non filtrées | Les (0) montrent les options sans aucune fiche dans la base totale, pas dans la sélection courante. Plus honnête vis-à-vis de l'invitation à contribuer. |
|
||
|
||
### Composants créés
|
||
|
||
- `NavMap.vue` — Leaflet + OSM + clusters + pins personnalisés
|
||
- `EchelleFilter.vue` — chips exclusifs National/Régional/Local
|
||
- `FonctionFilter.vue` — multi-sélect 10 fonctions (max 5)
|
||
- `TerritoireToggle.vue` — Métropole + 5 DOM-TOM sous-onglets
|
||
- `NavSidebar.vue` — wrapper desktop
|
||
- `FilterDrawer.vue` — drawer mobile (Teleport + transition Vue native)
|
||
|
||
### Fichiers modifiés
|
||
|
||
- `assets/css/main.css` — tokens CSS palette NAV V2 + surcharges Leaflet/clusters
|
||
- `tailwind.config.js` — couleurs `nav.*` ajoutées
|
||
- `nuxt.config.ts` — optimizeDeps Leaflet pour Vite
|
||
- `server/routes/api/organisations.get.ts` — V2 champs + fallback seed JSON
|
||
- `pages/index.vue` — refonte complète layout + filtres + URL sync
|
||
|
||
### Commits Session 2
|
||
|
||
- `64f5b0a` — feat(deps): installer leaflet, markercluster, headless UI
|
||
- `dc849ef` — feat(style): palette NAV V2 — tokens CSS + tailwind
|
||
- `799d8fc` — feat(api): GET /api/organisations V2 avec fallback seed JSON
|
||
- `450a45c` — feat(components): carte Leaflet + sidebar filtres (étape 2)
|
||
- `3f486df` — feat(page): index.vue V2 — carte + sidebar + URL sync
|
||
|
||
### Validation build
|
||
|
||
- `npm run build` : ✓ sans erreur, 2.09 MB bundle
|
||
|
||
### Notes pour Sonnet 2 (étape 3 — fiche détail)
|
||
|
||
- `@headlessui/vue` installé et disponible si besoin pour la modale commentaires
|
||
- Interface `Org` définie dans `pages/index.vue` — à extraire dans `types/org.ts` si partagée (étape 3 en aura besoin)
|
||
- Route API existante : `GET /api/organisations/[id]` dans `server/routes/api/organisations/[id].get.ts` — fonctionne, utiliser tel quel
|
||
- Champ `description_enrichie` prévu mais absent du seed — prévoir fallback sur `description`
|
||
- Champ `points_cles` : chaîne ou JSON array ? Vérifier dans NocoDB avant d'afficher
|
||
|
||
### Notes pour Sonnet 3 (étape 6 — formulaire contribuer)
|
||
|
||
- Route `POST /api/organisations` existe déjà en V1 (`server/routes/api/organisations.post.ts`) — à adapter pour V2 (nouveaux champs + moderation_status: pending)
|
||
- Zod non encore installé — à ajouter dans package.json
|
||
- Rate limit : trancher JSON simple (recommandé pour MVP) vs Redis
|
||
|
||
### Points ouverts
|
||
|
||
- [ ] 8 anciens records V1 (IDs 1-8, moderation_status null) à purger en prod
|
||
- [ ] Vérifier type de `points_cles` dans NocoDB avant rendu fiche (string ou JSON)
|
||
- [ ] Tester carte sur VPS avec données NocoDB réelles (96 fiches)
|
||
|
||
---
|
||
|
||
---
|
||
|
||
## 2026-04-14 — Session 2 (suite) : Spec définitive — Top nav + chatbot + filtres
|
||
|
||
**Exécutant :** Sonnet 2/3
|
||
**Durée :** ~1h
|
||
|
||
### Décisions prises
|
||
|
||
| Décision | Détail |
|
||
|----------|--------|
|
||
| `app.vue` = layout global | Refondu avec top nav (logo + TopSearchBar + Contribuer + Aléatoire). Supprime le header V1 (vert sage). NuxtPage prend le flex-1 restant. |
|
||
| Recherche top nav → URL `?q=` | TopSearchBar émet vers app.vue → router.replace ?q=. pages/index.vue watch route.query.q. Pas de prop passée via NuxtPage (non supporté simplement). |
|
||
| Sidebar sans recherche | Input recherche retiré de NavSidebar — redondant avec top nav. |
|
||
| Sidebar sans chatbot | Zone chatbot déplacée dans la zone carte (bas). |
|
||
| ChatbotPlaceholder sous carte | Input désactivé 52px, fond `--nav-bg`. Présent sous Métropole ET Outre-mer. |
|
||
| Échelle multi-select (string[]) | EchelleFilter + NavSidebar + FilterDrawer + pages/index.vue tous adaptés en `string[]`. Plus de `string \| null`. |
|
||
| Fiche aléatoire | ?random=1 dans l'URL → pages/index.vue redirige vers /fiche/[id] aléatoire. |
|
||
| shadcn | Écarté — Vue+Tailwind+CSS scoped suffit, zéro complexité d'intégration. |
|
||
| Bonus "ouvrir chatbot" | Skippé — pas de valeur avant S3. |
|
||
|
||
### Composants créés
|
||
|
||
- `TopSearchBar.vue` — barre recherche animée (compact 44px → 280px au focus), CSS scoped
|
||
- `ChatbotPlaceholder.vue` — zone bas carte, input désactivé, réserve espace S3
|
||
|
||
### Composants modifiés
|
||
|
||
- `app.vue` — refonte complète layout global (V1 sage vert → V2 bleu nuit)
|
||
- `pages/index.vue` — supprime header dupliqué, intègre ChatbotPlaceholder, échelle string[]
|
||
- `EchelleFilter.vue` — single-select → multi-select (string[])
|
||
- `NavSidebar.vue` — supprime input recherche + zone chatbot, prop echelle string[]
|
||
- `FilterDrawer.vue` — prop echelle string[], activeCount adapté
|
||
|
||
### Commits Session 2 (suite)
|
||
|
||
- `33fbd3b` — feat(nav): top nav global avec barre de recherche animée
|
||
- `1d5d9ab` — feat(ux): chatbot placeholder sous la carte + sidebar sans chatbot
|
||
- `905f338` — feat(filtres): échelle multi-select + sidebar sans recherche ni chatbot
|
||
|
||
### Validation build
|
||
|
||
- `npm run build` : ✓ sans erreur, 2.12 MB bundle
|
||
|
||
### Points en attente / non implémentés
|
||
|
||
- [ ] TerritoireTabs dans le drawer mobile — non dupliqué (à faire si besoin)
|
||
- [ ] Fiche aléatoire nécessite que les orgs soient chargées — si orgs vides au moment du ?random=1, pas de redirect. Acceptable pour MVP.
|
||
|
||
---
|
||
|
||
---
|
||
|
||
## 2026-04-14 — Session 2 (Sonnet 2) : Fiche détail + Commentaires
|
||
|
||
**Exécutant :** Sonnet 2 (agent NAV V2)
|
||
**Durée :** ~1h
|
||
|
||
### Décisions prises
|
||
|
||
| Décision | Détail |
|
||
|----------|--------|
|
||
| types/org.ts | Interface Org canonique extraite — suppression des 2 déclarations dupliquées dans index.vue et fiche/[id].vue |
|
||
| Route API dédiée | GET /api/fiche/[id] dans server/api/fiche/ (distinct de server/routes/api/organisations/[id]) — meilleure séparation des responsabilités |
|
||
| Table commentaires | commentTableId = COMMENT_TABLE_ID ?? AVIS_TABLE_ID (fallback table V1 si pas de table dédiée configurée) |
|
||
| Mistral Nemo timeout | 2s via AbortController — fallback safe_check: 'pending' si timeout ou clé absente |
|
||
| Retour filtres | sessionStorage nav_back_filters (pas de query param _back) — plus simple, pas de fuite dans URL partagée |
|
||
| mini-carte | Leaflet non-interactif dans FicheDetail — zoom:10, dragging:false, toutes interactions désactivées |
|
||
| points_cles | Tente JSON.parse d'abord, fallback découpage par lignes si format texte |
|
||
| Rate limit Redis | Posé par Sonnet 3 — server/utils/rateLimit.ts avec fallback mémoire si Redis KO |
|
||
|
||
### Composants créés
|
||
|
||
- `components/FicheDetail.vue` — affichage complet avec mini-carte Leaflet
|
||
- `components/CommentSection.vue` — liste commentaires publiés + refresh prop
|
||
- `components/CommentForm.vue` — formulaire soumission + gestion 429
|
||
|
||
### Routes API créées
|
||
|
||
- `server/api/fiche/[id].get.ts` — proxy NocoDB champs V2 complets
|
||
- `server/api/comment/index.post.ts` — filtre éthique Mistral Nemo (timeout 2s, fallback pending)
|
||
- `server/api/comment/[orgId].get.ts` — commentaires publiés triés chronologiquement
|
||
|
||
### Fichiers modifiés
|
||
|
||
- `pages/fiche/[id].vue` — refonte complète SSR (FicheDetail + CommentSection + CommentForm + SEO)
|
||
- `pages/index.vue` — ajout storeFiltersForBack() pour sessionStorage + import type Org
|
||
- `types/org.ts` — CRÉÉ : interface canonique V2
|
||
- `nuxt.config.ts` — ajout mistralApiKey + commentTableId
|
||
|
||
### Commits Session 2 (Sonnet 2)
|
||
|
||
- `a653336` — refactor(types): extraire interface Org canonique dans types/org.ts
|
||
- `89bd22a` — feat(api): GET /api/fiche/[id] + POST/GET /api/comment avec filtre Mistral Nemo
|
||
- `06c44cd` — feat(components): FicheDetail + CommentSection + CommentForm
|
||
- `420f534` — feat(page): /fiche/[id] SSR complète — FicheDetail + comments + SEO + retour filtres
|
||
|
||
### Validation build
|
||
|
||
- `npm run build` : ✓ sans erreur, 2.74 MB bundle
|
||
|
||
### Coordination Sonnet 3 (rate limit Redis)
|
||
|
||
**Posé par Sonnet 3 :** `server/utils/rateLimit.ts` (ioredis + fallback mémoire) importé dans POST /api/comment.
|
||
Sonnet 3 a modifié `server/api/comment/index.post.ts` pour brancher `checkRateLimit(ip, 'comment', 5)`.
|
||
Configuration : `REDIS_URL` dans `.env`, fallback mémoire si Redis KO (dev sans Docker).
|
||
|
||
### Points ouverts pour Session 3
|
||
|
||
- [ ] `public/og-default.png` à créer (logo par défaut pour og:image) — actuellement référencé, pas encore créé
|
||
- [ ] `COMMENT_TABLE_ID` à ajouter dans `.env` VPS si table dédiée à part de `AVIS_TABLE_ID`
|
||
- [ ] `MISTRAL_API_KEY` à vérifier dans `/opt/nav-carte/.env` — la clé est dans le .env VPS (Session 1), juste s'assurer du nom de variable exact
|
||
- [ ] Vérifier type de `points_cles` dans NocoDB (JSON array stringifié ou texte brut ?)
|
||
- [ ] Tester sur VPS avec données NocoDB réelles (96 fiches seed)
|
||
|
||
---
|
||
|
||
## 2026-04-14 — Session 2 (parallèle) : Étape 6 — Formulaire `/contribuer` + Redis rate limit
|
||
|
||
**Exécutant :** Sonnet 3 (agent)
|
||
|
||
### Redis VPS
|
||
|
||
- `redis-server` absent au départ (pas mentionné dans VPS-check.md)
|
||
- Installé : `apt-get install redis-server` → `systemctl enable + start` ✓
|
||
- Bind : `127.0.0.1 -::1` uniquement (localhost, sécurisé)
|
||
- Test : `redis-cli ping` → PONG ✓
|
||
- **En prod :** ajouter `REDIS_URL=redis://127.0.0.1:6379` dans `/opt/nav-carte/.env`
|
||
- **En dev local :** fallback compteur en mémoire (ioredis lazyConnect — pas de crash si Redis absent)
|
||
|
||
### Fichiers produits
|
||
|
||
| Fichier | Description |
|
||
|---------|-------------|
|
||
| `server/utils/rateLimit.ts` | Helper Redis générique — `checkRateLimit(ip, action, maxPerDay)` avec fallback mémoire |
|
||
| `server/api/submit/index.post.ts` | POST /api/submit — Zod + Nominatim + NocoDB pending + rate limit 3/IP/j |
|
||
| `server/api/comment/index.post.ts` | Rate limit Redis 5/IP/j ajouté en tête (base Sonnet 2) |
|
||
| `pages/contribuer.vue` | Page dédiée — 8 champs, validation Zod client+serveur, 422/429, succès + trackingUrl |
|
||
|
||
### Décisions
|
||
|
||
| Décision | Détail |
|
||
|----------|--------|
|
||
| Page vs modal | Page dédiée `/contribuer` — plus simple pour MVP. Route duale desktop (redirect ?contribute=1 + modale) en backlog V2.1 |
|
||
| description_user | K-prompt (50-500) > E-spec (20-200) — tranché : 50-500 chars |
|
||
| Champ ville | Optionnel (K-prompt) — moins de friction, fallback fiche sans coords prévu |
|
||
| NOCODB_BASE_ID | Valeur fallback `p_nav_v2` dans le code. À confirmer : `ssh vps-hetzner "grep -i base /opt/nav-carte/.env"` |
|
||
|
||
### Rate limits posés (Redis)
|
||
|
||
- `/api/submit` : **3 soumissions / IP / jour**
|
||
- `/api/comment` : **5 commentaires / IP / jour**
|
||
|
||
### Commits
|
||
|
||
- `d9b6a31` — feat(deps): ajouter zod + ioredis + REDIS_URL runtime config
|
||
- `e81625f` — feat(server): helper rate limit Redis avec fallback mémoire
|
||
- `5c24c06` — feat(api): POST /api/submit — validation Zod + geocoding + NocoDB pending
|
||
- `fc0c52c` — feat(page): /contribuer — formulaire V2 Zod client + UX mobile-first
|
||
- (rate limit /api/comment dans commit Sonnet 2 `420f534`)
|
||
|
||
### Build
|
||
|
||
- `npm run build` : ✓ sans erreur, 2.74 MB bundle
|
||
|
||
### TODO avant prod
|
||
|
||
- [ ] `REDIS_URL=redis://127.0.0.1:6379` → `/opt/nav-carte/.env` (VPS)
|
||
- [ ] Confirmer `NOCODB_BASE_ID` : `ssh vps-hetzner "grep -i base /opt/nav-carte/.env"`
|
||
- [ ] Tester submit valide → fiche `pending` dans NocoDB
|
||
- [ ] Tester 4ème submit → vérifier HTTP 429
|
||
|
||
---
|
||
|
||
## 2026-04-14 — Session 2 (Étape 2) : Mobile UX
|
||
|
||
**Exécutant :** Sonnet 1.8 (agent NAV V2 Mobile)
|
||
**Durée :** ~1h
|
||
|
||
### Décisions prises
|
||
|
||
| Décision | Détail |
|
||
|----------|--------|
|
||
| FilterDrawer supprimé | L'ancien drawer filtres (bouton flottant gauche, slide depuis gauche) est supprimé. Les filtres sont désormais inline dans le flow mobile. |
|
||
| ChatbotSheet nouveau composant | `ChatbotSheet.vue` — bottom sheet plein écran, Teleport + transition slide-up, `92dvh`, input désactivé (S3), fermeture backdrop + bouton Retour. |
|
||
| Carte mobile 45dvh | `height: 45dvh` sur mobile, `min-height: 180px`. `dvh` pour éviter le bug clavier mobile. |
|
||
| Tagging compact inline | Bandeau entre carte et liste : échelle 3 checkboxes `16px` avec labels courts (Nat/Rég/Loc) + fonctions scroll horizontal `overflow-x: auto`. |
|
||
| Tap card → centre carte | `onSelectOrgMobile` → `selectedId` change → `NavMap.vue` réagit via `watch selectedId → mapInstance.panTo`. |
|
||
| Bouton chatbot flottant | `56×56px`, `bottom: 1.5rem; right: 1rem`, `opacity: 0.88`, `z-[1000]`, `lg:hidden`. |
|
||
| Multi-Leaflet Outre-mer | Grille 5 mini-cartes existante conservée — build clean, à tester en conditions réelles. |
|
||
| Poignée draggable | Skippé — estimé > 30 min, noté pour Session 3. |
|
||
| Interface Org | Sonnet 2 a extrait `types/org.ts`, linter auto-appliqué l'import dans `pages/index.vue`. |
|
||
|
||
### Composants créés
|
||
|
||
- `ChatbotSheet.vue` — bottom sheet chatbot plein écran (92dvh, slide-up, backdrop, poignée visuelle, input désactivé)
|
||
|
||
### Composants supprimés
|
||
|
||
- `FilterDrawer.vue` — drawer filtres mobile (remplacé par tagging inline + ChatbotSheet)
|
||
|
||
### Fichiers modifiés
|
||
|
||
- `pages/index.vue` — refonte complète zone mobile + bouton chatbot flottant + ChatbotSheet
|
||
|
||
### Commits Session 2 (Mobile)
|
||
|
||
- `d39e7be` — feat(mobile): ChatbotSheet — bottom sheet plein écran avec animation slide-up
|
||
- `0843301` — feat(mobile): supprimer FilterDrawer — remplacé par tagging inline + ChatbotSheet
|
||
- `pages/index.vue` — inclus dans refacto Sonnet 2 (`a653336` → import `types/org.ts`)
|
||
|
||
### Validation build
|
||
|
||
- `npm run build` : ✓ sans erreur, 2.12 MB bundle
|
||
|
||
### Backlog mobile S3
|
||
|
||
- [ ] Poignée draggable entre carte et liste (drag resize)
|
||
- [ ] Multi-Leaflet Outre-mer mobile à tester en conditions réelles → fallback dropdown si trop lourd
|
||
- [ ] Animation léger zoom pin au tap card (spec F)
|
||
- [ ] Chatbot IA branché (Session 3)
|
||
|
||
---
|
||
|
||
---
|
||
|
||
## 2026-04-14 — Session S2 Ajustements finaux v2 (correctif branding + polish)
|
||
|
||
**Exécutant :** Sonnet 1.9b (agent AEP V2)
|
||
|
||
### Contexte
|
||
|
||
L'agent précédent avait commis une erreur de spec sur le renommage : "NAV" avait été remplacé par "Écosystème Architecture" dans les textes visibles. La bonne règle est : "NAV" (texte visible) → "AEP" (Architecture d'Écologie Politique).
|
||
|
||
### Corrections branding (commit fix)
|
||
|
||
| Fichier | Avant | Après |
|
||
|---------|-------|-------|
|
||
| `app.vue` logo icône | `N` | `A` |
|
||
| `app.vue` logo texte | `Écosystème Archi.` | `AEP` |
|
||
| `components/ChatbotSheet.vue` | `aria-label="Assistant Écosystème Architecture"` | `aria-label="Assistant AEP"` |
|
||
| `pages/index.vue` SEO title | `Écosystème Architecture — Cartographie AEP` | `AEP — Cartographie de l'écologie politique architecturale` |
|
||
| `pages/contribuer.vue` SEO title | `Proposer une ressource — Écosystème Architecture` | `Proposer une ressource — AEP` |
|
||
| `pages/ajouter.vue` SEO title | `Proposer une fiche — Écosystème Architecture` | `Proposer une fiche — AEP` |
|
||
| `pages/fiche/[id].vue` og:description fallback | `Fiche organisation — NAV Architectes` | `Fiche organisation — AEP` |
|
||
| `pages/fiche/[id].vue` SEO title | `{nom} — NAV` | `{nom} — AEP` |
|
||
| `pages/fiche/[id].vue` og:title | `{nom} — NAV Architectes` | `{nom} — AEP` |
|
||
|
||
### Ajustements complémentaires
|
||
|
||
- Bouton flottant mobile : icône seule → pill avec texte "Chatbot" visible (48px h, gap-2, font-weight 600)
|
||
|
||
### État des 4 ajustements spec
|
||
|
||
| # | Ajustement | État |
|
||
|---|-----------|------|
|
||
| 1 | Échelle inline 1 ligne | ✓ Fait par agent précédent (`EchelleFilter.vue` `flex-wrap gap-x-4`) |
|
||
| 2 | NAV → AEP (textes visibles) | ✓ Corrigé dans ce commit |
|
||
| 3 | Mobile search au-dessus liste | ✓ Fait par agent précédent (lignes 167-203 `pages/index.vue`) |
|
||
| 4 | Bouton mobile "Chatbot" | ✓ Fait dans ce commit (pill avec label visible) |
|
||
|
||
### Build
|
||
|
||
- `npm run build` : ✓ sans erreur, 2.74 MB bundle
|
||
|
||
---
|
||
|
||
## 2026-04-14 — Session 3 : Chatbot IA (Étape 5bis)
|
||
|
||
**Pilote :** Opus
|
||
**Exécutant :** agent Sonnet S3
|
||
**Durée :** ~1h
|
||
|
||
### Réalisé
|
||
|
||
**ChatbotSheet.vue (mobile) — refactorisé :**
|
||
- Conversation IA complète : messages user/assistant, scroll auto, loading dots
|
||
- Message onboarding exact (texte E §6) affiché avant la première question
|
||
- Bulles fiches recommandées : card compacte avec lien `/fiche/[id]` + explication
|
||
- Erreurs typées : 429 (rate limit), 503 (budget épuisé), fallback générique
|
||
- Emit `highlightOrgs` pour highlight carte
|
||
- Animations slide-up + `prefers-reduced-motion` respecté
|
||
|
||
**ChatbotPlaceholder.vue (desktop) — refactorisé :**
|
||
- Même conversation IA que le mobile, format panneau latéral expand/collapse
|
||
- Même onboarding, mêmes bulles fiches, même gestion d'erreurs
|
||
- Input en bas du panneau étendu, Enter pour envoyer
|
||
|
||
**server/api/chatbot/index.post.ts — créé :**
|
||
- Rate limit : `checkRateLimit(ip, 'chatbot', 10)` via helper Redis S2 existant
|
||
- Circuit breaker : lecture cumul `stats_usage` mois courant, 503 si ≥ 20€
|
||
- Fetch top-20 fiches NocoDB (`moderation_status=approved`) avec scoring keyword
|
||
- Contexte JSON compact injecté dans le prompt système Mistral Small
|
||
- Appel `mistral-small-latest` (temperature: 0.3, max_tokens: 600, json_object)
|
||
- Parse JSON → `{ reponse_texte, fiches_recommandees }`
|
||
- Log `stats_usage` (fire and forget)
|
||
|
||
### Décisions
|
||
|
||
| Décision | Détail |
|
||
|----------|--------|
|
||
| Rate limit helper | Réutilisation du helper Redis S2 (`checkRateLimit`) — plus robuste que JSON fichier /tmp. Comportement identique (10 req/j). |
|
||
| `STATS_TABLE_ID` env | Valeur par défaut `mbbq7n47ixy19mc` (créé en S1) — override via env si besoin |
|
||
| Scoring fiches | Keyword match sur nom + description + tags — suffisant pour MVP, pas de vector search |
|
||
| `prefers-reduced-motion` | Animations supprimées si l'utilisateur a activé ce préfé navigateur |
|
||
|
||
### Build
|
||
|
||
- `npm run build` : ✓ sans erreur, 2.78 MB bundle
|
||
|
||
### Commits Session 3 (étape 5bis)
|
||
|
||
- feat(chatbot): ChatbotSheet mobile + ChatbotPlaceholder desktop — conversation IA branchée
|
||
- feat(api): POST /api/chatbot — Mistral Small + rate limit + circuit breaker
|
||
|
||
### TODO avant prod
|
||
|
||
- [ ] Vérifier `STATS_TABLE_ID` dans `/opt/nav-carte/.env` (valeur par défaut : `mbbq7n47ixy19mc`)
|
||
- [ ] Tester 2-3 requêtes réelles sur VPS avec fiches NocoDB
|
||
- [ ] Vérifier index.vue passe bien `highlightOrgs` au NavMap depuis ChatbotSheet et ChatbotPlaceholder
|
||
- [ ] Tester mobile iOS Safari + Android Chrome
|
||
|
||
---
|
||
|
||
---
|
||
|
||
## 2026-04-14 — Session 3 (Étapes 4-5) : Worker IA + Test pipeline
|
||
|
||
**Exécutant :** Sonnet (agent)
|
||
**Durée :** ~2h
|
||
|
||
### Réalisé
|
||
|
||
**Étape 4 — Worker enrichissement IA :**
|
||
- `worker/enrich.js` créé (Node.js ESM, systemd timer 5 min)
|
||
- Pipeline complet : fetch pending → scrape crawl4ai → Mistral Nemo → update NocoDB → log stats_usage
|
||
- Circuit breaker budget 20€ (filtre JS, car NocoDB v0.301.5 rejette les filtres datetime)
|
||
- Email Jules via Resend si seuil 5 fiches pending en modération
|
||
- Lock anti-overlap via `/tmp/nav-worker.lock`
|
||
- Retry 2x sur erreur Mistral + flag `ai_error` si 3 échecs
|
||
|
||
**Infrastructure :**
|
||
- `worker/package.json` + `node_modules/dotenv` installés
|
||
- Variables RESEND_API_KEY, EMAIL_JULES, BUDGET_MAX_EUR, WORKER_LIMIT ajoutées au `.env`
|
||
- `nav-worker.service` + `nav-worker.timer` systemd créés et activés
|
||
|
||
**Étape 5 — Test pipeline sur 3 fiches :**
|
||
- 3 fiches injectées en NocoDB (CNOA Id=106, Archireport Id=107, Collectif Fil Id=108)
|
||
- Checkpoint 1 fiche (CNOA) : €0.000029, extrapolé 96 fiches = €0.0028 → GO
|
||
- 3 fiches enrichies avec succès
|
||
|
||
### Métriques pipeline
|
||
|
||
| Fiche | tokens_in | tokens_out | cout_eur | Temps | Confiance |
|
||
|-------|-----------|------------|----------|-------|-----------|
|
||
| CNOA | 1 248 | 167 | €0.000029 | 3.0s | haute |
|
||
| Archireport | 4 376 | 261 | €0.000091 | 3.9s | haute |
|
||
| Collectif Fil | 2 628 | 221 | €0.000057 | 4.3s | haute |
|
||
| **Total** | **8 252** | **649** | **€0.000177** | **11.2s** | — |
|
||
|
||
Extrapolation 96 fiches : **€0.0057** (budget 20€ = 3 500× la consommation réelle)
|
||
|
||
### Problèmes résolus
|
||
|
||
| Problème | Solution |
|
||
|---------|---------|
|
||
| Playwright absent → crawl4ai crash | `AsyncHTTPCrawlerStrategy` (mode statique) |
|
||
| NocoDB rejette filtres datetime | Filtre JS sur tous les records stats_usage |
|
||
| Apostrophe U+0027 dans tags refusée | U+2019 (typographique) partout dans VALID_FONCTIONS |
|
||
| import dynamique ESM incompatible | spawnSync importé statiquement |
|
||
|
||
### Décisions prises en autonomie
|
||
|
||
| Décision | Raison |
|
||
|---------|--------|
|
||
| `AsyncHTTPCrawlerStrategy` au lieu d'`AsyncWebCrawler` standard | Playwright non installé sur VPS, mode statique suffisant pour sites archi |
|
||
| Filtre budget JS (pas NocoDB) | API NocoDB 0.301.5 ne supporte pas les filtres de date en mode gte |
|
||
| `spawnSync` pour appel Python | Évite les complications d'import dynamique en ESM |
|
||
|
||
### Fichiers produits
|
||
|
||
- `/opt/nav-carte/worker/enrich.js` — Worker IA principal
|
||
- `/opt/nav-carte/worker/package.json` — Dépendances (dotenv)
|
||
- `/etc/systemd/system/nav-worker.service` — Service oneshot
|
||
- `/etc/systemd/system/nav-worker.timer` — Timer 5 min
|
||
- `nav-carte/PIPE-IA-DOC.md` — Documentation complète pipeline + résultats
|
||
|
||
### TODO avant deploy prod (Étape 9)
|
||
|
||
- [ ] Chatbot (Étape 5bis) — non implémenté dans cette session
|
||
- [ ] Bandeau bas + Liberapay (Étape 7) — non implémenté
|
||
- [ ] Page /a-propos (Étape 8) — non implémenté
|
||
- [ ] Deploy git sur VPS + build Nuxt production
|
||
- [ ] Purger 8 anciens records V1 (IDs 1-8, moderation_status null) avant prod
|
||
- [ ] Tester worker sur soumission réelle via /contribuer
|
||
- [ ] Vérifier que les 3 fiches test (Ids 106-108) sont modérées ou purgées avant prod (sont en mode test)
|
||
|
||
---
|
||
|
||
---
|
||
|
||
## Session 3b — Deploy final AEP (2026-04-14 soir)
|
||
|
||
**Durée :** ~3h · **Résultat :** AEP V2 live sur `aep.trans-former.fr` + `nav.trans-former.fr` (alias)
|
||
|
||
### Dispatch
|
||
|
||
| # | Action | Modèle | Status |
|
||
|---|--------|--------|--------|
|
||
| A | Prompt système chatbot Mistral Small | Opus + Jules | ✓ |
|
||
| B | Composant `BandeauBas.vue` + endpoint `/api/stats` | Sonnet | ✓ |
|
||
| C | Page `/a-propos` (brouillon — texte à réécrire par Jules) | Sonnet | ✓ |
|
||
| D | Cron purge `/tmp/nav-ratelimit/` (systemd timer quotidien 03:00 UTC) | Sonnet | ✓ |
|
||
| E | Enrichissement rétro 96 fiches (worker IA Mistral Nemo) | Sonnet | ✓ |
|
||
| F | DNS OVH + Caddy alias + build + deploy + tests HTTPS | Opus + Jules | ✓ |
|
||
|
||
### Décisions tranchées
|
||
|
||
| Décision | Choix | Raison |
|
||
|----------|-------|--------|
|
||
| Domaine V2 | `aep.trans-former.fr` nouveau + alias `nav.*` | Pas de V1 figée réelle à préserver : le VPS servait déjà la V2 |
|
||
| Structure Caddy | Alias simple (2 domaines → même service `:3333`) | Éviter 2 instances Node pour un code identique |
|
||
| Prompt chatbot | "AEP — Écosystème Entraide", posture engagée, règle hors-scope | Cohérent avec positionnement AEP vs terminologie NAV résiduelle |
|
||
| Rate limit chatbot | JSON SHA-256 (RGPD) — Redis retiré | Volume borné par circuit breaker, pas besoin de Redis |
|
||
| Enrichissement rétro | Bulk patch `pending` → worker → bulk approve API | Spot-check qualité accepté, tags à corriger en modération manuelle |
|
||
|
||
### Commits
|
||
|
||
```
|
||
74c9722 feat(aep-s3b): bandeau transparence + /a-propos + cron purge rate-limit
|
||
68e1e53 fix(chatbot): purge version Redis + prompt système AEP
|
||
```
|
||
|
||
### Enrichissement IA — bilan qualité
|
||
|
||
- **Coût réel** : €0.006 pour 96 fiches (×3 300 sous le seuil €20/mois)
|
||
- **Descriptions** : factuellement correctes (spot-check 10 fiches OK)
|
||
- **Tags** : 6/10 vides sur Maisons de l'Architecture (mapping ne matche pas "expositions/conférences"), 1 sur-tagging MAOP Id 11 (10/10 tags — site généraliste scraped), MAF Id 15 manque "Juridique"
|
||
- **Action Jules** : correction manuelle tags dans NocoDB UI (non bloquant pour deploy)
|
||
|
||
### Découverte imprévue — bug `deploy.sh`
|
||
|
||
Incohérence entre `deploy.sh` (rsync vers `/opt/nav-carte/`) et `nav-carte.service` (exec `/opt/nav-carte/.output/server/index.mjs`). Le `deploy.sh` écrit au mauvais endroit — le service tourne sur `.output/` qui n'est jamais mis à jour par le script. Contournement cette session : tar + ssh extract manuel dans `.output/`. **À patcher en V3** : aligner `deploy.sh` sur la structure `.output/`.
|
||
|
||
### État à la clôture
|
||
|
||
```
|
||
✓ aep.trans-former.fr — HTTPS 200 (/, /a-propos, /api/stats)
|
||
✓ nav.trans-former.fr — alias OK
|
||
✓ 99 fiches approved (96 enrichies + 3 S3a)
|
||
✓ Bandeau bas + chatbot + lien À propos intégrés
|
||
✓ Cron purge rate-limit actif (prochaine exécution 2026-04-15 03:00 UTC)
|
||
✓ V1 Redis chatbot purgée
|
||
```
|
||
|
||
### TODO post-session (Jules, async)
|
||
|
||
- [ ] Réécrire le texte de `pages/a-propos.vue` (placeholders `<!-- TODO Jules -->` en tête de chaque section)
|
||
- [ ] Corriger tags Maisons de l'Architecture + MAOP + MAF dans NocoDB UI
|
||
- [ ] Tests manuels : submit `/contribuer`, chatbot mobile (iOS + Android), feedback UX
|
||
- [ ] Patcher `deploy.sh` pour cibler `/opt/nav-carte/.output/` (voir découverte ci-dessus)
|
||
- [ ] Vérifier widget Liberapay à `liberapay.com/trans-former.fr` — compte existe ?
|
||
|
||
---
|
||
|
||
## Backlog V3 (hors scope V2)
|
||
|
||
### Infra / deploy
|
||
- **Fix `deploy.sh`** — cible `/opt/nav-carte/.output/` pas `/opt/nav-carte/` (incohérence découverte en S3b)
|
||
- **Migration `/opt/nav-carte/` → `/opt/aep/`** — alignement nom projet (dette terminologique NAV)
|
||
- **Rebrand service systemd** `nav-carte.service` → `aep.service` (low-priority)
|
||
- **Monitoring** — sonde Uptime Kuma (`status.trans-former.fr`) : ajouter `aep.*/api/stats`
|
||
- **Backup NocoDB** — dump quotidien base `pipilvsi7dibo80` vers stockage externe
|
||
|
||
### Produit / UX
|
||
- **Tags auto** — améliorer le mapping worker pour matcher "expositions/conférences/culture" → Développement + Formation (évite tags vides sur MA)
|
||
- **Modération UI custom** — interface dédiée pour batch approve/reject (au lieu de cliquer 96 rows)
|
||
- **Sidebar "Fiches récemment ajoutées"** — boost engagement
|
||
- **Recherche sémantique chatbot** — passer de keyword match à embeddings (Mistral Embed, volume à estimer)
|
||
- **Page `/transparence` publique** — détail coûts, CO2, donateurs Liberapay
|
||
|
||
### Éditorial
|
||
- **Contenu `/a-propos`** — texte politique écrit par Jules seul (hors scope IA)
|
||
- **Badge IA souveraine** — vérifier formulation (Hetzner = Falkenstein DE, pas FR — dire "Hébergé en Europe" ?)
|
||
- **Page Contribuer — modération visible** — expliquer pipeline "formulaire → worker IA → modération humaine"
|
||
|
||
### Monétisation / association
|
||
- **Bascule Liberapay → HelloAsso** quand ASO créée (scénario 2 du doc `C-systeme-dons.md`)
|
||
- **Widget Liberapay "total collecté"** — actuellement juste CTA, ajouter feedback
|
||
|
||
### Technique
|
||
- **Caching API organisations** — actuellement re-fetch NocoDB à chaque render
|
||
- **Full-text search côté client** — Fuse.js sur descriptions enrichies
|
||
- **Mode offline / PWA** — manifest + service worker pour usage terrain
|