Python 68.8%
TypeScript 18.6%
CSS 8.7%
JavaScript 3.3%
HTML 0.6%
1#!/usr/bin/env python32# -----------------------------------------------------------------------------3# Rent-Ka — Rental listings aggregator (Canada, outside Québec)4# Author: Simon-Pierre Boucher — contact@spboucher.ai5# run.py : entry point — `sync`, `watch`, `serve`6# -----------------------------------------------------------------------------7"""Usage:8 python run.py sync [source ...] # sync the listings9 python run.py watch [minutes] # sync loop (default 60 min)10 python run.py serve [port] # start the API + frontend (default 8125)11 python run.py list # list the registered connectors12 python run.py record <source ...> # record a source's test fixtures13 python run.py geocode [n] # geocode listings missing coordinates14 python run.py quality [n] # recompute completeness + publication15 python run.py imgaudit [n] [source] # image quality audit16 python run.py fairvalue # recompute fair rental values17 python run.py poi [n] # nearby amenities per building18 python run.py env [n] # OSM environment tiles (KA Scores base)19 python run.py kascores [all] # compute KA Scores (all = full recompute)20 python run.py buildings # building passports (building_key + stats)21 python run.py managers [budget_s] # managers: Google Maps profile + reviews22"""23from __future__ import annotations2425import os26import sys27from pathlib import Path2829# Load .env (SCRAPFLY_KEY, APIFY_TOKEN, etc.) with no external dependency30_env = Path(__file__).parent / ".env"31if _env.exists():32 for line in _env.read_text().splitlines():33 line = line.strip()34 if line and not line.startswith("#") and "=" in line:35 k, _, v = line.partition("=")36 os.environ.setdefault(k.strip(), v.strip())373839def main() -> None:40 cmd = sys.argv[1] if len(sys.argv) > 1 else "serve"41 if cmd == "sync":42 from rentka import ingest43 ingest.run(sys.argv[2:] or None)44 elif cmd == "watch":45 from rentka import ingest46 minutes = int(sys.argv[2]) if len(sys.argv) > 2 else 6047 ingest.watch(minutes * 60)48 elif cmd == "list":49 from rentka.connectors import CONNECTORS50 for sid in sorted(CONNECTORS):51 print(sid)52 print(f"-- {len(CONNECTORS)} connectors")53 elif cmd == "geocode1":54 from rentka import geocode55 limit = int(sys.argv[2]) if len(sys.argv) > 2 else None56 geocode.run(limit)57 elif cmd == "geocode":58 from rentka import geocode59 limit = int(sys.argv[2]) if len(sys.argv) > 2 else None60 geocode.run_batch(limit)61 elif cmd == "quality":62 # recompute the completeness score + publication/quarantine63 from rentka import quality64 limit = int(sys.argv[2]) if len(sys.argv) > 2 else None65 quality.backfill(limit)66 elif cmd == "imgaudit":67 # image quality audit (dead links, tiny images, placeholders,68 # duplicates) — incremental: python run.py imgaudit [n] [source]69 from rentka import imgcheck70 limit = int(sys.argv[2]) if len(sys.argv) > 2 else 150071 src = sys.argv[3] if len(sys.argv) > 3 else None72 imgcheck.run(limit, source=src)73 elif cmd == "fairvalue":74 # recompute the fair rental value of every published listing75 from rentka import fairvalue76 fairvalue.compute_all()77 elif cmd == "poi":78 from rentka import poi79 limit = int(sys.argv[2]) if len(sys.argv) > 2 else None80 poi.run(limit)81 elif cmd == "env":82 # OSM environment data per tile (roads, rails, cycling, bars, full83 # POI) — ~3-month cache, the KA Scores substrate84 from rentka import environment85 limit = int(sys.argv[2]) if len(sys.argv) > 2 else None86 print(environment.run(limit))87 elif cmd == "kascores":88 # recompute the KA Scores (Walk/Transit/Bike/Quiet/Services + global)89 # incremental; «python run.py kascores all» forces the whole stock90 from rentka import kascores91 force = len(sys.argv) > 2 and sys.argv[2] == "all"92 print(kascores.run(recompute_all=force))93 elif cmd == "buildings":94 # recompute every building passport (building_key + stats)95 from rentka import building96 print(building.rollup())97 elif cmd == "managers":98 # managers: seeding + Google Maps resolution + reviews (SerpApi)99 # «python run.py managers [budget_s]»100 from rentka import managers101 b = float(sys.argv[2]) if len(sys.argv) > 2 else 300102 print(managers.precompute(budget_s=b))103 elif cmd == "record":104 from rentka import fixtures105 from rentka.connectors import CONNECTORS106 targets = sys.argv[2:] or sorted(CONNECTORS)107 for sid in targets:108 try:109 print(f"[rent-ka] record {sid} ... {fixtures.record(sid)}")110 except Exception as exc:111 print(f"[rent-ka] record {sid} FAILED: {exc}", file=sys.stderr)112 elif cmd == "serve":113 import uvicorn114 port = int(sys.argv[2]) if len(sys.argv) > 2 else 8125115 uvicorn.run("rentka.web:app", host="0.0.0.0", port=port)116 else:117 print(__doc__)118 sys.exit(1)119120121if __name__ == "__main__":122 main()123