@gonzih/exam-prep-mcp 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Maksim Soltan
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,287 @@
1
+ # exam-prep-mcp
2
+
3
+ > AI-powered exam preparation MCP server with FSRS-5 spaced repetition
4
+
5
+ [![npm version](https://badge.fury.io/js/@gonzih%2Fexam-prep-mcp.svg)](https://www.npmjs.com/package/@gonzih/exam-prep-mcp)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
+
8
+ ## Overview
9
+
10
+ `exam-prep-mcp` is a Model Context Protocol (MCP) server that brings adaptive, science-backed exam preparation into your AI assistant workflow. It uses the **FSRS-5 spaced repetition algorithm** to schedule reviews at optimal intervals — you study smarter, not harder.
11
+
12
+ ### Features
13
+
14
+ - **FSRS-5 Spaced Repetition** — Cards are scheduled based on your performance and forgetting curves
15
+ - **50+ Real Practice Questions** — Accurate questions with detailed explanations across AP and SAT subjects
16
+ - **Study Plan Generator** — Personalized day-by-day schedule based on your exam date and level
17
+ - **Mock Exams** — Full-length timed practice exams with grade estimates
18
+ - **Readiness Score** — Track your preparation progress with a 0-100 readiness score
19
+ - **Weak Area Detection** — Automatically identifies topics that need more focus
20
+ - **Multiple Exam Types** — AP Biology, AP Chemistry, AP US History, SAT Math, MCAT, LSAT, GRE, and more
21
+
22
+ ## Installation
23
+
24
+ ### Global install (recommended)
25
+
26
+ ```bash
27
+ npm install -g @gonzih/exam-prep-mcp
28
+ ```
29
+
30
+ ### Run without installing
31
+
32
+ ```bash
33
+ npx @gonzih/exam-prep-mcp
34
+ ```
35
+
36
+ ## MCP Configuration
37
+
38
+ ### Claude Desktop
39
+
40
+ Add to your `claude_desktop_config.json`:
41
+
42
+ **macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
43
+ **Windows**: `%APPDATA%\Claude\claude_desktop_config.json`
44
+
45
+ ```json
46
+ {
47
+ "mcpServers": {
48
+ "exam-prep": {
49
+ "command": "exam-prep-mcp"
50
+ }
51
+ }
52
+ }
53
+ ```
54
+
55
+ Or with npx:
56
+
57
+ ```json
58
+ {
59
+ "mcpServers": {
60
+ "exam-prep": {
61
+ "command": "npx",
62
+ "args": ["-y", "@gonzih/exam-prep-mcp"]
63
+ }
64
+ }
65
+ }
66
+ ```
67
+
68
+ ### Custom database path
69
+
70
+ ```json
71
+ {
72
+ "mcpServers": {
73
+ "exam-prep": {
74
+ "command": "exam-prep-mcp",
75
+ "env": {
76
+ "EXAM_PREP_DB": "/path/to/your/study.sqlite"
77
+ }
78
+ }
79
+ }
80
+ }
81
+ ```
82
+
83
+ ## Environment Variables
84
+
85
+ | Variable | Default | Description |
86
+ |----------|---------|-------------|
87
+ | `EXAM_PREP_DB` | `~/.exam-prep/study.sqlite` | Path to SQLite database file |
88
+
89
+ ## Tools
90
+
91
+ ### 1. `set_exam`
92
+
93
+ Configure an exam for a student profile. Seeds question cards and generates a personalized study plan.
94
+
95
+ **Parameters:**
96
+ - `profileId` (string, required) — Unique student identifier (e.g., `"alice"`, `"student-001"`)
97
+ - `examType` (string, required) — Exam to prepare for (see Supported Exams below)
98
+ - `examDate` (string, required) — Exam date in `YYYY-MM-DD` format
99
+ - `currentLevel` (string, required) — `"beginner"`, `"intermediate"`, or `"advanced"`
100
+
101
+ **Returns:** Study plan, topics identified, days remaining, seeded card count
102
+
103
+ ---
104
+
105
+ ### 2. `get_daily_session`
106
+
107
+ Get today's personalized practice questions based on FSRS spaced repetition scheduling.
108
+
109
+ **Parameters:**
110
+ - `profileId` (string, required) — Student profile ID
111
+
112
+ **Returns:** 10-15 questions with card IDs, topics, question text, choices (for MC), estimated time, focus areas
113
+
114
+ ---
115
+
116
+ ### 3. `answer_question`
117
+
118
+ Submit an answer to a practice question. Evaluates correctness, applies FSRS scheduling, and detects weak areas.
119
+
120
+ **Parameters:**
121
+ - `profileId` (string, required) — Student profile ID
122
+ - `cardId` (string, required) — Card ID from `get_daily_session` or `start_mock_exam`
123
+ - `answer` (string, required) — Student's answer (`"A"`, `"B"`, `"C"`, `"D"` for multiple choice; text for others)
124
+ - `rating` (1-4, optional) — Manual difficulty rating: 1=Again, 2=Hard, 3=Good, 4=Easy
125
+
126
+ **Returns:** Correct/incorrect, correct answer, explanation, next review date, weak area alert if applicable
127
+
128
+ ---
129
+
130
+ ### 4. `start_mock_exam`
131
+
132
+ Begin a timed full-length practice exam simulating real exam conditions.
133
+
134
+ **Parameters:**
135
+ - `profileId` (string, required) — Student profile ID
136
+
137
+ **Returns:** Session ID, all exam questions, time limit in minutes, instructions
138
+
139
+ ---
140
+
141
+ ### 5. `submit_mock_exam`
142
+
143
+ Submit all answers for a completed mock exam and receive detailed scoring and analysis.
144
+
145
+ **Parameters:**
146
+ - `sessionId` (string, required) — Session ID from `start_mock_exam`
147
+ - `answers` (array, required) — Array of `{ cardId, answer }` objects
148
+
149
+ **Returns:** Score, grade estimate, topic breakdown, weak/strong topics, readiness score update, recommendation
150
+
151
+ ---
152
+
153
+ ### 6. `get_readiness`
154
+
155
+ Get a comprehensive readiness report with your current score, grade estimate, performance trend, and recommendation.
156
+
157
+ **Parameters:**
158
+ - `profileId` (string, required) — Student profile ID
159
+
160
+ **Returns:** 0-100 readiness score, strong/weak topics, coverage percent, trend (improving/declining/stable), grade estimate, recommendation
161
+
162
+ ---
163
+
164
+ ### 7. `get_weak_areas`
165
+
166
+ Identify topics with accuracy below 60% that need focused study.
167
+
168
+ **Parameters:**
169
+ - `profileId` (string, required) — Student profile ID
170
+
171
+ **Returns:** Ranked list of weak topics with accuracy and attempt counts, suggested focus plan
172
+
173
+ ## Supported Exam Types
174
+
175
+ | Exam Type | Description | Questions | Time Limit |
176
+ |-----------|-------------|-----------|------------|
177
+ | `AP_Biology` | AP Biology | 60 | 170 min |
178
+ | `AP_Chemistry` | AP Chemistry | 60 | 190 min |
179
+ | `AP_US_History` | AP US History | 55 | 205 min |
180
+ | `AP_World_History` | AP World History | 55 | 205 min |
181
+ | `AP_Physics` | AP Physics | 50 | 180 min |
182
+ | `AP_Calculus` | AP Calculus AB/BC | 45 | 195 min |
183
+ | `AP_Statistics` | AP Statistics | 40 | 180 min |
184
+ | `AP_English` | AP English Language | 45 | 215 min |
185
+ | `AP_Economics` | AP Micro/Macro Economics | 60 | 130 min |
186
+ | `SAT_Math` | SAT Math section | 44 | 70 min |
187
+ | `SAT_Reading` | SAT Reading section | 52 | 65 min |
188
+ | `SAT_Writing` | SAT Writing section | 44 | 35 min |
189
+ | `MCAT` | Medical College Admissions | 230 | 375 min |
190
+ | `LSAT` | Law School Admissions | 100 | 175 min |
191
+ | `GRE` | Graduate Record Examination | 80 | 225 min |
192
+
193
+ ## Example Conversation
194
+
195
+ **Scenario:** Student has AP Chemistry exam in 3 weeks
196
+
197
+ ---
198
+
199
+ **Student:** I have my AP Chemistry exam on May 15th. I'm currently at an intermediate level. Can you set me up?
200
+
201
+ **Claude:** I'll set up your AP Chemistry exam prep right away!
202
+
203
+ *[calls `set_exam` with profileId="student", examType="AP_Chemistry", examDate="2025-05-15", currentLevel="intermediate"]*
204
+
205
+ Great! I've configured your AP Chemistry prep:
206
+ - **21 days** until your exam
207
+ - **10 topics** identified: Atomic Structure, Chemical Bonding, Stoichiometry, Gas Laws, Thermodynamics, Chemical Equilibrium, Acids and Bases, Electrochemistry, Kinetics, Nuclear Chemistry
208
+ - **48 practice cards** seeded into your study queue
209
+ - Weekly goal: Master 3-4 topics per week
210
+
211
+ ---
212
+
213
+ **Student:** What should I study today?
214
+
215
+ **Claude:** *[calls `get_daily_session`]*
216
+
217
+ Today's session (12 questions, ~30 min):
218
+
219
+ **Focus areas:** Stoichiometry, Gas Laws, Acids and Bases
220
+
221
+ 1. **[Stoichiometry]** How many grams of H₂O are produced when 4.0g of H₂ reacts completely with excess O₂?
222
+ 2. **[Gas Laws]** A gas occupies 2.0 L at 300K and 1.0 atm. What volume at 600K and 2.0 atm?
223
+ ...
224
+
225
+ ---
226
+
227
+ **Student:** For question 1, I think it's 36 grams.
228
+
229
+ **Claude:** *[calls `answer_question` with answer="36 g"]*
230
+
231
+ **Correct!** Well done.
232
+
233
+ 36 g is right. 2H₂ + O₂ → 2H₂O. Moles H₂ = 4.0g / 2.0g/mol = 2.0 mol. From stoichiometry, 2.0 mol H₂O × 18 g/mol = **36 g**.
234
+
235
+ Next review: in 3 days
236
+
237
+ ---
238
+
239
+ **Student:** Let's do a mock exam!
240
+
241
+ **Claude:** *[calls `start_mock_exam`]*
242
+
243
+ Starting your AP Chemistry mock exam:
244
+ - **60 questions**
245
+ - **190 minutes** time limit
246
+ - Answer all questions, then use `submit_mock_exam` when done.
247
+
248
+ ---
249
+
250
+ **Student:** How ready am I overall?
251
+
252
+ **Claude:** *[calls `get_readiness`]*
253
+
254
+ **Readiness Score: 67/100**
255
+
256
+ - Estimated AP Score: **4/5** (Well Qualified)
257
+ - Topics mastered: Chemical Bonding, Atomic Structure
258
+ - Weak areas: Electrochemistry (40%), Thermodynamics (50%)
259
+ - Trend: **Improving**
260
+ - 21 days remaining — excellent time to close the gaps!
261
+
262
+ **Recommendation:** Focus on Electrochemistry and Thermodynamics this week. You're on track for a 4!
263
+
264
+ ## How FSRS Works
265
+
266
+ FSRS-5 (Free Spaced Repetition Scheduler) is a state-of-the-art algorithm that models human memory:
267
+
268
+ - **Stability (S):** How long you'll remember something (in days)
269
+ - **Difficulty (D):** How hard the material is for you (1-10 scale)
270
+ - **Retrievability (R):** Probability you can recall it right now (0-1)
271
+
272
+ After each answer, the algorithm calculates your next optimal review date. Cards you find easy get longer intervals; cards you struggle with come back sooner. This minimizes total study time while maximizing retention.
273
+
274
+ ## Development
275
+
276
+ ```bash
277
+ git clone https://github.com/gonzih/exam-prep-mcp
278
+ cd exam-prep-mcp
279
+ npm install
280
+ npm run dev # Run with tsx (no build needed)
281
+ npm run build # Compile TypeScript
282
+ npm start # Run compiled version
283
+ ```
284
+
285
+ ## License
286
+
287
+ MIT © Gonzih
package/SKILL.md ADDED
@@ -0,0 +1,79 @@
1
+ # exam-prep-mcp
2
+
3
+ Trigger: when user mentions studying for AP exams, SAT, MCAT, LSAT, GRE, finals, or any exam preparation.
4
+
5
+ ## What This Does
6
+
7
+ `exam-prep-mcp` is an MCP server that provides AI-powered exam preparation with FSRS-5 spaced repetition. It maintains a SQLite database of practice questions, tracks student performance, schedules reviews at optimal intervals, runs mock exams, and generates readiness reports.
8
+
9
+ Key capabilities:
10
+ - Personalized spaced repetition scheduling (FSRS-5 algorithm)
11
+ - 50+ real practice questions across AP Biology, AP Chemistry, AP US History, SAT Math
12
+ - Adaptive study plans based on exam date and student level
13
+ - Full-length timed mock exams
14
+ - Topic-level performance tracking and weak area detection
15
+ - Readiness scores with grade estimates (AP 1-5, SAT scores, etc.)
16
+
17
+ ## Available MCP Tools
18
+
19
+ ### 1. `set_exam`
20
+ Configure exam prep for a student. Seeds practice cards and generates a daily study plan.
21
+ - Required: `profileId`, `examType`, `examDate` (YYYY-MM-DD), `currentLevel` (beginner/intermediate/advanced)
22
+
23
+ ### 2. `get_daily_session`
24
+ Fetch today's practice questions, selected by FSRS scheduling (due cards + new cards to hit 10-15 questions).
25
+ - Required: `profileId`
26
+
27
+ ### 3. `answer_question`
28
+ Submit a student answer, evaluate correctness, apply FSRS scheduling, and check for weak area alerts.
29
+ - Required: `profileId`, `cardId`, `answer`
30
+ - Optional: `rating` (1=Again, 2=Hard, 3=Good, 4=Easy)
31
+
32
+ ### 4. `start_mock_exam`
33
+ Begin a full-length timed mock exam. Returns all questions and a session ID.
34
+ - Required: `profileId`
35
+
36
+ ### 5. `submit_mock_exam`
37
+ Grade a completed mock exam. Returns score, grade estimate, topic breakdown, readiness update.
38
+ - Required: `sessionId`, `answers` (array of `{cardId, answer}`)
39
+
40
+ ### 6. `get_readiness`
41
+ Get overall readiness score (0-100), strong/weak topics, performance trend, and grade estimate.
42
+ - Required: `profileId`
43
+
44
+ ### 7. `get_weak_areas`
45
+ List topics with accuracy below 60%, ranked by most urgent, with a suggested focus plan.
46
+ - Required: `profileId`
47
+
48
+ ## Supported Exam Types
49
+
50
+ - `AP_Biology`, `AP_Chemistry`, `AP_US_History`, `AP_World_History`
51
+ - `AP_Physics`, `AP_Calculus`, `AP_Statistics`, `AP_English`, `AP_Economics`
52
+ - `SAT_Math`, `SAT_Reading`, `SAT_Writing`
53
+ - `MCAT`, `LSAT`, `GRE`
54
+
55
+ ## Quick Start
56
+
57
+ When a user asks to prepare for an exam:
58
+
59
+ 1. **Set up** with `set_exam`:
60
+ ```
61
+ profileId: "student" (or their name)
62
+ examType: "AP_Chemistry"
63
+ examDate: "2025-05-15"
64
+ currentLevel: "intermediate"
65
+ ```
66
+
67
+ 2. **Daily practice** with `get_daily_session` → present questions → `answer_question` for each response
68
+
69
+ 3. **Check progress** with `get_readiness` and `get_weak_areas`
70
+
71
+ 4. **Mock exams** with `start_mock_exam` → collect all answers → `submit_mock_exam`
72
+
73
+ ## Usage Pattern
74
+
75
+ - Use `profileId` consistently to track one student's progress across sessions
76
+ - Always call `set_exam` first before any other tool
77
+ - For multiple choice questions, accept "A", "B", "C", or "D" as the answer
78
+ - For free response, keyword matching is used — encourage complete sentences
79
+ - The FSRS algorithm handles scheduling automatically; just answer questions consistently
package/dist/db.d.ts ADDED
@@ -0,0 +1,66 @@
1
+ import Database from 'better-sqlite3';
2
+ export interface Card {
3
+ id: string;
4
+ profile_id: string;
5
+ subject: string;
6
+ topic: string;
7
+ question: string;
8
+ answer: string;
9
+ difficulty: number;
10
+ stability: number;
11
+ retrievability: number;
12
+ next_review: string | null;
13
+ review_count: number;
14
+ last_rating: number | null;
15
+ created_at: string;
16
+ }
17
+ export interface Session {
18
+ id: string;
19
+ profile_id: string;
20
+ exam_type: string;
21
+ started_at: string;
22
+ ended_at: string | null;
23
+ questions_answered: number;
24
+ correct: number;
25
+ readiness_score: number | null;
26
+ }
27
+ export interface ExamConfig {
28
+ profile_id: string;
29
+ exam_type: string;
30
+ exam_date: string;
31
+ current_level: string;
32
+ created_at: string;
33
+ }
34
+ export interface TopicStats {
35
+ profile_id: string;
36
+ topic: string;
37
+ subject: string;
38
+ total_attempts: number;
39
+ correct_attempts: number;
40
+ last_attempt: string | null;
41
+ }
42
+ export declare function getDb(): Database.Database;
43
+ export declare function initDb(): Database.Database;
44
+ export declare function insertCard(card: Card): void;
45
+ export declare function getCard(id: string): Card | undefined;
46
+ export declare function updateCard(card: Partial<Card> & {
47
+ id: string;
48
+ }): void;
49
+ export declare function getDueCards(profileId: string, now: string, limit: number): Card[];
50
+ export declare function getNewCards(profileId: string, limit: number): Card[];
51
+ export declare function getCardsByProfile(profileId: string): Card[];
52
+ export declare function countCardsByQuestion(profileId: string, question: string): number;
53
+ export declare function getCardsByTopic(profileId: string, topic: string, limit: number): Card[];
54
+ export declare function getAllCardsForExam(profileId: string, subject: string, limit: number): Card[];
55
+ export declare function insertSession(session: Session): void;
56
+ export declare function updateSession(session: Partial<Session> & {
57
+ id: string;
58
+ }): void;
59
+ export declare function getSession(id: string): Session | undefined;
60
+ export declare function getRecentSessions(profileId: string, limit: number): Session[];
61
+ export declare function upsertExamConfig(config: ExamConfig): void;
62
+ export declare function getExamConfig(profileId: string): ExamConfig | undefined;
63
+ export declare function upsertTopicStats(stats: TopicStats): void;
64
+ export declare function getTopicStats(profileId: string): TopicStats[];
65
+ export declare function getTopicStat(profileId: string, topic: string): TopicStats | undefined;
66
+ export declare function getWeakTopics(profileId: string, threshold: number): TopicStats[];
package/dist/db.js ADDED
@@ -0,0 +1,188 @@
1
+ import Database from 'better-sqlite3';
2
+ import { homedir } from 'os';
3
+ import { join } from 'path';
4
+ import { mkdirSync } from 'fs';
5
+ let db = null;
6
+ export function getDb() {
7
+ if (!db) {
8
+ db = initDb();
9
+ }
10
+ return db;
11
+ }
12
+ export function initDb() {
13
+ const dbPath = process.env.EXAM_PREP_DB || join(homedir(), '.exam-prep', 'study.sqlite');
14
+ const dir = dbPath.substring(0, dbPath.lastIndexOf('/'));
15
+ mkdirSync(dir, { recursive: true });
16
+ const database = new Database(dbPath);
17
+ database.pragma('journal_mode = WAL');
18
+ database.exec(`
19
+ CREATE TABLE IF NOT EXISTS cards (
20
+ id TEXT PRIMARY KEY,
21
+ profile_id TEXT NOT NULL,
22
+ subject TEXT NOT NULL,
23
+ topic TEXT NOT NULL,
24
+ question TEXT NOT NULL,
25
+ answer TEXT NOT NULL,
26
+ difficulty REAL DEFAULT 0.3,
27
+ stability REAL DEFAULT 1.0,
28
+ retrievability REAL DEFAULT 1.0,
29
+ next_review TEXT,
30
+ review_count INTEGER DEFAULT 0,
31
+ last_rating INTEGER,
32
+ created_at TEXT NOT NULL
33
+ );
34
+
35
+ CREATE TABLE IF NOT EXISTS sessions (
36
+ id TEXT PRIMARY KEY,
37
+ profile_id TEXT NOT NULL,
38
+ exam_type TEXT NOT NULL,
39
+ started_at TEXT NOT NULL,
40
+ ended_at TEXT,
41
+ questions_answered INTEGER DEFAULT 0,
42
+ correct INTEGER DEFAULT 0,
43
+ readiness_score REAL
44
+ );
45
+
46
+ CREATE TABLE IF NOT EXISTS exam_config (
47
+ profile_id TEXT PRIMARY KEY,
48
+ exam_type TEXT NOT NULL,
49
+ exam_date TEXT NOT NULL,
50
+ current_level TEXT NOT NULL,
51
+ created_at TEXT NOT NULL
52
+ );
53
+
54
+ CREATE TABLE IF NOT EXISTS topic_stats (
55
+ profile_id TEXT NOT NULL,
56
+ topic TEXT NOT NULL,
57
+ subject TEXT NOT NULL,
58
+ total_attempts INTEGER DEFAULT 0,
59
+ correct_attempts INTEGER DEFAULT 0,
60
+ last_attempt TEXT,
61
+ PRIMARY KEY (profile_id, topic)
62
+ );
63
+ `);
64
+ db = database;
65
+ return database;
66
+ }
67
+ // Card CRUD
68
+ export function insertCard(card) {
69
+ const database = getDb();
70
+ database.prepare(`
71
+ INSERT OR IGNORE INTO cards
72
+ (id, profile_id, subject, topic, question, answer, difficulty, stability, retrievability, next_review, review_count, last_rating, created_at)
73
+ VALUES
74
+ (@id, @profile_id, @subject, @topic, @question, @answer, @difficulty, @stability, @retrievability, @next_review, @review_count, @last_rating, @created_at)
75
+ `).run(card);
76
+ }
77
+ export function getCard(id) {
78
+ return getDb().prepare('SELECT * FROM cards WHERE id = ?').get(id);
79
+ }
80
+ export function updateCard(card) {
81
+ const database = getDb();
82
+ database.prepare(`
83
+ UPDATE cards SET
84
+ difficulty = @difficulty,
85
+ stability = @stability,
86
+ retrievability = @retrievability,
87
+ next_review = @next_review,
88
+ review_count = @review_count,
89
+ last_rating = @last_rating
90
+ WHERE id = @id
91
+ `).run(card);
92
+ }
93
+ export function getDueCards(profileId, now, limit) {
94
+ return getDb().prepare(`
95
+ SELECT * FROM cards
96
+ WHERE profile_id = ? AND (next_review IS NULL OR next_review <= ?)
97
+ ORDER BY next_review ASC NULLS FIRST
98
+ LIMIT ?
99
+ `).all(profileId, now, limit);
100
+ }
101
+ export function getNewCards(profileId, limit) {
102
+ return getDb().prepare(`
103
+ SELECT * FROM cards
104
+ WHERE profile_id = ? AND review_count = 0
105
+ ORDER BY created_at ASC
106
+ LIMIT ?
107
+ `).all(profileId, limit);
108
+ }
109
+ export function getCardsByProfile(profileId) {
110
+ return getDb().prepare('SELECT * FROM cards WHERE profile_id = ?').all(profileId);
111
+ }
112
+ export function countCardsByQuestion(profileId, question) {
113
+ const row = getDb().prepare('SELECT COUNT(*) as cnt FROM cards WHERE profile_id = ? AND question = ?').get(profileId, question);
114
+ return row.cnt;
115
+ }
116
+ export function getCardsByTopic(profileId, topic, limit) {
117
+ return getDb().prepare(`
118
+ SELECT * FROM cards WHERE profile_id = ? AND topic = ?
119
+ ORDER BY RANDOM() LIMIT ?
120
+ `).all(profileId, topic, limit);
121
+ }
122
+ export function getAllCardsForExam(profileId, subject, limit) {
123
+ return getDb().prepare(`
124
+ SELECT * FROM cards WHERE profile_id = ? AND subject = ?
125
+ ORDER BY RANDOM() LIMIT ?
126
+ `).all(profileId, subject, limit);
127
+ }
128
+ // Session CRUD
129
+ export function insertSession(session) {
130
+ getDb().prepare(`
131
+ INSERT INTO sessions (id, profile_id, exam_type, started_at, ended_at, questions_answered, correct, readiness_score)
132
+ VALUES (@id, @profile_id, @exam_type, @started_at, @ended_at, @questions_answered, @correct, @readiness_score)
133
+ `).run(session);
134
+ }
135
+ export function updateSession(session) {
136
+ getDb().prepare(`
137
+ UPDATE sessions SET
138
+ ended_at = @ended_at,
139
+ questions_answered = @questions_answered,
140
+ correct = @correct,
141
+ readiness_score = @readiness_score
142
+ WHERE id = @id
143
+ `).run(session);
144
+ }
145
+ export function getSession(id) {
146
+ return getDb().prepare('SELECT * FROM sessions WHERE id = ?').get(id);
147
+ }
148
+ export function getRecentSessions(profileId, limit) {
149
+ return getDb().prepare(`
150
+ SELECT * FROM sessions WHERE profile_id = ? ORDER BY started_at DESC LIMIT ?
151
+ `).all(profileId, limit);
152
+ }
153
+ // Exam Config CRUD
154
+ export function upsertExamConfig(config) {
155
+ getDb().prepare(`
156
+ INSERT OR REPLACE INTO exam_config (profile_id, exam_type, exam_date, current_level, created_at)
157
+ VALUES (@profile_id, @exam_type, @exam_date, @current_level, @created_at)
158
+ `).run(config);
159
+ }
160
+ export function getExamConfig(profileId) {
161
+ return getDb().prepare('SELECT * FROM exam_config WHERE profile_id = ?').get(profileId);
162
+ }
163
+ // Topic Stats CRUD
164
+ export function upsertTopicStats(stats) {
165
+ getDb().prepare(`
166
+ INSERT INTO topic_stats (profile_id, topic, subject, total_attempts, correct_attempts, last_attempt)
167
+ VALUES (@profile_id, @topic, @subject, @total_attempts, @correct_attempts, @last_attempt)
168
+ ON CONFLICT(profile_id, topic) DO UPDATE SET
169
+ total_attempts = topic_stats.total_attempts + @total_attempts,
170
+ correct_attempts = topic_stats.correct_attempts + @correct_attempts,
171
+ last_attempt = @last_attempt,
172
+ subject = @subject
173
+ `).run(stats);
174
+ }
175
+ export function getTopicStats(profileId) {
176
+ return getDb().prepare('SELECT * FROM topic_stats WHERE profile_id = ?').all(profileId);
177
+ }
178
+ export function getTopicStat(profileId, topic) {
179
+ return getDb().prepare('SELECT * FROM topic_stats WHERE profile_id = ? AND topic = ?').get(profileId, topic);
180
+ }
181
+ export function getWeakTopics(profileId, threshold) {
182
+ return getDb().prepare(`
183
+ SELECT * FROM topic_stats
184
+ WHERE profile_id = ? AND total_attempts >= 3
185
+ AND CAST(correct_attempts AS REAL) / total_attempts < ?
186
+ ORDER BY CAST(correct_attempts AS REAL) / total_attempts ASC
187
+ `).all(profileId, threshold);
188
+ }
package/dist/fsrs.d.ts ADDED
@@ -0,0 +1,10 @@
1
+ export interface FSRSResult {
2
+ stability: number;
3
+ difficulty: number;
4
+ retrievability: number;
5
+ nextReview: Date;
6
+ intervalDays: number;
7
+ }
8
+ export declare function scheduleReview(rating: 1 | 2 | 3 | 4, currentStability: number, currentDifficulty: number, reviewCount: number, lastReview?: Date): FSRSResult;
9
+ export declare function calculateRetrievability(stability: number, daysSinceReview: number): number;
10
+ export declare function accuracyToRating(correct: boolean, responseTime?: number): 1 | 2 | 3 | 4;