Vaibhav Pharmacy
Fix database: Auto-seed all 189 artists to fix HF missing database issue
9321b3c
Raw History Blame Contribute Delete
6.93 kB
"""SQLite database helpers for the music dashboard."""
import sqlite3
import os
from datetime import datetime, timezone, timedelta
from pathlib import Path
from dotenv import load_dotenv
load_dotenv()
DB_PATH = Path(__file__).parent.parent / "db" / "music_dashboard.db"
SCHEMA_PATH = Path(__file__).parent.parent / "db" / "schema.sql"
def get_connection():
"""Get a SQLite connection with WAL mode for better concurrency."""
conn = sqlite3.connect(str(DB_PATH))
conn.row_factory = sqlite3.Row
conn.execute("PRAGMA journal_mode=WAL")
conn.execute("PRAGMA foreign_keys=ON")
return conn
def init_db():
"""Initialize the database from schema.sql and run migrations."""
os.makedirs(DB_PATH.parent, exist_ok=True)
conn = get_connection()
with open(SCHEMA_PATH, "r") as f:
conn.executescript(f.read())
# Migration: add cycle_id if missing
cols = [row["name"] for row in conn.execute("PRAGMA table_info(play_snapshots)").fetchall()]
if "cycle_id" not in cols:
conn.execute("ALTER TABLE play_snapshots ADD COLUMN cycle_id TEXT")
conn.execute("CREATE INDEX IF NOT EXISTS idx_snapshots_cycle ON play_snapshots(cycle_id)")
conn.commit()
print("[migration] Added cycle_id to play_snapshots")
# Migration: add ytmusic_play_count if missing
cols = [row["name"] for row in conn.execute("PRAGMA table_info(play_snapshots)").fetchall()]
if "ytmusic_play_count" not in cols:
conn.execute("ALTER TABLE play_snapshots ADD COLUMN ytmusic_play_count INTEGER")
conn.commit()
print("[migration] Added ytmusic_play_count to play_snapshots")
# Composite indexes for the dashboard's hot queries (latest snapshot per
# song by platform, songs by artist+platform, alerts by platform)
conn.execute("CREATE INDEX IF NOT EXISTS idx_snapshots_platform_song ON play_snapshots(platform, song_id, id)")
conn.execute("CREATE INDEX IF NOT EXISTS idx_snapshots_song_platform_time ON play_snapshots(song_id, platform, collected_at)")
conn.execute("CREATE INDEX IF NOT EXISTS idx_songs_artist_platform ON songs(artist_id, platform)")
conn.execute("CREATE INDEX IF NOT EXISTS idx_alerts_platform ON viral_alerts(platform)")
conn.commit()
# Auto-seed if database is empty
cursor = conn.execute("SELECT COUNT(*) FROM artists")
count = cursor.fetchone()[0]
if count == 0:
print("[init_db] Database is empty. Seeding default artists...")
try:
from seed_artists import ARTISTS, upsert_artist
for artist in ARTISTS:
upsert_artist(conn, artist)
conn.commit()
print(f"[init_db] Successfully seeded {len(ARTISTS)} artists.")
except Exception as e:
print(f"[init_db] Failed to auto-seed: {e}")
conn.close()
print(f"Database initialized at {DB_PATH}")
def upsert_artist(conn, artist: dict):
"""Insert or update an artist."""
conn.execute(
"""INSERT INTO artists (id, name, spotify_id, youtube_channel_id, genre, image_url, is_watched)
VALUES (?, ?, ?, ?, ?, ?, ?)
ON CONFLICT(id) DO UPDATE SET
name=excluded.name,
spotify_id=excluded.spotify_id,
youtube_channel_id=excluded.youtube_channel_id,
genre=excluded.genre,
image_url=excluded.image_url""",
(
artist["id"],
artist["name"],
artist.get("spotify_id"),
artist.get("youtube_channel_id"),
artist.get("genre"),
artist.get("image_url"),
artist.get("is_watched", 0),
),
)
def upsert_song(conn, song: dict):
"""Insert or update a song."""
conn.execute(
"""INSERT INTO songs (id, artist_id, title, platform, platform_id, album_name, release_date, thumbnail_url)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
ON CONFLICT(platform, platform_id) DO UPDATE SET
title=excluded.title,
album_name=excluded.album_name,
release_date=excluded.release_date,
thumbnail_url=excluded.thumbnail_url""",
(
song["id"],
song["artist_id"],
song["title"],
song["platform"],
song["platform_id"],
song.get("album_name"),
song.get("release_date"),
song.get("thumbnail_url"),
),
)
def generate_cycle_id(platform: str = "youtube") -> str:
"""Generate a cycle_id for the current collection run.
Format: {platform}_{YYYYMMDD_HHMM_IST}
All snapshots in the same run share this ID.
"""
ist = datetime.now(timezone.utc) + timedelta(hours=5, minutes=30)
return f"{platform}_{ist.strftime('%Y%m%d_%H%M')}"
def insert_snapshot(conn, song_id: str, play_count: int, like_count: int, comment_count: int, platform: str, cycle_id: str = None, ytmusic_play_count: int = None):
"""Record a play count snapshot with optional cycle_id and ytmusic_play_count."""
conn.execute(
"""INSERT INTO play_snapshots (song_id, play_count, like_count, comment_count, platform, cycle_id, ytmusic_play_count)
VALUES (?, ?, ?, ?, ?, ?, ?)""",
(song_id, play_count, like_count, comment_count, platform, cycle_id, ytmusic_play_count),
)
def get_previous_snapshot(conn, song_id: str, platform: str):
"""Get the most recent snapshot for a song (before the current one)."""
row = conn.execute(
"""SELECT play_count, collected_at FROM play_snapshots
WHERE song_id = ? AND platform = ?
ORDER BY collected_at DESC LIMIT 1""",
(song_id, platform),
).fetchone()
return dict(row) if row else None
def insert_viral_alert(conn, alert: dict):
"""Insert a viral alert."""
conn.execute(
"""INSERT INTO viral_alerts (song_id, previous_count, current_count, growth_factor, platform)
VALUES (?, ?, ?, ?, ?)""",
(
alert["song_id"],
alert["previous_count"],
alert["current_count"],
alert["growth_factor"],
alert["platform"],
),
)
def get_all_artists(conn, platform_filter=None):
"""Get all artists, optionally filtered by platform availability."""
if platform_filter == "youtube":
return conn.execute(
"SELECT * FROM artists WHERE youtube_channel_id IS NOT NULL ORDER BY name"
).fetchall()
elif platform_filter == "spotify":
return conn.execute(
"SELECT * FROM artists WHERE spotify_id IS NOT NULL ORDER BY name"
).fetchall()
return conn.execute("SELECT * FROM artists ORDER BY name").fetchall()
def get_watched_artists(conn):
"""Get all watched artists."""
return conn.execute(
"SELECT * FROM artists WHERE is_watched = 1 ORDER BY name"
).fetchall()
if __name__ == "__main__":
init_db()