#!/usr/bin/env python3 # =========================================== # Alpha Sentiment Engine — Database # =========================================== # This file manages the SQLite database. # SQLite is a simple database that lives as # a single file on your hard drive. No setup # needed, no Docker, no passwords. # # It stores every single sentiment score so # we can track trends over time. # =========================================== import sqlite3 from datetime import datetime, timezone # The database file (created automatically) DB_FILE = "sentiment_data.db" def get_connection() -> sqlite3.Connection: """ Open a connection to the database. If the database file doesn't exist yet, SQLite creates it automatically. """ conn = sqlite3.connect(DB_FILE) conn.row_factory = sqlite3.Row # So we can access columns by name return conn def create_tables() -> None: """ Create the database tables if they don't already exist. This is safe to call multiple times — it won't delete existing data. """ conn = get_connection() cursor = conn.cursor() # The main table: stores every scored headline cursor.execute(""" CREATE TABLE IF NOT EXISTS sentiment_scores ( id INTEGER PRIMARY KEY AUTOINCREMENT, ticker TEXT NOT NULL, headline TEXT NOT NULL, score REAL NOT NULL, source TEXT, scraped_at TEXT NOT NULL ) """) # A summary table: stores the average per stock per scrape cycle cursor.execute(""" CREATE TABLE IF NOT EXISTS sentiment_averages ( id INTEGER PRIMARY KEY AUTOINCREMENT, ticker TEXT NOT NULL, average_score REAL NOT NULL, num_headlines INTEGER NOT NULL, scraped_at TEXT NOT NULL ) """) conn.commit() conn.close() def save_score(ticker: str, headline: str, score: float, source: str) -> None: """ Save one scored headline to the database. Called once per headline after the AI scores it. """ conn = get_connection() cursor = conn.cursor() now = datetime.now(timezone.utc).isoformat() cursor.execute( "INSERT INTO sentiment_scores (ticker, headline, score, source, scraped_at) VALUES (?, ?, ?, ?, ?)", (ticker, headline, score, source, now), ) conn.commit() conn.close() def save_average(ticker: str, average_score: float, num_headlines: int) -> None: """ Save the average sentiment for a stock after a scrape cycle. This is what the dashboard will use to draw trend charts. """ conn = get_connection() cursor = conn.cursor() now = datetime.now(timezone.utc).isoformat() cursor.execute( "INSERT INTO sentiment_averages (ticker, average_score, num_headlines, scraped_at) VALUES (?, ?, ?, ?)", (ticker, average_score, num_headlines, now), ) conn.commit() conn.close() def get_recent_scores(ticker: str = None, limit: int = 50) -> list[dict]: """ Get the most recent scored headlines from the database. If ticker is provided, filter by that stock. """ conn = get_connection() cursor = conn.cursor() if ticker: cursor.execute( "SELECT * FROM sentiment_scores WHERE ticker = ? ORDER BY scraped_at DESC LIMIT ?", (ticker, limit), ) else: cursor.execute( "SELECT * FROM sentiment_scores ORDER BY scraped_at DESC LIMIT ?", (limit,), ) rows = cursor.fetchall() conn.close() return [dict(row) for row in rows] def get_recent_averages(ticker: str = None, limit: int = 100) -> list[dict]: """ Get the most recent average scores (for trend charts). If ticker is provided, filter by that stock. """ conn = get_connection() cursor = conn.cursor() if ticker: cursor.execute( "SELECT * FROM sentiment_averages WHERE ticker = ? ORDER BY scraped_at DESC LIMIT ?", (ticker, limit), ) else: cursor.execute( "SELECT * FROM sentiment_averages ORDER BY scraped_at DESC LIMIT ?", (limit,), ) rows = cursor.fetchall() conn.close() return [dict(row) for row in rows] def get_all_tickers() -> list[str]: """ Get a list of all unique ticker symbols in the database. """ conn = get_connection() cursor = conn.cursor() cursor.execute("SELECT DISTINCT ticker FROM sentiment_averages ORDER BY ticker") rows = cursor.fetchall() conn.close() return [row["ticker"] for row in rows] def get_stats() -> dict: """ Get overall database statistics. """ conn = get_connection() cursor = conn.cursor() cursor.execute("SELECT COUNT(*) as count FROM sentiment_scores") total_scores = cursor.fetchone()["count"] cursor.execute("SELECT COUNT(*) as count FROM sentiment_averages") total_averages = cursor.fetchone()["count"] cursor.execute("SELECT COUNT(DISTINCT ticker) as count FROM sentiment_scores") unique_tickers = cursor.fetchone()["count"] conn.close() return { "total_scores": total_scores, "total_averages": total_averages, "unique_tickers": unique_tickers, } # Create the tables when this module is first imported create_tables()