@vue-skuilder/db 0.1.17 → 0.1.20
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/{userDB-BqwxtJ_7.d.mts → classroomDB-CZdMBiTU.d.ts} +427 -104
- package/dist/{userDB-DNa0XPtn.d.ts → classroomDB-PxDZTky3.d.cts} +427 -104
- package/dist/core/index.d.cts +304 -0
- package/dist/core/index.d.ts +237 -25
- package/dist/core/index.js +2246 -118
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.mjs +2235 -114
- package/dist/core/index.mjs.map +1 -1
- package/dist/{dataLayerProvider-VlngD19_.d.mts → dataLayerProvider-D0MoZMjH.d.cts} +1 -1
- package/dist/{dataLayerProvider-BV5iZqt_.d.ts → dataLayerProvider-D8o6ZnKW.d.ts} +1 -1
- package/dist/impl/couch/{index.d.mts → index.d.cts} +47 -5
- package/dist/impl/couch/index.d.ts +46 -4
- package/dist/impl/couch/index.js +2250 -134
- package/dist/impl/couch/index.js.map +1 -1
- package/dist/impl/couch/index.mjs +2212 -97
- package/dist/impl/couch/index.mjs.map +1 -1
- package/dist/impl/static/{index.d.mts → index.d.cts} +6 -6
- package/dist/impl/static/index.d.ts +5 -5
- package/dist/impl/static/index.js +1950 -143
- package/dist/impl/static/index.js.map +1 -1
- package/dist/impl/static/index.mjs +1922 -117
- package/dist/impl/static/index.mjs.map +1 -1
- package/dist/{index-Bmll7Xse.d.mts → index-B_j6u5E4.d.cts} +1 -1
- package/dist/{index-CD8BZz2k.d.ts → index-Dj0SEgk3.d.ts} +1 -1
- package/dist/{index.d.mts → index.d.cts} +97 -13
- package/dist/index.d.ts +96 -12
- package/dist/index.js +2439 -180
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2386 -135
- package/dist/index.mjs.map +1 -1
- package/dist/pouch/index.js +3 -3
- package/dist/{types-Dbp5DaRR.d.mts → types-Bn0itutr.d.cts} +1 -1
- package/dist/{types-CewsN87z.d.ts → types-DQaXnuoc.d.ts} +1 -1
- package/dist/{types-legacy-6ettoclI.d.ts → types-legacy-DDY4N-Uq.d.cts} +3 -1
- package/dist/{types-legacy-6ettoclI.d.mts → types-legacy-DDY4N-Uq.d.ts} +3 -1
- package/dist/util/packer/{index.d.mts → index.d.cts} +3 -3
- package/dist/util/packer/index.d.ts +3 -3
- package/dist/util/packer/index.js.map +1 -1
- package/dist/util/packer/index.mjs.map +1 -1
- package/docs/brainstorm-navigation-paradigm.md +369 -0
- package/docs/navigators-architecture.md +370 -0
- package/docs/todo-evolutionary-orchestration.md +310 -0
- package/docs/todo-nominal-tag-types.md +121 -0
- package/docs/todo-strategy-authoring.md +401 -0
- package/eslint.config.mjs +1 -1
- package/package.json +9 -4
- package/src/core/index.ts +1 -0
- package/src/core/interfaces/contentSource.ts +88 -4
- package/src/core/interfaces/courseDB.ts +13 -0
- package/src/core/interfaces/navigationStrategyManager.ts +0 -5
- package/src/core/interfaces/userDB.ts +32 -0
- package/src/core/navigators/CompositeGenerator.ts +268 -0
- package/src/core/navigators/Pipeline.ts +318 -0
- package/src/core/navigators/PipelineAssembler.ts +194 -0
- package/src/core/navigators/elo.ts +104 -15
- package/src/core/navigators/filters/eloDistance.ts +132 -0
- package/src/core/navigators/filters/index.ts +9 -0
- package/src/core/navigators/filters/types.ts +115 -0
- package/src/core/navigators/filters/userTagPreference.ts +232 -0
- package/src/core/navigators/generators/index.ts +2 -0
- package/src/core/navigators/generators/types.ts +107 -0
- package/src/core/navigators/hardcodedOrder.ts +111 -12
- package/src/core/navigators/hierarchyDefinition.ts +266 -0
- package/src/core/navigators/index.ts +404 -3
- package/src/core/navigators/inferredPreference.ts +107 -0
- package/src/core/navigators/interferenceMitigator.ts +355 -0
- package/src/core/navigators/relativePriority.ts +255 -0
- package/src/core/navigators/srs.ts +195 -0
- package/src/core/navigators/userGoal.ts +136 -0
- 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 +51 -0
- package/src/impl/couch/courseDB.ts +147 -49
- package/src/impl/static/courseDB.ts +11 -4
- package/src/study/SessionController.ts +149 -1
- package/src/study/TagFilteredContentSource.ts +255 -0
- package/src/study/index.ts +1 -0
- package/src/util/dataDirectory.test.ts +51 -22
- package/src/util/logger.ts +0 -1
- package/tests/core/navigators/CompositeGenerator.test.ts +455 -0
- package/tests/core/navigators/Pipeline.test.ts +406 -0
- package/tests/core/navigators/PipelineAssembler.test.ts +351 -0
- package/tests/core/navigators/SRSNavigator.test.ts +344 -0
- package/tests/core/navigators/eloDistanceFilter.test.ts +192 -0
- package/tests/core/navigators/navigators.test.ts +710 -0
- package/tsconfig.json +1 -1
- package/vitest.config.ts +29 -0
- package/dist/core/index.d.mts +0 -92
- /package/dist/{SyncStrategy-CyATpyLQ.d.mts → SyncStrategy-CyATpyLQ.d.cts} +0 -0
- /package/dist/pouch/{index.d.mts → index.d.cts} +0 -0
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
# Navigation Strategy Architecture
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
The navigation strategy system selects and scores cards for study sessions. It uses a
|
|
6
|
+
**Pipeline architecture** where generators produce candidates and filters transform scores.
|
|
7
|
+
|
|
8
|
+
## Core Concepts
|
|
9
|
+
|
|
10
|
+
### WeightedCard
|
|
11
|
+
|
|
12
|
+
A card with a suitability score, audit trail, and pre-fetched data:
|
|
13
|
+
|
|
14
|
+
```typescript
|
|
15
|
+
interface WeightedCard {
|
|
16
|
+
cardId: string;
|
|
17
|
+
courseId: string;
|
|
18
|
+
score: number; // 0-1 suitability score
|
|
19
|
+
provenance: StrategyContribution[]; // Audit trail
|
|
20
|
+
tags?: string[]; // Pre-fetched tags (hydrated by Pipeline)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
interface StrategyContribution {
|
|
24
|
+
strategy: string; // Type: 'elo', 'srs', 'hierarchyDefinition'
|
|
25
|
+
strategyName: string; // Human-readable: "ELO (default)"
|
|
26
|
+
strategyId: string; // Document ID: 'NAVIGATION_STRATEGY-ELO-default'
|
|
27
|
+
action: 'generated' | 'passed' | 'boosted' | 'penalized';
|
|
28
|
+
score: number; // Score after this strategy
|
|
29
|
+
reason: string; // Human-readable explanation
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### CardGenerator
|
|
34
|
+
|
|
35
|
+
Produces candidate cards with initial scores:
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
interface CardGenerator {
|
|
39
|
+
name: string;
|
|
40
|
+
getWeightedCards(limit: number, context: GeneratorContext): Promise<WeightedCard[]>;
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
**Implementations:**
|
|
45
|
+
- `ELONavigator` — New cards scored by ELO proximity to user skill
|
|
46
|
+
- `SRSNavigator` — Review cards scored by overdueness and interval recency
|
|
47
|
+
- `HardcodedOrderNavigator` — Fixed sequence defined by course author
|
|
48
|
+
- `CompositeGenerator` — Merges multiple generators with frequency boost
|
|
49
|
+
|
|
50
|
+
### CardFilter
|
|
51
|
+
|
|
52
|
+
Transforms card scores (pure function, no side effects):
|
|
53
|
+
|
|
54
|
+
```typescript
|
|
55
|
+
interface CardFilter {
|
|
56
|
+
name: string;
|
|
57
|
+
transform(cards: WeightedCard[], context: FilterContext): Promise<WeightedCard[]>;
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Filters receive cards with pre-hydrated data (e.g., `card.tags`) from Pipeline, eliminating
|
|
62
|
+
redundant database queries.
|
|
63
|
+
|
|
64
|
+
**Implementations:**
|
|
65
|
+
- `HierarchyDefinitionNavigator` — Gates cards by prerequisite mastery (score=0 if locked)
|
|
66
|
+
- `InterferenceMitigatorNavigator` — Reduces scores for confusable content
|
|
67
|
+
- `RelativePriorityNavigator` — Boosts scores for high-utility content
|
|
68
|
+
- `UserTagPreferenceFilter` — Applies user-configured tag preferences (path constraints)
|
|
69
|
+
- `createEloDistanceFilter()` — Penalizes cards far from user's current ELO
|
|
70
|
+
|
|
71
|
+
### Pipeline
|
|
72
|
+
|
|
73
|
+
Orchestrates generator, data hydration, and filters:
|
|
74
|
+
|
|
75
|
+
```typescript
|
|
76
|
+
class Pipeline {
|
|
77
|
+
constructor(
|
|
78
|
+
generator: CardGenerator,
|
|
79
|
+
filters: CardFilter[],
|
|
80
|
+
user: UserDBInterface,
|
|
81
|
+
course: CourseDBInterface
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
async getWeightedCards(limit: number): Promise<WeightedCard[]> {
|
|
85
|
+
// Build shared context (user ELO, etc.)
|
|
86
|
+
const context = await this.buildContext();
|
|
87
|
+
|
|
88
|
+
// Generate candidates
|
|
89
|
+
let cards = await this.generator.getWeightedCards(fetchLimit, context);
|
|
90
|
+
|
|
91
|
+
// Hydrate shared data (tags, etc.) in single batch query
|
|
92
|
+
cards = await this.hydrateTags(cards);
|
|
93
|
+
|
|
94
|
+
// Apply filters sequentially
|
|
95
|
+
for (const filter of this.filters) {
|
|
96
|
+
cards = await filter.transform(cards, context);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return cards.filter(c => c.score > 0)
|
|
100
|
+
.sort((a, b) => b.score - a.score)
|
|
101
|
+
.slice(0, limit);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
**Responsibilities:**
|
|
107
|
+
- **Context building** — Fetches shared data (user ELO) once for all strategies
|
|
108
|
+
- **Data hydration** — Pre-fetches commonly needed data (tags) in batch queries
|
|
109
|
+
- **Filter orchestration** — Applies filters in sequence, accumulating provenance
|
|
110
|
+
- **Result selection** — Removes zero-scores, sorts, and returns top N
|
|
111
|
+
|
|
112
|
+
## Pipeline Assembly
|
|
113
|
+
|
|
114
|
+
`PipelineAssembler` builds pipelines from strategy documents:
|
|
115
|
+
|
|
116
|
+
```typescript
|
|
117
|
+
const assembler = new PipelineAssembler();
|
|
118
|
+
const { pipeline, warnings } = await assembler.assemble({
|
|
119
|
+
strategies: allStrategies,
|
|
120
|
+
user,
|
|
121
|
+
course,
|
|
122
|
+
});
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Assembly logic:
|
|
126
|
+
1. Separate strategies into generators and filters by `NavigatorRole`
|
|
127
|
+
2. Instantiate generators — wrap multiple in `CompositeGenerator`
|
|
128
|
+
3. Instantiate filters — sorted alphabetically for determinism
|
|
129
|
+
4. Return `Pipeline(generator, filters)`
|
|
130
|
+
|
|
131
|
+
If no strategies are configured, `courseDB.createNavigator()` returns a default pipeline:
|
|
132
|
+
```typescript
|
|
133
|
+
Pipeline(
|
|
134
|
+
CompositeGenerator([ELONavigator, SRSNavigator]),
|
|
135
|
+
[eloDistanceFilter]
|
|
136
|
+
)
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## Score Semantics
|
|
140
|
+
|
|
141
|
+
| Score | Meaning |
|
|
142
|
+
|-------|---------|
|
|
143
|
+
| 1.0 | Fully suitable |
|
|
144
|
+
| 0.5 | Neutral |
|
|
145
|
+
| 0.0 | Exclude (hard filter) |
|
|
146
|
+
| 0.x | Proportional suitability |
|
|
147
|
+
|
|
148
|
+
**All filters are multipliers.** This means:
|
|
149
|
+
- Filter order doesn't affect final scores (multiplication is commutative)
|
|
150
|
+
- Score 0 from any filter excludes the card
|
|
151
|
+
- Filters are applied alphabetically for determinism
|
|
152
|
+
|
|
153
|
+
## Provenance Tracking
|
|
154
|
+
|
|
155
|
+
Each card's provenance shows how it was scored:
|
|
156
|
+
|
|
157
|
+
```typescript
|
|
158
|
+
provenance: [
|
|
159
|
+
{
|
|
160
|
+
strategy: 'elo',
|
|
161
|
+
strategyName: 'ELO (default)',
|
|
162
|
+
strategyId: 'NAVIGATION_STRATEGY-ELO-default',
|
|
163
|
+
action: 'generated',
|
|
164
|
+
score: 0.85,
|
|
165
|
+
reason: 'ELO distance 75 (card: 1025, user: 1100), new card'
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
strategy: 'hierarchyDefinition',
|
|
169
|
+
strategyName: 'Hierarchy: Phonics Basics',
|
|
170
|
+
strategyId: 'NAVIGATION_STRATEGY-hierarchy-phonics',
|
|
171
|
+
action: 'passed',
|
|
172
|
+
score: 0.85,
|
|
173
|
+
reason: 'Prerequisites met, tags: letter-sounds'
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
strategy: 'eloDistance',
|
|
177
|
+
strategyName: 'ELO Distance Filter',
|
|
178
|
+
strategyId: 'ELO_DISTANCE_FILTER',
|
|
179
|
+
action: 'penalized',
|
|
180
|
+
score: 0.72,
|
|
181
|
+
reason: 'ELO distance 150 (card: 1150, user: 1000) → 0.85x'
|
|
182
|
+
}
|
|
183
|
+
]
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
Use `getCardOrigin(card)` to extract 'new', 'review', or 'failed' from provenance.
|
|
187
|
+
|
|
188
|
+
## Creating New Strategies
|
|
189
|
+
|
|
190
|
+
### Generator
|
|
191
|
+
|
|
192
|
+
```typescript
|
|
193
|
+
class MyGenerator extends ContentNavigator implements CardGenerator {
|
|
194
|
+
name = 'My Generator';
|
|
195
|
+
|
|
196
|
+
async getWeightedCards(limit: number, context?: GeneratorContext): Promise<WeightedCard[]> {
|
|
197
|
+
const candidates = await this.findCandidates(limit);
|
|
198
|
+
|
|
199
|
+
return candidates.map(c => ({
|
|
200
|
+
cardId: c.id,
|
|
201
|
+
courseId: this.course.getCourseID(),
|
|
202
|
+
score: this.computeScore(c),
|
|
203
|
+
provenance: [{
|
|
204
|
+
strategy: 'myGenerator',
|
|
205
|
+
strategyName: this.name,
|
|
206
|
+
strategyId: this.strategyId || 'MY_GENERATOR',
|
|
207
|
+
action: 'generated',
|
|
208
|
+
score: this.computeScore(c),
|
|
209
|
+
reason: 'Explanation here, new card'
|
|
210
|
+
}]
|
|
211
|
+
}));
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Legacy methods - stub or implement for backward compat
|
|
215
|
+
async getNewCards() { return []; }
|
|
216
|
+
async getPendingReviews() { return []; }
|
|
217
|
+
}
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
Register in `NavigatorRoles` as `NavigatorRole.GENERATOR`.
|
|
221
|
+
|
|
222
|
+
### Filter
|
|
223
|
+
|
|
224
|
+
```typescript
|
|
225
|
+
class MyFilter extends ContentNavigator implements CardFilter {
|
|
226
|
+
name = 'My Filter';
|
|
227
|
+
|
|
228
|
+
async transform(cards: WeightedCard[], context: FilterContext): Promise<WeightedCard[]> {
|
|
229
|
+
return cards.map(card => {
|
|
230
|
+
const multiplier = this.computeMultiplier(card, context);
|
|
231
|
+
const newScore = card.score * multiplier;
|
|
232
|
+
const action = multiplier < 1 ? 'penalized' : multiplier > 1 ? 'boosted' : 'passed';
|
|
233
|
+
|
|
234
|
+
return {
|
|
235
|
+
...card,
|
|
236
|
+
score: newScore,
|
|
237
|
+
provenance: [...card.provenance, {
|
|
238
|
+
strategy: 'myFilter',
|
|
239
|
+
strategyName: this.name,
|
|
240
|
+
strategyId: this.strategyId || 'MY_FILTER',
|
|
241
|
+
action,
|
|
242
|
+
score: newScore,
|
|
243
|
+
reason: 'Explanation here'
|
|
244
|
+
}]
|
|
245
|
+
};
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Legacy methods - filters don't generate cards
|
|
250
|
+
async getWeightedCards() { throw new Error('Use transform() via Pipeline'); }
|
|
251
|
+
async getNewCards() { return []; }
|
|
252
|
+
async getPendingReviews() { return []; }
|
|
253
|
+
}
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
Register in `NavigatorRoles` as `NavigatorRole.FILTER`.
|
|
257
|
+
|
|
258
|
+
## Strategy State Storage
|
|
259
|
+
|
|
260
|
+
Strategies can persist user-scoped state (preferences, learned patterns, temporal tracking)
|
|
261
|
+
using the `STRATEGY_STATE` document type in the user database.
|
|
262
|
+
|
|
263
|
+
### Goals vs Preferences vs Inferred
|
|
264
|
+
|
|
265
|
+
The system distinguishes three types of user-scoped navigation data:
|
|
266
|
+
|
|
267
|
+
| Type | Defines | Example | Affects ELO | Implementation |
|
|
268
|
+
|------|---------|---------|-------------|----------------|
|
|
269
|
+
| **Goal** | Destination (what to learn) | "Master ear-training" | Yes | `userGoal.ts` (stub) |
|
|
270
|
+
| **Preference** | Path (how to learn) | "Skip text-heavy cards" | No | `filters/userTagPreference.ts` |
|
|
271
|
+
| **Inferred** | Learned patterns | "User prefers visual" | No | `inferredPreference.ts` (stub) |
|
|
272
|
+
|
|
273
|
+
- **Goals** redefine the optimization target — they scope which content matters for progress
|
|
274
|
+
- **Preferences** constrain the path — they affect card selection without changing progress tracking
|
|
275
|
+
- **Inferred** preferences are learned from behavior — they act as soft suggestions
|
|
276
|
+
|
|
277
|
+
See stub files for detailed architectural intent on goals and inferred preferences.
|
|
278
|
+
|
|
279
|
+
### Storage API
|
|
280
|
+
|
|
281
|
+
`ContentNavigator` provides protected helper methods:
|
|
282
|
+
|
|
283
|
+
```typescript
|
|
284
|
+
// Get this strategy's persisted state for the current course
|
|
285
|
+
protected async getStrategyState<T>(): Promise<T | null>
|
|
286
|
+
|
|
287
|
+
// Persist this strategy's state for the current course
|
|
288
|
+
protected async putStrategyState<T>(data: T): Promise<void>
|
|
289
|
+
|
|
290
|
+
// Override to customize the storage key (default: constructor name)
|
|
291
|
+
protected get strategyKey(): string
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
### Document Format
|
|
295
|
+
|
|
296
|
+
```typescript
|
|
297
|
+
interface StrategyStateDoc<T> {
|
|
298
|
+
_id: StrategyStateId; // "STRATEGY_STATE::{courseId}::{strategyKey}"
|
|
299
|
+
docType: DocType.STRATEGY_STATE;
|
|
300
|
+
courseId: string;
|
|
301
|
+
strategyKey: string;
|
|
302
|
+
data: T; // Strategy-specific payload
|
|
303
|
+
updatedAt: string; // ISO timestamp
|
|
304
|
+
}
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
### Example: User Tag Preferences
|
|
308
|
+
|
|
309
|
+
`UserTagPreferenceFilter` reads user preferences from strategy state:
|
|
310
|
+
|
|
311
|
+
```typescript
|
|
312
|
+
interface UserTagPreferenceState {
|
|
313
|
+
/**
|
|
314
|
+
* Tag-specific multipliers.
|
|
315
|
+
* - 0 = exclude (card score = 0)
|
|
316
|
+
* - 0.5 = penalize by 50%
|
|
317
|
+
* - 1.0 = neutral/no effect
|
|
318
|
+
* - 2.0 = 2x preference boost
|
|
319
|
+
* - Higher = stronger preference
|
|
320
|
+
*/
|
|
321
|
+
boost: Record<string, number>;
|
|
322
|
+
updatedAt: string;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// In filter's transform():
|
|
326
|
+
const prefs = await this.getStrategyState<UserTagPreferenceState>();
|
|
327
|
+
if (!prefs || Object.keys(prefs.boost).length === 0) {
|
|
328
|
+
return cards; // No preferences configured
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// Apply multipliers (max wins when multiple tags match)
|
|
332
|
+
const multiplier = computeMultiplier(cardTags, prefs.boost);
|
|
333
|
+
return { ...card, score: card.score * multiplier };
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
**UI Component**: `packages/common-ui/src/components/UserTagPreferences.vue`
|
|
337
|
+
- Slider-based interface (0-2 default range, expandable to 10)
|
|
338
|
+
- All sliders share global max for consistent visual comparison
|
|
339
|
+
- Writes to strategy state via `userDB.putStrategyState()`
|
|
340
|
+
|
|
341
|
+
## File Reference
|
|
342
|
+
|
|
343
|
+
| File | Purpose |
|
|
344
|
+
|------|---------|
|
|
345
|
+
| `core/navigators/index.ts` | `ContentNavigator`, `WeightedCard`, `NavigatorRole` |
|
|
346
|
+
| `core/navigators/generators/types.ts` | `CardGenerator`, `GeneratorContext` |
|
|
347
|
+
| `core/navigators/filters/types.ts` | `CardFilter`, `FilterContext` |
|
|
348
|
+
| `core/navigators/Pipeline.ts` | Pipeline orchestration |
|
|
349
|
+
| `core/navigators/PipelineAssembler.ts` | Builds Pipeline from strategy docs |
|
|
350
|
+
| `core/navigators/CompositeGenerator.ts` | Merges multiple generators |
|
|
351
|
+
| `core/navigators/elo.ts` | ELO generator |
|
|
352
|
+
| `core/navigators/srs.ts` | SRS generator |
|
|
353
|
+
| `core/navigators/hardcodedOrder.ts` | Fixed-order generator |
|
|
354
|
+
| `core/navigators/hierarchyDefinition.ts` | Prerequisite filter |
|
|
355
|
+
| `core/navigators/interferenceMitigator.ts` | Interference filter |
|
|
356
|
+
| `core/navigators/relativePriority.ts` | Priority filter |
|
|
357
|
+
| `core/navigators/filters/eloDistance.ts` | ELO distance filter |
|
|
358
|
+
| `core/navigators/filters/userTagPreference.ts` | User tag preference filter |
|
|
359
|
+
| `common-ui/.../UserTagPreferences.vue` | UI for tag preference sliders |
|
|
360
|
+
| `core/navigators/userGoal.ts` | User goal navigator (stub) |
|
|
361
|
+
| `core/navigators/inferredPreference.ts` | Inferred preference navigator (stub) |
|
|
362
|
+
| `core/types/strategyState.ts` | `StrategyStateDoc`, `StrategyStateId` |
|
|
363
|
+
| `impl/couch/courseDB.ts` | `createNavigator()` entry point |
|
|
364
|
+
|
|
365
|
+
## Related Documentation
|
|
366
|
+
|
|
367
|
+
- `todo-pipeline-optimization.md` — Batch tag hydration implementation (✅ completed)
|
|
368
|
+
- `todo-strategy-authoring.md` — UX and DX for authoring strategies
|
|
369
|
+
- `todo-evolutionary-orchestration.md` — Long-term adaptive strategy vision
|
|
370
|
+
- `devlog/1004` — Implementation details for tag hydration optimization
|
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
# TODO: Evolutionary Orchestration Vision
|
|
2
|
+
|
|
3
|
+
## Status: FOUNDATIONAL WORK COMPLETE, ORCHESTRATION NOT STARTED
|
|
4
|
+
|
|
5
|
+
> **Prerequisite:** `packages/db/docs/todo-naive-orchestration.md` — Pipeline assembly must be
|
|
6
|
+
> working before evolutionary selection can be layered on top. This document describes the
|
|
7
|
+
> "grander vision"; naive orchestration is the foundation.
|
|
8
|
+
|
|
9
|
+
This document tracks progress toward the "grander vision" of dynamic orchestration with
|
|
10
|
+
evolutionary pressures, as outlined in `u.3.strategic-nuggets.md`.
|
|
11
|
+
|
|
12
|
+
## The Vision (Summary)
|
|
13
|
+
|
|
14
|
+
A self-improving courseware system where:
|
|
15
|
+
|
|
16
|
+
1. **N strategies exist** — Each a hypothesis about effective content sequencing
|
|
17
|
+
2. **M users exist** — Each with learning goals and measurable outcomes
|
|
18
|
+
3. **Multi-arm bandit selection** — Strategies are applied based on confidence in their utility
|
|
19
|
+
4. **Evolutionary pressure** — Effective strategies propagate, ineffective ones decay
|
|
20
|
+
5. **Self-healing content** — System identifies barriers and incentivizes remediation
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Progress Assessment
|
|
25
|
+
|
|
26
|
+
### ✅ COMPLETE: Strategy Infrastructure
|
|
27
|
+
|
|
28
|
+
| Component | Status | Notes |
|
|
29
|
+
|-----------|--------|-------|
|
|
30
|
+
| `WeightedCard` API | ✅ Done | Unified scored candidate model |
|
|
31
|
+
| `ContentNavigator` base class | ✅ Done | Extensible strategy framework |
|
|
32
|
+
| Delegate pattern composition | ✅ Done | Strategies can wrap/chain |
|
|
33
|
+
| `HierarchyDefinition` strategy | ✅ Done | Prerequisite gating |
|
|
34
|
+
| `InterferenceMitigator` strategy | ✅ Done | Confusable concept separation |
|
|
35
|
+
| `RelativePriority` strategy | ✅ Done | Utility-based content ordering |
|
|
36
|
+
| `SessionController` integration | ✅ Done | `getWeightedCards()` is live |
|
|
37
|
+
| Pipeline assembly | ❌ Not done | See `todo-naive-orchestration.md` |
|
|
38
|
+
|
|
39
|
+
**What this enables:**
|
|
40
|
+
- Multiple strategies can coexist and be configured per-course
|
|
41
|
+
- Strategies return graded suitability scores (0-1), not just binary include/exclude
|
|
42
|
+
- Composition allows layering strategies: `Priority(Interference(Hierarchy(ELO)))`
|
|
43
|
+
|
|
44
|
+
**What's missing for basic operation:**
|
|
45
|
+
- No pipeline configuration in CourseConfig (`navigationPipeline` field)
|
|
46
|
+
- No assembly logic to chain configured strategies
|
|
47
|
+
- Currently falls back to hard-coded ELO navigator
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
### 🟡 PARTIAL: Strategy State & Context
|
|
52
|
+
|
|
53
|
+
| Component | Status | Notes |
|
|
54
|
+
|-----------|--------|-------|
|
|
55
|
+
| Read user ELO (global + per-tag) | ✅ Done | Via `CourseRegistration.elo` |
|
|
56
|
+
| Read user history | ✅ Done | `getSeenCards()`, `getPendingReviews()` |
|
|
57
|
+
| Write strategy-specific state | ❌ Not done | See `todo-strategy-state-storage.md` |
|
|
58
|
+
| Temporal tracking | ❌ Not done | "When was tag X last introduced?" |
|
|
59
|
+
|
|
60
|
+
**Gap for evolutionary vision:**
|
|
61
|
+
- Strategies cannot yet persist their own learning/state
|
|
62
|
+
- No mechanism for tracking strategy effectiveness over time
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
### ❌ NOT STARTED: Multi-Arm Bandit Selection
|
|
67
|
+
|
|
68
|
+
The core orchestration logic described in the vision:
|
|
69
|
+
|
|
70
|
+
```
|
|
71
|
+
each of N strategies divides into cohorts via some hashId collision metric
|
|
72
|
+
eg: h(strategyHash + slowRotationSalt) xor userHash
|
|
73
|
+
if resultant Ones are < utilityConfidence percent of total bits,
|
|
74
|
+
then include the strategy for the user
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
**What's needed:**
|
|
78
|
+
|
|
79
|
+
1. **Strategy Registry** — Central list of available strategies with metadata
|
|
80
|
+
2. **Utility Confidence** — Per-strategy confidence score (0-1)
|
|
81
|
+
3. **Cohort Assignment** — Deterministic but rotatable user-to-strategy mapping
|
|
82
|
+
4. **Salt Rotation** — Periodic rotation to prevent user lock-in
|
|
83
|
+
5. **Outcome Measurement** — Track goal achievement per cohort
|
|
84
|
+
|
|
85
|
+
**Proposed architecture:**
|
|
86
|
+
|
|
87
|
+
```typescript
|
|
88
|
+
interface StrategyRegistryEntry {
|
|
89
|
+
strategyId: string;
|
|
90
|
+
strategyType: string;
|
|
91
|
+
config: unknown;
|
|
92
|
+
|
|
93
|
+
// Evolutionary metadata
|
|
94
|
+
utilityConfidence: number; // 0-1, probability of usefulness
|
|
95
|
+
createdAt: string;
|
|
96
|
+
lastMeasuredAt: string;
|
|
97
|
+
cohortSalt: string; // Rotated periodically
|
|
98
|
+
|
|
99
|
+
// Outcome tracking
|
|
100
|
+
metrics: {
|
|
101
|
+
usersExposed: number;
|
|
102
|
+
goalAchievementRate: number;
|
|
103
|
+
engagementScore: number;
|
|
104
|
+
progressRate: number;
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
interface Orchestrator {
|
|
109
|
+
// Determine which strategies apply to this user for this session
|
|
110
|
+
selectStrategies(userId: string, courseId: string): Promise<StrategyRegistryEntry[]>;
|
|
111
|
+
|
|
112
|
+
// Record outcome for evolutionary learning
|
|
113
|
+
recordOutcome(userId: string, strategyIds: string[], outcome: Outcome): Promise<void>;
|
|
114
|
+
|
|
115
|
+
// Rotate cohort assignments
|
|
116
|
+
rotateSalt(): Promise<void>;
|
|
117
|
+
|
|
118
|
+
// Prune ineffective strategies
|
|
119
|
+
pruneStrategies(minConfidence: number): Promise<void>;
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
### ❌ NOT STARTED: Parameterizable / Programmable Strategies
|
|
126
|
+
|
|
127
|
+
The vision describes:
|
|
128
|
+
|
|
129
|
+
```
|
|
130
|
+
would like to extend into parameterizable / programmable strategies.
|
|
131
|
+
eg, should be able to specify deps like:
|
|
132
|
+
`grade-{n}` & `geometry` -> `grade-{n+1}` & `geometry`
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
**What's needed:**
|
|
136
|
+
|
|
137
|
+
1. **Template syntax** — Pattern matching for tag relationships
|
|
138
|
+
2. **Variable binding** — Extract and apply variables in prerequisite rules
|
|
139
|
+
3. **Rule engine** — Evaluate parameterized rules against user state
|
|
140
|
+
|
|
141
|
+
**Example:**
|
|
142
|
+
```typescript
|
|
143
|
+
interface ParameterizedPrerequisite {
|
|
144
|
+
pattern: "grade-{n} & {subject}";
|
|
145
|
+
implies: "grade-{n+1} & {subject}";
|
|
146
|
+
constraints: {
|
|
147
|
+
n: { type: "number", min: 1, max: 12 },
|
|
148
|
+
subject: { type: "tag-category", category: "academic-subject" }
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
### ❌ NOT STARTED: Self-Healing Content
|
|
156
|
+
|
|
157
|
+
The meta-consideration from the vision:
|
|
158
|
+
|
|
159
|
+
```
|
|
160
|
+
- identifying learning 'barriers' where substantive portion of cohort gets stuck or abandons
|
|
161
|
+
- surfacing that info in a useful way
|
|
162
|
+
- author aims to diagnose and remedy by providing intermediate content
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
**What's needed:**
|
|
166
|
+
|
|
167
|
+
1. **Barrier Detection** — Identify cards/tags where many users get stuck
|
|
168
|
+
2. **Cohort Analysis** — Compare progress across strategy cohorts
|
|
169
|
+
3. **Alert System** — Surface barriers to authors
|
|
170
|
+
4. **Intervention Hooks** — Enable targeted content insertion
|
|
171
|
+
5. **Feedback Loop** — Measure intervention effectiveness
|
|
172
|
+
|
|
173
|
+
**Signals for barrier detection:**
|
|
174
|
+
- High failure rate on specific cards
|
|
175
|
+
- Long dwell time before mastery
|
|
176
|
+
- Drop-off (users abandon course at specific points)
|
|
177
|
+
- ELO stagnation on specific tags
|
|
178
|
+
|
|
179
|
+
---
|
|
180
|
+
|
|
181
|
+
## Roadmap to Full Vision
|
|
182
|
+
|
|
183
|
+
### Phase A: Foundation (COMPLETE ✅)
|
|
184
|
+
|
|
185
|
+
- [x] WeightedCard API
|
|
186
|
+
- [x] ContentNavigator framework
|
|
187
|
+
- [x] Core strategies (Hierarchy, Interference, RelativePriority)
|
|
188
|
+
- [x] SessionController integration
|
|
189
|
+
|
|
190
|
+
### Phase B: Strategy Ecosystem (IN PROGRESS 🟡)
|
|
191
|
+
|
|
192
|
+
- [ ] SRS as ContentNavigator (`todo-srs-navigator.md`)
|
|
193
|
+
- [ ] Strategy authoring tools (`todo-strategy-authoring.md`)
|
|
194
|
+
- [ ] Strategy state storage (`todo-strategy-state-storage.md`)
|
|
195
|
+
- [ ] Provenance/audit trail (`todo-provenance.md`)
|
|
196
|
+
|
|
197
|
+
### Phase C: Measurement Infrastructure (NOT STARTED)
|
|
198
|
+
|
|
199
|
+
- [ ] Define "learning outcome" metrics
|
|
200
|
+
- [ ] Instrument strategy usage per user
|
|
201
|
+
- [ ] Track outcome-to-strategy correlation
|
|
202
|
+
- [ ] Build cohort comparison views
|
|
203
|
+
|
|
204
|
+
### Phase D: Multi-Arm Bandit Orchestrator (NOT STARTED)
|
|
205
|
+
|
|
206
|
+
- [ ] Strategy registry with utility confidence
|
|
207
|
+
- [ ] Cohort assignment algorithm
|
|
208
|
+
- [ ] Salt rotation mechanism
|
|
209
|
+
- [ ] Confidence update rules (Bayesian or frequentist)
|
|
210
|
+
- [ ] Orchestrator integration with SessionController
|
|
211
|
+
|
|
212
|
+
### Phase E: Evolutionary Pressure (NOT STARTED)
|
|
213
|
+
|
|
214
|
+
- [ ] Strategy creation incentives
|
|
215
|
+
- [ ] Automatic strategy pruning
|
|
216
|
+
- [ ] Strategy mutation/variation generation
|
|
217
|
+
- [ ] A/B testing framework
|
|
218
|
+
|
|
219
|
+
### Phase F: Self-Healing (NOT STARTED)
|
|
220
|
+
|
|
221
|
+
- [ ] Barrier detection pipeline
|
|
222
|
+
- [ ] Author alerting system
|
|
223
|
+
- [ ] Intervention recommendation engine
|
|
224
|
+
- [ ] Content gap analysis
|
|
225
|
+
|
|
226
|
+
### Phase G: Programmable Strategies (NOT STARTED)
|
|
227
|
+
|
|
228
|
+
- [ ] Template syntax design
|
|
229
|
+
- [ ] Rule engine implementation
|
|
230
|
+
- [ ] Variable binding in prerequisites
|
|
231
|
+
- [ ] Cross-course strategy generalization
|
|
232
|
+
|
|
233
|
+
---
|
|
234
|
+
|
|
235
|
+
## Key Design Decisions Ahead
|
|
236
|
+
|
|
237
|
+
### 1. Centralized vs Decentralized Orchestration
|
|
238
|
+
|
|
239
|
+
**Option A: Central Orchestrator Service**
|
|
240
|
+
- Single service decides strategy application
|
|
241
|
+
- Cleaner cohort tracking
|
|
242
|
+
- Requires backend infrastructure
|
|
243
|
+
|
|
244
|
+
**Option B: Client-Side Orchestration**
|
|
245
|
+
- Each client computes its own strategy set
|
|
246
|
+
- Works offline
|
|
247
|
+
- Harder to track cohorts
|
|
248
|
+
|
|
249
|
+
**Recommendation:** Hybrid — client computes from synced registry, outcomes reported to backend.
|
|
250
|
+
|
|
251
|
+
### 2. Confidence Update Mechanism
|
|
252
|
+
|
|
253
|
+
**Option A: Frequentist (Simple)**
|
|
254
|
+
- Track success/failure counts
|
|
255
|
+
- Confidence = success_rate with smoothing
|
|
256
|
+
- Easy to implement
|
|
257
|
+
|
|
258
|
+
**Option B: Bayesian (Principled)**
|
|
259
|
+
- Prior belief + observed evidence
|
|
260
|
+
- Thompson sampling for exploration/exploitation
|
|
261
|
+
- More complex but theoretically sound
|
|
262
|
+
|
|
263
|
+
**Recommendation:** Start with frequentist, migrate to Bayesian if needed.
|
|
264
|
+
|
|
265
|
+
### 3. Strategy Granularity
|
|
266
|
+
|
|
267
|
+
**Question:** What counts as "a strategy" for evolutionary purposes?
|
|
268
|
+
|
|
269
|
+
- A specific `HierarchyDefinition` config? (Fine-grained)
|
|
270
|
+
- The `HierarchyDefinition` approach in general? (Coarse-grained)
|
|
271
|
+
- A specific prerequisite rule within a hierarchy? (Very fine-grained)
|
|
272
|
+
|
|
273
|
+
**Recommendation:** Start coarse (strategy type + major config), add granularity as measurement matures.
|
|
274
|
+
|
|
275
|
+
### 4. Outcome Attribution
|
|
276
|
+
|
|
277
|
+
**Question:** If a user is exposed to multiple strategies, how do we attribute outcomes?
|
|
278
|
+
|
|
279
|
+
- All strategies get credit/blame equally?
|
|
280
|
+
- Weight by score contribution?
|
|
281
|
+
- Require isolated A/B cells?
|
|
282
|
+
|
|
283
|
+
**Recommendation:** Start with equal attribution, instrument for isolated testing later.
|
|
284
|
+
|
|
285
|
+
---
|
|
286
|
+
|
|
287
|
+
## Related Documents
|
|
288
|
+
|
|
289
|
+
- `u.3.strategic-nuggets.md` — Original vision statement
|
|
290
|
+
- `todo-srs-navigator.md` — SRS migration
|
|
291
|
+
- `todo-strategy-authoring.md` — Authoring tools
|
|
292
|
+
- `todo-strategy-state-storage.md` — State persistence
|
|
293
|
+
- `todo-provenance.md` — Audit trail for transparency
|
|
294
|
+
- `a.2.plan.md` — Detailed implementation plan
|
|
295
|
+
- `a.3.todo.md` — Task tracking
|
|
296
|
+
- `packages/db/docs/navigators-architecture.md` — Technical architecture
|
|
297
|
+
|
|
298
|
+
---
|
|
299
|
+
|
|
300
|
+
## Summary
|
|
301
|
+
|
|
302
|
+
**Where we are:** Solid foundation. Strategies exist, compose, return scores, and are integrated
|
|
303
|
+
with SessionController. The infrastructure supports the vision.
|
|
304
|
+
|
|
305
|
+
**What's next:** Build the measurement and orchestration layers. The strategies can now *exist*;
|
|
306
|
+
we need to make them *compete* and *evolve*.
|
|
307
|
+
|
|
308
|
+
**The gap:** The evolutionary pressure mechanisms (multi-arm bandit selection, outcome measurement,
|
|
309
|
+
confidence updates, self-healing) are not yet implemented. This is where the "magic" of the
|
|
310
|
+
vision lives, and it's entirely ahead of us.
|