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.
@@ -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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"}
@@ -30,7 +30,7 @@ export * from './types/index.js';
30
30
  /**
31
31
  * Package version
32
32
  */
33
- export declare const VERSION = "0.1.1";
33
+ export declare const VERSION = "0.5.0";
34
34
  /**
35
35
  * Package name
36
36
  */
@@ -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;;;;GAIG;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"}
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"}