#!/usr/bin/env python3 # ----------------------------------------------------------------------------- # Rent-Ka — Rental listings aggregator (Canada, outside Québec) # Author: Simon-Pierre Boucher — contact@spboucher.ai # run.py : entry point — `sync`, `watch`, `serve` # ----------------------------------------------------------------------------- """Usage: python run.py sync [source ...] # sync the listings python run.py watch [minutes] # sync loop (default 60 min) python run.py serve [port] # start the API + frontend (default 8125) python run.py list # list the registered connectors python run.py record # record a source's test fixtures python run.py geocode [n] # geocode listings missing coordinates python run.py quality [n] # recompute completeness + publication python run.py imgaudit [n] [source] # image quality audit python run.py fairvalue # recompute fair rental values python run.py poi [n] # nearby amenities per building python run.py env [n] # OSM environment tiles (KA Scores base) python run.py kascores [all] # compute KA Scores (all = full recompute) python run.py buildings # building passports (building_key + stats) python run.py managers [budget_s] # managers: Google Maps profile + reviews """ from __future__ import annotations import os import sys from pathlib import Path # Load .env (SCRAPFLY_KEY, APIFY_TOKEN, etc.) with no external dependency _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 rentka import ingest ingest.run(sys.argv[2:] or None) elif cmd == "watch": from rentka import ingest minutes = int(sys.argv[2]) if len(sys.argv) > 2 else 60 ingest.watch(minutes * 60) elif cmd == "list": from rentka.connectors import CONNECTORS for sid in sorted(CONNECTORS): print(sid) print(f"-- {len(CONNECTORS)} connectors") elif cmd == "geocode1": from rentka import geocode limit = int(sys.argv[2]) if len(sys.argv) > 2 else None geocode.run(limit) elif cmd == "geocode": from rentka import geocode limit = int(sys.argv[2]) if len(sys.argv) > 2 else None geocode.run_batch(limit) elif cmd == "quality": # recompute the completeness score + publication/quarantine from rentka import quality limit = int(sys.argv[2]) if len(sys.argv) > 2 else None quality.backfill(limit) elif cmd == "imgaudit": # image quality audit (dead links, tiny images, placeholders, # duplicates) — incremental: python run.py imgaudit [n] [source] from rentka import imgcheck limit = int(sys.argv[2]) if len(sys.argv) > 2 else 1500 src = sys.argv[3] if len(sys.argv) > 3 else None imgcheck.run(limit, source=src) elif cmd == "fairvalue": # recompute the fair rental value of every published listing from rentka import fairvalue fairvalue.compute_all() elif cmd == "poi": from rentka import poi limit = int(sys.argv[2]) if len(sys.argv) > 2 else None poi.run(limit) elif cmd == "env": # OSM environment data per tile (roads, rails, cycling, bars, full # POI) — ~3-month cache, the KA Scores substrate from rentka import environment limit = int(sys.argv[2]) if len(sys.argv) > 2 else None print(environment.run(limit)) elif cmd == "kascores": # recompute the KA Scores (Walk/Transit/Bike/Quiet/Services + global) # incremental; «python run.py kascores all» forces the whole stock from rentka import kascores force = len(sys.argv) > 2 and sys.argv[2] == "all" print(kascores.run(recompute_all=force)) elif cmd == "buildings": # recompute every building passport (building_key + stats) from rentka import building print(building.rollup()) elif cmd == "managers": # managers: seeding + Google Maps resolution + reviews (SerpApi) # «python run.py managers [budget_s]» from rentka import managers b = float(sys.argv[2]) if len(sys.argv) > 2 else 300 print(managers.precompute(budget_s=b)) elif cmd == "record": from rentka import fixtures from rentka.connectors import CONNECTORS targets = sys.argv[2:] or sorted(CONNECTORS) for sid in targets: try: print(f"[rent-ka] record {sid} ... {fixtures.record(sid)}") except Exception as exc: print(f"[rent-ka] record {sid} FAILED: {exc}", file=sys.stderr) elif cmd == "serve": import uvicorn port = int(sys.argv[2]) if len(sys.argv) > 2 else 8125 uvicorn.run("rentka.web:app", host="0.0.0.0", port=port) else: print(__doc__) sys.exit(1) if __name__ == "__main__": main()