# Food·Ka ### Tous les prix d'épicerie du Québec. Un seul endroit. **[www.food-ka.com](https://www.food-ka.com)** ![Python](https://img.shields.io/badge/Python-3.14-141814?style=for-the-badge&logo=python&logoColor=d9f26b) ![FastAPI](https://img.shields.io/badge/FastAPI-API-141814?style=for-the-badge&logo=fastapi&logoColor=d9f26b) ![React](https://img.shields.io/badge/React_18-Vite_+_TS-141814?style=for-the-badge&logo=react&logoColor=d9f26b) ![SQLite](https://img.shields.io/badge/SQLite-storage-141814?style=for-the-badge&logo=sqlite&logoColor=d9f26b) ![PWA](https://img.shields.io/badge/PWA-mobile_ready-141814?style=for-the-badge&logoColor=d9f26b) ![Bannières](https://img.shields.io/badge/banni%C3%A8res_recens%C3%A9es-30-1c5c41?style=flat-square) ![Connecteurs](https://img.shields.io/badge/connecteurs_actifs-24-1c5c41?style=flat-square) ![Produits](https://img.shields.io/badge/produits_agr%C3%A9g%C3%A9s-22%20000%2B-1c5c41?style=flat-square) ![Couverture](https://img.shields.io/badge/couverture-tout_le_Qu%C3%A9bec-1c5c41?style=flat-square) *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** [![Email](https://img.shields.io/badge/contact@spboucher.ai-141814?style=for-the-badge&logo=minutemailer&logoColor=d9f26b)](mailto:contact@spboucher.ai) [![GitHub](https://img.shields.io/badge/spboucher--ai-141814?style=for-the-badge&logo=github&logoColor=d9f26b)](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.