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%
7.7 KB

# Trouve-KA — Cherche le Québec.

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 (node m2m32 du cluster MacLustr, tunnel ngrok).

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


# 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=3'

# 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.