Resto·Ka v1 — agrégateur des restaurants du Québec (menus & prix réels)
- Pipeline complet calqué sur Lou·Ka : connecteurs auto-découverts, schéma Restaurant + menu imbriqué (sections → items → prix → options), validation stricte (price_context/price_source obligatoires), menus conservés PAR contexte, historique des prix par item, alertes de dérive, délai de grâce. - Connecteur templatisé UEAT (API GraphQL, prix takeout réels) : 19 marques, 542 restos, 61 330 plats (98 % avec photo, 69 % avec options), 14/17 régions. - Géocodage Nominatim + Adresses Québec (96 %), régions ville→région, déduplication inter-sources (succursales jamais fusionnées). - Frontend Vite/React 18/TS — design « éditorial sharp » Groupe KA, recherche par resto ET par plat (groupée par marque), pagination numérotée, images, options en pastilles avec suppléments, badges de contexte de prix. - 41 tests (fixtures UEAT réelles hors ligne). Déployé : www.resto-ka.com (M3U96b:8115, PM2 + ngrok + sync hebdo). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
44 changed files +7,899 −0
added
.gitignore
+16 −0
@@ -0,0 +1,16 @@ | ||
| 1 | +# ============================================================================== | |
| 2 | +# Author: Simon-Pierre Boucher <contact@spboucher.ai> | |
| 3 | +# File: .gitignore | |
| 4 | +# Desc: Resto·Ka — fichiers exclus du dépôt (secrets, base, builds). | |
| 5 | +# ============================================================================== | |
| 6 | +.env | |
| 7 | +__pycache__/ | |
| 8 | +*.pyc | |
| 9 | +.venv/ | |
| 10 | +.pytest_cache/ | |
| 11 | +data/*.db | |
| 12 | +data/*.db-journal | |
| 13 | +frontend/node_modules/ | |
| 14 | +frontend/dist/ | |
| 15 | +frontend/tsconfig.tsbuildinfo | |
| 16 | +.DS_Store | |
added
CLAUDE.md
+572 −0
@@ -0,0 +1,572 @@ | ||
| 1 | +<!-- | |
| 2 | +================================================================================ | |
| 3 | + Author: Simon-Pierre Boucher <contact@spboucher.ai> | |
| 4 | + File: CLAUDE.md | |
| 5 | + Project: Resto·Ka — Agrégateur québécois de tous les restaurants (menus + prix) | |
| 6 | +================================================================================ | |
| 7 | +--> | |
| 8 | + | |
| 9 | +# CLAUDE.md — Resto·Ka | |
| 10 | + | |
| 11 | +> **Agrégateur exhaustif de tous les restaurants du Québec, avec leurs menus | |
| 12 | +> complets et leurs prix.** | |
| 13 | +> Chaque resto, chaque plat, chaque prix — dans les 17 régions, au même endroit, | |
| 14 | +> comparable et cherchable. | |
| 15 | + | |
| 16 | +Ce fichier est la source de vérité pour tout agent Claude (ou humain) qui travaille | |
| 17 | +sur ce dépôt. Lis-le **en entier** avant d'écrire une seule ligne de code. | |
| 18 | + | |
| 19 | +--- | |
| 20 | + | |
| 21 | +## 0. À LIRE EN PREMIER — Règles non négociables | |
| 22 | + | |
| 23 | +1. **En-tête d'auteur obligatoire.** **Chaque fichier** du dépôt (code, config, | |
| 24 | + doc, script) doit commencer par un en-tête mentionnant l'auteur : | |
| 25 | + `Simon-Pierre Boucher <contact@spboucher.ai>`. Voir la **section 17** pour le | |
| 26 | + format exact selon le type de fichier. Aucun fichier ne doit être commité sans | |
| 27 | + cet en-tête. | |
| 28 | + | |
| 29 | +2. **Inspire-toi du dépôt Lou·Ka.** Avant de concevoir ou d'écrire **le moindre | |
| 30 | + connecteur**, va lire le **dépôt de Lou·Ka** (le site jumeau, agrégateur de | |
| 31 | + logements locatifs). Ses connecteurs sont le patron de référence : structure | |
| 32 | + des modules, extraction (Firecrawl / Scrapfly), normalisation, déduplication, | |
| 33 | + gestion des mises à jour, planification des tâches. **Ne réinvente rien** : | |
| 34 | + reprends les mêmes conventions, la même architecture de connecteur, les mêmes | |
| 35 | + utilitaires partagés. Resto·Ka doit être cohérent avec l'écosystème `·Ka` | |
| 36 | + (Lou·Ka, Immo·Ka, Auto·Ka, Food·Ka, Fabri·Ka, Sorti·Ka). Voir **section 3**. | |
| 37 | + | |
| 38 | +3. **Travail de moine.** L'agrégation de menus est un travail minutieux, source par | |
| 39 | + source, plat par plat, sans rien précipiter. Un menu mal parsé (mauvais prix, | |
| 40 | + plat manquant, option ignorée) détruit la confiance dans toute la base. Mieux | |
| 41 | + vaut un connecteur robuste de plus que dix fragiles. | |
| 42 | + | |
| 43 | +4. **Vérifie l'existant avant d'ajouter.** Avant d'intégrer un resto ou une source, | |
| 44 | + confirme qu'il/elle n'est **pas déjà couvert(e)** (voir le registre des sources | |
| 45 | + et l'index des restaurants, sections 9 et 12). Ne duplique jamais un connecteur | |
| 46 | + ni une fiche resto. Un restaurant = une fiche canonique, quelles que soient les | |
| 47 | + sources qui l'alimentent. | |
| 48 | + | |
| 49 | +5. **Chaque source a son connecteur sur mesure**, mais les sources qui partagent | |
| 50 | + une **même plateforme** (livraison, commande en ligne, POS, réservation) | |
| 51 | + doivent être couvertes par un **connecteur générique templatisé** réutilisable. | |
| 52 | + Voir **section 8**. | |
| 53 | + | |
| 54 | +6. **La provenance du prix est sacrée.** Un prix de livraison (Uber Eats, DoorDash, | |
| 55 | + Skip) est **majoré de 25-30 %** par rapport à la salle à manger. Ne **jamais** | |
| 56 | + présenter un prix sans son contexte (`price_context`). Voir **sections 5 et 11**. | |
| 57 | + | |
| 58 | +--- | |
| 59 | + | |
| 60 | +## 1. Mission | |
| 61 | + | |
| 62 | +Rendre visible, en un seul endroit, **tous les restaurants du Québec avec leurs | |
| 63 | +menus et leurs prix réels**, dans les 17 régions — pour que quiconque puisse | |
| 64 | +répondre à *« qu'est-ce que je mange, où, et combien ça coûte ? »* sans ouvrir | |
| 65 | +quinze applis. | |
| 66 | + | |
| 67 | +### Ce qui nous distingue | |
| 68 | + | |
| 69 | +Les plateformes existantes sont soit des **apps de livraison** (Uber Eats, Skip, | |
| 70 | +DoorDash — menus complets mais prix majorés, périmètre limité aux restos inscrits | |
| 71 | +et payants), soit des **annuaires** (Google, Yelp, TripAdvisor — avis et coordonnées, | |
| 72 | +mais **pas de menus structurés ni de prix comparables**), soit des **médias food** | |
| 73 | +(Tastet, RestoMontréal — éditorial, pas exhaustif). | |
| 74 | + | |
| 75 | +Personne n'offre **le menu complet + les prix de tous les restos, comparables entre | |
| 76 | +eux, partout au Québec.** C'est le créneau de Resto·Ka. | |
| 77 | + | |
| 78 | +--- | |
| 79 | + | |
| 80 | +## 2. Périmètre — TOUS les établissements où l'on mange | |
| 81 | + | |
| 82 | +Périmètre maximaliste. Si on peut y commander à manger, ça doit finir dans Resto·Ka : | |
| 83 | + | |
| 84 | +- **Restaurants** (tous services : gastronomique, décontracté, familial). | |
| 85 | +- **Fast-food & chaînes** (menus standardisés, multi-succursales). | |
| 86 | +- **Cafés & salons de thé** (avec offre alimentaire). | |
| 87 | +- **Bars & pubs** avec cuisine. | |
| 88 | +- **Comptoirs pour emporter, cantines, casse-croûtes, food trucks.** | |
| 89 | +- **Pizzérias, sushis, poulet, burgers, shawarma**, etc. | |
| 90 | +- **Boulangeries-pâtisseries et traiteurs** avec menu et prix. | |
| 91 | +- **Microbrasseries et établissements** avec menu de bouffe. | |
| 92 | +- **Restaurants d'hôtel**, cuisines fantômes (ghost kitchens). | |
| 93 | + | |
| 94 | +> Règle d'or : en cas de doute sur l'inclusion, **on inclut** et on catégorise par | |
| 95 | +> type d'établissement. Le périmètre est large par conception. | |
| 96 | + | |
| 97 | +--- | |
| 98 | + | |
| 99 | +## 3. Dépôts de référence — étudier Lou·Ka AVANT de coder | |
| 100 | + | |
| 101 | +Resto·Ka fait partie de la famille `·Ka`. **Le dépôt de Lou·Ka est la référence | |
| 102 | +canonique pour l'architecture des connecteurs.** Avant tout développement, l'agent | |
| 103 | +Claude doit : | |
| 104 | + | |
| 105 | +1. **Ouvrir le dépôt de Lou·Ka** et localiser le dossier des connecteurs | |
| 106 | + (typiquement `connectors/`, `scrapers/` ou `sources/`). | |
| 107 | +2. **Lire 2 ou 3 connecteurs complets** de bout en bout pour comprendre : | |
| 108 | + - la structure d'un module connecteur (interface commune, méthodes attendues) ; | |
| 109 | + - l'**extraction** (Firecrawl / Scrapfly, endpoints JSON, sitemaps) ; | |
| 110 | + - la **normalisation** vers le schéma commun ; | |
| 111 | + - la **déduplication** (annonces publiées sur plusieurs plateformes) ; | |
| 112 | + - la gestion des **mises à jour** (nouveautés, changements, retraits) ; | |
| 113 | + - la **planification** (cadence, files, retries, backoff), les **erreurs** et le | |
| 114 | + **logging**. | |
| 115 | +3. **Réutiliser** les utilitaires partagés de Lou·Ka (client Firecrawl/Scrapfly, | |
| 116 | + helpers de normalisation, déduplication, géocodage) plutôt que d'en réécrire. | |
| 117 | +4. **Calquer** les conventions de nommage, la structure des dossiers et le style | |
| 118 | + de code de Lou·Ka. Un dev qui connaît Lou·Ka doit se retrouver immédiatement. | |
| 119 | + | |
| 120 | +> Adaptation propre à Resto·Ka : là où Lou·Ka normalise une **annonce**, Resto·Ka | |
| 121 | +> normalise un **restaurant + un menu imbriqué (catégories → plats → prix → | |
| 122 | +> options)**. Le schéma est plus profond ; le reste du patron s'applique tel quel. | |
| 123 | + | |
| 124 | +> Si tu ne trouves pas le dépôt Lou·Ka ou l'accès aux connecteurs, **arrête-toi et | |
| 125 | +> demande** plutôt que d'inventer une architecture divergente. | |
| 126 | + | |
| 127 | +Écosystème complet, pour contexte : | |
| 128 | + | |
| 129 | +| Site | Domaine | Agrège | | |
| 130 | +|-------------|------------------|-------------------------------------------| | |
| 131 | +| Lou·Ka | lou-ka.com | Logements à louer | | |
| 132 | +| Immo·Ka | immo-ka.com | Propriétés à vendre | | |
| 133 | +| Auto·Ka | auto-ka.com | Véhicules usagés | | |
| 134 | +| Food·Ka | food-ka.com | Prix d'épicerie & circulaires | | |
| 135 | +| Fabri·Ka | fabri-ka.com | Produits québécois | | |
| 136 | +| Sorti·Ka | sorti-ka.com | Sorties & événements | | |
| 137 | +| **Resto·Ka**| **resto-ka.com** | **Restaurants, menus & prix (ce dépôt)** | | |
| 138 | + | |
| 139 | +--- | |
| 140 | + | |
| 141 | +## 4. Architecture | |
| 142 | + | |
| 143 | +Pipeline standard de la famille `·Ka`, adapté aux restaurants et menus : | |
| 144 | + | |
| 145 | +``` | |
| 146 | + ┌─────────────┐ ┌──────────────┐ ┌────────────────┐ ┌──────────────┐ ┌──────────┐ | |
| 147 | + │ CONNECTEUR │──▶│ EXTRACTION │──▶│ NORMALISATION │──▶│ DÉDUPLICATION │──▶│ STOCKAGE │ | |
| 148 | + │ (par source)│ │ FC / Scrapfly│ │ resto + menu │ │ identité resto│ │ (DB) │ | |
| 149 | + │ │ │ + parse PDF │ │ (schéma commun)│ │ + items │ │ │ | |
| 150 | + └─────────────┘ └──────────────┘ └────────────────┘ └──────────────┘ └────┬─────┘ | |
| 151 | + │ | |
| 152 | + ┌──────────▼──────────┐ | |
| 153 | + │ API / RECHERCHE / │ | |
| 154 | + │ UI (menus & prix) │ | |
| 155 | + └─────────────────────┘ | |
| 156 | +``` | |
| 157 | + | |
| 158 | +Composants : | |
| 159 | + | |
| 160 | +- **Connecteurs** : un module par source (ou par plateforme), interface commune. | |
| 161 | +- **Orchestrateur** : planifie et exécute (cadence, retries, backoff). | |
| 162 | +- **Extraction** : Firecrawl & Scrapfly + clients d'API + **parsing de menus PDF / | |
| 163 | + images** (OCR) pour les menus non structurés. | |
| 164 | +- **Normalisation** : mappe le brut vers le **schéma Restaurant + Menu** (section 5). | |
| 165 | +- **Déduplication** : fusionne les restos et les items en double inter-sources. | |
| 166 | +- **Stockage** : base restos + menus + **historique des prix** (suivi des hausses). | |
| 167 | +- **API / UI** : recherche géolocalisée, filtres (cuisine, prix, diète), comparaison. | |
| 168 | + | |
| 169 | +--- | |
| 170 | + | |
| 171 | +## 5. Modèle de données — schéma Restaurant + Menu | |
| 172 | + | |
| 173 | +### 5.1 Restaurant | |
| 174 | + | |
| 175 | +```jsonc | |
| 176 | +{ | |
| 177 | + "id": "string", // * id interne canonique (post-déduplication) | |
| 178 | + "source_ids": ["ubereats:abc"], // * id(s) d'origine par source (préfixés) | |
| 179 | + "name": "string", // * nom normalisé | |
| 180 | + "chain": "string|null", // marque/chaîne si applicable | |
| 181 | + "cuisines": ["italien", "pizza"], // * taxonomie cuisine (section 6) | |
| 182 | + "establishment_type": "restaurant",// restaurant|fast-food|cafe|bar|food-truck|traiteur... | |
| 183 | + "price_range": "$$", // $ à $$$$ (estimé à partir des prix) | |
| 184 | + "address": "string", // * | |
| 185 | + "city": "string", // * | |
| 186 | + "region": "string", // * une des 17 régions (section 7) | |
| 187 | + "postal_code": "string", | |
| 188 | + "lat": 0.0, // * géolocalisation | |
| 189 | + "lng": 0.0, // * | |
| 190 | + "phone": "string", | |
| 191 | + "website": "string", | |
| 192 | + "hours": { /* horaires par jour */ }, | |
| 193 | + "services": ["salle", "emporter", "livraison"], // dine-in|takeout|delivery | |
| 194 | + "dietary_options": ["vegan", "sans-gluten", "halal"], | |
| 195 | + "languages": ["fr", "en"], | |
| 196 | + "menu": { /* voir 5.2 */ }, | |
| 197 | + "status": "open", // open|temporarily_closed|closed | |
| 198 | + "first_seen": "ISO-8601", // * | |
| 199 | + "last_seen": "ISO-8601", // * | |
| 200 | + "updated_at": "ISO-8601" // * | |
| 201 | +} | |
| 202 | +``` | |
| 203 | + | |
| 204 | +### 5.2 Menu (imbriqué) | |
| 205 | + | |
| 206 | +```jsonc | |
| 207 | +"menu": { | |
| 208 | + "currency": "CAD", | |
| 209 | + "price_context": "delivery", // * dine-in | takeout | delivery (voir §6) | |
| 210 | + "price_source": "ubereats", // * source du prix (traçabilité) | |
| 211 | + "captured_at": "ISO-8601", // * quand ce menu/prix a été capté | |
| 212 | + "sections": [ | |
| 213 | + { | |
| 214 | + "name": "Pizzas", | |
| 215 | + "items": [ | |
| 216 | + { | |
| 217 | + "id": "string", | |
| 218 | + "name": "Margherita", // * | |
| 219 | + "description": "string", | |
| 220 | + "price": 16.50, // * prix de l'item (dans price_context) | |
| 221 | + "currency": "CAD", | |
| 222 | + "tags": ["vegetarien"], | |
| 223 | + "options": [ // modificateurs / variantes | |
| 224 | + { | |
| 225 | + "group": "Format", | |
| 226 | + "required": true, | |
| 227 | + "choices": [ | |
| 228 | + { "name": "10 pouces", "price_delta": 0.00 }, | |
| 229 | + { "name": "14 pouces", "price_delta": 6.00 } | |
| 230 | + ] | |
| 231 | + } | |
| 232 | + ] | |
| 233 | + } | |
| 234 | + ] | |
| 235 | + } | |
| 236 | + ] | |
| 237 | +} | |
| 238 | +``` | |
| 239 | + | |
| 240 | +Règles : | |
| 241 | +- `price_context` et `price_source` sont **obligatoires** : un prix sans contexte | |
| 242 | + est inutilisable (livraison ≠ salle). | |
| 243 | +- `lat`/`lng` obligatoires → **géocoder** l'adresse si absente. | |
| 244 | +- Capturer les **options/modificateurs** (formats, suppléments, choix) : ils | |
| 245 | + changent le prix réel. | |
| 246 | +- Conserver la **devise** partout (CAD). Au QC, TPS/TVQ s'ajoutent ; ne pas les | |
| 247 | + inclure dans `price` sauf si la source le fait, et alors le **documenter**. | |
| 248 | + | |
| 249 | +--- | |
| 250 | + | |
| 251 | +## 6. Taxonomies | |
| 252 | + | |
| 253 | +### 6.1 Cuisines (multi-valué) | |
| 254 | +`quebecois` · `francais` · `italien` · `pizza` · `burgers` · `poulet` · | |
| 255 | +`bbq-grillades` · `fruits-de-mer` · `sushi-japonais` · `chinois` · `thai` · | |
| 256 | +`vietnamien` · `coreen` · `indien` · `libanais-moyen-orient` · `mexicain` · | |
| 257 | +`grec` · `mediterraneen` · `dejeuner-brunch` · `cafe-dessert` · `vegetarien-vegan` · | |
| 258 | +`fast-food` · `autre` | |
| 259 | + | |
| 260 | +### 6.2 Contexte de prix (`price_context`) — CRITIQUE | |
| 261 | +- `dine-in` — prix en salle (référence idéale). | |
| 262 | +- `takeout` — prix pour emporter (souvent = salle). | |
| 263 | +- `delivery` — prix sur plateforme de livraison, **majoré de 25-30 %**. | |
| 264 | + | |
| 265 | +> Toujours privilégier `dine-in`/`takeout` (menu du resto lui-même ou sa plateforme | |
| 266 | +> de commande en ligne) plutôt que `delivery` quand les deux existent. Si on n'a que | |
| 267 | +> `delivery`, on l'affiche **clairement étiqueté**. | |
| 268 | + | |
| 269 | +### 6.3 Diètes / tags | |
| 270 | +`vegan` · `vegetarien` · `sans-gluten` · `halal` · `casher` · `sans-noix` · `epice` | |
| 271 | + | |
| 272 | +--- | |
| 273 | + | |
| 274 | +## 7. Régions du Québec (obligatoire pour chaque restaurant) | |
| 275 | + | |
| 276 | +Bas-Saint-Laurent · Saguenay–Lac-Saint-Jean · Capitale-Nationale · Mauricie · | |
| 277 | +Estrie · Montréal · Outaouais · Abitibi-Témiscamingue · Côte-Nord · | |
| 278 | +Nord-du-Québec · Gaspésie–Îles-de-la-Madeleine · Chaudière-Appalaches · Laval · | |
| 279 | +Lanaudière · Laurentides · Montérégie · Centre-du-Québec. | |
| 280 | + | |
| 281 | +Rattacher chaque resto à sa région via la ville/adresse (table de correspondance | |
| 282 | +ville → région, comme dans Lou·Ka). | |
| 283 | + | |
| 284 | +--- | |
| 285 | + | |
| 286 | +## 8. Philosophie des connecteurs | |
| 287 | + | |
| 288 | +- **Un connecteur par source**, taillé sur mesure. | |
| 289 | +- **Connecteurs templatisés par plateforme** : c'est le levier d'échelle nº 1. Une | |
| 290 | + plateforme de commande en ligne ou de livraison couvre des **milliers** de restos | |
| 291 | + avec la même structure → un seul connecteur paramétré les couvre tous (exactement | |
| 292 | + comme les connecteurs par plateforme d'Auto·Ka et Fabri·Ka). | |
| 293 | +- **Deux types de sources à combiner** : | |
| 294 | + 1. **Découverte** (liste des restos + métadonnées) : annuaires, cartes, tourisme. | |
| 295 | + 2. **Menus + prix** (le cœur) : commande en ligne, livraison, sites de restos, PDF. | |
| 296 | +- Interface commune (à calquer sur Lou·Ka) : `fetch()` → `normalize()` → `emit()`. | |
| 297 | + Idempotent et incrémental quand c'est possible. | |
| 298 | +- **Robustesse** : timeouts, retries + backoff, tolérance aux changements mineurs, | |
| 299 | + alerte si le nombre d'items d'un menu chute anormalement (connecteur cassé). | |
| 300 | + | |
| 301 | +--- | |
| 302 | + | |
| 303 | +## 9. Catalogue des sources à connecter (priorisé) | |
| 304 | + | |
| 305 | +> ⚠️ Vérifie le registre des sources et l'index des restos **avant** d'ajouter. | |
| 306 | + | |
| 307 | +### Palier 1 — Plateformes de commande en ligne des restos (MEILLEUR prix + volume) | |
| 308 | + | |
| 309 | +Ces plateformes propulsent les **sites/apps des restos eux-mêmes** → menus complets | |
| 310 | +à des **prix réels (dine-in/takeout, non majorés)**. À templatiser par plateforme : | |
| 311 | + | |
| 312 | +- **UEAT** (foodtech de Québec, marque blanche, **très répandue au QC**). | |
| 313 | +- **Square Online Ordering**, **Toast**, **Clover**, **GloriaFood (Oracle)**, | |
| 314 | + **Flipdish**, **Foodiv**, **ChowNow**, **Bite (DoorDash)**. | |
| 315 | + | |
| 316 | +### Palier 2 — Plateformes de livraison (menus complets, prix MAJORÉS) | |
| 317 | + | |
| 318 | +Menus riches et structurés, mais prix `delivery` (+25-30 %), anti-bot fort et CGU | |
| 319 | +restrictives (voir **section 15** — risque légal réel) : | |
| 320 | + | |
| 321 | +- **Uber Eats**, **SkipTheDishes**, **DoorDash**. | |
| 322 | +- Plateformes québécoises : **RestoLoco** et autres services locaux régionaux. | |
| 323 | + | |
| 324 | +> Politique : utiliser la livraison surtout pour la **découverte** et pour les | |
| 325 | +> restos qu'on ne trouve nulle part ailleurs. Toujours étiqueter `price_context: | |
| 326 | +> delivery`. Préférer une source `dine-in` dès qu'elle existe. | |
| 327 | + | |
| 328 | +### Palier 3 — Sites web des restaurants (menus « maison ») | |
| 329 | + | |
| 330 | +- Menus en **HTML** (parse direct) ou en **PDF / image** (→ parsing PDF + OCR, | |
| 331 | + voir section 10). Détecter la plateforme de commande embarquée (souvent Palier 1). | |
| 332 | + | |
| 333 | +### Palier 4 — Découverte & métadonnées (liste des restos) | |
| 334 | + | |
| 335 | +- **Google Places / Maps**, **Yelp**, **TripAdvisor** (coordonnées, horaires, type | |
| 336 | + de cuisine, fourchette de prix — **rarement les menus** ; respecter les CGU/API). | |
| 337 | +- **Associations touristiques régionales (ATR)**, Tourisme Québec. | |
| 338 | +- Médias food : **Tastet**, **RestoMontréal** et annuaires régionaux (découverte). | |
| 339 | + | |
| 340 | +### Palier 5 — Réservation (métadonnées + parfois menus) | |
| 341 | + | |
| 342 | +- **OpenTable**, **LibroReserve** (Québec), **Resy**, **Zenchef** — horaires, | |
| 343 | + services, parfois menus. | |
| 344 | + | |
| 345 | +### Palier 6 — Chaînes (menus standardisés) | |
| 346 | + | |
| 347 | +- Sites des grandes chaînes : menus et prix souvent uniformes par bannière et | |
| 348 | + parfois par région → un connecteur par chaîne couvre toutes les succursales. | |
| 349 | + | |
| 350 | +--- | |
| 351 | + | |
| 352 | +## 10. Extraction — Firecrawl, Scrapfly & parsing de menus | |
| 353 | + | |
| 354 | +Les connecteurs s'appuient **souvent sur Firecrawl et Scrapfly** (comme tout | |
| 355 | +l'écosystème `·Ka`). Règle de choix : | |
| 356 | + | |
| 357 | +- **API / endpoint JSON de la plateforme** → toujours privilégié (données | |
| 358 | + structurées, stables). Beaucoup de plateformes de commande chargent leur menu | |
| 359 | + via un appel JSON interne : **le trouver et le consommer directement.** | |
| 360 | +- **Firecrawl** → crawl et extraction en volume de sites relativement ouverts | |
| 361 | + (sites de restos, annuaires), extraction structurée (markdown/JSON). | |
| 362 | +- **Scrapfly** → sites protégés, JavaScript lourd, anti-bot, rendu navigateur, | |
| 363 | + rotation d'IP. **Requis** pour les plateformes de livraison (Uber Eats, Skip, | |
| 364 | + DoorDash) qui bloquent agressivement. | |
| 365 | +- **Parsing de menus PDF / images** : de nombreux restos publient un menu en PDF ou | |
| 366 | + en photo. Prévoir extraction de texte PDF + **OCR** + un post-traitement (LLM) qui | |
| 367 | + transforme le texte brut en structure `sections → items → price → options`. | |
| 368 | + Valider systématiquement (un prix mal OCR-isé est pire que pas de prix). | |
| 369 | + | |
| 370 | +Chaque connecteur doit **documenter** en tête de fichier son mode d'extraction et | |
| 371 | +pourquoi. Réutiliser les clients partagés de Lou·Ka. | |
| 372 | + | |
| 373 | +--- | |
| 374 | + | |
| 375 | +## 11. Normalisation | |
| 376 | + | |
| 377 | +- Nettoyer noms de restos, sections et plats (HTML résiduel, majuscules, doublons). | |
| 378 | +- Mapper vers le **schéma Restaurant + Menu** (section 5) intégralement. | |
| 379 | +- **Cuisines** et **type d'établissement** classés dès la normalisation (section 6). | |
| 380 | +- **Géocoder** (lat/lng obligatoires) et **rattacher la région** (section 7). | |
| 381 | +- **Prix** : renseigner **obligatoirement** `price_context` et `price_source` ; | |
| 382 | + capturer les **options/modificateurs**. Ne jamais mélanger des prix de contextes | |
| 383 | + différents dans un même menu. | |
| 384 | +- **Devise** CAD ; documenter le traitement des taxes (TPS/TVQ hors prix au QC). | |
| 385 | +- Conserver les **`source_ids`** préfixés (ex. `ueat:1234`, `ubereats:abcd`). | |
| 386 | + | |
| 387 | +--- | |
| 388 | + | |
| 389 | +## 12. Déduplication — restaurants ET items | |
| 390 | + | |
| 391 | +Un même resto apparaît sur Uber Eats + DoorDash + Skip + son propre site + Google. | |
| 392 | + | |
| 393 | +### 12.1 Identité du restaurant | |
| 394 | +1. **Clé** : nom normalisé + adresse (géoloc arrondie) + téléphone → empreinte. | |
| 395 | +2. **Approché** : noms proches + même adresse → fusion candidate (journaliser). | |
| 396 | +3. **Fusion** : un enregistrement canonique, agréger les `source_ids`, choisir la | |
| 397 | + meilleure source par champ. | |
| 398 | +4. **Multi-succursales / chaînes** : chaque **adresse** est une fiche resto | |
| 399 | + distincte, reliée par `chain`. Ne pas fusionner deux succursales. | |
| 400 | + | |
| 401 | +### 12.2 Menus & prix | |
| 402 | +- Ne pas écraser un menu `dine-in` par un menu `delivery` : **conserver les deux | |
| 403 | + contextes** et présenter le meilleur (dine-in prioritaire), en gardant delivery | |
| 404 | + étiqueté. | |
| 405 | +- Dédupliquer les **items** au sein d'un même contexte (même plat listé deux fois). | |
| 406 | +- **Historiser** les prix : à chaque capture, si le prix d'un item change, garder la | |
| 407 | + série temporelle (utile pour détecter les hausses et pour la crédibilité). | |
| 408 | + | |
| 409 | +S'inspirer de la déduplication de **Lou·Ka** (multi-plateformes) et d'Auto·Ka | |
| 410 | +(identité forte par clé). | |
| 411 | + | |
| 412 | +--- | |
| 413 | + | |
| 414 | +## 13. Fraîcheur des menus & états | |
| 415 | + | |
| 416 | +- **Menus changeants** : prix et plats évoluent (saisonnier, table d'hôte, | |
| 417 | + spéciaux). Rafraîchir régulièrement et **horodater** chaque capture (`captured_at`). | |
| 418 | +- **Fermetures** : resto fermé temporairement / définitivement → `status` mis à jour | |
| 419 | + plutôt que suppression brutale (politique de grâce via `last_seen`, comme Lou·Ka). | |
| 420 | +- **Items retirés** : disparition d'un plat → le marquer inactif, conserver | |
| 421 | + l'historique. | |
| 422 | +- **Spéciaux / table d'hôte** : capturer si structurés, avec leur période de validité. | |
| 423 | + | |
| 424 | +--- | |
| 425 | + | |
| 426 | +## 14. Cadence de rafraîchissement | |
| 427 | + | |
| 428 | +- Grandes plateformes (commande en ligne, livraison, chaînes) : **hebdomadaire** au | |
| 429 | + minimum ; **quotidien** pour les prix sur les gros volumes urbains. | |
| 430 | +- Sites de restos indépendants : **hebdomadaire à mensuel** (menus stables). | |
| 431 | +- Découverte (annuaires) : **mensuel** pour repérer nouveaux restos / fermetures. | |
| 432 | +- Toujours mettre à jour `last_seen` et `captured_at` à chaque passage réussi. | |
| 433 | + | |
| 434 | +--- | |
| 435 | + | |
| 436 | +## 15. Conformité & aspects légaux — À PRENDRE AU SÉRIEUX | |
| 437 | + | |
| 438 | +> C'est le point le plus sensible de ce projet. Les plateformes de livraison | |
| 439 | +> **protègent activement** leurs données (menus/prix) et l'interdisent dans leurs | |
| 440 | +> CGU. Ne pas traiter ça à la légère. | |
| 441 | + | |
| 442 | +- Respecter les **CGU** et `robots.txt` de chaque source. Les plateformes de | |
| 443 | + livraison sont explicitement restrictives : **minimiser**, privilégier d'autres | |
| 444 | + sources pour les mêmes restos, et **demander** en cas de doute avant d'industrialiser. | |
| 445 | +- Privilégier les **sources de première partie** : le **site du resto** et sa | |
| 446 | + **plateforme de commande** (UEAT, etc.) sont la voie la plus légitime pour le | |
| 447 | + menu et le prix réel. | |
| 448 | +- **Rate-limiting** raisonnable, identification honnête, pas de surcharge. | |
| 449 | +- Utiliser les **API officielles** quand elles existent (Google Places, Yelp) et | |
| 450 | + en respecter les quotas et conditions d'affichage/attribution. | |
| 451 | +- Ne republier que ce qui est nécessaire à la découverte (nom, plats, prix, infos | |
| 452 | + pratiques, lien vers la source). **Attribuer** les sources requises. | |
| 453 | +- Consigner par source la base légale/mode d'accès retenu (API vs scraping) en tête | |
| 454 | + de connecteur. | |
| 455 | + | |
| 456 | +--- | |
| 457 | + | |
| 458 | +## 16. Conventions de code | |
| 459 | + | |
| 460 | +- Suivre **les mêmes conventions que Lou·Ka** (langage, structure de dossiers, | |
| 461 | + nommage, linting, formatage). Cohérence > préférences personnelles. | |
| 462 | +- Un connecteur = un module isolé, testable indépendamment. | |
| 463 | +- Secrets (clés Firecrawl/Scrapfly, API Google/Yelp) via variables d'environnement / | |
| 464 | + gestionnaire de secrets. Jamais en clair. | |
| 465 | +- Logging structuré (source, nb de restos, nb d'items, erreurs, durée). | |
| 466 | +- Commits atomiques, messages clairs. | |
| 467 | + | |
| 468 | +--- | |
| 469 | + | |
| 470 | +## 17. En-tête d'auteur — OBLIGATOIRE dans chaque fichier | |
| 471 | + | |
| 472 | +**Tout fichier** du dépôt doit débuter par un en-tête d'auteur. L'auteur est | |
| 473 | +toujours **Simon-Pierre Boucher \<contact@spboucher.ai\>**. | |
| 474 | + | |
| 475 | +**Python / Shell / YAML / TOML / Dockerfile :** | |
| 476 | +```python | |
| 477 | +# ============================================================================== | |
| 478 | +# Author: Simon-Pierre Boucher <contact@spboucher.ai> | |
| 479 | +# File: <nom_du_fichier> | |
| 480 | +# Desc: <brève description> | |
| 481 | +# ============================================================================== | |
| 482 | +``` | |
| 483 | + | |
| 484 | +**JavaScript / TypeScript / Go / Rust / Java / C :** | |
| 485 | +```javascript | |
| 486 | +// ============================================================================== | |
| 487 | +// Author: Simon-Pierre Boucher <contact@spboucher.ai> | |
| 488 | +// File: <nom_du_fichier> | |
| 489 | +// Desc: <brève description> | |
| 490 | +// ============================================================================== | |
| 491 | +``` | |
| 492 | + | |
| 493 | +**Markdown / HTML / XML :** | |
| 494 | +```html | |
| 495 | +<!-- | |
| 496 | + Author: Simon-Pierre Boucher <contact@spboucher.ai> | |
| 497 | + File: <nom_du_fichier> | |
| 498 | + Desc: <brève description> | |
| 499 | +--> | |
| 500 | +``` | |
| 501 | + | |
| 502 | +**CSS / SCSS :** | |
| 503 | +```css | |
| 504 | +/* | |
| 505 | + Author: Simon-Pierre Boucher <contact@spboucher.ai> | |
| 506 | + File: <nom_du_fichier> | |
| 507 | +*/ | |
| 508 | +``` | |
| 509 | + | |
| 510 | +Règles : | |
| 511 | +- **Aucun** fichier commité sans cet en-tête. | |
| 512 | +- Pour un connecteur, indiquer dans `Desc:` la **source/plateforme couverte**, le | |
| 513 | + **mode d'extraction** (API / Firecrawl / Scrapfly / PDF-OCR) et le **contexte de | |
| 514 | + prix** produit (dine-in / takeout / delivery). | |
| 515 | +- Vérifier la présence de l'en-tête en revue de code (idéalement hook de pré-commit). | |
| 516 | + | |
| 517 | +--- | |
| 518 | + | |
| 519 | +## 18. Tests & qualité | |
| 520 | + | |
| 521 | +- Chaque connecteur : **tests unitaires** de normalisation sur des fixtures réelles | |
| 522 | + (réponses JSON, HTML, PDF de menus figés). | |
| 523 | +- Tests de **parsing de menus** : vérifier prix, options et sections sur des | |
| 524 | + échantillons connus (les prix sont l'actif le plus critique). | |
| 525 | +- Tests de **déduplication** resto + items multi-sources. | |
| 526 | +- **Validation de schéma** : tout resto/menu émis doit passer la validation avant | |
| 527 | + stockage ; rejeter un menu sans `price_context`/`price_source`. | |
| 528 | +- **Monitoring** : alerte si le nb d'items d'un resto ou le volume d'une source | |
| 529 | + chute anormalement (connecteur probablement cassé). | |
| 530 | + | |
| 531 | +--- | |
| 532 | + | |
| 533 | +## 19. Checklist — ajouter un nouveau connecteur / restaurant | |
| 534 | + | |
| 535 | +1. [ ] Lire les connecteurs de référence dans le **dépôt Lou·Ka** (section 3). | |
| 536 | +2. [ ] Vérifier que la source/le resto **n'est pas déjà couvert** (registres). | |
| 537 | +3. [ ] Identifier la **plateforme** : peut-on templatiser/réutiliser un connecteur ? | |
| 538 | +4. [ ] Choisir le **mode d'extraction** : API > site du resto > Firecrawl > Scrapfly ; | |
| 539 | + PDF/OCR si menu non structuré (section 10). | |
| 540 | +5. [ ] Déterminer le **`price_context`** produit et le documenter. | |
| 541 | +6. [ ] Implémenter `fetch()` → `normalize()` → `emit()` (interface commune). | |
| 542 | +7. [ ] Mapper **tout** le schéma resto + menu, géocoder, région, cuisines, options. | |
| 543 | +8. [ ] Brancher la **déduplication** resto + items (section 12). | |
| 544 | +9. [ ] Gérer **fraîcheur, fermetures, items retirés, historique des prix** (§13). | |
| 545 | +10. [ ] Vérifier la **conformité** CGU/robots et consigner la base d'accès (§15). | |
| 546 | +11. [ ] Ajouter l'**en-tête d'auteur** (section 17). | |
| 547 | +12. [ ] Écrire les **tests** + fixtures, dont le parsing de prix (section 18). | |
| 548 | +13. [ ] Enregistrer la source dans le **registre** avec sa cadence. | |
| 549 | +14. [ ] Valider volume et qualité sur un premier run, journaliser. | |
| 550 | + | |
| 551 | +--- | |
| 552 | + | |
| 553 | +## 20. Roadmap / MVP | |
| 554 | + | |
| 555 | +**MVP** : templatiser **un** connecteur pour la plateforme de commande en ligne la | |
| 556 | +plus répandue au QC (**UEAT**) → menus complets à prix réels (`dine-in`/`takeout`) | |
| 557 | +pour des milliers de restos d'un coup. Ajouter la découverte via Google Places/Yelp | |
| 558 | +pour la liste et les métadonnées, et le parsing des menus PDF des sites de restos. | |
| 559 | +UI de recherche géolocalisée avec filtres (cuisine, prix, diète, service) et | |
| 560 | +affichage clair du **contexte de prix**. | |
| 561 | + | |
| 562 | +**Ensuite** : plateformes de livraison (avec étiquetage strict), chaînes, | |
| 563 | +réservation, régions moins couvertes. Puis historique des prix, comparaison entre | |
| 564 | +restos, et cross-linking avec les autres sites `·Ka`. | |
| 565 | + | |
| 566 | +**Nord stratégique** : être **le seul** à offrir menus **et** prix **comparables** | |
| 567 | +de **tous** les restos du Québec — là où la livraison est chère et partielle, les | |
| 568 | +annuaires sans menus, et les médias food non exhaustifs. | |
| 569 | + | |
| 570 | +--- | |
| 571 | + | |
| 572 | +*Fin de CLAUDE.md — Resto·Ka. Auteur : Simon-Pierre Boucher \<contact@spboucher.ai\>.* | |
added
README.md
+106 −0
@@ -0,0 +1,106 @@ | ||
| 1 | +<!-- | |
| 2 | + Author: Simon-Pierre Boucher <contact@spboucher.ai> | |
| 3 | + File: README.md | |
| 4 | + Desc: Resto·Ka — agrégateur des restaurants du Québec (menus & prix). | |
| 5 | +--> | |
| 6 | + | |
| 7 | +# Resto·Ka — [resto-ka.com](https://www.resto-ka.com) | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | +> **Agrégateur exhaustif de tous les restaurants du Québec, avec leurs menus | |
| 23 | +> complets et leurs prix.** Chaque resto, chaque plat, chaque prix — dans les | |
| 24 | +> 17 régions, au même endroit, comparable et cherchable — **par resto ou par | |
| 25 | +> plat**. | |
| 26 | + | |
| 27 | +Plateforme de la famille **·Ka** (Lou·Ka, Immo·Ka, Auto·Ka, Food·Ka, Fabri·Ka, | |
| 28 | +Sorti·Ka). Architecture des connecteurs **calquée sur Lou·Ka**, identité | |
| 29 | +visuelle **« éditorial sharp » du Groupe KA** (Space Grotesk / Inter / | |
| 30 | +JetBrains Mono, papier-encre-lime, bordures encre + ombres décalées). | |
| 31 | +`CLAUDE.md` est la source de vérité du projet. | |
| 32 | + | |
| 33 | +## L'app | |
| 34 | + | |
| 35 | +| Accueil — recherche de restos | Recherche par plat | | |
| 36 | +|---|---| | |
| 37 | +|  |  | | |
| 38 | + | |
| 39 | +| Fiche resto — menu, photos, options & prix | Couverture en direct | | |
| 40 | +|---|---| | |
| 41 | +|  |  | | |
| 42 | + | |
| 43 | +## Ce qui rend Resto·Ka différent | |
| 44 | + | |
| 45 | +- **Prix réels** : captés sur la plateforme de commande des restos eux-mêmes | |
| 46 | + (Palier 1), pas sur les apps de livraison (+25-30 %). | |
| 47 | +- **Un prix n'est JAMAIS présenté sans son contexte** (`dine-in` / `takeout` / | |
| 48 | + `delivery`) — un menu sans `price_context`/`price_source` est rejeté à | |
| 49 | + l'ingestion, et un menu salle n'est jamais écrasé par un menu livraison. | |
| 50 | +- **Recherche par plat** dans les 61 330 items (groupée par marque pour ne pas | |
| 51 | + répéter 76 fois la même poutine de chaîne). | |
| 52 | +- **Options & formats capturés** avec leurs suppléments (« Mini +3,70 $ ») — | |
| 53 | + ils changent le prix réel. | |
| 54 | +- **Historique des prix par item** (`item_price_log`) à chaque changement. | |
| 55 | +- **Alerte de dérive** : chute anormale du volume d'une source ou du nombre | |
| 56 | + d'items d'un menu → retraits suspendus / ancien menu conservé. | |
| 57 | + | |
| 58 | +## Démarrage | |
| 59 | + | |
| 60 | +```bash | |
| 61 | +python3 -m venv .venv && .venv/bin/pip install -r requirements.txt | |
| 62 | +cd frontend && npm install && npm run build && cd .. | |
| 63 | + | |
| 64 | +.venv/bin/python run.py sync # synchronise (+ géocode + dédup) | |
| 65 | +.venv/bin/python run.py serve 8115 # API + frontend sur :8115 | |
| 66 | +.venv/bin/python run.py watch 168 # boucle hebdomadaire | |
| 67 | +.venv/bin/python -m pytest tests/ # 41 tests (fixtures réelles hors ligne) | |
| 68 | +``` | |
| 69 | + | |
| 70 | +## Architecture (patron Lou·Ka) | |
| 71 | + | |
| 72 | +``` | |
| 73 | +restoka/ | |
| 74 | +├── connectors/ # un module par source/plateforme, auto-enregistrés | |
| 75 | +│ ├── base.py # requests direct / Firecrawl / Scrapfly + cache détail | |
| 76 | +│ └── ueat.py # UEAT (templatisé) — API GraphQL, prix TAKEOUT réels | |
| 77 | +├── schema.py # Restaurant + menu imbriqué (sections→items→options) + finalize() | |
| 78 | +├── normalize.py # prix, cuisines, type d'établissement, diètes, $-$$$$ | |
| 79 | +├── regions.py # ville → 17 régions (+ repli postal, + repli lat/lng) | |
| 80 | +├── db.py # SQLite : restos, menus PAR CONTEXTE, historique de prix | |
| 81 | +├── ingest.py # pipeline sync → geocode → dedup (délai de grâce, alertes) | |
| 82 | +├── dedup.py # identité resto inter-sources (succursales jamais fusionnées) | |
| 83 | +├── geocode.py # Nominatim + Adresses Québec, cache persistant | |
| 84 | +└── web.py # FastAPI + frontend React (frontend/dist) | |
| 85 | +frontend/ # Vite + React 18 + TS — design éditorial sharp Groupe KA | |
| 86 | +``` | |
| 87 | + | |
| 88 | +## Sources connectées | |
| 89 | + | |
| 90 | +Registre : `data/sources.json` (à consulter AVANT d'ajouter quoi que ce soit). | |
| 91 | + | |
| 92 | +| Source | Palier | Contexte de prix | Couverture | | |
| 93 | +|--------|--------|------------------|------------| | |
| 94 | +| **UEAT** (commande en ligne, API GraphQL anonyme) | 1 | `takeout` (prix réels) | 19 intégrations : Sushi Shop (141), Poulet Rouge (82), Valentine (76), Normandin (44), Chocolats Favoris (41), Allô mon Coco (34), Thaïzone (26), Scores (25), Chez Ashton (23), Pacini (20), Tutti Frutti, Mandy's, Panda Boba-T, Ryu Sushi, Impérial, Pizza Mia, Station W, Le Super Qualité, Château Frontenac | | |
| 95 | +| St-Hubert | 6 | `takeout` (visé) | à faire — anti-bot Akamai, voir `data/sources.json` | | |
| 96 | + | |
| 97 | +## Déploiement | |
| 98 | + | |
| 99 | +Node **M3U96b** (MacLustr), `~/apps/resto-ka`, PM2 : `resto-ka` (:8115) + | |
| 100 | +`resto-ka-ngrok` (→ https://www.resto-ka.com) + `resto-ka-sync` (hebdo). | |
| 101 | +Registre cross-session : `~/Desktop/cluster-skill/cluster-deployments.json`. | |
| 102 | + | |
| 103 | +--- | |
| 104 | + | |
| 105 | +*Resto·Ka — plateforme du [Groupe KA](https://www.groupe-ka.com). Auteur : | |
| 106 | +Simon-Pierre Boucher \<contact@spboucher.ai\>.* | |
added
data/sources.json
+54 −0
@@ -0,0 +1,54 @@ | ||
| 1 | +{ | |
| 2 | + "_comment": "Resto·Ka — Registre des sources (restaurants du Québec, menus & prix). Auteur : Simon-Pierre Boucher — contact@spboucher.ai. Vérifier ce registre AVANT d'ajouter une source ou un resto (CLAUDE.md §0.4).", | |
| 3 | + "sources": [ | |
| 4 | + { | |
| 5 | + "id": "ueat", | |
| 6 | + "name": "UEAT (commande en ligne)", | |
| 7 | + "url": "https://ueat.io", | |
| 8 | + "platform": "ueat", | |
| 9 | + "connector": "ueat", | |
| 10 | + "tier": 1, | |
| 11 | + "price_context": "takeout", | |
| 12 | + "extraction": "API GraphQL interne (api.ueat.io/graphql), auth anonyme par GUID d'intégration — la même API que le widget officiel de commande", | |
| 13 | + "acces_legal": "Source de première partie : plateforme de commande des restos eux-mêmes, prix réels non majorés (CLAUDE.md §9 Palier 1, §15). API anonyme publique, throttling poli 0,35 s, User-Agent identifiable RestoKaBot.", | |
| 14 | + "cadence": "hebdomadaire", | |
| 15 | + "status": "actif", | |
| 16 | + "integrations": [ | |
| 17 | + { "key": "4268100d-6470-40b9-a755-e11342c91d6d", "label": "Poulet Rouge (chaîne)" }, | |
| 18 | + { "key": "01d1c391-59cf-45a3-b051-7b2f9a623858", "label": "Fairmont Le Château Frontenac (Québec)" }, | |
| 19 | + { "key": "b7282ce3-95cc-43ff-80b5-0cca8a7e4778", "label": "Allô mon Coco (chaîne, déjeuner-brunch)" }, | |
| 20 | + { "key": "b1c2c004-bcff-4eb0-b3b9-d3bc5c8799a3", "label": "Restaurant Impérial (Brossard, Pointe-Claire)" }, | |
| 21 | + { "key": "2f51961a-f378-4812-a31a-a0b45da3f87e", "label": "Pizza Mia (Laval)" }, | |
| 22 | + { "key": "1b432c57-98d6-4681-a560-d1b7bfafdd6f", "label": "Station W (Montréal, café)" }, | |
| 23 | + { "key": "ff376cab-30e2-4cce-9999-ed755583c7f5", "label": "Chocolats Favoris (chaîne, dessert)" }, | |
| 24 | + { "key": "c5548a05-1776-49ac-9c9f-2ac8c998eed4", "label": "Le Super Qualité (Montréal, indien)" }, | |
| 25 | + { "key": "d84801b5-66f5-44b3-b0dc-52c0365f571c", "label": "Ryu Sushi (Montréal)" }, | |
| 26 | + { "key": "87c690ed-be3c-4a5d-b58b-676235faa9c1", "label": "Panda Boba-T (chaîne, bubble tea)" }, | |
| 27 | + { "key": "970d8e90-55ca-4655-9631-a8f9af08f4b7", "label": "Chez Ashton (23 succ., poutine)" }, | |
| 28 | + { "key": "e473d88d-b405-41ee-9ee8-2aa628eee604", "label": "Thaïzone (26 succ. QC)" }, | |
| 29 | + { "key": "f89d74e1-c2ab-4ace-8bce-39069900f5b4", "label": "Normandin (44 succ., familial)" }, | |
| 30 | + { "key": "e15b2545-2f2a-4e5f-baa6-00ab849cfa16", "label": "Valentine (76 succ. QC, casse-croûte)" }, | |
| 31 | + { "key": "657f8ce3-d288-4245-aecf-7ca017364b8e", "label": "Tutti Frutti (9 succ. QC, déjeuner)" }, | |
| 32 | + { "key": "00f8d52b-7aaf-4bcf-8722-6d538aad5a9a", "label": "Pacini (20 succ. QC, italien)" }, | |
| 33 | + { "key": "d679a49f-0a25-4694-82f2-56073a8b8790", "label": "Scores (25 succ., rôtisserie)" }, | |
| 34 | + { "key": "1696ecd3-d4ea-4224-9f98-9299ebcb339c", "label": "Mandy's (9 succ. QC, salades)" }, | |
| 35 | + { "key": "dce3e009-8038-47bc-8d5e-f336458e573f", "label": "Sushi Shop (141 succ. QC)" }, | |
| 36 | + { "key": "e8015de3-09ac-4cd2-a18d-0820dbe356ed", "label": "(intégration retirée chez UEAT)", "status": "inactif" }, | |
| 37 | + { "key": "dfde098b-ee0f-46a3-b1ad-09ec2acb54b9", "label": "(intégration retirée chez UEAT)", "status": "inactif" } | |
| 38 | + ] | |
| 39 | + }, | |
| 40 | + { | |
| 41 | + "id": "sthubert", | |
| 42 | + "name": "St-Hubert (commande en ligne)", | |
| 43 | + "url": "https://commande.st-hubert.com", | |
| 44 | + "platform": "st-hubert", | |
| 45 | + "connector": null, | |
| 46 | + "tier": 6, | |
| 47 | + "price_context": "takeout", | |
| 48 | + "extraction": "À déterminer — Akamai anti-bot sur st-hubert.com ; commande.st-hubert.com rend une page vide même via Scrapfly ASP (flux JS multi-étapes, sélection de succursale requise). Prochain essai : rejouer les XHR internes après sélection d'une succursale (js_scenario Scrapfly).", | |
| 49 | + "acces_legal": "Site de commande de la chaîne elle-même (prix réels). À industrialiser prudemment (CLAUDE.md §15).", | |
| 50 | + "cadence": "hebdomadaire (visé)", | |
| 51 | + "status": "à faire" | |
| 52 | + } | |
| 53 | + ] | |
| 54 | +} | |
added
docs/screenshots/accueil.png
+0 −0
Binary file not shown.
added
docs/screenshots/fiche.png
+0 −0
Binary file not shown.
added
docs/screenshots/plats.png
+0 −0
Binary file not shown.
added
docs/screenshots/stats.png
+0 −0
Binary file not shown.
added
frontend/index.html
+27 −0
@@ -0,0 +1,27 @@ | ||
| 1 | +<!doctype html> | |
| 2 | +<!-- --------------------------------------------------------------------------- | |
| 3 | + Author: Simon-Pierre Boucher <contact@spboucher.ai> | |
| 4 | + File: index.html | |
| 5 | + Desc: Resto·Ka — agrégateur des restaurants du Québec (menus & prix). | |
| 6 | +---------------------------------------------------------------------------- --> | |
| 7 | +<html lang="fr"> | |
| 8 | + <head> | |
| 9 | + <meta charset="UTF-8" /> | |
| 10 | + <meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" /> | |
| 11 | + <title>Resto-Ka — Tous les restaurants du Québec, menus et prix</title> | |
| 12 | + <meta name="description" content="Resto-Ka agrège les restaurants du Québec avec leurs menus complets et leurs prix réels — comparables, cherchables, dans les 17 régions. Chaque resto, chaque plat, chaque prix." /> | |
| 13 | + <meta name="theme-color" content="#ffffff" /> | |
| 14 | + <meta name="mobile-web-app-capable" content="yes" /> | |
| 15 | + <meta name="apple-mobile-web-app-capable" content="yes" /> | |
| 16 | + <meta name="apple-mobile-web-app-status-bar-style" content="default" /> | |
| 17 | + <meta name="apple-mobile-web-app-title" content="Resto-Ka" /> | |
| 18 | + <link rel="preconnect" href="https://fonts.googleapis.com" /> | |
| 19 | + <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin /> | |
| 20 | + <link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@500;700&family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@500;700&display=swap" rel="stylesheet" /> | |
| 21 | + <link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64'%3E%3Crect width='64' height='64' rx='12' fill='%23141814'/%3E%3Ccircle cx='32' cy='32' r='21' fill='none' stroke='%23d9f26b' stroke-width='5'/%3E%3Ccircle cx='32' cy='32' r='9.5' fill='%23d9f26b'/%3E%3Cpath d='M13 20 v10 M9.5 20 v6 M16.5 20 v6' stroke='%23f5f3ee' stroke-width='2.6' stroke-linecap='round'/%3E%3Cpath d='M51.5 20 v24' stroke='%23f5f3ee' stroke-width='2.6' stroke-linecap='round'/%3E%3C/svg%3E" /> | |
| 22 | + </head> | |
| 23 | + <body> | |
| 24 | + <div id="root"></div> | |
| 25 | + <script type="module" src="/src/main.tsx"></script> | |
| 26 | + </body> | |
| 27 | +</html> | |
added
frontend/package-lock.json
+1834 −0
Large diff (1,834 lines) is not loaded by default to keep the page fast.Load diff
added
frontend/package.json
+24 −0
Large diff (24 lines) is not loaded by default to keep the page fast.Load diff
added
frontend/src/App.tsx
+209 −0
Large diff (209 lines) is not loaded by default to keep the page fast.Load diff
added
frontend/src/api.ts
+244 −0
Large diff (244 lines) is not loaded by default to keep the page fast.Load diff
added
frontend/src/components/Icons.tsx
+80 −0
Large diff (80 lines) is not loaded by default to keep the page fast.Load diff
added
frontend/src/components/Logo.tsx
+68 −0
Large diff (68 lines) is not loaded by default to keep the page fast.Load diff
added
frontend/src/components/Pager.tsx
+50 −0
Large diff (50 lines) is not loaded by default to keep the page fast.Load diff
added
frontend/src/components/RestoCard.tsx
+66 −0
Large diff (66 lines) is not loaded by default to keep the page fast.Load diff
added
frontend/src/main.tsx
+18 −0
Large diff (18 lines) is not loaded by default to keep the page fast.Load diff
added
frontend/src/pages/Home.tsx
+370 −0
Large diff (370 lines) is not loaded by default to keep the page fast.Load diff
added
frontend/src/pages/Resto.tsx
+237 −0
Large diff (237 lines) is not loaded by default to keep the page fast.Load diff
added
frontend/src/pages/Sources.tsx
+76 −0
Large diff (76 lines) is not loaded by default to keep the page fast.Load diff
added
frontend/src/pages/Stats.tsx
+129 −0
Large diff (129 lines) is not loaded by default to keep the page fast.Load diff
added
frontend/src/styles.css
+812 −0
Large diff (812 lines) is not loaded by default to keep the page fast.Load diff
added
frontend/tsconfig.json
+21 −0
Large diff (21 lines) is not loaded by default to keep the page fast.Load diff
added
frontend/vite.config.ts
+16 −0
Large diff (16 lines) is not loaded by default to keep the page fast.Load diff
added
requirements.txt
+9 −0
Large diff (9 lines) is not loaded by default to keep the page fast.Load diff
added
restoka/__init__.py
+6 −0
Large diff (6 lines) is not loaded by default to keep the page fast.Load diff
added
restoka/connectors/__init__.py
+29 −0
Large diff (29 lines) is not loaded by default to keep the page fast.Load diff
added
restoka/connectors/base.py
+157 −0
Large diff (157 lines) is not loaded by default to keep the page fast.Load diff
added
restoka/connectors/ueat.py
+343 −0
Large diff (343 lines) is not loaded by default to keep the page fast.Load diff
added
restoka/db.py
+414 −0
Large diff (414 lines) is not loaded by default to keep the page fast.Load diff
added
restoka/dedup.py
+131 −0
Large diff (131 lines) is not loaded by default to keep the page fast.Load diff
added
restoka/geocode.py
+173 −0
Large diff (173 lines) is not loaded by default to keep the page fast.Load diff
added
restoka/ingest.py
+87 −0
Large diff (87 lines) is not loaded by default to keep the page fast.Load diff
added
restoka/normalize.py
+203 −0
Large diff (203 lines) is not loaded by default to keep the page fast.Load diff
added
restoka/regions.py
+241 −0
Large diff (241 lines) is not loaded by default to keep the page fast.Load diff
added
restoka/schema.py
+154 −0
Large diff (154 lines) is not loaded by default to keep the page fast.Load diff
added
restoka/web.py
+409 −0
Large diff (409 lines) is not loaded by default to keep the page fast.Load diff
added
run.py
+59 −0
Large diff (59 lines) is not loaded by default to keep the page fast.Load diff
added
tests/conftest.py
+25 −0
Large diff (25 lines) is not loaded by default to keep the page fast.Load diff
added
tests/fixtures/ueat/poulet_rouge.json
+76 −0
Large diff (76 lines) is not loaded by default to keep the page fast.Load diff
added
tests/test_normalize.py
+107 −0
Large diff (107 lines) is not loaded by default to keep the page fast.Load diff
added
tests/test_schema_db.py
+145 −0
Large diff (145 lines) is not loaded by default to keep the page fast.Load diff
added
tests/test_ueat.py
+102 −0
Large diff (103 lines) is not loaded by default to keep the page fast.Load diff