@vue-skuilder/db 0.1.16 → 0.1.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/dist/{userDB-DNa0XPtn.d.ts → classroomDB-BgfrVb8d.d.ts} +357 -103
  2. package/dist/{userDB-BqwxtJ_7.d.mts → classroomDB-CTOenngH.d.cts} +358 -104
  3. package/dist/core/index.d.cts +230 -0
  4. package/dist/core/index.d.ts +161 -23
  5. package/dist/core/index.js +1964 -154
  6. package/dist/core/index.js.map +1 -1
  7. package/dist/core/index.mjs +1925 -121
  8. package/dist/core/index.mjs.map +1 -1
  9. package/dist/{dataLayerProvider-BV5iZqt_.d.ts → dataLayerProvider-CZxC9GtB.d.ts} +1 -1
  10. package/dist/{dataLayerProvider-VlngD19_.d.mts → dataLayerProvider-D6PoCwS6.d.cts} +1 -1
  11. package/dist/impl/couch/{index.d.mts → index.d.cts} +46 -5
  12. package/dist/impl/couch/index.d.ts +44 -3
  13. package/dist/impl/couch/index.js +1971 -171
  14. package/dist/impl/couch/index.js.map +1 -1
  15. package/dist/impl/couch/index.mjs +1933 -134
  16. package/dist/impl/couch/index.mjs.map +1 -1
  17. package/dist/impl/static/{index.d.mts → index.d.cts} +5 -6
  18. package/dist/impl/static/index.d.ts +2 -3
  19. package/dist/impl/static/index.js +1614 -119
  20. package/dist/impl/static/index.js.map +1 -1
  21. package/dist/impl/static/index.mjs +1585 -92
  22. package/dist/impl/static/index.mjs.map +1 -1
  23. package/dist/{index-Bmll7Xse.d.mts → index-D-Fa4Smt.d.cts} +1 -1
  24. package/dist/{index.d.mts → index.d.cts} +97 -13
  25. package/dist/index.d.ts +90 -6
  26. package/dist/index.js +2085 -153
  27. package/dist/index.js.map +1 -1
  28. package/dist/index.mjs +2031 -106
  29. package/dist/index.mjs.map +1 -1
  30. package/dist/pouch/index.js +3 -3
  31. package/dist/{types-Dbp5DaRR.d.mts → types-CzPDLAK6.d.cts} +1 -1
  32. package/dist/util/packer/{index.d.mts → index.d.cts} +3 -3
  33. package/dist/util/packer/index.js.map +1 -1
  34. package/dist/util/packer/index.mjs.map +1 -1
  35. package/docs/brainstorm-navigation-paradigm.md +369 -0
  36. package/docs/navigators-architecture.md +265 -0
  37. package/docs/todo-evolutionary-orchestration.md +310 -0
  38. package/docs/todo-nominal-tag-types.md +121 -0
  39. package/docs/todo-pipeline-optimization.md +117 -0
  40. package/docs/todo-strategy-authoring.md +401 -0
  41. package/docs/todo-strategy-state-storage.md +278 -0
  42. package/eslint.config.mjs +1 -1
  43. package/package.json +9 -4
  44. package/src/core/interfaces/contentSource.ts +88 -4
  45. package/src/core/interfaces/navigationStrategyManager.ts +0 -5
  46. package/src/core/navigators/CompositeGenerator.ts +268 -0
  47. package/src/core/navigators/Pipeline.ts +205 -0
  48. package/src/core/navigators/PipelineAssembler.ts +194 -0
  49. package/src/core/navigators/elo.ts +104 -15
  50. package/src/core/navigators/filters/eloDistance.ts +132 -0
  51. package/src/core/navigators/filters/index.ts +6 -0
  52. package/src/core/navigators/filters/types.ts +115 -0
  53. package/src/core/navigators/generators/index.ts +2 -0
  54. package/src/core/navigators/generators/types.ts +107 -0
  55. package/src/core/navigators/hardcodedOrder.ts +111 -12
  56. package/src/core/navigators/hierarchyDefinition.ts +266 -0
  57. package/src/core/navigators/index.ts +345 -3
  58. package/src/core/navigators/interferenceMitigator.ts +367 -0
  59. package/src/core/navigators/relativePriority.ts +267 -0
  60. package/src/core/navigators/srs.ts +195 -0
  61. package/src/impl/couch/classroomDB.ts +51 -0
  62. package/src/impl/couch/courseDB.ts +117 -39
  63. package/src/impl/static/courseDB.ts +0 -4
  64. package/src/study/SessionController.ts +149 -1
  65. package/src/study/TagFilteredContentSource.ts +255 -0
  66. package/src/study/index.ts +1 -0
  67. package/src/util/dataDirectory.test.ts +51 -22
  68. package/src/util/logger.ts +0 -1
  69. package/tests/core/navigators/CompositeGenerator.test.ts +455 -0
  70. package/tests/core/navigators/Pipeline.test.ts +405 -0
  71. package/tests/core/navigators/PipelineAssembler.test.ts +351 -0
  72. package/tests/core/navigators/SRSNavigator.test.ts +344 -0
  73. package/tests/core/navigators/eloDistanceFilter.test.ts +192 -0
  74. package/tests/core/navigators/navigators.test.ts +710 -0
  75. package/tsconfig.json +1 -1
  76. package/vitest.config.ts +29 -0
  77. package/dist/core/index.d.mts +0 -92
  78. /package/dist/{SyncStrategy-CyATpyLQ.d.mts → SyncStrategy-CyATpyLQ.d.cts} +0 -0
  79. /package/dist/pouch/{index.d.mts → index.d.cts} +0 -0
  80. /package/dist/{types-legacy-6ettoclI.d.mts → types-legacy-6ettoclI.d.cts} +0 -0
@@ -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