learngraph 0.4.0 → 0.5.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/dist/cjs/index.js +1 -1
- package/dist/cjs/query/index.js +23 -4
- package/dist/cjs/query/index.js.map +1 -1
- package/dist/cjs/query/path.js +313 -0
- package/dist/cjs/query/path.js.map +1 -0
- package/dist/cjs/query/spaced-repetition.js +298 -0
- package/dist/cjs/query/spaced-repetition.js.map +1 -0
- package/dist/cjs/query/zpd.js +216 -0
- package/dist/cjs/query/zpd.js.map +1 -0
- package/dist/esm/index.js +1 -1
- package/dist/esm/query/index.js +11 -5
- package/dist/esm/query/index.js.map +1 -1
- package/dist/esm/query/path.js +308 -0
- package/dist/esm/query/path.js.map +1 -0
- package/dist/esm/query/spaced-repetition.js +292 -0
- package/dist/esm/query/spaced-repetition.js.map +1 -0
- package/dist/esm/query/zpd.js +211 -0
- package/dist/esm/query/zpd.js.map +1 -0
- package/dist/types/index.d.ts +1 -1
- package/dist/types/query/index.d.ts +8 -0
- package/dist/types/query/index.d.ts.map +1 -1
- package/dist/types/query/path.d.ts +102 -0
- package/dist/types/query/path.d.ts.map +1 -0
- package/dist/types/query/spaced-repetition.d.ts +135 -0
- package/dist/types/query/spaced-repetition.d.ts.map +1 -0
- package/dist/types/query/zpd.d.ts +97 -0
- package/dist/types/query/zpd.d.ts.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Spaced Repetition Scheduler (SM-2 Algorithm)
|
|
3
|
+
*
|
|
4
|
+
* Implements the SuperMemo SM-2 algorithm for optimal review scheduling.
|
|
5
|
+
* Schedules reviews at increasing intervals based on performance.
|
|
6
|
+
*
|
|
7
|
+
* @packageDocumentation
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Default options for review scheduling
|
|
11
|
+
*/
|
|
12
|
+
export const REVIEW_DEFAULTS = {
|
|
13
|
+
includeUpcoming: true,
|
|
14
|
+
upcomingDays: 7,
|
|
15
|
+
maxReviews: 50,
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* SM-2 algorithm constants
|
|
19
|
+
*/
|
|
20
|
+
export const SM2_CONSTANTS = {
|
|
21
|
+
/** Minimum easiness factor */
|
|
22
|
+
MIN_EASINESS: 1.3,
|
|
23
|
+
/** Default easiness factor */
|
|
24
|
+
DEFAULT_EASINESS: 2.5,
|
|
25
|
+
/** Maximum easiness factor */
|
|
26
|
+
MAX_EASINESS: 3.5,
|
|
27
|
+
/** Initial interval (days) after first review */
|
|
28
|
+
INITIAL_INTERVAL: 1,
|
|
29
|
+
/** Second interval (days) after second review */
|
|
30
|
+
SECOND_INTERVAL: 6,
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* Spaced Repetition Scheduler
|
|
34
|
+
*
|
|
35
|
+
* Uses the SM-2 algorithm to schedule optimal review times.
|
|
36
|
+
* The algorithm adjusts intervals based on recall quality:
|
|
37
|
+
* - Quality 5: Perfect response
|
|
38
|
+
* - Quality 4: Correct with hesitation
|
|
39
|
+
* - Quality 3: Correct with difficulty
|
|
40
|
+
* - Quality 2: Incorrect but easily recalled
|
|
41
|
+
* - Quality 1: Incorrect, remembered when shown
|
|
42
|
+
* - Quality 0: Complete blackout
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* ```typescript
|
|
46
|
+
* const scheduler = new SpacedRepetitionScheduler(storage);
|
|
47
|
+
*
|
|
48
|
+
* // Get review schedule
|
|
49
|
+
* const schedule = await scheduler.getSchedule(learnerState);
|
|
50
|
+
* console.log(`${schedule.dueNow.length} reviews due now`);
|
|
51
|
+
*
|
|
52
|
+
* // Update after a review
|
|
53
|
+
* const result = scheduler.calculateNextReview(masteryState, 4); // quality 4
|
|
54
|
+
* console.log(`Next review in ${result.intervalDays} days`);
|
|
55
|
+
* ```
|
|
56
|
+
*/
|
|
57
|
+
export class SpacedRepetitionScheduler {
|
|
58
|
+
storage;
|
|
59
|
+
constructor(storage) {
|
|
60
|
+
this.storage = storage;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Get the review schedule for a learner
|
|
64
|
+
*
|
|
65
|
+
* @param learner - Current learner state
|
|
66
|
+
* @param options - Schedule options
|
|
67
|
+
* @returns Review schedule with due and upcoming items
|
|
68
|
+
*/
|
|
69
|
+
async getSchedule(learner, options = {}) {
|
|
70
|
+
const opts = { ...REVIEW_DEFAULTS, ...options };
|
|
71
|
+
const now = new Date();
|
|
72
|
+
const upcomingCutoff = new Date(now.getTime() + opts.upcomingDays * 24 * 60 * 60 * 1000);
|
|
73
|
+
// Get all skills that have been practiced
|
|
74
|
+
const allSkills = await this.storage.findSkills({});
|
|
75
|
+
const dueNow = [];
|
|
76
|
+
const upcoming = [];
|
|
77
|
+
for (const skill of allSkills) {
|
|
78
|
+
const mastery = learner.masteryStates.get(skill.id);
|
|
79
|
+
if (!mastery) {
|
|
80
|
+
continue; // Never practiced
|
|
81
|
+
}
|
|
82
|
+
// Only schedule reviews for skills with some mastery
|
|
83
|
+
if (mastery.mastery < 0.1) {
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
const nextReview = this.calculateNextReviewDate(mastery);
|
|
87
|
+
const priority = this.calculatePriority(mastery, nextReview, now);
|
|
88
|
+
const reviewItem = {
|
|
89
|
+
skill,
|
|
90
|
+
state: {
|
|
91
|
+
mastery: mastery.mastery,
|
|
92
|
+
lastAttempt: mastery.lastAttempt,
|
|
93
|
+
streak: mastery.streak,
|
|
94
|
+
},
|
|
95
|
+
nextReview,
|
|
96
|
+
priority,
|
|
97
|
+
};
|
|
98
|
+
if (nextReview <= now) {
|
|
99
|
+
dueNow.push(reviewItem);
|
|
100
|
+
}
|
|
101
|
+
else if (opts.includeUpcoming && nextReview <= upcomingCutoff) {
|
|
102
|
+
upcoming.push(reviewItem);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
// Sort by priority (highest first)
|
|
106
|
+
dueNow.sort((a, b) => b.priority - a.priority);
|
|
107
|
+
upcoming.sort((a, b) => a.nextReview.getTime() - b.nextReview.getTime());
|
|
108
|
+
// Apply limits
|
|
109
|
+
const limitedDue = opts.maxReviews ? dueNow.slice(0, opts.maxReviews) : dueNow;
|
|
110
|
+
const remainingLimit = opts.maxReviews
|
|
111
|
+
? Math.max(0, opts.maxReviews - limitedDue.length)
|
|
112
|
+
: undefined;
|
|
113
|
+
const limitedUpcoming = remainingLimit !== undefined
|
|
114
|
+
? upcoming.slice(0, remainingLimit)
|
|
115
|
+
: upcoming;
|
|
116
|
+
// Calculate total review time
|
|
117
|
+
const totalReviewMinutes = [...limitedDue, ...limitedUpcoming].reduce((sum, item) => sum + Math.ceil(item.skill.estimatedMinutes * 0.3), // Reviews are ~30% of initial learning time
|
|
118
|
+
0);
|
|
119
|
+
return {
|
|
120
|
+
dueNow: limitedDue,
|
|
121
|
+
upcoming: limitedUpcoming,
|
|
122
|
+
totalReviewMinutes,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Calculate the next review interval using SM-2 algorithm
|
|
127
|
+
*
|
|
128
|
+
* @param currentState - Current mastery state
|
|
129
|
+
* @param quality - Review quality (0-5)
|
|
130
|
+
* @returns Updated SM-2 parameters
|
|
131
|
+
*/
|
|
132
|
+
calculateNextReview(currentState, quality) {
|
|
133
|
+
const now = new Date();
|
|
134
|
+
// Get current SM-2 state
|
|
135
|
+
let easiness = currentState.easinessFactor ?? SM2_CONSTANTS.DEFAULT_EASINESS;
|
|
136
|
+
let repetitions = currentState.streak;
|
|
137
|
+
let interval;
|
|
138
|
+
// Quality must be >= 3 for successful recall
|
|
139
|
+
if (quality >= 3) {
|
|
140
|
+
// Successful recall
|
|
141
|
+
if (repetitions === 0) {
|
|
142
|
+
interval = SM2_CONSTANTS.INITIAL_INTERVAL;
|
|
143
|
+
}
|
|
144
|
+
else if (repetitions === 1) {
|
|
145
|
+
interval = SM2_CONSTANTS.SECOND_INTERVAL;
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
// Get previous interval from last attempt
|
|
149
|
+
const lastInterval = this.estimatePreviousInterval(currentState);
|
|
150
|
+
interval = Math.round(lastInterval * easiness);
|
|
151
|
+
}
|
|
152
|
+
repetitions++;
|
|
153
|
+
}
|
|
154
|
+
else {
|
|
155
|
+
// Failed recall - reset to beginning
|
|
156
|
+
repetitions = 0;
|
|
157
|
+
interval = SM2_CONSTANTS.INITIAL_INTERVAL;
|
|
158
|
+
}
|
|
159
|
+
// Update easiness factor
|
|
160
|
+
// EF = EF + (0.1 - (5 - q) * (0.08 + (5 - q) * 0.02))
|
|
161
|
+
const easinessChange = 0.1 - (5 - quality) * (0.08 + (5 - quality) * 0.02);
|
|
162
|
+
easiness = Math.max(SM2_CONSTANTS.MIN_EASINESS, Math.min(SM2_CONSTANTS.MAX_EASINESS, easiness + easinessChange));
|
|
163
|
+
// Calculate next review date
|
|
164
|
+
const nextReview = new Date(now.getTime() + interval * 24 * 60 * 60 * 1000);
|
|
165
|
+
return {
|
|
166
|
+
easinessFactor: easiness,
|
|
167
|
+
intervalDays: interval,
|
|
168
|
+
nextReview,
|
|
169
|
+
repetitions,
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Convert review quality to mastery update
|
|
174
|
+
*
|
|
175
|
+
* @param quality - Review quality (0-5)
|
|
176
|
+
* @returns Mastery level adjustment
|
|
177
|
+
*/
|
|
178
|
+
qualityToMasteryAdjustment(quality) {
|
|
179
|
+
// Map quality to mastery change
|
|
180
|
+
switch (quality) {
|
|
181
|
+
case 5: return 0.1; // Perfect - increase mastery
|
|
182
|
+
case 4: return 0.05; // Correct with hesitation
|
|
183
|
+
case 3: return 0.0; // Correct with difficulty - maintain
|
|
184
|
+
case 2: return -0.1; // Incorrect but close
|
|
185
|
+
case 1: return -0.2; // Poor recall
|
|
186
|
+
case 0: return -0.3; // Complete failure
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Calculate next review date based on current mastery state
|
|
191
|
+
*/
|
|
192
|
+
calculateNextReviewDate(mastery) {
|
|
193
|
+
if (!mastery.lastAttempt) {
|
|
194
|
+
return new Date(); // Due immediately
|
|
195
|
+
}
|
|
196
|
+
const easiness = mastery.easinessFactor ?? SM2_CONSTANTS.DEFAULT_EASINESS;
|
|
197
|
+
const streak = mastery.streak;
|
|
198
|
+
// Calculate interval based on streak
|
|
199
|
+
let interval;
|
|
200
|
+
if (streak === 0) {
|
|
201
|
+
interval = SM2_CONSTANTS.INITIAL_INTERVAL;
|
|
202
|
+
}
|
|
203
|
+
else if (streak === 1) {
|
|
204
|
+
interval = SM2_CONSTANTS.SECOND_INTERVAL;
|
|
205
|
+
}
|
|
206
|
+
else {
|
|
207
|
+
// Exponential growth based on streak and easiness
|
|
208
|
+
interval = Math.round(SM2_CONSTANTS.SECOND_INTERVAL * Math.pow(easiness, streak - 1));
|
|
209
|
+
}
|
|
210
|
+
// Clamp to reasonable bounds (1 day to 1 year)
|
|
211
|
+
interval = Math.max(1, Math.min(365, interval));
|
|
212
|
+
return new Date(mastery.lastAttempt.getTime() + interval * 24 * 60 * 60 * 1000);
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Estimate previous interval from mastery state
|
|
216
|
+
*/
|
|
217
|
+
estimatePreviousInterval(mastery) {
|
|
218
|
+
const streak = mastery.streak;
|
|
219
|
+
const easiness = mastery.easinessFactor ?? SM2_CONSTANTS.DEFAULT_EASINESS;
|
|
220
|
+
if (streak <= 1) {
|
|
221
|
+
return SM2_CONSTANTS.INITIAL_INTERVAL;
|
|
222
|
+
}
|
|
223
|
+
else if (streak === 2) {
|
|
224
|
+
return SM2_CONSTANTS.SECOND_INTERVAL;
|
|
225
|
+
}
|
|
226
|
+
else {
|
|
227
|
+
return Math.round(SM2_CONSTANTS.SECOND_INTERVAL * Math.pow(easiness, streak - 2));
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Calculate review priority
|
|
232
|
+
* Higher priority = more urgent to review
|
|
233
|
+
*/
|
|
234
|
+
calculatePriority(mastery, nextReview, now) {
|
|
235
|
+
// Base priority on how overdue the review is
|
|
236
|
+
const overdueDays = (now.getTime() - nextReview.getTime()) / (24 * 60 * 60 * 1000);
|
|
237
|
+
// Higher mastery items get slightly lower priority (we want to maintain them)
|
|
238
|
+
const masteryFactor = 1 - mastery.mastery * 0.3;
|
|
239
|
+
// Combine factors
|
|
240
|
+
// Overdue items get positive priority, upcoming items get negative
|
|
241
|
+
return overdueDays * masteryFactor * 10;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Create a spaced repetition scheduler
|
|
246
|
+
*/
|
|
247
|
+
export function createSpacedRepetitionScheduler(storage) {
|
|
248
|
+
return new SpacedRepetitionScheduler(storage);
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Standalone SM-2 calculation (for use without storage)
|
|
252
|
+
*
|
|
253
|
+
* @param quality - Review quality (0-5)
|
|
254
|
+
* @param previousEasiness - Previous easiness factor
|
|
255
|
+
* @param previousInterval - Previous interval in days
|
|
256
|
+
* @param repetitions - Number of previous successful repetitions
|
|
257
|
+
* @returns Updated SM-2 parameters
|
|
258
|
+
*/
|
|
259
|
+
export function calculateSM2(quality, previousEasiness = SM2_CONSTANTS.DEFAULT_EASINESS, previousInterval = 0, repetitions = 0) {
|
|
260
|
+
const now = new Date();
|
|
261
|
+
let easiness = previousEasiness;
|
|
262
|
+
let interval;
|
|
263
|
+
let newRepetitions = repetitions;
|
|
264
|
+
if (quality >= 3) {
|
|
265
|
+
// Successful recall
|
|
266
|
+
if (newRepetitions === 0) {
|
|
267
|
+
interval = SM2_CONSTANTS.INITIAL_INTERVAL;
|
|
268
|
+
}
|
|
269
|
+
else if (newRepetitions === 1) {
|
|
270
|
+
interval = SM2_CONSTANTS.SECOND_INTERVAL;
|
|
271
|
+
}
|
|
272
|
+
else {
|
|
273
|
+
interval = Math.round(previousInterval * easiness);
|
|
274
|
+
}
|
|
275
|
+
newRepetitions++;
|
|
276
|
+
}
|
|
277
|
+
else {
|
|
278
|
+
// Failed - reset
|
|
279
|
+
interval = SM2_CONSTANTS.INITIAL_INTERVAL;
|
|
280
|
+
newRepetitions = 0;
|
|
281
|
+
}
|
|
282
|
+
// Update easiness
|
|
283
|
+
const easinessChange = 0.1 - (5 - quality) * (0.08 + (5 - quality) * 0.02);
|
|
284
|
+
easiness = Math.max(SM2_CONSTANTS.MIN_EASINESS, Math.min(SM2_CONSTANTS.MAX_EASINESS, easiness + easinessChange));
|
|
285
|
+
return {
|
|
286
|
+
easinessFactor: easiness,
|
|
287
|
+
intervalDays: interval,
|
|
288
|
+
nextReview: new Date(now.getTime() + interval * 24 * 60 * 60 * 1000),
|
|
289
|
+
repetitions: newRepetitions,
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
//# sourceMappingURL=spaced-repetition.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spaced-repetition.js","sourceRoot":"","sources":["../../../src/query/spaced-repetition.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Zone of Proximal Development (ZPD) Calculator
|
|
3
|
+
*
|
|
4
|
+
* Implements Vygotsky's Zone of Proximal Development theory to identify
|
|
5
|
+
* skills that are "just right" for a learner - challenging but achievable.
|
|
6
|
+
*
|
|
7
|
+
* @packageDocumentation
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Default options for ZPD calculation
|
|
11
|
+
*/
|
|
12
|
+
export const ZPD_DEFAULTS = {
|
|
13
|
+
difficultyTolerance: 0.2,
|
|
14
|
+
prioritizeThresholdConcepts: true,
|
|
15
|
+
maxResults: 50,
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* ZPD Calculator
|
|
19
|
+
*
|
|
20
|
+
* Calculates the Zone of Proximal Development for a learner based on:
|
|
21
|
+
* 1. Current mastery states
|
|
22
|
+
* 2. Prerequisite graph structure
|
|
23
|
+
* 3. Skill difficulty levels
|
|
24
|
+
*
|
|
25
|
+
* A skill is in the ZPD if:
|
|
26
|
+
* - All prerequisites are mastered
|
|
27
|
+
* - The skill is not yet mastered
|
|
28
|
+
* - The skill difficulty is within tolerance of learner ability
|
|
29
|
+
*/
|
|
30
|
+
export class ZPDCalculator {
|
|
31
|
+
storage;
|
|
32
|
+
constructor(storage) {
|
|
33
|
+
this.storage = storage;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Calculate ZPD for a learner
|
|
37
|
+
*
|
|
38
|
+
* @param learner - Learner profile with mastery states
|
|
39
|
+
* @param options - Calculation options
|
|
40
|
+
* @returns ZPD result with ready skills and blocked skills
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* ```typescript
|
|
44
|
+
* const calculator = new ZPDCalculator(storage);
|
|
45
|
+
*
|
|
46
|
+
* const learner = {
|
|
47
|
+
* masteryStates: new Map([
|
|
48
|
+
* ['skill-1', { skillId: 'skill-1', mastery: 0.9, ... }],
|
|
49
|
+
* ['skill-2', { skillId: 'skill-2', mastery: 0.3, ... }],
|
|
50
|
+
* ])
|
|
51
|
+
* };
|
|
52
|
+
*
|
|
53
|
+
* const zpd = await calculator.calculate(learner);
|
|
54
|
+
* console.log('Ready to learn:', zpd.ready.map(s => s.name));
|
|
55
|
+
* ```
|
|
56
|
+
*/
|
|
57
|
+
async calculate(learner, options = {}) {
|
|
58
|
+
const opts = { ...ZPD_DEFAULTS, ...options };
|
|
59
|
+
// Get all skills from storage
|
|
60
|
+
let allSkills = await this.storage.findSkills({});
|
|
61
|
+
// Apply filters
|
|
62
|
+
if (opts.filterTags && opts.filterTags.length > 0) {
|
|
63
|
+
allSkills = allSkills.filter((skill) => opts.filterTags.some((tag) => skill.tags.includes(tag)));
|
|
64
|
+
}
|
|
65
|
+
if (opts.filterDomain) {
|
|
66
|
+
allSkills = allSkills.filter((skill) => skill.metadata?.domain === opts.filterDomain);
|
|
67
|
+
}
|
|
68
|
+
// Calculate learner ability level if not provided
|
|
69
|
+
const abilityLevel = learner.abilityLevel ?? this.calculateAbilityLevel(learner);
|
|
70
|
+
// Categorize skills
|
|
71
|
+
const zpd = [];
|
|
72
|
+
const ready = [];
|
|
73
|
+
const blocked = new Map();
|
|
74
|
+
let masteredCount = 0;
|
|
75
|
+
for (const skill of allSkills) {
|
|
76
|
+
const mastery = learner.masteryStates.get(skill.id);
|
|
77
|
+
const masteryLevel = mastery?.mastery ?? 0;
|
|
78
|
+
// Check if already mastered
|
|
79
|
+
if (masteryLevel >= skill.masteryThreshold) {
|
|
80
|
+
masteredCount++;
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
// Get prerequisites
|
|
84
|
+
const prerequisites = await this.storage.getPrerequisitesOf(skill.id);
|
|
85
|
+
const unmetPrereqs = [];
|
|
86
|
+
for (const prereq of prerequisites) {
|
|
87
|
+
const prereqMastery = learner.masteryStates.get(prereq.id);
|
|
88
|
+
const prereqLevel = prereqMastery?.mastery ?? 0;
|
|
89
|
+
if (prereqLevel < prereq.masteryThreshold) {
|
|
90
|
+
unmetPrereqs.push(prereq.name);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
if (unmetPrereqs.length > 0) {
|
|
94
|
+
// Skill is blocked
|
|
95
|
+
blocked.set(skill.id, unmetPrereqs);
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
// Skill is in ZPD (prerequisites met, not yet mastered)
|
|
99
|
+
zpd.push(skill);
|
|
100
|
+
// Check if within difficulty tolerance
|
|
101
|
+
const difficultyDelta = skill.difficulty - abilityLevel;
|
|
102
|
+
if (difficultyDelta <= opts.difficultyTolerance) {
|
|
103
|
+
ready.push(skill);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
// Sort ready skills by priority
|
|
108
|
+
this.sortByPriority(ready, opts.prioritizeThresholdConcepts);
|
|
109
|
+
// Limit results
|
|
110
|
+
const limitedReady = opts.maxResults ? ready.slice(0, opts.maxResults) : ready;
|
|
111
|
+
const limitedZpd = opts.maxResults ? zpd.slice(0, opts.maxResults) : zpd;
|
|
112
|
+
return {
|
|
113
|
+
zpd: limitedZpd,
|
|
114
|
+
ready: limitedReady,
|
|
115
|
+
blocked,
|
|
116
|
+
stats: {
|
|
117
|
+
totalSkills: allSkills.length,
|
|
118
|
+
mastered: masteredCount,
|
|
119
|
+
inZPD: zpd.length,
|
|
120
|
+
blocked: blocked.size,
|
|
121
|
+
},
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Get the next recommended skill to learn
|
|
126
|
+
*
|
|
127
|
+
* @param learner - Learner profile
|
|
128
|
+
* @param options - ZPD options
|
|
129
|
+
* @returns The highest priority skill to learn next, or null if none available
|
|
130
|
+
*/
|
|
131
|
+
async getNextSkill(learner, options = {}) {
|
|
132
|
+
const zpd = await this.calculate(learner, { ...options, maxResults: 1 });
|
|
133
|
+
return zpd.ready[0] ?? zpd.zpd[0] ?? null;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Check if a specific skill is in the learner's ZPD
|
|
137
|
+
*
|
|
138
|
+
* @param skillId - Skill to check
|
|
139
|
+
* @param learner - Learner profile
|
|
140
|
+
* @returns Object with inZPD status and blocking prerequisites if any
|
|
141
|
+
*/
|
|
142
|
+
async isInZPD(skillId, learner) {
|
|
143
|
+
const skill = await this.storage.getSkill(skillId);
|
|
144
|
+
if (!skill) {
|
|
145
|
+
return { inZPD: false, blocking: ['Skill not found'] };
|
|
146
|
+
}
|
|
147
|
+
// Check if already mastered
|
|
148
|
+
const mastery = learner.masteryStates.get(skillId);
|
|
149
|
+
if (mastery && mastery.mastery >= skill.masteryThreshold) {
|
|
150
|
+
return { inZPD: false, blocking: [] };
|
|
151
|
+
}
|
|
152
|
+
// Check prerequisites
|
|
153
|
+
const prerequisites = await this.storage.getPrerequisitesOf(skillId);
|
|
154
|
+
const blocking = [];
|
|
155
|
+
for (const prereq of prerequisites) {
|
|
156
|
+
const prereqMastery = learner.masteryStates.get(prereq.id);
|
|
157
|
+
const prereqLevel = prereqMastery?.mastery ?? 0;
|
|
158
|
+
if (prereqLevel < prereq.masteryThreshold) {
|
|
159
|
+
blocking.push(prereq.name);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return {
|
|
163
|
+
inZPD: blocking.length === 0,
|
|
164
|
+
blocking,
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Calculate learner ability level from mastery states
|
|
169
|
+
*/
|
|
170
|
+
calculateAbilityLevel(learner) {
|
|
171
|
+
if (learner.masteryStates.size === 0) {
|
|
172
|
+
return 0.3; // Default starting ability
|
|
173
|
+
}
|
|
174
|
+
let totalWeighted = 0;
|
|
175
|
+
let totalWeight = 0;
|
|
176
|
+
for (const state of learner.masteryStates.values()) {
|
|
177
|
+
// Weight by mastery level (higher mastery = more confident estimate)
|
|
178
|
+
const weight = state.mastery;
|
|
179
|
+
totalWeighted += state.mastery * weight;
|
|
180
|
+
totalWeight += weight;
|
|
181
|
+
}
|
|
182
|
+
return totalWeight > 0 ? totalWeighted / totalWeight : 0.3;
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Sort skills by learning priority
|
|
186
|
+
*/
|
|
187
|
+
sortByPriority(skills, prioritizeThreshold) {
|
|
188
|
+
skills.sort((a, b) => {
|
|
189
|
+
// Threshold concepts first (if enabled)
|
|
190
|
+
if (prioritizeThreshold) {
|
|
191
|
+
if (a.isThresholdConcept && !b.isThresholdConcept)
|
|
192
|
+
return -1;
|
|
193
|
+
if (!a.isThresholdConcept && b.isThresholdConcept)
|
|
194
|
+
return 1;
|
|
195
|
+
}
|
|
196
|
+
// Then by difficulty (easier first for progressive learning)
|
|
197
|
+
if (a.difficulty !== b.difficulty) {
|
|
198
|
+
return a.difficulty - b.difficulty;
|
|
199
|
+
}
|
|
200
|
+
// Finally by estimated time (shorter first)
|
|
201
|
+
return a.estimatedMinutes - b.estimatedMinutes;
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Create a ZPD calculator
|
|
207
|
+
*/
|
|
208
|
+
export function createZPDCalculator(storage) {
|
|
209
|
+
return new ZPDCalculator(storage);
|
|
210
|
+
}
|
|
211
|
+
//# sourceMappingURL=zpd.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"zpd.js","sourceRoot":"","sources":["../../../src/query/zpd.ts"],"names":[],"mappings":"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"}
|
package/dist/types/index.d.ts
CHANGED
|
@@ -1,7 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Query and traversal engines
|
|
3
3
|
*
|
|
4
|
+
* This module provides intelligent query engines for:
|
|
5
|
+
* - Zone of Proximal Development (ZPD) calculation
|
|
6
|
+
* - Learning path generation
|
|
7
|
+
* - Spaced repetition scheduling
|
|
8
|
+
*
|
|
4
9
|
* @packageDocumentation
|
|
5
10
|
*/
|
|
6
11
|
export type { QueryOperator, QueryFilter, QuerySort, QueryPagination, SkillQuery, ZPDResult, LearningPath, LearningSession, ReviewSchedule, ReviewItem, ZPDOptions, PathOptions, ReviewOptions, } from '../types/query.js';
|
|
12
|
+
export { ZPDCalculator, createZPDCalculator, ZPD_DEFAULTS, type LearnerProfile, } from './zpd.js';
|
|
13
|
+
export { PathGenerator, createPathGenerator, PATH_DEFAULTS, type LearnerState, } from './path.js';
|
|
14
|
+
export { SpacedRepetitionScheduler, createSpacedRepetitionScheduler, calculateSM2, REVIEW_DEFAULTS, SM2_CONSTANTS, type ReviewQuality, type ReviewState, type SM2Result, } from './spaced-repetition.js';
|
|
7
15
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/query/index.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/query/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,YAAY,EACV,aAAa,EACb,WAAW,EACX,SAAS,EACT,eAAe,EACf,UAAU,EACV,SAAS,EACT,YAAY,EACZ,eAAe,EACf,cAAc,EACd,UAAU,EACV,UAAU,EACV,WAAW,EACX,aAAa,GACd,MAAM,mBAAmB,CAAC;AAG3B,OAAO,EACL,aAAa,EACb,mBAAmB,EACnB,YAAY,EACZ,KAAK,cAAc,GACpB,MAAM,UAAU,CAAC;AAGlB,OAAO,EACL,aAAa,EACb,mBAAmB,EACnB,aAAa,EACb,KAAK,YAAY,GAClB,MAAM,WAAW,CAAC;AAGnB,OAAO,EACL,yBAAyB,EACzB,+BAA+B,EAC/B,YAAY,EACZ,eAAe,EACf,aAAa,EACb,KAAK,aAAa,EAClB,KAAK,WAAW,EAChB,KAAK,SAAS,GACf,MAAM,wBAAwB,CAAC"}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Learning Path Generator
|
|
3
|
+
*
|
|
4
|
+
* Generates personalized learning paths from current state to target skills.
|
|
5
|
+
* Uses topological sorting and groups skills into manageable sessions.
|
|
6
|
+
*
|
|
7
|
+
* @packageDocumentation
|
|
8
|
+
*/
|
|
9
|
+
import type { SkillId } from '../types/skill.js';
|
|
10
|
+
import type { MasteryState } from '../types/mastery.js';
|
|
11
|
+
import type { GraphStorage } from '../types/storage.js';
|
|
12
|
+
import type { LearningPath, PathOptions } from '../types/query.js';
|
|
13
|
+
/**
|
|
14
|
+
* Default options for path generation
|
|
15
|
+
*/
|
|
16
|
+
export declare const PATH_DEFAULTS: Required<PathOptions>;
|
|
17
|
+
/**
|
|
18
|
+
* Learner state for path generation
|
|
19
|
+
*/
|
|
20
|
+
export interface LearnerState {
|
|
21
|
+
/** Mastery states for skills (keyed by skill ID) */
|
|
22
|
+
masteryStates: Map<SkillId, MasteryState>;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Learning Path Generator
|
|
26
|
+
*
|
|
27
|
+
* Generates optimal learning paths to reach target skills by:
|
|
28
|
+
* 1. Finding all unmastered prerequisites
|
|
29
|
+
* 2. Topologically sorting based on dependencies
|
|
30
|
+
* 3. Grouping into sessions based on cognitive load
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* ```typescript
|
|
34
|
+
* const generator = new PathGenerator(storage);
|
|
35
|
+
*
|
|
36
|
+
* const path = await generator.generatePath(
|
|
37
|
+
* 'advanced-skill',
|
|
38
|
+
* learnerState,
|
|
39
|
+
* { sessionMinutes: 45 }
|
|
40
|
+
* );
|
|
41
|
+
*
|
|
42
|
+
* console.log(`Path has ${path.skills.length} skills`);
|
|
43
|
+
* console.log(`Estimated time: ${path.totalMinutes} minutes`);
|
|
44
|
+
* ```
|
|
45
|
+
*/
|
|
46
|
+
export declare class PathGenerator {
|
|
47
|
+
private readonly storage;
|
|
48
|
+
constructor(storage: GraphStorage);
|
|
49
|
+
/**
|
|
50
|
+
* Generate a learning path to a target skill
|
|
51
|
+
*
|
|
52
|
+
* @param targetId - The skill to learn
|
|
53
|
+
* @param learner - Current learner state
|
|
54
|
+
* @param options - Path generation options
|
|
55
|
+
* @returns Learning path with skills grouped into sessions
|
|
56
|
+
*/
|
|
57
|
+
generatePath(targetId: SkillId, learner: LearnerState, options?: PathOptions): Promise<LearningPath | null>;
|
|
58
|
+
/**
|
|
59
|
+
* Generate paths to multiple target skills
|
|
60
|
+
*
|
|
61
|
+
* @param targetIds - Skills to learn
|
|
62
|
+
* @param learner - Current learner state
|
|
63
|
+
* @param options - Path generation options
|
|
64
|
+
* @returns Array of learning paths
|
|
65
|
+
*/
|
|
66
|
+
generatePaths(targetIds: SkillId[], learner: LearnerState, options?: PathOptions): Promise<LearningPath[]>;
|
|
67
|
+
/**
|
|
68
|
+
* Get a merged learning path for multiple targets
|
|
69
|
+
* Combines all required skills and eliminates duplicates
|
|
70
|
+
*/
|
|
71
|
+
generateMergedPath(targetIds: SkillId[], learner: LearnerState, options?: PathOptions): Promise<LearningPath | null>;
|
|
72
|
+
/**
|
|
73
|
+
* Get all transitive prerequisites of a skill
|
|
74
|
+
*/
|
|
75
|
+
private getTransitivePrerequisites;
|
|
76
|
+
/**
|
|
77
|
+
* Filter skills to only unmastered ones
|
|
78
|
+
*/
|
|
79
|
+
private filterUnmastered;
|
|
80
|
+
/**
|
|
81
|
+
* Topologically sort skills based on prerequisites
|
|
82
|
+
* Skills with no unmastered prerequisites come first
|
|
83
|
+
*/
|
|
84
|
+
private topologicalSort;
|
|
85
|
+
/**
|
|
86
|
+
* Group skills into learning sessions based on target duration
|
|
87
|
+
*/
|
|
88
|
+
private groupIntoSessions;
|
|
89
|
+
/**
|
|
90
|
+
* Create a learning session from a group of skills
|
|
91
|
+
*/
|
|
92
|
+
private createSession;
|
|
93
|
+
/**
|
|
94
|
+
* Determine the focus/theme of a session
|
|
95
|
+
*/
|
|
96
|
+
private determineFocus;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Create a path generator
|
|
100
|
+
*/
|
|
101
|
+
export declare function createPathGenerator(storage: GraphStorage): PathGenerator;
|
|
102
|
+
//# sourceMappingURL=path.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"path.d.ts","sourceRoot":"","sources":["../../../src/query/path.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAa,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAC5D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,KAAK,EAAE,YAAY,EAAmB,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAEpF;;GAEG;AACH,eAAO,MAAM,aAAa,EAAE,QAAQ,CAAC,WAAW,CAI/C,CAAC;AAEF;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,oDAAoD;IACpD,aAAa,EAAE,GAAG,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC;CAC3C;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,qBAAa,aAAa;IACZ,OAAO,CAAC,QAAQ,CAAC,OAAO;gBAAP,OAAO,EAAE,YAAY;IAElD;;;;;;;OAOG;IACG,YAAY,CAChB,QAAQ,EAAE,OAAO,EACjB,OAAO,EAAE,YAAY,EACrB,OAAO,GAAE,WAAgB,GACxB,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC;IA4D/B;;;;;;;OAOG;IACG,aAAa,CACjB,SAAS,EAAE,OAAO,EAAE,EACpB,OAAO,EAAE,YAAY,EACrB,OAAO,GAAE,WAAgB,GACxB,OAAO,CAAC,YAAY,EAAE,CAAC;IAa1B;;;OAGG;IACG,kBAAkB,CACtB,SAAS,EAAE,OAAO,EAAE,EACpB,OAAO,EAAE,YAAY,EACrB,OAAO,GAAE,WAAgB,GACxB,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC;IAiE/B;;OAEG;YACW,0BAA0B;IAyBxC;;OAEG;YACW,gBAAgB;IAmB9B;;;OAGG;YACW,eAAe;IA0C7B;;OAEG;IACH,OAAO,CAAC,iBAAiB;IA+BzB;;OAEG;IACH,OAAO,CAAC,aAAa;IA0BrB;;OAEG;IACH,OAAO,CAAC,cAAc;CAqBvB;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,YAAY,GAAG,aAAa,CAExE"}
|