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%

feat(doc): page documentation /doc (guide + captures) + lien footer + PDF téléchargeable

Simon-Pierre Boucher committed 1 mo ago (Aug 24, 2026) parent 2771ce0

8 changed files +161 −2

modified foodka/web.py +10 −1
@@ -12,7 +12,7 @@ from pathlib import Path
12 12
13 13 from fastapi import BackgroundTasks, Body, FastAPI, HTTPException, Query, Request
14 14 from fastapi.middleware.cors import CORSMiddleware
15 −from fastapi.responses import FileResponse, Response
15 +from fastapi.responses import FileResponse, RedirectResponse, Response
16 16 from fastapi.staticfiles import StaticFiles
17 17
18 18 from . import auth, db, hubfav, ingest
@@ -404,6 +404,15 @@ app.include_router(auth.router)
404 404 if FRONTEND_DIST.exists():
405 405 app.mount("/assets", StaticFiles(directory=FRONTEND_DIST / "assets"), name="assets")
406 406
407 + # Documentation statique (/doc/) — servie avant le catch-all SPA
408 + doc_dir = FRONTEND_DIST / "doc"
409 + if doc_dir.is_dir():
410 + @app.get("/doc", include_in_schema=False)
411 + def doc_slash():
412 + return RedirectResponse("/doc/", status_code=308)
413 +
414 + app.mount("/doc", StaticFiles(directory=doc_dir, html=True), name="doc")
415 +
407 416 # --- SEO : robots, sitemaps, SSR des pages publiques — AVANT le catch-all
408 417 from . import seo # noqa: E402
409 418 app.include_router(seo.router)
added frontend/public/doc/food-ka-documentation.pdf +0 −0

Binary file not shown.

added frontend/public/doc/img/etape1.png +0 −0

Binary file not shown.

added frontend/public/doc/img/etape2.png +0 −0

Binary file not shown.

added frontend/public/doc/img/etape3.png +0 −0

Binary file not shown.

added frontend/public/doc/img/etape4.png +0 −0

Binary file not shown.

