docs: README v3 — chiffres live vérifiés, pastilles dynamiques, sections complètes
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 changed file +185 −67
modified
README.md
+185 −67
@@ -1,8 +1,8 @@ | ||
| 1 | 1 | <p align="center"> |
| 2 | − <a href="https://www.trouve-ka.com"><img src="https://www.trouve-ka.com/og.png" width="760" alt="Trouve·Ka — Le moteur de recherche du web québécois"></a> | |
| 2 | + <a href="https://www.trouve-ka.com"><img src="https://www.trouve-ka.com/og.png" width="760" alt="Trouve·Ka — Le moteur de recherche du Groupe KA"></a> | |
| 3 | 3 | </p> |
| 4 | 4 | <h1 align="center">Trouve·Ka</h1> |
| 5 | −<p align="center"><b>Le moteur de recherche du web québécois</b></p> | |
| 5 | +<p align="center"><b>Le moteur de recherche du Groupe KA — bâti au Québec, de zéro</b></p> | |
| 6 | 6 | |
| 7 | 7 | <div align="center"> |
| 8 | 8 | |
@@ -11,25 +11,56 @@ | ||
| 11 | 11 | [](https://www.trouve-ka.com/doc/trouve-ka-documentation.pdf) |
| 12 | 12 |  |
| 13 | 13 |  |
| 14 | − | |
| 15 | − | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | +[](https://www.trouve-ka.com/status) | |
| 18 | +[](https://www.trouve-ka.com/status) | |
| 19 | +[](https://www.trouve-ka.com/status) | |
| 20 | +[](https://www.trouve-ka.com/status) | |
| 21 | +[&color=555555&style=flat-square)](https://www.trouve-ka.com/status) | |
| 22 | +[](https://www.trouve-ka.com/status) | |
| 23 | + | |
| 24 | + | |
| 16 | 25 |  |
| 17 | − | |
| 26 | + | |
| 27 | + | |
| 18 | 28 |  |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 19 | 32 |  |
| 33 | + | |
| 20 | 34 | |
| 21 | 35 | </div> |
| 22 | 36 | |
| 23 | −**Trouve·Ka** est un **vrai moteur de recherche québécois** — 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 **64 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. C'est aussi **le moteur de recherche du Groupe KA** : les **14 sites de l'écosystème** (logements, propriétés, emplois, restos, événements, produits, véhicules, créateurs…) sont indexés en continu, avec **couverture sémantique de 100 %** de l'index. | |
| 37 | +> Les pastilles de la deuxième rangée sont **dynamiques** : elles interrogent [`/api/status`](https://www.trouve-ka.com/api/status) en direct — les chiffres que tu vois là-haut sont ceux du moteur en ce moment même. | |
| 38 | + | |
| 39 | +**Trouve·Ka** est un **vrai moteur de recherche** — pas un métamoteur : **aucune dépendance à Google, Bing ou Brave** pour les résultats. Crawler, frontier, parseur, classification, index, ranking, sémantique : tout est maison. Né « moteur du web québécois ouvert » (64 seeds à forte autorité — gouvernement, municipalités, universités, médias — 7 898 domaines découverts), il a **pivoté le 2026-08-23** pour devenir **le moteur de recherche du Groupe KA** : il indexe en continu les **14 sites de l'écosystème** (logements, propriétés, emplois, restos, événements, produits, véhicules, créateurs, estimations…), alimenté par leurs **sitemaps ingérés quotidiennement** — le seul frontier dépasse les **4,6 M d'URL** en attente. Chaque page fetchée est jugée (`page_quebec_score`, `domain_quebec_score`) et indexée **immédiatement** : **cherchable en ~2–4 secondes**. L'enrichissement (autorité, entités, embeddings, reranking) arrive après, en asynchrone, sans jamais bloquer — avec une **couverture sémantique de 100 %** de l'index. | |
| 24 | 40 | |
| 25 | 41 | > **Crawl en continu. Indexe immédiatement. Recherche immédiatement. Améliore en asynchrone.** |
| 26 | 42 | |
| 27 | 43 | ``` |
| 28 | −Crawler → Frontier → Fetcher → Parser → Classification Québec | |
| 29 | −→ Déduplication → Indexer → Index → Ranking → API → Web App | |
| 44 | +Sitemaps KA + Seeds → Frontier → Fetcher → Parser → Classification Québec | |
| 45 | +→ Déduplication → Indexer → Index → Ranking (+ sémantique, rerank) → API → Web App | |
| 30 | 46 | ``` |
| 31 | 47 | |
| 32 | −En production : **https://www.trouve-ka.com** — **177 000+ pages indexées** (181 369 constatées en direct sur [/status](https://www.trouve-ka.com/status) le 24 août 2026 — la croissance de l'index fait partie du produit). Détails techniques dans [docs/architecture.md](docs/architecture.md) (diagrammes Mermaid) et [docs/decisions.md](docs/decisions.md) ; guide utilisateur illustré sur [/doc](https://www.trouve-ka.com/doc/index.html). | |
| 48 | +En production : **https://www.trouve-ka.com** — **302 000+ pages indexées** (302 017 constatées sur [/api/status](https://www.trouve-ka.com/api/status) le 24 août 2026 à 14 h 47, contre 181 369 le matin même : **+120 000 pages dans la journée** — la croissance de l'index fait partie du produit, la pastille dynamique ci-dessus fait foi). Détails techniques dans [docs/architecture.md](docs/architecture.md) (diagrammes Mermaid) et [docs/decisions.md](docs/decisions.md) ; guide utilisateur illustré sur [/doc](https://www.trouve-ka.com/doc/index.html). | |
| 49 | + | |
| 50 | +## Chiffres live (instantané du 2026-08-24) | |
| 51 | + | |
| 52 | +| Métrique | Valeur constatée | Source | | |
| 53 | +|---|---|---| | |
| 54 | +| Pages indexées | **302 017** (et ça monte — voir pastille dynamique) | `/api/status` | | |
| 55 | +| Sites du Groupe KA indexés | **14** | `/api/status` | | |
| 56 | +| Pages indexées, dernière heure | **8 482** | `/api/status` | | |
| 57 | +| Pages fetchées, dernière heure | **13 287** | `/api/status` | | |
| 58 | +| Frontier (URL en attente) | **4 646 668** | `/api/status` | | |
| 59 | +| Couverture sémantique | **100 %** | `/api/status` | | |
| 60 | +| Pages crawlées — 30 jours | **357 209** | `/api/stats/dashboard` | | |
| 61 | +| Erreurs de crawl — 30 jours | **641** (~0,2 %) | `/api/stats/dashboard` | | |
| 62 | +| Latence de recherche (sémantique + rerank actifs) | **258–425 ms** sur 8 requêtes tests | `/api/search` (`took_ms`) | | |
| 63 | +| Workers de crawl | **9**, répartis sur **5 nœuds** | `pm2 ls` + satellites | | |
| 33 | 64 | |
| 34 | 65 | ## Visite guidée |
| 35 | 66 | |
@@ -41,12 +72,12 @@ Toutes les captures sont trackées dans le repo (`docs/screenshots/` et `apps/we | ||
| 41 | 72 | <td align="center"><img src="docs/screenshots/trouve-ka-mobile.png" width="240"><br><sub><b>Accueil mobile — même expérience, pastille live du compteur d'index</b></sub></td> |
| 42 | 73 | </tr> |
| 43 | 74 | <tr> |
| 44 | − <td align="center"><img src="apps/web/public/doc/img/etape2.png" width="420"><br><sub><b>Résultats « cabane à sucre » — 693 résultats en 269 ms, recherche sémantique active, filtres par univers KA / langue / fraîcheur</b></sub></td> | |
| 75 | + <td align="center"><img src="apps/web/public/doc/img/etape2.png" width="420"><br><sub><b>Résultats « cabane à sucre » — 693 résultats en 269 ms à la capture (1 131 résultats au 2026-08-24), recherche sémantique active, filtres par univers KA / langue / fraîcheur</b></sub></td> | |
| 45 | 76 | <td align="center"><img src="apps/web/public/doc/img/etape3.png" width="420"><br><sub><b>Onglet Images — 692 résultats en 74 ms, galerie hotlink avec attribution du domaine source</b></sub></td> |
| 46 | 77 | </tr> |
| 47 | 78 | <tr> |
| 48 | − <td align="center"><img src="apps/web/public/doc/img/etape4.png" width="420"><br><sub><b>État du moteur (/status) — 177 301 pages indexées, couverture sémantique 100 %, 14 sites du Groupe KA, 4,58 M d'URL au frontier</b></sub></td> | |
| 49 | − <td align="center"><img src="docs/screenshots/resultats.png" width="420"><br><sub><b>« plombier gatineau » — 4 278 résultats en 21 ms, badges Québec, filtres Tout / Images / Actualités / Gouvernement / FR / EN</b></sub></td> | |
| 79 | + <td align="center"><img src="apps/web/public/doc/img/etape4.png" width="420"><br><sub><b>État du moteur (/status) — 177 301 pages indexées à la capture (302 000+ au 2026-08-24), couverture sémantique 100 %, 14 sites du Groupe KA, 4,6 M d'URL au frontier</b></sub></td> | |
| 80 | + <td align="center"><img src="docs/screenshots/resultats.png" width="420"><br><sub><b>« plombier gatineau » — 4 278 résultats en 21 ms, badges Québec, filtres Tout / Images / Actualités / Gouvernement / FR / EN (phase « web québécois ouvert »)</b></sub></td> | |
| 50 | 81 | </tr> |
| 51 | 82 | <tr> |
| 52 | 83 | <td align="center"><img src="docs/screenshots/images.png" width="420"><br><sub><b>Onglet Images sur « montréal » — 1 554 résultats en 29 ms (phase crawl du web québécois ouvert)</b></sub></td> |
@@ -59,70 +90,136 @@ Toutes les captures sont trackées dans le repo (`docs/screenshots/` et `apps/we | ||
| 59 | 90 | |
| 60 | 91 | ## Fonctionnalités |
| 61 | 92 | |
| 62 | −- **Recherche Québec-first** : **177 000+ pages indexées** (181 369 constatées live), **7 900+ domaines québécois** découverts à partir de 64 seeds, **14 sites du Groupe KA** indexés en continu, **latence de recherche 20–95 ms** en lexical (mesurée en prod ; jusqu'à ~270 ms quand la réécriture sémantique s'active). | |
| 93 | +- **Moteur du Groupe KA** : **302 000+ pages indexées** (302 017 constatées live au 2026-08-24), **14 sites de l'écosystème** indexés en continu, alimentés par **ingestion quotidienne des sitemaps** (`tk-sitemaps` — vrai-prix déclare à lui seul ~3,75 M d'URL sur 84 chunks, traités en streaming) ; **4,6 M d'URL** au frontier. | |
| 63 | 94 | - **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). |
| 64 | −- **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. | |
| 95 | +- **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. Héritée de la phase « web ouvert », toujours active (`ka_only=False` dans la config restaure le comportement web québécois ouvert — le pivot est réversible). | |
| 65 | 96 | - **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). |
| 66 | −- **Recherche sémantique** : embeddings sur 100 % de l'index — « heat pump » trouve « thermopompe », pas seulement par mots-clés ; la couverture progresse en arrière-plan sans bloquer le lexical. | |
| 97 | +- **Recherche sémantique + reranking** : embeddings sur **100 % de l'index** — « heat pump » trouve « thermopompe », pas seulement par mots-clés — et un **reranker** réordonne les meilleurs résultats (service dédié sur le nœud m4mc). Latence mesurée au 2026-08-24 : **258–425 ms** avec sémantique + rerank actifs sur toutes les requêtes tests ; le chemin lexical pur descendait à **20–95 ms** (captures de la phase web ouvert). | |
| 67 | 98 | - **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). |
| 68 | 99 | - **Onglet Images** : galerie des pages avec image représentative (hotlink + attribution, jamais de crawl d'images). |
| 69 | −- **Métriques publiques réelles** : `/status` et `/stats` (tableau de bord + rapports PDF Groupe KA) — aucun compteur simulé, c'est une règle du projet. Les chiffres se rafraîchissent toutes les 10 secondes. | |
| 70 | −- **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. | |
| 71 | −- **SSO KA ID** + favoris « Mon univers Ka », design Groupe KA et widget **KA Agent** (bulle de chat IA). | |
| 100 | +- **Métriques publiques réelles** : `/status` et `/stats` (tableau de bord + rapports PDF Groupe KA personnalisables) — aucun compteur simulé, c'est une règle du projet. Les chiffres se rafraîchissent toutes les 10 secondes — et les pastilles de ce README interrogent la même API. | |
| 101 | +- **Crawl distribué multi-nœuds auto-guéri** : **9 workers sur 5 nœuds** (2 sur le hub M2M32, 3 sur M2M32c, 2 sur M4BP48, 1 sur M1M32, 1 sur m4ma), coordonnés **sans orchestrateur** via le frontier Postgres (`FOR UPDATE SKIP LOCKED`) et les verrous Redis. Tout le trafic inter-nœuds passe par des **tunnels SSH sur 127.0.0.1** (contournement du filtre « réseau local » de macOS 26, qui bloque les binaires non signés). Watchdog toutes les 2 min, passe de flotte ~30 min, survie au reboot prouvée (API tuée → ressuscitée en 40 s ; reboot complet de m4ma → tunnels + worker revenus seuls). | |
| 102 | +- **SSO KA ID** + favoris « Mon univers Ka », design Groupe KA, SEO complet (canonical, JSON-LD WebSite/SearchAction/Organization, sitemap) et widget **KA Agent** (bulle de chat IA déplaçable). | |
| 72 | 103 | |
| 73 | 104 | ## Pipeline complet |
| 74 | 105 | |
| 75 | 106 | Le chemin d'une page, de sa découverte à sa présence dans les résultats : |
| 76 | 107 | |
| 77 | −1. **Découverte** — le frontier (Postgres) part de 64 seeds à forte autorité et grandit avec chaque lien sortant découvert (4,5 M+ d'URL en attente en prod). Les workers se servent sans orchestrateur via `FOR UPDATE SKIP LOCKED`. | |
| 108 | +1. **Découverte** — le frontier (Postgres) est alimenté par les **14 seeds** (les pages d'accueil des sites KA, `scripts/bootstrap-seeds/seeds.txt`) et surtout par l'**ingestion quotidienne des sitemaps** des sites du Groupe KA (`scripts/ka-sitemaps/ingest.py`, process PM2 `tk-sitemaps`) : découverte via robots.txt (`Sitemap:`), suivi des index de sitemaps, traitement en streaming chunk par chunk, priorité aux pages peu profondes (accueil, villes, catégories) avant les fiches. 4,6 M+ d'URL en attente en prod. Les workers se servent sans orchestrateur via `FOR UPDATE SKIP LOCKED`. | |
| 78 | 109 | 2. **Fetch poli** — `TrouveKABot` prend un verrou de politesse Redis par hôte (2 s par défaut, `Crawl-delay` honoré), vérifie robots.txt (Protego, cache 24 h) et la garde SSRF, puis télécharge avec ETag/If-Modified-Since. |
| 79 | −3. **Parsing** — extraction du contenu principal, du titre, des métadonnées, du JSON-LD, des liens sortants et d'une image représentative. | |
| 110 | +3. **Parsing** — extraction du contenu principal, du titre, des métadonnées, du JSON-LD, des liens sortants et d'une image représentative (selectolax). | |
| 80 | 111 | 4. **Classification Québec** — calcul déterministe de `page_quebec_score` et `domain_quebec_score` (TLD, toponymes, codes postaux, indicatifs, organisations, langue) — aucun LLM dans le chemin chaud. |
| 81 | 112 | 5. **Déduplication + indexation inline** — hash de contenu, puis indexation immédiate dans **OpenSearch 2.17** (`refresh_interval: 1s`) : la page est **cherchable en ~2–4 s**. |
| 82 | 113 | 6. **Enrichissement asynchrone** — via **Redis Streams** : autorité de domaine (inlinks pondérés), entités, embeddings sémantiques. Ne bloque jamais l'indexation ; en panne, le lexical continue. |
| 83 | 114 | 7. **Recrawl adaptatif** — le scheduler recale l'intervalle de revisite : contenu inchangé → intervalle ×2, contenu volatil → ÷2. |
| 84 | −8. **Ranking** — `function_score` : BM25 FR/EN + synonymes bilingues + scores Québec + autorité + fraîcheur + boost de localité, avec réécriture sémantique quand elle aide. | |
| 115 | +8. **Ranking** — `function_score` : BM25 FR/EN + synonymes bilingues + scores Québec + autorité + fraîcheur + boost de localité, avec réécriture sémantique et **reranking** quand ils aident. | |
| 85 | 116 | |
| 86 | 117 | ## API |
| 87 | 118 | |
| 88 | −L'API FastAPI (`trouveka.api`) est servie derrière le proxy Next.js (`/api/*`) — un seul port exposé (3000). | |
| 119 | +L'API FastAPI (`trouveka.api`) est servie derrière le proxy Next.js (`/api/*`) — un seul port exposé (3000). Toutes les routes ci-dessous sont celles du code (`@app.get/post`), vérifiées au 2026-08-24 : | |
| 89 | 120 | |
| 90 | 121 | | Endpoint | Méthode | Rôle | |
| 91 | 122 | |---|---|---| |
| 92 | −| `/api/search` | GET | Recherche principale (requête, filtres univers/langue/fraîcheur, onglet images) | | |
| 93 | −| `/api/suggest` | GET | Suggestions de saisie | | |
| 94 | −| `/api/status` | GET | État du moteur en JSON : pages indexées, débits, frontier, erreurs, couverture sémantique | | |
| 95 | −| `/api/live` | GET | Flux « en ce moment » (page en cours de crawl) | | |
| 123 | +| `/api/search` | GET | Recherche principale — requête, filtres univers KA/langue/fraîcheur, onglet images ; renvoie `total`, `took_ms`, `semantic`, `reranked`, `related` | | |
| 124 | +| `/api/suggest` | GET | Suggestions de saisie (autocomplete) | | |
| 125 | +| `/api/status` | GET | État du moteur en JSON : `pages_indexed`, `domains_count`, `indexed_last_hour`, `fetched_last_hour`, `errors_last_hour`, `frontier_pending`, `frontier_in_progress`, `crawler_state`, `search_ok`, `embedding_coverage` | | |
| 126 | +| `/api/live` | GET | Flux « en ce moment » — la page en cours de crawl (url, domaine, issue) | | |
| 96 | 127 | | `/api/submit` | POST | Soumission publique d'une URL au crawl ([/soumettre](https://www.trouve-ka.com/soumettre)) | |
| 97 | −| `/api/stats/dashboard` · `/catalog` · `/report` | GET/POST | Tableau de bord `/stats` + rapports PDF Groupe KA | | |
| 98 | −| `/api/health` | GET | Sonde de santé | | |
| 99 | −| `/api/admin/*` (overview, recent, frontier, zero-results, pause, resume, recrawl, seeds, domains/block) | GET/POST | Endpoints d'exploitation, protégés par `X-Admin-Token` | | |
| 100 | −| `/embed` · `/rerank` | POST | Service d'embeddings interne (enrichissement sémantique) | | |
| 128 | +| `/api/health` | GET | Sonde de santé (`{"ok": true, "search_ok": true}`) | | |
| 129 | +| `/api/stats/dashboard` | GET | KPI, jauges, séries 30 jours du tableau de bord `/stats` | | |
| 130 | +| `/api/stats/catalog` | GET | Catalogue des blocs disponibles pour les rapports | | |
| 131 | +| `/api/stats/report` | GET | Rapport PDF Groupe KA standard | | |
| 132 | +| `/api/stats/report/custom` | POST | Rapport PDF personnalisé (ReportBuilder — blocs et rendus au choix) | | |
| 133 | +| `/api/admin/overview` · `/recent` · `/frontier` · `/zero-results` | GET | Exploitation : vue d'ensemble, derniers crawls, état du frontier, requêtes sans résultat — protégés par `X-Admin-Token` | | |
| 134 | +| `/api/admin/pause` · `/resume` · `/recrawl` · `/seeds` · `/domains/block` | POST | Exploitation : pause/reprise du crawl, recrawl forcé, ajout de seeds, blocage de domaine — protégés par `X-Admin-Token` | | |
| 135 | +| `/embed` · `/rerank` | POST | Service d'embeddings + reranker interne (nœud m4mc, `services/embedding`) — jamais exposé publiquement | | |
| 101 | 136 | |
| 102 | −**Latences observées en prod** : recherche lexicale **20–95 ms** (21 ms sur « plombier gatineau », 29 ms sur l'onglet Images), **~70–270 ms** quand la recherche sémantique s'active (269 ms sur « cabane à sucre »). | |
| 137 | +**Latences observées en prod (2026-08-24)** : **258–425 ms** avec recherche sémantique + reranking actifs (345 ms « poutine » · 4 395 résultats, 258 ms « resto québec » · 2 959 résultats, 425 ms « cabane à sucre » · 1 131 résultats). Le chemin lexical pur mesurait **20–95 ms** (21 ms « plombier gatineau », 29 ms onglet Images — captures de la phase web ouvert). | |
| 103 | 138 | |
| 104 | 139 | ## Architecture |
| 105 | 140 | |
| 106 | −En production sur **M2M32**, l'application tourne en processus natifs sous **PM2**, avec l'infrastructure de données en **Docker** : | |
| 141 | +Le moteur tourne en **architecture native distribuée sur 7 nœuds du cluster MacLustr**, coordonnée sans orchestrateur. Hub **M2M32** (Mac Studio, 12 cœurs, 32 Go) : processus natifs sous **PM2**, données en **Docker** ; l'index et la sémantique vivent sur des nœuds dédiés, reliés par tunnels SSH. | |
| 107 | 142 | |
| 108 | −| Processus | Rôle | | |
| 143 | +### Processus PM2 sur M2M32 (commandes réelles) | |
| 144 | + | |
| 145 | +| Processus | Commande | Rôle | | |
| 146 | +|---|---|---| | |
| 147 | +| **tk-web** | `pnpm start` (cwd `apps/web`) | Web public **Next.js 15** (port **3000**) — proxifie `/api/*` vers l'API interne ; un seul port exposé | | |
| 148 | +| **tk-api** | `.venv/bin/python -m uvicorn trouveka.api.main:app --host 0.0.0.0 --port 8080` | API **FastAPI** — recherche, statut, stats, soumission, admin (`X-Admin-Token`) | | |
| 149 | +| **tk-crawler-1** / **tk-crawler-2** | `.venv/bin/python -m trouveka.crawler.worker` | Workers de crawl du hub : fetch → parse → score → indexation inline | | |
| 150 | +| **tk-enrichment** | `.venv/bin/python -m trouveka.enrichment.worker` | Enrichissement asynchrone (autorité, entités, embeddings) via **Redis Streams** | | |
| 151 | +| **tk-scheduler** | `.venv/bin/python -m trouveka.scheduler.loop` | Recrawls adaptatifs, entretien du frontier, rétention des données | | |
| 152 | +| **tk-sitemaps** | `scripts/ka-sitemaps/ingest.py` | Ingestion quotidienne des sitemaps des 14 sites KA (streaming, priorités) — s'arrête entre deux passes | | |
| 153 | +| **tk-tun-search** | `ssh -N -L 127.0.0.1:9210:localhost:9200 …@192.168.2.77` | Tunnel vers OpenSearch (M2M32b) | | |
| 154 | +| **tk-tun-embed** | `ssh -N -L 127.0.0.1:8191:localhost:8091 …@192.168.2.75` | Tunnel vers embeddings + reranker (m4mc) | | |
| 155 | + | |
| 156 | +### Répartition sur le cluster | |
| 157 | + | |
| 158 | +| Nœud | Rôle | | |
| 109 | 159 | |---|---| |
| 110 | −| **tk-web** | Application web publique **Next.js 15** (port **3000**) — proxifie `/api/*` vers l'API interne ; un seul port exposé | | |
| 111 | −| **tk-api** | API **FastAPI** (`trouveka.api`) — recherche, statut, soumission d'URL, endpoints admin protégés (`X-Admin-Token`) | | |
| 112 | −| **tk-crawler-1** / **tk-crawler-2** | Workers de crawl : fetch → parse → score Québec → indexation inline, politesse par hôte, garde SSRF | | |
| 113 | −| **tk-enrichment** | Enrichissement asynchrone (autorité de domaine, entités, embeddings) via **Redis Streams** — ne bloque jamais l'indexation | | |
| 114 | −| **tk-scheduler** | Planification des recrawls adaptatifs et entretien du frontier | | |
| 115 | −| **postgres** (Docker, `postgres:16-alpine`) | Frontier, domaines, documents, graphe de liens, analytics | | |
| 116 | −| **redis** (Docker, `redis:7-alpine`) | Verrous de politesse par hôte, pause du crawl, Redis Streams pour l'enrichissement | | |
| 117 | −| **com.trouveka.ngrok** (launchd) | Tunnel `ngrok http --url=www.trouve-ka.com 3000` — exposition publique du port 3000 | | |
| 160 | +| **M2M32** (hub) | PM2 ci-dessus + **postgres:16-alpine** et **redis:7-alpine** en Docker (frontier, domaines, documents, graphe de liens, analytics ; verrous de politesse, pause, Redis Streams) + tunnel **ngrok** (launchd `com.trouveka.ngrok`, `ngrok http --url=www.trouve-ka.com 3000`) | | |
| 161 | +| **M2M32b** | **OpenSearch 2.17** en Docker (`trouveka-opensearch`, heap 8 G) — l'index, BM25 FR/EN + `function_score` Québec-first | | |
| 162 | +| **m4mc** | Service **embeddings + reranker** (`services/embedding`, venv dédié avec torch, port 8091) | | |
| 163 | +| **M2M32c** (×3), **M4BP48** (×2), **M1M32**, **m4ma** | Workers de crawl satellites (`trouveka.crawler.worker`) — se connectent à Postgres/Redis (192.168.2.90) et OpenSearch (192.168.2.77) par tunnels SSH bouclés (`trouveka-tunloop-*`) | | |
| 118 | 164 | |
| 119 | −La recherche s'appuie sur **OpenSearch 2.17** (BM25 FR/EN, synonymes bilingues, `function_score` Québec-first). Dégradation gracieuse partout : embeddings en panne → le lexical continue ; un worker crash → le frontier continue. | |
| 165 | +Postgres est réglé pour cette flotte (`max_connections=200`, keepalives TCP, `idle_in_transaction_session_timeout` — un satellite tué net ne tient plus de verrous zombies). **Auto-guérison** : `io.trouveka.boot` (launchd, tous les nœuds), `io.trouveka.watchdog` (M2M32, toutes les 2 min), passe de flotte ~30 min, **backups quotidiens croisés** Postgres + OpenSearch (03:30–04:40). Dégradation gracieuse partout : embeddings en panne → le lexical continue ; un worker crash → le frontier continue. | |
| 166 | + | |
| 167 | +## Démarrage rapide (dev local) | |
| 168 | + | |
| 169 | +```bash | |
| 170 | +# 1. Infrastructure de données (Postgres 16 + Redis 7 + OpenSearch 2.17.1) | |
| 171 | +docker compose up -d postgres redis opensearch | |
| 172 | + | |
| 173 | +# 2. Backend Python (>= 3.12, package namespace trouveka.*) | |
| 174 | +python3 -m venv .venv | |
| 175 | +.venv/bin/pip install -e ".[dev]" | |
| 176 | + | |
| 177 | +# 3. Migrations + seeds (idempotent — les URL connues sont ignorées) | |
| 178 | +pnpm crawl:seed # = scripts/bootstrap-seeds/seed.sh → trouveka.crawler.seed | |
| 179 | + | |
| 180 | +# 4. Frontend Next.js 15 (workspace pnpm) | |
| 181 | +pnpm install | |
| 182 | +pnpm dev # http://localhost:3000 | |
| 183 | + | |
| 184 | +# 5. Tests + garde-fous | |
| 185 | +.venv/bin/python -m pytest tests/ # 10 suites : fetcher, frontier, hybrid_search, | |
| 186 | + # parser(+encoding), quebec_scoring, ranking, | |
| 187 | + # ssrf, traps, urls | |
| 188 | +pnpm check:headers # header auteur obligatoire (CI) | |
| 189 | +``` | |
| 190 | + | |
| 191 | +Scripts pnpm du monorepo : `dev` · `build` · `start` (app web), `crawl:seed`, `check:headers`, `deploy:m2m32`. Côté ops : `scripts/ka-sitemaps/ingest.py` (alimentation par sitemaps), `scripts/health-check/`, `scripts/eval/` (qualité de recherche), `scripts/update-synonyms/`, `scripts/migrate-search-index/`, `scripts/cleanup-garbage.py`, `scripts/fix-mojibake/`. | |
| 192 | + | |
| 193 | +## Variables d'environnement | |
| 194 | + | |
| 195 | +Noms seulement — voir `.env.example` (jamais de secrets dans le repo) : | |
| 196 | + | |
| 197 | +| Groupe | Variables | | |
| 198 | +|---|---| | |
| 199 | +| Bases de données | `DATABASE_URL`, `REDIS_URL`, `SEARCH_URL`, `SEARCH_URL_BACKEND`, `PG_PORT`, `REDIS_PORT` | | |
| 200 | +| Crawler | `CRAWLER_USER_AGENT`, `CRAWLER_CONTACT_URL`, `MAX_GLOBAL_CONCURRENCY`, `MAX_PER_HOST_CONCURRENCY`, `DEFAULT_HOST_DELAY`, `MAX_RESPONSE_BYTES`, `MAX_REDIRECTS`, `FETCH_TIMEOUT`, `MAX_CRAWL_DEPTH`, `MAX_LINKS_PER_PAGE`, `MAX_URLS_PER_DOMAIN` | | |
| 201 | +| API | `API_HOST`, `API_PORT`, `ADMIN_TOKEN` | | |
| 202 | +| Web | `PUBLIC_URL`, `NGROK_DOMAIN`, `API_URL` | | |
| 203 | +| Index & sémantique | `SEARCH_INDEX`, `EMBEDDING_URL` | | |
| 204 | +| SSO Groupe KA | `KA_SSO_SECRET`, `KA_HUB_URL` | | |
| 205 | + | |
| 206 | +## Données & conformité | |
| 207 | + | |
| 208 | +- **Un robot qui se présente** : UA `TrouveKABot` avec URL de contact (`CRAWLER_CONTACT_URL`), page publique [/trouveka-bot](https://www.trouve-ka.com/trouveka-bot) qui explique qui il est, comment le bloquer ou l'inviter. | |
| 209 | +- **robots.txt d'abord** : parsé avec Protego, mis en cache 24 h, `Crawl-delay` honoré ; politesse par hôte via verrou Redis (2 s par défaut), plafonds de concurrence globaux et par hôte. | |
| 210 | +- **Provenance des données** : depuis le pivot du 2026-08-23, le moteur n'indexe que les **14 sites du Groupe KA** (`ka_only=True` dans `packages/config`) — du contenu de l'écosystème, découvert par seeds + sitemaps. La phase « web québécois ouvert » reste réactivable par configuration. | |
| 211 | +- **Fraîcheur sans gaspillage** : ETag/If-Modified-Since, hash de contenu, recrawl adaptatif (×2 si inchangé, ÷2 si volatil) — on ne re-télécharge pas ce qui n'a pas bougé. | |
| 212 | +- **Sécurité du fetch** : garde SSRF (IP privées, loopback, métadonnées cloud bloquées, revalidée à chaque redirection), taille de réponse et redirections plafonnées, détection de pièges de crawl. | |
| 213 | +- **Images** : jamais crawlées ni copiées — hotlink avec attribution du domaine source. | |
| 214 | +- **Rétention** : `crawl_attempts` 30 jours, `search_queries` 180 jours (purge quotidienne par le scheduler) ; backups quotidiens croisés Postgres + OpenSearch. | |
| 215 | +- **Métriques honnêtes** : aucun compteur simulé nulle part — `/status`, `/stats`, les pastilles de ce README et les rapports PDF lisent tous les mêmes données réelles. | |
| 216 | +- **Retrait** : blocage de domaine via `/api/admin/domains/block` — et `TrouveKABot` respecte tout `Disallow` qui le vise. | |
| 120 | 217 | |
| 121 | 218 | ## Documentation |
| 122 | 219 | |
| 123 | 220 | - **Guide utilisateur illustré** : [www.trouve-ka.com/doc](https://www.trouve-ka.com/doc/index.html) — la visite en 4 étapes (accueil, recherche, images, état du moteur + soumission de site). |
| 124 | 221 | - **Guide PDF téléchargeable** : [trouve-ka-documentation.pdf](https://www.trouve-ka.com/doc/trouve-ka-documentation.pdf). |
| 125 | −- **Architecture** : [docs/architecture.md](docs/architecture.md) (diagrammes Mermaid) · **Décisions** : [docs/decisions.md](docs/decisions.md) · **Runbook** : [docs/RUNBOOK.md](docs/RUNBOOK.md). | |
| 222 | +- **Architecture** : [docs/architecture.md](docs/architecture.md) (diagrammes Mermaid) · **Décisions** : [docs/decisions.md](docs/decisions.md) · **Runbook** : [docs/RUNBOOK.md](docs/RUNBOOK.md) (auto-guérison, backups, journaux, procédures). | |
| 126 | 223 | - **Le robot** : [/trouveka-bot](https://www.trouve-ka.com/trouveka-bot) — qui est `TrouveKABot`, comment le bloquer ou l'inviter. |
| 127 | 224 | |
| 128 | 225 | ## Structure du repo |
@@ -133,13 +230,15 @@ services/ # crawler, frontier, parser, classifier, indexer, ranking, | ||
| 133 | 230 | # scheduler, enrichment, embedding |
| 134 | 231 | packages/ # config, database, logging, queue, search-core, shared, types |
| 135 | 232 | infrastructure/ # docker/, migrations/, monitoring/, deployment/ |
| 136 | −scripts/ # bootstrap-seeds, start-crawler, health-check, eval, ops, | |
| 137 | − # ka-sitemaps, update-synonyms, check-headers.py… | |
| 138 | −tests/ # unitaires : canonicalisation, SSRF, robots, scoring Québec, ranking… | |
| 233 | +scripts/ # bootstrap-seeds, ka-sitemaps, start-crawler, health-check, eval, | |
| 234 | + # ops, update-synonyms, migrate-search-index, cleanup-garbage, | |
| 235 | + # fix-mojibake, check-headers.py | |
| 236 | +tests/ # 10 suites : canonicalisation d'URL, SSRF, robots/frontier, | |
| 237 | + # scoring Québec, ranking, recherche hybride, parseur, encodage, pièges | |
| 139 | 238 | docs/ # architecture.md, decisions.md, RUNBOOK.md, screenshots/ |
| 140 | 239 | ``` |
| 141 | 240 | |
| 142 | −Le backend Python est un seul package namespace `trouveka.*` mappé sur ce layout (voir `pyproject.toml`) ; le frontend vit dans un workspace **pnpm**. | |
| 241 | +Le backend Python est un seul package namespace `trouveka.*` mappé sur ce layout (voir `pyproject.toml` — FastAPI, httpx, selectolax, protego, asyncpg, redis, opensearch-py, pydantic v2) ; le frontend vit dans un workspace **pnpm**. | |
| 143 | 242 | |
| 144 | 243 | ## Développement (remote-first) |
| 145 | 244 | |
@@ -162,7 +261,7 @@ pnpm build # build de apps/web | ||
| 162 | 261 | |
| 163 | 262 | # Appliquer un changement |
| 164 | 263 | pm2 restart tk-web # ou tk-api, tk-crawler-1, tk-crawler-2, |
| 165 | − # tk-enrichment, tk-scheduler | |
| 264 | + # tk-enrichment, tk-scheduler, tk-sitemaps | |
| 166 | 265 | |
| 167 | 266 | git add <fichiers> && git commit -m "…" && git push origin main |
| 168 | 267 | ``` |
@@ -171,39 +270,58 @@ Dev local complet possible : `docker compose up -d postgres redis opensearch`, ` | ||
| 171 | 270 | |
| 172 | 271 | ## Déploiement |
| 173 | 272 | |
| 174 | −- **Nœud** : **M2M32** (`~/trouve-ka`) — Mac Studio, 12 cœurs, 32 Go. | |
| 273 | +- **Nœud** : **M2M32** (`~/trouve-ka`) — Mac Studio, 12 cœurs, 32 Go — plus M2M32b (index), m4mc (sémantique) et 4 satellites de crawl (voir Architecture). | |
| 175 | 274 | - **Web** : `tk-web` sur le port **3000**, exposé sur **https://www.trouve-ka.com**. |
| 176 | −- **Processus** : **PM2** (`pm2 ls` → tk-web, tk-api, tk-crawler-1/2, tk-enrichment, tk-scheduler) — auto-restart, survie au reboot. | |
| 177 | −- **Données** : **PostgreSQL 16** + **Redis 7** en **Docker** (`docker compose up -d postgres redis`). | |
| 275 | +- **Processus** : **PM2** (`pm2 ls` → tk-web, tk-api, tk-crawler-1/2, tk-enrichment, tk-scheduler, tk-sitemaps, tk-tun-search, tk-tun-embed) — auto-restart, survie au reboot. | |
| 276 | +- **Données** : **PostgreSQL 16** + **Redis 7** en **Docker** sur M2M32 (`docker compose up -d postgres redis`) ; **OpenSearch 2.17** en Docker sur **M2M32b**. | |
| 178 | 277 | - **Tunnel** : **ngrok** sous **launchd** — label `com.trouveka.ngrok` (`~/Library/LaunchAgents/com.trouveka.ngrok.plist`). |
| 278 | +- **Auto-guérison** : launchd `io.trouveka.boot` (tous les nœuds), `io.trouveka.watchdog` (2 min), backups `io.trouveka.backup-pg`/`backup-os` (quotidiens) — journaux `~/trouveka-watchdog.log`, `~/trouveka-boot.log`, `~/trouveka-backups/backup.log`. | |
| 179 | 279 | |
| 180 | 280 | ```bash |
| 181 | 281 | ssh M2M32 |
| 182 | 282 | cd ~/trouve-ka |
| 183 | 283 | pm2 ls # état des services |
| 184 | 284 | pm2 logs tk-crawler-1 # crawl en direct |
| 185 | −docker ps # postgres + redis | |
| 285 | +docker ps # postgres + redis (opensearch : docker ps sur M2M32b) | |
| 186 | 286 | ``` |
| 187 | 287 | |
| 188 | 288 | Runbook et observabilité : [docs/RUNBOOK.md](docs/RUNBOOK.md), dashboard `/admin` (files, débits, latences p50/p95, flux live) et [infrastructure/monitoring/](infrastructure/monitoring/). |
| 189 | 289 | |
| 190 | −## Écosystème Groupe KA | |
| 290 | +## Historique | |
| 291 | + | |
| 292 | +28 commits en 11 jours — de la première ligne au moteur distribué du Groupe KA : | |
| 191 | 293 | |
| 192 | −| Plateforme | Vocation | | |
| 294 | +| Date | Jalon | | |
| 193 | 295 | |---|---| |
| 194 | −| [groupe-ka.com](https://www.groupe-ka.com) | Portail du Groupe KA | | |
| 195 | −| [lou-ka.com](https://www.lou-ka.com) | Logements à louer | | |
| 196 | −| [immo-ka.com](https://www.immo-ka.com) | Propriétés à vendre | | |
| 197 | −| [vrai-prix.com](https://www.vrai-prix.com) | Estimation immobilière | | |
| 198 | −| [auto-ka.com](https://www.auto-ka.com) | Véhicules | | |
| 199 | −| [fabri-ka.com](https://www.fabri-ka.com) | Produits québécois | | |
| 200 | −| [food-ka.com](https://www.food-ka.com) | Épicerie et alimentation | | |
| 201 | −| [resto-ka.com](https://www.resto-ka.com) | Restaurants | | |
| 202 | −| [sorti-ka.com](https://www.sorti-ka.com) | Sorties et événements | | |
| 203 | −| [job-ka.com](https://www.job-ka.com) | Emplois | | |
| 204 | −| [crea-ka.com](https://www.crea-ka.com) | Créateurs | | |
| 205 | −| [trouve-ka.com](https://www.trouve-ka.com) | Petites annonces et recherche — **ce repo** | | |
| 206 | −| [api-ka.com](https://www.api-ka.com) | API de données | | |
| 296 | +| 2026-08-13 | **Naissance** (`5eedcb2`) — moteur de recherche Québec-first complet : crawler, index, ranking, API, web | | |
| 297 | +| 2026-08-16 | **Recherche hybride sémantique + architecture native multi-nœuds** (`708a9ba`) · auto-guérison, survie au reboot, backups croisés, rétention (`78ddae6`) | | |
| 298 | +| 2026-08-17 | **Design Groupe KA + SSO KA ID** (`bf69142`) · tableau de bord `/stats` + rapport PDF (`93cbd33`) · widget KA Agent (`1676a98`) | | |
| 299 | +| 2026-08-19 | **Mobile-first** (hamburger, safe-area, dvh — `37a4bf7`) · KA Agent v2 plein écran + Markdown streaming (`8bdbe5f`) | | |
| 300 | +| 2026-08-22 | **SEO complet** — canonical, JSON-LD WebSite/SearchAction, sitemap, fr-CA (`491f0f6`) · standard header/menu KA (`8678110`) | | |
| 301 | +| 2026-08-23 | **Pivot Groupe KA** (`42bf528`) — le moteur n'indexe plus que les 14 sites KA ; rebrand « le moteur de recherche du Groupe KA » (`fd0b68c`) · stats v3, rapports PDF personnalisés (`105720d`) · favoris « Mon univers Ka » (`1e29ebf`) | | |
| 302 | +| 2026-08-24 | **Documentation publique** `/doc` + guide PDF (`8880849`) · README v2 puis v3 — l'index passe de 181 k à 302 k pages dans la journée | | |
| 303 | + | |
| 304 | +## Écosystème Groupe KA | |
| 305 | + | |
| 306 | +Les 14 sites marqués ● sont ceux que le moteur indexe en continu (les seeds du frontier) : | |
| 307 | + | |
| 308 | +| Plateforme | Vocation | Indexé | | |
| 309 | +|---|---|---| | |
| 310 | +| [groupe-ka.com](https://www.groupe-ka.com) | Portail du Groupe KA | ● | | |
| 311 | +| [trouve-ka.com](https://www.trouve-ka.com) | **Moteur de recherche — ce repo** | ● | | |
| 312 | +| [lou-ka.com](https://www.lou-ka.com) | Logements à louer | ● | | |
| 313 | +| [immo-ka.com](https://www.immo-ka.com) | Propriétés à vendre | ● | | |
| 314 | +| [vrai-prix.com](https://www.vrai-prix.com) | Estimation immobilière | ● | | |
| 315 | +| [toit-ka.com](https://www.toit-ka.com) | Louer ou acheter — les annonces réunies | ● | | |
| 316 | +| [valoplex.com](https://www.valoplex.com) | Valeur immobilière, sans boîte noire | ● | | |
| 317 | +| [auto-ka.com](https://www.auto-ka.com) | Véhicules | ● | | |
| 318 | +| [fabri-ka.com](https://www.fabri-ka.com) | Produits québécois | ● | | |
| 319 | +| [food-ka.com](https://www.food-ka.com) | Épicerie et alimentation | ● | | |
| 320 | +| [resto-ka.com](https://www.resto-ka.com) | Restaurants | ● | | |
| 321 | +| [sorti-ka.com](https://www.sorti-ka.com) | Sorties et événements | ● | | |
| 322 | +| [job-ka.com](https://www.job-ka.com) | Emplois | ● | | |
| 323 | +| [crea-ka.com](https://www.crea-ka.com) | Créateurs | ● | | |
| 324 | +| [api-ka.com](https://www.api-ka.com) | API de données (technique, non indexée) | — | | |
| 207 | 325 | |
| 208 | 326 | ## Contact |
| 209 | 327 | |
| 210 | 328 | |