SPB Git

spb/trouve-ka Public

Trouve-KA — moteur de recherche web indépendant, Québec-first. Crawler distribué, index OpenSearch, ranking bilingue, galerie d'images. En prod : www.trouve-ka.com

Python 76.8% TypeScript 15.7% SQL 3.9% Shell 1.4% CSS 1.3% Dockerfile 0.7%
4.2 KB

# Trouve-KA — Architecture

Author: Simon-Pierre Boucher — Contact: contact@spboucher.ai

# Vue d'ensemble

flowchart LR
    subgraph Découverte
        SEEDS[Seeds §8] --> FRONTIER
        SUBMIT[Soumissions /soumettre] --> FRONTIER
        LINKS[Liens sortants] --> FRONTIER
    end
    FRONTIER[(Frontier<br/>Postgres)] -->|claim SKIP LOCKED| CW[crawler-worker × N]
    CW -->|politesse SET NX PX| REDIS[(Redis)]
    CW --> ROBOTS[robots.txt cache]
    CW --> FETCH[Fetcher HTTP<br/>garde SSRF]
    FETCH --> PARSE[Parser selectolax]
    PARSE --> QC[Classification Québec<br/>page_quebec_score]
    QC --> DEDUP[Hash contenu / doublons]
    DEDUP -->|indexation IMMÉDIATE| OS[(OpenSearch<br/>trouveka-docs)]
    DEDUP --> PG[(Postgres<br/>documents, domaines, liens)]
    CW -->|jamais bloquant| STREAM[Redis Stream<br/>trouveka:enrich]
    STREAM --> EW[enrichment-worker]
    EW -->|update partiel| OS
    SCHED[scheduler] -->|items abandonnés,<br/>autorité de domaine| PG
    OS --> API[API FastAPI]
    PG --> API
    API --> WEB[Next.js web<br/>+ /admin]
    WEB --> NGROK[ngrok<br/>www.trouve-ka.com]

# Principe cardinal (§0.3)

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

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)

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.