@vue-skuilder/db 0.1.16 → 0.1.18

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.
Files changed (80) hide show
  1. package/dist/{userDB-DNa0XPtn.d.ts → classroomDB-BgfrVb8d.d.ts} +357 -103
  2. package/dist/{userDB-BqwxtJ_7.d.mts → classroomDB-CTOenngH.d.cts} +358 -104
  3. package/dist/core/index.d.cts +230 -0
  4. package/dist/core/index.d.ts +161 -23
  5. package/dist/core/index.js +1964 -154
  6. package/dist/core/index.js.map +1 -1
  7. package/dist/core/index.mjs +1925 -121
  8. package/dist/core/index.mjs.map +1 -1
  9. package/dist/{dataLayerProvider-BV5iZqt_.d.ts → dataLayerProvider-CZxC9GtB.d.ts} +1 -1
  10. package/dist/{dataLayerProvider-VlngD19_.d.mts → dataLayerProvider-D6PoCwS6.d.cts} +1 -1
  11. package/dist/impl/couch/{index.d.mts → index.d.cts} +46 -5
  12. package/dist/impl/couch/index.d.ts +44 -3
  13. package/dist/impl/couch/index.js +1971 -171
  14. package/dist/impl/couch/index.js.map +1 -1
  15. package/dist/impl/couch/index.mjs +1933 -134
  16. package/dist/impl/couch/index.mjs.map +1 -1
  17. package/dist/impl/static/{index.d.mts → index.d.cts} +5 -6
  18. package/dist/impl/static/index.d.ts +2 -3
  19. package/dist/impl/static/index.js +1614 -119
  20. package/dist/impl/static/index.js.map +1 -1
  21. package/dist/impl/static/index.mjs +1585 -92
  22. package/dist/impl/static/index.mjs.map +1 -1
  23. package/dist/{index-Bmll7Xse.d.mts → index-D-Fa4Smt.d.cts} +1 -1
  24. package/dist/{index.d.mts → index.d.cts} +97 -13
  25. package/dist/index.d.ts +90 -6
  26. package/dist/index.js +2085 -153
  27. package/dist/index.js.map +1 -1
  28. package/dist/index.mjs +2031 -106
  29. package/dist/index.mjs.map +1 -1
  30. package/dist/pouch/index.js +3 -3
  31. package/dist/{types-Dbp5DaRR.d.mts → types-CzPDLAK6.d.cts} +1 -1
  32. package/dist/util/packer/{index.d.mts → index.d.cts} +3 -3
  33. package/dist/util/packer/index.js.map +1 -1
  34. package/dist/util/packer/index.mjs.map +1 -1
  35. package/docs/brainstorm-navigation-paradigm.md +369 -0
  36. package/docs/navigators-architecture.md +265 -0
  37. package/docs/todo-evolutionary-orchestration.md +310 -0
  38. package/docs/todo-nominal-tag-types.md +121 -0
  39. package/docs/todo-pipeline-optimization.md +117 -0
  40. package/docs/todo-strategy-authoring.md +401 -0
  41. package/docs/todo-strategy-state-storage.md +278 -0
  42. package/eslint.config.mjs +1 -1
  43. package/package.json +9 -4
  44. package/src/core/interfaces/contentSource.ts +88 -4
  45. package/src/core/interfaces/navigationStrategyManager.ts +0 -5
  46. package/src/core/navigators/CompositeGenerator.ts +268 -0
  47. package/src/core/navigators/Pipeline.ts +205 -0
  48. package/src/core/navigators/PipelineAssembler.ts +194 -0
  49. package/src/core/navigators/elo.ts +104 -15
  50. package/src/core/navigators/filters/eloDistance.ts +132 -0
  51. package/src/core/navigators/filters/index.ts +6 -0
  52. package/src/core/navigators/filters/types.ts +115 -0
  53. package/src/core/navigators/generators/index.ts +2 -0
  54. package/src/core/navigators/generators/types.ts +107 -0
  55. package/src/core/navigators/hardcodedOrder.ts +111 -12
  56. package/src/core/navigators/hierarchyDefinition.ts +266 -0
  57. package/src/core/navigators/index.ts +345 -3
  58. package/src/core/navigators/interferenceMitigator.ts +367 -0
  59. package/src/core/navigators/relativePriority.ts +267 -0
  60. package/src/core/navigators/srs.ts +195 -0
  61. package/src/impl/couch/classroomDB.ts +51 -0
  62. package/src/impl/couch/courseDB.ts +117 -39
  63. package/src/impl/static/courseDB.ts +0 -4
  64. package/src/study/SessionController.ts +149 -1
  65. package/src/study/TagFilteredContentSource.ts +255 -0
  66. package/src/study/index.ts +1 -0
  67. package/src/util/dataDirectory.test.ts +51 -22
  68. package/src/util/logger.ts +0 -1
  69. package/tests/core/navigators/CompositeGenerator.test.ts +455 -0
  70. package/tests/core/navigators/Pipeline.test.ts +405 -0
  71. package/tests/core/navigators/PipelineAssembler.test.ts +351 -0
  72. package/tests/core/navigators/SRSNavigator.test.ts +344 -0
  73. package/tests/core/navigators/eloDistanceFilter.test.ts +192 -0
  74. package/tests/core/navigators/navigators.test.ts +710 -0
  75. package/tsconfig.json +1 -1
  76. package/vitest.config.ts +29 -0
  77. package/dist/core/index.d.mts +0 -92
  78. /package/dist/{SyncStrategy-CyATpyLQ.d.mts → SyncStrategy-CyATpyLQ.d.cts} +0 -0
  79. /package/dist/pouch/{index.d.mts → index.d.cts} +0 -0
  80. /package/dist/{types-legacy-6ettoclI.d.mts → types-legacy-6ettoclI.d.cts} +0 -0
