""" sync-elo.py — download the stat-check.com ELO Excel sheet and emit public/elo-data.json. Column auto-detection: scans the header row for keywords. If headers change, check the CI log — it always prints what it found. """ import json import sys from datetime import datetime, timezone from io import BytesIO try: import requests import openpyxl except ImportError: print("Installing dependencies…", flush=True) import subprocess subprocess.check_call([sys.executable, "-m", "pip", "install", "requests", "openpyxl", "-q"]) import requests import openpyxl DOWNLOAD_URL = ( "https://excel.officeapps.live.com/x/_layouts/XlFileHandler.aspx" "?WacUserType=WOPI" "&usid=4bcd36aa-91bc-4c95-a692-c2ef5de1d13a" "&NoAuth=1" "&waccluster=PCA1" ) OUTPUT_PATH = "public/elo-data.json" def find_col(headers, *keywords): """Return index of first header containing any keyword (case-insensitive).""" for i, h in enumerate(headers): hl = str(h).lower() if any(kw in hl for kw in keywords): return i return None def main(): print(f"Downloading ELO sheet…", flush=True) resp = requests.get(DOWNLOAD_URL, timeout=120, headers={"User-Agent": "Mozilla/5.0"}) resp.raise_for_status() print(f"Downloaded {len(resp.content):,} bytes", flush=True) wb = openpyxl.load_workbook(BytesIO(resp.content), read_only=True, data_only=True) ws = wb.active rows = ws.iter_rows(values_only=True) raw_headers = next(rows) headers = [str(c).strip() if c is not None else "" for c in raw_headers] print(f"Headers ({len(headers)}): {headers}", flush=True) rank_col = find_col(headers, "rank") name_col = find_col(headers, "name", "player") rating_col = find_col(headers, "rating", "elo", "score", "points") if name_col is None: print("ERROR: Could not find a name/player column. Check the headers above.", file=sys.stderr) sys.exit(1) print( f"Mapped rank→col {rank_col} ('{headers[rank_col] if rank_col is not None else '—'}') " f"name→col {name_col} ('{headers[name_col]}') " f"rating→col {rating_col} ('{headers[rating_col] if rating_col is not None else '—'}')", flush=True, ) by_name = {} skipped = 0 for seq, row in enumerate(rows, start=2): raw_name = row[name_col] if len(row) > name_col else None if not raw_name: skipped += 1 continue name = str(raw_name).strip() key = name.lower() raw_rank = row[rank_col] if rank_col is not None and len(row) > rank_col else None raw_rating = row[rating_col] if rating_col is not None and len(row) > rating_col else None try: rank = int(raw_rank) if raw_rank is not None else seq except (ValueError, TypeError): rank = seq try: rating = round(float(raw_rating), 1) if raw_rating is not None else None except (ValueError, TypeError): rating = None by_name[key] = {"rank": rank, "rating": rating, "name": name} wb.close() print(f"Parsed {len(by_name):,} players ({skipped} blank rows skipped)", flush=True) output = { "updated": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"), "count": len(by_name), "byName": by_name, } with open(OUTPUT_PATH, "w", encoding="utf-8") as f: json.dump(output, f, separators=(",", ":"), ensure_ascii=False) print(f"Wrote {OUTPUT_PATH}", flush=True) if __name__ == "__main__": main()