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%
ZIP
NameLast commitUpdated
apps Trouve-KA — moteur de recherche Québec-first (crawler, index, ranking,... 1 h ago
docs Trouve-KA — moteur de recherche Québec-first (crawler, index, ranking,... 1 h ago
infrastructure Trouve-KA — moteur de recherche Québec-first (crawler, index, ranking,... 1 h ago
M2M32brouve-ka Trouve-KA — moteur de recherche Québec-first (crawler, index, ranking,... 1 h ago
M2M32crouve-ka Trouve-KA — moteur de recherche Québec-first (crawler, index, ranking,... 1 h ago
packages Trouve-KA — moteur de recherche Québec-first (crawler, index, ranking,... 1 h ago
scripts Trouve-KA — moteur de recherche Québec-first (crawler, index, ranking,... 1 h ago
services Trouve-KA — moteur de recherche Québec-first (crawler, index, ranking,... 1 h ago
tests Trouve-KA — moteur de recherche Québec-first (crawler, index, ranking,... 1 h ago
.env.example Trouve-KA — moteur de recherche Québec-first (crawler, index, ranking,... 1 h ago
.gitignore Trouve-KA — moteur de recherche Québec-first (crawler, index, ranking,... 1 h ago
CLAUDE.md Trouve-KA — moteur de recherche Québec-first (crawler, index, ranking,... 1 h ago
docker-compose.yml Trouve-KA — moteur de recherche Québec-first (crawler, index, ranking,... 1 h ago
package.json Trouve-KA — moteur de recherche Québec-first (crawler, index, ranking,... 1 h ago
pnpm-lock.yaml Trouve-KA — moteur de recherche Québec-first (crawler, index, ranking,... 1 h ago
pnpm-workspace.yaml Trouve-KA — moteur de recherche Québec-first (crawler, index, ranking,... 1 h ago
pyproject.toml Trouve-KA — moteur de recherche Québec-first (crawler, index, ranking,... 1 h ago
README.md Trouve-KA — moteur de recherche Québec-first (crawler, index, ranking,... 1 h ago
README.md

# Trouve-KA — Cherche le Québec.

Statut Site Pages indexées Domaines Recherche Python Next.js OpenSearch PostgreSQL Redis Tests par Groupe KA

Moteur de recherche web indépendant, Québec-first. Son propre crawler, son propre index, son propre ranking, son API et son application web publique. Pas un métamoteur : aucune dépendance à Google, Bing ou Brave pour les résultats.

Crawl en continu. Indexe immédiatement. Recherche immédiatement. Améliore en asynchrone.

En production : https://www.trouve-ka.com — un moteur de recherche par Groupe KA.

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


# Aperçu

Accueil Résultats
Accueil — la boîte de recherche, le compteur vivant, le flux « KA bot scrappe en ce moment » au footer Résultats — pastilles éditoriales (◆ gradué selon le score Québec), vignettes og:image, snippets surlignés, 21 ms
Galerie d'images État du moteur
Onglet Images — galerie des pages avec image représentative (hotlink + attribution, jamais de crawl d'images) /status — métriques publiques réelles, auto-refresh 10 s

# Métriques réelles (2026-08-13, jour 1 — ~6 h après le premier crawl)

Métrique Valeur
Pages indexées et cherchables 59 073 (+~35 000/heure)
Domaines québécois découverts 7 898 (à partir de 64 seeds)
URLs découvertes en attente (frontier) 433 999
Pages fetchées / heure 76 198
Latence de recherche (mesurée en prod) 20–95 ms (cible p50 < 100 ms ✓)
Délai fetch → cherchable ~2–4 secondes
Nodes de crawl 3 (M2M32 ×5 workers Docker, M2M32b, M2M32c)
Taille de l'index ~14 Ko/document (791 Mo à 55 k docs)
Tests unitaires 57 ✓ (canonicalisation, SSRF, robots, scoring Québec, pièges, fetcher, ranking)

Chaque chiffre ci-dessus vient des vraies tables (crawl_attempts, frontier_items) et de l'index réel — aucun compteur simulé (règle § fake du projet).


# Ce que c'est

text
Crawler → Frontier → Fetcher → Parser → Classification Québec
→ Déduplication → Indexer → Index → Ranking → API → Web App

Le crawler découvre le web québécois à partir de seeds à forte autorité (gouvernement, municipalités, universités, médias), suit les liens sortants, juge la pertinence québécoise de chaque page (page_quebec_score et domain_quebec_score), et indexe immédiatement : une page fetchée est cherchable en ~2-4 secondes, pendant que le frontier continue de grandir. L'enrichissement (autorité, entités, embeddings) arrive après, en asynchrone, sans jamais bloquer.

Détails : docs/architecture.md (diagrammes Mermaid) et docs/decisions.md (pourquoi OpenSearch, pourquoi Postgres comme frontier, etc.).

# Stack

Couche Choix
Web Next.js 15, TypeScript, React 19, Tailwind (composants style shadcn/ui)
API FastAPI (Python 3.12)
Pipeline Python 3.12 async — httpx, selectolax, Protego
BD relationnelle PostgreSQL 16 (frontier, domaines, documents, graphe de liens, analytics)
Coordination Redis 7 (politesse par hôte, pause, Redis Streams pour l'enrichissement)
Recherche OpenSearch 2.17 (BM25 FR/EN, synonymes bilingues, function_score Québec-first)
Déploiement Docker Compose sur m2m32 + ngrok (www.trouve-ka.com)

# Layout du monorepo

text
apps/web         # moteur public + dashboard /admin (Next.js)
apps/api         # FastAPI (trouveka.api)
services/        # crawler, frontier, parser, classifier, indexer, ranking, scheduler, enrichment
packages/        # config, database, logging, queue, search-core, shared, types
infrastructure/  # docker/, migrations/, monitoring/, deployment/ (m2m32 + ngrok)
scripts/         # bootstrap-seeds/, start-crawler/, health-check/, eval/, check-headers.py
tests/           # unitaires : canonicalisation, SSRF, robots, scoring Québec, fetcher, ranking

Le backend Python est un seul package namespace trouveka.* mappé sur ce layout (voir pyproject.toml).

# Démarrage rapide (dev)

bash
git clone && cd trouve-ka
cp .env.example .env
docker compose up -d postgres redis opensearch   # infra
uv venv --python 3.12 .venv && uv pip install -e ".[dev]" --python .venv/bin/python
pnpm install

pnpm crawl:seed                                  # migrations + 60+ seeds québécoises
bash scripts/start-crawler/start.sh &            # le crawl démarre
.venv/bin/uvicorn trouveka.api.main:app --port 8080 &
pnpm dev                                         # → http://localhost:3000
# → des résultats apparaissent en quelques secondes

Ports occupés sur la machine? Surcharger dans .env : PG_PORT, REDIS_PORT, SEARCH_PORT, API_PORT (et les URLs correspondantes).

Stack complet en containers : docker compose up -d --build (le service migrate applique les migrations, crawler-worker se scale avec docker compose up -d --scale crawler-worker=3).

# Comment ça marche

# Crawl et politesse

  • Identité assumée : UA Mozilla/5.0 (compatible; TrouveKABot/0.1; +https://www.trouve-ka.com/trouveka-bot), page publique /trouveka-bot, pas de stealth.
  • robots.txt parsé avec Protego, cache 24 h en base; noindex/nofollow/X-Robots-Tag respectés.
  • Politesse par origine : verrou Redis par hôte (défaut 2 s entre requêtes, Crawl-delay respecté), quel que soit le nombre de workers.
  • Sécurité : garde SSRF (IP privées/loopback/métadonnées cloud bloquées, revalidée à chaque redirection), limites par réponse (3 Mo, 5 redirections, timeout 20 s), détection de pièges (session IDs, calendriers infinis, facettes explosives, pagination sans fin).

# Indexation incrémentale

Le worker exécute fetch→parse→score→index inline : refresh_interval: 1s côté OpenSearch → cherchable en secondes. Détection de changement par hash de contenu + ETag/If-Modified-Since; recrawl adaptatif (inchangé → intervalle ×2, volatil → ÷2).

# Détection Québec

Deux scores distincts (page et domaine) calculés à partir de : TLD (.qc.ca, .quebec), gazetteer de toponymes (pondération réduite pour les ambigus type Laval/Hull), codes postaux G/H/J, indicatifs (418/514/438/…), organisations connues (Hydro-Québec, RAMQ, UQAM…), mentions structurées de la province (JSON-LD), langue française (indice, pas preuve). Déterministe et gratuit — aucun LLM dans le chemin chaud (§12).

# Ranking

function_score OpenSearch : BM25 bilingue (analyzers FR + EN, synonymes thermopompe↔heat pump à la recherche) + scores Québec + autorité de domaine (inlinks pondérés) + fraîcheur + boost de localité (« plombier Gatineau » → documents avec preuve géographique locations). Chaque composant est optionnel; BM25 tient seul.

Évaluation mesurable : python3 scripts/eval/run-eval.py --api http://localhost:8080 (dataset dans scripts/eval/ranking-eval.yaml).

# API

  • GET /api/search?q=&page=&limit=&language=&category=&quebec_only=&freshness=
  • GET /api/status — compteurs publics (pages, domaines, débit, état du crawler)
  • POST /api/submit {"url": …} — soumettre un site québécois (soumission ≠ inclusion)
  • GET|POST /api/admin/* — protégé par header X-Admin-Token : overview, flux live, pause/reprise, seeds, recrawl, blocage de domaine, inspection du frontier

# Tests

bash
.venv/bin/python -m pytest tests/          # 54 tests : URLs, SSRF, pièges, Québec, frontier, parser, fetcher, ranking
python3 scripts/check-headers.py           # header auteur obligatoire dans chaque fichier source (CI)
bash scripts/health-check/check.sh         # santé du stack

# Déploiement m2m32 + ngrok (www.trouve-ka.com)

Prérequis sur m2m32 : colima + docker + docker-compose (brew), ngrok authentifié, domaine www.trouve-ka.com réservé dans le compte ngrok.

bash
pnpm deploy:m2m32        # = bash infrastructure/deployment/deploy-m2m32.sh

Le script : rsync du monorepo → docker compose up -d --build → migrations + seeds (idempotent) → tunnel ngrok http --url=www.trouve-ka.com 3000 → health-check. Le web proxifie /api/* vers l'API interne : un seul port exposé, pas d'URL absolues côté client, cookies/CORS sans surprise derrière le tunnel.

Opérations courantes sur le node :

bash
ssh M2M32 'cd trouve-ka && docker compose logs -f crawler-worker'   # crawl en direct
ssh M2M32 'cd trouve-ka && docker compose up -d --scale crawler-worker=5'

# Crawl distribué (M2M32b, M2M32c)

Des workers satellites tournent en natif sur d'autres nodes du cluster et se coordonnent sans orchestrateur via le frontier Postgres (FOR UPDATE SKIP LOCKED) et les verrous de politesse Redis (tous nodes confondus : jamais plus d'une requête par site par seconde). Communication par l'Ethernet LAN interne (192.168.2.x).

bash
# Sur un node satellite (uv + python 3.12, .env pointé sur le node central) :
cd ~/trouve-ka && pm2 start .venv/bin/python --name trouveka-crawler \
  --interpreter none -- -m trouveka.crawler.worker

⚠️ macOS Sequoia bloque l'accès « réseau local » des processus launchd : lancer les workers satellites via pm2/nohup depuis une session SSH, pas via LaunchAgent/Daemon.

# Observabilité

Logs JSON structurés par service, métriques réelles via /api/status et /api/admin/overview, dashboard /admin (files, débits, distribution HTTP, latences p50/p95, flux live). Voir infrastructure/monitoring/README.md.

# Règles du dépôt

  • Header auteur obligatoire dans chaque fichier source (§0.1) — vérifié par scripts/check-headers.py.
  • Aucune donnée factice : pas de compteurs simulés, pas de résultats hard-codés; les fixtures vivent dans tests/ uniquement.
  • Provenance préservée : URL originale, canonique, timestamp de crawl, domaine source. Trouve-KA renvoie vers les éditeurs originaux.