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%
1# Trouve-KA — Cherche le Québec.23**Moteur de recherche web indépendant, Québec-first.** Son propre crawler, son propre4index, son propre ranking, son API et son application web publique. Pas un métamoteur :5aucune dépendance à Google, Bing ou Brave pour les résultats.67> **Crawl en continu. Indexe immédiatement. Recherche immédiatement. Améliore en asynchrone.**89En production : **https://www.trouve-ka.com** (node m2m32 du cluster MacLustr, tunnel ngrok).1011Author: Simon-Pierre Boucher — Contact: contact@spboucher.ai1213---1415## Ce que c'est1617```18Crawler → Frontier → Fetcher → Parser → Classification Québec19→ Déduplication → Indexer → Index → Ranking → API → Web App20```2122Le crawler découvre le web québécois à partir de seeds à forte autorité (gouvernement,23municipalités, universités, médias), suit les liens sortants, juge la pertinence24québécoise de chaque page (`page_quebec_score` **et** `domain_quebec_score`),25et indexe **immédiatement** : une page fetchée est cherchable en ~2-4 secondes,26pendant que le frontier continue de grandir. L'enrichissement (autorité, entités,27embeddings) arrive après, en asynchrone, sans jamais bloquer.2829Détails : [docs/architecture.md](docs/architecture.md) (diagrammes Mermaid) et30[docs/decisions.md](docs/decisions.md) (pourquoi OpenSearch, pourquoi Postgres31comme frontier, etc.).3233## Stack3435| Couche | Choix |36|---|---|37| Web | Next.js 15, TypeScript, React 19, Tailwind (composants style shadcn/ui) |38| API | FastAPI (Python 3.12) |39| Pipeline | Python 3.12 async — httpx, selectolax, Protego |40| BD relationnelle | PostgreSQL 16 (frontier, domaines, documents, graphe de liens, analytics) |41| Coordination | Redis 7 (politesse par hôte, pause, Redis Streams pour l'enrichissement) |42| Recherche | OpenSearch 2.17 (BM25 FR/EN, synonymes bilingues, function_score Québec-first) |43| Déploiement | Docker Compose sur m2m32 + ngrok (www.trouve-ka.com) |4445## Layout du monorepo4647```48apps/web # moteur public + dashboard /admin (Next.js)49apps/api # FastAPI (trouveka.api)50services/ # crawler, frontier, parser, classifier, indexer, ranking, scheduler, enrichment51packages/ # config, database, logging, queue, search-core, shared, types52infrastructure/ # docker/, migrations/, monitoring/, deployment/ (m2m32 + ngrok)53scripts/ # bootstrap-seeds/, start-crawler/, health-check/, eval/, check-headers.py54tests/ # unitaires : canonicalisation, SSRF, robots, scoring Québec, fetcher, ranking55```5657Le backend Python est un seul package namespace `trouveka.*` mappé sur ce layout58(voir `pyproject.toml`).5960## Démarrage rapide (dev)6162```bash63git clone … && cd trouve-ka64cp .env.example .env65docker compose up -d postgres redis opensearch # infra66uv venv --python 3.12 .venv && uv pip install -e ".[dev]" --python .venv/bin/python67pnpm install6869pnpm crawl:seed # migrations + 60+ seeds québécoises70bash scripts/start-crawler/start.sh & # le crawl démarre71.venv/bin/uvicorn trouveka.api.main:app --port 8080 &72pnpm dev # → http://localhost:300073# → des résultats apparaissent en quelques secondes74```7576Ports occupés sur la machine? Surcharger dans `.env` : `PG_PORT`, `REDIS_PORT`,77`SEARCH_PORT`, `API_PORT` (et les URLs correspondantes).7879Stack complet en containers : `docker compose up -d --build` (le service `migrate`80applique les migrations, `crawler-worker` se scale avec81`docker compose up -d --scale crawler-worker=3`).8283## Comment ça marche8485### Crawl et politesse86- **Identité assumée** : UA `Mozilla/5.0 (compatible; TrouveKABot/0.1; +https://www.trouve-ka.com/trouveka-bot)`,87 page publique [/trouveka-bot](https://www.trouve-ka.com/trouveka-bot), pas de stealth.88- **robots.txt** parsé avec Protego, cache 24 h en base; `noindex`/`nofollow`/`X-Robots-Tag` respectés.89- **Politesse par origine** : verrou Redis par hôte (défaut 2 s entre requêtes, `Crawl-delay` respecté),90 quel que soit le nombre de workers.91- **Sécurité** : garde SSRF (IP privées/loopback/métadonnées cloud bloquées, revalidée à chaque92 redirection), limites par réponse (3 Mo, 5 redirections, timeout 20 s), détection de pièges93 (session IDs, calendriers infinis, facettes explosives, pagination sans fin).9495### Indexation incrémentale96Le worker exécute fetch→parse→score→index **inline** : `refresh_interval: 1s` côté97OpenSearch → cherchable en secondes. Détection de changement par hash de contenu +98ETag/If-Modified-Since; recrawl adaptatif (inchangé → intervalle ×2, volatil → ÷2).99100### Détection Québec101Deux scores distincts (`page` et `domaine`) calculés à partir de : TLD (.qc.ca, .quebec),102gazetteer de toponymes (pondération réduite pour les ambigus type Laval/Hull), codes103postaux G/H/J, indicatifs (418/514/438/…), organisations connues (Hydro-Québec, RAMQ,104UQAM…), mentions structurées de la province (JSON-LD), langue française (indice, pas preuve).105Déterministe et gratuit — aucun LLM dans le chemin chaud (§12).106107### Ranking108`function_score` OpenSearch : BM25 bilingue (analyzers FR + EN, synonymes109thermopompe↔heat pump à la recherche) + scores Québec + autorité de domaine110(inlinks pondérés) + fraîcheur + boost de localité (« plombier Gatineau » → documents111avec preuve géographique `locations`). Chaque composant est optionnel; BM25 tient seul.112113Évaluation mesurable : `python3 scripts/eval/run-eval.py --api http://localhost:8080`114(dataset dans `scripts/eval/ranking-eval.yaml`).115116## API117118- `GET /api/search?q=&page=&limit=&language=&category=&quebec_only=&freshness=`119- `GET /api/status` — compteurs publics (pages, domaines, débit, état du crawler)120- `POST /api/submit {"url": …}` — soumettre un site québécois (soumission ≠ inclusion)121- `GET|POST /api/admin/*` — protégé par header `X-Admin-Token` : overview, flux live,122 pause/reprise, seeds, recrawl, blocage de domaine, inspection du frontier123124## Tests125126```bash127.venv/bin/python -m pytest tests/ # 54 tests : URLs, SSRF, pièges, Québec, frontier, parser, fetcher, ranking128python3 scripts/check-headers.py # header auteur obligatoire dans chaque fichier source (CI)129bash scripts/health-check/check.sh # santé du stack130```131132## Déploiement m2m32 + ngrok (www.trouve-ka.com)133134Prérequis sur m2m32 : colima + docker + docker-compose (brew), ngrok authentifié,135domaine `www.trouve-ka.com` réservé dans le compte ngrok.136137```bash138pnpm deploy:m2m32 # = bash infrastructure/deployment/deploy-m2m32.sh139```140141Le script : rsync du monorepo → `docker compose up -d --build` → migrations + seeds142(idempotent) → tunnel `ngrok http --url=www.trouve-ka.com 3000` → health-check.143Le web proxifie `/api/*` vers l'API interne : un seul port exposé, pas d'URL absolues144côté client, cookies/CORS sans surprise derrière le tunnel.145146Opérations courantes sur le node :147148```bash149ssh M2M32 'cd trouve-ka && docker compose logs -f crawler-worker' # crawl en direct150ssh M2M32 'cd trouve-ka && docker compose up -d --scale crawler-worker=3'151```152153## Observabilité154155Logs JSON structurés par service, métriques réelles via `/api/status` et156`/api/admin/overview`, dashboard `/admin` (files, débits, distribution HTTP, latences157p50/p95, flux live). Voir [infrastructure/monitoring/README.md](infrastructure/monitoring/README.md).158159## Règles du dépôt160161- **Header auteur obligatoire** dans chaque fichier source (§0.1) — vérifié par162 `scripts/check-headers.py`.163- **Aucune donnée factice** : pas de compteurs simulés, pas de résultats hard-codés;164 les fixtures vivent dans `tests/` uniquement.165- Provenance préservée : URL originale, canonique, timestamp de crawl, domaine source.166 Trouve-KA renvoie vers les éditeurs originaux.167