Lou·Ka — tous les logements à louer du Québec, un seul endroit.
- 264commits
- 1branches
- 0releases
- 180.7 MBon disk
- maindefault branch
- 5 days agolast push
1#!/usr/bin/env python32# -----------------------------------------------------------------------------3# Lou-Ka — Agrégateur de logements à louer (province de Québec)4# Auteur : Simon-Pierre Boucher — contact@spboucher.ai5# run.py : point d'entrée — `sync`, `watch`, `serve`6# -----------------------------------------------------------------------------7"""Utilisation :8 python run.py sync [source ...] # synchronise les annonces9 python run.py watch [minutes] # synchronise en boucle (défaut 60 min)10 python run.py sync-ct [source ...] # synchronise la section court terme11 python run.py watch-ct [minutes] # boucle court terme (défaut 360 min)12 python run.py serve [port] # démarre l'API + le frontend (défaut 8080)13 python run.py record <source ...> # enregistre les fixtures de test d'une source14 python run.py geocode [n] # géocode les annonces sans coordonnées (max n requêtes)15 python run.py poi [n] # commodités de proximité par immeuble (max n requêtes)16 python run.py env [n] # environnement OSM par tuile (socle des KA Scores)17 python run.py kascores [all] # calcule les KA Scores du parc (all = tout recalculer)18 python run.py buildings # passeport des immeubles (building_key + stats)19 python run.py managers [budget_s] # gestionnaires : fiche Google Maps + avis (SerpApi)20"""21from __future__ import annotations2223import os24import sys25from pathlib import Path2627# Charger .env (FIRECRAWL_API_KEY, etc.) sans dépendance externe28_env = Path(__file__).parent / ".env"29if _env.exists():30 for line in _env.read_text().splitlines():31 line = line.strip()32 if line and not line.startswith("#") and "=" in line:33 k, _, v = line.partition("=")34 os.environ.setdefault(k.strip(), v.strip())353637def main() -> None:38 cmd = sys.argv[1] if len(sys.argv) > 1 else "serve"39 if cmd == "sync":40 from louka import ingest41 ingest.run(sys.argv[2:] or None)42 elif cmd == "watch":43 from louka import ingest44 minutes = int(sys.argv[2]) if len(sys.argv) > 2 else 6045 ingest.watch(minutes * 60)46 elif cmd == "sync-ct":47 from louka.shortterm import ingest as ct_ingest48 ct_ingest.run(sys.argv[2:] or None)49 elif cmd == "watch-ct":50 from louka.shortterm import ingest as ct_ingest51 minutes = int(sys.argv[2]) if len(sys.argv) > 2 else 36052 ct_ingest.watch(minutes * 60)53 elif cmd == "geocode1":54 from louka import geocode55 limit = int(sys.argv[2]) if len(sys.argv) > 2 else None56 geocode.run(limit)57 elif cmd == "geocode":58 # lot Adresses Québec (rapide) ; « geocode1 » = ancien mode 1-par-159 from louka import geocode60 limit = int(sys.argv[2]) if len(sys.argv) > 2 else None61 geocode.run_batch(limit)62 elif cmd == "quality":63 # (re)calcule le score de complétude + publication/quarantaine64 from louka import quality65 limit = int(sys.argv[2]) if len(sys.argv) > 2 else None66 quality.backfill(limit)67 elif cmd == "imgaudit":68 # contrôle qualité des images (liens morts, minuscules, placeholders,69 # doublons) — incrémental : python run.py imgaudit [n_annonces] [source]70 from louka import imgcheck71 limit = int(sys.argv[2]) if len(sys.argv) > 2 else 150072 src = sys.argv[3] if len(sys.argv) > 3 else None73 imgcheck.run(limit, source=src)74 elif cmd == "fairvalue":75 # (re)calcule la juste valeur locative de toutes les annonces publiées76 from louka import fairvalue77 fairvalue.compute_all()78 elif cmd == "poi":79 from louka import poi80 limit = int(sys.argv[2]) if len(sys.argv) > 2 else None81 poi.run(limit)82 elif cmd == "quartier":83 from louka import quartier84 limit = int(sys.argv[2]) if len(sys.argv) > 2 else None85 quartier.enrich(limit)86 elif cmd == "env":87 # données d'environnement OSM par tuile (routes, rails, cyclable,88 # bars, POI complets) — cache ~3 mois, socle des KA Scores89 from louka import environment90 limit = int(sys.argv[2]) if len(sys.argv) > 2 else None91 print(environment.run(limit))92 elif cmd == "kascores":93 # (re)calcule les KA Scores (Walk/Transit/Bike/Calme/Services + global)94 # incrémental ; « python run.py kascores all » force tout le parc95 from louka import kascores96 force = len(sys.argv) > 2 and sys.argv[2] == "all"97 print(kascores.run(recompute_all=force))98 elif cmd == "inondation-build":99 from louka import inondation100 inondation.build()101 elif cmd == "air-refresh":102 from louka import air103 air.refresh()104 elif cmd == "gaz-refresh":105 from louka import gaz106 print(f"[gaz] {gaz.refresh()} stations")107 elif cmd == "hydro-precompute":108 from louka import hydro109 n = int(sys.argv[2]) if len(sys.argv) > 2 else 100110 b = int(sys.argv[3]) if len(sys.argv) > 3 else 40111 hydro.precompute(limit=n, budget=b)112 elif cmd == "rdl":113 from louka import rdl114 rdl.refresh()115 elif cmd == "tal":116 # historique TAL (décisions SOQUIJ) par adresse, par lots117 from louka import tal118 n = int(sys.argv[2]) if len(sys.argv) > 2 else 60119 b = int(sys.argv[3]) if len(sys.argv) > 3 else 600120 tal.precompute(limit=n, budget=b)121 elif cmd == "buildings":122 # (re)calcule le passeport de tous les immeubles (building_key + stats)123 from louka import building124 print(building.rollup())125 elif cmd == "managers":126 # gestionnaires : amorçage + résolution Google Maps + avis (SerpApi)127 # « python run.py managers [budget_s] »128 from louka import managers129 b = float(sys.argv[2]) if len(sys.argv) > 2 else 300130 print(managers.precompute(budget_s=b))131 elif cmd == "record":132 from louka import fixtures133 from louka.connectors import CONNECTORS134 targets = sys.argv[2:] or sorted(CONNECTORS)135 for sid in targets:136 try:137 print(f"[lou-ka] record {sid} ... {fixtures.record(sid)}")138 except Exception as exc:139 print(f"[lou-ka] record {sid} ÉCHEC : {exc}", file=sys.stderr)140 elif cmd == "serve":141 import uvicorn142 port = int(sys.argv[2]) if len(sys.argv) > 2 else 8080143 uvicorn.run("louka.web:app", host="0.0.0.0", port=port)144 else:145 print(__doc__)146 sys.exit(1)147148149if __name__ == "__main__":150 main()151