SPB Git

spb/lou-ka

Public

Lou·Ka — tous les logements à louer du Québec, un seul endroit.

7.6 KB · 185 lines python
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
Files · f9aa650b5dc0ff1772c91c86818d444d5f0f27fd
Menu

Theme