@vue-skuilder/db 0.1.18 → 0.1.21

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 (87) hide show
  1. package/CLAUDE.md +2 -2
  2. package/dist/{classroomDB-BgfrVb8d.d.ts → contentSource-BP9hznNV.d.ts} +220 -197
  3. package/dist/{classroomDB-CTOenngH.d.cts → contentSource-DsJadoBU.d.cts} +220 -197
  4. package/dist/core/index.d.cts +80 -6
  5. package/dist/core/index.d.ts +80 -6
  6. package/dist/core/index.js +735 -1560
  7. package/dist/core/index.js.map +1 -1
  8. package/dist/core/index.mjs +708 -1539
  9. package/dist/core/index.mjs.map +1 -1
  10. package/dist/{dataLayerProvider-D6PoCwS6.d.cts → dataLayerProvider-CHYrQ5pB.d.cts} +1 -1
  11. package/dist/{dataLayerProvider-CZxC9GtB.d.ts → dataLayerProvider-MDTxXq2l.d.ts} +1 -1
  12. package/dist/impl/couch/index.d.cts +8 -23
  13. package/dist/impl/couch/index.d.ts +8 -23
  14. package/dist/impl/couch/index.js +723 -1578
  15. package/dist/impl/couch/index.js.map +1 -1
  16. package/dist/impl/couch/index.mjs +692 -1552
  17. package/dist/impl/couch/index.mjs.map +1 -1
  18. package/dist/impl/static/index.d.cts +25 -8
  19. package/dist/impl/static/index.d.ts +25 -8
  20. package/dist/impl/static/index.js +700 -1400
  21. package/dist/impl/static/index.js.map +1 -1
  22. package/dist/impl/static/index.mjs +688 -1393
  23. package/dist/impl/static/index.mjs.map +1 -1
  24. package/dist/{index-D-Fa4Smt.d.cts → index-B_j6u5E4.d.cts} +1 -1
  25. package/dist/{index-CD8BZz2k.d.ts → index-Dj0SEgk3.d.ts} +1 -1
  26. package/dist/index.d.cts +71 -63
  27. package/dist/index.d.ts +71 -63
  28. package/dist/index.js +1162 -1996
  29. package/dist/index.js.map +1 -1
  30. package/dist/index.mjs +1124 -1955
  31. package/dist/index.mjs.map +1 -1
  32. package/dist/pouch/index.js +3 -0
  33. package/dist/pouch/index.js.map +1 -1
  34. package/dist/pouch/index.mjs +3 -0
  35. package/dist/pouch/index.mjs.map +1 -1
  36. package/dist/{types-CzPDLAK6.d.cts → types-Bn0itutr.d.cts} +1 -1
  37. package/dist/{types-CewsN87z.d.ts → types-DQaXnuoc.d.ts} +1 -1
  38. package/dist/{types-legacy-6ettoclI.d.cts → types-legacy-DDY4N-Uq.d.cts} +3 -1
  39. package/dist/{types-legacy-6ettoclI.d.ts → types-legacy-DDY4N-Uq.d.ts} +3 -1
  40. package/dist/util/packer/index.d.cts +3 -3
  41. package/dist/util/packer/index.d.ts +3 -3
  42. package/docs/navigators-architecture.md +115 -17
  43. package/package.json +4 -4
  44. package/src/core/index.ts +1 -0
  45. package/src/core/interfaces/classroomDB.ts +5 -13
  46. package/src/core/interfaces/contentSource.ts +6 -66
  47. package/src/core/interfaces/courseDB.ts +15 -7
  48. package/src/core/interfaces/userDB.ts +32 -0
  49. package/src/core/navigators/Pipeline.ts +136 -52
  50. package/src/core/navigators/PipelineAssembler.ts +1 -1
  51. package/src/core/navigators/defaults.ts +84 -0
  52. package/src/core/navigators/{hierarchyDefinition.ts → filters/hierarchyDefinition.ts} +15 -29
  53. package/src/core/navigators/filters/index.ts +3 -0
  54. package/src/core/navigators/filters/inferredPreferenceStub.ts +107 -0
  55. package/src/core/navigators/{interferenceMitigator.ts → filters/interferenceMitigator.ts} +11 -37
  56. package/src/core/navigators/{relativePriority.ts → filters/relativePriority.ts} +12 -38
  57. package/src/core/navigators/filters/userGoalStub.ts +136 -0
  58. package/src/core/navigators/filters/userTagPreference.ts +217 -0
  59. package/src/core/navigators/{CompositeGenerator.ts → generators/CompositeGenerator.ts} +15 -64
  60. package/src/core/navigators/{elo.ts → generators/elo.ts} +13 -63
  61. package/src/core/navigators/{srs.ts → generators/srs.ts} +11 -40
  62. package/src/core/navigators/generators/types.ts +1 -1
  63. package/src/core/navigators/index.ts +95 -91
  64. package/src/core/types/strategyState.ts +84 -0
  65. package/src/core/types/types-legacy.ts +2 -0
  66. package/src/impl/common/BaseUserDB.ts +74 -7
  67. package/src/impl/couch/adminDB.ts +1 -2
  68. package/src/impl/couch/classroomDB.ts +100 -103
  69. package/src/impl/couch/courseDB.ts +35 -91
  70. package/src/impl/couch/pouchdb-setup.ts +7 -0
  71. package/src/impl/static/StaticDataUnpacker.ts +50 -1
  72. package/src/impl/static/courseDB.ts +87 -37
  73. package/src/study/SessionController.ts +122 -202
  74. package/src/study/SourceMixer.ts +65 -0
  75. package/src/study/TagFilteredContentSource.ts +49 -92
  76. package/src/study/index.ts +1 -0
  77. package/src/study/services/CardHydrationService.ts +165 -81
  78. package/src/util/dataDirectory.ts +1 -1
  79. package/src/util/index.ts +0 -1
  80. package/tests/core/navigators/CompositeGenerator.test.ts +44 -168
  81. package/tests/core/navigators/Pipeline.test.ts +6 -72
  82. package/tests/core/navigators/PipelineAssembler.test.ts +8 -58
  83. package/tests/core/navigators/navigators.test.ts +118 -151
  84. package/docs/todo-pipeline-optimization.md +0 -117
  85. package/docs/todo-strategy-state-storage.md +0 -278
  86. package/src/core/navigators/hardcodedOrder.ts +0 -163
  87. package/src/util/tuiLogger.ts +0 -139
