SPB Git

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%
8.2 KB · 187 lines markdown
Rendered Raw Blame History
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)**89![Python](https://img.shields.io/badge/Python-3.14-141814?style=for-the-badge&logo=python&logoColor=d9f26b)10![FastAPI](https://img.shields.io/badge/FastAPI-API-141814?style=for-the-badge&logo=fastapi&logoColor=d9f26b)11![React](https://img.shields.io/badge/React_18-Vite_+_TS-141814?style=for-the-badge&logo=react&logoColor=d9f26b)12![SQLite](https://img.shields.io/badge/SQLite-storage-141814?style=for-the-badge&logo=sqlite&logoColor=d9f26b)13![PWA](https://img.shields.io/badge/PWA-mobile_ready-141814?style=for-the-badge&logoColor=d9f26b)1415![Bannières](https://img.shields.io/badge/banni%C3%A8res_recens%C3%A9es-30-1c5c41?style=flat-square)16![Connecteurs](https://img.shields.io/badge/connecteurs_actifs-24-1c5c41?style=flat-square)17![Produits](https://img.shields.io/badge/produits_agr%C3%A9g%C3%A9s-22%20000%2B-1c5c41?style=flat-square)18![Couverture](https://img.shields.io/badge/couverture-tout_le_Qu%C3%A9bec-1c5c41?style=flat-square)1920*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[![Email](https://img.shields.io/badge/contact@spboucher.ai-141814?style=for-the-badge&logo=minutemailer&logoColor=d9f26b)](mailto:contact@spboucher.ai)179[![GitHub](https://img.shields.io/badge/spboucher--ai-141814?style=for-the-badge&logo=github&logoColor=d9f26b)](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