SPB Git forge

spb/food-ka

Public

Food-Ka — agrégateur de produits d'épicerie du Québec — www.food-ka.com

55commits 1branches 0releases
10.2 MBsize
maindefault branch
9 days agolast push
Python 53.9% TypeScript 24% CSS 14.9% JavaScript 5.8% HTML 1.4%

docs: README v3 — chiffres live vérifiés, pastilles dynamiques, sections complètes

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

1 changed file +94 −19

modified README.md +94 −19
@@ -13,18 +13,31 @@
13 13 [![PDF](https://img.shields.io/badge/guide-PDF-1f9d55?style=flat-square)](https://www.food-ka.com/doc/food-ka-documentation.pdf)
14 14 ![Nœud](https://img.shields.io/badge/n%C5%93ud-M4M64b-1f6feb?style=flat-square)
15 15 ![Port](https://img.shields.io/badge/port-8097-555?style=flat-square)
16 −![PM2](https://img.shields.io/badge/process-PM2-2b037a?style=flat-square)
16 +![PM2](https://img.shields.io/badge/PM2-3_processus-2b037a?style=flat-square)
17 +
18 +[![Produits](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fwww.food-ka.com%2Fapi%2Fstats&query=%24.total&label=produits&color=1f9d55&style=flat-square&cacheSeconds=3600)](https://www.food-ka.com)
19 +[![Sources](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fwww.food-ka.com%2Fapi%2Fstats&query=%24.sources&label=sources&color=1f9d55&style=flat-square&cacheSeconds=3600)](https://www.food-ka.com/sources)
20 +[![En solde](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fwww.food-ka.com%2Fapi%2Fstats&query=%24.on_sale&label=en%20solde&color=1f9d55&style=flat-square&cacheSeconds=3600)](https://www.food-ka.com/aubaines)
21 +[![Catégories](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fwww.food-ka.com%2Fapi%2Fstats&query=%24.categories&label=cat%C3%A9gories&color=1f9d55&style=flat-square&cacheSeconds=3600)](https://www.food-ka.com)
22 +
17 23 ![Python](https://img.shields.io/badge/Python-3.14-3776ab?style=flat-square&logo=python&logoColor=white)
18 24 ![FastAPI](https://img.shields.io/badge/FastAPI-API-009688?style=flat-square&logo=fastapi&logoColor=white)
19 −![React](https://img.shields.io/badge/React%2018-Vite%20%2B%20TS-61dafb?style=flat-square&logo=react&logoColor=black)
25 +![React](https://img.shields.io/badge/React%2018-TS-61dafb?style=flat-square&logo=react&logoColor=black)
26 +![Vite](https://img.shields.io/badge/Vite-build-646cff?style=flat-square&logo=vite&logoColor=white)
20 27 ![SQLite](https://img.shields.io/badge/SQLite-price__log-003b57?style=flat-square&logo=sqlite&logoColor=white)
21 −![Groupe KA](https://img.shields.io/badge/Groupe-KA-b7f000?style=flat-square)
28 +![PWA](https://img.shields.io/badge/PWA-installable-5a0fc8?style=flat-square&logo=pwa&logoColor=white)
29 +![PM2](https://img.shields.io/badge/PM2-process%20manager-2b037a?style=flat-square&logo=pm2&logoColor=white)
30 +![ngrok](https://img.shields.io/badge/ngrok-tunnel-1f1e37?style=flat-square&logo=ngrok&logoColor=white)
31 +![Groupe KA](https://img.shields.io/badge/Groupe-KA-1f9d55?style=flat-square)
32 +![spbgit](https://img.shields.io/badge/remote--first-spbgit-8250df?style=flat-square&logo=git&logoColor=white)
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