"""Fetch OpenDota hero matchups into data/matchups.json (for audit_relations). Usage: python fetch_matchups.py python fetch_matchups.py --delay 1.0 """ from __future__ import annotations import sys from pathlib import Path sys.path.insert(0, str(Path(__file__).resolve().parents[1])) import argparse import json import time import urllib.error from datetime import datetime, timezone from shared.grid import hero_table from shared.http_utils import http_json from shared.paths import SHARED_DATA API = "https://api.opendota.com/api/heroes/{hero_id}/matchups" OUT = SHARED_DATA / "matchups.json" def fetch_one(hero_id: int, timeout: float = 30.0) -> list[dict]: return http_json(API.format(hero_id=hero_id), timeout=int(timeout)) def main() -> None: ap = argparse.ArgumentParser(description=__doc__) ap.add_argument("--delay", type=float, default=1.0, help="seconds between requests") ap.add_argument("--out", type=Path, default=OUT) args = ap.parse_args() table = hero_table() ids = sorted({int(h["id"]) for h in table}) by_hero: dict[str, dict] = {} if args.out.is_file(): try: prev = json.loads(args.out.read_text(encoding="utf-8")) by_hero = dict(prev.get("by_hero") or {}) print(f"resuming with {len(by_hero)} heroes already cached", flush=True) except (OSError, json.JSONDecodeError): pass pending = [i for i in ids if str(i) not in by_hero] print(f"fetching {len(pending)} / {len(ids)} heroes -> {args.out}", flush=True) for n, hid in enumerate(pending, start=1): try: rows = fetch_one(hid) except (urllib.error.URLError, TimeoutError, json.JSONDecodeError) as e: print(f" [{n}/{len(pending)}] hero {hid} failed: {e}", flush=True) time.sleep(args.delay * 2) continue cell: dict[str, dict] = {} for r in rows: opp = r.get("hero_id") games = int(r.get("games_played") or 0) wins = int(r.get("wins") or 0) if opp is None or games <= 0: continue cell[str(int(opp))] = {"games": games, "wins": wins} by_hero[str(hid)] = cell print(f" [{n}/{len(pending)}] hero {hid}: {len(cell)} matchups", flush=True) args.out.parent.mkdir(parents=True, exist_ok=True) payload = { "fetched_at": datetime.now(timezone.utc).isoformat(), "source": "opendota", "attribution": "https://opendota.com", "by_hero": by_hero, } args.out.write_text( json.dumps(payload, ensure_ascii=False, indent=2) + "\n", encoding="utf-8", ) time.sleep(args.delay) print(f"done: {len(by_hero)} heroes in {args.out}", flush=True) if __name__ == "__main__": main()