@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 +21 -0
- package/README.md +287 -0
- package/SKILL.md +79 -0
- package/dist/db.d.ts +66 -0
- package/dist/db.js +188 -0
- package/dist/fsrs.d.ts +10 -0
- package/dist/fsrs.js +101 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +226 -0
- package/dist/questions.d.ts +17 -0
- package/dist/questions.js +906 -0
- package/dist/readiness.d.ts +15 -0
- package/dist/readiness.js +272 -0
- package/dist/study-plan.d.ts +25 -0
- package/dist/study-plan.js +313 -0
- package/dist/tools.d.ts +132 -0
- package/dist/tools.js +424 -0
- package/llms.txt +83 -0
- package/package.json +30 -0
- package/src/db.ts +257 -0
- package/src/fsrs.ts +118 -0
- package/src/index.ts +262 -0
- package/src/questions.ts +962 -0
- package/src/readiness.ts +268 -0
- package/src/study-plan.ts +364 -0
- package/src/tools.ts +557 -0
- package/tsconfig.json +16 -0
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
|
+
[](https://www.npmjs.com/package/@gonzih/exam-prep-mcp)
|
|
6
|
+
[](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;
|