@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.
- package/CLAUDE.md +2 -2
- package/dist/{classroomDB-BgfrVb8d.d.ts → contentSource-BP9hznNV.d.ts} +220 -197
- package/dist/{classroomDB-CTOenngH.d.cts → contentSource-DsJadoBU.d.cts} +220 -197
- package/dist/core/index.d.cts +80 -6
- package/dist/core/index.d.ts +80 -6
- package/dist/core/index.js +735 -1560
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.mjs +708 -1539
- package/dist/core/index.mjs.map +1 -1
- package/dist/{dataLayerProvider-D6PoCwS6.d.cts → dataLayerProvider-CHYrQ5pB.d.cts} +1 -1
- package/dist/{dataLayerProvider-CZxC9GtB.d.ts → dataLayerProvider-MDTxXq2l.d.ts} +1 -1
- package/dist/impl/couch/index.d.cts +8 -23
- package/dist/impl/couch/index.d.ts +8 -23
- package/dist/impl/couch/index.js +723 -1578
- package/dist/impl/couch/index.js.map +1 -1
- package/dist/impl/couch/index.mjs +692 -1552
- package/dist/impl/couch/index.mjs.map +1 -1
- package/dist/impl/static/index.d.cts +25 -8
- package/dist/impl/static/index.d.ts +25 -8
- package/dist/impl/static/index.js +700 -1400
- package/dist/impl/static/index.js.map +1 -1
- package/dist/impl/static/index.mjs +688 -1393
- package/dist/impl/static/index.mjs.map +1 -1
- package/dist/{index-D-Fa4Smt.d.cts → index-B_j6u5E4.d.cts} +1 -1
- package/dist/{index-CD8BZz2k.d.ts → index-Dj0SEgk3.d.ts} +1 -1
- package/dist/index.d.cts +71 -63
- package/dist/index.d.ts +71 -63
- package/dist/index.js +1162 -1996
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1124 -1955
- package/dist/index.mjs.map +1 -1
- package/dist/pouch/index.js +3 -0
- package/dist/pouch/index.js.map +1 -1
- package/dist/pouch/index.mjs +3 -0
- package/dist/pouch/index.mjs.map +1 -1
- package/dist/{types-CzPDLAK6.d.cts → types-Bn0itutr.d.cts} +1 -1
- package/dist/{types-CewsN87z.d.ts → types-DQaXnuoc.d.ts} +1 -1
- package/dist/{types-legacy-6ettoclI.d.cts → types-legacy-DDY4N-Uq.d.cts} +3 -1
- package/dist/{types-legacy-6ettoclI.d.ts → types-legacy-DDY4N-Uq.d.ts} +3 -1
- package/dist/util/packer/index.d.cts +3 -3
- package/dist/util/packer/index.d.ts +3 -3
- package/docs/navigators-architecture.md +115 -17
- package/package.json +4 -4
- package/src/core/index.ts +1 -0
- package/src/core/interfaces/classroomDB.ts +5 -13
- package/src/core/interfaces/contentSource.ts +6 -66
- package/src/core/interfaces/courseDB.ts +15 -7
- package/src/core/interfaces/userDB.ts +32 -0
- package/src/core/navigators/Pipeline.ts +136 -52
- package/src/core/navigators/PipelineAssembler.ts +1 -1
- package/src/core/navigators/defaults.ts +84 -0
- package/src/core/navigators/{hierarchyDefinition.ts → filters/hierarchyDefinition.ts} +15 -29
- package/src/core/navigators/filters/index.ts +3 -0
- package/src/core/navigators/filters/inferredPreferenceStub.ts +107 -0
- package/src/core/navigators/{interferenceMitigator.ts → filters/interferenceMitigator.ts} +11 -37
- package/src/core/navigators/{relativePriority.ts → filters/relativePriority.ts} +12 -38
- package/src/core/navigators/filters/userGoalStub.ts +136 -0
- package/src/core/navigators/filters/userTagPreference.ts +217 -0
- package/src/core/navigators/{CompositeGenerator.ts → generators/CompositeGenerator.ts} +15 -64
- package/src/core/navigators/{elo.ts → generators/elo.ts} +13 -63
- package/src/core/navigators/{srs.ts → generators/srs.ts} +11 -40
- package/src/core/navigators/generators/types.ts +1 -1
- package/src/core/navigators/index.ts +95 -91
- package/src/core/types/strategyState.ts +84 -0
- package/src/core/types/types-legacy.ts +2 -0
- package/src/impl/common/BaseUserDB.ts +74 -7
- package/src/impl/couch/adminDB.ts +1 -2
- package/src/impl/couch/classroomDB.ts +100 -103
- package/src/impl/couch/courseDB.ts +35 -91
- package/src/impl/couch/pouchdb-setup.ts +7 -0
- package/src/impl/static/StaticDataUnpacker.ts +50 -1
- package/src/impl/static/courseDB.ts +87 -37
- package/src/study/SessionController.ts +122 -202
- package/src/study/SourceMixer.ts +65 -0
- package/src/study/TagFilteredContentSource.ts +49 -92
- package/src/study/index.ts +1 -0
- package/src/study/services/CardHydrationService.ts +165 -81
- package/src/util/dataDirectory.ts +1 -1
- package/src/util/index.ts +0 -1
- package/tests/core/navigators/CompositeGenerator.test.ts +44 -168
- package/tests/core/navigators/Pipeline.test.ts +6 -72
- package/tests/core/navigators/PipelineAssembler.test.ts +8 -58
- package/tests/core/navigators/navigators.test.ts +118 -151
- package/docs/todo-pipeline-optimization.md +0 -117
- package/docs/todo-strategy-state-storage.md +0 -278
- package/src/core/navigators/hardcodedOrder.ts +0 -163
- package/src/util/tuiLogger.ts +0 -139
|
@@ -1,27 +1,23 @@
|
|
|
1
|
-
import { describe, it, expect
|
|
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
|
|
4
|
+
// Mock implementation of ContentNavigator for testing base class behavior
|
|
7
5
|
class MockNavigator extends ContentNavigator {
|
|
8
|
-
|
|
9
|
-
|
|
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
|
-
|
|
16
|
-
|
|
17
|
-
|
|
10
|
+
// Mock implementation that properly implements getWeightedCards
|
|
11
|
+
class ProperMockNavigator extends ContentNavigator {
|
|
12
|
+
name: string = 'ProperMockNavigator';
|
|
13
|
+
private cards: WeightedCard[] = [];
|
|
18
14
|
|
|
19
|
-
|
|
20
|
-
|
|
15
|
+
setCards(cards: WeightedCard[]) {
|
|
16
|
+
this.cards = cards;
|
|
21
17
|
}
|
|
22
18
|
|
|
23
|
-
async
|
|
24
|
-
return this.
|
|
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
|
|
81
|
-
|
|
76
|
+
describe('ContentNavigator base class', () => {
|
|
77
|
+
it('should throw error when getWeightedCards is not implemented', async () => {
|
|
78
|
+
const navigator = new MockNavigator();
|
|
82
79
|
|
|
83
|
-
|
|
84
|
-
|
|
80
|
+
await expect(navigator.getWeightedCards(10)).rejects.toThrow(
|
|
81
|
+
'must implement getWeightedCards()'
|
|
82
|
+
);
|
|
85
83
|
});
|
|
86
84
|
|
|
87
|
-
it('should
|
|
88
|
-
navigator
|
|
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(
|
|
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
|
-
|
|
139
|
-
|
|
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
|
-
|
|
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
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
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(
|
|
160
|
-
expect(result[0].
|
|
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
|
|
121
|
+
const navigator = new ProperMockNavigator();
|
|
122
|
+
navigator.setCards([
|
|
165
123
|
{
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
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
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
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
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
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
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
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
|
|
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(
|
|
243
|
-
expect(
|
|
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.
|