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%
11.2 KB · 225 lines markdown
Rendered Raw Blame History
1# Trouve-KA — Cherche le Québec.23![Statut](https://img.shields.io/badge/statut-en_production-1c5c41?style=flat-square)4![Site](https://img.shields.io/badge/site-www.trouve--ka.com-141814?style=flat-square)5![Pages indexées](https://img.shields.io/badge/pages_index%C3%A9es-59 000%2B_et_%C3%A7a_monte-b9cfee?style=flat-square&labelColor=141814)6![Domaines](https://img.shields.io/badge/domaines_qu%C3%A9b%C3%A9cois-7 900%2B-b9cfee?style=flat-square&labelColor=141814)7![Recherche](https://img.shields.io/badge/latence_recherche-20--95_ms-1c5c41?style=flat-square)8![Python](https://img.shields.io/badge/Python-3.12-3776ab?style=flat-square&logo=python&logoColor=white)9![Next.js](https://img.shields.io/badge/Next.js-15-000000?style=flat-square&logo=nextdotjs)10![OpenSearch](https://img.shields.io/badge/OpenSearch-2.17-005eb8?style=flat-square&logo=opensearch&logoColor=white)11![PostgreSQL](https://img.shields.io/badge/PostgreSQL-16-4169e1?style=flat-square&logo=postgresql&logoColor=white)12![Redis](https://img.shields.io/badge/Redis-7-dc382d?style=flat-square&logo=redis&logoColor=white)13![Tests](https://img.shields.io/badge/tests-57_passing-1c5c41?style=flat-square)14![par Groupe KA](https://img.shields.io/badge/par-Groupe_KA-b9cfee?style=flat-square&labelColor=141814)1516**Moteur de recherche web indépendant, Québec-first.** Son propre crawler, son propre17index, son propre ranking, son API et son application web publique. Pas un métamoteur :18aucune dépendance à Google, Bing ou Brave pour les résultats.1920> **Crawl en continu. Indexe immédiatement. Recherche immédiatement. Améliore en asynchrone.**2122En production : **https://www.trouve-ka.com** — un moteur de recherche par23[Groupe KA](https://www.groupe-ka.com).2425Author: Simon-Pierre Boucher — Contact: contact@spboucher.ai2627---2829## Aperçu3031| | |32|---|---|33| ![Accueil](docs/screenshots/accueil.png) | ![Résultats](docs/screenshots/resultats.png) |34| **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 |35| ![Galerie d'images](docs/screenshots/images.png) | ![État du moteur](docs/screenshots/statut.png) |36| **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 |3738## Métriques réelles (2026-08-13, jour 1 — ~6 h après le premier crawl)3940| Métrique | Valeur |41|---|---|42| Pages indexées et cherchables | **59 073** (+~35 000/heure) |43| Domaines québécois découverts | **7 898** (à partir de 64 seeds) |44| URLs découvertes en attente (frontier) | **433 999** |45| Pages fetchées / heure | **76 198** |46| Latence de recherche (mesurée en prod) | **20–95 ms** (cible p50 < 100 ms ✓) |47| Délai fetch → cherchable | **~2–4 secondes** |48| Nodes de crawl | **3** (M2M32 ×5 workers Docker, M2M32b, M2M32c) |49| Taille de l'index | ~14 Ko/document (791 Mo à 55 k docs) |50| Tests unitaires | 57 ✓ (canonicalisation, SSRF, robots, scoring Québec, pièges, fetcher, ranking) |5152*Chaque chiffre ci-dessus vient des vraies tables (`crawl_attempts`, `frontier_items`) et de53l'index réel — aucun compteur simulé (règle § fake du projet).*5455---5657## Ce que c'est5859```60Crawler → Frontier → Fetcher → Parser → Classification Québec61→ Déduplication → Indexer → Index → Ranking → API → Web App62```6364Le crawler découvre le web québécois à partir de seeds à forte autorité (gouvernement,65municipalités, universités, médias), suit les liens sortants, juge la pertinence66québécoise de chaque page (`page_quebec_score` **et** `domain_quebec_score`),67et indexe **immédiatement** : une page fetchée est cherchable en ~2-4 secondes,68pendant que le frontier continue de grandir. L'enrichissement (autorité, entités,69embeddings) arrive après, en asynchrone, sans jamais bloquer.7071Détails : [docs/architecture.md](docs/architecture.md) (diagrammes Mermaid) et72[docs/decisions.md](docs/decisions.md) (pourquoi OpenSearch, pourquoi Postgres73comme frontier, etc.).7475## Stack7677| Couche | Choix |78|---|---|79| Web | Next.js 15, TypeScript, React 19, Tailwind (composants style shadcn/ui) |80| API | FastAPI (Python 3.12) |81| Pipeline | Python 3.12 async — httpx, selectolax, Protego |82| BD relationnelle | PostgreSQL 16 (frontier, domaines, documents, graphe de liens, analytics) |83| Coordination | Redis 7 (politesse par hôte, pause, Redis Streams pour l'enrichissement) |84| Recherche | OpenSearch 2.17 (BM25 FR/EN, synonymes bilingues, function_score Québec-first) |85| Déploiement | Docker Compose sur m2m32 + ngrok (www.trouve-ka.com) |8687## Layout du monorepo8889```90apps/web         # moteur public + dashboard /admin (Next.js)91apps/api         # FastAPI (trouveka.api)92services/        # crawler, frontier, parser, classifier, indexer, ranking, scheduler, enrichment93packages/        # config, database, logging, queue, search-core, shared, types94infrastructure/  # docker/, migrations/, monitoring/, deployment/ (m2m32 + ngrok)95scripts/         # bootstrap-seeds/, start-crawler/, health-check/, eval/, check-headers.py96tests/           # unitaires : canonicalisation, SSRF, robots, scoring Québec, fetcher, ranking97```9899Le backend Python est un seul package namespace `trouveka.*` mappé sur ce layout100(voir `pyproject.toml`).101102## Démarrage rapide (dev)103104```bash105git clone && cd trouve-ka106cp .env.example .env107docker compose up -d postgres redis opensearch   # infra108uv venv --python 3.12 .venv && uv pip install -e ".[dev]" --python .venv/bin/python109pnpm install110111pnpm crawl:seed                                  # migrations + 60+ seeds québécoises112bash scripts/start-crawler/start.sh &            # le crawl démarre113.venv/bin/uvicorn trouveka.api.main:app --port 8080 &114pnpm dev                                         # → http://localhost:3000115# → des résultats apparaissent en quelques secondes116```117118Ports occupés sur la machine? Surcharger dans `.env` : `PG_PORT`, `REDIS_PORT`,119`SEARCH_PORT`, `API_PORT` (et les URLs correspondantes).120121Stack complet en containers : `docker compose up -d --build` (le service `migrate`122applique les migrations, `crawler-worker` se scale avec123`docker compose up -d --scale crawler-worker=3`).124125## Comment ça marche126127### Crawl et politesse128- **Identité assumée** : UA `Mozilla/5.0 (compatible; TrouveKABot/0.1; +https://www.trouve-ka.com/trouveka-bot)`,129  page publique [/trouveka-bot](https://www.trouve-ka.com/trouveka-bot), pas de stealth.130- **robots.txt** parsé avec Protego, cache 24 h en base; `noindex`/`nofollow`/`X-Robots-Tag` respectés.131- **Politesse par origine** : verrou Redis par hôte (défaut 2 s entre requêtes, `Crawl-delay` respecté),132  quel que soit le nombre de workers.133- **Sécurité** : garde SSRF (IP privées/loopback/métadonnées cloud bloquées, revalidée à chaque134  redirection), limites par réponse (3 Mo, 5 redirections, timeout 20 s), détection de pièges135  (session IDs, calendriers infinis, facettes explosives, pagination sans fin).136137### Indexation incrémentale138Le worker exécute fetch→parse→score→index **inline** : `refresh_interval: 1s` côté139OpenSearch → cherchable en secondes. Détection de changement par hash de contenu +140ETag/If-Modified-Since; recrawl adaptatif (inchangé → intervalle ×2, volatil → ÷2).141142### Détection Québec143Deux scores distincts (`page` et `domaine`) calculés à partir de : TLD (.qc.ca, .quebec),144gazetteer de toponymes (pondération réduite pour les ambigus type Laval/Hull), codes145postaux G/H/J, indicatifs (418/514/438/…), organisations connues (Hydro-Québec, RAMQ,146UQAM…), mentions structurées de la province (JSON-LD), langue française (indice, pas preuve).147Déterministe et gratuit — aucun LLM dans le chemin chaud (§12).148149### Ranking150`function_score` OpenSearch : BM25 bilingue (analyzers FR + EN, synonymes151thermopompe↔heat pump à la recherche) + scores Québec + autorité de domaine152(inlinks pondérés) + fraîcheur + boost de localité (« plombier Gatineau » → documents153avec preuve géographique `locations`). Chaque composant est optionnel; BM25 tient seul.154155Évaluation mesurable : `python3 scripts/eval/run-eval.py --api http://localhost:8080`156(dataset dans `scripts/eval/ranking-eval.yaml`).157158## API159160- `GET /api/search?q=&page=&limit=&language=&category=&quebec_only=&freshness=`161- `GET /api/status` — compteurs publics (pages, domaines, débit, état du crawler)162- `POST /api/submit {"url": …}` — soumettre un site québécois (soumission ≠ inclusion)163- `GET|POST /api/admin/*` — protégé par header `X-Admin-Token` : overview, flux live,164  pause/reprise, seeds, recrawl, blocage de domaine, inspection du frontier165166## Tests167168```bash169.venv/bin/python -m pytest tests/          # 54 tests : URLs, SSRF, pièges, Québec, frontier, parser, fetcher, ranking170python3 scripts/check-headers.py           # header auteur obligatoire dans chaque fichier source (CI)171bash scripts/health-check/check.sh         # santé du stack172```173174## Déploiement m2m32 + ngrok (www.trouve-ka.com)175176Prérequis sur m2m32 : colima + docker + docker-compose (brew), ngrok authentifié,177domaine `www.trouve-ka.com` réservé dans le compte ngrok.178179```bash180pnpm deploy:m2m32        # = bash infrastructure/deployment/deploy-m2m32.sh181```182183Le script : rsync du monorepo → `docker compose up -d --build` → migrations + seeds184(idempotent) → tunnel `ngrok http --url=www.trouve-ka.com 3000` → health-check.185Le web proxifie `/api/*` vers l'API interne : un seul port exposé, pas d'URL absolues186côté client, cookies/CORS sans surprise derrière le tunnel.187188Opérations courantes sur le node :189190```bash191ssh M2M32 'cd trouve-ka && docker compose logs -f crawler-worker'   # crawl en direct192ssh M2M32 'cd trouve-ka && docker compose up -d --scale crawler-worker=5'193```194195### Crawl distribué (M2M32b, M2M32c)196197Des workers satellites tournent en natif sur d'autres nodes du cluster et se198coordonnent **sans orchestrateur** via le frontier Postgres (`FOR UPDATE SKIP LOCKED`)199et les verrous de politesse Redis (tous nodes confondus : jamais plus d'une requête200par site par seconde). Communication par l'Ethernet LAN interne (`192.168.2.x`).201202```bash203# Sur un node satellite (uv + python 3.12, .env pointé sur le node central) :204cd ~/trouve-ka && pm2 start .venv/bin/python --name trouveka-crawler \205  --interpreter none -- -m trouveka.crawler.worker206```207208⚠️ macOS Sequoia bloque l'accès « réseau local » des processus launchd : lancer les209workers satellites via pm2/nohup depuis une session SSH, pas via LaunchAgent/Daemon.210211## Observabilité212213Logs JSON structurés par service, métriques réelles via `/api/status` et214`/api/admin/overview`, dashboard `/admin` (files, débits, distribution HTTP, latences215p50/p95, flux live). Voir [infrastructure/monitoring/README.md](infrastructure/monitoring/README.md).216217## Règles du dépôt218219- **Header auteur obligatoire** dans chaque fichier source (§0.1) — vérifié par220  `scripts/check-headers.py`.221- **Aucune donnée factice** : pas de compteurs simulés, pas de résultats hard-codés;222  les fixtures vivent dans `tests/` uniquement.223- Provenance préservée : URL originale, canonique, timestamp de crawl, domaine source.224  Trouve-KA renvoie vers les éditeurs originaux.225