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# Lou-Ka — run.py : point d'entrée — sync/watch/serve (+ outils)3"""python run.py {sync|watch|sync-ct|watch-ct|serve|record|geocode|quality|imgaudit|fairvalue|poi|quartier|env|kascores|buildings|managers|tal|rdl|...}"""4from __future__ import annotations56import os7import sys8from pathlib import Path910_env = Path(__file__).parent / ".env"11if _env.exists():12 for line in _env.read_text().splitlines():13 line = line.strip()14 if line and not line.startswith("#") and "=" in line:15 k, _, v = line.partition("=")16 os.environ.setdefault(k.strip(), v.strip())171819def _disk_guard() -> None:20 # imgcache (miniatures /api/img, régénérables) : aucune éviction → peut21 # remplir le disque (ENOSPC → 500). Purge si critique, sinon TTL 30 j.22 import shutil23 import threading24 import time2526 root = Path(__file__).parent27 cache = root / "data" / "imgcache"2829 def _run() -> None:30 try:31 free = shutil.disk_usage(root).free / 1e932 print(f"[lou-ka] disk_guard: {free:.2f} Go libres", flush=True)33 if not cache.is_dir():34 return35 crit = free < 536 cutoff = time.time() - 30 * 8640037 n = 038 with os.scandir(cache) as it:39 for e in it:40 try:41 if crit or e.stat().st_mtime < cutoff:42 os.unlink(e.path)43 n += 144 except OSError:45 pass46 if n:47 free = shutil.disk_usage(root).free / 1e948 print(f"[lou-ka] disk_guard: {n} fichiers purgés "49 f"({'critique' if crit else 'TTL'}), "50 f"{free:.2f} Go libres", flush=True)51 except Exception as exc:52 print(f"[lou-ka] disk_guard: erreur ignorée : {exc}", flush=True)5354 threading.Thread(target=_run, name="disk-guard", daemon=True).start()555657def main() -> None:58 cmd = sys.argv[1] if len(sys.argv) > 1 else "serve"59 if cmd in ("serve", "watch", "watch-ct", "sync", "sync-ct"):60 _disk_guard()61 if cmd == "sync":62 from louka import ingest63 ingest.run(sys.argv[2:] or None)64 elif cmd == "watch":65 from louka import ingest66 minutes = int(sys.argv[2]) if len(sys.argv) > 2 else 6067 ingest.watch(minutes * 60)68 elif cmd == "sync-ct":69 from louka.shortterm import ingest as ct_ingest70 ct_ingest.run(sys.argv[2:] or None)71 elif cmd == "watch-ct":72 from louka.shortterm import ingest as ct_ingest73 minutes = int(sys.argv[2]) if len(sys.argv) > 2 else 36074 ct_ingest.watch(minutes * 60)75 elif cmd == "geocode1":76 from louka import geocode77 limit = int(sys.argv[2]) if len(sys.argv) > 2 else None78 geocode.run(limit)79 elif cmd == "geocode":80 from louka import geocode81 limit = int(sys.argv[2]) if len(sys.argv) > 2 else None82 geocode.run_batch(limit)83 elif cmd == "quality":84 from louka import quality85 limit = int(sys.argv[2]) if len(sys.argv) > 2 else None86 quality.backfill(limit)87 elif cmd == "imgaudit":88 from louka import imgcheck89 limit = int(sys.argv[2]) if len(sys.argv) > 2 else 150090 src = sys.argv[3] if len(sys.argv) > 3 else None91 imgcheck.run(limit, source=src)92 elif cmd == "fairvalue":93 from louka import fairvalue94 fairvalue.compute_all()95 elif cmd == "poi":96 from louka import poi97 limit = int(sys.argv[2]) if len(sys.argv) > 2 else None98 poi.run(limit)99 elif cmd == "quartier":100 from louka import quartier101 limit = int(sys.argv[2]) if len(sys.argv) > 2 else None102 quartier.enrich(limit)103 elif cmd == "env":104 from louka import environment105 limit = int(sys.argv[2]) if len(sys.argv) > 2 else None106 print(environment.run(limit))107 elif cmd == "kascores":108 from louka import kascores109 force = len(sys.argv) > 2 and sys.argv[2] == "all"110 print(kascores.run(recompute_all=force))111 elif cmd == "inondation-build":112 from louka import inondation113 inondation.build()114 elif cmd == "air-refresh":115 from louka import air116 air.refresh()117 elif cmd == "gaz-refresh":118 from louka import gaz119 print(f"[gaz] {gaz.refresh()} stations")120 elif cmd == "hydro-precompute":121 from louka import hydro122 n = int(sys.argv[2]) if len(sys.argv) > 2 else 100123 b = int(sys.argv[3]) if len(sys.argv) > 3 else 40124 hydro.precompute(limit=n, budget=b)125 elif cmd == "rdl":126 from louka import rdl127 rdl.refresh()128 elif cmd == "tal":129 from louka import tal130 n = int(sys.argv[2]) if len(sys.argv) > 2 else 60131 b = int(sys.argv[3]) if len(sys.argv) > 3 else 600132 tal.precompute(limit=n, budget=b)133 elif cmd == "buildings":134 from louka import building135 print(building.rollup())136 elif cmd == "managers":137 from louka import managers138 b = float(sys.argv[2]) if len(sys.argv) > 2 else 300139 print(managers.precompute(budget_s=b))140 elif cmd == "record":141 from louka import fixtures142 from louka.connectors import CONNECTORS143 targets = sys.argv[2:] or sorted(CONNECTORS)144 for sid in targets:145 try:146 print(f"[lou-ka] record {sid} ... {fixtures.record(sid)}")147 except Exception as exc:148 print(f"[lou-ka] record {sid} ÉCHEC : {exc}", file=sys.stderr)149 elif cmd == "serve":150 import uvicorn151 port = int(sys.argv[2]) if len(sys.argv) > 2 else 8080152 uvicorn.run("louka.web:app", host="0.0.0.0", port=port)153 else:154 print(__doc__)155 sys.exit(1)156157158if __name__ == "__main__":159 main()160# ---------------------------------------------------------------------------161# [ka2 2026-08-31] padding intentionnel : sur disque APFS saturé (ENOSPC),162# les réécritures ne tronquaient pas le fichier — une queue corrompue issue163# d'une écriture précédente (pannes du 2026-08-31) subsistait après l'ancien164# EOF et cassait le parse (SyntaxError). Ces lignes de commentaire allongent165# le fichier au-delà de l'ancienne taille pour garantir l'écrasement complet.166# Elles sont inertes et peuvent être supprimées lors d'un prochain commit167# une fois le disque assaini.168# ---------------------------------------------------------------------------169###############################################################################170###############################################################################171###############################################################################172###############################################################################173###############################################################################174###############################################################################175###############################################################################176###############################################################################177###############################################################################178###############################################################################179###############################################################################180###############################################################################181###############################################################################182###############################################################################183###############################################################################184# fin du padding ka2185