@@ -5,23 +5,282 @@ import {
5
5
  StudySessionReviewItem,
6
6
  StudySessionNewItem,
7
7
  } from '..';
8
+
9
+ // Re-export filter types
10
+ export type { CardFilter, FilterContext, CardFilterFactory } from './filters/types';
11
+
12
+ // Re-export generator types
13
+ export type { CardGenerator, GeneratorContext, CardGeneratorFactory } from './generators/types';
14
+
8
15
  import { ContentNavigationStrategyData } from '../types/contentNavigationStrategy';
9
16
  import { ScheduledCard } from '../types/user';
10
17
  import { logger } from '../../util/logger';
11
18
 
19
+ // ============================================================================
20
+ // NAVIGATION STRATEGY API
21
+ // ============================================================================
22
+ //
23
+ // This module defines the ContentNavigator base class and the WeightedCard type,
24
+ // which form the foundation of the pluggable navigation strategy system.
25
+ //
26
+ // KEY CONCEPTS:
27
+ //
28
+ // 1. WeightedCard - A card with a suitability score (0-1) and provenance trail.
29
+ // The provenance tracks how each strategy in the pipeline contributed to
30
+ // the card's final score, ensuring transparency and debuggability.
31
+ //
32
+ // 2. ContentNavigator - Abstract base class for backward compatibility.
33
+ // New code should use CardGenerator or CardFilter interfaces directly.
34
+ //
35
+ // 3. CardGenerator vs CardFilter:
36
+ // - Generators (ELO, SRS, HardcodedOrder) produce candidate cards with scores
37
+ // - Filters (Hierarchy, Interference, Priority, EloDistance) transform scores
38
+ //
39
+ // 4. Pipeline architecture:
40
+ // Pipeline(generator, [filter1, filter2, ...]) executes:
41
+ // cards = generator.getWeightedCards()
42
+ // cards = filter1.transform(cards, context)
43
+ // cards = filter2.transform(cards, context)
44
+ // return sorted(cards)
45
+ //
46
+ // 5. Provenance tracking - Each strategy adds an entry explaining its contribution.
47
+ // This makes the system transparent and debuggable.
48
+ //
49
+ // ============================================================================
50
+
51
+ /**
52
+ * Tracks a single strategy's contribution to a card's final score.
53
+ *
54
+ * Each strategy in the pipeline adds a StrategyContribution entry to the
55
+ * card's provenance array, creating an audit trail of scoring decisions.
56
+ */
57
+ export interface StrategyContribution {
58
+ /**
59
+ * Strategy type (implementing class name).
60
+ * Examples: 'elo', 'hierarchyDefinition', 'interferenceMitigator'
61
+ */
62
+ strategy: string;
63
+
64
+ /**
65
+ * Human-readable name identifying this specific strategy instance.
66
+ * Extracted from ContentNavigationStrategyData.name.
67
+ * Courses may have multiple instances of the same strategy type with
68
+ * different configurations.
69
+ *
70
+ * Examples:
71
+ * - "ELO (default)"
72
+ * - "Interference: b/d/p confusion"
73
+ * - "Interference: phonetic confusables"
74
+ * - "Priority: Common letters first"
75
+ */
76
+ strategyName: string;
77
+
78
+ /**
79
+ * Unique database document ID for this strategy instance.
80
+ * Extracted from ContentNavigationStrategyData._id.
81
+ * Use this to fetch the full strategy configuration document.
82
+ *
83
+ * Examples:
84
+ * - "NAVIGATION_STRATEGY-ELO-default"
85
+ * - "NAVIGATION_STRATEGY-interference-bdp"
86
+ * - "NAVIGATION_STRATEGY-priority-common-letters"
87
+ */
88
+ strategyId: string;
89
+
90
+ /**
91
+ * What the strategy did:
92
+ * - 'generated': Strategy produced this card (generators only)
93
+ * - 'passed': Strategy evaluated but didn't change score (transparent pass-through)
94
+ * - 'boosted': Strategy increased the score
95
+ * - 'penalized': Strategy decreased the score
96
+ */
97
+ action: 'generated' | 'passed' | 'boosted' | 'penalized';
98
+
99
+ /** Score after this strategy's processing */
100
+ score: number;
101
+
102
+ /**
103
+ * Human-readable explanation of the strategy's decision.
104
+ *
105
+ * Examples:
106
+ * - "ELO distance 75, new card"
107
+ * - "Prerequisites met: letter-sounds"
108
+ * - "Interferes with immature tag 'd' (decay 0.8)"
109
+ * - "High-priority tag 's' (0.95) → boost 1.15x"
110
+ *
111
+ * Required for transparency - silent adjusters are anti-patterns.
112
+ */
113
+ reason: string;
114
+ }
115
+
116
+ /**
117
+ * A card with a suitability score and provenance trail.
118
+ *
119
+ * Scores range from 0-1:
120
+ * - 1.0 = fully suitable
121
+ * - 0.0 = hard filter (e.g., prerequisite not met)
122
+ * - 0.5 = neutral
123
+ * - Intermediate values = soft preference
124
+ *
125
+ * Provenance tracks the scoring pipeline:
126
+ * - First entry: Generator that produced the card
127
+ * - Subsequent entries: Filters that transformed the score
128
+ * - Each entry includes action and human-readable reason
129
+ */
130
+ export interface WeightedCard {
131
+ cardId: string;
132
+ courseId: string;
133
+ /** Suitability score from 0-1 */
134
+ score: number;
135
+ /**
136
+ * Audit trail of strategy contributions.
137
+ * First entry is from the generator, subsequent entries from filters.
138
+ */
139
+ provenance: StrategyContribution[];
140
+ }
141
+
142
+ /**
143
+ * Extract card origin from provenance trail.
144
+ *
145
+ * The first provenance entry (from the generator) indicates whether
146
+ * this is a new card, review, or failed card. We parse the reason
147
+ * string to extract this information.
148
+ *
149
+ * @param card - Card with provenance trail
150
+ * @returns Card origin ('new', 'review', or 'failed')
151
+ */
152
+ export function getCardOrigin(card: WeightedCard): 'new' | 'review' | 'failed' {
153
+ if (card.provenance.length === 0) {
154
+ throw new Error('Card has no provenance - cannot determine origin');
155
+ }
156
+
157
+ const firstEntry = card.provenance[0];
158
+ const reason = firstEntry.reason.toLowerCase();
159
+
160
+ if (reason.includes('failed')) {
161
+ return 'failed';
162
+ }
163
+ if (reason.includes('review')) {
164
+ return 'review';
165
+ }
166
+ return 'new';
167
+ }
168
+
12
169
  export enum Navigators {
13
170
  ELO = 'elo',
171
+ SRS = 'srs',
14
172
  HARDCODED = 'hardcodedOrder',
173
+ HIERARCHY = 'hierarchyDefinition',
174
+ INTERFERENCE = 'interferenceMitigator',
175
+ RELATIVE_PRIORITY = 'relativePriority',
176
+ }
177
+
178
+ // ============================================================================
179
+ // NAVIGATOR ROLE CLASSIFICATION
180
+ // ============================================================================
181
+ //
182
+ // Navigators are classified as either generators or filters:
183
+ // - Generators: Produce candidate cards (ELO, SRS, HardcodedOrder)
184
+ // - Filters: Transform/score candidates (Hierarchy, Interference, RelativePriority)
185
+ //
186
+ // This classification is used by PipelineAssembler to build pipelines:
187
+ // 1. Instantiate generators (possibly into a CompositeGenerator)
188
+ // 2. Instantiate filters
189
+ // 3. Create Pipeline(generator, filters)
190
+ //
191
+ // ============================================================================
192
+
193
+ /**
194
+ * Role classification for navigation strategies.
195
+ *
196
+ * - GENERATOR: Produces candidate cards with initial scores
197
+ * - FILTER: Transforms cards with score multipliers
198
+ */
199
+ export enum NavigatorRole {
200
+ GENERATOR = 'generator',
201
+ FILTER = 'filter',
202
+ }
203
+
204
+ /**
205
+ * Registry mapping navigator implementations to their roles.
206
+ */
207
+ export const NavigatorRoles: Record<Navigators, NavigatorRole> = {
208
+ [Navigators.ELO]: NavigatorRole.GENERATOR,
209
+ [Navigators.SRS]: NavigatorRole.GENERATOR,
210
+ [Navigators.HARDCODED]: NavigatorRole.GENERATOR,
211
+ [Navigators.HIERARCHY]: NavigatorRole.FILTER,
212
+ [Navigators.INTERFERENCE]: NavigatorRole.FILTER,
213
+ [Navigators.RELATIVE_PRIORITY]: NavigatorRole.FILTER,
214
+ };
215
+
216
+ /**
217
+ * Check if a navigator implementation is a generator.
218
+ *
219
+ * @param impl - Navigator implementation name (e.g., 'elo', 'hierarchyDefinition')
220
+ * @returns true if the navigator is a generator, false otherwise
221
+ */
222
+ export function isGenerator(impl: string): boolean {
223
+ return NavigatorRoles[impl as Navigators] === NavigatorRole.GENERATOR;
224
+ }
225
+
226
+ /**
227
+ * Check if a navigator implementation is a filter.
228
+ *
229
+ * @param impl - Navigator implementation name (e.g., 'elo', 'hierarchyDefinition')
230
+ * @returns true if the navigator is a filter, false otherwise
231
+ */
232
+ export function isFilter(impl: string): boolean {
233
+ return NavigatorRoles[impl as Navigators] === NavigatorRole.FILTER;
15
234
  }
