# Ora·Ka — Cinq agrégateurs. Une barre. **Auteur : Simon-Pierre Boucher — contact@spboucher.ai** [![Live](https://img.shields.io/badge/live-www.ora--ka.com-d9f26b?style=flat-square&labelColor=141814)](https://www.ora-ka.com) [![Python](https://img.shields.io/badge/python-3.14-3776ab?style=flat-square&labelColor=141814&logo=python&logoColor=white)](requirements.txt) [![FastAPI](https://img.shields.io/badge/fastapi-%E2%89%A50.110-009688?style=flat-square&labelColor=141814)](oraka/web.py) [![React](https://img.shields.io/badge/react-18-61dafb?style=flat-square&labelColor=141814&logo=react&logoColor=61dafb)](apps) [![SQLite](https://img.shields.io/badge/sqlite-%E2%89%881.4_Go-044a64?style=flat-square&labelColor=141814&logo=sqlite&logoColor=white)](apps) [![Embeddings](https://img.shields.io/badge/openai-text--embedding--3--small_256d-6e56cf?style=flat-square&labelColor=141814)](oraka/semantic.py) [![Inscriptions](https://img.shields.io/badge/inscriptions-400_498-d9f26b?style=flat-square&labelColor=141814)](https://www.ora-ka.com/api/overview) [![Node](https://img.shields.io/badge/prod-M4M64a%3A8200_·_pm2_·_ngrok-ff5a2a?style=flat-square&labelColor=141814)](#production-maclustr--nœud-m4m64a-port-8200) Ora-Ka unifie les cinq agrégateurs automatisés de la famille Ka en une seule plateforme : **une barre de recherche**, un moteur hybride exact + sémantique, et les cinq applications d'origine **reproduites fidèlement** sous leur propre préfixe — sans modifier une ligne de leurs backends. ![Landing Ora-Ka](docs/screenshots/landing.png) ## Métriques (prod, 2026-08-13) | Univers | Préfixe | Contenu | Inscriptions | Sources | Accent | |-----------|-----------|--------------------------------|-------------:|--------:|-----------| | Immo·Ka | `/immo/` | Propriétés à vendre (QC) | 48 487 | 33 | `#e23744` | | Lou·Ka | `/lou/` | Logements à louer | 16 563 | 255 | `#d9f26b` | | Fabri·Ka | `/fabri/` | Produits québécois | 291 132 | 3 224 boutiques | `#c4532e` | | Auto·Ka | `/auto/` | Véhicules d'occasion | 17 997 | 138 | `#ff5a2a` | | Food·Ka | `/food/` | Épicerie comparée | 26 319 | 30 | `#1f7a4d` | | **Total** | | | **400 498** | | | | Dimension | Valeur | |------------------------|--------| | Index sémantique | 362 508 vecteurs · text-embedding-3-small · 256 d float16 · ≈ 164 Mo npz | | Bases SQLite | ≈ 1,4 Go (5 bases + 2 bases quartier) | | Moteur `oraka/` | 2 090 lignes Python (8 modules) | | Backends embarqués | 339 fichiers Python · 65 852 lignes (inchangés) | | Frontends | 5 SPA React 18 + Vite, rebuildées sous préfixe | | Cadence d'ingestion | immo 180 min · lou 60 · auto 120 · fabri/food 360 | | Latence recherche | brute-force numpy sur npz en RAM, hot-reload sur mtime | ## La recherche hybride Une seule barre (`/api/search`) interroge les cinq univers avec un **score unifié par candidat** — pas de cascade exact-puis-sémantique : - **Candidats** : correspondance exacte par jetons AND (LIKE/FTS5) ∪ top-k sémantique par univers. - **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. - **Coupure dynamique** (best − 0,22) + **garantie de diversité** : chaque univers ≥ best − 0,30 est épinglé dans le top 12. - **Affichage** : pertinence relative en % (score/best), jauge, surlignage des jetons (préfixe en début de mot, stopwords exclus). | « condo 2 chambres montréal » | « fromage du québec » | |---|---| | ![Recherche condo](docs/screenshots/recherche-condo.png) | ![Recherche fromage](docs/screenshots/recherche-fromage.png) | L'index (`oraka/semantic.py`) vit dans `data/semantic/.npz` ; le serveur le recharge à chaud sur changement de mtime — un simple rsync des npz suffit, sans redémarrage. Rebuild incrémental : `run.py index` (~10 min, ~0,25 $ ; à relancer après les grosses syncs sinon l'index dérive). ## Les cinq univers Chaque app est montée telle quelle sous son préfixe — mêmes fiches, mêmes cartes, mêmes filtres que l'app d'origine : | | | |---|---| | ![Immo·Ka](docs/screenshots/immo.png) `/immo/` | ![Lou·Ka](docs/screenshots/lou.png) `/lou/` | | ![Fabri·Ka](docs/screenshots/fabri.png) `/fabri/` | ![Auto·Ka](docs/screenshots/auto.png) `/auto/` | | ![Food·Ka](docs/screenshots/food.png) `/food/` | ![Splash Groupe KA](docs/screenshots/splash.png) splash au boot | *Captures régénérables : `node scripts/screenshots.mjs` (playwright requis).* ## Architecture ``` ora-ka/ run.py # serve [port] | sync [app ...] | watch | index oraka/ web.py # app maîtresse : landing + /api/overview + /healthz + 5 mounts landing.py # landing « famille Ka × verre » (splash, ticker, cartes) search.py # /api/search — score unifié exact ∪ sémantique semantic.py # index npz par univers, hot-reload, OpenAI embeddings auth.py # SSO « KA ID » via le hub groupe-ka.com hubfav.py # favoris synchronisés au hub legal.py # conditions + confidentialité apps/ immo/ { immoka/, data/{immoka.db, quartier.db, sources.json…}, frontend/ } lou/ { louka/, data/{louka.db, quartier.db, sources.json}, frontend/ } fabri/ { fabrika/, data/{fabrika.db, stores.json}, frontend/ } auto/ { autoka/, data/{autoka.db, sources.json}, frontend/ } food/ { foodka/, data/{foodka.db, sources.json}, frontend/ } data/semantic/ # .npz + .meta.json (non versionnés) ``` Chaque app conserve son arborescence d'origine (`/`, `data/`, `frontend/dist/` frères) — ses chemins internes calculés via `__file__` restent valides, **aucun code backend n'a été modifié**. Les frontends React/Vite ont uniquement été rebuildés avec `base: '//'` + `basename` react-router + `API_BASE = import.meta.env.BASE_URL` (+ cas spéciaux : worker MapLibre de lou, logos de food). Design de la landing : hybride « famille Ka × verre » — papier grainé `#f5f3ee`, encre `#141814`, lime `#d9f26b`, JetBrains Mono uppercase, bordures encre + ombres décalées dures, badge « Ka » incliné −2°, ticker, splash Groupe KA (1,6 s, sessionStorage). ## Lancement ```bash python3 -m venv .venv && .venv/bin/pip install -r requirements.txt # builds frontend (une fois) : cd apps//frontend && npm install && npm run build .venv/bin/python run.py serve 8200 # plateforme complète .venv/bin/python run.py sync # ingestion des 5 apps (ou : sync immo lou …) .venv/bin/python run.py watch # boucle permanente, cadence native par app .venv/bin/python run.py index # (re)construit l'index sémantique ``` `.env` (non versionné) : `OPENAI_API_KEY` (index sémantique), `FIRECRAWL_API_KEY`, `SCRAPFLY_KEY`, `SCRAPFLY_API_KEY` (ingestion seulement, jamais le serveur) ; `KA_SSO_SECRET`, `KA_HUB_URL` (SSO KA ID). ## Production (MacLustr — nœud M4M64a, port 8200) ```bash pm2 start .venv/bin/python --name ora-ka-web --interpreter none --cwd ~/apps/ora-ka -- run.py serve 8200 pm2 start .venv/bin/python --name ora-ka-sync --interpreter none --cwd ~/apps/ora-ka -- run.py watch pm2 start /opt/homebrew/bin/ngrok --name ora-ka-ngrok --interpreter none -- http --url=www.ora-ka.com 8200 ``` Healthcheck : `GET /healthz` — vérifie la présence des 5 bases SQLite. Stats agrégées en direct : `GET /api/overview`. --- © 2026 Groupe KA — Ora-Ka agrège des données publiques ; chaque inscription reste la propriété de sa source.