#!/usr/bin/env python3 # ----------------------------------------------------------------------------- # House-Ka — Homes-for-sale aggregator (Canada outside Québec) # Auteur : Simon-Pierre Boucher — contact@spboucher.ai # run.py : point d'entrée — `sync`, `watch`, `serve` # ----------------------------------------------------------------------------- """Utilisation : python run.py sync [source ...] # synchronise les propriétés python run.py watch [minutes] # synchronise en boucle (défaut 60 min) python run.py serve [port] # démarre l'API + le frontend (défaut 8090) python run.py list # liste les connecteurs enregistrés python run.py geocode [n] # géocode les annonces sans coordonnées (max n requêtes) python run.py geocode-audit [n] [dry] # audit coordonnées <-> ville (n = budget Nominatim) python run.py poi [n] # commodités de proximité par immeuble (max n requêtes) python run.py quartier [n] # rattache chaque annonce à son aire de diffusion (DAUID) python run.py mortgage-sync [slug…] # collecte les taux hypothécaires (toutes banques ou celles listées) python run.py mortgage-watch [min] # collecte en boucle (défaut IMMOKA_MORTGAGE_INTERVAL_MIN=180) python run.py mortgage-status # santé des providers de taux (OK/WARNING/ERROR) """ from __future__ import annotations import os import sys from pathlib import Path # Charger .env (FIRECRAWL_API_KEY, etc.) sans dépendance externe _env = Path(__file__).parent / ".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()) def main() -> None: cmd = sys.argv[1] if len(sys.argv) > 1 else "serve" if cmd == "sync": from immoka import ingest ingest.run(sys.argv[2:] or None) elif cmd == "watch": from immoka import ingest minutes = int(sys.argv[2]) if len(sys.argv) > 2 else 60 ingest.watch(minutes * 60) elif cmd == "list": from immoka.connectors import CONNECTORS for sid in sorted(CONNECTORS): print(sid) print(f"-- {len(CONNECTORS)} connecteur(s)") elif cmd == "geocode": from immoka import geocode geocode.run_batch(int(sys.argv[2]) if len(sys.argv) > 2 else None) elif cmd == "geocode1": # ancien mode 1-par-1 (+ repli Nominatim) from immoka import geocode geocode.run(int(sys.argv[2]) if len(sys.argv) > 2 else None) elif cmd == "geocode-audit": # cohérence coordonnées <-> ville annoncée from immoka import geocode budget = next((int(a) for a in sys.argv[2:] if a.isdigit()), None) geocode.run_audit(nominatim_budget=budget, apply="dry" not in sys.argv[2:]) elif cmd == "poi": from immoka import poi poi.run(int(sys.argv[2]) if len(sys.argv) > 2 else None) elif cmd == "quartier": from immoka import quartier quartier.enrich(int(sys.argv[2]) if len(sys.argv) > 2 else None) elif cmd == "mortgage-sync": from immoka.mortgage import scheduler scheduler.run(sys.argv[2:] or None) elif cmd == "mortgage-watch": from immoka.mortgage import scheduler minutes = int(sys.argv[2]) if len(sys.argv) > 2 else None scheduler.watch(minutes) elif cmd == "mortgage-status": from immoka.mortgage import store con = store.connect() for h in store.provider_health(con): print(f"{h['level']:8s} {h['provider']:15s} " f"statut={h.get('status') or '-'} " f"produits_courants={h.get('current_products', 0)} " f"dernière_passe=il y a {h.get('age_minutes', '?')} min") elif cmd == "serve": import uvicorn port = int(sys.argv[2]) if len(sys.argv) > 2 else 8098 uvicorn.run("immoka.web:app", host="0.0.0.0", port=port) else: print(__doc__) sys.exit(1) if __name__ == "__main__": main()