research-assistant / database.py
nellaivijay's picture
Add comprehensive improvements: AI integration, data sources, visualization, PDF analysis, and database migration
6414a15
Raw
History Blame Contribute Delete
20.2 kB
"""
Database Module - SQLite implementation for better data management
"""
import sqlite3
import json
from typing import List, Dict, Optional, Any
from datetime import datetime
from pathlib import Path
from contextlib import contextmanager
class DatabaseManager:
"""Manages SQLite database operations for the research assistant"""
def __init__(self, db_path: str = "research_assistant.db"):
self.db_path = db_path
self.init_database()
@contextmanager
def get_connection(self):
"""Context manager for database connections"""
conn = sqlite3.connect(self.db_path)
conn.row_factory = sqlite3.Row
try:
yield conn
conn.commit()
except Exception as e:
conn.rollback()
raise e
finally:
conn.close()
def init_database(self):
"""Initialize database with required tables"""
with self.get_connection() as conn:
cursor = conn.cursor()
# Users table
cursor.execute("""
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id TEXT UNIQUE NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
last_active TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
""")
# Reading lists table
cursor.execute("""
CREATE TABLE IF NOT EXISTS reading_lists (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id TEXT NOT NULL,
paper_id TEXT NOT NULL,
title TEXT NOT NULL,
year TEXT,
abstract TEXT,
url TEXT,
citation_count INTEGER DEFAULT 0,
status TEXT DEFAULT 'to-read',
added_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
paper_data TEXT,
FOREIGN KEY (user_id) REFERENCES users(user_id),
UNIQUE(user_id, paper_id)
)
""")
# Notes table
cursor.execute("""
CREATE TABLE IF NOT EXISTS notes (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id TEXT NOT NULL,
paper_id TEXT NOT NULL,
notes TEXT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(user_id),
UNIQUE(user_id, paper_id)
)
""")
# Custom prompts table
cursor.execute("""
CREATE TABLE IF NOT EXISTS custom_prompts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id TEXT,
prompt_name TEXT NOT NULL,
prompt_content TEXT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(user_id),
UNIQUE(user_id, prompt_name)
)
""")
# A/B test results table
cursor.execute("""
CREATE TABLE IF NOT EXISTS ab_test_results (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id TEXT,
test_id TEXT NOT NULL,
paper_title TEXT,
model_a TEXT,
model_b TEXT,
analysis_type TEXT,
timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
user_preference TEXT,
preference_reason TEXT,
winner TEXT,
test_data TEXT,
FOREIGN KEY (user_id) REFERENCES users(user_id)
)
""")
# Search history table
cursor.execute("""
CREATE TABLE IF NOT EXISTS search_history (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id TEXT,
search_query TEXT NOT NULL,
source TEXT,
results_count INTEGER DEFAULT 0,
timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(user_id)
)
""")
# Create indexes for better performance
cursor.execute("CREATE INDEX IF NOT EXISTS idx_reading_lists_user ON reading_lists(user_id)")
cursor.execute("CREATE INDEX IF NOT EXISTS idx_reading_lists_status ON reading_lists(status)")
cursor.execute("CREATE INDEX IF NOT EXISTS idx_notes_user ON notes(user_id)")
cursor.execute("CREATE INDEX IF NOT EXISTS idx_notes_paper ON notes(paper_id)")
cursor.execute("CREATE INDEX IF NOT EXISTS idx_ab_tests_user ON ab_test_results(user_id)")
cursor.execute("CREATE INDEX IF NOT EXISTS idx_search_history_user ON search_history(user_id)")
conn.commit()
def get_or_create_user(self, user_id: str) -> int:
"""Get existing user or create new one"""
with self.get_connection() as conn:
cursor = conn.cursor()
# Try to get existing user
cursor.execute("SELECT id FROM users WHERE user_id = ?", (user_id,))
result = cursor.fetchone()
if result:
# Update last active
cursor.execute(
"UPDATE users SET last_active = CURRENT_TIMESTAMP WHERE user_id = ?",
(user_id,)
)
return result['id']
else:
# Create new user
cursor.execute(
"INSERT INTO users (user_id) VALUES (?)",
(user_id,)
)
return cursor.lastrowid
# Reading List Operations
def add_to_reading_list(self, user_id: str, paper: Dict) -> bool:
"""Add paper to user's reading list"""
try:
with self.get_connection() as conn:
cursor = conn.cursor()
# Ensure user exists
self.get_or_create_user(user_id)
# Insert paper
cursor.execute("""
INSERT OR REPLACE INTO reading_lists
(user_id, paper_id, title, year, abstract, url, citation_count, status, paper_data)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
""", (
user_id,
paper.get('externalIds', {}).get('ArXiv') or paper.get('paperId') or paper.get('title'),
paper.get('title', ''),
paper.get('year', ''),
paper.get('abstract', ''),
paper.get('url', ''),
paper.get('citationCount', 0),
paper.get('status', 'to-read'),
json.dumps(paper)
))
return True
except Exception as e:
print(f"Error adding to reading list: {e}")
return False
def get_reading_list(self, user_id: str) -> List[Dict]:
"""Get user's reading list"""
try:
with self.get_connection() as conn:
cursor = conn.cursor()
cursor.execute("""
SELECT * FROM reading_lists
WHERE user_id = ?
ORDER BY added_date DESC
""", (user_id,))
papers = []
for row in cursor.fetchall():
paper = dict(row)
# Parse JSON data
if paper['paper_data']:
try:
paper.update(json.loads(paper['paper_data']))
except:
pass
papers.append(paper)
return papers
except Exception as e:
print(f"Error getting reading list: {e}")
return []
def update_reading_status(self, user_id: str, paper_id: str, status: str) -> bool:
"""Update status of paper in reading list"""
try:
with self.get_connection() as conn:
cursor = conn.cursor()
cursor.execute("""
UPDATE reading_lists
SET status = ?
WHERE user_id = ? AND paper_id = ?
""", (status, user_id, paper_id))
return cursor.rowcount > 0
except Exception as e:
print(f"Error updating reading status: {e}")
return False
def remove_from_reading_list(self, user_id: str, paper_id: str) -> bool:
"""Remove paper from reading list"""
try:
with self.get_connection() as conn:
cursor = conn.cursor()
cursor.execute("""
DELETE FROM reading_lists
WHERE user_id = ? AND paper_id = ?
""", (user_id, paper_id))
return cursor.rowcount > 0
except Exception as e:
print(f"Error removing from reading list: {e}")
return False
# Notes Operations
def save_note(self, user_id: str, paper_id: str, notes: str) -> bool:
"""Save notes for a paper"""
try:
with self.get_connection() as conn:
cursor = conn.cursor()
# Ensure user exists
self.get_or_create_user(user_id)
cursor.execute("""
INSERT OR REPLACE INTO notes (user_id, paper_id, notes, updated_at)
VALUES (?, ?, ?, CURRENT_TIMESTAMP)
""", (user_id, paper_id, notes))
return True
except Exception as e:
print(f"Error saving note: {e}")
return False
def get_note(self, user_id: str, paper_id: str) -> Optional[str]:
"""Get notes for a paper"""
try:
with self.get_connection() as conn:
cursor = conn.cursor()
cursor.execute("""
SELECT notes FROM notes
WHERE user_id = ? AND paper_id = ?
""", (user_id, paper_id))
result = cursor.fetchone()
return result['notes'] if result else None
except Exception as e:
print(f"Error getting note: {e}")
return None
def get_all_notes(self, user_id: str) -> List[Dict]:
"""Get all notes for a user"""
try:
with self.get_connection() as conn:
cursor = conn.cursor()
cursor.execute("""
SELECT * FROM notes
WHERE user_id = ?
ORDER BY updated_at DESC
""", (user_id,))
return [dict(row) for row in cursor.fetchall()]
except Exception as e:
print(f"Error getting all notes: {e}")
return []
# Custom Prompts Operations
def save_custom_prompt(self, user_id: str, name: str, content: str) -> bool:
"""Save custom prompt"""
try:
with self.get_connection() as conn:
cursor = conn.cursor()
cursor.execute("""
INSERT OR REPLACE INTO custom_prompts (user_id, prompt_name, prompt_content)
VALUES (?, ?, ?)
""", (user_id, name, content))
return True
except Exception as e:
print(f"Error saving custom prompt: {e}")
return False
def get_custom_prompts(self, user_id: str = None) -> List[Dict]:
"""Get custom prompts"""
try:
with self.get_connection() as conn:
cursor = conn.cursor()
if user_id:
cursor.execute("""
SELECT * FROM custom_prompts
WHERE user_id = ? OR user_id IS NULL
""", (user_id,))
else:
cursor.execute("SELECT * FROM custom_prompts WHERE user_id IS NULL")
return [dict(row) for row in cursor.fetchall()]
except Exception as e:
print(f"Error getting custom prompts: {e}")
return []
# A/B Test Operations
def save_ab_test_result(self, user_id: str, test_data: Dict) -> bool:
"""Save A/B test result"""
try:
with self.get_connection() as conn:
cursor = conn.cursor()
cursor.execute("""
INSERT INTO ab_test_results
(user_id, test_id, paper_title, model_a, model_b, analysis_type, test_data)
VALUES (?, ?, ?, ?, ?, ?, ?)
""", (
user_id,
test_data.get('test_id'),
test_data.get('paper_title'),
test_data.get('model_a'),
test_data.get('model_b'),
test_data.get('analysis_type'),
json.dumps(test_data)
))
return True
except Exception as e:
print(f"Error saving A/B test result: {e}")
return False
def update_ab_test_preference(self, test_id: str, preference: str, reason: str = "") -> bool:
"""Update A/B test preference"""
try:
with self.get_connection() as conn:
cursor = conn.cursor()
cursor.execute("""
UPDATE ab_test_results
SET user_preference = ?, preference_reason = ?, winner = ?
WHERE test_id = ?
""", (preference, reason, preference, test_id))
return cursor.rowcount > 0
except Exception as e:
print(f"Error updating A/B test preference: {e}")
return False
def get_ab_test_statistics(self, user_id: str = None) -> Dict:
"""Get A/B test statistics"""
try:
with self.get_connection() as conn:
cursor = conn.cursor()
if user_id:
cursor.execute("""
SELECT model_a, model_b, winner
FROM ab_test_results
WHERE user_id = ? AND winner IS NOT NULL
""", (user_id,))
else:
cursor.execute("""
SELECT model_a, model_b, winner
FROM ab_test_results
WHERE winner IS NOT NULL
""")
results = cursor.fetchall()
model_wins = {}
total_tests = len(results)
for row in results:
winner = row['winner']
if winner:
model_wins[winner] = model_wins.get(winner, 0) + 1
return {
'total_tests': total_tests,
'model_wins': model_wins,
'win_rates': {
model: (wins / total_tests * 100) if total_tests > 0 else 0
for model, wins in model_wins.items()
}
}
except Exception as e:
print(f"Error getting A/B test statistics: {e}")
return {'total_tests': 0, 'model_wins': {}, 'win_rates': {}}
# Search History Operations
def save_search(self, user_id: str, query: str, source: str, results_count: int) -> bool:
"""Save search to history"""
try:
with self.get_connection() as conn:
cursor = conn.cursor()
cursor.execute("""
INSERT INTO search_history (user_id, search_query, source, results_count)
VALUES (?, ?, ?, ?)
""", (user_id, query, source, results_count))
return True
except Exception as e:
print(f"Error saving search: {e}")
return False
def get_search_history(self, user_id: str, limit: int = 10) -> List[Dict]:
"""Get recent search history"""
try:
with self.get_connection() as conn:
cursor = conn.cursor()
cursor.execute("""
SELECT * FROM search_history
WHERE user_id = ?
ORDER BY timestamp DESC
LIMIT ?
""", (user_id, limit))
return [dict(row) for row in cursor.fetchall()]
except Exception as e:
print(f"Error getting search history: {e}")
return []
# Utility Operations
def get_user_statistics(self, user_id: str) -> Dict:
"""Get comprehensive user statistics"""
try:
with self.get_connection() as conn:
cursor = conn.cursor()
stats = {}
# Reading list count
cursor.execute("SELECT COUNT(*) as count FROM reading_lists WHERE user_id = ?", (user_id,))
stats['reading_list_count'] = cursor.fetchone()['count']
# Notes count
cursor.execute("SELECT COUNT(*) as count FROM notes WHERE user_id = ?", (user_id,))
stats['notes_count'] = cursor.fetchone()['count']
# A/B tests count
cursor.execute("SELECT COUNT(*) as count FROM ab_test_results WHERE user_id = ?", (user_id,))
stats['ab_tests_count'] = cursor.fetchone()['count']
# Search count
cursor.execute("SELECT COUNT(*) as count FROM search_history WHERE user_id = ?", (user_id,))
stats['search_count'] = cursor.fetchone()['count']
# Reading list by status
cursor.execute("""
SELECT status, COUNT(*) as count
FROM reading_lists
WHERE user_id = ?
GROUP BY status
""", (user_id,))
stats['reading_list_by_status'] = {row['status']: row['count'] for row in cursor.fetchall()}
return stats
except Exception as e:
print(f"Error getting user statistics: {e}")
return {}
def export_user_data(self, user_id: str) -> Dict:
"""Export all user data"""
try:
return {
'reading_list': self.get_reading_list(user_id),
'notes': self.get_all_notes(user_id),
'custom_prompts': self.get_custom_prompts(user_id),
'search_history': self.get_search_history(user_id, limit=100),
'statistics': self.get_user_statistics(user_id),
'export_date': datetime.now().isoformat()
}
except Exception as e:
print(f"Error exporting user data: {e}")
return {}
# Global database manager instance
db_manager = DatabaseManager()