| """ |
| Memory System β conversations + progress tracking + spaced repetition. |
| |
| Schema: |
| conversations (id, timestamp, user_input, response) |
| progress (id, timestamp, subject, topic, correct, time_sec, question) |
| sr_cards (id, topic, easiness, interval, repetitions, next_review, last_review) |
| """ |
|
|
| import sqlite3 |
| import os |
| from datetime import datetime, date, timedelta |
| from typing import List, Tuple, Optional |
|
|
| DB_PATH = os.path.join(os.path.dirname(__file__), "anveshai_memory.db") |
| HISTORY_LIMIT = 10 |
|
|
|
|
| def _get_connection() -> sqlite3.Connection: |
| conn = sqlite3.connect(DB_PATH) |
| conn.row_factory = sqlite3.Row |
| return conn |
|
|
|
|
| def initialize_db() -> None: |
| with _get_connection() as conn: |
| conn.execute(""" |
| CREATE TABLE IF NOT EXISTS conversations ( |
| id INTEGER PRIMARY KEY AUTOINCREMENT, |
| timestamp TEXT NOT NULL, |
| user_input TEXT NOT NULL, |
| response TEXT NOT NULL |
| ) |
| """) |
| conn.execute(""" |
| CREATE TABLE IF NOT EXISTS progress ( |
| id INTEGER PRIMARY KEY AUTOINCREMENT, |
| timestamp TEXT NOT NULL, |
| subject TEXT NOT NULL, |
| topic TEXT NOT NULL, |
| correct INTEGER NOT NULL, |
| time_sec REAL NOT NULL DEFAULT 0, |
| question TEXT NOT NULL DEFAULT '' |
| ) |
| """) |
| conn.execute(""" |
| CREATE TABLE IF NOT EXISTS sr_cards ( |
| id INTEGER PRIMARY KEY AUTOINCREMENT, |
| topic TEXT NOT NULL UNIQUE, |
| easiness REAL NOT NULL DEFAULT 2.5, |
| interval INTEGER NOT NULL DEFAULT 1, |
| repetitions INTEGER NOT NULL DEFAULT 0, |
| next_review TEXT NOT NULL, |
| last_review TEXT NOT NULL |
| ) |
| """) |
| conn.commit() |
|
|
|
|
| |
| |
| |
|
|
| def save_interaction(user_input: str, response: str) -> None: |
| timestamp = datetime.now().isoformat(sep=" ", timespec="seconds") |
| with _get_connection() as conn: |
| conn.execute( |
| "INSERT INTO conversations (timestamp, user_input, response) VALUES (?, ?, ?)", |
| (timestamp, user_input, response), |
| ) |
| conn.commit() |
|
|
|
|
| def get_recent_history(limit: int = HISTORY_LIMIT) -> List[sqlite3.Row]: |
| with _get_connection() as conn: |
| cursor = conn.execute( |
| "SELECT id, timestamp, user_input, response FROM conversations " |
| "ORDER BY id DESC LIMIT ?", (limit,) |
| ) |
| rows = cursor.fetchall() |
| return list(reversed(rows)) |
|
|
|
|
| def format_history() -> str: |
| rows = get_recent_history() |
| if not rows: |
| return "No conversation history yet." |
| lines = [f" Last {len(rows)} interaction(s):\n"] |
| for row in rows: |
| lines.append(f" [{row['timestamp']}]") |
| lines.append(f" You : {row['user_input']}") |
| lines.append(f" AnveshAI : {row['response']}") |
| lines.append("") |
| return "\n".join(lines).rstrip() |
|
|
|
|
| def clear_history() -> None: |
| with _get_connection() as conn: |
| conn.execute("DELETE FROM conversations") |
| conn.commit() |
|
|
|
|
| |
| |
| |
|
|
| def save_progress(subject: str, topic: str, correct: bool, |
| time_sec: float = 0.0, question: str = "") -> None: |
| timestamp = datetime.now().isoformat(sep=" ", timespec="seconds") |
| with _get_connection() as conn: |
| conn.execute( |
| "INSERT INTO progress (timestamp, subject, topic, correct, time_sec, question) " |
| "VALUES (?, ?, ?, ?, ?, ?)", |
| (timestamp, subject, topic, int(correct), time_sec, question), |
| ) |
| conn.commit() |
|
|
|
|
| def get_progress_summary() -> dict: |
| """Return dict: subject β {attempted, correct, accuracy, topics: {topic: {attempted, correct}}}""" |
| with _get_connection() as conn: |
| rows = conn.execute( |
| "SELECT subject, topic, correct, time_sec FROM progress" |
| ).fetchall() |
| summary: dict = {} |
| for row in rows: |
| subj = row['subject'] |
| topic = row['topic'] |
| if subj not in summary: |
| summary[subj] = {'attempted': 0, 'correct': 0, 'time_sec': 0.0, 'topics': {}} |
| summary[subj]['attempted'] += 1 |
| summary[subj]['correct'] += row['correct'] |
| summary[subj]['time_sec'] += row['time_sec'] |
| if topic not in summary[subj]['topics']: |
| summary[subj]['topics'][topic] = {'attempted': 0, 'correct': 0} |
| summary[subj]['topics'][topic]['attempted'] += 1 |
| summary[subj]['topics'][topic]['correct'] += row['correct'] |
| for subj in summary: |
| a = summary[subj]['attempted'] |
| c = summary[subj]['correct'] |
| summary[subj]['accuracy'] = c / a * 100 if a > 0 else 0.0 |
| return summary |
|
|
|
|
| def get_weak_topics(threshold: float = 60.0, min_attempts: int = 1) -> List[Tuple[str, str, float]]: |
| """Return [(subject, topic, accuracy%)] for topics below threshold.""" |
| summary = get_progress_summary() |
| weak = [] |
| for subj, data in summary.items(): |
| for topic, tdata in data['topics'].items(): |
| if tdata['attempted'] >= min_attempts: |
| acc = tdata['correct'] / tdata['attempted'] * 100 |
| if acc < threshold: |
| weak.append((subj, topic, acc)) |
| weak.sort(key=lambda x: x[2]) |
| return weak |
|
|
|
|
| |
| |
| |
|
|
| def _today() -> str: |
| return date.today().isoformat() |
|
|
|
|
| def update_sr_card(topic: str, quality: int) -> None: |
| """ |
| Update SM-2 card for a topic. |
| quality: 0-5 (0-1 = blackout, 2 = incorrect but close, 3 = correct+hard, 4 = correct, 5 = easy) |
| """ |
| today = _today() |
| with _get_connection() as conn: |
| row = conn.execute( |
| "SELECT * FROM sr_cards WHERE topic = ?", (topic,) |
| ).fetchone() |
|
|
| if row is None: |
| |
| easiness = 2.5 |
| repetitions = 0 |
| interval = 1 |
| else: |
| easiness = row['easiness'] |
| repetitions = row['repetitions'] |
| interval = row['interval'] |
|
|
| |
| if quality < 3: |
| repetitions = 0 |
| interval = 1 |
| else: |
| if repetitions == 0: |
| interval = 1 |
| elif repetitions == 1: |
| interval = 6 |
| else: |
| interval = round(interval * easiness) |
| repetitions += 1 |
| easiness = max(1.3, easiness + 0.1 - (5 - quality) * (0.08 + (5 - quality) * 0.02)) |
| next_review = (date.today() + timedelta(days=interval)).isoformat() |
|
|
| if row is None: |
| conn.execute( |
| "INSERT INTO sr_cards (topic, easiness, interval, repetitions, next_review, last_review) " |
| "VALUES (?, ?, ?, ?, ?, ?)", |
| (topic, easiness, interval, repetitions, next_review, today), |
| ) |
| else: |
| conn.execute( |
| "UPDATE sr_cards SET easiness=?, interval=?, repetitions=?, " |
| "next_review=?, last_review=? WHERE topic=?", |
| (easiness, interval, repetitions, next_review, today, topic), |
| ) |
| conn.commit() |
|
|
|
|
| def get_due_topics() -> List[dict]: |
| """Return list of topics due for review today.""" |
| today = _today() |
| with _get_connection() as conn: |
| rows = conn.execute( |
| "SELECT topic, easiness, interval, repetitions, next_review, last_review " |
| "FROM sr_cards WHERE next_review <= ? ORDER BY next_review", |
| (today,) |
| ).fetchall() |
| return [dict(r) for r in rows] |
|
|
|
|
| def get_all_sr_cards() -> List[dict]: |
| with _get_connection() as conn: |
| rows = conn.execute( |
| "SELECT topic, easiness, interval, repetitions, next_review, last_review " |
| "FROM sr_cards ORDER BY next_review" |
| ).fetchall() |
| return [dict(r) for r in rows] |
|
|