SPB Git

spb/resto-ka

Public

Resto·Ka — tous les restaurants du Québec, menus complets et prix réels (famille ·Ka)

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

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Simon-Pierre Boucher committed (Aug 24, 2026) parent e016aa8 Browse files

8 changed files +170 −2

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 +158 −0
@@ -0,0 +1,158 @@
1+<!DOCTYPE html>
2+<!-- GABARIT COMMUN — page /doc des sites du Groupe KA (2026-08-24).
3+ À adapter par site : remplacer resto-ka (id, ex. lou-ka), Resto·Ka (ex. Lou·Ka),
4+ Tous les restaurants du Québec — menus complets et prix réels, comparables et cherchables dans les 17 régions., #f08c00 (couleur accent du site, ex. #b7f000), les sections
5+ et les étapes (captures dans ./img/). Page auto-suffisante (aucun JS requis). -->
6+<html lang="fr">
7+<head>
8+<meta charset="utf-8">
9+<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
10+<title>Documentation — Resto·Ka</title>
11+<meta name="description" content="Comment fonctionne Resto·Ka : guide pas à pas, données, architecture.">
12+<style>
13+ :root{ --ink:#101014; --paper:#faf9f6; --accent:#f08c00; --muted:#6b6b70; --line:#e6e4de; }
14+ *{box-sizing:border-box} html,body{margin:0;padding:0;background:var(--paper);color:var(--ink);
15+ font:16px/1.65 -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;-webkit-text-size-adjust:100%}
16+ .container{max-width:960px;margin:0 auto;padding:0 20px}
17+ header.doc{background:var(--ink);color:var(--paper);padding:20px 0}
18+ header.doc .container{display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap}
19+ header.doc a{color:var(--paper);text-decoration:none}
20+ .wordmark{font-weight:800;font-size:20px;letter-spacing:.02em}
21+ .wordmark .ka{color:var(--accent)}
22+ .btn-pdf{display:inline-block;background:var(--accent);color:var(--ink);font-weight:700;
23+ padding:10px 18px;border-radius:999px;text-decoration:none;font-size:15px}
24+ .hero{padding:48px 0 8px}
25+ .hero h1{font-size:clamp(28px,5vw,44px);line-height:1.1;margin:0 0 12px}
26+ .hero h1 mark{background:var(--accent);color:var(--ink);padding:2px 8px}
27+ .hero p.lead{font-size:18px;color:var(--muted);max-width:640px}
28+ .kicker{font-size:12px;letter-spacing:.14em;text-transform:uppercase;color:var(--muted);
29+ border-left:3px solid var(--accent);padding-left:10px;margin:40px 0 8px;font-weight:700}
30+ section{padding:8px 0 16px}
31+ h2{font-size:26px;margin:6px 0 12px}
32+ .step{display:grid;grid-template-columns:56px 1fr;gap:16px;margin:26px 0;align-items:start}
33+ .step .num{width:44px;height:44px;border-radius:50%;background:var(--ink);color:var(--accent);
34+ display:flex;align-items:center;justify-content:center;font-weight:800;font-size:18px}
35+ .step h3{margin:6px 0 6px;font-size:19px}
36+ .step p{margin:0 0 12px;color:#3a3a40}
37+ .shot{border:1px solid var(--line);border-radius:12px;overflow:hidden;box-shadow:0 8px 30px rgba(16,16,20,.08)}
38+ .shot img{display:block;width:100%;height:auto}
39+ .cards{display:grid;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));gap:14px;margin:16px 0}
40+ .card{border:1px solid var(--line);border-radius:12px;padding:16px;background:#fff}
41+ .card b{display:block;margin-bottom:6px}
42+ table{border-collapse:collapse;width:100%;font-size:15px}
43+ th,td{border:1px solid var(--line);padding:8px 10px;text-align:left;vertical-align:top}
44+ th{background:var(--ink);color:var(--paper)}
45+ footer.doc{margin-top:56px;background:var(--ink);color:var(--paper);padding:28px 0;font-size:14px}
46+ footer.doc a{color:var(--accent);text-decoration:none}
47+ @media print{
48+ header.doc .btn-pdf{display:none}
49+ .shot{box-shadow:none;break-inside:avoid}
50+ .step{break-inside:avoid}
51+ a{color:inherit;text-decoration:none}
52+ }
53+ @media(max-width:640px){ .step{grid-template-columns:40px 1fr} .step .num{width:34px;height:34px;font-size:15px} }
54+</style>
55+</head>
56+<body>
57+<header class="doc">
58+ <div class="container">
59+ <a class="wordmark" href="/">Resto<span class="ka">·Ka</span></a>
60+ <nav style="display:flex;gap:14px;align-items:center">
61+ <a href="/">← Retour au site</a>
62+ <a class="btn-pdf" href="./resto-ka-documentation.pdf" download>Télécharger le PDF</a>
63+ </nav>
64+ </div>
65+</header>
66+
67+<div class="container">
68+ <div class="hero">
69+ <p class="kicker">Documentation · mise à jour 2026-08-24</p>
70+ <h1>Comment fonctionne <mark>Resto·Ka</mark></h1>
71+ <p class="lead">Tous les restaurants du Québec — menus complets et prix réels, comparables et cherchables dans les 17 régions.</p>
72+ </div>
73+
74+ <section>
75+ <p class="kicker">Vue d'ensemble</p>
76+ <h2>À quoi sert le site</h2>
77+ <p><b>Resto·Ka</b> (<a href="https://www.resto-ka.com">www.resto-ka.com</a>) est l'agrégateur exhaustif des restaurants
78+ du Québec : chaque resto, chaque plat, chaque prix — dans les 17 régions administratives, au même endroit, comparable et
79+ cherchable <b>par resto ou par plat</b>. Les prix sont des <b>prix takeout réels, non majorés</b>, captés sur la plateforme de
80+ commande des restos eux-mêmes (pas sur les apps de livraison, majorées de 25-30 %). Pour quiconque veut répondre à
81+ « qu'est-ce que je mange, où, et combien ça coûte ? » sans ouvrir quinze applis.</p>
82+ <div class="cards">
83+ <div class="card"><b>14 091 restaurants</b>Découverte OpenStreetMap dans les 17 régions ; 99 % géolocalisés.</div>
84+ <div class="card"><b>79 159 plats avec prix</b>738 restos avec menu complet, 98 % des items avec photo.</div>
85+ <div class="card"><b>Prix réels, non majorés</b>Prix takeout captés chez les restos (connecteur UEAT, 29 intégrations de chaînes), jamais sur les apps de livraison.</div>
86+ <div class="card"><b>Historique des prix</b>Chaque changement de prix d'un item est journalisé (item_price_log).</div>
87+ </div>
88+ </section>
89+
90+ <section>
91+ <p class="kicker">Guide pas à pas</p>
92+ <h2>Utiliser le site en 4 étapes</h2>
93+ <div class="step">
94+ <div class="num">1</div>
95+ <div>
96+ <h3>Cherchez un resto ou un plat depuis l'accueil</h3>
97+ <p>La recherche couvre les 14 091 restos et les 79 159 plats. La recherche par plat est groupée par marque — la poutine d'une chaîne n'est pas répétée 76 fois. Filtrez par cuisine, type d'établissement ou gamme de prix.</p>
98+ <div class="shot"><img src="./img/etape1.png" alt="Étape 1 — Cherchez un resto ou un plat depuis l'accueil"></div>
99+ </div>
100+ </div>
101+ <div class="step">
102+ <div class="num">2</div>
103+ <div>
104+ <h3>Parcourez les restos d'une ville</h3>
105+ <p>Les pages villes (ex. /ville/quebec) listent les établissements avec leur plat médian, leurs cuisines et le badge « UEAT (commande en ligne) » quand le menu complet avec prix réels est disponible.</p>
106+ <div class="shot"><img src="./img/etape2.png" alt="Étape 2 — Parcourez les restos d'une ville"></div>
107+ </div>
108+ </div>
109+ <div class="step">
110+ <div class="num">3</div>
111+ <div>
112+ <h3>Ouvrez la fiche d'un restaurant</h3>
113+ <p>Le menu complet par section, chaque plat avec sa photo, son prix et son contexte (salle / emporter / livraison), les options et formats avec leurs suppléments (« Mini +3,70 $ »), les coordonnées et la carte. Un prix n'est jamais présenté sans son contexte.</p>
114+ <div class="shot"><img src="./img/etape3.png" alt="Étape 3 — Ouvrez la fiche d'un restaurant"></div>
115+ </div>
116+ </div>
117+ <div class="step">
118+ <div class="num">4</div>
119+ <div>
120+ <h3>Comparez le marché sur /stats</h3>
121+ <p>Le tableau de bord : volumes par région et cuisine, prix médians, chaînes suivies — et des rapports PDF téléchargeables (catalogue + constructeur de rapport).</p>
122+ <div class="shot"><img src="./img/etape4.png" alt="Étape 4 — Comparez le marché sur /stats"></div>
123+ </div>
124+ </div>
125+ </section>
126+
127+ <section>
128+ <p class="kicker">Sous le capot</p>
129+ <h2>D'où viennent les données</h2>
130+ <p>Pipeline (patron Lou·Ka) : <b>connecteurs → normalisation → déduplication → SQLite → API/frontend</b>.
131+ La découverte passe par OpenStreetMap (API Overpass) : 13 500+ établissements nommés dans les 17 régions. Les menus et prix
132+ réels viennent du <b>connecteur UEAT templatisé</b> (API GraphQL) qui couvre 29 intégrations de chaînes (Sushi Shop, Valentine,
133+ Normandin, Thaïzone, Chez Ashton…), complété par des connecteurs directs / Firecrawl / Scrapfly. Une boucle hebdomadaire
134+ (<code>resto-ka-sync</code>) resynchronise tout ; un menu sans contexte de prix est rejeté à l'ingestion, un menu salle n'est jamais
135+ écrasé par un menu livraison, et une alerte de dérive suspend les retraits si une source chute anormalement. Géocodage à 99 %
136+ (Nominatim + Adresses Québec). Le registre public <a href="/sources">/sources</a> documente chaque source.</p>
137+ </section>
138+
139+ <section>
140+ <p class="kicker">Questions fréquentes</p>
141+ <h2>FAQ</h2>
142+ <div class="card" style="margin:10px 0"><b>Pourquoi les prix diffèrent-ils des apps de livraison ?</b><p style="margin:6px 0 0">Les apps de livraison majorent les prix de 25-30 %. Resto·Ka capte les prix takeout réels sur la plateforme de commande des restos eux-mêmes.</p></div>
143+ <div class="card" style="margin:10px 0"><b>Puis-je commander via Resto·Ka ?</b><p style="margin:6px 0 0">Non. Resto·Ka est un index : chaque fiche renvoie vers le site ou la plateforme de commande du resto. Groupe KA n'est partie à aucune transaction.</p></div>
144+ <div class="card" style="margin:10px 0"><b>Pourquoi certains restos n'ont-ils pas de menu ?</b><p style="margin:6px 0 0">Les 14 091 restos viennent de la découverte OpenStreetMap ; le menu complet avec prix n'est disponible que lorsque le resto a une source de menu connectable (738 restos, 24 chaînes suivies à ce jour).</p></div>
145+ <div class="card" style="margin:10px 0"><b>Les succursales d'une chaîne sont-elles fusionnées ?</b><p style="margin:6px 0 0">Jamais : la déduplication inter-sources préserve chaque succursale, chacune avec son menu et ses prix propres.</p></div>
146+ <div class="card" style="margin:10px 0"><b>À quelle fréquence les menus sont-ils rafraîchis ?</b><p style="margin:6px 0 0">La boucle de synchronisation est hebdomadaire ; chaque changement de prix est archivé dans l'historique de l'item.</p></div>
147+ </section>
148+</div>
149+
150+<footer class="doc">
151+ <div class="container">
152+ <p><b>Resto·Ka</b> — un service <a href="https://www.groupe-ka.com">Groupe KA</a>.
153+ Écosystème : les 13 plateformes sont liées au pied de chaque site.</p>
154+ <p>© 2026 Groupe KA — Simon-Pierre Boucher · <a href="mailto:contact@spboucher.ai">contact@spboucher.ai</a></p>
155+ </div>
156+</footer>
157+</body>
158+</html>
added frontend/public/doc/resto-ka-documentation.pdf +0 −0

Binary file not shown.

modified frontend/src/App.tsx +1 −1
@@ -234,7 +234,7 @@ function Footer() {
234 </div> 234 </div>
235 </div> 235 </div>
236 </div> 236 </div>
237− <KaFooter siteId="resto-ka" /> 237+ <KaFooter siteId="resto-ka" localLegal={[{ label: "Documentation", href: "/doc/" }]} />
238 </> 238 </>
239 ); 239 );
240 } 240 }
modified restoka/web.py +11 −1
@@ -16,7 +16,7 @@ from pathlib import Path
16 from fastapi import BackgroundTasks, Body, FastAPI, HTTPException, Query, Request 16 from fastapi import BackgroundTasks, Body, FastAPI, HTTPException, Query, Request
17 from fastapi.middleware.cors import CORSMiddleware 17 from fastapi.middleware.cors import CORSMiddleware
18 from fastapi.middleware.gzip import GZipMiddleware 18 from fastapi.middleware.gzip import GZipMiddleware
19−from fastapi.responses import FileResponse, Response 19+from fastapi.responses import FileResponse, RedirectResponse, Response
20 from fastapi.staticfiles import StaticFiles 20 from fastapi.staticfiles import StaticFiles
21 21
22 from . import db, hubfav, ingest 22 from . import db, hubfav, ingest
@@ -600,6 +600,16 @@ if FRONTEND_DIST.exists():
600 resp.headers["Cache-Control"] = "no-cache" 600 resp.headers["Cache-Control"] = "no-cache"
601 return resp 601 return resp
602 602
603+ # Documentation utilisateur (frontend/public/doc → dist/doc) — routes
604+ # explicites AVANT le catch-all SPA pour servir /doc/ comme un index.
605+ @app.get("/doc")
606+ def doc_redirect():
607+ return RedirectResponse("/doc/", status_code=308)
608+
609+ @app.get("/doc/")
610+ def doc_index():
611+ return FileResponse(FRONTEND_DIST / "doc" / "index.html")
612+
603 _CLIENT_PREFIXES = ("resto/", "sources", "stats", "favoris", "contact") 613 _CLIENT_PREFIXES = ("resto/", "sources", "stats", "favoris", "contact")
604 614
605 @app.get("/{full_path:path}") 615 @app.get("/{full_path:path}")
606 616
Menu

Theme