Download database.py from Youssef1F/AI-Study-Companion: direct link, hf CLI and curl.
- Browser
- Download file 11.5 kB
-
https://huggingface.co/spaces/Youssef1F/AI-Study-Companion/resolve/main/database.py
- Command line
-
hf download hf://spaces/Youssef1F/AI-Study-Companion/database.py
-
curl -L -o database.py https://huggingface.co/spaces/Youssef1F/AI-Study-Companion/resolve/main/database.py
11.5 kB
| # ============================================ | |
| # database.py | |
| # SQLite persistence for study sessions | |
| # ============================================ | |
| import os | |
| import json | |
| import sqlite3 | |
| import logging | |
| from datetime import datetime | |
| logger = logging.getLogger(__name__) | |
| DB_PATH = os.path.join("data", "study_companion.db") | |
| def get_connection(): | |
| """Get a database connection, creating tables if needed.""" | |
| os.makedirs(os.path.dirname(DB_PATH), exist_ok=True) | |
| conn = sqlite3.connect(DB_PATH) | |
| conn.row_factory = sqlite3.Row | |
| conn.execute("PRAGMA journal_mode=WAL") | |
| _create_tables(conn) | |
| return conn | |
| def _create_tables(conn): | |
| """Create all tables if they don't exist.""" | |
| conn.executescript(""" | |
| CREATE TABLE IF NOT EXISTS sessions ( | |
| id TEXT PRIMARY KEY, | |
| document_name TEXT, | |
| document_text TEXT, | |
| summary TEXT, | |
| keywords TEXT, | |
| created_at TEXT DEFAULT (datetime('now')), | |
| updated_at TEXT DEFAULT (datetime('now')) | |
| ); | |
| CREATE TABLE IF NOT EXISTS quiz_results ( | |
| id INTEGER PRIMARY KEY AUTOINCREMENT, | |
| session_id TEXT, | |
| score INTEGER, | |
| total INTEGER, | |
| percentage REAL, | |
| grade TEXT, | |
| difficulty TEXT, | |
| details TEXT, | |
| created_at TEXT DEFAULT (datetime('now')), | |
| FOREIGN KEY (session_id) REFERENCES sessions(id) | |
| ); | |
| CREATE TABLE IF NOT EXISTS qa_history ( | |
| id INTEGER PRIMARY KEY AUTOINCREMENT, | |
| session_id TEXT, | |
| question TEXT, | |
| answer TEXT, | |
| confidence REAL, | |
| created_at TEXT DEFAULT (datetime('now')), | |
| FOREIGN KEY (session_id) REFERENCES sessions(id) | |
| ); | |
| CREATE TABLE IF NOT EXISTS flashcards ( | |
| id INTEGER PRIMARY KEY AUTOINCREMENT, | |
| session_id TEXT, | |
| term TEXT, | |
| definition TEXT, | |
| category TEXT DEFAULT 'general', | |
| mastery_level INTEGER DEFAULT 0, | |
| next_review TEXT, | |
| review_count INTEGER DEFAULT 0, | |
| created_at TEXT DEFAULT (datetime('now')), | |
| FOREIGN KEY (session_id) REFERENCES sessions(id) | |
| ); | |
| CREATE TABLE IF NOT EXISTS study_progress ( | |
| id INTEGER PRIMARY KEY AUTOINCREMENT, | |
| session_id TEXT, | |
| action_type TEXT, | |
| details TEXT, | |
| created_at TEXT DEFAULT (datetime('now')), | |
| FOREIGN KEY (session_id) REFERENCES sessions(id) | |
| ); | |
| """) | |
| conn.commit() | |
| # ---- Session CRUD ---- | |
| def save_session(session_id, document_name, document_text, summary=None, keywords=None): | |
| """Save or update a study session.""" | |
| conn = get_connection() | |
| try: | |
| conn.execute(""" | |
| INSERT INTO sessions (id, document_name, document_text, summary, keywords, updated_at) | |
| VALUES (?, ?, ?, ?, ?, datetime('now')) | |
| ON CONFLICT(id) DO UPDATE SET | |
| document_name=excluded.document_name, | |
| document_text=excluded.document_text, | |
| summary=COALESCE(excluded.summary, sessions.summary), | |
| keywords=COALESCE(excluded.keywords, sessions.keywords), | |
| updated_at=datetime('now') | |
| """, (session_id, document_name, document_text, | |
| summary, json.dumps(keywords) if keywords else None)) | |
| conn.commit() | |
| finally: | |
| conn.close() | |
| def load_session(session_id): | |
| """Load a session from database.""" | |
| conn = get_connection() | |
| try: | |
| row = conn.execute("SELECT * FROM sessions WHERE id = ?", (session_id,)).fetchone() | |
| if row: | |
| return dict(row) | |
| return None | |
| finally: | |
| conn.close() | |
| def get_all_sessions(): | |
| """Get all sessions (for dashboard).""" | |
| conn = get_connection() | |
| try: | |
| rows = conn.execute( | |
| "SELECT id, document_name, created_at, updated_at FROM sessions ORDER BY updated_at DESC" | |
| ).fetchall() | |
| return [dict(r) for r in rows] | |
| finally: | |
| conn.close() | |
| def delete_session(session_id): | |
| """Delete a session and all related data.""" | |
| conn = get_connection() | |
| try: | |
| conn.execute("DELETE FROM flashcards WHERE session_id = ?", (session_id,)) | |
| conn.execute("DELETE FROM quiz_results WHERE session_id = ?", (session_id,)) | |
| conn.execute("DELETE FROM qa_history WHERE session_id = ?", (session_id,)) | |
| conn.execute("DELETE FROM study_progress WHERE session_id = ?", (session_id,)) | |
| conn.execute("DELETE FROM sessions WHERE id = ?", (session_id,)) | |
| conn.commit() | |
| finally: | |
| conn.close() | |
| # ---- Quiz Results ---- | |
| def save_quiz_result(session_id, score, total, percentage, grade, difficulty, details): | |
| """Save a quiz result.""" | |
| conn = get_connection() | |
| try: | |
| conn.execute(""" | |
| INSERT INTO quiz_results (session_id, score, total, percentage, grade, difficulty, details) | |
| VALUES (?, ?, ?, ?, ?, ?, ?) | |
| """, (session_id, score, total, percentage, grade, difficulty, json.dumps(details))) | |
| conn.commit() | |
| _log_progress(conn, session_id, "quiz", f"Score: {score}/{total} ({grade})") | |
| finally: | |
| conn.close() | |
| def get_quiz_history(session_id=None): | |
| """Get quiz history, optionally filtered by session.""" | |
| conn = get_connection() | |
| try: | |
| if session_id: | |
| rows = conn.execute( | |
| "SELECT * FROM quiz_results WHERE session_id = ? ORDER BY created_at DESC", (session_id,) | |
| ).fetchall() | |
| else: | |
| rows = conn.execute( | |
| "SELECT * FROM quiz_results ORDER BY created_at DESC LIMIT 50" | |
| ).fetchall() | |
| return [dict(r) for r in rows] | |
| finally: | |
| conn.close() | |
| # ---- Q&A History ---- | |
| def save_qa_entry(session_id, question, answer, confidence): | |
| """Save a Q&A interaction.""" | |
| conn = get_connection() | |
| try: | |
| conn.execute(""" | |
| INSERT INTO qa_history (session_id, question, answer, confidence) | |
| VALUES (?, ?, ?, ?) | |
| """, (session_id, question, answer, confidence)) | |
| conn.commit() | |
| finally: | |
| conn.close() | |
| # ---- Flashcards ---- | |
| def save_flashcards(session_id, flashcards): | |
| """Save a list of flashcards.""" | |
| conn = get_connection() | |
| try: | |
| now = datetime.now().isoformat() | |
| for fc in flashcards: | |
| conn.execute(""" | |
| INSERT INTO flashcards (session_id, term, definition, category, next_review) | |
| VALUES (?, ?, ?, ?, ?) | |
| """, (session_id, fc.get("term", ""), fc.get("definition", ""), | |
| fc.get("category", "general"), now)) | |
| conn.commit() | |
| _log_progress(conn, session_id, "flashcards", f"Generated {len(flashcards)} flashcards") | |
| finally: | |
| conn.close() | |
| def get_flashcards(session_id): | |
| """Get all flashcards for a session.""" | |
| conn = get_connection() | |
| try: | |
| rows = conn.execute( | |
| "SELECT * FROM flashcards WHERE session_id = ? ORDER BY mastery_level ASC, id ASC", | |
| (session_id,) | |
| ).fetchall() | |
| return [dict(r) for r in rows] | |
| finally: | |
| conn.close() | |
| def get_due_flashcards(session_id): | |
| """Get flashcards due for review (spaced repetition).""" | |
| conn = get_connection() | |
| try: | |
| now = datetime.now().isoformat() | |
| rows = conn.execute(""" | |
| SELECT * FROM flashcards | |
| WHERE session_id = ? AND (next_review <= ? OR next_review IS NULL) | |
| ORDER BY mastery_level ASC, review_count ASC | |
| """, (session_id, now)).fetchall() | |
| return [dict(r) for r in rows] | |
| finally: | |
| conn.close() | |
| def update_flashcard_mastery(flashcard_id, correct): | |
| """Update flashcard after review (spaced repetition algorithm).""" | |
| conn = get_connection() | |
| try: | |
| row = conn.execute("SELECT * FROM flashcards WHERE id = ?", (flashcard_id,)).fetchone() | |
| if not row: | |
| return | |
| mastery = row["mastery_level"] | |
| review_count = row["review_count"] + 1 | |
| if correct: | |
| mastery = min(mastery + 1, 5) | |
| else: | |
| mastery = max(mastery - 1, 0) | |
| # Spaced repetition intervals (in hours) | |
| intervals = {0: 0.5, 1: 4, 2: 24, 3: 72, 4: 168, 5: 720} | |
| hours = intervals.get(mastery, 24) | |
| from datetime import timedelta | |
| next_review = (datetime.now() + timedelta(hours=hours)).isoformat() | |
| conn.execute(""" | |
| UPDATE flashcards SET mastery_level = ?, review_count = ?, next_review = ? | |
| WHERE id = ? | |
| """, (mastery, review_count, next_review, flashcard_id)) | |
| conn.commit() | |
| finally: | |
| conn.close() | |
| # ---- Study Progress ---- | |
| def _log_progress(conn, session_id, action_type, details): | |
| """Internal: log a study action.""" | |
| conn.execute(""" | |
| INSERT INTO study_progress (session_id, action_type, details) | |
| VALUES (?, ?, ?) | |
| """, (session_id, action_type, details)) | |
| conn.commit() | |
| def get_study_stats(session_id=None): | |
| """Get overall study statistics for the dashboard.""" | |
| conn = get_connection() | |
| try: | |
| stats = {} | |
| # Total sessions | |
| stats["total_sessions"] = conn.execute("SELECT COUNT(*) FROM sessions").fetchone()[0] | |
| # Quiz stats | |
| if session_id: | |
| quiz_rows = conn.execute( | |
| "SELECT * FROM quiz_results WHERE session_id = ? ORDER BY created_at DESC", | |
| (session_id,) | |
| ).fetchall() | |
| else: | |
| quiz_rows = conn.execute( | |
| "SELECT * FROM quiz_results ORDER BY created_at DESC" | |
| ).fetchall() | |
| if quiz_rows: | |
| pcts = [r["percentage"] for r in quiz_rows] | |
| stats["total_quizzes"] = len(quiz_rows) | |
| stats["average_score"] = round(sum(pcts) / len(pcts), 1) | |
| stats["best_score"] = max(pcts) | |
| stats["worst_score"] = min(pcts) | |
| stats["total_questions"] = sum(r["total"] for r in quiz_rows) | |
| stats["total_correct"] = sum(r["score"] for r in quiz_rows) | |
| # Score history for chart | |
| stats["score_history"] = [ | |
| {"date": r["created_at"], "score": r["percentage"], | |
| "grade": r["grade"], "difficulty": r["difficulty"]} | |
| for r in quiz_rows[:20] | |
| ] | |
| else: | |
| stats["total_quizzes"] = 0 | |
| stats["average_score"] = 0 | |
| stats["score_history"] = [] | |
| # Flashcard stats | |
| if session_id: | |
| fc_rows = conn.execute( | |
| "SELECT mastery_level, COUNT(*) as cnt FROM flashcards WHERE session_id = ? GROUP BY mastery_level", | |
| (session_id,) | |
| ).fetchall() | |
| else: | |
| fc_rows = conn.execute( | |
| "SELECT mastery_level, COUNT(*) as cnt FROM flashcards GROUP BY mastery_level" | |
| ).fetchall() | |
| stats["flashcard_mastery"] = {str(r["mastery_level"]): r["cnt"] for r in fc_rows} | |
| stats["total_flashcards"] = sum(r["cnt"] for r in fc_rows) | |
| # QA stats | |
| qa_count = conn.execute( | |
| "SELECT COUNT(*) FROM qa_history" + (" WHERE session_id = ?" if session_id else ""), | |
| (session_id,) if session_id else () | |
| ).fetchone()[0] | |
| stats["total_questions_asked"] = qa_count | |
| return stats | |
| finally: | |
| conn.close() | |