# Food·Ka
### Tous les prix d'épicerie du Québec. Un seul endroit.
**[www.food-ka.com](https://www.food-ka.com)**









*Agrégateur indépendant de produits d'épicerie — chaque produit avec son prix courant,
son prix régulier, son prix unitaire comparable ($/100 g) et un lien direct vers la
fiche originale de la bannière. Toujours à jour, automatiquement.*
---
## Pourquoi Food-Ka ?
Comparer les prix d'épicerie au Québec, c'est ouvrir Metro, IGA, Maxi, Super C,
Provigo, Walmart… chacun avec sa propre navigation, son propre panier, son propre
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, et détecte les
changements de prix en continu.
> Les épiceries n'offrent pas de webhooks. Food-Ka reproduit l'équivalent :
> **synchronisation périodique + hash de contenu** → nouveaux produits, changements
> de prix et retraits détectés automatiquement. Chaque variation de prix est
> historisée (`price_log`) — les soldes deviennent traçables.
## L'architecture en 30 secondes
```mermaid
flowchart LR
subgraph Sources["Bannières d'épicerie"]
S1["Metro · Super C · IGA/Voilà
Maxi · Provigo · Walmart
Mayrand · Avril · PA · Tau
… 24 connecteurs actifs"]
end
subgraph FoodKa["Food-Ka"]
C["Connecteurs
1 adaptateur / bannière"] --> N["Normalisation
schéma Product unique"]
N --> D[("SQLite
hash + diff + prix")]
D --> A["API FastAPI
/api/products · /api/facets"]
A --> F["React 18 + Vite
PWA mobile"]
end
W["⏱ Watcher (PM2)"] -.-> C
S1 --> C
F --> U["🛒 Consommateur"]
```
| Couche | Rôle | Fichiers |
|---|---|---|
| **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` |
| **Schéma** | `Product` standardisé : nom, marque, format, prix, prix régulier, **prix unitaire $/100 g**, catégorie canonique, images | `foodka/schema.py` |
| **Diff engine** | Upsert par hash de contenu — nouveau / modifié / disparu (désactivé), historique de prix | `foodka/db.py` |
| **API** | Filtres catégorie / bannière / marque / prix / soldes / recherche, tris (prix, prix unitaire, rabais), facettes, stats | `foodka/web.py` |
| **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/` |
## Démarrage rapide
```bash
git clone https://github.com/spboucher-ai/food-ka.git && cd food-ka
# Backend
python3 -m venv .venv && .venv/bin/pip install -r requirements.txt
# Frontend
cd frontend && npm install && npm run build && cd ..
# Sites protégés (anti-bot) et rendu JavaScript
cat > .env <<'ENV'
SCRAPFLY_API_KEY=scp-live-votre-cle
FIRECRAWL_API_KEY=fc-votre-cle
ENV
# Ingestion puis service
.venv/bin/python run.py sync # toutes les sources (ou: run.py sync metro iga)
.venv/bin/python run.py serve 8080 # → http://localhost:8080
.venv/bin/python run.py watch 360 # resynchronisation en boucle (minutes)
```
## Ajouter une bannière (≈ 30 lignes)
L'enregistrement est **auto-découvrant** : déposez un module dans `foodka/connectors/`,
c'est tout — aucun fichier partagé à modifier.
```python
# foodka/connectors/mon_epicerie.py
from ..schema import Product, normalize_category, parse_price
from .base import BaseConnector
class MonEpicerieConnector(BaseConnector):
source_id = "mon_epicerie"
def fetch(self) -> list[Product]:
html = self.get("https://mon-epicerie.ca/produits").text # throttlé, poli
# ... ou self.get_scrapfly(url) si le site est protégé ...
return [Product(
source=self.source_id, external_id="123",
url="https://mon-epicerie.ca/produit/123",
name="Beurre d'arachide croquant", brand="Kraft",
category=normalize_category("Garde-manger"),
size_label="500 g", price=parse_price("4,99 $"),
regular_price=6.49, images=[...],
)]
```
Puis : `.venv/bin/python run.py sync mon_epicerie` — et les produits apparaissent sur
le site, avec prix unitaire calculé et comparaison inter-bannières. Ajoutez l'entrée
correspondante dans `data/sources.json` pour la page **Sources**.
## API
| Endpoint | Description |
|---|---|
| `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) |
| `GET /api/products/{uid}` | Fiche complète + historique de prix + **comparaison inter-bannières** |
| `GET /api/facets` | Valeurs distinctes pour construire les filtres |
| `GET /api/sources` | Registre des bannières + compteurs + dernière sync |
| `GET /api/stats` | Totaux, soldes actifs, meilleures aubaines, journal de synchronisation |
| `POST /api/sync` | Déclenche une synchronisation en arrière-plan |
## Couverture
**Grandes bannières** — Metro, Super C, IGA (Voilà), Maxi, Provigo, Walmart Canada,
Costco Canada (épicerie livrée), Marché Adonis.
**Spécialisées & indépendantes** — Mayrand (grossiste), Avril Supermarché Santé,
Marché Tau, PA Supermarché, Aubut, Maturin (producteurs québécois), Giant Tiger,
Epipresto, Marché Nuvo, La Boîte à Grains, BocoBoco, Aliments Merci, Club Entrepôt,
Épiceries LOCO, Akhavan, T&T Supermarket.
Chaque bannière non-connectable est **documentée avec sa raison** dans
`data/sources.json` (ex. : prix liés à une session Instacart/DoorDash, catalogue
sans prix, anti-bot strict).
## Production
Déployé sous **PM2** (3 processus) derrière **ngrok** :
```
food-ka-web .venv/bin/python run.py serve 8096 # API + frontend
food-ka-sync .venv/bin/python run.py watch 360 # resync aux 6 h
food-ka-ngrok ngrok http --url=www.food-ka.com 8096 # tunnel
```
Philosophie d'exploitation : **on ne pousse que le code — le serveur maintient ses
données lui-même.**
## Principes
1. **Politesse** — délai ≥ 0,5 s entre requêtes, périmètre de crawl borné,
User-Agent identifié.
2. **Fidélité** — aucun prix inventé : si la source n'affiche pas de prix,
`price = null` ; un « prix régulier » incohérent est rejeté.
3. **Traçabilité** — chaque fiche renvoie vers le produit original de la bannière,
et chaque changement de prix est historisé.
4. **Robustesse** — un connecteur qui casse n'affecte jamais les autres
(auto-découverte tolérante, try/except par produit, détection de dérive,
journal `sync_log`).
---
## Auteur
**Simon-Pierre Boucher**
[](mailto:contact@spboucher.ai)
[](https://github.com/spboucher-ai)
*Conçu, construit et déployé en une journée — de la recherche de marché
(30 bannières recensées et vérifiées) au produit en production.*
© 2026 Simon-Pierre Boucher — tous droits réservés.