@@ -1,27 +1,23 @@
1
- import { describe, it, expect, beforeEach } from 'vitest';
1
+ import { describe, it, expect } from 'vitest';
2
2
  import { ContentNavigator, WeightedCard, getCardOrigin } from '../../../src/core/navigators/index';
3
- import { StudySessionNewItem, StudySessionReviewItem } from '../../../src/core';
4
- import { ScheduledCard } from '../../../src/core/types/user';
5
3
 
6
- // Mock implementation of ContentNavigator for testing the default getWeightedCards
4
+ // Mock implementation of ContentNavigator for testing base class behavior
7
5
  class MockNavigator extends ContentNavigator {
8
- private mockNewCards: StudySessionNewItem[] = [];
9
- private mockReviews: (StudySessionReviewItem & ScheduledCard)[] = [];
10
-
11
- setMockNewCards(cards: StudySessionNewItem[]) {
12
- this.mockNewCards = cards;
13
- }
6
+ name: string = 'MockNavigator';
7
+ // Base class no longer has legacy methods - subclasses must implement getWeightedCards
8
+ }
14
9
 
15
- setMockReviews(reviews: (StudySessionReviewItem & ScheduledCard)[]) {
16
- this.mockReviews = reviews;
17
- }
10
+ // Mock implementation that properly implements getWeightedCards
11
+ class ProperMockNavigator extends ContentNavigator {
12
+ name: string = 'ProperMockNavigator';
13
+ private cards: WeightedCard[] = [];
18
14
 
19
- async getNewCards(n?: number): Promise<StudySessionNewItem[]> {
20
- return this.mockNewCards.slice(0, n);
15
+ setCards(cards: WeightedCard[]) {
16
+ this.cards = cards;
21
17
  }
22
18
 
23
- async getPendingReviews(): Promise<(StudySessionReviewItem & ScheduledCard)[]> {
24
- return this.mockReviews;
19
+ async getWeightedCards(limit: number): Promise<WeightedCard[]> {
20
+ return this.cards.slice(0, limit);
25
21
  }
26
22
  }
27
23
 
@@ -77,111 +73,97 @@ describe('WeightedCard', () => {
77
73
  });
78
74
  });
79
75
 