added frontend/public/doc/index.html +150 −0
@@ -0,0 +1,150 @@
1 +<!DOCTYPE html>
2 +<html lang="fr">
3 +<head>
4 +<meta charset="utf-8">
5 +<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
6 +<title>Documentation — Food·Ka</title>
7 +<meta name="description" content="Comment fonctionne Food·Ka : guide pas à pas, données, architecture.">
8 +<style>
9 + :root{ --ink:#101014; --paper:#faf9f6; --accent:#1f9d55; --muted:#6b6b70; --line:#e6e4de; }
10 + *{box-sizing:border-box} html,body{margin:0;padding:0;background:var(--paper);color:var(--ink);
11 + font:16px/1.65 -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;-webkit-text-size-adjust:100%}
12 + .container{max-width:960px;margin:0 auto;padding:0 20px}
13 + header.doc{background:var(--ink);color:var(--paper);padding:20px 0}
14 + header.doc .container{display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap}
15 + header.doc a{color:var(--paper);text-decoration:none}
16 + .wordmark{font-weight:800;font-size:20px;letter-spacing:.02em}
17 + .wordmark .ka{color:var(--accent)}
18 + .btn-pdf{display:inline-block;background:var(--accent);color:#fff;font-weight:700;
19 + padding:10px 18px;border-radius:999px;text-decoration:none;font-size:15px}
20 + .hero{padding:48px 0 8px}
21 + .hero h1{font-size:clamp(28px,5vw,44px);line-height:1.1;margin:0 0 12px}
22 + .hero h1 mark{background:var(--accent);color:#fff;padding:2px 8px}
23 + .hero p.lead{font-size:18px;color:var(--muted);max-width:640px}
24 + .kicker{font-size:12px;letter-spacing:.14em;text-transform:uppercase;color:var(--muted);
25 + border-left:3px solid var(--accent);padding-left:10px;margin:40px 0 8px;font-weight:700}
26 + section{padding:8px 0 16px}
27 + h2{font-size:26px;margin:6px 0 12px}
28 + .step{display:grid;grid-template-columns:56px 1fr;gap:16px;margin:26px 0;align-items:start}
29 + .step .num{width:44px;height:44px;border-radius:50%;background:var(--ink);color:var(--accent);
30 + display:flex;align-items:center;justify-content:center;font-weight:800;font-size:18px}
31 + .step h3{margin:6px 0 6px;font-size:19px}
32 + .step p{margin:0 0 12px;color:#3a3a40}
33 + .shot{border:1px solid var(--line);border-radius:12px;overflow:hidden;box-shadow:0 8px 30px rgba(16,16,20,.08)}
34 + .shot img{display:block;width:100%;height:auto}
35 + .cards{display:grid;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));gap:14px;margin:16px 0}
36 + .card{border:1px solid var(--line);border-radius:12px;padding:16px;background:#fff}
37 + .card b{display:block;margin-bottom:6px}
38 + table{border-collapse:collapse;width:100%;font-size:15px}
39 + th,td{border:1px solid var(--line);padding:8px 10px;text-align:left;vertical-align:top}
40 + th{background:var(--ink);color:var(--paper)}
41 + footer.doc{margin-top:56px;background:var(--ink);color:var(--paper);padding:28px 0;font-size:14px}
42 + footer.doc a{color:var(--accent);text-decoration:none}
43 + @media print{
44 + header.doc .btn-pdf{display:none}
45 + .shot{box-shadow:none;break-inside:avoid}
46 + .step{break-inside:avoid}
47 + a{color:inherit;text-decoration:none}
48 + }
49 + @media(max-width:640px){ .step{grid-template-columns:40px 1fr} .step .num{width:34px;height:34px;font-size:15px} }
50 +</style>
51 +</head>
52 +<body>
53 +<header class="doc">
54 + <div class="container">
55 + <a class="wordmark" href="/">Food<span class="ka">·Ka</span></a>
56 + <nav style="display:flex;gap:14px;align-items:center">
57 + <a href="/">← Retour au site</a>
58 + <a class="btn-pdf" href="./food-ka-documentation.pdf" download>Télécharger le PDF</a>
59 + </nav>
60 + </div>
61 +</header>
62 +
63 +<div class="container">
64 + <div class="hero">
65 + <p class="kicker">Documentation · mise à jour 2026-08-24</p>
66 + <h1>Comment fonctionne <mark>Food·Ka</mark></h1>
67 + <p class="lead">Tous les prix d'épicerie du Québec — comparés, historisés et toujours à jour, un seul endroit.</p>
68 + </div>
69 +
70 + <section>
71 + <p class="kicker">Vue d'ensemble</p>
72 + <h2>À quoi sert le site</h2>
73 + <p>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 un <b>prix unitaire comparable en $/100 g</b> — et détecte les changements de prix en continu, chaque variation étant historisée. Grandes bannières comme épiceries spécialisées et indépendantes (Mayrand, Avril, PA, Tau, Giant Tiger, SAQ…).</p>
74 + <div class="cards">
75 + <div class="card"><b>50 892 produits</b>suivis dans 18 catégories canoniques.</div>
76 + <div class="card"><b>57 sources</b>connectées — grandes chaînes et bannières spécialisées, tout le Québec.</div>
77 + <div class="card"><b>8 777 produits en solde</b>détectés par comparaison prix courant / prix régulier.</div>
78 + <div class="card"><b>$/100 g</b>chaque produit ramené en prix unitaire pour comparer l'incomparable.</div>
79 + </div>
80 + </section>
81 +
82 + <section>
83 + <p class="kicker">Guide pas à pas</p>
84 + <h2>Utiliser le site en 4 étapes</h2>
85 +
86 + <div class="step">
87 + <div class="num">1</div>
88 + <div>
89 + <h3>Arriver sur l'accueil : le catalogue en un coup d'œil</h3>
90 + <p>La page d'accueil affiche le catalogue complet : produits suivis, bannières connectées, soldes actifs, prix moyen — et le ticker temps réel des bannières. La barre de recherche et les filtres (catégorie, prix, tri) sont directement sous le titre. Food·Ka est aussi une <b>PWA installable</b> : ajoutez-la à l'écran d'accueil de votre téléphone.</p>
91 + <div class="shot"><img src="./img/etape1.png" alt="Étape 1 — accueil Food·Ka"></div>
92 + </div>
93 + </div>
94 +
95 + <div class="step">
96 + <div class="num">2</div>
97 + <div>
98 + <h3>Chercher un produit</h3>
99 + <p>Tapez un produit — ici « lait 2% » — et affinez par catégorie, bannière, marque, fourchette de prix ou soldes seulement. Chaque carte de résultat montre le prix, le <b>prix unitaire</b> (15 ¢/100 ml, 52 ¢/100 ml…), la bannière d'origine et le rabais éventuel : la comparaison commence dès la liste.</p>
100 + <div class="shot"><img src="./img/etape2.png" alt="Étape 2 — recherche « lait 2% » avec prix unitaires"></div>
101 + </div>
102 + </div>
103 +
104 + <div class="step">
105 + <div class="num">3</div>
106 + <div>
107 + <h3>Ouvrir une fiche : comparer entre bannières</h3>
108 + <p>La fiche produit montre le prix, le prix unitaire et le format — et surtout le panneau <b>« Comparer les prix »</b> : les produits équivalents chez les autres bannières, du moins cher au plus cher, avec le badge <b>« Meilleur prix »</b>. Ici la même eau de source se trouve de 0,50 $ chez Maxi à 1,00 $ chez Provigo. Chaque fiche garde le lien direct vers la fiche originale de la bannière.</p>
109 + <div class="shot"><img src="./img/etape3.png" alt="Étape 3 — fiche produit avec comparaison inter-bannières"></div>
110 + </div>
111 + </div>
112 +
113 + <div class="step">
114 + <div class="num">4</div>
115 + <div>
116 + <h3>Chasser les aubaines</h3>
117 + <p>L'onglet <b>Aubaines</b> réunit tous les soldes actifs (8 789 au moment de la capture), triés par meilleur rabais : prix courant, prix régulier barré et pourcentage de réduction calculé. Les filtres par catégorie et par bannière s'appliquent aussi ici. Avec un compte KA ID, le cœur enregistre un produit dans vos favoris « Mon univers Ka ».</p>
118 + <div class="shot"><img src="./img/etape4.png" alt="Étape 4 — aubaines triées par rabais"></div>
119 + </div>
120 + </div>
121 + </section>
122 +
123 + <section>
124 + <p class="kicker">Sous le capot</p>
125 + <h2>D'où viennent les données</h2>
126 + <p>Food·Ka entretient un <b>connecteur dédié par bannière</b> (HTML rendu serveur, APIs JSON Shopify ou WooCommerce, <code>__NEXT_DATA__</code>…) ; chaque bannière non connectable est documentée avec sa raison dans le registre des sources. Les épiceries n'offrent pas de webhooks ; Food·Ka en reproduit l'équivalent : une <b>resynchronisation autonome</b> revisite toutes les bannières en continu (cycle d'environ 6 heures), et un <b>diff engine</b> compare le hash de contenu de chaque produit pour détecter les nouveautés, les changements de prix et les retraits — chaque variation est <b>historisée</b> dans le journal des prix. La <b>comparaison inter-bannières</b> repose sur un moteur d'appariement qui regroupe les produits équivalents entre bannières. Règles de fidélité : aucun prix inventé (fiche sans prix si la source n'en affiche pas), prix régulier incohérent rejeté, throttle entre requêtes et User-Agent identifié.</p>
127 + </section>
128 +
129 + <section>
130 + <p class="kicker">Questions fréquentes</p>
131 + <h2>FAQ</h2>
132 + <div class="cards">
133 + <div class="card"><b>Les prix sont-ils à jour ?</b>Chaque bannière est resynchronisée en continu (cycle ~6 h) et chaque variation de prix est historisée. Le prix affiché en magasin ou en ligne reste confirmé à la source, via le lien direct de chaque fiche.</div>
134 + <div class="card"><b>Pourquoi un prix en $/100 g ?</b>Deux formats différents ne se comparent pas : 1 L à 2,06 $ ou 4 L à 7,11 $ ? Le prix unitaire ($/100 g ou $/100 ml) ramène tout à la même base — c'est lui qui dit ce qui est vraiment le moins cher.</div>
135 + <div class="card"><b>Comment marche la comparaison inter-bannières ?</b>Un moteur d'appariement regroupe les produits équivalents entre bannières ; la fiche affiche alors les prix du moins cher au plus cher avec le badge « Meilleur prix ».</div>
136 + <div class="card"><b>Peut-on acheter sur Food·Ka ?</b>Non — Food·Ka est un comparateur indépendant. Chaque fiche mène à la fiche originale de la bannière, où se fait l'achat.</div>
137 + <div class="card"><b>À quoi sert le compte KA ID ?</b>C'est le compte unique du Groupe KA (courriel + Google) : mêmes identifiants sur les 13 plateformes, favoris synchronisés dans « Mon univers Ka ».</div>
138 + </div>
139 + </section>
140 +</div>
141 +
142 +<footer class="doc">
143 + <div class="container">
144 + <p><b>Food·Ka</b> — un service <a href="https://www.groupe-ka.com">Groupe KA</a>.
145 + Écosystème : les 13 plateformes sont liées au pied de chaque site.</p>
146 + <p>© 2026 Groupe KA — Simon-Pierre Boucher · <a href="mailto:contact@spboucher.ai">contact@spboucher.ai</a></p>
147 + </div>
148 +</footer>
149 +</body>
150 +</html>
modified frontend/src/App.tsx +1 −1
@@ -263,7 +263,7 @@ export default function App() {
263 263 </main>
264 264 <KaFooter
265 265 siteId="food-ka"
266 − localLegal={[{ label: "Confidentialité", href: "/confidentialite" }]}
266 + localLegal={[{ label: "Documentation", href: "/doc/" }, { label: "Confidentialité", href: "/confidentialite" }]}
267 267 />
268 268 <CookieConsent />
269 269 </FavProvider>
270 270