 Accueil — recherche par resto ou par plat (desktop) |
 Accueil — version mobile |
 Étape 1 · Cherchez un resto ou un plat depuis l'accueil |
 Étape 2 · Parcourez les restos d'une ville |
 Étape 3 · Ouvrez la fiche d'un restaurant |
 Étape 4 · Comparez le marché sur /stats |
 Accueil — variante pleine page |
 Fiche restaurant — menu structuré et prix contextualisés |
 Recherche par plat — 149 569 items, groupés par marque |
 Tableau de bord /stats — le marché resto en chiffres |
## Fonctionnalités
- **Prix réels et contextualisés** — un prix n'est jamais présenté sans son contexte (`dine-in` / `takeout` / `delivery`) ; un menu sans `price_context`/`price_source` est rejeté à l'ingestion, et un menu salle n'est jamais écrasé par un menu livraison.
- **Recherche par plat** dans les 149 569 items (au 2026-08-24), groupée par marque (pour ne pas répéter la même poutine de chaîne des dizaines de fois).
- **Options & formats capturés** avec leurs suppléments (« Mini +3,70 $ ») — ils changent le prix réel.
- **Historique des prix par item** (`item_price_log`, 155 312 entrées au 2026-08-24) à chaque changement.
- **Alerte de dérive** — chute anormale du volume d'une source ou du nombre d'items d'un menu → retraits suspendus / ancien menu conservé.
- **Déduplication inter-sources** (les succursales ne sont jamais fusionnées) et **géocodage à 100 %** (Nominatim + Adresses Québec, cache persistant).
- **Découverte OpenStreetMap** (API Overpass) : 13 427 établissements nommés au dernier passage — restos, fast-foods, cafés, bars, crèmeries, boulangeries — dans les 17 régions ; **connecteur UEAT templatisé** (API GraphQL) pour les menus takeout réels de **116 intégrations de chaînes découvertes** (Sushi Shop, Valentine, Normandin, Thaïzone, Chez Ashton…).
- **Découverte des sites web** des restos OSM (connecteur `site-finder`, Serper /maps) et **extraction des photos** depuis les sites des restos (`site-resto`).
- **Enrichissements officiels** : inspections MAPAQ (2 994 importées, 288 appariées à 204 restos) et permis d'alcool RACJ (1 859 restos).
- **Avis & notes** : Yelp par scraping quotidien (177 restos notés) et croisement Uber Eats (36 restos, prix marqués `delivery`).
- **Connexion KA ID** (SSO Groupe KA), favoris unifiés « Mon univers Ka », tableau de bord `/stats` + rapports PDF (catalogue + ReportBuilder), widget **KA Agent** (chat IA).
- **Registre des sources** — `data/sources.json` (10 sources), à consulter avant tout ajout (statut et raison des sources non connectables).
## Démarrage rapide
```bash
# Backend
python3 -m venv .venv && .venv/bin/pip install -r requirements.txt
# Frontend (build servi par FastAPI)
cd frontend && npm install && npm run build && cd ..
# ou en dev : cd frontend && npm run dev
# Lancer
.venv/bin/python run.py sync # synchronise (+ géocode + dédup)
.venv/bin/python run.py serve 8115 # API + frontend sur :8115
.venv/bin/python run.py watch 168 # boucle hebdomadaire (heures)
# Tests (80 tests pytest, fixtures réelles hors ligne)
.venv/bin/python -m pytest tests/
```
## API (endpoints)
Servis par `restoka/web.py` (FastAPI) sur le port 8115.
| Méthode | Endpoint | Description |
|---|---|---|
| `GET` | `/api/restaurants` | recherche de restos (ville, région, cuisine, texte libre, `uids=`, pagination) |
| `GET` | `/api/restaurants/{uid}` | fiche complète : menu structuré par contexte de prix, photos, coordonnées, horaires |
| `GET` | `/api/restaurants/{uid}/prices` | historique des prix des items du resto (`item_price_log`) |
| `GET` | `/api/restaurants/{uid}/inspections` | inspections/permis (enrichissements MAPAQ + RACJ) |
| `GET` | `/api/dishes` | recherche par plat dans les 149 569 items, groupée par marque |
| `GET` | `/api/facets` | facettes de filtres (régions, villes, cuisines, types) |
| `GET` | `/api/sources` | registre des sources (statuts, cadences) |
| `GET` | `/api/stats` | statistiques publiques JSON (alimente les pastilles ci-dessus) |
| `GET` | `/api/stats/dashboard` | données du tableau de bord `/stats` |
| `GET` | `/api/stats/report` | rapport PDF principal |
| `GET` | `/api/stats/catalog` | catalogue des rapports disponibles |
| `POST` | `/api/stats/report/custom` | rapport PDF personnalisé (ReportBuilder) |
| `GET` | `/api/favorites` | favoris « Mon univers Ka » (session KA ID) |
| `POST` | `/api/favorites/toggle` | ajout/retrait d'un favori |
| `POST` | `/api/sync` | déclenchement d'une synchronisation |
| `GET` | `/healthz` | santé du service (`{"status":"ok"}`) |
| `GET` | `/doc/` | guide d'utilisation en ligne |
| `GET` | `/{full_path}` | SPA + pages SSR légères (accueil, fiches JSON-LD, /villes) |
## Connecteurs
Auto-enregistrés dans `restoka/connectors/` (requests direct / Firecrawl / Scrapfly + cache détail dans `base.py`, résilience `_resilient.py`, chaîne anti-bot Oxylabs → Scrapfly → Bright Data) :
| Connecteur | Famille | Rôle | Cadence |
|---|---|---|---|
| `osm` | découverte | OpenStreetMap (Overpass, miroirs fiabilisés) — 13 427 établissements nommés, 17/17 régions | mensuelle |
| `ueat` | menus takeout (tier 1) | connecteur templatisé UEAT (API GraphQL) — menus takeout réels de 116 intégrations découvertes | hebdomadaire |
| `siteresto` | menus salle | menus + photos publiés sur le site des restos indépendants | hebdo (batch incrémental), re-crawl mensuel |
| `sitefinder` | découverte | sites web / plateformes de commande des restos OSM (Serper /maps) | hebdo par lots (3 000 req.), échecs re-sondés 180 j |
| `ubereats` | livraison | complément livraison (prix `delivery`, jamais prioritaires sur un menu salle) | quotidienne par petits lots (60 pages), re-vérif 30 j |
| `yelp` | annuaire | enrichissement via l'API Yelp Fusion (clé requise — SkipSource sans `YELP_API_KEY`) | hebdo, re-vérification 30 j |
| `yelp_scrape` | annuaire | notes, nombre d'avis, gamme de prix, catégories, fermetures via yelp.ca | quotidienne par petits lots (80 pages), re-vérif 30 j |
S'y ajoutent deux **enrichissements officiels** exécutés par `ingest.enrich` (garde-fou 6 jours) : `mapaq` (inspections) et `racj` (permis d'alcool), tous deux hebdomadaires.
## Données & conformité
- **Registre des sources** : `data/sources.json` — **10 sources** au 2026-08-24 (8 actives, 1 « clé requise » : yelp, 1 « à faire » : sthubert). À consulter **avant** tout ajout de source (CLAUDE.md §0.4) : chaque entrée documente l'accès légal, l'extraction et la cadence.
- **Prix de première partie** : les menus takeout viennent de la plateforme de commande des restos eux-mêmes (UEAT — la même API GraphQL que le widget officiel, auth anonyme publique) ou de leur propre site — **prix réels non majorés**. Les menus salle (`site-resto`) et takeout passent toujours avant les plateformes de livraison.
- **Scraping poli** : throttling (0,35 s sur UEAT), User-Agent identifiable `RestoKaBot`, budgets par passage (Yelp 450 req/j, site-finder 3 000 req, Uber Eats 60 pages/j).
- **Données publiques officielles** : inspections MAPAQ et permis d'alcool RACJ (jeux de données gouvernementaux).
- **Cadence de resync** : boucle `run.py watch 168` (cycle hebdomadaire) ; les sources quotidiennes (yelp-scrape, ubereats) avancent par petits lots à chaque passage ; alertes de dérive + délai de grâce avant tout retrait.
- La base SQLite de prod (`data/restoka.db`) est **gitignorée** ; aucun secret dans le repo (`.env` local, gabarit `.env.example`).
## Architecture
Pipeline (patron Lou·Ka) : **connecteurs → normalisation → déduplication → SQLite → API/frontend**.
- **Backend Python 3.14 / FastAPI / Uvicorn** (`restoka/web.py`) : API + service du build Vite + SSR léger SEO (JSON-LD Restaurant, sitemaps chunkés, pages /villes).
- **SQLite** (`restoka/db.py`) : restos, **menus par contexte de prix**, historique de prix (`item_price_log`), inspections, favoris, caches détail/géocodage.
- **Connecteurs auto-enregistrés** (`restoka/connectors/`) : requests direct / Firecrawl / Scrapfly + cache détail (`base.py`), UEAT templatisé (`ueat.py`), découverte Overpass/OSM.
- **Ingestion** (`restoka/ingest.py`) : sync → geocode → dedup, délai de grâce et alertes de dérive ; enrichissements inspections/permis/heures.
- **Structuration LLM** (`restoka/menullm.py`) : menus maison structurés par Claude (API Anthropic).
- **Frontend React 18 + Vite + TypeScript** (`frontend/`), design « éditorial sharp » Groupe KA.
- **80 tests pytest** avec fixtures réelles hors ligne (connecteurs, normalisation, schéma, horaires, permis…).
Processus PM2 sur le nœud (commandes de start réelles) :
| Processus | Rôle | Commande | Cadence |
|---|---|---|---|
| `resto-ka` | serveur FastAPI (API + frontend) sur le port **8115** | `.venv/bin/python run.py serve 8115` | continu |
| `resto-ka-sync` | boucle de synchronisation | `.venv/bin/python run.py watch 168` | cycle hebdomadaire (168 h) |
| `resto-ka-ngrok` | tunnel vers **www.resto-ka.com** | `ngrok http --url=www.resto-ka.com 8115` | continu |
## Variables d'environnement
Gabarit : `.env.example` (copier vers `.env`, jamais commité). **Noms seulement — aucune valeur dans le repo.**
| Variable | Rôle |
|---|---|
| `SCRAPFLY_KEY` | rendu JS + bypass anti-bot (plateformes de livraison) |
| `ANTHROPIC_API_KEY` | structuration des menus maison par Claude (`restoka/menullm.py`) |
| `FIRECRAWL_API_KEY` | rendu JS de secours pour les sites de restos SPA (`site-resto`) |
| `YELP_API_KEY` | API Yelp Fusion (sans clé : source sautée proprement) |
| `YELP_BUDGET` | budget de requêtes Yelp par passage (quota quotidien 500) |
| `SERPER_API_KEY` | Google Maps via Serper — découverte des sites web (`site-finder`) |
| `SITE_FINDER_BUDGET` | budget de requêtes Serper par passage (défaut 3 000) |
| `KA_SSO_SECRET` | SSO Groupe KA (« Se connecter avec KA ») |
| `SESSION_SECRET` | sessions signées |
| `RESTOKA_BASE_URL` | URL publique du site (défaut `https://www.resto-ka.com`) |
## Structure du repo
```
resto-ka/
├── run.py # point d'entrée CLI (sync / watch / serve)
├── restoka/ # cœur Python : web, db, ingest, dedup, geocode, regions, schema, normalize, auth, stats, menullm… + connectors/
├── frontend/ # React 18 + Vite + TypeScript — design éditorial sharp Groupe KA + public/doc/ (guide)
├── data/ # sources.json (registre), ueat-discovered.json (116 intégrations), base SQLite de prod (gitignorée)
├── scripts/ # scripts d'appoint (captures, déploiement)
├── tests/ # 80 tests pytest (fixtures réelles hors ligne)
└── docs/ # captures d'écran + docs connecteurs
```
## Documentation
- **Guide en ligne** : [www.resto-ka.com/doc/](https://www.resto-ka.com/doc/) — à quoi sert le site, le parcours en 4 étapes (recherche → restos d'une ville → fiche → /stats), d'où viennent les données, FAQ.
- **Guide PDF** : [resto-ka-documentation.pdf](https://www.resto-ka.com/doc/resto-ka-documentation.pdf) — la même documentation, téléchargeable.
- Les captures du guide sont versionnées dans `frontend/public/doc/img/` (etape1 → etape4).
## Développement (remote-first)
**La source de vérité est le repo git sur le nœud M3U96b** (`~/apps/resto-ka`) — on n'édite jamais les copies laptop. Toute modification se fait sur le nœud via SSH : édition, build, `pm2 restart`, puis commit/push depuis le nœud.
- Remote `origin` = **spbgit** (git perso [git.spboucher.ai](https://git.spboucher.ai), bare repos sur M3U96a). **Pas GitHub.**
- L'agent forwarding SSH est actif : le `git push origin main` fonctionne pendant une session SSH depuis le laptop.
- `CLAUDE.md` est la source de vérité du projet (règles d'ingestion, en-têtes d'auteur obligatoires).
```bash
pm2 restart resto-ka # après un changement en production
```
## Déploiement
- **Nœud** : M3U96b (Mac Studio, cluster MacLustr) — répertoire `~/apps/resto-ka`
- **Port** : **8115** (local, exposé uniquement via le tunnel)
- **Processus PM2** : `resto-ka` (serveur) + `resto-ka-sync` (synchro) + `resto-ka-ngrok` (tunnel)
- **Domaine** : **https://www.resto-ka.com** (tunnel ngrok)
## Historique
Jalons tirés du `git log` :
- **2026-08-17** — naissance : pipeline connecteurs→SQLite→API (patron Lou·Ka), assets Groupe KA, tableau de bord `/stats` + rapport PDF, câblage du widget KA Agent.
- **2026-08-18** — vagues 2-3 d'enrichissement : inspections MAPAQ, horaires structurés, Yelp (API + scraping), avis/notes sans clé, menus livraison, permis d'alcool ; documentation standardisée des sources.
- **2026-08-19** — Stats v2 (dashboard complet + 5 rapports PDF), campagne mobile (menus tactiles, tabbar), KA Agent v2 plein écran, standard « panneau héro premier dans le DOM ».
- **2026-08-22** — SEO : SSR léger (JSON-LD Restaurant), sitemaps chunkés, robots.txt ; chaîne de fetch anti-bot résiliente (Oxylabs → Scrapfly → Bright Data) ; section « Récemment consultés ».
- **2026-08-23** — connecteur `site-finder` (Serper /maps), photos extraites des sites des restos, Stats v3 (rapports PDF personnalisés, ReportBuilder), favoris unifiés « Mon univers Ka », pages SEO /villes ; 32 → 116 intégrations UEAT découvertes.
- **2026-08-24** — page documentation `/doc` (guide + captures + PDF), refonte du README (v2 puis v3), mise à jour du répertoire UEAT.
## Écosystème Groupe KA
| Plateforme | Vocation |
|---|---|
| [groupe-ka.com](https://www.groupe-ka.com) | portail du groupe et compte unique KA ID |
| [lou-ka.com](https://www.lou-ka.com) | logements à louer |
| [immo-ka.com](https://www.immo-ka.com) | propriétés à vendre |
| [vrai-prix.com](https://www.vrai-prix.com) | estimation immobilière |
| [auto-ka.com](https://www.auto-ka.com) | véhicules |
| [fabri-ka.com](https://www.fabri-ka.com) | produits québécois |
| [food-ka.com](https://www.food-ka.com) | épicerie et alimentation |
| [resto-ka.com](https://www.resto-ka.com) | restaurants *(ce repo)* |
| [sorti-ka.com](https://www.sorti-ka.com) | sorties et événements |
| [job-ka.com](https://www.job-ka.com) | emplois |
| [crea-ka.com](https://www.crea-ka.com) | créateurs de contenu |
| [trouve-ka.com](https://www.trouve-ka.com) | petites annonces |
| [api-ka.com](https://www.api-ka.com) | API de données |
## Contact
**Simon-Pierre Boucher** — fondateur, Groupe KA
📧 [contact@spboucher.ai](mailto:contact@spboucher.ai)
---
© Groupe KA — Simon-Pierre Boucher · contact@spboucher.ai
Ce repo vit sur **spbgit** ([git.spboucher.ai](https://git.spboucher.ai)).