docs: README v3 — chiffres live vérifiés, pastilles dynamiques, sections complètes
1 changed file +94 −19
modified
README.md
+94 −19
@@ -13,18 +13,31 @@ | ||
| 13 | 13 | [](https://www.food-ka.com/doc/food-ka-documentation.pdf) |
| 14 | 14 |  |
| 15 | 15 |  |
| 16 | − | |
| 16 | + | |
| 17 | + | |
| 18 | +[](https://www.food-ka.com) | |
| 19 | +[](https://www.food-ka.com/sources) | |
| 20 | +[](https://www.food-ka.com/aubaines) | |
| 21 | +[](https://www.food-ka.com) | |
| 22 | + | |
| 17 | 23 |  |
| 18 | 24 |  |
| 19 | − | |
| 25 | + | |
| 26 | + | |
| 20 | 27 |  |
| 21 | − | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 22 | 33 | |
| 23 | 34 | </div> |
| 24 | 35 | |
| 36 | +> Les pastilles de la deuxième rangée sont **dynamiques** : elles interrogent [`/api/stats`](https://www.food-ka.com/api/stats) en direct. | |
| 37 | + | |
| 25 | 38 | **Food-Ka** est un agrégateur et comparateur indépendant de produits d'épicerie couvrant tout le Québec. Comparer les prix d'épicerie, c'est normalement ouvrir Metro, IGA, Maxi, Super C, Provigo, Walmart… chacun avec sa propre navigation, son panier, son format. Food-Ka retourne le problème : un **connecteur dédié par bannière** visite chaque site, **normalise chaque produit** vers un schéma unique (avec **prix unitaire comparable en $/100 g**) et **détecte les changements de prix en continu**. |
| 26 | 39 | |
| 27 | −Les épiceries n'offrent pas de webhooks ; Food-Ka en reproduit l'équivalent : synchronisation périodique + hash de contenu → nouveaux produits, changements de prix et retraits détectés automatiquement, chaque variation étant **historisée** (`price_log`). En date du 2026-08-24, le catalogue compte **50 892 produits** provenant de **57 sources** dans **18 catégories** canoniques, dont **8 777 produits en solde** — grandes bannières (Metro, Super C, IGA/Voilà, Maxi, Provigo, Walmart…) comme spécialisées et indépendantes (Mayrand, Avril, PA, Tau, Giant Tiger, SAQ…). | |
| 40 | +Les épiceries n'offrent pas de webhooks ; Food-Ka en reproduit l'équivalent : synchronisation périodique + hash de contenu → nouveaux produits, changements de prix et retraits détectés automatiquement, chaque variation étant **historisée** (`price_log`). Au 2026-08-24, le catalogue compte **50 892 produits** provenant de **57 sources** dans **18 catégories** canoniques, dont **8 818 produits en solde** — grandes bannières (Metro, Super C, IGA/Voilà, Maxi, Provigo, Walmart…) comme spécialisées et indépendantes (Mayrand, Avril, PA, Tau, Giant Tiger, SAQ…). | |
| 28 | 41 | |
| 29 | 42 | ## Visite guidée |
| 30 | 43 | |
@@ -57,6 +70,47 @@ Les épiceries n'offrent pas de webhooks ; Food-Ka en reproduit l'équivalent : | ||
| 57 | 70 | - **PWA installable** — design « éditorial sharp » (Space Grotesk, accent lime, ticker temps réel), mobile-first, design system **ka-ui** partagé, widget **KA Agent** (bulle de chat IA). |
| 58 | 71 | - **Fidélité et politesse** — aucun prix inventé (`price = null` si absent), prix régulier incohérent rejeté, throttle entre requêtes, User-Agent identifié, journal `sync_log`. |
| 59 | 72 | |
| 73 | +## Démarrage rapide | |
| 74 | + | |
| 75 | +```bash | |
| 76 | +ssh M4M64b && cd ~/apps/food-ka # source de vérité : le nœud | |
| 77 | + | |
| 78 | +# Backend | |
| 79 | +python3 -m venv .venv | |
| 80 | +.venv/bin/pip install -r requirements.txt | |
| 81 | +.venv/bin/python run.py sync # synchroniser toutes les bannières | |
| 82 | +.venv/bin/python run.py sync metro iga saq # ...ou seulement certaines (id du registre data/sources.json) | |
| 83 | +.venv/bin/python run.py serve 8080 # API + PWA + SSR en local | |
| 84 | + | |
| 85 | +# Frontend (build servi ensuite par FastAPI) | |
| 86 | +cd frontend && npm install && npm run build && cd .. | |
| 87 | +cd frontend && npm run dev # ...ou serveur de dev Vite | |
| 88 | + | |
| 89 | +# Tests (normalisation, connecteur Flipp) | |
| 90 | +.venv/bin/pip install pytest | |
| 91 | +.venv/bin/python -m pytest tests/ -q | |
| 92 | + | |
| 93 | +# Validation d'ordre visuel des fiches (standard Groupe KA) | |
| 94 | +node frontend/scripts/check-order.mjs <uid> | |
| 95 | + | |
| 96 | +# Boucle de synchronisation continue (ce que fait PM2 en prod) | |
| 97 | +.venv/bin/python run.py watch 360 # re-parcourt les sources, cycle ≈ 6 h | |
| 98 | +``` | |
| 99 | + | |
| 100 | +`run.py` est le seul point d'entrée : `sync [source ...]` · `watch [minutes]` (défaut 360) · `serve [port]` (défaut 8080). | |
| 101 | + | |
| 102 | +## Variables d'environnement | |
| 103 | + | |
| 104 | +Noms seulement — les valeurs vivent dans `.env` sur le nœud (jamais versionnées). | |
| 105 | + | |
| 106 | +| Variable | Rôle | | |
| 107 | +|---|---| | |
| 108 | +| `FOODKA_BASE_URL` | URL publique canonique (SEO, sitemaps, SSO) | | |
| 109 | +| `SESSION_SECRET` | Secret de session (cookies signés) | | |
| 110 | +| `KA_SSO_SECRET` · `KA_HUB_URL` | SSO KA ID via le hub groupe-ka.com | | |
| 111 | +| `FIRECRAWL_API_KEY` · `SCRAPFLY_API_KEY` | Escalade anti-bot des connecteurs (`_resilient.py`) | | |
| 112 | +| `FOODKA_SAQ_MAX_PRODUCTS` | Borne du connecteur SAQ (optionnelle) | | |
| 113 | + | |
| 60 | 114 | ## Architecture |
| 61 | 115 | |
| 62 | 116 | | Composant | Technologie | Rôle | |
@@ -69,11 +123,11 @@ Les épiceries n'offrent pas de webhooks ; Food-Ka en reproduit l'équivalent : | ||
| 69 | 123 | |
| 70 | 124 | ### Processus PM2 |
| 71 | 125 | |
| 72 | −| Processus | Commande | Rôle | | |
| 126 | +| Processus | Commande de démarrage | Rôle | | |
| 73 | 127 | |---|---|---| |
| 74 | −| `food-ka-web` | `.venv/bin/python run.py serve 8097` | Sert l'API `/api/*`, la PWA buildée et le rendu SEO — le seul processus exposé (via ngrok) | | |
| 75 | −| `food-ka-sync` | `.venv/bin/python run.py watch 360` | Watcher de resynchronisation : reparcourt les 57 sources en boucle, **cycle complet ≈ 6 h**, alimente le diff engine et `price_log` | | |
| 76 | −| `food-ka-ngrok` | `ngrok http --url=www.food-ka.com 8097` | Tunnel public vers le domaine www.food-ka.com | | |
| 128 | +| `food-ka-web` | `pm2 start .venv/bin/python --name food-ka-web -- run.py serve 8097` | Sert l'API `/api/*`, la PWA buildée et le rendu SEO — le seul processus exposé (via ngrok) | | |
| 129 | +| `food-ka-sync` | `pm2 start .venv/bin/python --name food-ka-sync -- run.py watch 360` | Watcher de resynchronisation : reparcourt les 57 sources en boucle, **cycle complet ≈ 6 h**, alimente le diff engine et `price_log` | | |
| 130 | +| `food-ka-ngrok` | `pm2 start ~/bin/ngrok --name food-ka-ngrok -- http --url=www.food-ka.com 8097` | Tunnel public vers le domaine www.food-ka.com | | |
| 77 | 131 | |
| 78 | 132 | ### API principale |
| 79 | 133 | |
@@ -84,7 +138,8 @@ Les épiceries n'offrent pas de webhooks ; Food-Ka en reproduit l'équivalent : | ||
| 84 | 138 | | GET | `/api/products/{uid}/compare` | Équivalents du produit chez les autres bannières (matching inter-bannières) | |
| 85 | 139 | | GET | `/api/facets` | Facettes dynamiques (compteurs par catégorie, bannière, marque…) pour les filtres | |
| 86 | 140 | | GET | `/api/sources` | Registre et état des bannières connectées | |
| 87 | −| GET | `/api/stats` · `/api/stats/dashboard` · `/api/stats/detailed` | Statistiques du catalogue (tuiles, distributions, soldes) | | |
| 141 | +| GET | `/api/stats` | Tuiles de synthèse JSON (total, soldes, sources, catégories, prix moyen, répartitions) — alimente les pastilles dynamiques ci-dessus | | |
| 142 | +| GET | `/api/stats/dashboard` · `/api/stats/detailed` | Tableau de bord analytique (distributions, soldes, fraîcheur, fenêtre `?syncs_since_h` pour la supervision api-ka) | | |
| 88 | 143 | | GET | `/api/stats/catalog` · `/api/stats/report` · `/api/stats/rapport.pdf` | Catalogue stats v3 + rapports PDF | |
| 89 | 144 | | POST | `/api/stats/report/custom` | Rapport PDF personnalisé (ReportBuilder) | |
| 90 | 145 | | GET/POST | `/api/favorites` · `/api/favorites/toggle` | Favoris KA ID (synchronisés au hub) | |
@@ -95,18 +150,18 @@ S'y ajoutent les routes SSR SEO (`/produit/{uid}`, sitemaps produits/pages, `rob | ||
| 95 | 150 | |
| 96 | 151 | ### Connecteurs et sources |
| 97 | 152 | |
| 98 | −**~58 modules** dans `foodka/connectors/` couvrent **57 sources** recensées dans `data/sources.json` (chaque bannière non-connectable y est documentée avec sa raison). Les bannières d'un même groupe partagent une **base technique commune** : | |
| 153 | +**65 fichiers** dans `foodka/connectors/` : **57 connecteurs de source** (un par bannière active — 1:1 avec les **57 sources actives** du registre `data/sources.json`), **5 bases techniques partagées** et le socle `base.py` / `_resilient.py`. Le registre compte **61 entrées** au 2026-08-24 : 57 actives + **4 bannières non connectables documentées avec leur raison** (Bulk Barn, Dollarama, Fermes Lufa, Frenco). Les bannières d'un même groupe partagent une **base technique commune** : | |
| 99 | 154 | |
| 100 | −| Base partagée | Technique | Bannières servies | | |
| 155 | +| Base partagée | Technique | Connecteurs servis | | |
| 101 | 156 | |---|---|---| |
| 102 | −| `_metro.py` | Site Metro & cie | `metro`, `superc` | | |
| 103 | −| `_loblaw.py` | API Loblaw | `maxi`, `provigo`, `club_entrepot` | | |
| 104 | −| `_flipp.py` | Circulaires Flipp | 32 connecteurs : circulaires des grandes bannières (`metro_flyer`, `iga_flyer`, `maxi_flyer`, `superc_flyer`, `provigo_flyer`, `walmart_flyer`, `costco_flyer`, `adonis_flyer`, `avril_flyer`) + pharmacies (`pharmaprix`, `jean_coutu`, `uniprix`, `brunet`) + indépendantes et ethniques (`kim_phat`, `fu_tai`, `euro_marche`, `rachelle_bery`, `tradition`, `bonichoix`, `axep`, `marche_ami`, `richelieu`, `pasquier`, `inter_marche`, `inter_marche_intl`, `marche_ct`, `marche_vegetarien`, `aures`, `bonanza`, `val_mont`, `pa_nature`, `aliments_mm`) | | |
| 105 | −| `_shopify.py` | Shopify `products.json` | `giant_tiger`, `pa`, `epipresto`, `nuvo`, `boite_a_grains` | | |
| 106 | −| `_woocommerce.py` | WooCommerce Store API | `akhavan`, `aliments_merci`, `bocoboco` | | |
| 157 | +| `_flipp.py` | Circulaires Flipp | **32 connecteurs** : circulaires des grandes bannières (`metro_flyer`, `iga_flyer`, `maxi_flyer`, `superc_flyer`, `provigo_flyer`, `walmart_flyer`, `costco_flyer`, `adonis_flyer`, `avril_flyer`) + pharmacies (`pharmaprix`, `jean_coutu`, `uniprix`, `brunet`) + indépendantes et ethniques (`kim_phat`, `fu_tai`, `euro_marche`, `rachelle_bery`, `tradition`, `bonichoix`, `axep`, `marche_ami`, `richelieu`, `pasquier`, `inter_marche`, `inter_marche_intl`, `marche_ct`, `marche_vegetarien`, `aures`, `bonanza`, `val_mont`, `pa_nature`, `aliments_mm`) | | |
| 158 | +| `_shopify.py` | Shopify `products.json` | **5 connecteurs** : `giant_tiger`, `pa`, `epipresto`, `nuvo`, `boite_a_grains` | | |
| 159 | +| `_loblaw.py` | API Loblaw | **3 connecteurs** : `maxi`, `provigo`, `club_entrepot` | | |
| 160 | +| `_woocommerce.py` | WooCommerce Store API | **3 connecteurs** : `akhavan`, `aliments_merci`, `bocoboco` | | |
| 161 | +| `_metro.py` | Site Metro & cie | **2 connecteurs** : `metro`, `superc` | | |
| 107 | 162 | | `base.py` + `_resilient.py` | Socle commun | normalisation, hash, throttle, retries/backoff, escalade anti-bot | |
| 108 | 163 | |
| 109 | −Connecteurs autonomes (site propre ou API dédiée) : `iga` (Voilà), `walmart`, `saq`, `costco`, `adonis`, `avril`, `mayrand`, `aubut`, `tau`, `tt`, `loco`, `maturin`. | |
| 164 | +Connecteurs autonomes (site propre ou API dédiée — **12**) : `iga` (Voilà), `walmart`, `saq` (API GraphQL), `costco`, `adonis`, `avril`, `mayrand`, `aubut`, `tau`, `tt`, `loco`, `maturin`. | |
| 110 | 165 | |
| 111 | 166 | ### Diff engine, `price_log` & matching inter-bannières |
| 112 | 167 | |
@@ -116,6 +171,14 @@ Connecteurs autonomes (site propre ou API dédiée) : `iga` (Voilà), `walmart`, | ||
| 116 | 171 | 4. Le **matching inter-bannières** (`foodka/matching.py`) relie le même produit vendu chez plusieurs bannières — c'est lui qui alimente le tableau de comparaison de la fiche (`/api/products/{uid}/compare`). |
| 117 | 172 | 5. Chaque passage de connecteur est journalisé dans **`sync_log`** (supervision api-ka, fenêtre de sync paramétrable). |
| 118 | 173 | |
| 174 | +## Données & conformité | |
| 175 | + | |
| 176 | +- **Provenance** — chaque produit est lu **à la source** (site ou API publique de la bannière, circulaires Flipp) ; aucune donnée revendue par un tiers. | |
| 177 | +- **Cadence** — le watcher `food-ka-sync` reparcourt les 57 sources en continu, **cycle complet ≈ 6 heures** ; chaque passage est journalisé dans `sync_log` (fraîcheur visible sur `/sources` et via `?syncs_since_h`). | |
| 178 | +- **Fidélité** — aucun prix inventé (`price = null` si absent), prix régulier incohérent rejeté ; les prix des circulaires sont valides pour la durée de la circulaire. | |
| 179 | +- **Politesse de crawl** — throttle entre requêtes, retries avec backoff, User-Agent identifié ; l'escalade anti-bot (`_resilient.py`) n'est utilisée que là où le site le rend nécessaire. | |
| 180 | +- **Avertissement** — Food-Ka est un comparateur **indépendant**, sans affiliation avec les bannières référencées ; les prix sont indicatifs et peuvent varier selon le magasin — le prix en magasin fait foi. Les bannières non connectables restent listées avec leur raison dans le registre plutôt que silencieusement omises. | |
| 181 | + | |
| 119 | 182 | ## Structure du repo |
| 120 | 183 | |
| 121 | 184 | | Répertoire / fichier | Rôle | |
@@ -124,10 +187,10 @@ Connecteurs autonomes (site propre ou API dédiée) : `iga` (Voilà), `walmart`, | ||
| 124 | 187 | | `requirements.txt` | Dépendances backend (FastAPI, uvicorn, requests, bs4, reportlab, fpdf2, pillow) | |
| 125 | 188 | | `foodka/` | Backend Python : schéma Product, normalisation, ingestion, db, web, seo, matching inter-bannières, nutrition, auth KA ID, favoris, stats, PDF + `connectors/` | |
| 126 | 189 | | `frontend/` | PWA React 18 + Vite + TypeScript (build servi par FastAPI) — inclut la page `/doc` (`frontend/public/doc/`) | |
| 127 | −| `data/` | `foodka.db` (SQLite) + `sources.json` (registre des bannières) | | |
| 190 | +| `data/` | `foodka.db` (SQLite) + `sources.json` (registre des 61 bannières, raisons des non-connectables incluses) | | |
| 128 | 191 | | `docs/` | Captures d'écran + documentation générée des connecteurs | |
| 129 | 192 | | `scripts/` | Outillage (`gen_connector_docs.py`) | |
| 130 | −| `tests/` | Tests (normalisation, connecteur Flipp) | | |
| 193 | +| `tests/` | Tests pytest (`test_normalize.py`, `test_flipp.py`) | | |
| 131 | 194 | |
| 132 | 195 | ## Documentation |
| 133 | 196 | |
@@ -135,6 +198,18 @@ Connecteurs autonomes (site propre ou API dédiée) : `iga` (Voilà), `walmart`, | ||
| 135 | 198 | - **Guide PDF téléchargeable** : [food-ka-documentation.pdf](https://www.food-ka.com/doc/food-ka-documentation.pdf). |
| 136 | 199 | - Le guide est aussi lié depuis le pied de page du site ; ses captures vivent dans `frontend/public/doc/img/`. |
| 137 | 200 | |
| 201 | +## Historique | |
| 202 | + | |
| 203 | +| Date | Commit | Jalon | | |
| 204 | +|---|---|---| | |
| 205 | +| 2026-08-12 | `59727c8` | Naissance de Food-Ka — agrégateur de produits d'épicerie du Québec | | |
| 206 | +| 2026-08-16 | `44589f8` | Connecteurs **Flipp** : socle + 19 bannières de circulaires (~2 760 produits) | | |
| 207 | +| 2026-08-17 | `7395f5f` | Harmonisation **ka-ui** : accent vert marché, footer Groupe KA, **SSO KA ID** actif | | |
| 208 | +| 2026-08-18 | `2e52f7c` | Vague 3 : **SAQ** (API GraphQL directe) + 6 circulaires Flipp (4 pharmacies, Adonis, Avril) + Metro allées prioritaires | | |
| 209 | +| 2026-08-18 | `114aa76` | Matching inter-bannières : `MAX_BLOCK` 400 → 3000 (fenêtre triée, rebuild mesuré à 7 s) | | |
| 210 | +| 2026-08-23 | `8acf4c4` | Stats v3 : **rapports PDF personnalisés** (catalogue, ReportBuilder) | | |
| 211 | +| 2026-08-24 | `3a264e7` | Page documentation `/doc` (guide + captures) + PDF téléchargeable | | |
| 212 | + | |
| 138 | 213 | ## Développement (remote-first) |
| 139 | 214 | |
| 140 | 215 | La **source de vérité est le repo git sur le nœud M4M64b** (`~/apps/food-ka`), pas une copie locale. Toute modification se fait sur le nœud via SSH ; le remote `origin` = **spbgit** (git perso [https://git.spboucher.ai](https://git.spboucher.ai)), via l'alias SSH `gitsrv` configuré sur le nœud → `gitsrv:srv/git/food-ka.git` (bare repos hébergés sur M3U96a). Pas GitHub. |
| 141 | 216 | |