SPB Git forge

spb/resto-ka

Public

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

52commits 1branches 0releases
11.6 MBsize
maindefault branch
19 days agolast push
Python 69.3% TypeScript 16.7% CSS 7.9% JavaScript 4.7% HTML 1.4%

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 1 mo ago (Aug 24, 2026) parent e016aa8

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 234 </div>
235 235 </div>
236 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 16 from fastapi import BackgroundTasks, Body, FastAPI, HTTPException, Query, Request
17 17 from fastapi.middleware.cors import CORSMiddleware
18 18 from fastapi.middleware.gzip import GZipMiddleware
19 −from fastapi.responses import FileResponse, Response
19 +from fastapi.responses import FileResponse, RedirectResponse, Response
20 20 from fastapi.staticfiles import StaticFiles
21 21
22 22 from . import db, hubfav, ingest
@@ -600,6 +600,16 @@ if FRONTEND_DIST.exists():
600 600 resp.headers["Cache-Control"] = "no-cache"
601 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 613 _CLIENT_PREFIXES = ("resto/", "sources", "stats", "favoris", "contact")
604 614
605 615 @app.get("/{full_path:path}")
606 616