Reorganize repository into pc web shared monorepo
Separate the local recognition, web publishing, and shared data paths while preserving direct script execution and existing site content. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
"""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()
|
||||
Reference in New Issue
Block a user