spb/food-ka Public
Food-Ka — agrégateur de produits d'épicerie du Québec — www.food-ka.com
Python 57.7%
TypeScript 24.9%
CSS 16.7%
HTML 0.6%
1<div align="center">23# Food·Ka45### Tous les prix d'épicerie du Québec. Un seul endroit.67**[www.food-ka.com](https://www.food-ka.com)**891011121314151617181920*Agrégateur indépendant de produits d'épicerie — chaque produit avec son prix courant,21son prix régulier, son prix unitaire comparable ($/100 g) et un lien direct vers la22fiche originale de la bannière. Toujours à jour, automatiquement.*2324</div>2526---2728## Pourquoi Food-Ka ?2930Comparer les prix d'épicerie au Québec, c'est ouvrir Metro, IGA, Maxi, Super C,31Provigo, Walmart… chacun avec sa propre navigation, son propre panier, son propre32format. **Food-Ka retourne le problème** : un connecteur dédié par bannière visite33chaque site, normalise chaque produit vers un schéma unique, et détecte les34changements de prix en continu.3536> Les épiceries n'offrent pas de webhooks. Food-Ka reproduit l'équivalent :37> **synchronisation périodique + hash de contenu** → nouveaux produits, changements38> de prix et retraits détectés automatiquement. Chaque variation de prix est39> historisée (`price_log`) — les soldes deviennent traçables.4041## L'architecture en 30 secondes4243```mermaid44flowchart LR45 subgraph Sources["Bannières d'épicerie"]46 S1["Metro · Super C · IGA/Voilà<br/>Maxi · Provigo · Walmart<br/>Mayrand · Avril · PA · Tau<br/>… 24 connecteurs actifs"]47 end48 subgraph FoodKa["Food-Ka"]49 C["Connecteurs<br/><i>1 adaptateur / bannière</i>"] --> N["Normalisation<br/><i>schéma Product unique</i>"]50 N --> D[("SQLite<br/>hash + diff + prix")]51 D --> A["API FastAPI<br/>/api/products · /api/facets"]52 A --> F["React 18 + Vite<br/>PWA mobile"]53 end54 W["⏱ Watcher (PM2)"] -.-> C55 S1 --> C56 F --> U["🛒 Consommateur"]57```5859| Couche | Rôle | Fichiers |60|---|---|---|61| **Connecteurs** | 1 module Python par bannière : HTML rendu serveur, API JSON ouvertes (Shopify `products.json`, WooCommerce Store API), `__NEXT_DATA__` (Loblaw), ou **Scrapfly** (anti-bot ASP + rendu JS) / **Firecrawl** pour les sites protégés | `foodka/connectors/*.py` |62| **Schéma** | `Product` standardisé : nom, marque, format, prix, prix régulier, **prix unitaire $/100 g**, catégorie canonique, images | `foodka/schema.py` |63| **Diff engine** | Upsert par hash de contenu — nouveau / modifié / disparu (désactivé), historique de prix | `foodka/db.py` |64| **API** | Filtres catégorie / bannière / marque / prix / soldes / recherche, tris (prix, prix unitaire, rabais), facettes, stats | `foodka/web.py` |65| **Frontend** | Design « éditorial sharp » : Space Grotesk, ombres décalées, accent lime, ticker temps réel, fiches produit avec comparaison inter-bannières, PWA installable | `frontend/` |6667## Démarrage rapide6869```bash70git clone https://github.com/spboucher-ai/food-ka.git && cd food-ka7172# Backend73python3 -m venv .venv && .venv/bin/pip install -r requirements.txt7475# Frontend76cd frontend && npm install && npm run build && cd ..7778# Sites protégés (anti-bot) et rendu JavaScript79cat > .env <<'ENV'80SCRAPFLY_API_KEY=scp-live-votre-cle81FIRECRAWL_API_KEY=fc-votre-cle82ENV8384# Ingestion puis service85.venv/bin/python run.py sync # toutes les sources (ou: run.py sync metro iga)86.venv/bin/python run.py serve 8080 # → http://localhost:808087.venv/bin/python run.py watch 360 # resynchronisation en boucle (minutes)88```8990## Ajouter une bannière (≈ 30 lignes)9192L'enregistrement est **auto-découvrant** : déposez un module dans `foodka/connectors/`,93c'est tout — aucun fichier partagé à modifier.9495```python96# foodka/connectors/mon_epicerie.py97from ..schema import Product, normalize_category, parse_price98from .base import BaseConnector99100class MonEpicerieConnector(BaseConnector):101 source_id = "mon_epicerie"102103 def fetch(self) -> list[Product]:104 html = self.get("https://mon-epicerie.ca/produits").text # throttlé, poli105 # ... ou self.get_scrapfly(url) si le site est protégé ...106 return [Product(107 source=self.source_id, external_id="123",108 url="https://mon-epicerie.ca/produit/123",109 name="Beurre d'arachide croquant", brand="Kraft",110 category=normalize_category("Garde-manger"),111 size_label="500 g", price=parse_price("4,99 $"),112 regular_price=6.49, images=[...],113 )]114```115116Puis : `.venv/bin/python run.py sync mon_epicerie` — et les produits apparaissent sur117le site, avec prix unitaire calculé et comparaison inter-bannières. Ajoutez l'entrée118correspondante dans `data/sources.json` pour la page **Sources**.119120## API121122| Endpoint | Description |123|---|---|124| `GET /api/products?category=&source=&brand=&price_min=&price_max=&on_sale=1&q=&sort=` | Recherche filtrée (tris : prix, prix unitaire, rabais, nom, récents) |125| `GET /api/products/{uid}` | Fiche complète + historique de prix + **comparaison inter-bannières** |126| `GET /api/facets` | Valeurs distinctes pour construire les filtres |127| `GET /api/sources` | Registre des bannières + compteurs + dernière sync |128| `GET /api/stats` | Totaux, soldes actifs, meilleures aubaines, journal de synchronisation |129| `POST /api/sync` | Déclenche une synchronisation en arrière-plan |130131## Couverture132133**Grandes bannières** — Metro, Super C, IGA (Voilà), Maxi, Provigo, Walmart Canada,134Costco Canada (épicerie livrée), Marché Adonis.135136**Spécialisées & indépendantes** — Mayrand (grossiste), Avril Supermarché Santé,137Marché Tau, PA Supermarché, Aubut, Maturin (producteurs québécois), Giant Tiger,138Epipresto, Marché Nuvo, La Boîte à Grains, BocoBoco, Aliments Merci, Club Entrepôt,139Épiceries LOCO, Akhavan, T&T Supermarket.140141Chaque bannière non-connectable est **documentée avec sa raison** dans142`data/sources.json` (ex. : prix liés à une session Instacart/DoorDash, catalogue143sans prix, anti-bot strict).144145## Production146147Déployé sous **PM2** (3 processus) derrière **ngrok** :148149```150food-ka-web .venv/bin/python run.py serve 8096 # API + frontend151food-ka-sync .venv/bin/python run.py watch 360 # resync aux 6 h152food-ka-ngrok ngrok http --url=www.food-ka.com 8096 # tunnel153```154155Philosophie d'exploitation : **on ne pousse que le code — le serveur maintient ses156données lui-même.**157158## Principes1591601. **Politesse** — délai ≥ 0,5 s entre requêtes, périmètre de crawl borné,161 User-Agent identifié.1622. **Fidélité** — aucun prix inventé : si la source n'affiche pas de prix,163 `price = null` ; un « prix régulier » incohérent est rejeté.1643. **Traçabilité** — chaque fiche renvoie vers le produit original de la bannière,165 et chaque changement de prix est historisé.1664. **Robustesse** — un connecteur qui casse n'affecte jamais les autres167 (auto-découverte tolérante, try/except par produit, détection de dérive,168 journal `sync_log`).169170---171172<div align="center">173174## Auteur175176**Simon-Pierre Boucher**177178[](mailto:contact@spboucher.ai)179[](https://github.com/spboucher-ai)180181*Conçu, construit et déployé en une journée — de la recherche de marché182(30 bannières recensées et vérifiées) au produit en production.*183184© 2026 Simon-Pierre Boucher — tous droits réservés.185186</div>187