#!/usr/bin/env python3 import argparse import os import sqlite3 import sys from pathlib import Path import requests def find_project_root(): direnv_file = os.environ.get("DIRENV_FILE") if direnv_file: return Path(direnv_file).parent.resolve() direnv_dir = os.environ.get("DIRENV_DIR") if direnv_dir: return Path(direnv_dir).resolve() cwd = Path.cwd().resolve() for parent in [cwd] + list(cwd.parents): if (parent / "flake.nix").exists(): return parent raise RuntimeError( "Cannot find project root. Set DIRENV_FILE or DIRENV_DIR, " "or run from within the project tree." ) def fetch_json(api_root, path): url = f"{api_root}{path}" resp = requests.get(url, timeout=30) resp.raise_for_status() return resp.json() def main(): parser = argparse.ArgumentParser( description="Populate a local SQLite DB from production data" ) parser.add_argument( "--url", default=os.environ.get("PHENIX_URL", "https://ballephenix.com"), help="Source API root (default: https://ballephenix.com)", ) parser.add_argument( "--db", default=None, help="Path to SQLite DB (default: /phenix.db)", ) parser.add_argument( "--force", action="store_true", help="Clear existing games before inserting", ) args = parser.parse_args() api_root = args.url.rstrip("/") project_root = find_project_root() db_path = Path(args.db) if args.db else project_root / "phenix.db" years = fetch_json(api_root, "/api/available-years.json") print(f"Years on source: {len(years)} ({min(years)}–{max(years)})") all_games = [] for year in years: games = fetch_json(api_root, f"/api/results/{year}.json") all_games.extend(games) print(f"Total games fetched: {len(all_games)}") conn = sqlite3.connect(str(db_path)) cursor = conn.cursor() cursor.execute(""" CREATE TABLE IF NOT EXISTS games ( id TEXT PRIMARY KEY, date TEXT NOT NULL, time TEXT NOT NULL, home TEXT NOT NULL, away TEXT NOT NULL, year INTEGER NOT NULL, home_score INTEGER, away_score INTEGER, practice INTEGER NOT NULL DEFAULT 0, rain INTEGER NOT NULL DEFAULT 0, UNIQUE(date, time) ) """) if args.force: cursor.execute("DELETE FROM games") print("Cleared existing games") inserted = 0 skipped = 0 errors = 0 for g in all_games: try: home_score = int(g["homeScore"]) if g.get("homeScore") is not None else None away_score = int(g["awayScore"]) if g.get("awayScore") is not None else None except (ValueError, TypeError): home_score = None away_score = None try: cursor.execute( """ INSERT OR IGNORE INTO games (id, date, time, home, away, year, home_score, away_score, practice, rain) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) """, ( g.get("id", ""), g["date"], g.get("time", ""), g.get("home", ""), g.get("away", ""), g.get("year", 0), home_score, away_score, 1 if g.get("practice") else 0, 1 if g.get("rain") else 0, ), ) if cursor.rowcount > 0: inserted += 1 else: skipped += 1 except sqlite3.Error as e: print(f"DB error on {g.get('date')} {g.get('time')}: {e}", file=sys.stderr) errors += 1 conn.commit() conn.close() print(f"Inserted: {inserted}, skipped: {skipped}, errors: {errors}") print(f"Database: {db_path}") if __name__ == "__main__": main()