Files
gateway-gamers.net/scripts/sync-elo.py
T

112 lines
3.7 KiB
Python
Raw Normal View History

"""
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
import requests
import openpyxl
DOWNLOAD_URL = (
2026-06-26 21:06:27 -05:00
"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()
content_type = resp.headers.get("Content-Type", "")
print(f"Downloaded {len(resp.content):,} bytes Content-Type: {content_type}", flush=True)
# Diagnose non-Excel responses before attempting to parse
if "html" in content_type.lower() or not resp.content.startswith(b"PK"):
print("ERROR: Response is not an Excel/ZIP file. First 500 chars:", flush=True)
print(resp.content[:500].decode("utf-8", errors="replace"), flush=True)
sys.exit(1)
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()