# Trouve-KA — Architecture Author: Simon-Pierre Boucher — Contact: contact@spboucher.ai ## Vue d'ensemble ```mermaid flowchart LR subgraph Découverte SEEDS[Seeds §8] --> FRONTIER SUBMIT[Soumissions /soumettre] --> FRONTIER LINKS[Liens sortants] --> FRONTIER end FRONTIER[(Frontier
Postgres)] -->|claim SKIP LOCKED| CW[crawler-worker × N] CW -->|politesse SET NX PX| REDIS[(Redis)] CW --> ROBOTS[robots.txt cache] CW --> FETCH[Fetcher HTTP
garde SSRF] FETCH --> PARSE[Parser selectolax] PARSE --> QC[Classification Québec
page_quebec_score] QC --> DEDUP[Hash contenu / doublons] DEDUP -->|indexation IMMÉDIATE| OS[(OpenSearch
trouveka-docs)] DEDUP --> PG[(Postgres
documents, domaines, liens)] CW -->|jamais bloquant| STREAM[Redis Stream
trouveka:enrich] STREAM --> EW[enrichment-worker] EW -->|update partiel| OS SCHED[scheduler] -->|items abandonnés,
autorité de domaine| PG OS --> API[API FastAPI] PG --> API API --> WEB[Next.js web
+ /admin] WEB --> NGROK[ngrok
www.trouve-ka.com] ``` ## Principe cardinal (§0.3) ```mermaid sequenceDiagram participant F as Frontier participant W as crawler-worker participant O as OpenSearch participant U as Utilisateur F->>W: claim URL (t+0s) W->>W: fetch + parse + score Québec (t+2s) W->>O: index_document (t+3s) Note over O: refresh_interval 1s U->>O: recherche (t+4s) — la page est déjà cherchable W--)W: enrichissement async (étapes 2-3, plus tard) ``` Jamais de cycle « crawler tout → indexer → chercher ». L'enrichissement met à jour des documents **déjà cherchables** (update partiel), il ne conditionne rien. ## Cycle de vie d'une URL ```mermaid stateDiagram-v2 [*] --> pending: découverte (seed, lien, soumission) pending --> in_progress: claim (priorité DESC, SKIP LOCKED) in_progress --> pending: politesse (trop tôt pour cet hôte) in_progress --> pending: succès → next_crawl_at adaptatif in_progress --> pending: erreur transitoire (retry backoff ≤3) in_progress --> done: robots refusé / redirection / doublon in_progress --> failed: erreur permanente ou retries épuisés in_progress --> blocked: domaine bloqué pending --> in_progress: recrawl (fréquence mesurée §5.5) ``` ## Scores (§4, §7, §9) | Étape | Quand | Champs | |---|---|---| | 1 — immédiat | pipeline inline | titre, corps, headings, langue, `page_quebec_score`, `locations`, catégories grossières | | 2 — async | enrichment-worker | `domain_quebec_score` à jour, `authority_score` propagé (plus tard : embeddings, entités) | | 3 — async | scheduler | autorité de domaine depuis `domain_links` (inlinks pondérés Québec) | Ranking à la requête : `function_score = BM25 (multi_match FR/EN + synonymes) + w·page_quebec + w·domain_quebec + w·autorité + gauss(published_at) + boost localité` (désactivable composant par composant — BM25 reste seul debout si tout tombe, §9). ## Dégradation gracieuse (§13) - OpenSearch en panne → l'API répond 503 sur /search, /status reste up; le crawler continue d'alimenter Postgres? Non : l'indexation échoue → l'item est relâché en retry; le frontier survit. - Redis en panne → politesse locale impossible : le worker s'arrête proprement; Postgres intact. - Un worker crash → `scheduler.reset_stale_items` relance ses URLs après 30 min. - Enrichissement en retard → aucune conséquence sur la recherche (backlog visible dans /admin). ## Déploiement m2m32 (§0.2) ```mermaid flowchart LR NG[ngrok www.trouve-ka.com] --> WEB3000[web :3000] WEB3000 -->|rewrite /api/*| API8080[api :8080] subgraph m2m32[Docker Compose sur m2m32 — 32 Go] WEB3000 API8080 PG5432[postgres] RD[redis] OS9200[opensearch 2 Go heap] CWX[crawler-worker × N] EN[enrichment-worker] SC[scheduler] end ``` Un seul port exposé publiquement (3000 via ngrok). Budgets mémoire : OpenSearch 2 Go de heap (~3 Go RSS), Postgres < 1 Go, workers Python ~100-200 Mo chacun, web ~150 Mo — large marge sur 32 Go, scalable par `docker compose up -d --scale crawler-worker=3`.