80
- describe('ContentNavigator.getWeightedCards', () => {
81
- let navigator: MockNavigator;
76
+ describe('ContentNavigator base class', () => {
77
+ it('should throw error when getWeightedCards is not implemented', async () => {
78
+ const navigator = new MockNavigator();
82
79
 
83
- beforeEach(() => {
84
- navigator = new MockNavigator();
80
+ await expect(navigator.getWeightedCards(10)).rejects.toThrow(
81
+ 'must implement getWeightedCards()'
82
+ );
85
83
  });
86
84
 
87
- it('should return empty array when no cards available', async () => {
88
- navigator.setMockNewCards([]);
89
- navigator.setMockReviews([]);
90
-
91
- const result = await navigator.getWeightedCards(10);
85
+ it('should throw error mentioning legacy methods have been removed', async () => {
86
+ const navigator = new MockNavigator();
92
87
 
93
- expect(result).toEqual([]);
94
- });
95
-
96
- it('should assign score=1.0 to all cards by default', async () => {
97
- navigator.setMockNewCards([
98
- {
99
- cardID: 'card-1',
100
- courseID: 'course-1',
101
- contentSourceType: 'course',
102
- contentSourceID: 'course-1',
103
- status: 'new',
104
- },
105
- {
106
- cardID: 'card-2',
107
- courseID: 'course-1',
108
- contentSourceType: 'course',
109
- contentSourceID: 'course-1',
110
- status: 'new',
111
- },
112
- ]);
113
-
114
- const result = await navigator.getWeightedCards(10);
115
-
116
- expect(result).toHaveLength(2);
117
- expect(result[0].score).toBe(1.0);
118
- expect(result[1].score).toBe(1.0);
119
- });
120
-
121
- it('should mark new cards with source="new"', async () => {
122
- navigator.setMockNewCards([
123
- {
124
- cardID: 'card-1',
125
- courseID: 'course-1',
126
- contentSourceType: 'course',
127
- contentSourceID: 'course-1',
128
- status: 'new',
129
- },
130
- ]);
131
-
132
- const result = await navigator.getWeightedCards(10);
133
-
134
- expect(getCardOrigin(result[0])).toBe('new');
135
- expect(result[0].provenance[0].reason).toContain('new');
88
+ await expect(navigator.getWeightedCards(10)).rejects.toThrow();
136
89
  });
90
+ });
137
91
 
138
- it('should mark reviews with origin="review"', async () => {
139
- navigator.setMockReviews([
92
+ describe('ContentNavigator.getWeightedCards with proper implementation', () => {
93
+ it('should return cards from implementation', async () => {
94
+ const navigator = new ProperMockNavigator();
95
+ navigator.setCards([
140
96
  {
141
- cardID: 'review-1',
142
- courseID: 'course-1',
143
- contentSourceType: 'course',
144
- contentSourceID: 'course-1',
145
- status: 'review',
146
- reviewID: 'review-id-1',
147
- _id: 'scheduled-1',
148
- cardId: 'review-1',
97
+ cardId: 'card-1',
149
98
  courseId: 'course-1',
150
- scheduledFor: 'course',
151
- schedulingAgentId: 'agent-1',
152
- reviewTime: new Date(),
153
- scheduledAt: new Date(),
154
- } as unknown as StudySessionReviewItem & ScheduledCard,
99
+ score: 0.8,
100
+ provenance: [
101
+ {
102
+ strategy: 'test',
103
+ strategyName: 'Test',
104
+ strategyId: 'TEST',
105
+ action: 'generated',
106
+ score: 0.8,
107
+ reason: 'Test new card',
108
+ },
109
+ ],
110
+ },
155
111
  ]);
156
112
 
157
113
  const result = await navigator.getWeightedCards(10);
158
114
 
159
- expect(getCardOrigin(result[0])).toBe('review');
160
- expect(result[0].provenance[0].reason).toContain('review');
115
+ expect(result).toHaveLength(1);
116
+ expect(result[0].cardId).toBe('card-1');
117
+ expect(result[0].score).toBe(0.8);
161
118
  });
162
119
 
163
120
  it('should respect limit parameter', async () => {
164
- navigator.setMockNewCards([
121
+ const navigator = new ProperMockNavigator();
122
+ navigator.setCards([
165
123
  {
166
- cardID: 'card-1',
167
- courseID: 'course-1',
168
- contentSourceType: 'course',
169
- contentSourceID: 'course-1',
170
- status: 'new',
124
+ cardId: 'card-1',
125
+ courseId: 'course-1',
126
+ score: 0.9,
127
+ provenance: [
128
+ {
129
+ strategy: 'test',
130
+ strategyName: 'Test',
131
+ strategyId: 'TEST',
132
+ action: 'generated',
133
+ score: 0.9,
134
+ reason: 'Test new card',
135
+ },
136
+ ],
171
137
  },
172
138
  {
173
- cardID: 'card-2',
174
- courseID: 'course-1',
175
- contentSourceType: 'course',
176
- contentSourceID: 'course-1',
177
- status: 'new',
139
+ cardId: 'card-2',
140
+ courseId: 'course-1',
141
+ score: 0.8,
142
+ provenance: [
143
+ {
144
+ strategy: 'test',
145
+ strategyName: 'Test',
146
+ strategyId: 'TEST',
147
+ action: 'generated',
148
+ score: 0.8,
149
+ reason: 'Test new card',
150
+ },
151
+ ],
178
152
  },
179
153
  {
180
- cardID: 'card-3',
181
- courseID: 'course-1',
182
- contentSourceType: 'course',
183
- contentSourceID: 'course-1',
184
- status: 'new',
154
+ cardId: 'card-3',
155
+ courseId: 'course-1',
156
+ score: 0.7,
157
+ provenance: [
158
+ {
159
+ strategy: 'test',
160
+ strategyName: 'Test',
161
+ strategyId: 'TEST',
162
+ action: 'generated',
163
+ score: 0.7,
164
+ reason: 'Test new card',
165
+ },
166
+ ],
185
167
  },
186
168
  ]);
187
169
 
@@ -190,57 +172,42 @@ describe('ContentNavigator.getWeightedCards', () => {
190
172
  expect(result).toHaveLength(2);
191
173
  });
192
174
 
193
- it('should combine new cards and reviews', async () => {
194
- navigator.setMockNewCards([
195
- {
196
- cardID: 'new-1',
197
- courseID: 'course-1',
198
- contentSourceType: 'course',
199
- contentSourceID: 'course-1',
200
- status: 'new',
201
- },
202
- ]);
203
- navigator.setMockReviews([
204
- {
205
- cardID: 'review-1',
206
- courseID: 'course-1',
207
- contentSourceType: 'course',
208
- contentSourceID: 'course-1',
209
- status: 'review',
210
- reviewID: 'review-id-1',
211
- _id: 'scheduled-1',
212
- cardId: 'review-1',
213
- courseId: 'course-1',
214
- scheduledFor: 'course',
215
- schedulingAgentId: 'agent-1',
216
- reviewTime: new Date(),
217
- scheduledAt: new Date(),
218
- } as unknown as StudySessionReviewItem & ScheduledCard,
219
- ]);
220
-
221
- const result = await navigator.getWeightedCards(10);
222
-
223
- expect(result).toHaveLength(2);
224
- const origins = result.map((c) => getCardOrigin(c));
225
- expect(origins).toContain('new');
226
- expect(origins).toContain('review');
227
- });
228
-
229
- it('should correctly map cardID to cardId and courseID to courseId', async () => {
230
- navigator.setMockNewCards([
231
- {
232
- cardID: 'CARD-123',
233
- courseID: 'COURSE-456',
234
- contentSourceType: 'course',
235
- contentSourceID: 'COURSE-456',
236
- status: 'new',
237
- },
238
- ]);
175
+ it('should correctly identify card origins from provenance', () => {
176
+ const newCard: WeightedCard = {
177
+ cardId: 'new-1',
178
+ courseId: 'course-1',
179
+ score: 1.0,
180
+ provenance: [
181
+ {
182
+ strategy: 'test',
183
+ strategyName: 'Test',
184
+ strategyId: 'TEST',
185
+ action: 'generated',
186
+ score: 1.0,
187
+ reason: 'ELO distance 50, new card',
188
+ },
189
+ ],
190
+ };
239
191
 
240
- const result = await navigator.getWeightedCards(10);
192
+ const reviewCard: WeightedCard = {
193
+ cardId: 'review-1',
194
+ courseId: 'course-1',
195
+ score: 0.8,
196
+ reviewID: 'SCHEDULED_CARD-123',
197
+ provenance: [
198
+ {
199
+ strategy: 'srs',
200
+ strategyName: 'SRS',
201
+ strategyId: 'SRS',
202
+ action: 'generated',
203
+ score: 0.8,
204
+ reason: '48h overdue (interval: 72h), review',
205
+ },
206
+ ],
207
+ };
241
208
 
242
- expect(result[0].cardId).toBe('CARD-123');
243
- expect(result[0].courseId).toBe('COURSE-456');
209
+ expect(getCardOrigin(newCard)).toBe('new');
210
+ expect(getCardOrigin(reviewCard)).toBe('review');
244
211
  });
245
212
  });
246
213
 
@@ -1,117 +0,0 @@
1
- # TODO: Pipeline Optimization - Batch Tag Hydration
2
-
3
- ## Status: NOT STARTED
4
-
5
- ## Problem
6
-
7
- Each filter strategy independently queries for card tags, resulting in redundant database operations.
8
-
9
- For N cards through 3 filters = 3N tag lookups, when N would suffice.
10
-
11
- ```typescript
12
- // In HierarchyDefinitionNavigator
13
- const tagResponse = await context.course.getAppliedTags(card.cardId);
14
-
15
- // In InterferenceMitigatorNavigator
16
- const tagResponse = await context.course.getAppliedTags(card.cardId);
17
-
18
- // In RelativePriorityNavigator
19
- const tagResponse = await context.course.getAppliedTags(card.cardId);
20
- ```
21
-
22
- ## Proposed Solution: Hydrate Tags in WeightedCard
23
-
24
- Extend `WeightedCard` to optionally carry pre-fetched tag data:
25
-
26
- ```typescript
27
- interface WeightedCard {
28
- cardId: string;
29
- courseId: string;
30
- score: number;
31
- provenance: StrategyContribution[];
32
-
33
- /** Pre-fetched tags. If present, filters should use this instead of querying. */
34
- tags?: string[];
35
- }
36
- ```
37
-
38
- ### Implementation Steps
39
-
40
- #### Step 1: Add batch tag lookup method
41
-
42
- ```typescript
43
- // In CourseDBInterface
44
- getAppliedTagsBatch(cardIds: string[]): Promise<Map<string, string[]>>;
45
- ```
46
-
47
- #### Step 2: Update WeightedCard type
48
-
49
- Add optional `tags?: string[]` field to `WeightedCard` in `core/navigators/index.ts`.
50
-
51
- #### Step 3: Hydrate in Pipeline
52
-
53
- The `Pipeline` class should batch-fetch tags after getting candidates from the generator:
54
-
55
- ```typescript
56
- async getWeightedCards(limit: number): Promise<WeightedCard[]> {
57
- const context = await this.buildContext();
58
- let cards = await this.generator.getWeightedCards(fetchLimit, context);
59
-
60
- // Batch hydrate tags
61
- cards = await this.hydrateTags(cards);
62
-
63
- for (const filter of this.filters) {
64
- cards = await filter.transform(cards, context);
65
- }
66
-
67
- return cards.filter(c => c.score > 0)
68
- .sort((a, b) => b.score - a.score)
69
- .slice(0, limit);
70
- }
71
-
72
- private async hydrateTags(cards: WeightedCard[]): Promise<WeightedCard[]> {
73
- const cardIds = cards.map(c => c.cardId);
74
- const tagsByCard = await this.course.getAppliedTagsBatch(cardIds);
75
-
76
- return cards.map(c => ({
77
- ...c,
78
- tags: tagsByCard.get(c.cardId) ?? []
79
- }));
80
- }
81
- ```
82
-
83
- #### Step 4: Update filter strategies
84
-
85
- Each filter checks for pre-hydrated tags before querying:
86
-
87
- ```typescript
88
- const cardTags = card.tags ?? await this.getCardTags(card.cardId, context.course);
89
- ```
90
-
91
- #### Step 5: Add tests
92
-
93
- - Verify tags are populated by Pipeline
94
- - Verify filters use pre-fetched tags when available
95
- - Verify fallback works if tags missing
96
-
97
- ## Files to Modify
98
-
99
- | File | Change |
100
- |------|--------|
101
- | `core/navigators/index.ts` | Add `tags?` to `WeightedCard` |
102
- | `core/interfaces/courseDB.ts` | Add `getAppliedTagsBatch()` |
103
- | `impl/couch/courseDB.ts` | Implement `getAppliedTagsBatch()` |
104
- | `impl/static/courseDB.ts` | Implement `getAppliedTagsBatch()` |
105
- | `core/navigators/Pipeline.ts` | Add `hydrateTags()` step |
106
- | `core/navigators/hierarchyDefinition.ts` | Use `card.tags` if available |
107
- | `core/navigators/interferenceMitigator.ts` | Use `card.tags` if available |
108
- | `core/navigators/relativePriority.ts` | Use `card.tags` if available |
109
-
110
- ## Performance Expectations
111
-
112
- | Scenario | Before | After |
113
- |----------|--------|-------|
114
- | 20 cards, 3 filters | 60 tag queries | 1 batch query (20 cards) |
115
- | 50 cards, 4 filters | 200 tag queries | 1 batch query (50 cards) |
116
-
117
- Batch queries also reduce round-trip overhead compared to individual queries.