@vue-skuilder/db 0.1.20 → 0.1.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +2 -2
- package/dist/{classroomDB-CZdMBiTU.d.ts → contentSource-BP9hznNV.d.ts} +150 -196
- package/dist/{classroomDB-PxDZTky3.d.cts → contentSource-DsJadoBU.d.cts} +150 -196
- package/dist/core/index.d.cts +3 -3
- package/dist/core/index.d.ts +3 -3
- package/dist/core/index.js +615 -1758
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.mjs +579 -1727
- package/dist/core/index.mjs.map +1 -1
- package/dist/{dataLayerProvider-D8o6ZnKW.d.ts → dataLayerProvider-CHYrQ5pB.d.cts} +1 -1
- package/dist/{dataLayerProvider-D0MoZMjH.d.cts → dataLayerProvider-MDTxXq2l.d.ts} +1 -1
- package/dist/impl/couch/index.d.cts +6 -22
- package/dist/impl/couch/index.d.ts +6 -22
- package/dist/impl/couch/index.js +598 -1769
- package/dist/impl/couch/index.js.map +1 -1
- package/dist/impl/couch/index.mjs +579 -1755
- package/dist/impl/couch/index.mjs.map +1 -1
- package/dist/impl/static/index.d.cts +22 -6
- package/dist/impl/static/index.d.ts +22 -6
- package/dist/impl/static/index.js +617 -1629
- package/dist/impl/static/index.js.map +1 -1
- package/dist/impl/static/index.mjs +607 -1624
- package/dist/impl/static/index.mjs.map +1 -1
- package/dist/index.d.cts +64 -56
- package/dist/index.d.ts +64 -56
- package/dist/index.js +1000 -2161
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +970 -2127
- package/dist/index.mjs.map +1 -1
- package/dist/pouch/index.js +3 -0
- package/dist/pouch/index.js.map +1 -1
- package/dist/pouch/index.mjs +3 -0
- package/dist/pouch/index.mjs.map +1 -1
- package/docs/navigators-architecture.md +2 -9
- package/package.json +3 -3
- package/src/core/interfaces/classroomDB.ts +5 -13
- package/src/core/interfaces/contentSource.ts +6 -66
- package/src/core/interfaces/courseDB.ts +2 -7
- package/src/core/navigators/Pipeline.ts +24 -53
- package/src/core/navigators/PipelineAssembler.ts +1 -1
- package/src/core/navigators/defaults.ts +84 -0
- package/src/core/navigators/{hierarchyDefinition.ts → filters/hierarchyDefinition.ts} +11 -25
- package/src/core/navigators/{interferenceMitigator.ts → filters/interferenceMitigator.ts} +10 -24
- package/src/core/navigators/{relativePriority.ts → filters/relativePriority.ts} +10 -24
- package/src/core/navigators/filters/userTagPreference.ts +1 -16
- package/src/core/navigators/{CompositeGenerator.ts → generators/CompositeGenerator.ts} +15 -64
- package/src/core/navigators/{elo.ts → generators/elo.ts} +13 -63
- package/src/core/navigators/{srs.ts → generators/srs.ts} +11 -40
- package/src/core/navigators/generators/types.ts +1 -1
- package/src/core/navigators/index.ts +36 -91
- package/src/impl/couch/classroomDB.ts +100 -103
- package/src/impl/couch/courseDB.ts +5 -81
- package/src/impl/couch/pouchdb-setup.ts +7 -0
- package/src/impl/static/StaticDataUnpacker.ts +50 -1
- package/src/impl/static/courseDB.ts +76 -37
- package/src/study/SessionController.ts +122 -202
- package/src/study/SourceMixer.ts +65 -0
- package/src/study/TagFilteredContentSource.ts +49 -92
- package/src/study/index.ts +1 -0
- package/src/study/services/CardHydrationService.ts +165 -81
- package/src/util/dataDirectory.ts +1 -1
- package/src/util/index.ts +0 -1
- package/tests/core/navigators/CompositeGenerator.test.ts +44 -168
- package/tests/core/navigators/Pipeline.test.ts +5 -72
- package/tests/core/navigators/PipelineAssembler.test.ts +8 -58
- package/tests/core/navigators/navigators.test.ts +118 -151
- package/src/core/navigators/hardcodedOrder.ts +0 -163
- package/src/util/tuiLogger.ts +0 -139
- /package/src/core/navigators/{inferredPreference.ts → filters/inferredPreferenceStub.ts} +0 -0
- /package/src/core/navigators/{userGoal.ts → filters/userGoalStub.ts} +0 -0
package/dist/pouch/index.js
CHANGED
|
@@ -40,6 +40,9 @@ var import_pouchdb_find = __toESM(require("pouchdb-find"), 1);
|
|
|
40
40
|
var import_pouchdb_authentication = __toESM(require("@nilock2/pouchdb-authentication"), 1);
|
|
41
41
|
import_pouchdb.default.plugin(import_pouchdb_find.default);
|
|
42
42
|
import_pouchdb.default.plugin(import_pouchdb_authentication.default);
|
|
43
|
+
if (typeof import_pouchdb.default.debug !== "undefined") {
|
|
44
|
+
import_pouchdb.default.debug.disable();
|
|
45
|
+
}
|
|
43
46
|
import_pouchdb.default.defaults({
|
|
44
47
|
// ajax: {
|
|
45
48
|
// timeout: 60000,
|
package/dist/pouch/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/pouch/index.ts","../../src/impl/couch/pouchdb-setup.ts"],"sourcesContent":["// Export configured PouchDB instance\nexport { default } from '../impl/couch/pouchdb-setup.js';","import PouchDB from 'pouchdb';\nimport PouchDBFind from 'pouchdb-find';\nimport PouchDBAuth from '@nilock2/pouchdb-authentication';\n\n// Register plugins\nPouchDB.plugin(PouchDBFind);\nPouchDB.plugin(PouchDBAuth);\n\n// Configure PouchDB globally\nPouchDB.defaults({\n // ajax: {\n // timeout: 60000,\n // },\n});\n\nexport default PouchDB;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,qBAAoB;AACpB,0BAAwB;AACxB,oCAAwB;AAGxB,eAAAA,QAAQ,OAAO,oBAAAC,OAAW;AAC1B,eAAAD,QAAQ,OAAO,8BAAAE,OAAW;
|
|
1
|
+
{"version":3,"sources":["../../src/pouch/index.ts","../../src/impl/couch/pouchdb-setup.ts"],"sourcesContent":["// Export configured PouchDB instance\nexport { default } from '../impl/couch/pouchdb-setup.js';","import PouchDB from 'pouchdb';\nimport PouchDBFind from 'pouchdb-find';\nimport PouchDBAuth from '@nilock2/pouchdb-authentication';\n\n// Register plugins\nPouchDB.plugin(PouchDBFind);\nPouchDB.plugin(PouchDBAuth);\n\n// Disable PouchDB debug logging to prevent interference with CLI prompts\n// Debug logging (like DerivedLogger.emit) will still go to the TUI log file\n// if initializeTuiLogging() has been called, but won't clutter terminal output\nif (typeof PouchDB.debug !== 'undefined') {\n PouchDB.debug.disable();\n}\n\n// Configure PouchDB globally\nPouchDB.defaults({\n // ajax: {\n // timeout: 60000,\n // },\n});\n\nexport default PouchDB;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,qBAAoB;AACpB,0BAAwB;AACxB,oCAAwB;AAGxB,eAAAA,QAAQ,OAAO,oBAAAC,OAAW;AAC1B,eAAAD,QAAQ,OAAO,8BAAAE,OAAW;AAK1B,IAAI,OAAO,eAAAF,QAAQ,UAAU,aAAa;AACxC,iBAAAA,QAAQ,MAAM,QAAQ;AACxB;AAGA,eAAAA,QAAQ,SAAS;AAAA;AAAA;AAAA;AAIjB,CAAC;AAED,IAAO,wBAAQ,eAAAA;","names":["PouchDB","PouchDBFind","PouchDBAuth"]}
|
package/dist/pouch/index.mjs
CHANGED
|
@@ -4,6 +4,9 @@ import PouchDBFind from "pouchdb-find";
|
|
|
4
4
|
import PouchDBAuth from "@nilock2/pouchdb-authentication";
|
|
5
5
|
PouchDB.plugin(PouchDBFind);
|
|
6
6
|
PouchDB.plugin(PouchDBAuth);
|
|
7
|
+
if (typeof PouchDB.debug !== "undefined") {
|
|
8
|
+
PouchDB.debug.disable();
|
|
9
|
+
}
|
|
7
10
|
PouchDB.defaults({
|
|
8
11
|
// ajax: {
|
|
9
12
|
// timeout: 60000,
|
package/dist/pouch/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/impl/couch/pouchdb-setup.ts"],"sourcesContent":["import PouchDB from 'pouchdb';\nimport PouchDBFind from 'pouchdb-find';\nimport PouchDBAuth from '@nilock2/pouchdb-authentication';\n\n// Register plugins\nPouchDB.plugin(PouchDBFind);\nPouchDB.plugin(PouchDBAuth);\n\n// Configure PouchDB globally\nPouchDB.defaults({\n // ajax: {\n // timeout: 60000,\n // },\n});\n\nexport default PouchDB;\n"],"mappings":";AAAA,OAAO,aAAa;AACpB,OAAO,iBAAiB;AACxB,OAAO,iBAAiB;AAGxB,QAAQ,OAAO,WAAW;AAC1B,QAAQ,OAAO,WAAW;
|
|
1
|
+
{"version":3,"sources":["../../src/impl/couch/pouchdb-setup.ts"],"sourcesContent":["import PouchDB from 'pouchdb';\nimport PouchDBFind from 'pouchdb-find';\nimport PouchDBAuth from '@nilock2/pouchdb-authentication';\n\n// Register plugins\nPouchDB.plugin(PouchDBFind);\nPouchDB.plugin(PouchDBAuth);\n\n// Disable PouchDB debug logging to prevent interference with CLI prompts\n// Debug logging (like DerivedLogger.emit) will still go to the TUI log file\n// if initializeTuiLogging() has been called, but won't clutter terminal output\nif (typeof PouchDB.debug !== 'undefined') {\n PouchDB.debug.disable();\n}\n\n// Configure PouchDB globally\nPouchDB.defaults({\n // ajax: {\n // timeout: 60000,\n // },\n});\n\nexport default PouchDB;\n"],"mappings":";AAAA,OAAO,aAAa;AACpB,OAAO,iBAAiB;AACxB,OAAO,iBAAiB;AAGxB,QAAQ,OAAO,WAAW;AAC1B,QAAQ,OAAO,WAAW;AAK1B,IAAI,OAAO,QAAQ,UAAU,aAAa;AACxC,UAAQ,MAAM,QAAQ;AACxB;AAGA,QAAQ,SAAS;AAAA;AAAA;AAAA;AAIjB,CAAC;AAED,IAAO,wBAAQ;","names":[]}
|
|
@@ -9,7 +9,7 @@ The navigation strategy system selects and scores cards for study sessions. It u
|
|
|
9
9
|
|
|
10
10
|
### WeightedCard
|
|
11
11
|
|
|
12
|
-
A card with a suitability score, audit trail, and pre-fetched
|
|
12
|
+
A card with a suitability score, audit trail, and pre-fetched metadata:
|
|
13
13
|
|
|
14
14
|
```typescript
|
|
15
15
|
interface WeightedCard {
|
|
@@ -210,10 +210,6 @@ class MyGenerator extends ContentNavigator implements CardGenerator {
|
|
|
210
210
|
}]
|
|
211
211
|
}));
|
|
212
212
|
}
|
|
213
|
-
|
|
214
|
-
// Legacy methods - stub or implement for backward compat
|
|
215
|
-
async getNewCards() { return []; }
|
|
216
|
-
async getPendingReviews() { return []; }
|
|
217
213
|
}
|
|
218
214
|
```
|
|
219
215
|
|
|
@@ -246,10 +242,8 @@ class MyFilter extends ContentNavigator implements CardFilter {
|
|
|
246
242
|
});
|
|
247
243
|
}
|
|
248
244
|
|
|
249
|
-
// Legacy
|
|
245
|
+
// Legacy method - filters don't generate cards
|
|
250
246
|
async getWeightedCards() { throw new Error('Use transform() via Pipeline'); }
|
|
251
|
-
async getNewCards() { return []; }
|
|
252
|
-
async getPendingReviews() { return []; }
|
|
253
247
|
}
|
|
254
248
|
```
|
|
255
249
|
|
|
@@ -364,7 +358,6 @@ return { ...card, score: card.score * multiplier };
|
|
|
364
358
|
|
|
365
359
|
## Related Documentation
|
|
366
360
|
|
|
367
|
-
- `todo-pipeline-optimization.md` — Batch tag hydration implementation (✅ completed)
|
|
368
361
|
- `todo-strategy-authoring.md` — UX and DX for authoring strategies
|
|
369
362
|
- `todo-evolutionary-orchestration.md` — Long-term adaptive strategy vision
|
|
370
363
|
- `devlog/1004` — Implementation details for tag hydration optimization
|
package/package.json
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
7
|
-
"version": "0.1.
|
|
7
|
+
"version": "0.1.21",
|
|
8
8
|
"description": "Database layer for vue-skuilder",
|
|
9
9
|
"main": "dist/index.js",
|
|
10
10
|
"module": "dist/index.mjs",
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
},
|
|
49
49
|
"dependencies": {
|
|
50
50
|
"@nilock2/pouchdb-authentication": "^1.0.2",
|
|
51
|
-
"@vue-skuilder/common": "0.1.
|
|
51
|
+
"@vue-skuilder/common": "0.1.21",
|
|
52
52
|
"cross-fetch": "^4.1.0",
|
|
53
53
|
"moment": "^2.29.4",
|
|
54
54
|
"pouchdb": "^9.0.0",
|
|
@@ -62,5 +62,5 @@
|
|
|
62
62
|
"vite": "^7.0.0",
|
|
63
63
|
"vitest": "^4.0.15"
|
|
64
64
|
},
|
|
65
|
-
"stableVersion": "0.1.
|
|
65
|
+
"stableVersion": "0.1.21"
|
|
66
66
|
}
|
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
import { ClassroomConfig } from '@vue-skuilder/common';
|
|
2
|
-
import { ScheduledCard } from '../types/user';
|
|
3
|
-
import { StudySessionNewItem, StudySessionReviewItem } from './contentSource';
|
|
4
2
|
|
|
5
3
|
/**
|
|
6
4
|
* Classroom management
|
|
@@ -29,17 +27,11 @@ export interface TeacherClassroomDBInterface extends ClassroomDBInterface {
|
|
|
29
27
|
removeContent?(content: AssignedContent): Promise<void>;
|
|
30
28
|
}
|
|
31
29
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* For student interfaces: get new cards
|
|
40
|
-
*/
|
|
41
|
-
getNewCards?(limit?: number): Promise<StudySessionNewItem[]>;
|
|
42
|
-
}
|
|
30
|
+
/**
|
|
31
|
+
* Student-facing classroom interface.
|
|
32
|
+
* Content is accessed via StudyContentSource.getWeightedCards().
|
|
33
|
+
*/
|
|
34
|
+
export type StudentClassroomDBInterface = ClassroomDBInterface;
|
|
43
35
|
|
|
44
36
|
export type AssignedContent = AssignedCourse | AssignedTag | AssignedCard;
|
|
45
37
|
|
|
@@ -1,43 +1,10 @@
|
|
|
1
1
|
import { getDataLayer } from '@db/factory';
|
|
2
2
|
import { UserDBInterface } from '..';
|
|
3
3
|
import { StudentClassroomDB } from '../../impl/couch/classroomDB';
|
|
4
|
-
import { ScheduledCard } from '@db/core/types/user';
|
|
5
4
|
import { WeightedCard } from '../navigators';
|
|
6
5
|
import { TagFilter, hasActiveFilter } from '@vue-skuilder/common';
|
|
7
6
|
import { TagFilteredContentSource } from '../../study/TagFilteredContentSource';
|
|
8
7
|
|
|
9
|
-
// ============================================================================
|
|
10
|
-
// API MIGRATION NOTICE
|
|
11
|
-
// ============================================================================
|
|
12
|
-
//
|
|
13
|
-
// The StudyContentSource interface is being superseded by the ContentNavigator
|
|
14
|
-
// class and its getWeightedCards() API. See:
|
|
15
|
-
// packages/db/src/core/navigators/ARCHITECTURE.md
|
|
16
|
-
//
|
|
17
|
-
// HISTORICAL CONTEXT:
|
|
18
|
-
// - This interface was designed to abstract 'classrooms' and 'courses' as
|
|
19
|
-
// content sources for study sessions.
|
|
20
|
-
// - getNewCards() and getPendingReviews() were artifacts of two hard-coded
|
|
21
|
-
// navigation strategies: ELO proximity (new) and SRS scheduling (reviews).
|
|
22
|
-
// - The new/review split reflected implementation details, not fundamentals.
|
|
23
|
-
//
|
|
24
|
-
// THE PROBLEM:
|
|
25
|
-
// - "What does 'get reviews' mean for an interference mitigator?" - it doesn't.
|
|
26
|
-
// - SRS is just one strategy that could express review urgency as scores.
|
|
27
|
-
// - Some strategies generate candidates, others filter/score them.
|
|
28
|
-
//
|
|
29
|
-
// THE SOLUTION:
|
|
30
|
-
// - ContentNavigator.getWeightedCards() returns unified scored candidates.
|
|
31
|
-
// - WeightedCard.source field distinguishes new/review/failed (metadata, not API).
|
|
32
|
-
// - Strategies compose via delegate pattern (filter wraps generator).
|
|
33
|
-
//
|
|
34
|
-
// MIGRATION PATH:
|
|
35
|
-
// 1. ContentNavigator implements StudyContentSource for backward compat
|
|
36
|
-
// 2. SessionController will migrate to call getWeightedCards()
|
|
37
|
-
// 3. Legacy methods will be deprecated, then removed
|
|
38
|
-
//
|
|
39
|
-
// ============================================================================
|
|
40
|
-
|
|
41
8
|
export type StudySessionFailedItem = StudySessionFailedNewItem | StudySessionFailedReviewItem;
|
|
42
9
|
|
|
43
10
|
export interface StudySessionFailedNewItem extends StudySessionItem {
|
|
@@ -89,49 +56,22 @@ export interface ContentSourceID {
|
|
|
89
56
|
/**
|
|
90
57
|
* Interface for sources that provide study content to SessionController.
|
|
91
58
|
*
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
* strategies. The new API returns unified WeightedCard[] with scores.
|
|
95
|
-
*
|
|
96
|
-
* MIGRATION:
|
|
97
|
-
* - Implement ContentNavigator instead of StudyContentSource directly
|
|
98
|
-
* - Override getWeightedCards() as the primary method
|
|
99
|
-
* - Legacy methods can delegate to getWeightedCards() or be left as-is
|
|
59
|
+
* Content sources return scored candidates via getWeightedCards(), which
|
|
60
|
+
* SessionController uses to populate study queues.
|
|
100
61
|
*
|
|
101
|
-
* See: packages/db/
|
|
62
|
+
* See: packages/db/docs/navigators-architecture.md
|
|
102
63
|
*/
|
|
103
64
|
export interface StudyContentSource {
|
|
104
|
-
/**
|
|
105
|
-
* Get cards scheduled for review based on SRS algorithm.
|
|
106
|
-
*
|
|
107
|
-
* @deprecated Will be replaced by getWeightedCards() which returns scored candidates.
|
|
108
|
-
* Review urgency will be expressed as a score rather than a separate method.
|
|
109
|
-
*/
|
|
110
|
-
getPendingReviews(): Promise<(StudySessionReviewItem & ScheduledCard)[]>;
|
|
111
|
-
|
|
112
|
-
/**
|
|
113
|
-
* Get new cards for introduction, typically ordered by ELO proximity.
|
|
114
|
-
*
|
|
115
|
-
* @deprecated Will be replaced by getWeightedCards() which returns scored candidates.
|
|
116
|
-
* New card selection and scoring will be unified with review scoring.
|
|
117
|
-
*
|
|
118
|
-
* @param n - Maximum number of new cards to return
|
|
119
|
-
*/
|
|
120
|
-
getNewCards(n?: number): Promise<StudySessionNewItem[]>;
|
|
121
|
-
|
|
122
65
|
/**
|
|
123
66
|
* Get cards with suitability scores for presentation.
|
|
124
67
|
*
|
|
125
|
-
*
|
|
126
|
-
*
|
|
127
|
-
*
|
|
128
|
-
* The `source` field on WeightedCard indicates origin ('new' | 'review' | 'failed')
|
|
129
|
-
* for queue routing purposes during the migration period.
|
|
68
|
+
* Returns unified scored candidates that can be sorted and selected by SessionController.
|
|
69
|
+
* The card origin ('new' | 'review' | 'failed') is determined by provenance metadata.
|
|
130
70
|
*
|
|
131
71
|
* @param limit - Maximum number of cards to return
|
|
132
72
|
* @returns Cards sorted by score descending
|
|
133
73
|
*/
|
|
134
|
-
getWeightedCards
|
|
74
|
+
getWeightedCards(limit: number): Promise<WeightedCard[]>;
|
|
135
75
|
}
|
|
136
76
|
// #endregion docs_StudyContentSource
|
|
137
77
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { CourseConfig, CourseElo, DataShape, SkuilderCourseData } from '@vue-skuilder/common';
|
|
2
|
-
import {
|
|
2
|
+
import { StudyContentSource, StudySessionItem } from './contentSource';
|
|
3
3
|
import { TagStub, Tag, QualifiedCardID } from '../types/types-legacy';
|
|
4
4
|
import { DataLayerResult } from '../types/db';
|
|
5
5
|
import { NavigationStrategyManager } from './navigationStrategyManager';
|
|
@@ -26,7 +26,7 @@ export interface CourseInfo {
|
|
|
26
26
|
registeredUsers: number;
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
export interface CourseDBInterface extends NavigationStrategyManager {
|
|
29
|
+
export interface CourseDBInterface extends NavigationStrategyManager, StudyContentSource {
|
|
30
30
|
/**
|
|
31
31
|
* Get course config
|
|
32
32
|
*/
|
|
@@ -74,11 +74,6 @@ export interface CourseDBInterface extends NavigationStrategyManager {
|
|
|
74
74
|
*/
|
|
75
75
|
updateCardElo(cardId: string, elo: CourseElo): Promise<PouchDB.Core.Response>;
|
|
76
76
|
|
|
77
|
-
/**
|
|
78
|
-
* Get new cards for study
|
|
79
|
-
*/
|
|
80
|
-
getNewCards(limit?: number): Promise<StudySessionNewItem[]>;
|
|
81
|
-
|
|
82
77
|
/**
|
|
83
78
|
* Get cards centered at a particular ELO rating
|
|
84
79
|
*/
|
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
import { toCourseElo } from '@vue-skuilder/common';
|
|
2
2
|
import type { CourseDBInterface } from '../interfaces/courseDB';
|
|
3
3
|
import type { UserDBInterface } from '../interfaces/userDB';
|
|
4
|
-
import type { ScheduledCard } from '../types/user';
|
|
5
4
|
import { ContentNavigator } from './index';
|
|
6
5
|
import type { WeightedCard } from './index';
|
|
7
6
|
import type { CardFilter, FilterContext } from './filters/types';
|
|
8
7
|
import type { CardGenerator, GeneratorContext } from './generators/types';
|
|
9
|
-
import type { StudySessionNewItem, StudySessionReviewItem } from '../interfaces/contentSource';
|
|
10
8
|
import { logger } from '../../util/logger';
|
|
11
9
|
|
|
12
10
|
// ============================================================================
|
|
@@ -22,14 +20,11 @@ import { logger } from '../../util/logger';
|
|
|
22
20
|
* Shows generator and filter chain structure.
|
|
23
21
|
*/
|
|
24
22
|
function logPipelineConfig(generator: CardGenerator, filters: CardFilter[]): void {
|
|
25
|
-
const filterList =
|
|
26
|
-
? '\n - ' + filters.map(f => f.name).join('\n - ')
|
|
27
|
-
: ' none';
|
|
23
|
+
const filterList =
|
|
24
|
+
filters.length > 0 ? '\n - ' + filters.map((f) => f.name).join('\n - ') : ' none';
|
|
28
25
|
|
|
29
26
|
logger.info(
|
|
30
|
-
`[Pipeline] Configuration:\n` +
|
|
31
|
-
` Generator: ${generator.name}\n` +
|
|
32
|
-
` Filters:${filterList}`
|
|
27
|
+
`[Pipeline] Configuration:\n` + ` Generator: ${generator.name}\n` + ` Filters:${filterList}`
|
|
33
28
|
);
|
|
34
29
|
}
|
|
35
30
|
|
|
@@ -39,11 +34,11 @@ function logPipelineConfig(generator: CardGenerator, filters: CardFilter[]): voi
|
|
|
39
34
|
*/
|
|
40
35
|
function logTagHydration(cards: WeightedCard[], tagsByCard: Map<string, string[]>): void {
|
|
41
36
|
const totalTags = Array.from(tagsByCard.values()).reduce((sum, tags) => sum + tags.length, 0);
|
|
42
|
-
const cardsWithTags = Array.from(tagsByCard.values()).filter(tags => tags.length > 0).length;
|
|
37
|
+
const cardsWithTags = Array.from(tagsByCard.values()).filter((tags) => tags.length > 0).length;
|
|
43
38
|
|
|
44
39
|
logger.debug(
|
|
45
40
|
`[Pipeline] Tag hydration: ${cards.length} cards, ` +
|
|
46
|
-
|
|
41
|
+
`${cardsWithTags} have tags (${totalTags} total tags) - single batch query`
|
|
47
42
|
);
|
|
48
43
|
}
|
|
49
44
|
|
|
@@ -58,13 +53,12 @@ function logExecutionSummary(
|
|
|
58
53
|
finalCount: number,
|
|
59
54
|
topScores: number[]
|
|
60
55
|
): void {
|
|
61
|
-
const scoreDisplay =
|
|
62
|
-
? topScores.map(s => s.toFixed(2)).join(', ')
|
|
63
|
-
: 'none';
|
|
56
|
+
const scoreDisplay =
|
|
57
|
+
topScores.length > 0 ? topScores.map((s) => s.toFixed(2)).join(', ') : 'none';
|
|
64
58
|
|
|
65
59
|
logger.info(
|
|
66
60
|
`[Pipeline] Execution: ${generatorName} produced ${generatedCount} → ` +
|
|
67
|
-
|
|
61
|
+
`${filterCount} filters → ${finalCount} results (top scores: ${scoreDisplay})`
|
|
68
62
|
);
|
|
69
63
|
}
|
|
70
64
|
|
|
@@ -154,6 +148,14 @@ export class Pipeline extends ContentNavigator {
|
|
|
154
148
|
this.user = user;
|
|
155
149
|
this.course = course;
|
|
156
150
|
|
|
151
|
+
course
|
|
152
|
+
.getCourseConfig()
|
|
153
|
+
.then((cfg) => {
|
|
154
|
+
logger.debug(`[pipeline] Crated pipeline for ${cfg.name}`);
|
|
155
|
+
})
|
|
156
|
+
.catch((e) => {
|
|
157
|
+
logger.error(`[pipeline] Failed to lookup courseCfg: ${e}`);
|
|
158
|
+
});
|
|
157
159
|
// Toggle pipeline configuration logging:
|
|
158
160
|
logPipelineConfig(generator, filters);
|
|
159
161
|
}
|
|
@@ -210,8 +212,14 @@ export class Pipeline extends ContentNavigator {
|
|
|
210
212
|
const result = cards.slice(0, limit);
|
|
211
213
|
|
|
212
214
|
// Toggle execution summary logging:
|
|
213
|
-
const topScores = result.slice(0, 3).map(c => c.score);
|
|
214
|
-
logExecutionSummary(
|
|
215
|
+
const topScores = result.slice(0, 3).map((c) => c.score);
|
|
216
|
+
logExecutionSummary(
|
|
217
|
+
this.generator.name,
|
|
218
|
+
generatedCount,
|
|
219
|
+
this.filters.length,
|
|
220
|
+
result.length,
|
|
221
|
+
topScores
|
|
222
|
+
);
|
|
215
223
|
|
|
216
224
|
// Toggle provenance logging (shows scoring history for top cards):
|
|
217
225
|
logCardProvenance(result, 3);
|
|
@@ -272,43 +280,6 @@ export class Pipeline extends ContentNavigator {
|
|
|
272
280
|
};
|
|
273
281
|
}
|
|
274
282
|
|
|
275
|
-
// ===========================================================================
|
|
276
|
-
// Legacy StudyContentSource methods
|
|
277
|
-
// ===========================================================================
|
|
278
|
-
//
|
|
279
|
-
// These delegate to the generator for backward compatibility.
|
|
280
|
-
// Eventually SessionController will use getWeightedCards() exclusively.
|
|
281
|
-
//
|
|
282
|
-
|
|
283
|
-
/**
|
|
284
|
-
* Get new cards via legacy API.
|
|
285
|
-
* Delegates to the generator if it supports the legacy interface.
|
|
286
|
-
*/
|
|
287
|
-
async getNewCards(n?: number): Promise<StudySessionNewItem[]> {
|
|
288
|
-
// Check if generator has legacy method (ContentNavigator-based generators do)
|
|
289
|
-
if ('getNewCards' in this.generator && typeof this.generator.getNewCards === 'function') {
|
|
290
|
-
return (this.generator as ContentNavigator).getNewCards(n);
|
|
291
|
-
}
|
|
292
|
-
// Pure CardGenerator without legacy support - return empty
|
|
293
|
-
return [];
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
/**
|
|
297
|
-
* Get pending reviews via legacy API.
|
|
298
|
-
* Delegates to the generator if it supports the legacy interface.
|
|
299
|
-
*/
|
|
300
|
-
async getPendingReviews(): Promise<(StudySessionReviewItem & ScheduledCard)[]> {
|
|
301
|
-
// Check if generator has legacy method (ContentNavigator-based generators do)
|
|
302
|
-
if (
|
|
303
|
-
'getPendingReviews' in this.generator &&
|
|
304
|
-
typeof this.generator.getPendingReviews === 'function'
|
|
305
|
-
) {
|
|
306
|
-
return (this.generator as ContentNavigator).getPendingReviews();
|
|
307
|
-
}
|
|
308
|
-
// Pure CardGenerator without legacy support - return empty
|
|
309
|
-
return [];
|
|
310
|
-
}
|
|
311
|
-
|
|
312
283
|
/**
|
|
313
284
|
* Get the course ID for this pipeline.
|
|
314
285
|
*/
|
|
@@ -7,7 +7,7 @@ import { DocType } from '../types/types-legacy';
|
|
|
7
7
|
import { logger } from '../../util/logger';
|
|
8
8
|
import type { CourseDBInterface } from '../interfaces/courseDB';
|
|
9
9
|
import type { UserDBInterface } from '../interfaces/userDB';
|
|
10
|
-
import CompositeGenerator from './CompositeGenerator';
|
|
10
|
+
import CompositeGenerator from './generators/CompositeGenerator';
|
|
11
11
|
|
|
12
12
|
// ============================================================================
|
|
13
13
|
// PIPELINE ASSEMBLER
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { Navigators } from './index';
|
|
2
|
+
import { Pipeline } from './Pipeline';
|
|
3
|
+
import CompositeGenerator from './generators/CompositeGenerator';
|
|
4
|
+
import ELONavigator from './generators/elo';
|
|
5
|
+
import SRSNavigator from './generators/srs';
|
|
6
|
+
import { createEloDistanceFilter } from './filters/eloDistance';
|
|
7
|
+
import type { ContentNavigationStrategyData } from '../types/contentNavigationStrategy';
|
|
8
|
+
import { DocType } from '../types/types-legacy';
|
|
9
|
+
import type { CourseDBInterface, UserDBInterface } from '../interfaces';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Default navigation pipeline configuration.
|
|
13
|
+
*
|
|
14
|
+
* This module provides factory functions for creating the canonical default
|
|
15
|
+
* navigation pipeline used by both CouchDB and static course implementations.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Create default ELO navigation strategy data.
|
|
20
|
+
* Used when no custom strategies are configured.
|
|
21
|
+
*
|
|
22
|
+
* @param courseId - The course ID to associate with this strategy
|
|
23
|
+
* @returns Strategy data for default ELO navigation
|
|
24
|
+
*/
|
|
25
|
+
export function createDefaultEloStrategy(courseId: string): ContentNavigationStrategyData {
|
|
26
|
+
return {
|
|
27
|
+
_id: 'NAVIGATION_STRATEGY-ELO-default',
|
|
28
|
+
docType: DocType.NAVIGATION_STRATEGY,
|
|
29
|
+
name: 'ELO (default)',
|
|
30
|
+
description: 'Default ELO-based navigation strategy for new cards',
|
|
31
|
+
implementingClass: Navigators.ELO,
|
|
32
|
+
course: courseId,
|
|
33
|
+
serializedData: '',
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Create default SRS navigation strategy data.
|
|
39
|
+
* Used when no custom strategies are configured.
|
|
40
|
+
*
|
|
41
|
+
* @param courseId - The course ID to associate with this strategy
|
|
42
|
+
* @returns Strategy data for default SRS navigation
|
|
43
|
+
*/
|
|
44
|
+
export function createDefaultSrsStrategy(courseId: string): ContentNavigationStrategyData {
|
|
45
|
+
return {
|
|
46
|
+
_id: 'NAVIGATION_STRATEGY-SRS-default',
|
|
47
|
+
docType: DocType.NAVIGATION_STRATEGY,
|
|
48
|
+
name: 'SRS (default)',
|
|
49
|
+
description: 'Default SRS-based navigation strategy for reviews',
|
|
50
|
+
implementingClass: Navigators.SRS,
|
|
51
|
+
course: courseId,
|
|
52
|
+
serializedData: '',
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Creates the default navigation pipeline for courses with no configured strategies.
|
|
58
|
+
*
|
|
59
|
+
* Default: Pipeline(Composite(ELO, SRS), [eloDistanceFilter])
|
|
60
|
+
* - ELO generator: scores new cards by skill proximity
|
|
61
|
+
* - SRS generator: scores reviews by overdueness and interval recency
|
|
62
|
+
* - ELO distance filter: penalizes cards far from user's current level
|
|
63
|
+
*
|
|
64
|
+
* This is the canonical default configuration used when:
|
|
65
|
+
* - No navigation strategy documents exist in the course
|
|
66
|
+
* - PipelineAssembler fails to build from strategy documents
|
|
67
|
+
*
|
|
68
|
+
* @param user - User database interface for accessing user state
|
|
69
|
+
* @param course - Course database interface for accessing course data
|
|
70
|
+
* @returns Configured Pipeline ready for use
|
|
71
|
+
*/
|
|
72
|
+
export function createDefaultPipeline(
|
|
73
|
+
user: UserDBInterface,
|
|
74
|
+
course: CourseDBInterface
|
|
75
|
+
): Pipeline {
|
|
76
|
+
const courseId = course.getCourseID();
|
|
77
|
+
const eloNavigator = new ELONavigator(user, course, createDefaultEloStrategy(courseId));
|
|
78
|
+
const srsNavigator = new SRSNavigator(user, course, createDefaultSrsStrategy(courseId));
|
|
79
|
+
|
|
80
|
+
const compositeGenerator = new CompositeGenerator([eloNavigator, srsNavigator]);
|
|
81
|
+
const eloDistanceFilter = createEloDistanceFilter();
|
|
82
|
+
|
|
83
|
+
return new Pipeline(compositeGenerator, [eloDistanceFilter], user, course);
|
|
84
|
+
}
|
|
@@ -1,11 +1,9 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import type {
|
|
3
|
-
import
|
|
4
|
-
import {
|
|
5
|
-
import type {
|
|
6
|
-
import type {
|
|
7
|
-
import type { StudySessionReviewItem, StudySessionNewItem } from '..';
|
|
8
|
-
import type { CardFilter, FilterContext } from './filters/types';
|
|
1
|
+
import type { CourseDBInterface } from '../../interfaces/courseDB';
|
|
2
|
+
import type { UserDBInterface } from '../../interfaces/userDB';
|
|
3
|
+
import { ContentNavigator } from '../index';
|
|
4
|
+
import type { WeightedCard } from '../index';
|
|
5
|
+
import type { ContentNavigationStrategyData } from '../../types/contentNavigationStrategy';
|
|
6
|
+
import type { CardFilter, FilterContext } from './types';
|
|
9
7
|
import { toCourseElo } from '@vue-skuilder/common';
|
|
10
8
|
|
|
11
9
|
/**
|
|
@@ -53,7 +51,6 @@ const DEFAULT_MIN_COUNT = 3;
|
|
|
53
51
|
*/
|
|
54
52
|
export default class HierarchyDefinitionNavigator extends ContentNavigator implements CardFilter {
|
|
55
53
|
private config: HierarchyConfig;
|
|
56
|
-
private _strategyData: ContentNavigationStrategyData;
|
|
57
54
|
|
|
58
55
|
/** Human-readable name for CardFilter interface */
|
|
59
56
|
name: string;
|
|
@@ -61,12 +58,11 @@ export default class HierarchyDefinitionNavigator extends ContentNavigator imple
|
|
|
61
58
|
constructor(
|
|
62
59
|
user: UserDBInterface,
|
|
63
60
|
course: CourseDBInterface,
|
|
64
|
-
|
|
61
|
+
strategyData: ContentNavigationStrategyData
|
|
65
62
|
) {
|
|
66
|
-
super(user, course,
|
|
67
|
-
this.
|
|
68
|
-
this.
|
|
69
|
-
this.name = _strategyData.name || 'Hierarchy Definition';
|
|
63
|
+
super(user, course, strategyData);
|
|
64
|
+
this.config = this.parseConfig(strategyData.serializedData);
|
|
65
|
+
this.name = strategyData.name || 'Hierarchy Definition';
|
|
70
66
|
}
|
|
71
67
|
|
|
72
68
|
private parseConfig(serializedData: string): HierarchyConfig {
|
|
@@ -159,7 +155,7 @@ export default class HierarchyDefinitionNavigator extends ContentNavigator imple
|
|
|
159
155
|
*/
|
|
160
156
|
private async checkCardUnlock(
|
|
161
157
|
card: WeightedCard,
|
|
162
|
-
|
|
158
|
+
_course: CourseDBInterface,
|
|
163
159
|
unlockedTags: Set<string>,
|
|
164
160
|
masteredTags: Set<string>
|
|
165
161
|
): Promise<{ isUnlocked: boolean; reason: string }> {
|
|
@@ -253,14 +249,4 @@ export default class HierarchyDefinitionNavigator extends ContentNavigator imple
|
|
|
253
249
|
'Use Pipeline with a generator and this filter via transform().'
|
|
254
250
|
);
|
|
255
251
|
}
|
|
256
|
-
|
|
257
|
-
// Legacy methods - stub implementations since filters don't generate cards
|
|
258
|
-
|
|
259
|
-
async getNewCards(_n?: number): Promise<StudySessionNewItem[]> {
|
|
260
|
-
return [];
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
async getPendingReviews(): Promise<(StudySessionReviewItem & ScheduledCard)[]> {
|
|
264
|
-
return [];
|
|
265
|
-
}
|
|
266
252
|
}
|
|
@@ -1,11 +1,9 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import type {
|
|
3
|
-
import
|
|
4
|
-
import {
|
|
5
|
-
import type {
|
|
6
|
-
import type {
|
|
7
|
-
import type { StudySessionReviewItem, StudySessionNewItem } from '..';
|
|
8
|
-
import type { CardFilter, FilterContext } from './filters/types';
|
|
1
|
+
import type { CourseDBInterface } from '../../interfaces/courseDB';
|
|
2
|
+
import type { UserDBInterface } from '../../interfaces/userDB';
|
|
3
|
+
import { ContentNavigator } from '../index';
|
|
4
|
+
import type { WeightedCard } from '../index';
|
|
5
|
+
import type { ContentNavigationStrategyData } from '../../types/contentNavigationStrategy';
|
|
6
|
+
import type { CardFilter, FilterContext } from './types';
|
|
9
7
|
import { toCourseElo } from '@vue-skuilder/common';
|
|
10
8
|
|
|
11
9
|
/**
|
|
@@ -80,7 +78,6 @@ const DEFAULT_INTERFERENCE_DECAY = 0.8;
|
|
|
80
78
|
*/
|
|
81
79
|
export default class InterferenceMitigatorNavigator extends ContentNavigator implements CardFilter {
|
|
82
80
|
private config: InterferenceConfig;
|
|
83
|
-
private _strategyData: ContentNavigationStrategyData;
|
|
84
81
|
|
|
85
82
|
/** Human-readable name for CardFilter interface */
|
|
86
83
|
name: string;
|
|
@@ -91,13 +88,12 @@ export default class InterferenceMitigatorNavigator extends ContentNavigator imp
|
|
|
91
88
|
constructor(
|
|
92
89
|
user: UserDBInterface,
|
|
93
90
|
course: CourseDBInterface,
|
|
94
|
-
|
|
91
|
+
strategyData: ContentNavigationStrategyData
|
|
95
92
|
) {
|
|
96
|
-
super(user, course,
|
|
97
|
-
this.
|
|
98
|
-
this.config = this.parseConfig(_strategyData.serializedData);
|
|
93
|
+
super(user, course, strategyData);
|
|
94
|
+
this.config = this.parseConfig(strategyData.serializedData);
|
|
99
95
|
this.interferenceMap = this.buildInterferenceMap();
|
|
100
|
-
this.name =
|
|
96
|
+
this.name = strategyData.name || 'Interference Mitigator';
|
|
101
97
|
}
|
|
102
98
|
|
|
103
99
|
private parseConfig(serializedData: string): InterferenceConfig {
|
|
@@ -342,14 +338,4 @@ export default class InterferenceMitigatorNavigator extends ContentNavigator imp
|
|
|
342
338
|
'Use Pipeline with a generator and this filter via transform().'
|
|
343
339
|
);
|
|
344
340
|
}
|
|
345
|
-
|
|
346
|
-
// Legacy methods - stub implementations since filters don't generate cards
|
|
347
|
-
|
|
348
|
-
async getNewCards(_n?: number): Promise<StudySessionNewItem[]> {
|
|
349
|
-
return [];
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
async getPendingReviews(): Promise<(StudySessionReviewItem & ScheduledCard)[]> {
|
|
353
|
-
return [];
|
|
354
|
-
}
|
|
355
341
|
}
|