SPB Git

spb/ora-ka Public

Ora-Ka — cinq agrégateurs Ka, une barre de recherche hybride (exact + sémantique)

Python 80% TypeScript 12.9% CSS 6.8%
7.7 KB · 138 lines markdown
Rendered Raw Blame History
1# Ora·Ka — Cinq agrégateurs. Une barre.23**Auteur : Simon-Pierre Boucher — contact@spboucher.ai**45[![Live](https://img.shields.io/badge/live-www.ora--ka.com-d9f26b?style=flat-square&labelColor=141814)](https://www.ora-ka.com)6[![Python](https://img.shields.io/badge/python-3.14-3776ab?style=flat-square&labelColor=141814&logo=python&logoColor=white)](requirements.txt)7[![FastAPI](https://img.shields.io/badge/fastapi-%E2%89%A50.110-009688?style=flat-square&labelColor=141814)](oraka/web.py)8[![React](https://img.shields.io/badge/react-18-61dafb?style=flat-square&labelColor=141814&logo=react&logoColor=61dafb)](apps)9[![SQLite](https://img.shields.io/badge/sqlite-%E2%89%881.4_Go-044a64?style=flat-square&labelColor=141814&logo=sqlite&logoColor=white)](apps)10[![Embeddings](https://img.shields.io/badge/openai-text--embedding--3--small_256d-6e56cf?style=flat-square&labelColor=141814)](oraka/semantic.py)11[![Inscriptions](https://img.shields.io/badge/inscriptions-400_498-d9f26b?style=flat-square&labelColor=141814)](https://www.ora-ka.com/api/overview)12[![Node](https://img.shields.io/badge/prod-M4M64a%3A8200_·_pm2_·_ngrok-ff5a2a?style=flat-square&labelColor=141814)](#production-maclustr--nœud-m4m64a-port-8200)1314Ora-Ka unifie les cinq agrégateurs automatisés de la famille Ka en une seule15plateforme : **une barre de recherche**, un moteur hybride exact + sémantique,16et les cinq applications d'origine **reproduites fidèlement** sous leur propre17préfixe — sans modifier une ligne de leurs backends.1819![Landing Ora-Ka](docs/screenshots/landing.png)2021## Métriques (prod, 2026-08-13)2223| Univers   | Préfixe   | Contenu                        | Inscriptions | Sources | Accent    |24|-----------|-----------|--------------------------------|-------------:|--------:|-----------|25| Immo·Ka   | `/immo/`  | Propriétés à vendre (QC)       |       48 487 |      33 | `#e23744` |26| Lou·Ka    | `/lou/`   | Logements à louer              |       16 563 |     255 | `#d9f26b` |27| Fabri·Ka  | `/fabri/` | Produits québécois             |      291 132 | 3 224 boutiques | `#c4532e` |28| Auto·Ka   | `/auto/`  | Véhicules d'occasion           |       17 997 |     138 | `#ff5a2a` |29| Food·Ka   | `/food/`  | Épicerie comparée              |       26 319 |      30 | `#1f7a4d` |30| **Total** |           |                                |  **400 498** |         |           |3132| Dimension              | Valeur |33|------------------------|--------|34| Index sémantique       | 362 508 vecteurs · text-embedding-3-small · 256 d float16 · ≈ 164 Mo npz |35| Bases SQLite           | ≈ 1,4 Go (5 bases + 2 bases quartier) |36| Moteur `oraka/`        | 2 090 lignes Python (8 modules) |37| Backends embarqués     | 339 fichiers Python · 65 852 lignes (inchangés) |38| Frontends              | 5 SPA React 18 + Vite, rebuildées sous préfixe |39| Cadence d'ingestion    | immo 180 min · lou 60 · auto 120 · fabri/food 360 |40| Latence recherche      | brute-force numpy sur npz en RAM, hot-reload sur mtime |4142## La recherche hybride4344Une seule barre (`/api/search`) interroge les cinq univers avec un **score45unifié par candidat** — pas de cascade exact-puis-sémantique :4647- **Candidats** : correspondance exacte par jetons AND (LIKE/FTS5) ∪ top-k sémantique par univers.48- **Score** : cosinus (lookup par uid, même pour les hits exacts) + 0,22·couverture-jetons + 0,10·phrase + 0,05·exact + affinité d'univers data-driven.49- **Coupure dynamique** (best − 0,22) + **garantie de diversité** : chaque univers ≥ best − 0,30 est épinglé dans le top 12.50- **Affichage** : pertinence relative en % (score/best), jauge, surlignage des jetons (préfixe en début de mot, stopwords exclus).5152| « condo 2 chambres montréal » | « fromage du québec » |53|---|---|54| ![Recherche condo](docs/screenshots/recherche-condo.png) | ![Recherche fromage](docs/screenshots/recherche-fromage.png) |5556L'index (`oraka/semantic.py`) vit dans `data/semantic/<univers>.npz` ; le57serveur le recharge à chaud sur changement de mtime — un simple rsync des npz58suffit, sans redémarrage. Rebuild incrémental : `run.py index`59(~10 min, ~0,25 $ ; à relancer après les grosses syncs sinon l'index dérive).6061## Les cinq univers6263Chaque app est montée telle quelle sous son préfixe — mêmes fiches, mêmes64cartes, mêmes filtres que l'app d'origine :6566| | |67|---|---|68| ![Immo·Ka](docs/screenshots/immo.png) `/immo/` | ![Lou·Ka](docs/screenshots/lou.png) `/lou/` |69| ![Fabri·Ka](docs/screenshots/fabri.png) `/fabri/` | ![Auto·Ka](docs/screenshots/auto.png) `/auto/` |70| ![Food·Ka](docs/screenshots/food.png) `/food/` | ![Splash Groupe KA](docs/screenshots/splash.png) splash au boot |7172*Captures régénérables : `node scripts/screenshots.mjs` (playwright requis).*7374## Architecture7576```77ora-ka/78  run.py               # serve [port] | sync [app ...] | watch | index79  oraka/80    web.py             # app maîtresse : landing + /api/overview + /healthz + 5 mounts81    landing.py         # landing « famille Ka × verre » (splash, ticker, cartes)82    search.py          # /api/search — score unifié exact ∪ sémantique83    semantic.py        # index npz par univers, hot-reload, OpenAI embeddings84    auth.py            # SSO « KA ID » via le hub groupe-ka.com85    hubfav.py          # favoris synchronisés au hub86    legal.py           # conditions + confidentialité87  apps/88    immo/  { immoka/,  data/{immoka.db, quartier.db, sources.json…}, frontend/ }89    lou/   { louka/,   data/{louka.db,  quartier.db, sources.json},  frontend/ }90    fabri/ { fabrika/, data/{fabrika.db, stores.json},               frontend/ }91    auto/  { autoka/,  data/{autoka.db, sources.json},               frontend/ }92    food/  { foodka/,  data/{foodka.db, sources.json},               frontend/ }93  data/semantic/       # <univers>.npz + .meta.json (non versionnés)94```9596Chaque app conserve son arborescence d'origine (`<package>/`, `data/`,97`frontend/dist/` frères) — ses chemins internes calculés via `__file__`98restent valides, **aucun code backend n'a été modifié**. Les frontends99React/Vite ont uniquement été rebuildés avec `base: '/<préfixe>/'` +100`basename` react-router + `API_BASE = import.meta.env.BASE_URL`101(+ cas spéciaux : worker MapLibre de lou, logos de food).102103Design de la landing : hybride « famille Ka × verre » — papier grainé104`#f5f3ee`, encre `#141814`, lime `#d9f26b`, JetBrains Mono uppercase,105bordures encre + ombres décalées dures, badge « Ka » incliné −2°, ticker,106splash Groupe KA (1,6 s, sessionStorage).107108## Lancement109110```bash111python3 -m venv .venv && .venv/bin/pip install -r requirements.txt112# builds frontend (une fois) : cd apps/<x>/frontend && npm install && npm run build113.venv/bin/python run.py serve 8200   # plateforme complète114.venv/bin/python run.py sync         # ingestion des 5 apps (ou : sync immo lou …)115.venv/bin/python run.py watch        # boucle permanente, cadence native par app116.venv/bin/python run.py index        # (re)construit l'index sémantique117```118119`.env` (non versionné) : `OPENAI_API_KEY` (index sémantique),120`FIRECRAWL_API_KEY`, `SCRAPFLY_KEY`, `SCRAPFLY_API_KEY` (ingestion seulement,121jamais le serveur) ; `KA_SSO_SECRET`, `KA_HUB_URL` (SSO KA ID).122123## Production (MacLustr — nœud M4M64a, port 8200)124125```bash126pm2 start .venv/bin/python --name ora-ka-web  --interpreter none --cwd ~/apps/ora-ka -- run.py serve 8200127pm2 start .venv/bin/python --name ora-ka-sync --interpreter none --cwd ~/apps/ora-ka -- run.py watch128pm2 start /opt/homebrew/bin/ngrok --name ora-ka-ngrok --interpreter none -- http --url=www.ora-ka.com 8200129```130131Healthcheck : `GET /healthz` — vérifie la présence des 5 bases SQLite.132Stats agrégées en direct : `GET /api/overview`.133134---135136© 2026 Groupe KA — Ora-Ka agrège des données publiques ; chaque inscription137reste la propriété de sa source.138