@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.
- package/dist/{userDB-DNa0XPtn.d.ts → classroomDB-BgfrVb8d.d.ts} +357 -103
- package/dist/{userDB-BqwxtJ_7.d.mts → classroomDB-CTOenngH.d.cts} +358 -104
- package/dist/core/index.d.cts +230 -0
- package/dist/core/index.d.ts +161 -23
- package/dist/core/index.js +1964 -154
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.mjs +1925 -121
- package/dist/core/index.mjs.map +1 -1
- package/dist/{dataLayerProvider-BV5iZqt_.d.ts → dataLayerProvider-CZxC9GtB.d.ts} +1 -1
- package/dist/{dataLayerProvider-VlngD19_.d.mts → dataLayerProvider-D6PoCwS6.d.cts} +1 -1
- package/dist/impl/couch/{index.d.mts → index.d.cts} +46 -5
- package/dist/impl/couch/index.d.ts +44 -3
- package/dist/impl/couch/index.js +1971 -171
- package/dist/impl/couch/index.js.map +1 -1
- package/dist/impl/couch/index.mjs +1933 -134
- package/dist/impl/couch/index.mjs.map +1 -1
- package/dist/impl/static/{index.d.mts → index.d.cts} +5 -6
- package/dist/impl/static/index.d.ts +2 -3
- package/dist/impl/static/index.js +1614 -119
- package/dist/impl/static/index.js.map +1 -1
- package/dist/impl/static/index.mjs +1585 -92
- package/dist/impl/static/index.mjs.map +1 -1
- package/dist/{index-Bmll7Xse.d.mts → index-D-Fa4Smt.d.cts} +1 -1
- package/dist/{index.d.mts → index.d.cts} +97 -13
- package/dist/index.d.ts +90 -6
- package/dist/index.js +2085 -153
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2031 -106
- package/dist/index.mjs.map +1 -1
- package/dist/pouch/index.js +3 -3
- package/dist/{types-Dbp5DaRR.d.mts → types-CzPDLAK6.d.cts} +1 -1
- package/dist/util/packer/{index.d.mts → index.d.cts} +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 +265 -0
- package/docs/todo-evolutionary-orchestration.md +310 -0
- package/docs/todo-nominal-tag-types.md +121 -0
- package/docs/todo-pipeline-optimization.md +117 -0
- package/docs/todo-strategy-authoring.md +401 -0
- package/docs/todo-strategy-state-storage.md +278 -0
- package/eslint.config.mjs +1 -1
- package/package.json +9 -4
- package/src/core/interfaces/contentSource.ts +88 -4
- package/src/core/interfaces/navigationStrategyManager.ts +0 -5
- package/src/core/navigators/CompositeGenerator.ts +268 -0
- package/src/core/navigators/Pipeline.ts +205 -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 +6 -0
- package/src/core/navigators/filters/types.ts +115 -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 +345 -3
- package/src/core/navigators/interferenceMitigator.ts +367 -0
- package/src/core/navigators/relativePriority.ts +267 -0
- package/src/core/navigators/srs.ts +195 -0
- package/src/impl/couch/classroomDB.ts +51 -0
- package/src/impl/couch/courseDB.ts +117 -39
- package/src/impl/static/courseDB.ts +0 -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 +405 -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
- /package/dist/{types-legacy-6ettoclI.d.mts → types-legacy-6ettoclI.d.cts} +0 -0
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
# Brainstorm: Navigation Paradigm Exploration
|
|
2
|
+
|
|
3
|
+
> **Status:** Informal brainstorming. Not a design doc or TODO.
|
|
4
|
+
|
|
5
|
+
This document explores the navigation strategy paradigm: what other strategy types
|
|
6
|
+
might exist, where the current design shines or struggles, and what alternative
|
|
7
|
+
approaches could achieve similar aims.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Potential Strategy Classes
|
|
12
|
+
|
|
13
|
+
### Generators (Candidate Sources)
|
|
14
|
+
|
|
15
|
+
These produce candidate cards, typically with initial scores.
|
|
16
|
+
|
|
17
|
+
| Strategy | Description | Trigger/Source |
|
|
18
|
+
|----------|-------------|----------------|
|
|
19
|
+
| **ELO** (exists) | Skill-proximity matching | Always active, scores by distance |
|
|
20
|
+
| **SRS** (planned) | Spaced repetition scheduling | Time-based, scores by overdueness |
|
|
21
|
+
| **HardcodedOrder** (exists) | Fixed sequence | Index position |
|
|
22
|
+
| **TriggerResponse** | Activated by specific events | See below |
|
|
23
|
+
| **RecentFailures** | Re-surfaces cards with recent errors | Performance signal |
|
|
24
|
+
| **Adaptive Drill** | Intensive repetition on weak spots | Threshold-triggered |
|
|
25
|
+
| **Random** | Uniform random selection | Always active (baseline) |
|
|
26
|
+
| **CurriculumSequence** | Author-defined learning path | Progress gates |
|
|
27
|
+
| **SocialSignal** | Cards others struggled with | Cohort data |
|
|
28
|
+
|
|
29
|
+
### Filters (Score Transformers)
|
|
30
|
+
|
|
31
|
+
These modify scores from upstream candidates.
|
|
32
|
+
|
|
33
|
+
| Strategy | Description | Effect |
|
|
34
|
+
|----------|-------------|--------|
|
|
35
|
+
| **HierarchyDefinition** (exists) | Prerequisite gating | Hard filter (score=0) |
|
|
36
|
+
| **InterferenceMitigator** (exists) | Confusable concept separation | Soft penalty |
|
|
37
|
+
| **RelativePriority** (exists) | Utility-based boosting | Soft boost |
|
|
38
|
+
| **TimeSinceTag** | Recency of tag exposure | Soft penalty/boost |
|
|
39
|
+
| **SessionCoherence** | Thematic consistency within session | Soft boost for related |
|
|
40
|
+
| **FatigueAdjuster** | Reduce difficulty as session progresses | Soft penalty for hard cards late |
|
|
41
|
+
| **UserPreference** | User-stated preferences/interests | Soft boost |
|
|
42
|
+
| **Novelty** | Freshness vs. review balance | Score adjustment |
|
|
43
|
+
|
|
44
|
+
### Trigger-Response Generators (Your Interest)
|
|
45
|
+
|
|
46
|
+
These are **event-driven generators** that activate under specific conditions:
|
|
47
|
+
|
|
48
|
+
#### Intensive Response Intervention
|
|
49
|
+
|
|
50
|
+
```
|
|
51
|
+
Trigger: User fails card C with tag T at mastery level M
|
|
52
|
+
AND failure count on T exceeds threshold in window
|
|
53
|
+
|
|
54
|
+
Response: Generator activates
|
|
55
|
+
- Surfaces remedial content for tag T
|
|
56
|
+
- May include prerequisite tags
|
|
57
|
+
- Runs for N cards or until success threshold
|
|
58
|
+
|
|
59
|
+
Exit: Deactivates when recovery criteria met
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
#### Other Trigger Scenarios
|
|
63
|
+
|
|
64
|
+
| Trigger | Response |
|
|
65
|
+
|---------|----------|
|
|
66
|
+
| **Frustration signal** (N failures in M minutes) | Shift to easier content, motivational material |
|
|
67
|
+
| **Plateau detection** (no ELO movement for K sessions) | Surface challenging content, introduce new tags |
|
|
68
|
+
| **Mastery celebration** (tag mastery achieved) | Capstone card, related advanced content |
|
|
69
|
+
| **Inactivity return** (first session after gap) | Review-heavy, confidence rebuilding |
|
|
70
|
+
| **Learning velocity drop** | Adjust presentation cadence, check interference |
|
|
71
|
+
| **Specific error pattern** (e.g., always confuses X and Y) | Targeted contrast exercises |
|
|
72
|
+
|
|
73
|
+
#### Implementation Considerations
|
|
74
|
+
|
|
75
|
+
Trigger-response generators need:
|
|
76
|
+
1. **Event bus / signal mechanism** — How do strategies observe events?
|
|
77
|
+
2. **Activation state** — How to track "I am currently intervening on tag T"?
|
|
78
|
+
3. **Priority over base generator** — When active, should it preempt or blend?
|
|
79
|
+
4. **Exit criteria** — When does intervention end?
|
|
80
|
+
|
|
81
|
+
This argues for strategies having **lifecycle state**, not just stateless scoring.
|
|
82
|
+
See `todo-strategy-state-storage.md` for related concerns.
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## Hybrid Generators
|
|
87
|
+
|
|
88
|
+
Some strategies might combine generation and filtering:
|
|
89
|
+
|
|
90
|
+
| Strategy | Behavior |
|
|
91
|
+
|----------|----------|
|
|
92
|
+
| **Contextual ELO** | ELO generator that adjusts target based on session state |
|
|
93
|
+
| **Pacing Controller** | Generates from pool but enforces cadence rules |
|
|
94
|
+
| **Multi-Objective** | Balances multiple generators (ELO + SRS + Priority) |
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
## Strengths of Current Paradigm
|
|
99
|
+
|
|
100
|
+
### 1. Composability via Delegation
|
|
101
|
+
|
|
102
|
+
The delegate pattern is genuinely elegant:
|
|
103
|
+
```
|
|
104
|
+
Priority(Interference(Hierarchy(ELO)))
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Each layer does one thing. Easy to reason about, test, and swap components.
|
|
108
|
+
|
|
109
|
+
### 2. Unified Score Semantics
|
|
110
|
+
|
|
111
|
+
Everything speaks `WeightedCard`. Scores compose multiplicatively. A hard filter
|
|
112
|
+
(score=0) works at any layer. Soft preferences blend naturally.
|
|
113
|
+
|
|
114
|
+
### 3. Separation of Concerns
|
|
115
|
+
|
|
116
|
+
- **Generators** know about card selection
|
|
117
|
+
- **Filters** know about constraints/preferences
|
|
118
|
+
- **SessionController** knows about time/queue management
|
|
119
|
+
- **No cross-cutting entanglement**
|
|
120
|
+
|
|
121
|
+
### 4. Backward Compatibility
|
|
122
|
+
|
|
123
|
+
Legacy `getNewCards()` / `getPendingReviews()` still work. Migration is incremental.
|
|
124
|
+
|
|
125
|
+
### 5. Extensibility
|
|
126
|
+
|
|
127
|
+
New strategies plug in via `ContentNavigator.create()` dynamic loading. No central
|
|
128
|
+
registry modification required.
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
## Weaknesses of Current Paradigm
|
|
133
|
+
|
|
134
|
+
### 1. Stateless by Default
|
|
135
|
+
|
|
136
|
+
Strategies are instantiated per-request. No persistence of:
|
|
137
|
+
- "I am currently running an intervention"
|
|
138
|
+
- "Last time I surfaced tag X was..."
|
|
139
|
+
- "This user responds well to strategy Y"
|
|
140
|
+
|
|
141
|
+
**Mitigation:** `todo-strategy-state-storage.md` (not implemented)
|
|
142
|
+
|
|
143
|
+
### 2. Pull-Only Model
|
|
144
|
+
|
|
145
|
+
Strategies are invoked via `getWeightedCards()`. They can't:
|
|
146
|
+
- React to events (card failure, session end)
|
|
147
|
+
- Proactively signal "I have urgent candidates"
|
|
148
|
+
- Coordinate across sessions
|
|
149
|
+
|
|
150
|
+
**Mitigation:** Would need event subscription / push mechanism
|
|
151
|
+
|
|
152
|
+
### 3. Limited Context Passing
|
|
153
|
+
|
|
154
|
+
Strategies receive `user`, `course`, `strategyData`. They don't receive:
|
|
155
|
+
- Current session state (cards seen, failures)
|
|
156
|
+
- Temporal context (time of day, day of week)
|
|
157
|
+
- User's explicit goals for this session
|
|
158
|
+
|
|
159
|
+
**Mitigation:** Expand context object passed to strategies
|
|
160
|
+
|
|
161
|
+
### 4. No Inter-Strategy Communication
|
|
162
|
+
|
|
163
|
+
Strategies can't:
|
|
164
|
+
- "I'm handling this, others back off"
|
|
165
|
+
- Share computed data (e.g., interference detection useful to multiple)
|
|
166
|
+
- Negotiate priority
|
|
167
|
+
|
|
168
|
+
**Mitigation:** Shared context / blackboard pattern
|
|
169
|
+
|
|
170
|
+
### 5. Pipeline Assembly Gap
|
|
171
|
+
|
|
172
|
+
(Addressed in `todo-naive-orchestration.md`)
|
|
173
|
+
|
|
174
|
+
The delegate pattern exists but isn't wired up. Each filter creates its own
|
|
175
|
+
delegate internally based on `serializedData`. No central orchestration.
|
|
176
|
+
|
|
177
|
+
### 6. Linear Pipeline Limitations
|
|
178
|
+
|
|
179
|
+
Current model is strictly linear: `A(B(C(D)))`. Some scenarios want:
|
|
180
|
+
- Parallel generators merged: `merge(ELO, SRS)`
|
|
181
|
+
- Conditional branches: `if frustrated then X else Y`
|
|
182
|
+
- Dynamic reconfiguration mid-session
|
|
183
|
+
|
|
184
|
+
---
|
|
185
|
+
|
|
186
|
+
## Alternative Mechanisms
|
|
187
|
+
|
|
188
|
+
### 1. Blackboard Architecture
|
|
189
|
+
|
|
190
|
+
Instead of a linear pipeline, strategies write to a shared "blackboard":
|
|
191
|
+
|
|
192
|
+
```
|
|
193
|
+
Blackboard {
|
|
194
|
+
candidates: WeightedCard[]
|
|
195
|
+
signals: { frustration: 0.3, velocity: 0.8, ... }
|
|
196
|
+
interventions: { activeTag: 'vowel-sounds', since: ... }
|
|
197
|
+
vetoes: Set<cardId>
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
Strategies read/write blackboard in phases:
|
|
201
|
+
1. Generators add candidates
|
|
202
|
+
2. Observers add signals
|
|
203
|
+
3. Filters modify scores
|
|
204
|
+
4. Interventions override/inject
|
|
205
|
+
5. Final selection
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
**Pros:** Richer coordination, event-driven capable
|
|
209
|
+
**Cons:** More complex, harder to reason about, ordering sensitive
|
|
210
|
+
|
|
211
|
+
### 2. Reactive / Event-Driven
|
|
212
|
+
|
|
213
|
+
Replace pull model with event streams:
|
|
214
|
+
|
|
215
|
+
```typescript
|
|
216
|
+
interface NavigationEventSource {
|
|
217
|
+
onCardResult(result: CardResult): void;
|
|
218
|
+
onSessionStart(session: SessionContext): void;
|
|
219
|
+
onSessionEnd(stats: SessionStats): void;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
interface ReactiveCandidateSource {
|
|
223
|
+
candidates$: Observable<WeightedCard[]>;
|
|
224
|
+
priority$: Observable<number>;
|
|
225
|
+
}
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
Strategies subscribe to events and emit candidates when they have something urgent.
|
|
229
|
+
|
|
230
|
+
**Pros:** Natural for trigger-response patterns
|
|
231
|
+
**Cons:** Complexity, backpressure, ordering
|
|
232
|
+
|
|
233
|
+
### 3. Rule Engine
|
|
234
|
+
|
|
235
|
+
Declarative rules instead of imperative strategies:
|
|
236
|
+
|
|
237
|
+
```yaml
|
|
238
|
+
rules:
|
|
239
|
+
- name: interference-cooldown
|
|
240
|
+
when:
|
|
241
|
+
- card.tags intersects session.recentTags
|
|
242
|
+
- tag.maturity < threshold
|
|
243
|
+
then:
|
|
244
|
+
- score *= 0.5
|
|
245
|
+
|
|
246
|
+
- name: frustration-intervention
|
|
247
|
+
when:
|
|
248
|
+
- session.failureRate > 0.4
|
|
249
|
+
- session.consecutiveFailures >= 3
|
|
250
|
+
then:
|
|
251
|
+
- activate: remedial-generator
|
|
252
|
+
- target: session.lastFailedTag
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
**Pros:** Declarative, inspectable, author-configurable
|
|
256
|
+
**Cons:** Limited expressiveness, new DSL to maintain
|
|
257
|
+
|
|
258
|
+
### 4. Bandit Selection (Planned)
|
|
259
|
+
|
|
260
|
+
Instead of a fixed pipeline, select among N candidate pipelines:
|
|
261
|
+
|
|
262
|
+
```
|
|
263
|
+
PipelineA: ELO
|
|
264
|
+
PipelineB: Hierarchy(ELO)
|
|
265
|
+
PipelineC: Interference(Hierarchy(ELO))
|
|
266
|
+
|
|
267
|
+
Orchestrator selects pipeline per-session based on:
|
|
268
|
+
- User cohort
|
|
269
|
+
- Historical effectiveness
|
|
270
|
+
- Exploration/exploitation balance
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
**Pros:** Learns what works, self-improving
|
|
274
|
+
**Cons:** Requires outcome measurement, cold-start problem
|
|
275
|
+
|
|
276
|
+
See `todo-evolutionary-orchestration.md`.
|
|
277
|
+
|
|
278
|
+
### 5. LLM-Guided Selection
|
|
279
|
+
|
|
280
|
+
Use a language model to interpret learner state and select content:
|
|
281
|
+
|
|
282
|
+
```
|
|
283
|
+
Given:
|
|
284
|
+
- User profile: { elo: 950, strugglingWith: ['vowel-sounds'], ... }
|
|
285
|
+
- Session context: { duration: 12min, cardsSeen: 8, recentFailures: 2 }
|
|
286
|
+
- Available cards: [...]
|
|
287
|
+
|
|
288
|
+
Select the next 5 cards and explain reasoning.
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
**Pros:** Flexible, can incorporate nuance
|
|
292
|
+
**Cons:** Latency, cost, unpredictability, hard to debug
|
|
293
|
+
|
|
294
|
+
### 6. Constraint Satisfaction
|
|
295
|
+
|
|
296
|
+
Frame card selection as constraint satisfaction:
|
|
297
|
+
|
|
298
|
+
```
|
|
299
|
+
Constraints:
|
|
300
|
+
- At most 3 new tags per session
|
|
301
|
+
- No more than 2 cards from same tag consecutively
|
|
302
|
+
- Prerequisite tags must be mastered
|
|
303
|
+
- Session should have 60% review, 40% new
|
|
304
|
+
|
|
305
|
+
Objective: Maximize expected learning gain
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
Solver finds valid card sequence.
|
|
309
|
+
|
|
310
|
+
**Pros:** Principled, globally optimal
|
|
311
|
+
**Cons:** Computational cost, hard to specify constraints
|
|
312
|
+
|
|
313
|
+
---
|
|
314
|
+
|
|
315
|
+
## Recommendation: Incremental Extension
|
|
316
|
+
|
|
317
|
+
Rather than wholesale paradigm shift, extend current model:
|
|
318
|
+
|
|
319
|
+
### Near-term (compatible with naive orchestration)
|
|
320
|
+
|
|
321
|
+
1. **Richer context** — Pass session state to strategies
|
|
322
|
+
2. **Trigger-response generator** — New generator type with activation state
|
|
323
|
+
3. **Session-scoped state** — Allow strategies to persist within-session
|
|
324
|
+
|
|
325
|
+
### Medium-term (with strategy state storage)
|
|
326
|
+
|
|
327
|
+
4. **Cross-session state** — Strategies remember past sessions
|
|
328
|
+
5. **Event hooks** — Strategies can subscribe to card results
|
|
329
|
+
6. **Intervention protocol** — Active intervention preempts normal generation
|
|
330
|
+
|
|
331
|
+
### Long-term (with evolutionary orchestration)
|
|
332
|
+
|
|
333
|
+
7. **Bandit selection** among pipelines
|
|
334
|
+
8. **Outcome measurement** for strategy effectiveness
|
|
335
|
+
9. **Hybrid architectures** where blackboard/reactive patterns augment pipeline
|
|
336
|
+
|
|
337
|
+
---
|
|
338
|
+
|
|
339
|
+
## Questions to Explore
|
|
340
|
+
|
|
341
|
+
1. **What's the API for trigger-response generators?**
|
|
342
|
+
- `shouldActivate(sessionState): boolean`?
|
|
343
|
+
- `getInterventionCandidates(): WeightedCard[]`?
|
|
344
|
+
- How to represent "I am currently intervening"?
|
|
345
|
+
|
|
346
|
+
2. **How do strategies share expensive computations?**
|
|
347
|
+
- E.g., tag lookups (addressed in `todo-pipeline-optimization.md`)
|
|
348
|
+
- E.g., user mastery state
|
|
349
|
+
- Shared context object? Memoization layer?
|
|
350
|
+
|
|
351
|
+
3. **What events should be observable?**
|
|
352
|
+
- Card shown, card answered (correct/incorrect), session start/end
|
|
353
|
+
- ELO change, tag mastery change
|
|
354
|
+
- User-initiated actions (skip, flag, etc.)
|
|
355
|
+
|
|
356
|
+
4. **How much state is too much?**
|
|
357
|
+
- Stateless is simple but limited
|
|
358
|
+
- Full state enables sophisticated behavior but complexity cost
|
|
359
|
+
- Right level of state for different strategy types?
|
|
360
|
+
|
|
361
|
+
---
|
|
362
|
+
|
|
363
|
+
## Related Documents
|
|
364
|
+
|
|
365
|
+
- `navigators-architecture.md` — Current architecture
|
|
366
|
+
- `todo-naive-orchestration.md` — Pipeline assembly (prerequisite)
|
|
367
|
+
- `todo-strategy-state-storage.md` — State persistence
|
|
368
|
+
- `todo-evolutionary-orchestration.md` — Bandit selection vision
|
|
369
|
+
- `todo-provenance.md` — Audit trail (transparency)
|
|
@@ -0,0 +1,265 @@
|
|
|
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 and audit trail:
|
|
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
|
+
}
|
|
21
|
+
|
|
22
|
+
interface StrategyContribution {
|
|
23
|
+
strategy: string; // Type: 'elo', 'srs', 'hierarchyDefinition'
|
|
24
|
+
strategyName: string; // Human-readable: "ELO (default)"
|
|
25
|
+
strategyId: string; // Document ID: 'NAVIGATION_STRATEGY-ELO-default'
|
|
26
|
+
action: 'generated' | 'passed' | 'boosted' | 'penalized';
|
|
27
|
+
score: number; // Score after this strategy
|
|
28
|
+
reason: string; // Human-readable explanation
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### CardGenerator
|
|
33
|
+
|
|
34
|
+
Produces candidate cards with initial scores:
|
|
35
|
+
|
|
36
|
+
```typescript
|
|
37
|
+
interface CardGenerator {
|
|
38
|
+
name: string;
|
|
39
|
+
getWeightedCards(limit: number, context: GeneratorContext): Promise<WeightedCard[]>;
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
**Implementations:**
|
|
44
|
+
- `ELONavigator` — New cards scored by ELO proximity to user skill
|
|
45
|
+
- `SRSNavigator` — Review cards scored by overdueness and interval recency
|
|
46
|
+
- `HardcodedOrderNavigator` — Fixed sequence defined by course author
|
|
47
|
+
- `CompositeGenerator` — Merges multiple generators with frequency boost
|
|
48
|
+
|
|
49
|
+
### CardFilter
|
|
50
|
+
|
|
51
|
+
Transforms card scores (pure function, no side effects):
|
|
52
|
+
|
|
53
|
+
```typescript
|
|
54
|
+
interface CardFilter {
|
|
55
|
+
name: string;
|
|
56
|
+
transform(cards: WeightedCard[], context: FilterContext): Promise<WeightedCard[]>;
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
**Implementations:**
|
|
61
|
+
- `HierarchyDefinitionNavigator` — Gates cards by prerequisite mastery (score=0 if locked)
|
|
62
|
+
- `InterferenceMitigatorNavigator` — Reduces scores for confusable content
|
|
63
|
+
- `RelativePriorityNavigator` — Boosts scores for high-utility content
|
|
64
|
+
- `createEloDistanceFilter()` — Penalizes cards far from user's current ELO
|
|
65
|
+
|
|
66
|
+
### Pipeline
|
|
67
|
+
|
|
68
|
+
Orchestrates generator and filters:
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
class Pipeline {
|
|
72
|
+
constructor(
|
|
73
|
+
generator: CardGenerator,
|
|
74
|
+
filters: CardFilter[],
|
|
75
|
+
user: UserDBInterface,
|
|
76
|
+
course: CourseDBInterface
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
async getWeightedCards(limit: number): Promise<WeightedCard[]> {
|
|
80
|
+
const context = await this.buildContext();
|
|
81
|
+
let cards = await this.generator.getWeightedCards(fetchLimit, context);
|
|
82
|
+
|
|
83
|
+
for (const filter of this.filters) {
|
|
84
|
+
cards = await filter.transform(cards, context);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return cards.filter(c => c.score > 0)
|
|
88
|
+
.sort((a, b) => b.score - a.score)
|
|
89
|
+
.slice(0, limit);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Pipeline Assembly
|
|
95
|
+
|
|
96
|
+
`PipelineAssembler` builds pipelines from strategy documents:
|
|
97
|
+
|
|
98
|
+
```typescript
|
|
99
|
+
const assembler = new PipelineAssembler();
|
|
100
|
+
const { pipeline, warnings } = await assembler.assemble({
|
|
101
|
+
strategies: allStrategies,
|
|
102
|
+
user,
|
|
103
|
+
course,
|
|
104
|
+
});
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Assembly logic:
|
|
108
|
+
1. Separate strategies into generators and filters by `NavigatorRole`
|
|
109
|
+
2. Instantiate generators — wrap multiple in `CompositeGenerator`
|
|
110
|
+
3. Instantiate filters — sorted alphabetically for determinism
|
|
111
|
+
4. Return `Pipeline(generator, filters)`
|
|
112
|
+
|
|
113
|
+
If no strategies are configured, `courseDB.createNavigator()` returns a default pipeline:
|
|
114
|
+
```typescript
|
|
115
|
+
Pipeline(
|
|
116
|
+
CompositeGenerator([ELONavigator, SRSNavigator]),
|
|
117
|
+
[eloDistanceFilter]
|
|
118
|
+
)
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## Score Semantics
|
|
122
|
+
|
|
123
|
+
| Score | Meaning |
|
|
124
|
+
|-------|---------|
|
|
125
|
+
| 1.0 | Fully suitable |
|
|
126
|
+
| 0.5 | Neutral |
|
|
127
|
+
| 0.0 | Exclude (hard filter) |
|
|
128
|
+
| 0.x | Proportional suitability |
|
|
129
|
+
|
|
130
|
+
**All filters are multipliers.** This means:
|
|
131
|
+
- Filter order doesn't affect final scores (multiplication is commutative)
|
|
132
|
+
- Score 0 from any filter excludes the card
|
|
133
|
+
- Filters are applied alphabetically for determinism
|
|
134
|
+
|
|
135
|
+
## Provenance Tracking
|
|
136
|
+
|
|
137
|
+
Each card's provenance shows how it was scored:
|
|
138
|
+
|
|
139
|
+
```typescript
|
|
140
|
+
provenance: [
|
|
141
|
+
{
|
|
142
|
+
strategy: 'elo',
|
|
143
|
+
strategyName: 'ELO (default)',
|
|
144
|
+
strategyId: 'NAVIGATION_STRATEGY-ELO-default',
|
|
145
|
+
action: 'generated',
|
|
146
|
+
score: 0.85,
|
|
147
|
+
reason: 'ELO distance 75 (card: 1025, user: 1100), new card'
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
strategy: 'hierarchyDefinition',
|
|
151
|
+
strategyName: 'Hierarchy: Phonics Basics',
|
|
152
|
+
strategyId: 'NAVIGATION_STRATEGY-hierarchy-phonics',
|
|
153
|
+
action: 'passed',
|
|
154
|
+
score: 0.85,
|
|
155
|
+
reason: 'Prerequisites met, tags: letter-sounds'
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
strategy: 'eloDistance',
|
|
159
|
+
strategyName: 'ELO Distance Filter',
|
|
160
|
+
strategyId: 'ELO_DISTANCE_FILTER',
|
|
161
|
+
action: 'penalized',
|
|
162
|
+
score: 0.72,
|
|
163
|
+
reason: 'ELO distance 150 (card: 1150, user: 1000) → 0.85x'
|
|
164
|
+
}
|
|
165
|
+
]
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Use `getCardOrigin(card)` to extract 'new', 'review', or 'failed' from provenance.
|
|
169
|
+
|
|
170
|
+
## Creating New Strategies
|
|
171
|
+
|
|
172
|
+
### Generator
|
|
173
|
+
|
|
174
|
+
```typescript
|
|
175
|
+
class MyGenerator extends ContentNavigator implements CardGenerator {
|
|
176
|
+
name = 'My Generator';
|
|
177
|
+
|
|
178
|
+
async getWeightedCards(limit: number, context?: GeneratorContext): Promise<WeightedCard[]> {
|
|
179
|
+
const candidates = await this.findCandidates(limit);
|
|
180
|
+
|
|
181
|
+
return candidates.map(c => ({
|
|
182
|
+
cardId: c.id,
|
|
183
|
+
courseId: this.course.getCourseID(),
|
|
184
|
+
score: this.computeScore(c),
|
|
185
|
+
provenance: [{
|
|
186
|
+
strategy: 'myGenerator',
|
|
187
|
+
strategyName: this.name,
|
|
188
|
+
strategyId: this.strategyId || 'MY_GENERATOR',
|
|
189
|
+
action: 'generated',
|
|
190
|
+
score: this.computeScore(c),
|
|
191
|
+
reason: 'Explanation here, new card'
|
|
192
|
+
}]
|
|
193
|
+
}));
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Legacy methods - stub or implement for backward compat
|
|
197
|
+
async getNewCards() { return []; }
|
|
198
|
+
async getPendingReviews() { return []; }
|
|
199
|
+
}
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
Register in `NavigatorRoles` as `NavigatorRole.GENERATOR`.
|
|
203
|
+
|
|
204
|
+
### Filter
|
|
205
|
+
|
|
206
|
+
```typescript
|
|
207
|
+
class MyFilter extends ContentNavigator implements CardFilter {
|
|
208
|
+
name = 'My Filter';
|
|
209
|
+
|
|
210
|
+
async transform(cards: WeightedCard[], context: FilterContext): Promise<WeightedCard[]> {
|
|
211
|
+
return cards.map(card => {
|
|
212
|
+
const multiplier = this.computeMultiplier(card, context);
|
|
213
|
+
const newScore = card.score * multiplier;
|
|
214
|
+
const action = multiplier < 1 ? 'penalized' : multiplier > 1 ? 'boosted' : 'passed';
|
|
215
|
+
|
|
216
|
+
return {
|
|
217
|
+
...card,
|
|
218
|
+
score: newScore,
|
|
219
|
+
provenance: [...card.provenance, {
|
|
220
|
+
strategy: 'myFilter',
|
|
221
|
+
strategyName: this.name,
|
|
222
|
+
strategyId: this.strategyId || 'MY_FILTER',
|
|
223
|
+
action,
|
|
224
|
+
score: newScore,
|
|
225
|
+
reason: 'Explanation here'
|
|
226
|
+
}]
|
|
227
|
+
};
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Legacy methods - filters don't generate cards
|
|
232
|
+
async getWeightedCards() { throw new Error('Use transform() via Pipeline'); }
|
|
233
|
+
async getNewCards() { return []; }
|
|
234
|
+
async getPendingReviews() { return []; }
|
|
235
|
+
}
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
Register in `NavigatorRoles` as `NavigatorRole.FILTER`.
|
|
239
|
+
|
|
240
|
+
## File Reference
|
|
241
|
+
|
|
242
|
+
| File | Purpose |
|
|
243
|
+
|------|---------|
|
|
244
|
+
| `core/navigators/index.ts` | `ContentNavigator`, `WeightedCard`, `NavigatorRole` |
|
|
245
|
+
| `core/navigators/generators/types.ts` | `CardGenerator`, `GeneratorContext` |
|
|
246
|
+
| `core/navigators/filters/types.ts` | `CardFilter`, `FilterContext` |
|
|
247
|
+
| `core/navigators/Pipeline.ts` | Pipeline orchestration |
|
|
248
|
+
| `core/navigators/PipelineAssembler.ts` | Builds Pipeline from strategy docs |
|
|
249
|
+
| `core/navigators/CompositeGenerator.ts` | Merges multiple generators |
|
|
250
|
+
| `core/navigators/elo.ts` | ELO generator |
|
|
251
|
+
| `core/navigators/srs.ts` | SRS generator |
|
|
252
|
+
| `core/navigators/hardcodedOrder.ts` | Fixed-order generator |
|
|
253
|
+
| `core/navigators/hierarchyDefinition.ts` | Prerequisite filter |
|
|
254
|
+
| `core/navigators/interferenceMitigator.ts` | Interference filter |
|
|
255
|
+
| `core/navigators/relativePriority.ts` | Priority filter |
|
|
256
|
+
| `core/navigators/filters/eloDistance.ts` | ELO distance filter |
|
|
257
|
+
| `impl/couch/courseDB.ts` | `createNavigator()` entry point |
|
|
258
|
+
|
|
259
|
+
## Related TODOs
|
|
260
|
+
|
|
261
|
+
- `todo-pipeline-optimization.md` - Batch tag hydration for filter efficiency
|
|
262
|
+
- `todo-strategy-authoring.md` - ux and dx for authoring strategies
|
|
263
|
+
- todo-pipeline-optimization.md -
|
|
264
|
+
- todo-strategy-state-storage
|
|
265
|
+
- todo-evolutionary-orchestration
|