16
235
 
17
236
  /**
18
- * A content-navigator provides runtime steering of study sessions.
237
+ * Abstract base class for navigation strategies.
238
+ *
239
+ * This class exists primarily for backward compatibility with legacy code.
240
+ * New code should use CardGenerator or CardFilter interfaces directly.
241
+ *
242
+ * The class implements StudyContentSource for compatibility with SessionController.
243
+ * Once SessionController migrates to use getWeightedCards() exclusively,
244
+ * the legacy methods can be removed.
19
245
  */
20
246
  export abstract class ContentNavigator implements StudyContentSource {
247
+ /** User interface for this navigation session */
248
+ protected user?: UserDBInterface;
249
+
250
+ /** Course interface for this navigation session */
251
+ protected course?: CourseDBInterface;
252
+
253
+ /** Human-readable name for this strategy instance (from ContentNavigationStrategyData.name) */
254
+ protected strategyName?: string;
255
+
256
+ /** Unique document ID for this strategy instance (from ContentNavigationStrategyData._id) */
257
+ protected strategyId?: string;
258
+
259
+ /**
260
+ * Constructor for standard navigators.
261
+ * Call this from subclass constructors to initialize common fields.
262
+ *
263
+ * Note: CompositeGenerator doesn't use this pattern and should call super() without args.
264
+ */
265
+ constructor(
266
+ user?: UserDBInterface,
267
+ course?: CourseDBInterface,
268
+ strategyData?: ContentNavigationStrategyData
269
+ ) {
270
+ if (user && course && strategyData) {
271
+ this.user = user;
272
+ this.course = course;
273
+ this.strategyName = strategyData.name;
274
+ this.strategyId = strategyData._id;
275
+ }
276
+ }
277
+
21
278
  /**
279
+ * Factory method to create navigator instances dynamically.
22
280
  *
23
- * @param user
24
- * @param strategyData
281
+ * @param user - User interface
282
+ * @param course - Course interface
283
+ * @param strategyData - Strategy configuration document
25
284
  * @returns the runtime object used to steer a study session.
26
285
  */
27
286
  static async create(
@@ -53,6 +312,89 @@ export abstract class ContentNavigator implements StudyContentSource {
53
312
  return new NavigatorImpl(user, course, strategyData);
54
313
  }
55
314
 
315
+ /**
316
+ * Get cards scheduled for review.
317
+ *
318
+ * @deprecated This method is part of the legacy StudyContentSource interface.
319
+ * New strategies should focus on implementing CardGenerator.getWeightedCards() instead.
320
+ */
56
321
  abstract getPendingReviews(): Promise<(StudySessionReviewItem & ScheduledCard)[]>;
322
+
323
+ /**
324
+ * Get new cards for introduction.
325
+ *
326
+ * @deprecated This method is part of the legacy StudyContentSource interface.
327
+ * New strategies should focus on implementing CardGenerator.getWeightedCards() instead.
328
+ *
329
+ * @param n - Maximum number of new cards to return
330
+ */
57
331
  abstract getNewCards(n?: number): Promise<StudySessionNewItem[]>;
332
+
333
+ /**
334
+ * Get cards with suitability scores and provenance trails.
335
+ *
336
+ * **This is the PRIMARY API for navigation strategies.**
337
+ *
338
+ * Returns cards ranked by suitability score (0-1). Higher scores indicate
339
+ * better candidates for presentation. Each card includes a provenance trail
340
+ * documenting how strategies contributed to the final score.
341
+ *
342
+ * ## For Generators
343
+ * Override this method to generate candidates and compute scores based on
344
+ * your strategy's logic (e.g., ELO proximity, review urgency). Create the
345
+ * initial provenance entry with action='generated'.
346
+ *
347
+ * ## Default Implementation
348
+ * The base class provides a backward-compatible default that:
349
+ * 1. Calls legacy getNewCards() and getPendingReviews()
350
+ * 2. Assigns score=1.0 to all cards
351
+ * 3. Creates minimal provenance from legacy methods
352
+ * 4. Returns combined results up to limit
353
+ *
354
+ * This allows existing strategies to work without modification while
355
+ * new strategies can override with proper scoring and provenance.
356
+ *
357
+ * @param limit - Maximum cards to return
358
+ * @returns Cards sorted by score descending, with provenance trails
359
+ */
360
+ async getWeightedCards(limit: number): Promise<WeightedCard[]> {
361
+ // Default implementation: delegate to legacy methods, assign score=1.0
362
+ const newCards = await this.getNewCards(limit);
363
+ const reviews = await this.getPendingReviews();
364
+
365
+ const weighted: WeightedCard[] = [
366
+ ...newCards.map((c) => ({
367
+ cardId: c.cardID,
368
+ courseId: c.courseID,
369
+ score: 1.0,
370
+ provenance: [
371
+ {
372
+ strategy: 'legacy',
373
+ strategyName: this.strategyName || 'Legacy API',
374
+ strategyId: this.strategyId || 'legacy-fallback',
375
+ action: 'generated' as const,
376
+ score: 1.0,
377
+ reason: 'Generated via legacy getNewCards(), new card',
378
+ },
379
+ ],
380
+ })),
381
+ ...reviews.map((r) => ({
382
+ cardId: r.cardID,
383
+ courseId: r.courseID,
384
+ score: 1.0,
385
+ provenance: [
386
+ {
387
+ strategy: 'legacy',
388
+ strategyName: this.strategyName || 'Legacy API',
389
+ strategyId: this.strategyId || 'legacy-fallback',
390
+ action: 'generated' as const,
391
+ score: 1.0,
392
+ reason: 'Generated via legacy getPendingReviews(), review',
393
+ },
394
+ ],
395
+ })),
396
+ ];
397
+
398
+ return weighted.slice(0, limit);
399
+ }
58
400
  }