SPB Git forge

spb/trouve-ka

Public ★ Pinned

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

40commits 1branches 0releases
9.2 MBsize
maindefault branch
23 days agolast push
Python 51.9% TypeScript 32.7% JavaScript 4.8% CSS 4.1% Shell 2.9% HTML 1.8% SQL 1.5%

docs: README à jour avec screenshot

Simon-Pierre Boucher committed 1 mo ago (Aug 18, 2026) parent 1676a98

2 changed files +113 −134

modified README.md +113 −134
@@ -1,105 +1,108 @@
1 1 # Trouve-KA — Cherche le Québec.
2 2
3 +**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.
4 +
5 +![Aperçu de Trouve-Ka](docs/screenshot.png)
6 +
3 7 ![Statut](https://img.shields.io/badge/statut-en_production-1c5c41?style=flat-square)
4 8 ![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 9 ![Python](https://img.shields.io/badge/Python-3.12-3776ab?style=flat-square&logo=python&logoColor=white)
9 10 ![Next.js](https://img.shields.io/badge/Next.js-15-000000?style=flat-square&logo=nextdotjs)
10 11 ![OpenSearch](https://img.shields.io/badge/OpenSearch-2.17-005eb8?style=flat-square&logo=opensearch&logoColor=white)
11 12 ![PostgreSQL](https://img.shields.io/badge/PostgreSQL-16-4169e1?style=flat-square&logo=postgresql&logoColor=white)
12 13 ![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 14 ![par Groupe KA](https://img.shields.io/badge/par-Groupe_KA-b9cfee?style=flat-square&labelColor=141814)
15 15
16 −**Moteur de recherche web indépendant, Québec-first.** Son propre crawler, son propre
17 −index, son propre ranking, son API et son application web publique. Pas un métamoteur :
18 −aucune dépendance à Google, Bing ou Brave pour les résultats.
16 +---
17 +
18 +## Description
19 +
20 +**Trouve-KA** est un **moteur de recherche québécois indépendant**. Ce n'est **pas un métamoteur** : **aucune dépendance à Google, Bing ou Brave** pour les résultats. 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.
19 21
20 22 > **Crawl en continu. Indexe immédiatement. Recherche immédiatement. Améliore en asynchrone.**
21 23
22 −En production : **https://www.trouve-ka.com** — un moteur de recherche par
23 −[Groupe KA](https://www.groupe-ka.com).
24 +En production : **https://www.trouve-ka.com** — un moteur de recherche par [**Groupe KA**](https://www.groupe-ka.com).
24 25
25 −Author: Simon-Pierre Boucher — Contact: contact@spboucher.ai
26 +```
27 +Crawler → Frontier → Fetcher → Parser → Classification Québec
28 +→ Déduplication → Indexer → Index → Ranking → API → Web App
29 +```
26 30
27 −---
31 +Détails : [docs/architecture.md](docs/architecture.md) (diagrammes Mermaid) et [docs/decisions.md](docs/decisions.md) (pourquoi OpenSearch, pourquoi Postgres comme frontier, etc.).
28 32
29 −## Aperçu
33 +**Auteur** : Simon-Pierre Boucher — **Contact** : contact@spboucher.ai
30 34
31 −| | |
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 |
35 +---
37 36
38 −## Métriques réelles (2026-08-13, jour 1 — ~6 h après le premier crawl)
37 +## Fonctionnalités
39 38
40 −| 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) |
51 −
52 −*Chaque chiffre ci-dessus vient des vraies tables (`crawl_attempts`, `frontier_items`) et de
53 −l'index réel — aucun compteur simulé (règle § fake du projet).*
39 +- **Recherche Québec-first** : **59 000+ pages indexées**, **7 900+ domaines québécois** découverts à partir de 64 seeds, **latence de recherche 20–95 ms** (mesurée en prod).
40 +- **Indexation incrémentale** : 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).
41 +- **Détection Québec déterministe** : TLD (**.qc.ca**, **.quebec**), gazetteer de toponymes, codes postaux **G/H/J**, indicatifs (**418/514/438/…**), organisations connues (Hydro-Québec, RAMQ, UQAM…), JSON-LD, langue française — **aucun LLM dans le chemin chaud**.
42 +- **Ranking bilingue** : `function_score` OpenSearch — **BM25 FR/EN** avec synonymes bilingues (thermopompe ↔ heat pump) + scores Québec + **autorité de domaine** (inlinks pondérés) + fraîcheur + **boost de localité** (« plombier Gatineau » → documents avec preuve géographique).
43 +- **Crawler poli et assumé** : UA identifié `TrouveKABot`, page publique [/trouveka-bot](https://www.trouve-ka.com/trouveka-bot), **robots.txt respecté** (Protego, cache 24 h), **verrou de politesse Redis par hôte** (défaut 2 s, `Crawl-delay` honoré), **garde SSRF** (IP privées/loopback/métadonnées cloud bloquées, revalidée à chaque redirection), détection de **pièges de crawl** (session IDs, calendriers infinis, facettes explosives).
44 +- **Onglet Images** : galerie des pages avec image représentative (**hotlink + attribution**, jamais de crawl d'images).
45 +- **Métriques publiques réelles** : `/status` et `/stats` (tableau de bord + rapport PDF Groupe-KA) — **aucun compteur simulé** (règle du projet).
46 +- **Crawl distribué multi-nœuds** : workers satellites sur **M2M32b** et **M2M32c**, coordonnés **sans orchestrateur** via le frontier Postgres (`FOR UPDATE SKIP LOCKED`) et les verrous Redis, communication par le **LAN interne 192.168.2.x**.
47 +- **SSO KA ID** + design Groupe KA (éditorial sharp, accent azur) + widget **KA Agent** (bulle de chat IA).
54 48
55 49 ---
56 50
57 −## Ce que c'est
51 +## Architecture (multi-services)
58 52
59 −```
60 −Crawler → Frontier → Fetcher → Parser → Classification Québec
61 −→ Déduplication → Indexer → Index → Ranking → API → Web App
62 −```
53 +En production sur le nœud **M2M32**, l'application tourne en **processus natifs sous PM2**, avec l'infrastructure de données en **Docker** :
54 +
55 +| Service PM2 | Rôle |
56 +|---|---|
57 +| **tk-web** | Application web publique **Next.js 15** (**port 3000**) — proxifie `/api/*` vers l'API interne |
58 +| **tk-api** | API **FastAPI** (`trouveka.api`) — recherche, statut, soumission, admin |
59 +| **tk-crawler-1** / **tk-crawler-2** | Workers de crawl (fetch → parse → score → index inline) |
60 +| **tk-enrichment** | Enrichissement asynchrone (autorité, entités, embeddings) via **Redis Streams** |
61 +| **tk-scheduler** | Planification des recrawls, entretien du frontier |
62 +| tk-tun-search / tk-tun-embed | Tunnels vers l'index de recherche et le service d'embeddings |
63 +
64 +| Conteneur Docker | Rôle |
65 +|---|---|
66 +| **PostgreSQL 16** (`postgres:16-alpine`, port 5432) | Frontier, domaines, documents, graphe de liens, analytics |
67 +| **Redis 7** (`redis:7-alpine`, port 6379) | Politesse par hôte, pause, **Redis Streams** pour l'enrichissement |
63 68
64 −Le crawler découvre le web québécois à partir de seeds à forte autorité (gouvernement,
65 −municipalités, universités, médias), suit les liens sortants, juge la pertinence
66 −québécoise de chaque page (`page_quebec_score` **et** `domain_quebec_score`),
67 −et indexe **immédiatement** : une page fetchée est cherchable en ~2-4 secondes,
68 −pendant que le frontier continue de grandir. L'enrichissement (autorité, entités,
69 −embeddings) arrive après, en asynchrone, sans jamais bloquer.
69 +L'exposition publique se fait par **ngrok** sous **launchd** (`com.trouveka.ngrok`) : `ngrok http --url=www.trouve-ka.com 3000`. **Un seul port exposé** — pas d'URL absolues côté client, cookies/CORS sans surprise derrière le tunnel.
70 70
71 −Détails : [docs/architecture.md](docs/architecture.md) (diagrammes Mermaid) et
72 −[docs/decisions.md](docs/decisions.md) (pourquoi OpenSearch, pourquoi Postgres
73 −comme frontier, etc.).
71 +---
74 72
75 73 ## Stack
76 74
77 75 | Couche | Choix |
78 76 |---|---|
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) |
77 +| **Web** | **Next.js 15**, TypeScript, React 19, Tailwind (composants style shadcn/ui) |
78 +| **API** | **FastAPI** (Python 3.12) |
79 +| **Pipeline** | **Python 3.12 async** — httpx, selectolax, Protego |
80 +| **BD relationnelle** | **PostgreSQL 16** (frontier, domaines, documents, graphe de liens, analytics) |
81 +| **Coordination** | **Redis 7** (politesse par hôte, pause, Redis Streams) |
82 +| **Recherche** | **OpenSearch 2.17** (BM25 FR/EN, synonymes bilingues, `function_score` Québec-first) |
83 +| **Déploiement** | **PM2 + Docker (postgres, redis) sur M2M32** + ngrok launchd (**www.trouve-ka.com**) |
86 84
87 −## Layout du monorepo
85 +---
86 +
87 +## Structure du monorepo
88 88
89 89 ```
90 90 apps/web # moteur public + dashboard /admin (Next.js)
91 91 apps/api # FastAPI (trouveka.api)
92 −services/ # crawler, frontier, parser, classifier, indexer, ranking, scheduler, enrichment
92 +services/ # crawler, frontier, parser, classifier, indexer, ranking,
93 + # scheduler, enrichment, embedding
93 94 packages/ # config, database, logging, queue, search-core, shared, types
94 −infrastructure/ # docker/, migrations/, monitoring/, deployment/ (m2m32 + ngrok)
95 +infrastructure/ # docker/, migrations/, monitoring/, deployment/
95 96 scripts/ # bootstrap-seeds/, start-crawler/, health-check/, eval/, check-headers.py
96 97 tests/ # unitaires : canonicalisation, SSRF, robots, scoring Québec, fetcher, ranking
98 +docs/ # architecture.md, decisions.md, screenshot.png
97 99 ```
98 100
99 −Le backend Python est un seul package namespace `trouveka.*` mappé sur ce layout
100 −(voir `pyproject.toml`).
101 +Le backend Python est un **seul package namespace `trouveka.*`** mappé sur ce layout (voir `pyproject.toml`).
102 +
103 +---
101 104
102 −## Démarrage rapide (dev)
105 +## Démarrage local (dev)
103 106
104 107 ```bash
105 108 git clone … && cd trouve-ka
@@ -115,110 +118,86 @@ pnpm dev # → http://localhost:3000
115 118 # → des résultats apparaissent en quelques secondes
116 119 ```
117 120
118 −Ports occupés sur la machine? Surcharger dans `.env` : `PG_PORT`, `REDIS_PORT`,
119 −`SEARCH_PORT`, `API_PORT` (et les URLs correspondantes).
120 −
121 −Stack complet en containers : `docker compose up -d --build` (le service `migrate`
122 −applique les migrations, `crawler-worker` se scale avec
123 −`docker compose up -d --scale crawler-worker=3`).
124 −
125 −## Comment ça marche
126 −
127 −### Crawl et politesse
128 −- **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 à chaque
134 − redirection), limites par réponse (3 Mo, 5 redirections, timeout 20 s), détection de pièges
135 − (session IDs, calendriers infinis, facettes explosives, pagination sans fin).
136 −
137 −### Indexation incrémentale
138 −Le worker exécute fetch→parse→score→index **inline** : `refresh_interval: 1s` côté
139 −OpenSearch → cherchable en secondes. Détection de changement par hash de contenu +
140 −ETag/If-Modified-Since; recrawl adaptatif (inchangé → intervalle ×2, volatil → ÷2).
141 −
142 −### Détection Québec
143 −Deux scores distincts (`page` et `domaine`) calculés à partir de : TLD (.qc.ca, .quebec),
144 −gazetteer de toponymes (pondération réduite pour les ambigus type Laval/Hull), codes
145 −postaux G/H/J, indicatifs (418/514/438/…), organisations connues (Hydro-Québec, RAMQ,
146 −UQAM…), mentions structurées de la province (JSON-LD), langue française (indice, pas preuve).
147 −Déterministe et gratuit — aucun LLM dans le chemin chaud (§12).
121 +Ports occupés sur la machine? Surcharger dans `.env` : `PG_PORT`, `REDIS_PORT`, `SEARCH_PORT`, `API_PORT` (et les URLs correspondantes).
148 122
149 −### Ranking
150 −`function_score` OpenSearch : BM25 bilingue (analyzers FR + EN, synonymes
151 −thermopompe↔heat pump à la recherche) + scores Québec + autorité de domaine
152 −(inlinks pondérés) + fraîcheur + boost de localité (« plombier Gatineau » → documents
153 −avec preuve géographique `locations`). Chaque composant est optionnel; BM25 tient seul.
154 −
155 −Évaluation mesurable : `python3 scripts/eval/run-eval.py --api http://localhost:8080`
156 −(dataset dans `scripts/eval/ranking-eval.yaml`).
157 −
158 −## API
123 +### API
159 124
160 125 - `GET /api/search?q=&page=&limit=&language=&category=&quebec_only=&freshness=`
161 126 - `GET /api/status` — compteurs publics (pages, domaines, débit, état du crawler)
162 127 - `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 frontier
128 +- `GET|POST /api/admin/*` — protégé par header `X-Admin-Token` : overview, flux live, pause/reprise, seeds, recrawl, blocage de domaine, inspection du frontier
165 129
166 −## Tests
130 +### Tests
167 131
168 132 ```bash
169 −.venv/bin/python -m pytest tests/ # 54 tests : URLs, SSRF, pièges, Québec, frontier, parser, fetcher, ranking
133 +.venv/bin/python -m pytest tests/ # URLs, SSRF, pièges, Québec, frontier, parser, fetcher, ranking
170 134 python3 scripts/check-headers.py # header auteur obligatoire dans chaque fichier source (CI)
171 135 bash scripts/health-check/check.sh # santé du stack
172 136 ```
173 137
174 −## Déploiement m2m32 + ngrok (www.trouve-ka.com)
138 +Évaluation du ranking : `python3 scripts/eval/run-eval.py --api http://localhost:8080` (dataset dans `scripts/eval/ranking-eval.yaml`).
175 139
176 −Prérequis sur m2m32 : colima + docker + docker-compose (brew), ngrok authentifié,
177 −domaine `www.trouve-ka.com` réservé dans le compte ngrok.
140 +---
178 141
179 −```bash
180 −pnpm deploy:m2m32 # = bash infrastructure/deployment/deploy-m2m32.sh
181 −```
142 +## Déploiement (production)
182 143
183 −Le script : rsync du monorepo → `docker compose up -d --build` → migrations + seeds
184 −(idempotent) → tunnel `ngrok http --url=www.trouve-ka.com 3000` → health-check.
185 −Le web proxifie `/api/*` vers l'API interne : un seul port exposé, pas d'URL absolues
186 −côté client, cookies/CORS sans surprise derrière le tunnel.
144 +- **Nœud** : **M2M32** (`~/trouve-ka`) — Mac Studio, 12 cœurs, 32 Go.
145 +- **Web** : **tk-web** sur le **port 3000**, exposé sur **https://www.trouve-ka.com**.
146 +- **Tunnel** : **ngrok** sous **launchd** — label **`com.trouveka.ngrok`** (`~/Library/LaunchAgents/com.trouveka.ngrok.plist`).
147 +- **Processus** : **PM2** (`pm2 ls` → tk-web, tk-api, tk-crawler-1/2, tk-enrichment, tk-scheduler) — auto-restart, survie au reboot.
148 +- **Données** : **PostgreSQL 16** + **Redis 7** en **Docker** (`docker compose up -d postgres redis`).
187 149
188 −Opérations courantes sur le node :
150 +Opérations courantes sur le nœud :
189 151
190 152 ```bash
191 −ssh M2M32 'cd trouve-ka && docker compose logs -f crawler-worker' # crawl en direct
192 −ssh M2M32 'cd trouve-ka && docker compose up -d --scale crawler-worker=5'
153 +ssh M2M32
154 +cd ~/trouve-ka
155 +pm2 ls # état des services
156 +pm2 logs tk-crawler-1 # crawl en direct
157 +pm2 restart tk-web # après un build web
158 +docker ps # postgres + redis
193 159 ```
194 160
195 161 ### Crawl distribué (M2M32b, M2M32c)
196 162
197 −Des workers satellites tournent en natif sur d'autres nodes du cluster et se
198 −coordonnent **sans orchestrateur** via le frontier Postgres (`FOR UPDATE SKIP LOCKED`)
199 −et les verrous de politesse Redis (tous nodes confondus : jamais plus d'une requête
200 −par site par seconde). Communication par l'Ethernet LAN interne (`192.168.2.x`).
163 +Des **workers satellites** tournent en natif sur d'autres nœuds du cluster et se coordonnent **sans orchestrateur** via le frontier Postgres (`FOR UPDATE SKIP LOCKED`) et les verrous de politesse Redis (tous nœuds confondus : **jamais plus d'une requête par site par seconde**). Communication par l'Ethernet LAN interne (**192.168.2.x**).
201 164
202 165 ```bash
203 −# Sur un node satellite (uv + python 3.12, .env pointé sur le node central) :
166 +# Sur un nœud satellite (uv + python 3.12, .env pointé sur le nœud central) :
204 167 cd ~/trouve-ka && pm2 start .venv/bin/python --name trouveka-crawler \
205 168 --interpreter none -- -m trouveka.crawler.worker
206 169 ```
207 170
208 −⚠️ macOS Sequoia bloque l'accès « réseau local » des processus launchd : lancer les
209 −workers satellites via pm2/nohup depuis une session SSH, pas via LaunchAgent/Daemon.
171 +⚠️ **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.
172 +
173 +### Observabilité
210 174
211 −## Observabilité
175 +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](infrastructure/monitoring/README.md).
212 176
213 −Logs JSON structurés par service, métriques réelles via `/api/status` et
214 −`/api/admin/overview`, dashboard `/admin` (files, débits, distribution HTTP, latences
215 −p50/p95, flux live). Voir [infrastructure/monitoring/README.md](infrastructure/monitoring/README.md).
177 +---
178 +
179 +## Développement remote-first (IMPORTANT)
180 +
181 +La **source de vérité est le repo git sur le nœud M2M32** (`~/trouve-ka`), **pas** une copie locale sur le laptop. Toute modification se fait **sur le nœud via SSH** : édition, build, `pm2 restart`, puis commit/push **sur le nœud**.
182 +
183 +- **Remote `origin` = spbgit** (git perso, **git.spboucher.ai**) — bare repo `~/srv/git/trouve-ka.git` sur M3U96a, accessible via l'**alias SSH `gitsrv`** configuré sur les nœuds. **Pas GitHub.**
184 +- **Agent forwarding actif** : le `git push` fonctionne pendant une session SSH depuis le laptop.
185 +
186 +```bash
187 +ssh M2M32
188 +cd ~/trouve-ka
189 +# … éditer, builder, pm2 restart …
190 +git add … && git commit -m "…" && git push origin main
191 +```
192 +
193 +---
216 194
217 195 ## Règles du dépôt
218 196
219 −- **Header auteur obligatoire** dans chaque fichier source (§0.1) — vérifié par
220 − `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.
197 +- **Header auteur obligatoire** dans chaque fichier source — vérifié par `scripts/check-headers.py`.
198 +- **Aucune donnée factice** : pas de compteurs simulés, pas de résultats hard-codés; les fixtures vivent dans `tests/` uniquement.
199 +- **Provenance préservée** : URL originale, canonique, timestamp de crawl, domaine source. **Trouve-KA renvoie vers les éditeurs originaux.**
200 +
201 +---
202 +
203 +Un service **Groupe Ka** — [www.groupe-ka.com](https://www.groupe-ka.com)
added docs/screenshot.png +0 −0

Binary file not shown.