Keep/_bmad-output/planning-artifacts/epic-search-improvement.md
sepehr 640fcb26f7 fix: improve note interactions and markdown LaTeX support
## Bug Fixes

### Note Card Actions
- Fix broken size change functionality (missing state declaration)
- Implement React 19 useOptimistic for instant UI feedback
- Add startTransition for non-blocking updates
- Ensure smooth animations without page refresh
- All note actions now work: pin, archive, color, size, checklist

### Markdown LaTeX Rendering
- Add remark-math and rehype-katex plugins
- Support inline equations with dollar sign syntax
- Support block equations with double dollar sign syntax
- Import KaTeX CSS for proper styling
- Equations now render correctly instead of showing raw LaTeX

## Technical Details

- Replace undefined currentNote references with optimistic state
- Add optimistic updates before server actions for instant feedback
- Use router.refresh() in transitions for smart cache invalidation
- Install remark-math, rehype-katex, and katex packages

## Testing

- Build passes successfully with no TypeScript errors
- Dev server hot-reloads changes correctly
2026-01-09 22:13:49 +01:00

464 lines
19 KiB
Markdown

# Epic: Amélioration de la Recherche Sémantique - Version 2.0
**Epic ID:** EPIC-SEARCH-2.0
**Status:** Draft
**Priority:** High
**Created:** 2026-01-09
**Owner:** Development Team
---
## Description du Problème
L'actuel système de recherche hybride (mots-clés + sémantique) produit des résultats non pertinents et imprévisibles. Les utilisateurs se plaignent que la recherche "fait n'importe quoi" - les résultats ne correspondent pas à leurs attentes, même quand les notes contiennent les termes recherchés.
### Analyse des Causes Racines
Après analyse du code dans `keep-notes/app/actions/notes.ts` (lignes 108-212), les problèmes identifiés sont:
1. **Seuil de similarité cosine trop bas (0.40)**: Permet des correspondances sémantiques de très faible qualité, créant du bruit dans les résultats
2. **RRF (Reciprocal Rank Fusion) mal configuré**: Le constant k=60 n'est pas optimal pour le petit nombre de notes typique dans Keep
3. **Pondération fixe recherche mot-clé/sémantique**: Les poids sont hardcodés (3 pour titre, 1 pour contenu, 2 pour labels) sans adaptation au type de requête
4. **Absence de validation des embeddings**: Pas de vérification que les embeddings sont correctement générés ou de dimensionnalité cohérente
5. **Manque de prétraitement des requêtes**: Pas de stemming, lemmatization, ou expansion de requête
6. **Aucune métrique de qualité**: Impossible de mesurer l'amélioration ou la dégradation des performances
### Impact Utilisateur
- **Frustration**: Perte de temps à filtrer manuellement les résultats non pertinents
- **Perte de confiance**: Les utilisateurs désactivent ou évitent la recherche intelligente
- **Expérience dégradée**: Contredit la promesse d'une recherche "intuitive" du PRD
---
## Objectifs Mesurables
1. **Améliorer la précision de recherche de 40%** (mesurée par tests automatisés)
2. **Réduire les faux positifs sémantiques de 60%** (seuil plus strict)
3. **Temps de réponse < 300ms** pour 1000 notes (objectif PRD existant)
4. **Satisfaction utilisateur > 4/5** (feedback post-déploiement)
5. **Taux de "serendipity" (résultats sémantiques sans mots-clés) entre 20-40%** (objectif PRD)
---
## User Stories
### Story 1: Validation et Qualité des Embeddings
**ID:** SEARCH-2.0-1
**Title:** Valider la qualité des embeddings générés
**Priority:** Must Have
**Estimation:** 3h
**En tant que:** développeur
**Je veux:** valider que les embeddings sont correctement générés et stockés
**Afin que:** la recherche sémantique fonctionne sur des données de qualité
**Critères d'Acceptation:**
1. **Given** une note avec du contenu texte
2. **When** l'embedding est généré via `provider.getEmbeddings()`
3. **Then** le vecteur doit:
- Avoir une dimensionnalité > 0
- Contenir des nombres valides (pas de NaN, Infinity)
- Avoir une norme L2 normale (entre 0.7 et 1.2)
4. **Given** des embeddings existants en base de données
5. **When** ils sont chargés via `parseNote()`
6. **Then** ils doivent être correctement désérialisés et validés
7. **And** une action admin `/api/debug/embeddings/validate` doit lister les notes problématiques
**Fichiers à Modifier:**
- `keep-notes/app/actions/notes.ts` - Ajouter validation dans `parseNote()`
- `keep-notes/lib/utils.ts` - Ajouter `validateEmbedding()` et `normalizeEmbedding()`
- `keep-notes/app/api/admin/embeddings/validate/route.ts` - Nouveau endpoint debug
**Tests Nécessaires:**
- Test unitaire `validateEmbedding()` avec vecteurs valides/invalides
- Test d'intégration création note → validation embedding
- Test endpoint API debug
**Risques:**
- Certains embeddings existants invalides nécessiteront un re-indexing
- Performance impact de la validation à chaque chargement
---
### Story 2: Optimisation du Seuil de Similarité Sémantique
**ID:** SEARCH-2.0-2
**Title:** Ajuster le seuil de similarité cosine pour éliminer le bruit
**Priority:** Must Have
**Estimation:** 4h
**En tant que:** utilisateur
**Je veux:** ne voir que des résultats sémantiquement pertinents
**Afin que:** la recherche me fasse confiance et me fasse gagner du temps
**Critères d'Acceptation:**
1. **Given** une requête de recherche sémantique
2. **When** les notes sont classées par similarité cosine
3. **Then** seules les notes avec similarité >= 0.65 sont considérées (au lieu de 0.40)
4. **And** le seuil doit être configurable dans `SystemConfig` (`SEARCH_SEMANTIC_THRESHOLD`)
5. **Given** une recherche avec résultats sémantiques faibles
6. **When** le seuil est appliqué
7. **Then** les faux positifs sont réduits d'au moins 50%
8. **And** un test automatisé mesure la réduction des faux positifs
**Fichiers à Modifier:**
- `keep-notes/app/actions/notes.ts` - Ligne 190, remplacer 0.40 par `config.SEARCH_SEMANTIC_THRESHOLD || 0.65`
- `keep-notes/lib/config.ts` - Lire la config depuis DB
- `keep-notes/tests/search-quality.spec.ts` - Ajouter tests de seuil
**Tests Nécessaires:**
- Test Playwright: recherche "coding" ne retourne PAS des notes sur "cuisine" (faux positifs)
- Test unitaire: vérifier que les scores < seuil sont filtrés
- Test de non-régression: s'assurer que les vrais positifs ne sont pas perdus
**Risques:**
- Seuil trop élevé peut éliminer des résultats pertinents (faux négatifs)
- Nécessite A/B testing pour trouver le seuil optimal
- Différents modèles d'embedding peuvent nécessiter des seuils différents
---
### Story 3: Reconfiguration de l'Algorithme RRF
**ID:** SEARCH-2.0-3
**Title:** Optimiser le paramètre k du Reciprocal Rank Fusion
**Priority:** Should Have
**Estimation:** 3h
**En tant que:** système
**Je veux:** un RRF avec un paramètre k adapté au nombre de notes typique
**Afin que:** le ranking hybride reflète mieux la pertinence réelle
**Critères d'Acceptation:**
1. **Given** un RRF avec constant k
2. **When** le nombre moyen de notes par utilisateur est < 500
3. **Then** k doit être 20 (au lieu de 60) pour mieux pénaliser les bas rangs
4. **And** k doit être configurable: `k = max(20, nombre_notes / 10)`
5. **Given** deux listes de ranking (mot-clé + sémantique)
6. **When** RRF est appliqué
7. **Then** les résultats bien classés dans les deux listes sont fortement favorisés
8. **And** la formule RRF est documentée dans le code
**Fichiers à Modifier:**
- `keep-notes/app/actions/notes.ts` - Lignes 182-198, ajuster k et ajouter logique adaptive
- `keep-notes/lib/utils.ts` - Ajouter `calculateRRFK(totalNotes: number): number`
**Tests Nécessaires:**
- Test unitaire: vérifier la formule RRF avec différents k
- Test d'intégration: comparer rankings avec k=20 vs k=60
- Test avec dataset de benchmark (notes + requêtes + résultats attendus)
**Risques:**
- Changer k peut impacter significativement l'ordre des résultats
- Nécessite validation utilisateur sur de vraies données
- Peut nécessiter des ajustements itératifs
---
### Story 4: Pondération Adaptative des Scores de Recherche
**ID:** SEARCH-2.0-4
**Title:** Adapter les poids mot-clé/sémantique selon le type de requête
**Priority:** Should Have
**Estimation:** 6h
**En tant que:** utilisateur
**Je veux:** que la recherche privilégie les mots-clés pour les termes exacts
**Et qu'elle privilégie le sémantique pour les concepts abstraits
**Afin que:** les résultats soient toujours pertinents
**Critères d'Acceptation:**
1. **Given** une requête avec des guillemets (ex: `"Error 404"`)
2. **When** la recherche est exécutée
3. **Then** le poids mot-clé est multiplié par 2 (recherche exacte prioritaire)
4. **Given** une requête conceptuelle (ex: "comment améliorer...")
5. **When** la recherche est exécutée
6. **Then** le poids sémantique est multiplié par 1.5 (concept prioritaire)
7. **Given** une requête mixte
8. **When** aucun pattern n'est détecté
9. **Then** les poids par défaut sont utilisés
10. **And** la logique de détection est documentée
**Fichiers à Modifier:**
- `keep-notes/app/actions/notes.ts` - Ajouter `detectQueryType()` et ajuster les poids
- `keep-notes/lib/types.ts` - Ajouter `QueryType: 'exact' | 'conceptual' | 'mixed'`
- `keep-notes/lib/utils.ts` - Ajouter `detectQueryType(query: string): QueryType`
**Tests Nécessaires:**
- Test unitaire `detectQueryType()` avec différents patterns
- Test d'intégration: vérifier que `"Error 404"` privilégie les mots-clés
- Test d'intégration: vérifier que "comment cuisiner" privilégie le sémantique
- Test Playwright: scénarios de recherche avec guillemets
**Risques:**
- La détection automatique du type de requête peut être imprécise
- Nécessite des règles bien pensées pour éviter les effets de bord
- Peut nécessiter du machine learning pour être vraiment efficace
---
### Story 5: Expansion et Normalisation des Requêtes
**ID:** SEARCH-2.0-5
**Title:** Améliorer les requêtes par expansion et normalisation
**Priority:** Could Have
**Estimation:** 5h
**En tant que:** utilisateur francophone/anglophone
**Je veux:** que ma recherche trouve les résultats même avec des variations de mots
**Afin que:** je n'aie pas à deviner les termes exacts utilisés
**Critères d'Acceptation:**
1. **Given** une requête avec des mots au pluriel (ex: "recettes pizzas")
2. **When** la recherche est exécutée
3. **Then** les singuliers sont aussi recherchés ("recette pizza")
4. **Given** une requête avec des accents (ex: "éléphant")
5. **When** la recherche est exécutée
6. **Then** les variantes sans accents sont aussi recherchées ("elephant")
7. **Given** une requête courte (< 3 mots)
8. **When** l'expansion est activée
9. **Then** des synonymes courants sont ajoutés (ex: "bug" "erreur", "problème")
10. **And** l'expansion est limitée à 3 termes par mot original
**Fichiers à Modifier:**
- `keep-notes/app/actions/notes.ts` - Ajouter `expandQuery()` avant le calcul des scores
- `keep-notes/lib/utils.ts` - Implémenter `expandQuery()` et `normalizeText()`
- `keep-notes/lib/data/synonyms.json` - Créer une liste de synonymes (FR/EN)
**Tests Nécessaires:**
- Test unitaire `expandQuery()` avec différents cas
- Test d'intégration: recherche "pizzas" trouve notes avec "pizza"
- Test d'intégration: recherche "bug" trouve notes avec "erreur"
- Test performance: vérifier que l'expansion ne dégrade pas les performances
**Risques:**
- L'expansion de requête peut augmenter significativement les faux positifs
- La gestion des synonymes est complexe et contextuelle
- Nécessite une base de synonymes bien maintenue
- Peut ne pas être pertinent pour toutes les langues
---
### Story 6: Interface de Debug et Monitoring de Recherche
**ID:** SEARCH-2.0-6
**Title:** Créer une interface de debug pour analyser la qualité de recherche
**Priority:** Should Have
**Estimation:** 8h
**En tant que:** développeur/testeur
**Je veux:** visualiser les détails du calcul de score pour chaque résultat
**Afin que:** je puisse comprendre et optimiser la recherche
**Critères d'Acceptation:**
1. **Given** une recherche exécutée
2. **When** le mode debug est activé (`?debug=true`)
3. **Then** chaque résultat affiche:
- Score mot-clé brut
- Score sémantique brut (similarité cosine)
- Score RRF final
- Rang dans chaque liste (mot-clé / sémantique)
4. **Given** la page `/debug-search`
5. **When** j'accède à la page
6. **Then** je vois une interface pour:
- Tester des requêtes avec tous les paramètres
- Comparer différents seuils de similarité
- Voir les embeddings des notes
7. **And** les métriques sont exportables en JSON
**Fichiers à Modifier:**
- `keep-notes/app/actions/notes.ts` - Retourner les scores debug si demandé
- `keep-notes/app/debug-search/page.tsx` - Créer la page (existante dans git status)
- `keep-notes/components/search-debug-results.tsx` - Nouveau composant
- `keep-notes/app/api/debug/search/route.ts` - Nouveau endpoint API
**Tests Nécessaires:**
- Test E2E: accès à la page debug-search
- Test API: endpoint retourne bien les scores détaillés
- Test visuel: vérifier l'affichage des scores dans l'UI
- Test de performance: vérifier que le mode debug n'impacte pas la recherche normale
**Risques:**
- Complexité supplémentaire dans la UI
- Nécessite de bien sécuriser l'accès (admin uniquement)
- Informations sensibles (embeddings) visibles
---
### Story 7: Re-génération et Validation des Embeddings Existants
**ID:** SEARCH-2.0-7
**Title:** Script de re-indexation des embeddings invalides ou manquants
**Priority:** Must Have
**Estimation:** 4h
**En tant que:** administrateur système
**Je veux:** un script pour régénérer les embeddings des notes existantes
**Afin que:** toutes les notes bénéficient de la recherche sémantique
**Critères d'Acceptation:**
1. **Given** des notes avec embeddings manquants ou invalides
2. **When** je lance le script `npm run reindex-embeddings`
3. **Then** les embeddings sont régénérés pour toutes les notes
4. **And** la progression est affichée (X/Y notes traitées)
5. **Given** des notes avec des embeddings valides
6. **When** le script est lancé avec `--force`
7. **Then** tous les embeddings sont régénérés (même les valides)
8. **And** le script peut être relancé sans erreurs (idempotent)
9. **And** un rapport final résume les succès/erreurs
**Fichiers à Modifier:**
- `keep-notes/scripts/reindex-embeddings.ts` - Créer le script
- `keep-notes/app/actions/admin.ts` - Ajouter `reindexAllEmbeddings()`
- `keep-notes/package.json` - Ajouter le script npm
**Tests Nécessaires:**
- Test du script sur base de données vide
- Test du script avec des notes sans embeddings
- Test du script avec des embeddings invalides (NaN, dimension 0)
- Test du mode `--force`
- Test d'idempotence (relancer le script deux fois)
**Risques:**
- Temps d'exécution long si beaucoup de notes (plusieurs minutes/heures)
- Peut saturer le provider IA (Ollama/OpenAI) avec trop de requêtes
- Nécessite un mécanisme de rate limiting
- Peut impacter les performances de l'application si lancé pendant l'utilisation
---
### Story 8: Suite de Tests Automatisés de Qualité de Recherche
**ID:** SEARCH-2.0-8
**Title:** Créer des tests automatisés pour mesurer la qualité de recherche
**Priority:** Should Have
**Estimation:** 6h
**En tant que:** développeur
**Je veux:** une suite de tests automatisés pour valider la qualité de recherche
**Afin que:** les améliorations soient mesurables et les régressions détectées
**Critères d'Acceptation:**
1. **Given** un dataset de test (notes + requêtes + résultats attendus)
2. **When** les tests sont exécutés
3. **Then** les métriques suivantes sont calculées:
- Precision: % de résultats pertinents dans le top 10
- Recall: % de résultats pertinents trouvés
- MRR (Mean Reciprocal Rank): rang moyen du premier résultat pertinent
- Faux positifs sémantiques: résultats sans pertinence
4. **Given** une modification du code de recherche
5. **When** les tests sont relancés
6. **Then** une régression de plus de 5% fait échouer les tests
7. **And** les tests prennent < 2 minutes à s'exécuter
8. **And** un rapport HTML est généré pour analyse
**Fichiers à Modifier:**
- `keep-notes/tests/search-benchmark.spec.ts` - Créer le benchmark
- `keep-notes/tests/fixtures/search-dataset.json` - Dataset de test
- `keep-notes/tests/utils/search-metrics.ts` - Utilitaires de calcul de métriques
- `keep-notes/playwright.config.ts` - Configuration pour générer le rapport HTML
**Tests Nécessaires:**
- Test du test (métatest) avec un dataset trivial
- Test avec dataset réel (notes sur tech, cuisine, voyage, etc.)
- Test de régression: introduire un bug et vérifier que les tests le détectent
- Test de performance: temps d'exécution des tests
**Risques:**
- Création du dataset manuelle et longue
- Subjectivité de la "pertinence" (qui décide quoi est pertinent?)
- Maintenance du dataset à chaque nouvelle feature
- Tests peuvent être "flaky" si les embeddings changent
---
## Dépendances Entre Stories
```
SEARCH-2.0-1 (Validation Embeddings)
SEARCH-2.0-7 (Re-génération) ← dépend de 1 pour détecter les invalides
SEARCH-2.0-2 (Seuil Similarité) ← dépend de 1 pour des embeddings valides
SEARCH-2.0-3 (RRF Config)
SEARCH-2.0-4 (Pondération Adaptative)
SEARCH-2.0-8 (Tests Automatisés) ← dépend de 2,3,4 pour mesurer les améliorations
SEARCH-2.0-6 (Debug Interface) ← utile pendant le développement de toutes les autres
SEARCH-2.0-5 (Expansion Requêtes) ← amélioration optionnelle à la fin
```
**Ordre recommandé:**
1. **Sprint 1:** SEARCH-2.0-1, SEARCH-2.0-7 (Base solide avec embeddings valides)
2. **Sprint 2:** SEARCH-2.0-2, SEARCH-2.0-3 (Corrections critiques du ranking)
3. **Sprint 3:** SEARCH-2.0-4, SEARCH-2.0-6 (Améliorations + tooling)
4. **Sprint 4:** SEARCH-2.0-8, SEARCH-2.0-5 (Tests + optimisations optionnelles)
---
## Métriques de Succès
### Avant/Après (Objectifs)
| Métrique | Avant | Après (Objectif) | Comment Mesurer |
|----------|-------|------------------|-----------------|
| Précision Top-10 | ~50% (estimé) | 70%+ | Tests automatisés Story 8 |
| Faux positifs sémantiques | ~30% | <10% | Tests Playwright |
| Temps de réponse (1000 notes) | 200ms | <300ms | Tests performance |
| Taux de serendipité | N/A | 20-40% | Tests dataset |
| Satisfaction utilisateur | 2/5 (subjectif) | 4/5+ | Sondage post-déploiement |
---
## Configuration Système Proposée
Nouveaux paramètres dans `SystemConfig`:
```typescript
interface SearchConfig {
// Seuil de similarité cosine minimum (0-1)
SEARCH_SEMANTIC_THRESHOLD: number; // défaut: 0.65
// Constante k pour RRF (adaptive)
SEARCH_RRF_K_BASE: number; // défaut: 20
SEARCH_RRF_K_ADAPTIVE: boolean; // défaut: true
// Pondération mot-clé vs sémantique
SEARCH_KEYWORD_BOOST_EXACT: number; // défaut: 2.0 (guillemets)
SEARCH_KEYWORD_BOOST_CONCEPTUAL: number; // défaut: 0.7
SEARCH_SEMANTIC_BOOST_EXACT: number; // défaut: 0.7
SEARCH_SEMANTIC_BOOST_CONCEPTUAL: number; // défaut: 1.5
// Expansion de requête
SEARCH_QUERY_EXPANSION_ENABLED: boolean; // défaut: true
SEARCH_QUERY_EXPANSION_MAX_SYNONYMS: number; // défaut: 3
// Debug
SEARCH_DEBUG_MODE: boolean; // défaut: false
}
```
---
## Fichers Critiques pour l'Implémentation
Les 5 fichiers les plus importants à modifier:
1. **keep-notes/app/actions/notes.ts** - Logique de recherche principale (RRF, seuils, ranking)
2. **keep-notes/lib/utils.ts** - Fonctions de similarité cosine et nouvelles utilités
3. **keep-notes/lib/ai/providers/ollama.ts** - Génération des embeddings avec validation
4. **keep-notes/tests/search-quality.spec.ts** - Tests de qualité de recherche
5. **keep-notes/lib/config.ts** - Configuration des nouveaux paramètres
---
**Document Version:** 1.0
**Last Updated:** 2026-01-09
**Agent:** Plan (a551c9b)