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: refonte du README (pastilles + captures d écran à jour)

Simon-Pierre Boucher committed 1 mo ago (Aug 24, 2026) parent 1e29ebf

3 changed files +86 −144

modified README.md +86 −144
@@ -1,203 +1,145 @@
1 −# Trouve-KA — Cherche le Québec.
1 +# Trouve·Ka
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.
3 +**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.
4 4
5 −![Aperçu de Trouve-Ka](docs/screenshot.png)
6 −
7 −![Statut](https://img.shields.io/badge/statut-en_production-1c5c41?style=flat-square)
8 −![Site](https://img.shields.io/badge/site-www.trouve--ka.com-141814?style=flat-square)
9 −![Python](https://img.shields.io/badge/Python-3.12-3776ab?style=flat-square&logo=python&logoColor=white)
5 +[![Site](https://img.shields.io/website?url=https%3A%2F%2Fwww.trouve-ka.com&style=flat-square&label=www.trouve-ka.com)](https://www.trouve-ka.com)
6 +![Nœud](https://img.shields.io/badge/n%C5%93ud-M2M32-1f6feb?style=flat-square)
7 +![Port](https://img.shields.io/badge/port-3000-141814?style=flat-square)
8 +![PM2](https://img.shields.io/badge/process-PM2-2b037a?style=flat-square)
9 +![Docker](https://img.shields.io/badge/Docker-Postgres%20%2B%20Redis-2496ED?style=flat-square&logo=docker)
10 10 ![Next.js](https://img.shields.io/badge/Next.js-15-000000?style=flat-square&logo=nextdotjs)
11 +![Python](https://img.shields.io/badge/Python-3.12%20%C2%B7%20FastAPI-3776ab?style=flat-square&logo=python&logoColor=white)
11 12 ![OpenSearch](https://img.shields.io/badge/OpenSearch-2.17-005eb8?style=flat-square&logo=opensearch&logoColor=white)
12 −![PostgreSQL](https://img.shields.io/badge/PostgreSQL-16-4169e1?style=flat-square&logo=postgresql&logoColor=white)
13 −![Redis](https://img.shields.io/badge/Redis-7-dc382d?style=flat-square&logo=redis&logoColor=white)
14 −![par Groupe KA](https://img.shields.io/badge/par-Groupe_KA-b9cfee?style=flat-square&labelColor=141814)
15 −
16 −---
17 −
18 −## Description
13 +![Groupe KA](https://img.shields.io/badge/Groupe-KA-b7f000?style=flat-square)
19 14
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.
15 +**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.
21 16
22 17 > **Crawl en continu. Indexe immédiatement. Recherche immédiatement. Améliore en asynchrone.**
23 18
24 −En production : **https://www.trouve-ka.com** — un moteur de recherche par [**Groupe KA**](https://www.groupe-ka.com).
25 −
26 19 ```
27 20 Crawler → Frontier → Fetcher → Parser → Classification Québec
28 21 → Déduplication → Indexer → Index → Ranking → API → Web App
29 22 ```
30 23
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.).
24 +En production : **https://www.trouve-ka.com** — détails techniques dans [docs/architecture.md](docs/architecture.md) (diagrammes Mermaid) et [docs/decisions.md](docs/decisions.md).
32 25
33 −**Auteur** : Simon-Pierre Boucher — **Contact** : contact@spboucher.ai
26 +## Captures d'écran
34 27
35 −---
28 +<p align="center">
29 + <img src="docs/screenshots/trouve-ka-desktop.png" width="640" alt="Accueil — desktop">
30 + <img src="docs/screenshots/trouve-ka-mobile.png" width="200" alt="Accueil — mobile">
31 +</p>
36 32
37 33 ## Fonctionnalités
38 34
39 35 - **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).
36 +- **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).
37 +- **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.
38 +- **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).
39 +- **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).
40 +- **Onglet Images** : galerie des pages avec image représentative (hotlink + attribution, jamais de crawl d'images).
41 +- **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.
42 +- **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.
43 +- **SSO KA ID** + favoris « Mon univers Ka », design Groupe KA et widget **KA Agent** (bulle de chat IA).
48 44
49 −---
45 +## Architecture
50 46
51 −## Architecture (multi-services)
47 +En production sur **M2M32**, l'application tourne en processus natifs sous **PM2**, avec l'infrastructure de données en **Docker** :
52 48
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 |
49 +| Processus | Rôle |
65 50 |---|---|
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 |
68 −
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.
51 +| **tk-web** | Application web publique **Next.js 15** (port **3000**) — proxifie `/api/*` vers l'API interne ; un seul port exposé |
52 +| **tk-api** | API **FastAPI** (`trouveka.api`) — recherche, statut, soumission d'URL, endpoints admin protégés (`X-Admin-Token`) |
53 +| **tk-crawler-1** / **tk-crawler-2** | Workers de crawl : fetch → parse → score Québec → indexation inline, politesse par hôte, garde SSRF |
54 +| **tk-enrichment** | Enrichissement asynchrone (autorité de domaine, entités, embeddings) via **Redis Streams** — ne bloque jamais l'indexation |
55 +| **tk-scheduler** | Planification des recrawls adaptatifs et entretien du frontier |
56 +| **postgres** (Docker, `postgres:16-alpine`) | Frontier, domaines, documents, graphe de liens, analytics |
57 +| **redis** (Docker, `redis:7-alpine`) | Verrous de politesse par hôte, pause du crawl, Redis Streams pour l'enrichissement |
58 +| **com.trouveka.ngrok** (launchd) | Tunnel `ngrok http --url=www.trouve-ka.com 3000` — exposition publique du port 3000 |
70 59
71 −---
72 −
73 −## Stack
60 +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.
74 61
75 −| Couche | Choix |
76 −|---|---|
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**) |
84 −
85 −---
86 −
87 −## Structure du monorepo
62 +## Structure du repo
88 63
89 64 ```
90 −apps/web # moteur public + dashboard /admin (Next.js)
91 −apps/api # FastAPI (trouveka.api)
65 +apps/ # web (Next.js 15, moteur public + dashboard /admin) et api (FastAPI)
92 66 services/ # crawler, frontier, parser, classifier, indexer, ranking,
93 67 # scheduler, enrichment, embedding
94 68 packages/ # config, database, logging, queue, search-core, shared, types
95 69 infrastructure/ # docker/, migrations/, monitoring/, deployment/
96 −scripts/ # bootstrap-seeds/, start-crawler/, health-check/, eval/, check-headers.py
97 −tests/ # unitaires : canonicalisation, SSRF, robots, scoring Québec, fetcher, ranking
98 −docs/ # architecture.md, decisions.md, screenshot.png
70 +scripts/ # bootstrap-seeds, start-crawler, health-check, eval, ops,
71 + # ka-sitemaps, update-synonyms, check-headers.py…
72 +tests/ # unitaires : canonicalisation, SSRF, robots, scoring Québec, ranking…
73 +docs/ # architecture.md, decisions.md, RUNBOOK.md, screenshots/
99 74 ```
100 75
101 −Le backend Python est un **seul package namespace `trouveka.*`** mappé sur ce layout (voir `pyproject.toml`).
76 +Le backend Python est un seul package namespace `trouveka.*` mappé sur ce layout (voir `pyproject.toml`) ; le frontend vit dans un workspace **pnpm**.
102 77
103 −---
104 −
105 −## Démarrage local (dev)
78 +## Développement (remote-first)
106 79
107 −```bash
108 −git clone … && cd trouve-ka
109 −cp .env.example .env
110 −docker compose up -d postgres redis opensearch # infra
111 −uv venv --python 3.12 .venv && uv pip install -e ".[dev]" --python .venv/bin/python
112 −pnpm install
80 +La **source de vérité est le repo git sur le nœud M2M32** (`~/trouve-ka`) — on n'édite **jamais** la copie laptop. Toute modification se fait sur le nœud via SSH : édition, build, `pm2 restart`, puis commit/push **sur le nœud**.
113 81
114 −pnpm crawl:seed # migrations + 60+ seeds québécoises
115 −bash scripts/start-crawler/start.sh & # le crawl démarre
116 −.venv/bin/uvicorn trouveka.api.main:app --port 8080 &
117 −pnpm dev # → http://localhost:3000
118 −# → des résultats apparaissent en quelques secondes
119 −```
82 +- **Remote `origin` = spbgit** (git perso, **https://git.spboucher.ai**) — bare repo sur M3U96a, accessible via l'alias SSH **`gitsrv`** configuré sur les nœuds. Pas GitHub.
83 +- Agent forwarding actif : le `git push` fonctionne pendant une session SSH depuis le laptop.
120 84
121 −Ports occupés sur la machine? Surcharger dans `.env` : `PG_PORT`, `REDIS_PORT`, `SEARCH_PORT`, `API_PORT` (et les URLs correspondantes).
85 +```bash
86 +ssh M2M32
87 +cd ~/trouve-ka
122 88
123 −### API
89 +# Backend Python (venv .venv, Python 3.12)
90 +.venv/bin/python -m pytest tests/ # tests unitaires
91 +python3 scripts/check-headers.py # header auteur obligatoire (CI)
124 92
125 −- `GET /api/search?q=&page=&limit=&language=&category=&quebec_only=&freshness=`
126 −- `GET /api/status` — compteurs publics (pages, domaines, débit, état du crawler)
127 −- `POST /api/submit {"url": …}` — soumettre un site québécois (soumission ≠ inclusion)
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
93 +# Frontend Next.js
94 +pnpm install
95 +pnpm build # build de apps/web
129 96
130 −### Tests
97 +# Appliquer un changement
98 +pm2 restart tk-web # ou tk-api, tk-crawler-1, tk-crawler-2,
99 + # tk-enrichment, tk-scheduler
131 100
132 −```bash
133 −.venv/bin/python -m pytest tests/ # URLs, SSRF, pièges, Québec, frontier, parser, fetcher, ranking
134 −python3 scripts/check-headers.py # header auteur obligatoire dans chaque fichier source (CI)
135 −bash scripts/health-check/check.sh # santé du stack
101 +git add <fichiers> && git commit -m "…" && git push origin main
136 102 ```
137 103
138 −Évaluation du ranking : `python3 scripts/eval/run-eval.py --api http://localhost:8080` (dataset dans `scripts/eval/ranking-eval.yaml`).
139 −
140 −---
104 +Dev local complet possible : `docker compose up -d postgres redis opensearch`, `pnpm crawl:seed` (migrations + seeds), puis `pnpm dev` → http://localhost:3000.
141 105
142 −## Déploiement (production)
106 +## Déploiement
143 107
144 108 - **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`).
109 +- **Web** : `tk-web` sur le port **3000**, exposé sur **https://www.trouve-ka.com**.
147 110 - **Processus** : **PM2** (`pm2 ls` → tk-web, tk-api, tk-crawler-1/2, tk-enrichment, tk-scheduler) — auto-restart, survie au reboot.
148 111 - **Données** : **PostgreSQL 16** + **Redis 7** en **Docker** (`docker compose up -d postgres redis`).
149 −
150 −Opérations courantes sur le nœud :
151 −
152 −```bash
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
159 −```
160 −
161 −### Crawl distribué (M2M32b, M2M32c)
162 −
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**).
164 −
165 −```bash
166 −# Sur un nœud satellite (uv + python 3.12, .env pointé sur le nœud central) :
167 −cd ~/trouve-ka && pm2 start .venv/bin/python --name trouveka-crawler \
168 − --interpreter none -- -m trouveka.crawler.worker
169 −```
170 −
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é
174 −
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).
176 −
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.
112 +- **Tunnel** : **ngrok** sous **launchd** — label `com.trouveka.ngrok` (`~/Library/LaunchAgents/com.trouveka.ngrok.plist`).
185 113
186 114 ```bash
187 115 ssh M2M32
188 116 cd ~/trouve-ka
189 −# … éditer, builder, pm2 restart …
190 −git add … && git commit -m "…" && git push origin main
117 +pm2 ls # état des services
118 +pm2 logs tk-crawler-1 # crawl en direct
119 +docker ps # postgres + redis
191 120 ```
192 121
193 −---
122 +Runbook et observabilité : [docs/RUNBOOK.md](docs/RUNBOOK.md), dashboard `/admin` (files, débits, latences p50/p95, flux live) et [infrastructure/monitoring/](infrastructure/monitoring/).
194 123
195 −## Règles du dépôt
124 +## Écosystème Groupe KA
196 125
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.**
126 +| Plateforme | Vocation |
127 +|---|---|
128 +| [groupe-ka.com](https://www.groupe-ka.com) | Portail du Groupe KA |
129 +| [lou-ka.com](https://www.lou-ka.com) | Logements à louer |
130 +| [immo-ka.com](https://www.immo-ka.com) | Propriétés à vendre |
131 +| [vrai-prix.com](https://www.vrai-prix.com) | Estimation immobilière |
132 +| [auto-ka.com](https://www.auto-ka.com) | Véhicules |
133 +| [fabri-ka.com](https://www.fabri-ka.com) | Produits québécois |
134 +| [food-ka.com](https://www.food-ka.com) | Épicerie et alimentation |
135 +| [resto-ka.com](https://www.resto-ka.com) | Restaurants |
136 +| [sorti-ka.com](https://www.sorti-ka.com) | Sorties et événements |
137 +| [job-ka.com](https://www.job-ka.com) | Emplois |
138 +| [crea-ka.com](https://www.crea-ka.com) | Créateurs |
139 +| [trouve-ka.com](https://www.trouve-ka.com) | Petites annonces et recherche — **ce repo** |
140 +| [api-ka.com](https://www.api-ka.com) | API de données |
200 141
201 142 ---
202 143
203 −Un service **Groupe Ka** — [www.groupe-ka.com](https://www.groupe-ka.com)
144 +© Groupe KA — Simon-Pierre Boucher · contact@spboucher.ai
145 +Ce repo vit sur **spbgit** (git.spboucher.ai), pas sur GitHub.
added docs/screenshots/trouve-ka-desktop.png +0 −0

Binary file not shown.

added docs/screenshots/trouve-ka-mobile.png +0 −0

Binary file not shown.