SPB Git forge

spb/immo-ka

Public

Immo-Ka — agrégateur des propriétés à vendre au Québec (73 connecteurs, ~40 000 annonces, React+FastAPI)

112commits 1branches 0releases
125.4 MBsize
maindefault branch
13 days agolast push
Python 47.5% HTML 27.9% TypeScript 15.5% CSS 7.2% JavaScript 2%

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 7cefef7

8 changed files +212 −1

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/immo-ka-documentation.pdf +0 −0

Binary file not shown.

added frontend/public/doc/index.html +201 −0
@@ -0,0 +1,201 @@
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 — Immo·Ka</title>
7 +<meta name="description" content="Comment fonctionne Immo·Ka : guide pas à pas, données, architecture.">
8 +<style>
9 + :root{ --ink:#101014; --paper:#faf9f6; --accent:#e23744; --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 + header.doc .btn-pdf{color:#fff}
21 + .hero{padding:48px 0 8px}
22 + .hero h1{font-size:clamp(28px,5vw,44px);line-height:1.1;margin:0 0 12px}
23 + .hero h1 mark{background:var(--accent);color:#fff;padding:2px 8px}
24 + .hero p.lead{font-size:18px;color:var(--muted);max-width:640px}
25 + .kicker{font-size:12px;letter-spacing:.14em;text-transform:uppercase;color:var(--muted);
26 + border-left:3px solid var(--accent);padding-left:10px;margin:40px 0 8px;font-weight:700}
27 + section{padding:8px 0 16px}
28 + h2{font-size:26px;margin:6px 0 12px}
29 + .step{display:grid;grid-template-columns:56px 1fr;gap:16px;margin:26px 0;align-items:start}
30 + .step .num{width:44px;height:44px;border-radius:50%;background:var(--ink);color:var(--accent);
31 + display:flex;align-items:center;justify-content:center;font-weight:800;font-size:18px}
32 + .step h3{margin:6px 0 6px;font-size:19px}
33 + .step p{margin:0 0 12px;color:#3a3a40}
34 + .shot{border:1px solid var(--line);border-radius:12px;overflow:hidden;box-shadow:0 8px 30px rgba(16,16,20,.08)}
35 + .shot img{display:block;width:100%;height:auto}
36 + .cards{display:grid;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));gap:14px;margin:16px 0}
37 + .card{border:1px solid var(--line);border-radius:12px;padding:16px;background:#fff}
38 + .card b{display:block;margin-bottom:6px}
39 + table{border-collapse:collapse;width:100%;font-size:15px}
40 + th,td{border:1px solid var(--line);padding:8px 10px;text-align:left;vertical-align:top}
41 + th{background:var(--ink);color:var(--paper)}
42 + .faq dt{font-weight:700;margin:18px 0 4px}
43 + .faq dd{margin:0;color:#3a3a40}
44 + footer.doc{margin-top:56px;background:var(--ink);color:var(--paper);padding:28px 0;font-size:14px}
45 + footer.doc a{color:var(--accent);text-decoration:none}
46 + @media print{
47 + header.doc .btn-pdf{display:none}
48 + .shot{box-shadow:none;break-inside:avoid}
49 + .step{break-inside:avoid}
50 + a{color:inherit;text-decoration:none}
51 + }
52 + @media(max-width:640px){ .step{grid-template-columns:40px 1fr} .step .num{width:34px;height:34px;font-size:15px} }
53 +</style>
54 +</head>
55 +<body>
56 +<header class="doc">
57 + <div class="container">
58 + <a class="wordmark" href="/">Immo<span class="ka">·Ka</span></a>
59 + <nav style="display:flex;gap:14px;align-items:center">
60 + <a href="/">← Retour au site</a>
61 + <a class="btn-pdf" href="./immo-ka-documentation.pdf" download>Télécharger le PDF</a>
62 + </nav>
63 + </div>
64 +</header>
65 +
66 +<div class="container">
67 + <div class="hero">
68 + <p class="kicker">Documentation · mise à jour 2026-08-24</p>
69 + <h1>Comment fonctionne <mark>Immo·Ka</mark></h1>
70 + <p class="lead">Toutes les propriétés à vendre du Québec — maisons, condos, plex et terrains — réunies au même endroit, toujours à jour.</p>
71 + </div>
72 +
73 + <section>
74 + <p class="kicker">Vue d'ensemble</p>
75 + <h2>À quoi sert le site</h2>
76 + <p><strong>Immo-Ka est un agrégateur immobilier indépendant</strong> pour la province de Québec.
77 + Plutôt que de jongler entre les sites de RE/MAX, Royal LePage, Sutton, Via Capitale, Century 21,
78 + DuProprio et des dizaines d'autres bannières — chacun avec sa navigation et ses filtres —,
79 + Immo-Ka visite chaque source avec un connecteur dédié, ramène chaque annonce dans un format
80 + unique et la garde à jour, avec un lien direct vers l'annonce originale. C'est le pendant
81 + « à vendre » de <a href="https://www.lou-ka.com">Lou-Ka</a> (location), branché sur le moteur
82 + d'estimation <a href="https://www.vrai-prix.com">Vrai-Prix</a> et sur le compte unique KA ID.</p>
83 + <div class="cards">
84 + <div class="card"><b>57 900+ propriétés actives</b>Maisons, condos, plex, terrains et commerces, partout au Québec.</div>
85 + <div class="card"><b>105 connecteurs</b>Flux centraux de bannières, sous-agences en plan B, plateformes sans courtier — 22 bannières et 159 sous-agences.</div>
86 + <div class="card"><b>2 825 villes couvertes</b>De Montréal à la Gaspésie, avec pages ville / type indexables.</div>
87 + <div class="card"><b>Resynchronisation aux 4 h</b>Ajouts, baisses de prix et retraits détectés automatiquement, en continu.</div>
88 + </div>
89 + </section>
90 +
91 + <section>
92 + <p class="kicker">Guide pas à pas</p>
93 + <h2>Utiliser le site en 4 étapes</h2>
94 +
95 + <div class="step">
96 + <div class="num">1</div>
97 + <div>
98 + <h3>Arriver sur l'accueil</h3>
99 + <p>La page d'accueil affiche les chiffres en direct de l'agrégat (propriétés actives, agences,
100 + villes, prix moyen) et la barre de recherche : texte libre (adresse, ville, n° MLS),
101 + sélecteur de ville, type de propriété et fourchette de prix. Les raccourcis Maison,
102 + Terrain, Condo, Duplex… lancent une recherche en un clic.</p>
103 + <div class="shot"><img src="./img/etape1.png" alt="Étape 1 — page d'accueil d'Immo-Ka"></div>
104 + </div>
105 + </div>
106 +
107 + <div class="step">
108 + <div class="num">2</div>
109 + <div>
110 + <h3>Filtrer et parcourir les résultats</h3>
111 + <p>Combinez ville + secteur, type, agence, prix, chambres, salles de bain et superficie ;
112 + triez par prix ou par récence. Chaque filtre actif apparaît en pastille (retirable d'un clic)
113 + et l'état complet de la recherche est encodé dans l'URL — elle se partage telle quelle.
114 + Chaque carte de résultat montre photo, prix, adresse, caractéristiques et bannière source.</p>
115 + <div class="shot"><img src="./img/etape2.png" alt="Étape 2 — résultats filtrés (condos à Montréal, 350 à 700 k$)"></div>
116 + </div>
117 + </div>
118 +
119 + <div class="step">
120 + <div class="num">3</div>
121 + <div>
122 + <h3>Ouvrir la fiche complète</h3>
123 + <p>La fiche regroupe la galerie photo avec lightbox, le prix et les caractéristiques Centris,
124 + les pièces et dimensions par étage, la description et les inclusions, puis les analyses :
125 + estimation <strong>Vrai-Prix</strong> (jauge P10–P90 avec verdict sur-évalué / aligné /
126 + sous l'estimation), rôle d'évaluation foncière, historique de prix horodaté, profil du
127 + quartier (revenu médian, proximité des services, îlot de chaleur) et couches territoriales
128 + (inondation, qualité de l'air, essence, transport en commun, estimation Hydro-Québec).
129 + Un lien mène toujours vers l'annonce originale chez l'agence.</p>
130 + <div class="shot"><img src="./img/etape3.png" alt="Étape 3 — fiche détail : galerie 48 photos et mini-carte 3D d'emplacement"></div>
131 + </div>
132 + </div>
133 +
134 + <div class="step">
135 + <div class="num">4</div>
136 + <div>
137 + <h3>Chercher sur la carte Ka Maps</h3>
138 + <p>La bascule Liste / Carte ouvre la carte Ka Maps (moteur Mapbox GL 3D, framework carto
139 + maison du Groupe KA) : grappes avec nombre d'annonces et prix moyen, marqueurs colorés
140 + selon l'écart au Vrai-Prix, option « Rechercher en déplaçant la carte » et bascule 2D/3D.
141 + Le compteur indique les propriétés visibles dans la zone et celles hors carte.</p>
142 + <div class="shot"><img src="./img/etape4.png" alt="Étape 4 — carte Ka Maps avec grappes de prix"></div>
143 + </div>
144 + </div>
145 + </section>
146 +
147 + <section>
148 + <p class="kicker">Sous le capot</p>
149 + <h2>D'où viennent les données</h2>
150 + <p>Immo-Ka fait tourner <strong>105 connecteurs</strong> — un par source : flux centraux des
151 + bannières (Meilisearch, Algolia, source.immo, wp-json), JSON-LD, sitemaps, et Firecrawl pour
152 + les sites difficiles. Chaque annonce est <strong>normalisée vers un schéma unique</strong>
153 + (<code>PropertyListing</code>) puis <strong>dédupliquée par numéro Centris</strong> : la même
154 + propriété affichée par la bannière et par sa sous-agence ne compte qu'une fois.</p>
155 + <p>Les sites d'agences n'offrent pas de webhooks : Immo-Ka en reproduit l'équivalent par
156 + <strong>synchronisation périodique + hash de contenu</strong>. Un processus autonome
157 + (<code>immo-ka-sync</code>) resynchronise les sources <strong>aux 4 heures</strong> : les
158 + nouvelles annonces apparaissent, les changements de prix sont journalisés (historique visible
159 + sur la fiche) et les annonces disparues (propriété vendue ou retirée) sont retirées après un
160 + délai de grâce qui protège contre les ratés ponctuels d'une source.</p>
161 + <p>Une <strong>couche qualité</strong> filtre ce qui est publié : contrôles de complétude et de
162 + cohérence, audit d'images, quarantaine des fiches douteuses et fusion des doublons en un
163 + <strong>golden record</strong> — la meilleure version de chaque propriété. S'y ajoutent les
164 + enrichissements locaux : géocodage, estimation Vrai-Prix, rôle d'évaluation, données de
165 + quartier (StatCan, INSPQ), registre des loyers, zones inondables (BDZI), qualité de l'air,
166 + prix de l'essence, commerces et transport en commun, estimation Hydro-Québec précalculée.</p>
167 + </section>
168 +
169 + <section>
170 + <p class="kicker">Questions fréquentes</p>
171 + <h2>FAQ</h2>
172 + <dl class="faq">
173 + <dt>Les annonces sont-elles à jour ?</dt>
174 + <dd>Oui : chaque source est resynchronisée aux 4 heures. Les changements de prix et les
175 + retraits sont détectés automatiquement ; une annonce vendue disparaît après un court délai
176 + de grâce.</dd>
177 + <dt>Peut-on acheter ou faire une offre via Immo-Ka ?</dt>
178 + <dd>Non. Immo-Ka est un moteur de recherche : chaque fiche renvoie vers l'annonce originale
179 + chez l'agence ou le vendeur, où se poursuit la démarche.</dd>
180 + <dt>D'où vient l'estimation de valeur affichée ?</dt>
181 + <dd>Du moteur Vrai-Prix du Groupe KA : un modèle hédonique combiné à des comparables, qui
182 + produit une fourchette P10–P90 et un verdict par rapport au prix demandé.</dd>
183 + <dt>Pourquoi une propriété n'apparaît-elle qu'une fois alors qu'elle est sur plusieurs sites ?</dt>
184 + <dd>Les doublons sont fusionnés par numéro Centris : la version la plus complète est publiée,
185 + les autres sont masquées.</dd>
186 + <dt>Faut-il un compte ?</dt>
187 + <dd>Non pour chercher. Un compte KA ID (gratuit, commun aux 13 plateformes du Groupe KA)
188 + permet de garder des favoris partagés dans « Mon univers Ka ».</dd>
189 + </dl>
190 + </section>
191 +</div>
192 +
193 +<footer class="doc">
194 + <div class="container">
195 + <p><b>Immo·Ka</b> — un service <a href="https://www.groupe-ka.com">Groupe KA</a>.
196 + Écosystème : les 13 plateformes sont liées au pied de chaque site.</p>
197 + <p>© 2026 Groupe KA — Simon-Pierre Boucher · <a href="mailto:contact@spboucher.ai">contact@spboucher.ai</a></p>
198 + </div>
199 +</footer>
200 +</body>
201 +</html>
modified frontend/src/App.tsx +1 −0
@@ -201,6 +201,7 @@ function Header() {
201 201 }
202 202
203 203 const LOCAL_LEGAL = [
204 + { label: "Documentation", href: "/doc/" },
204 205 { label: "Conditions (Immo-Ka)", href: "/conditions" },
205 206 { label: "Confidentialité (Immo-Ka)", href: "/confidentialite" },
206 207 { label: "Contact", href: "/contact" },
modified immoka/web.py +10 −1
@@ -13,7 +13,7 @@ from pathlib import Path
13 13 from fastapi import BackgroundTasks, Body, FastAPI, HTTPException, Query, Request
14 14 from fastapi.middleware.cors import CORSMiddleware
15 15 from fastapi.middleware.gzip import GZipMiddleware
16 −from fastapi.responses import FileResponse, Response
16 +from fastapi.responses import FileResponse, RedirectResponse, Response
17 17 from fastapi.staticfiles import StaticFiles
18 18
19 19 from . import auth, db, favorites, ingest, seo
@@ -599,6 +599,15 @@ if FRONTEND_DIR.exists():
599 599 if (FRONTEND_DIR / "assets").is_dir():
600 600 app.mount("/assets", StaticFiles(directory=FRONTEND_DIR / "assets"), name="assets")
601 601
602 + @app.get("/doc", include_in_schema=False)
603 + def doc_redirect():
604 + return RedirectResponse("/doc/", status_code=301)
605 +
606 + # /doc : documentation statique (index.html + captures + PDF) — montée
607 + # explicitement pour que le catch-all SPA ne l'intercepte pas.
608 + if (FRONTEND_DIR / "doc").is_dir():
609 + app.mount("/doc", StaticFiles(directory=FRONTEND_DIR / "doc", html=True), name="doc")
610 +
602 611 @app.get("/{full_path:path}")
603 612 def spa(full_path: str, request: Request):
604 613 target = FRONTEND_DIR / full_path
605 614