"""Backfill tss_tournaments.db from tournament ids already in match_summary. Usage: python TSSBOT/scripts/backfill_tournaments.py [--dry-run] [--limit N] [--sleep SECONDS] """ import argparse import asyncio import pathlib import sqlite3 import sys import time from typing import List, Optional, Tuple ROOT = pathlib.Path(__file__).resolve().parents[1] sys.path.insert(0, str(ROOT)) sys.path.insert(0, str(ROOT.parent / "SHARED")) # Match start_bot.py/tss_ws.py: standalone scripts need to load TSSBOT/.env # before importing storage, because storage resolves STORAGE_VOL_PATH at import. try: from dotenv import load_dotenv load_dotenv(dotenv_path=ROOT / ".env") except Exception: pass from BOT.storage import TSS_BATTLES_DB_PATH # noqa: E402 from BOT.tss_tournaments import ( # noqa: E402 TSS_TOURNAMENTS_DB_PATH, build_scan_sync, init_tss_tournaments_db, store_scan, ) async def tournament_ids(limit: Optional[int]) -> List[Tuple[int, Optional[str]]]: sql = """ SELECT tournament_id, NULLIF(MAX(tournament_name), '') AS tournament_name FROM match_summary WHERE tournament_id IS NOT NULL AND tournament_id > 0 GROUP BY tournament_id ORDER BY MAX(endtime_unix) DESC """ if limit: sql += " LIMIT ?" params = (limit,) else: params = () with sqlite3.connect(TSS_BATTLES_DB_PATH) as conn: rows = conn.execute(sql, params).fetchall() return [(int(row[0]), row[1]) for row in rows] def scanned_tournament_ids() -> set[int]: if not TSS_TOURNAMENTS_DB_PATH.exists(): return set() with sqlite3.connect(TSS_TOURNAMENTS_DB_PATH) as conn: try: rows = conn.execute( "SELECT tournament_id FROM tournaments WHERE scanned_unix IS NOT NULL" ).fetchall() except sqlite3.OperationalError: return set() return {int(row[0]) for row in rows} async def main() -> None: parser = argparse.ArgumentParser() parser.add_argument("--dry-run", action="store_true") parser.add_argument("--limit", type=int, default=None) parser.add_argument("--sleep", type=float, default=1.0) parser.add_argument("--battle-workers", type=int, default=8) parser.add_argument("--rescan", action="store_true", help="rescan tournaments already present in tss_tournaments.db") args = parser.parse_args() rows = await tournament_ids(args.limit) print(f"Found {len(rows)} tournament ids in {TSS_BATTLES_DB_PATH}") await init_tss_tournaments_db() if not args.rescan: done = scanned_tournament_ids() if done: before = len(rows) rows = [(tid, name) for tid, name in rows if tid not in done] print(f"Skipping {before - len(rows)} already-scanned tournaments ({len(rows)} remaining)") if args.dry_run: for tid, name in rows: print(f" {tid}: {name or 'Tournament ' + str(tid)}") return for index, (tid, name) in enumerate(rows, start=1): started = time.monotonic() print(f"[{index}/{len(rows)}] scanning tournament {tid}", flush=True) scan = build_scan_sync( tid, fallback_name=name, battle_workers=args.battle_workers, progress=lambda msg, tid=tid: print(f" {tid}: {msg}", flush=True), ) await store_scan(scan) elapsed = time.monotonic() - started print( f" stored {tid}: {scan['match_count']} matches, " f"{len(scan['battles'])} battles, {len(scan['standings'])} standings " f"({scan['status']}) in {elapsed:.1f}s", flush=True, ) if args.sleep and index < len(rows): await asyncio.sleep(args.sleep) if __name__ == "__main__": asyncio.run(main())