SPB Git

spb/resto-ka

Public

Resto·Ka — tous les restaurants du Québec, menus complets et prix réels (famille ·Ka)

20.8 KB · 274 lines markdown
1<!-- Auteur : Simon-Pierre Boucher — contact@spboucher.ai -->23<p align="center">4  <a href="https://www.resto-ka.com"><img src="https://www.resto-ka.com/og.png" width="760" alt="Resto·Ka — Chaque resto, chaque plat, chaque prix"></a>5</p>6<h1 align="center">Resto·Ka</h1>7<p align="center"><b>Chaque resto, chaque plat, chaque prix</b></p>89<div align="center">1011<a href="https://www.resto-ka.com"><img src="https://img.shields.io/website?url=https%3A%2F%2Fwww.resto-ka.com&style=flat-square&label=www.resto-ka.com&up_color=f08c00&up_message=en%20ligne" alt="Site"></a>12<a href="https://www.resto-ka.com/healthz"><img src="https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fwww.resto-ka.com%2Fhealthz&query=%24.status&label=sant%C3%A9%20API&color=f08c00&style=flat-square" alt="Santé API (live)"></a>13<a href="https://www.resto-ka.com/doc/"><img src="https://img.shields.io/badge/📖_documentation-%2Fdoc-f08c00?style=flat-square" alt="Documentation"></a>14<a href="https://www.resto-ka.com/doc/resto-ka-documentation.pdf"><img src="https://img.shields.io/badge/guide-PDF-f08c00?style=flat-square" alt="PDF"></a>15<img src="https://img.shields.io/badge/n%C5%93ud-M3U96b-1f6feb?style=flat-square" alt="Nœud">16<img src="https://img.shields.io/badge/port-8115-141814?style=flat-square" alt="Port">17<img src="https://img.shields.io/badge/process-PM2-2b037a?style=flat-square" alt="PM2">1819</div>2021<div align="center">2223<img src="https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fwww.resto-ka.com%2Fapi%2Fstats&query=%24.restaurants&label=restaurants&color=f08c00&style=flat-square" alt="Restaurants (live)">24<img src="https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fwww.resto-ka.com%2Fapi%2Fstats&query=%24.items&label=plats%20avec%20prix&color=f08c00&style=flat-square" alt="Plats avec prix (live)">25<img src="https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fwww.resto-ka.com%2Fapi%2Fstats&query=%24.with_menu&label=menus%20captur%C3%A9s&color=f08c00&style=flat-square" alt="Menus capturés (live)">26<img src="https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fwww.resto-ka.com%2Fapi%2Fstats&query=%24.chains&label=cha%C3%AEnes&color=f08c00&style=flat-square" alt="Chaînes (live)">27<img src="https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fwww.resto-ka.com%2Fapi%2Fstats&query=%24.regions&label=r%C3%A9gions&color=f08c00&style=flat-square" alt="Régions (live)">28<img src="https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fwww.resto-ka.com%2Fapi%2Fstats&query=%24.inspections&label=inspections%20MAPAQ&color=f08c00&style=flat-square" alt="Inspections MAPAQ (live)">29<img src="https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fwww.resto-ka.com%2Fapi%2Fstats&query=%24.with_alcohol_permit&label=permis%20alcool&color=f08c00&style=flat-square" alt="Permis d'alcool (live)">30<img src="https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fwww.resto-ka.com%2Fapi%2Fstats&query=%24.sources_registry&label=sources%20au%20registre&color=f08c00&style=flat-square" alt="Sources au registre (live)">3132</div>3334<div align="center">3536<img src="https://img.shields.io/badge/Python-3.14-3776AB?style=flat-square&logo=python&logoColor=white" alt="Python">37<img src="https://img.shields.io/badge/FastAPI-API-009688?style=flat-square&logo=fastapi&logoColor=white" alt="FastAPI">38<img src="https://img.shields.io/badge/React_18-TypeScript-087EA4?style=flat-square&logo=react&logoColor=white" alt="React">39<img src="https://img.shields.io/badge/Vite-5-646CFF?style=flat-square&logo=vite&logoColor=white" alt="Vite">40<img src="https://img.shields.io/badge/SQLite-menus_+_prix-003B57?style=flat-square&logo=sqlite&logoColor=white" alt="SQLite">41<img src="https://img.shields.io/badge/PM2-3_processus-2B037A?style=flat-square&logo=pm2&logoColor=white" alt="PM2">42<img src="https://img.shields.io/badge/ngrok-tunnel-1F1E37?style=flat-square&logo=ngrok&logoColor=white" alt="ngrok">43<img src="https://img.shields.io/badge/Groupe-KA-f08c00?style=flat-square" alt="Groupe KA">44<img src="https://img.shields.io/badge/remote--first-spbgit-141814?style=flat-square" alt="remote-first spbgit">4546</div>4748**Resto·Ka** ([www.resto-ka.com](https://www.resto-ka.com)) est l'**agrégateur exhaustif des restaurants du Québec** : chaque resto, chaque plat, chaque prix — dans les 17 régions administratives, au même endroit, comparable et cherchable **par resto ou par plat**. Les prix sont des **prix takeout réels, non majorés**, captés sur la plateforme de commande des restos eux-mêmes (pas sur les apps de livraison, majorées de 25-30 %).4950**En chiffres** (live `/api/stats`, au 2026-08-24) : **14 716 restaurants actifs** (hors doublons, découverte OpenStreetMap, 17/17 régions), **149 569 plats avec prix**, **1 682 menus capturés** (1 268 takeout · 386 salle · 31 livraison), **180 chaînes identifiées** dont **116 intégrations UEAT découvertes**, 2 994 inspections MAPAQ importées, 1 859 restos avec permis d'alcool (RACJ), 155 312 entrées d'historique de prix, 80 % des items avec photo, 100 % des restos géolocalisés. Pour qui ? Quiconque veut répondre à « qu'est-ce que je mange, où, et combien ça coûte ? » sans ouvrir quinze applis — là où les apps de livraison majorent et où les annuaires (Google, Yelp) n'ont ni menus structurés ni prix comparables.5152## Visite guidée5354<table>55  <tr>56    <td align="center"><img src="docs/screenshots/resto-ka-desktop.png" width="420"><br><sub><b>Accueil — recherche par resto ou par plat (desktop)</b></sub></td>57    <td align="center"><img src="docs/screenshots/resto-ka-mobile.png" width="220"><br><sub><b>Accueil — version mobile</b></sub></td>58  </tr>59  <tr>60    <td align="center"><img src="frontend/public/doc/img/etape1.png" width="420"><br><sub><b>Étape 1 · Cherchez un resto ou un plat depuis l'accueil</b></sub></td>61    <td align="center"><img src="frontend/public/doc/img/etape2.png" width="420"><br><sub><b>Étape 2 · Parcourez les restos d'une ville</b></sub></td>62  </tr>63  <tr>64    <td align="center"><img src="frontend/public/doc/img/etape3.png" width="420"><br><sub><b>Étape 3 · Ouvrez la fiche d'un restaurant</b></sub></td>65    <td align="center"><img src="frontend/public/doc/img/etape4.png" width="420"><br><sub><b>Étape 4 · Comparez le marché sur /stats</b></sub></td>66  </tr>67  <tr>68    <td align="center"><img src="docs/screenshots/accueil.png" width="420"><br><sub><b>Accueil — variante pleine page</b></sub></td>69    <td align="center"><img src="docs/screenshots/fiche.png" width="420"><br><sub><b>Fiche restaurant — menu structuré et prix contextualisés</b></sub></td>70  </tr>71  <tr>72    <td align="center"><img src="docs/screenshots/plats.png" width="420"><br><sub><b>Recherche par plat — 149 569 items, groupés par marque</b></sub></td>73    <td align="center"><img src="docs/screenshots/stats.png" width="420"><br><sub><b>Tableau de bord /stats — le marché resto en chiffres</b></sub></td>74  </tr>75</table>7677## Fonctionnalités7879- **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.80- **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).81- **Options & formats capturés** avec leurs suppléments (« Mini +3,70 $ ») — ils changent le prix réel.82- **Historique des prix par item** (`item_price_log`, 155 312 entrées au 2026-08-24) à chaque changement.83- **Alerte de dérive** — chute anormale du volume d'une source ou du nombre d'items d'un menu → retraits suspendus / ancien menu conservé.84- **Déduplication inter-sources** (les succursales ne sont jamais fusionnées) et **géocodage à 100 %** (Nominatim + Adresses Québec, cache persistant).85- **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…).86- **Découverte des sites web** des restos OSM (connecteur `site-finder`, Serper /maps) et **extraction des photos** depuis les sites des restos (`site-resto`).87- **Enrichissements officiels** : inspections MAPAQ (2 994 importées, 288 appariées à 204 restos) et permis d'alcool RACJ (1 859 restos).88- **Avis & notes** : Yelp par scraping quotidien (177 restos notés) et croisement Uber Eats (36 restos, prix marqués `delivery`).89- **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).90- **Registre des sources** — `data/sources.json` (10 sources), à consulter avant tout ajout (statut et raison des sources non connectables).9192## Démarrage rapide9394```bash95# Backend96python3 -m venv .venv && .venv/bin/pip install -r requirements.txt9798# Frontend (build servi par FastAPI)99cd frontend && npm install && npm run build && cd ..100# ou en dev : cd frontend && npm run dev101102# Lancer103.venv/bin/python run.py sync            # synchronise (+ géocode + dédup)104.venv/bin/python run.py serve 8115      # API + frontend sur :8115105.venv/bin/python run.py watch 168       # boucle hebdomadaire (heures)106107# Tests (80 tests pytest, fixtures réelles hors ligne)108.venv/bin/python -m pytest tests/109```110111## API (endpoints)112113Servis par `restoka/web.py` (FastAPI) sur le port 8115.114115| Méthode | Endpoint | Description |116|---|---|---|117| `GET` | `/api/restaurants` | recherche de restos (ville, région, cuisine, texte libre, `uids=`, pagination) |118| `GET` | `/api/restaurants/{uid}` | fiche complète : menu structuré par contexte de prix, photos, coordonnées, horaires |119| `GET` | `/api/restaurants/{uid}/prices` | historique des prix des items du resto (`item_price_log`) |120| `GET` | `/api/restaurants/{uid}/inspections` | inspections/permis (enrichissements MAPAQ + RACJ) |121| `GET` | `/api/dishes` | recherche par plat dans les 149 569 items, groupée par marque |122| `GET` | `/api/facets` | facettes de filtres (régions, villes, cuisines, types) |123| `GET` | `/api/sources` | registre des sources (statuts, cadences) |124| `GET` | `/api/stats` | statistiques publiques JSON (alimente les pastilles ci-dessus) |125| `GET` | `/api/stats/dashboard` | données du tableau de bord `/stats` |126| `GET` | `/api/stats/report` | rapport PDF principal |127| `GET` | `/api/stats/catalog` | catalogue des rapports disponibles |128| `POST` | `/api/stats/report/custom` | rapport PDF personnalisé (ReportBuilder) |129| `GET` | `/api/favorites` | favoris « Mon univers Ka » (session KA ID) |130| `POST` | `/api/favorites/toggle` | ajout/retrait d'un favori |131| `POST` | `/api/sync` | déclenchement d'une synchronisation |132| `GET` | `/healthz` | santé du service (`{"status":"ok"}`) |133| `GET` | `/doc/` | guide d'utilisation en ligne |134| `GET` | `/{full_path}` | SPA + pages SSR légères (accueil, fiches JSON-LD, /villes) |135136## Connecteurs137138Auto-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) :139140| Connecteur | Famille | Rôle | Cadence |141|---|---|---|---|142| `osm` | découverte | OpenStreetMap (Overpass, miroirs fiabilisés) — 13 427 établissements nommés, 17/17 régions | mensuelle |143| `ueat` | menus takeout (tier 1) | connecteur templatisé UEAT (API GraphQL) — menus takeout réels de 116 intégrations découvertes | hebdomadaire |144| `siteresto` | menus salle | menus + photos publiés sur le site des restos indépendants | hebdo (batch incrémental), re-crawl mensuel |145| `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 |146| `ubereats` | livraison | complément livraison (prix `delivery`, jamais prioritaires sur un menu salle) | quotidienne par petits lots (60 pages), re-vérif 30 j |147| `yelp` | annuaire | enrichissement via l'API Yelp Fusion (clé requise — SkipSource sans `YELP_API_KEY`) | hebdo, re-vérification 30 j |148| `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 |149150S'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.151152## Données & conformité153154- **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.155- **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.156- **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).157- **Données publiques officielles** : inspections MAPAQ et permis d'alcool RACJ (jeux de données gouvernementaux).158- **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.159- La base SQLite de prod (`data/restoka.db`) est **gitignorée** ; aucun secret dans le repo (`.env` local, gabarit `.env.example`).160161## Architecture162163Pipeline (patron Lou·Ka) : **connecteurs → normalisation → déduplication → SQLite → API/frontend**.164165- **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).166- **SQLite** (`restoka/db.py`) : restos, **menus par contexte de prix**, historique de prix (`item_price_log`), inspections, favoris, caches détail/géocodage.167- **Connecteurs auto-enregistrés** (`restoka/connectors/`) : requests direct / Firecrawl / Scrapfly + cache détail (`base.py`), UEAT templatisé (`ueat.py`), découverte Overpass/OSM.168- **Ingestion** (`restoka/ingest.py`) : sync → geocode → dedup, délai de grâce et alertes de dérive ; enrichissements inspections/permis/heures.169- **Structuration LLM** (`restoka/menullm.py`) : menus maison structurés par Claude (API Anthropic).170- **Frontend React 18 + Vite + TypeScript** (`frontend/`), design « éditorial sharp » Groupe KA.171- **80 tests pytest** avec fixtures réelles hors ligne (connecteurs, normalisation, schéma, horaires, permis…).172173Processus PM2 sur le nœud (commandes de start réelles) :174175| Processus | Rôle | Commande | Cadence |176|---|---|---|---|177| `resto-ka` | serveur FastAPI (API + frontend) sur le port **8115** | `.venv/bin/python run.py serve 8115` | continu |178| `resto-ka-sync` | boucle de synchronisation | `.venv/bin/python run.py watch 168` | cycle hebdomadaire (168 h) |179| `resto-ka-ngrok` | tunnel vers **www.resto-ka.com** | `ngrok http --url=www.resto-ka.com 8115` | continu |180181## Variables d'environnement182183Gabarit : `.env.example` (copier vers `.env`, jamais commité). **Noms seulement — aucune valeur dans le repo.**184185| Variable | Rôle |186|---|---|187| `SCRAPFLY_KEY` | rendu JS + bypass anti-bot (plateformes de livraison) |188| `ANTHROPIC_API_KEY` | structuration des menus maison par Claude (`restoka/menullm.py`) |189| `FIRECRAWL_API_KEY` | rendu JS de secours pour les sites de restos SPA (`site-resto`) |190| `YELP_API_KEY` | API Yelp Fusion (sans clé : source sautée proprement) |191| `YELP_BUDGET` | budget de requêtes Yelp par passage (quota quotidien 500) |192| `SERPER_API_KEY` | Google Maps via Serper — découverte des sites web (`site-finder`) |193| `SITE_FINDER_BUDGET` | budget de requêtes Serper par passage (défaut 3 000) |194| `KA_SSO_SECRET` | SSO Groupe KA (« Se connecter avec KA ») |195| `SESSION_SECRET` | sessions signées |196| `RESTOKA_BASE_URL` | URL publique du site (défaut `https://www.resto-ka.com`) |197198## Structure du repo199200```201resto-ka/202├── run.py           # point d'entrée CLI (sync / watch / serve)203├── restoka/         # cœur Python : web, db, ingest, dedup, geocode, regions, schema, normalize, auth, stats, menullm… + connectors/204├── frontend/        # React 18 + Vite + TypeScript — design éditorial sharp Groupe KA + public/doc/ (guide)205├── data/            # sources.json (registre), ueat-discovered.json (116 intégrations), base SQLite de prod (gitignorée)206├── scripts/         # scripts d'appoint (captures, déploiement)207├── tests/           # 80 tests pytest (fixtures réelles hors ligne)208└── docs/            # captures d'écran + docs connecteurs209```210211## Documentation212213- **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.214- **Guide PDF** : [resto-ka-documentation.pdf](https://www.resto-ka.com/doc/resto-ka-documentation.pdf) — la même documentation, téléchargeable.215- Les captures du guide sont versionnées dans `frontend/public/doc/img/` (etape1 → etape4).216217## Développement (remote-first)218219**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.220221- Remote `origin` = **spbgit** (git perso [git.spboucher.ai](https://git.spboucher.ai), bare repos sur M3U96a). **Pas GitHub.**222- L'agent forwarding SSH est actif : le `git push origin main` fonctionne pendant une session SSH depuis le laptop.223- `CLAUDE.md` est la source de vérité du projet (règles d'ingestion, en-têtes d'auteur obligatoires).224225```bash226pm2 restart resto-ka                    # après un changement en production227```228229## Déploiement230231- **Nœud** : M3U96b (Mac Studio, cluster MacLustr) — répertoire `~/apps/resto-ka`232- **Port** : **8115** (local, exposé uniquement via le tunnel)233- **Processus PM2** : `resto-ka` (serveur) + `resto-ka-sync` (synchro) + `resto-ka-ngrok` (tunnel)234- **Domaine** : **https://www.resto-ka.com** (tunnel ngrok)235236## Historique237238Jalons tirés du `git log` :239240- **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.241- **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.242- **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 ».243- **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 ».244- **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.245- **2026-08-24** — page documentation `/doc` (guide + captures + PDF), refonte du README (v2 puis v3), mise à jour du répertoire UEAT.246247## Écosystème Groupe KA248249| Plateforme | Vocation |250|---|---|251| [groupe-ka.com](https://www.groupe-ka.com) | portail du groupe et compte unique KA ID |252| [lou-ka.com](https://www.lou-ka.com) | logements à louer |253| [immo-ka.com](https://www.immo-ka.com) | propriétés à vendre |254| [vrai-prix.com](https://www.vrai-prix.com) | estimation immobilière |255| [auto-ka.com](https://www.auto-ka.com) | véhicules |256| [fabri-ka.com](https://www.fabri-ka.com) | produits québécois |257| [food-ka.com](https://www.food-ka.com) | épicerie et alimentation |258| [resto-ka.com](https://www.resto-ka.com) | restaurants *(ce repo)* |259| [sorti-ka.com](https://www.sorti-ka.com) | sorties et événements |260| [job-ka.com](https://www.job-ka.com) | emplois |261| [crea-ka.com](https://www.crea-ka.com) | créateurs de contenu |262| [trouve-ka.com](https://www.trouve-ka.com) | petites annonces |263| [api-ka.com](https://www.api-ka.com) | API de données |264265## Contact266267**Simon-Pierre Boucher** — fondateur, Groupe KA268📧 [contact@spboucher.ai](mailto:contact@spboucher.ai)269270---271272© Groupe KA — Simon-Pierre Boucher · contact@spboucher.ai273Ce repo vit sur **spbgit** ([git.spboucher.ai](https://git.spboucher.ai)).274
Files · 48394594f1aedf959655b2b0f3f60eb8e4662e2a
Menu

Theme