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%
4.4 KB · 118 lines python
Raw Blame History
1#!/usr/bin/env python32# -----------------------------------------------------------------------------3# Ora-Ka — Plateforme unifiée des agrégateurs Ka (Immo·Ka, Lou·Ka, Fabri·Ka,4#          Auto·Ka, Food·Ka) — un seul serveur, cinq univers.5# Auteur : Simon-Pierre Boucher — contact@spboucher.ai6# run.py : point d'entrée — `serve`, `sync`, `watch`7# -----------------------------------------------------------------------------8"""Utilisation :9    python run.py serve [port]            # démarre la plateforme unifiée (défaut 8200)10    python run.py sync [app ...]          # synchronise les 5 apps (ou celles nommées :11                                          #   immo, lou, fabri, auto, food)12    python run.py watch                   # boucle de synchronisation permanente13                                          #   (cadence native de chaque app)14"""15from __future__ import annotations1617import os18import sys19import threading20import time21from pathlib import Path2223ROOT = Path(__file__).resolve().parent2425# Charger .env (FIRECRAWL_API_KEY, SCRAPFLY_KEY, SCRAPFLY_API_KEY) sans dépendance26_env = ROOT / ".env"27if _env.exists():28    for line in _env.read_text().splitlines():29        line = line.strip()30        if line and not line.startswith("#") and "=" in line:31            k, _, v = line.partition("=")32            os.environ.setdefault(k.strip(), v.strip())3334# Rendre les 5 packages importables (chaque app garde sa racine intacte :35# apps/<x>/<package>/ avec data/ et frontend/dist/ frères — leurs chemins36# internes calculés via __file__ restent donc valides).37APPS = ["immo", "lou", "fabri", "auto", "food"]38for _d in APPS:39    sys.path.insert(0, str(ROOT / "apps" / _d))4041# Cadence native de chaque app (minutes) — reprise des déploiements individuels42WATCH_MINUTES = {"immo": 180, "lou": 60, "fabri": 360, "auto": 120, "food": 360}434445def _ingest_module(app: str):46    if app == "immo":47        from immoka import ingest48    elif app == "lou":49        from louka import ingest50    elif app == "fabri":51        from fabrika import ingest52    elif app == "auto":53        from autoka import ingest54    elif app == "food":55        from foodka import ingest56    else:57        raise ValueError(f"app inconnue : {app}")58    return ingest596061def _watch_one(app: str) -> None:62    """Boucle de sync d'une app, avec sa cadence native (reproduit son watch)."""63    interval = WATCH_MINUTES[app] * 6064    while True:65        started = time.time()66        try:67            print(f"[ora-ka] sync {app} ...", flush=True)68            if app == "lou":69                # Lou-Ka enchaîne sync + geocode + poi + quartier dans son watch70                from louka import ingest, geocode, poi, quartier71                ingest.run(None)72                geocode.run(120)73                poi.run(80)74                quartier.enrich()75            else:76                _ingest_module(app).run(None)77            print(f"[ora-ka] sync {app} terminé en {time.time() - started:.0f}s", flush=True)78        except Exception as exc:  # une app en échec ne tue jamais la boucle79            print(f"[ora-ka] sync {app} ÉCHEC : {exc}", file=sys.stderr, flush=True)80        time.sleep(max(60.0, interval - (time.time() - started)))818283def main() -> None:84    cmd = sys.argv[1] if len(sys.argv) > 1 else "serve"85    if cmd == "serve":86        import uvicorn87        port = int(sys.argv[2]) if len(sys.argv) > 2 else 820088        uvicorn.run("oraka.web:app", host="0.0.0.0", port=port)89    elif cmd == "sync":90        targets = [a for a in sys.argv[2:] if a in APPS] or APPS91        for app in targets:92            try:93                print(f"[ora-ka] sync {app} ...", flush=True)94                _ingest_module(app).run(None)95            except Exception as exc:96                print(f"[ora-ka] sync {app} ÉCHEC : {exc}", file=sys.stderr, flush=True)97    elif cmd == "index":98        from oraka import semantic99        targets = [a for a in sys.argv[2:] if a in APPS] or None100        semantic.build_index(targets)101    elif cmd == "watch":102        threads = [103            threading.Thread(target=_watch_one, args=(app,), daemon=True, name=f"watch-{app}")104            for app in APPS105        ]106        for t in threads:107            t.start()108            time.sleep(5)  # décale les démarrages pour lisser la charge109        while True:110            time.sleep(3600)111    else:112        print(__doc__)113        sys.exit(1)114115116if __name__ == "__main__":117    main()118