# Trouve-KA — Décisions d'architecture Author: Simon-Pierre Boucher — Contact: contact@spboucher.ai ## D1 — Backend de recherche : OpenSearch 2.x **Candidats évalués :** OpenSearch, Elasticsearch, Typesense, Meilisearch, Vespa, Tantivy, Quickwit (CLAUDE.md §3). **Choix : OpenSearch 2.17**, parce que : - **BM25 réel et paramétrable** par champ (multi_match, boosts), critère n°1 du MVP. - **Indexation incrémentale** near-real-time : `refresh_interval: 1s` → une page indexée est cherchable en ~1 s (principe cardinal §0.3). - **`function_score`** : la fonction de ranking Québec-first custom (§9) s'exprime nativement, poids ajustables sans réindexation. - **Analyzers FR/EN** + `synonym_graph` search-time (bilinguisme sans traduction à l'ingestion). - **Highlighting** natif pour les snippets, **facettes** pour les filtres, **k-NN natif** pour l'hybride sémantique futur (§17.7) sans changer de moteur. - Licence Apache 2.0, image ARM64, tourne en 2 Go de heap sur un seul node m2m32. **Rejetés :** Meilisearch/Typesense (ranking custom trop rigide pour `Score(d,q)` pondéré; c'était le choix « facile » interdit par la spec), Tantivy (librairie Rust, il faudrait construire le serveur), Quickwit (orienté logs append-only, pas de mise à jour de documents), Vespa (excellent mais complexité opérationnelle démesurée pour un node unique), Elasticsearch (équivalent fonctionnel d'OpenSearch, licence moins permissive). ## D2 — Backend Python unique (FastAPI + pipeline) Crawler, parser, classifier, indexer, ranking, scheduler, enrichment et API partagent un seul langage (Python 3.12) et un seul packaging (`pyproject.toml` racine, namespace `trouveka.*` mappé sur `packages/` et `services/`). Réduit la complexité (§3 : « le crawler peut rester Python même si l'API est TS » — ici tout le backend est Python, seul le web est TS). ## D3 — Frontier dans Postgres, coordination dans Redis - **Frontier = Postgres** : état relationnel (priorités, retries, scheduling), claims multi-workers via `FOR UPDATE SKIP LOCKED` — N workers sans coordinateur. - **Redis** : politesse par hôte (`SET NX PX`, un fetch par hôte par fenêtre, tous workers confondus), drapeau pause, **Redis Streams** pour l'enrichissement asynchrone (consumer groups, ack explicite). - Pas de Celery/RQ : la file d'enrichissement est un stream nu, observable (`XLEN`), sans dépendance lourde. ## D4 — Pipeline inline (fetch→parse→score→index) dans le crawler-worker Le chemin rapide est exécuté inline par le worker : c'est la garantie la plus simple du « cherchable en secondes ». La scalabilité passe par le nombre de workers (`--scale crawler-worker=N`), pas par une séparation prématurée fetch/parse/index (§13 : pas de complexité distribuée prématurée). Les étapes 2-3 (§4) passent par le stream d'enrichissement et ne bloquent jamais. ## D5 — apps/admin fusionné dans apps/web (route /admin) Un seul runtime Next.js sur m2m32 au lieu de deux (~150 Mo RSS économisés), même design system, même proxy API. La séparation reste possible plus tard (le dashboard est un groupe de composants isolés sous `components/admin/`). Justification prévue par §2 (« Claude peut améliorer cette structure sur justification technique solide »). ## D6 — Graphe de liens au niveau domaine d'abord `domain_links` agrégé (from, to, count) plutôt que page→page : suffisant pour l'autorité de domaine et la découverte au MVP, volume contrôlé. Le graphe page→page viendra quand le ranking l'exigera (§8). ## D7 — Réseau : le web proxifie /api Next.js rewrite `/api/*` → API interne. Derrière ngrok (www.trouve-ka.com), un seul port exposé (3000), pas d'URL absolues côté client, pas de CORS en prod.