#!/usr/bin/env python3 # ----------------------------------------------------------------------------- # Ora-Ka — Plateforme unifiée des agrégateurs Ka (Immo·Ka, Lou·Ka, Fabri·Ka, # Auto·Ka, Food·Ka) — un seul serveur, cinq univers. # Auteur : Simon-Pierre Boucher — contact@spboucher.ai # run.py : point d'entrée — `serve`, `sync`, `watch` # ----------------------------------------------------------------------------- """Utilisation : python run.py serve [port] # démarre la plateforme unifiée (défaut 8200) python run.py sync [app ...] # synchronise les 5 apps (ou celles nommées : # immo, lou, fabri, auto, food) python run.py watch # boucle de synchronisation permanente # (cadence native de chaque app) """ from __future__ import annotations import os import sys import threading import time from pathlib import Path ROOT = Path(__file__).resolve().parent # Charger .env (FIRECRAWL_API_KEY, SCRAPFLY_KEY, SCRAPFLY_API_KEY) sans dépendance _env = ROOT / ".env" if _env.exists(): for line in _env.read_text().splitlines(): line = line.strip() if line and not line.startswith("#") and "=" in line: k, _, v = line.partition("=") os.environ.setdefault(k.strip(), v.strip()) # Rendre les 5 packages importables (chaque app garde sa racine intacte : # apps/// avec data/ et frontend/dist/ frères — leurs chemins # internes calculés via __file__ restent donc valides). APPS = ["immo", "lou", "fabri", "auto", "food"] for _d in APPS: sys.path.insert(0, str(ROOT / "apps" / _d)) # Cadence native de chaque app (minutes) — reprise des déploiements individuels WATCH_MINUTES = {"immo": 180, "lou": 60, "fabri": 360, "auto": 120, "food": 360} def _ingest_module(app: str): if app == "immo": from immoka import ingest elif app == "lou": from louka import ingest elif app == "fabri": from fabrika import ingest elif app == "auto": from autoka import ingest elif app == "food": from foodka import ingest else: raise ValueError(f"app inconnue : {app}") return ingest def _watch_one(app: str) -> None: """Boucle de sync d'une app, avec sa cadence native (reproduit son watch).""" interval = WATCH_MINUTES[app] * 60 while True: started = time.time() try: print(f"[ora-ka] sync {app} ...", flush=True) if app == "lou": # Lou-Ka enchaîne sync + geocode + poi + quartier dans son watch from louka import ingest, geocode, poi, quartier ingest.run(None) geocode.run(120) poi.run(80) quartier.enrich() else: _ingest_module(app).run(None) print(f"[ora-ka] sync {app} terminé en {time.time() - started:.0f}s", flush=True) except Exception as exc: # une app en échec ne tue jamais la boucle print(f"[ora-ka] sync {app} ÉCHEC : {exc}", file=sys.stderr, flush=True) time.sleep(max(60.0, interval - (time.time() - started))) def main() -> None: cmd = sys.argv[1] if len(sys.argv) > 1 else "serve" if cmd == "serve": import uvicorn port = int(sys.argv[2]) if len(sys.argv) > 2 else 8200 uvicorn.run("oraka.web:app", host="0.0.0.0", port=port) elif cmd == "sync": targets = [a for a in sys.argv[2:] if a in APPS] or APPS for app in targets: try: print(f"[ora-ka] sync {app} ...", flush=True) _ingest_module(app).run(None) except Exception as exc: print(f"[ora-ka] sync {app} ÉCHEC : {exc}", file=sys.stderr, flush=True) elif cmd == "index": from oraka import semantic targets = [a for a in sys.argv[2:] if a in APPS] or None semantic.build_index(targets) elif cmd == "watch": threads = [ threading.Thread(target=_watch_one, args=(app,), daemon=True, name=f"watch-{app}") for app in APPS ] for t in threads: t.start() time.sleep(5) # décale les démarrages pour lisser la charge while True: time.sleep(3600) else: print(__doc__) sys.exit(1) if __name__ == "__main__": main()