""" db/database.py -------------- SQLite initialization, schema creation, and default game seeding. All table definitions live here. Call init_db() once on app startup. """ import sqlite3 import logging import os logger = logging.getLogger(__name__) # DB file lives in lottosight/data/lottosight.db BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) DB_PATH = os.path.join(BASE_DIR, "data", "lottosight.db") def get_connection(): """Return a sqlite3 connection with foreign keys enabled.""" conn = sqlite3.connect(DB_PATH) conn.execute("PRAGMA foreign_keys = ON") conn.row_factory = sqlite3.Row return conn def init_db(): """ Initialize the database — create all tables if they don't exist, then seed default game configs. Safe to call on every app launch (uses IF NOT EXISTS). """ logger.info("[DB] Initializing database at %s", DB_PATH) # Ensure data/ directory exists os.makedirs(os.path.dirname(DB_PATH), exist_ok=True) conn = get_connection() try: cursor = conn.cursor() # ── games ────────────────────────────────────────────────────────── cursor.execute(""" CREATE TABLE IF NOT EXISTS games ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL UNIQUE, main_count INTEGER NOT NULL, main_max INTEGER NOT NULL, bonus_count INTEGER NOT NULL DEFAULT 0, bonus_max INTEGER NOT NULL DEFAULT 0, active INTEGER NOT NULL DEFAULT 1 ) """) # ── draws ────────────────────────────────────────────────────────── cursor.execute(""" CREATE TABLE IF NOT EXISTS draws ( id INTEGER PRIMARY KEY AUTOINCREMENT, game_id INTEGER NOT NULL REFERENCES games(id), draw_date TEXT NOT NULL, numbers TEXT NOT NULL, bonus TEXT, multiplier TEXT, source TEXT, created_at TEXT NOT NULL DEFAULT (datetime('now')) ) """) # Unique constraint: one record per game per draw date cursor.execute(""" CREATE UNIQUE INDEX IF NOT EXISTS idx_draws_game_date ON draws (game_id, draw_date) """) # Index for fast date-range queries cursor.execute(""" CREATE INDEX IF NOT EXISTS idx_draws_date ON draws (draw_date) """) # ── predictions ──────────────────────────────────────────────────── cursor.execute(""" CREATE TABLE IF NOT EXISTS predictions ( id INTEGER PRIMARY KEY AUTOINCREMENT, game_id INTEGER NOT NULL REFERENCES games(id), strategy TEXT NOT NULL, numbers TEXT NOT NULL, bonus TEXT, created_at TEXT NOT NULL DEFAULT (datetime('now')) ) """) # ── fetch_log ────────────────────────────────────────────────────── cursor.execute(""" CREATE TABLE IF NOT EXISTS fetch_log ( id INTEGER PRIMARY KEY AUTOINCREMENT, source TEXT NOT NULL, fetched_at TEXT NOT NULL DEFAULT (datetime('now')), added INTEGER NOT NULL DEFAULT 0, skipped INTEGER NOT NULL DEFAULT 0, status TEXT NOT NULL DEFAULT 'success', message TEXT ) """) conn.commit() logger.info("[DB] Tables created/verified OK") _seed_games(cursor, conn) except Exception as e: conn.rollback() logger.error("[ERROR] DB init failed: %s", e, exc_info=True) raise finally: conn.close() def _seed_games(cursor, conn): """ Insert default game configs if they don't already exist. Uses INSERT OR IGNORE to be idempotent on every launch. """ defaults = [ # name main_count main_max bonus_count bonus_max active ("Powerball", 5, 69, 1, 26, 1), ("Mega Millions", 5, 70, 1, 25, 1), ] for row in defaults: cursor.execute(""" INSERT OR IGNORE INTO games (name, main_count, main_max, bonus_count, bonus_max, active) VALUES (?, ?, ?, ?, ?, ?) """, row) inserted = conn.total_changes conn.commit() if inserted > 0: logger.info("[DB] Seeded %d default game(s)", inserted) else: logger.info("[DB] Default games already seeded — skipped") def get_db_stats(): """ Return a dict of basic DB stats for display in Settings screen. {game_name: draw_count, ...} """ conn = get_connection() try: cursor = conn.cursor() cursor.execute(""" SELECT g.name, COUNT(d.id) as draw_count FROM games g LEFT JOIN draws d ON d.game_id = g.id GROUP BY g.id ORDER BY g.name """) rows = cursor.fetchall() return {row["name"]: row["draw_count"] for row in rows} finally: conn.close()