@vue-skuilder/db 0.1.23 → 0.1.25
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/{contentSource-BP9hznNV.d.ts → contentSource-BmnmvH8C.d.ts} +268 -3
- package/dist/{contentSource-DsJadoBU.d.cts → contentSource-DfBbaLA-.d.cts} +268 -3
- package/dist/core/index.d.cts +310 -6
- package/dist/core/index.d.ts +310 -6
- package/dist/core/index.js +2606 -666
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.mjs +2564 -639
- package/dist/core/index.mjs.map +1 -1
- package/dist/{dataLayerProvider-CHYrQ5pB.d.cts → dataLayerProvider-BeRXVMs5.d.cts} +1 -1
- package/dist/{dataLayerProvider-MDTxXq2l.d.ts → dataLayerProvider-CG9GfaAY.d.ts} +1 -1
- package/dist/impl/couch/index.d.cts +11 -3
- package/dist/impl/couch/index.d.ts +11 -3
- package/dist/impl/couch/index.js +2336 -656
- package/dist/impl/couch/index.js.map +1 -1
- package/dist/impl/couch/index.mjs +2316 -631
- package/dist/impl/couch/index.mjs.map +1 -1
- package/dist/impl/static/index.d.cts +4 -4
- package/dist/impl/static/index.d.ts +4 -4
- package/dist/impl/static/index.js +2312 -632
- package/dist/impl/static/index.js.map +1 -1
- package/dist/impl/static/index.mjs +2315 -630
- package/dist/impl/static/index.mjs.map +1 -1
- package/dist/{index-Dj0SEgk3.d.ts → index-BWvO-_rJ.d.ts} +1 -1
- package/dist/{index-B_j6u5E4.d.cts → index-Ba7hYbHj.d.cts} +1 -1
- package/dist/index.d.cts +278 -20
- package/dist/index.d.ts +278 -20
- package/dist/index.js +3603 -720
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +3529 -674
- package/dist/index.mjs.map +1 -1
- package/dist/{types-DQaXnuoc.d.ts → types-CJrLM1Ew.d.ts} +1 -1
- package/dist/{types-Bn0itutr.d.cts → types-W8n-B6HG.d.cts} +1 -1
- package/dist/{types-legacy-DDY4N-Uq.d.cts → types-legacy-JXDxinpU.d.cts} +5 -1
- package/dist/{types-legacy-DDY4N-Uq.d.ts → types-legacy-JXDxinpU.d.ts} +5 -1
- package/dist/util/packer/index.d.cts +3 -3
- package/dist/util/packer/index.d.ts +3 -3
- package/docs/brainstorm-navigation-paradigm.md +40 -34
- package/docs/future-orchestration-vision.md +216 -0
- package/docs/navigators-architecture.md +210 -9
- package/docs/todo-review-urgency-adaptation.md +205 -0
- package/docs/todo-strategy-authoring.md +8 -6
- package/package.json +3 -3
- package/src/core/index.ts +2 -0
- package/src/core/interfaces/contentSource.ts +7 -0
- package/src/core/interfaces/userDB.ts +50 -0
- package/src/core/navigators/Pipeline.ts +132 -5
- package/src/core/navigators/PipelineAssembler.ts +21 -22
- package/src/core/navigators/PipelineDebugger.ts +426 -0
- package/src/core/navigators/filters/WeightedFilter.ts +141 -0
- package/src/core/navigators/filters/types.ts +4 -0
- package/src/core/navigators/generators/CompositeGenerator.ts +82 -19
- package/src/core/navigators/generators/elo.ts +14 -1
- package/src/core/navigators/generators/srs.ts +146 -18
- package/src/core/navigators/generators/types.ts +4 -0
- package/src/core/navigators/index.ts +203 -13
- package/src/core/orchestration/gradient.ts +133 -0
- package/src/core/orchestration/index.ts +210 -0
- package/src/core/orchestration/learning.ts +250 -0
- package/src/core/orchestration/recording.ts +92 -0
- package/src/core/orchestration/signal.ts +67 -0
- package/src/core/types/contentNavigationStrategy.ts +38 -0
- package/src/core/types/learningState.ts +77 -0
- package/src/core/types/types-legacy.ts +4 -0
- package/src/core/types/userOutcome.ts +51 -0
- package/src/courseConfigRegistration.ts +107 -0
- package/src/factory.ts +6 -0
- package/src/impl/common/BaseUserDB.ts +16 -0
- package/src/impl/couch/user-course-relDB.ts +12 -0
- package/src/study/MixerDebugger.ts +555 -0
- package/src/study/SessionController.ts +159 -20
- package/src/study/SessionDebugger.ts +442 -0
- package/src/study/SourceMixer.ts +36 -17
- package/src/study/TODO-session-scheduling.md +133 -0
- package/src/study/index.ts +2 -0
- package/src/study/services/EloService.ts +79 -4
- package/src/study/services/ResponseProcessor.ts +130 -72
- package/src/study/services/SrsService.ts +9 -0
- package/tests/core/navigators/Pipeline.test.ts +2 -0
- package/tests/core/navigators/PipelineAssembler.test.ts +4 -4
- package/docs/todo-evolutionary-orchestration.md +0 -310
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
import { CourseElo, Status } from '@vue-skuilder/common';
|
|
8
8
|
import { Moment } from 'moment';
|
|
9
9
|
import { CardHistory, CardRecord, QualifiedCardID } from '../types/types-legacy';
|
|
10
|
+
import { UserOutcomeRecord } from '../types/userOutcome';
|
|
10
11
|
import { UserConfig } from '../types/user';
|
|
11
12
|
import { DocumentUpdater } from '@db/study';
|
|
12
13
|
|
|
@@ -62,6 +63,11 @@ export interface UserDBReader {
|
|
|
62
63
|
* Strategies use this to persist preferences, learned patterns, or temporal
|
|
63
64
|
* tracking data across sessions. Each strategy owns its own namespace.
|
|
64
65
|
*
|
|
66
|
+
* @deprecated Use `getCourseInterface(courseId).getStrategyState(strategyKey)` instead.
|
|
67
|
+
* Direct use bypasses course-scoping safety — the courseId parameter is unguarded,
|
|
68
|
+
* allowing accidental cross-course data access. The course-scoped interface binds
|
|
69
|
+
* courseId once at construction.
|
|
70
|
+
*
|
|
65
71
|
* @param courseId - The course this state applies to
|
|
66
72
|
* @param strategyKey - Unique key identifying the strategy (typically class name)
|
|
67
73
|
* @returns The strategy's data payload, or null if no state exists
|
|
@@ -151,6 +157,11 @@ export interface UserDBWriter extends DocumentUpdater {
|
|
|
151
157
|
* Strategies use this to persist preferences, learned patterns, or temporal
|
|
152
158
|
* tracking data across sessions. Each strategy owns its own namespace.
|
|
153
159
|
*
|
|
160
|
+
* @deprecated Use `getCourseInterface(courseId).putStrategyState(strategyKey, data)` instead.
|
|
161
|
+
* Direct use bypasses course-scoping safety — the courseId parameter is unguarded,
|
|
162
|
+
* allowing accidental cross-course data writes. The course-scoped interface binds
|
|
163
|
+
* courseId once at construction.
|
|
164
|
+
*
|
|
154
165
|
* @param courseId - The course this state applies to
|
|
155
166
|
* @param strategyKey - Unique key identifying the strategy (typically class name)
|
|
156
167
|
* @param data - The strategy's data payload to store
|
|
@@ -160,10 +171,18 @@ export interface UserDBWriter extends DocumentUpdater {
|
|
|
160
171
|
/**
|
|
161
172
|
* Delete strategy-specific state for a course.
|
|
162
173
|
*
|
|
174
|
+
* @deprecated Use `getCourseInterface(courseId).deleteStrategyState(strategyKey)` instead.
|
|
175
|
+
* Direct use bypasses course-scoping safety.
|
|
176
|
+
*
|
|
163
177
|
* @param courseId - The course this state applies to
|
|
164
178
|
* @param strategyKey - Unique key identifying the strategy (typically class name)
|
|
165
179
|
*/
|
|
166
180
|
deleteStrategyState(courseId: string, strategyKey: string): Promise<void>;
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Record a user learning outcome for evolutionary orchestration.
|
|
184
|
+
*/
|
|
185
|
+
putUserOutcome(record: UserOutcomeRecord): Promise<void>;
|
|
167
186
|
}
|
|
168
187
|
|
|
169
188
|
/**
|
|
@@ -222,6 +241,37 @@ export interface UsrCrsDataInterface {
|
|
|
222
241
|
getCourseSettings(): Promise<UserCourseSettings>;
|
|
223
242
|
updateCourseSettings(updates: UserCourseSetting[]): void; // [ ] return a result of some sort?
|
|
224
243
|
// getRegistrationDoc(): Promise<CourseRegistration>;
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Get strategy-specific state for this course.
|
|
247
|
+
*
|
|
248
|
+
* Course-scoped alternative to `UserDBInterface.getStrategyState()`.
|
|
249
|
+
* The courseId is bound at construction via `getCourseInterface(courseId)`,
|
|
250
|
+
* so callers cannot accidentally access another course's state.
|
|
251
|
+
*
|
|
252
|
+
* @param strategyKey - Unique key identifying the state document
|
|
253
|
+
* @returns The state payload, or null if no state exists
|
|
254
|
+
*/
|
|
255
|
+
getStrategyState<T>(strategyKey: string): Promise<T | null>;
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Store strategy-specific state for this course.
|
|
259
|
+
*
|
|
260
|
+
* Course-scoped alternative to `UserDBInterface.putStrategyState()`.
|
|
261
|
+
*
|
|
262
|
+
* @param strategyKey - Unique key identifying the state document
|
|
263
|
+
* @param data - The state payload to store
|
|
264
|
+
*/
|
|
265
|
+
putStrategyState<T>(strategyKey: string, data: T): Promise<void>;
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Delete strategy-specific state for this course.
|
|
269
|
+
*
|
|
270
|
+
* Course-scoped alternative to `UserDBInterface.deleteStrategyState()`.
|
|
271
|
+
*
|
|
272
|
+
* @param strategyKey - Unique key identifying the state document
|
|
273
|
+
*/
|
|
274
|
+
deleteStrategyState(strategyKey: string): Promise<void>;
|
|
225
275
|
}
|
|
226
276
|
|
|
227
277
|
export type ClassroomRegistrationDesignation = 'student' | 'teacher' | 'aide' | 'admin';
|
|
@@ -6,6 +6,8 @@ import type { WeightedCard } from './index';
|
|
|
6
6
|
import type { CardFilter, FilterContext } from './filters/types';
|
|
7
7
|
import type { CardGenerator, GeneratorContext } from './generators/types';
|
|
8
8
|
import { logger } from '../../util/logger';
|
|
9
|
+
import { createOrchestrationContext, OrchestrationContext } from '../orchestration';
|
|
10
|
+
import { captureRun, buildRunReport, type GeneratorSummary, type FilterImpact } from './PipelineDebugger';
|
|
9
11
|
|
|
10
12
|
// ============================================================================
|
|
11
13
|
// PIPELINE LOGGING HELPERS
|
|
@@ -51,14 +53,29 @@ function logExecutionSummary(
|
|
|
51
53
|
generatedCount: number,
|
|
52
54
|
filterCount: number,
|
|
53
55
|
finalCount: number,
|
|
54
|
-
topScores: number[]
|
|
56
|
+
topScores: number[],
|
|
57
|
+
filterImpacts: Array<{ name: string; boosted: number; penalized: number; passed: number }>
|
|
55
58
|
): void {
|
|
56
59
|
const scoreDisplay =
|
|
57
60
|
topScores.length > 0 ? topScores.map((s) => s.toFixed(2)).join(', ') : 'none';
|
|
58
61
|
|
|
62
|
+
let filterSummary = '';
|
|
63
|
+
if (filterImpacts.length > 0) {
|
|
64
|
+
const impacts = filterImpacts.map((f) => {
|
|
65
|
+
const parts: string[] = [];
|
|
66
|
+
if (f.boosted > 0) parts.push(`+${f.boosted}`);
|
|
67
|
+
if (f.penalized > 0) parts.push(`-${f.penalized}`);
|
|
68
|
+
if (f.passed > 0) parts.push(`=${f.passed}`);
|
|
69
|
+
return `${f.name}: ${parts.join('/')}`;
|
|
70
|
+
});
|
|
71
|
+
filterSummary = `\n Filter impact: ${impacts.join(', ')}`;
|
|
72
|
+
}
|
|
73
|
+
|
|
59
74
|
logger.info(
|
|
60
75
|
`[Pipeline] Execution: ${generatorName} produced ${generatedCount} → ` +
|
|
61
|
-
`${filterCount} filters → ${finalCount} results (top scores: ${scoreDisplay})`
|
|
76
|
+
`${filterCount} filters → ${finalCount} results (top scores: ${scoreDisplay})` +
|
|
77
|
+
filterSummary +
|
|
78
|
+
`\n 💡 Inspect: window.skuilder.pipeline`
|
|
62
79
|
);
|
|
63
80
|
}
|
|
64
81
|
|
|
@@ -189,17 +206,63 @@ export class Pipeline extends ContentNavigator {
|
|
|
189
206
|
// Get candidates from generator, passing context
|
|
190
207
|
let cards = await this.generator.getWeightedCards(fetchLimit, context);
|
|
191
208
|
const generatedCount = cards.length;
|
|
209
|
+
|
|
210
|
+
// Capture generator breakdown for debugging (if CompositeGenerator)
|
|
211
|
+
let generatorSummaries: GeneratorSummary[] | undefined;
|
|
212
|
+
if ((this.generator as any).generators) {
|
|
213
|
+
// This is a CompositeGenerator - extract per-generator info from provenance
|
|
214
|
+
const genMap = new Map<string, { cards: WeightedCard[] }>();
|
|
215
|
+
for (const card of cards) {
|
|
216
|
+
const firstProv = card.provenance[0];
|
|
217
|
+
if (firstProv) {
|
|
218
|
+
const genName = firstProv.strategyName;
|
|
219
|
+
if (!genMap.has(genName)) {
|
|
220
|
+
genMap.set(genName, { cards: [] });
|
|
221
|
+
}
|
|
222
|
+
genMap.get(genName)!.cards.push(card);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
generatorSummaries = Array.from(genMap.entries()).map(([name, data]) => {
|
|
226
|
+
const newCards = data.cards.filter((c) => c.provenance[0]?.reason?.includes('new card'));
|
|
227
|
+
const reviewCards = data.cards.filter((c) => c.provenance[0]?.reason?.includes('review'));
|
|
228
|
+
return {
|
|
229
|
+
name,
|
|
230
|
+
cardCount: data.cards.length,
|
|
231
|
+
newCount: newCards.length,
|
|
232
|
+
reviewCount: reviewCards.length,
|
|
233
|
+
topScore: Math.max(...data.cards.map((c) => c.score), 0),
|
|
234
|
+
};
|
|
235
|
+
});
|
|
236
|
+
}
|
|
192
237
|
|
|
193
238
|
logger.debug(`[Pipeline] Generator returned ${generatedCount} candidates`);
|
|
194
239
|
|
|
195
240
|
// Batch hydrate tags before filters run
|
|
196
241
|
cards = await this.hydrateTags(cards);
|
|
242
|
+
|
|
243
|
+
// Keep a copy of all cards for debug capture (before filtering removes any)
|
|
244
|
+
const allCardsBeforeFiltering = [...cards];
|
|
197
245
|
|
|
198
|
-
// Apply filters sequentially
|
|
246
|
+
// Apply filters sequentially, tracking impact
|
|
247
|
+
const filterImpacts: FilterImpact[] = [];
|
|
199
248
|
for (const filter of this.filters) {
|
|
200
249
|
const beforeCount = cards.length;
|
|
250
|
+
const beforeScores = new Map(cards.map((c) => [c.cardId, c.score]));
|
|
201
251
|
cards = await filter.transform(cards, context);
|
|
202
|
-
|
|
252
|
+
|
|
253
|
+
// Count boost/penalize/pass/removed for this filter
|
|
254
|
+
let boosted = 0, penalized = 0, passed = 0;
|
|
255
|
+
const removed = beforeCount - cards.length;
|
|
256
|
+
|
|
257
|
+
for (const card of cards) {
|
|
258
|
+
const before = beforeScores.get(card.cardId) ?? 0;
|
|
259
|
+
if (card.score > before) boosted++;
|
|
260
|
+
else if (card.score < before) penalized++;
|
|
261
|
+
else passed++;
|
|
262
|
+
}
|
|
263
|
+
filterImpacts.push({ name: filter.name, boosted, penalized, passed, removed });
|
|
264
|
+
|
|
265
|
+
logger.debug(`[Pipeline] Filter '${filter.name}': ${beforeScores.size} → ${cards.length} cards (↑${boosted} ↓${penalized} =${passed})`);
|
|
203
266
|
}
|
|
204
267
|
|
|
205
268
|
// Remove zero-score cards (hard filtered)
|
|
@@ -218,12 +281,31 @@ export class Pipeline extends ContentNavigator {
|
|
|
218
281
|
generatedCount,
|
|
219
282
|
this.filters.length,
|
|
220
283
|
result.length,
|
|
221
|
-
topScores
|
|
284
|
+
topScores,
|
|
285
|
+
filterImpacts
|
|
222
286
|
);
|
|
223
287
|
|
|
224
288
|
// Toggle provenance logging (shows scoring history for top cards):
|
|
225
289
|
logCardProvenance(result, 3);
|
|
226
290
|
|
|
291
|
+
// Capture run for debug API
|
|
292
|
+
try {
|
|
293
|
+
const courseName = await this.course?.getCourseConfig().then((c) => c.name).catch(() => undefined);
|
|
294
|
+
const report = buildRunReport(
|
|
295
|
+
this.course?.getCourseID() || 'unknown',
|
|
296
|
+
courseName,
|
|
297
|
+
this.generator.name,
|
|
298
|
+
generatorSummaries,
|
|
299
|
+
generatedCount,
|
|
300
|
+
filterImpacts,
|
|
301
|
+
allCardsBeforeFiltering,
|
|
302
|
+
result
|
|
303
|
+
);
|
|
304
|
+
captureRun(report);
|
|
305
|
+
} catch (e) {
|
|
306
|
+
logger.debug(`[Pipeline] Failed to capture debug run: ${e}`);
|
|
307
|
+
}
|
|
308
|
+
|
|
227
309
|
return result;
|
|
228
310
|
}
|
|
229
311
|
|
|
@@ -273,10 +355,14 @@ export class Pipeline extends ContentNavigator {
|
|
|
273
355
|
logger.debug(`[Pipeline] Could not get user ELO, using default: ${e}`);
|
|
274
356
|
}
|
|
275
357
|
|
|
358
|
+
// Initialize orchestration context (used for evolutionary weighting)
|
|
359
|
+
const orchestration = await createOrchestrationContext(this.user!, this.course!);
|
|
360
|
+
|
|
276
361
|
return {
|
|
277
362
|
user: this.user!,
|
|
278
363
|
course: this.course!,
|
|
279
364
|
userElo,
|
|
365
|
+
orchestration,
|
|
280
366
|
};
|
|
281
367
|
}
|
|
282
368
|
|
|
@@ -286,4 +372,45 @@ export class Pipeline extends ContentNavigator {
|
|
|
286
372
|
getCourseID(): string {
|
|
287
373
|
return this.course!.getCourseID();
|
|
288
374
|
}
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* Get orchestration context for outcome recording.
|
|
378
|
+
*/
|
|
379
|
+
async getOrchestrationContext(): Promise<OrchestrationContext> {
|
|
380
|
+
return createOrchestrationContext(this.user!, this.course!);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* Get IDs of all strategies in this pipeline.
|
|
385
|
+
* Used to record which strategies contributed to an outcome.
|
|
386
|
+
*/
|
|
387
|
+
getStrategyIds(): string[] {
|
|
388
|
+
const ids: string[] = [];
|
|
389
|
+
|
|
390
|
+
const extractId = (obj: any): string | null => {
|
|
391
|
+
// Check for strategyId property (ContentNavigator, WeightedFilter)
|
|
392
|
+
if (obj.strategyId) return obj.strategyId;
|
|
393
|
+
return null;
|
|
394
|
+
};
|
|
395
|
+
|
|
396
|
+
// Generator(s)
|
|
397
|
+
const genId = extractId(this.generator);
|
|
398
|
+
if (genId) ids.push(genId);
|
|
399
|
+
|
|
400
|
+
// Inspect CompositeGenerator children (accessing private field via cast)
|
|
401
|
+
if ((this.generator as any).generators && Array.isArray((this.generator as any).generators)) {
|
|
402
|
+
(this.generator as any).generators.forEach((g: any) => {
|
|
403
|
+
const subId = extractId(g);
|
|
404
|
+
if (subId) ids.push(subId);
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// Filters
|
|
409
|
+
for (const filter of this.filters) {
|
|
410
|
+
const fId = extractId(filter);
|
|
411
|
+
if (fId) ids.push(fId);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
return [...new Set(ids)];
|
|
415
|
+
}
|
|
289
416
|
}
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import type { ContentNavigationStrategyData } from '../types/contentNavigationStrategy';
|
|
2
|
-
import { ContentNavigator, isGenerator, isFilter
|
|
2
|
+
import { ContentNavigator, isGenerator, isFilter } from './index';
|
|
3
3
|
import type { CardFilter } from './filters/types';
|
|
4
|
+
import { WeightedFilter } from './filters/WeightedFilter';
|
|
4
5
|
import type { CardGenerator } from './generators/types';
|
|
5
6
|
import { Pipeline } from './Pipeline';
|
|
6
|
-
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
10
|
import CompositeGenerator from './generators/CompositeGenerator';
|
|
11
|
+
import { createDefaultEloStrategy, createDefaultSrsStrategy } from './defaults';
|
|
11
12
|
|
|
12
13
|
// ============================================================================
|
|
13
14
|
// PIPELINE ASSEMBLER
|
|
@@ -102,13 +103,15 @@ export class PipelineAssembler {
|
|
|
102
103
|
}
|
|
103
104
|
}
|
|
104
105
|
|
|
105
|
-
// If no generator but filters exist, use default ELO
|
|
106
|
+
// If no generator but filters exist, use default ELO and SRS generators
|
|
106
107
|
if (generatorStrategies.length === 0) {
|
|
107
108
|
if (filterStrategies.length > 0) {
|
|
108
109
|
logger.debug(
|
|
109
|
-
'[PipelineAssembler] No generator found, using default ELO with configured filters'
|
|
110
|
+
'[PipelineAssembler] No generator found, using default ELO and SRS with configured filters'
|
|
110
111
|
);
|
|
111
|
-
|
|
112
|
+
const courseId = course.getCourseID();
|
|
113
|
+
generatorStrategies.push(createDefaultEloStrategy(courseId));
|
|
114
|
+
generatorStrategies.push(createDefaultSrsStrategy(courseId));
|
|
112
115
|
} else {
|
|
113
116
|
warnings.push('No generator strategy found');
|
|
114
117
|
return {
|
|
@@ -149,7 +152,19 @@ export class PipelineAssembler {
|
|
|
149
152
|
const nav = await ContentNavigator.create(user, course, filterStrategy);
|
|
150
153
|
// The navigator implements CardFilter
|
|
151
154
|
if ('transform' in nav && typeof nav.transform === 'function') {
|
|
152
|
-
|
|
155
|
+
let filter = nav as unknown as CardFilter;
|
|
156
|
+
|
|
157
|
+
// Apply evolutionary weighting wrapper if configured
|
|
158
|
+
if (filterStrategy.learnable) {
|
|
159
|
+
filter = new WeightedFilter(
|
|
160
|
+
filter,
|
|
161
|
+
filterStrategy.learnable,
|
|
162
|
+
filterStrategy.staticWeight,
|
|
163
|
+
filterStrategy._id
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
filters.push(filter);
|
|
153
168
|
logger.debug(`[PipelineAssembler] Added filter: ${filterStrategy.name}`);
|
|
154
169
|
} else {
|
|
155
170
|
warnings.push(
|
|
@@ -175,20 +190,4 @@ export class PipelineAssembler {
|
|
|
175
190
|
warnings,
|
|
176
191
|
};
|
|
177
192
|
}
|
|
178
|
-
|
|
179
|
-
/**
|
|
180
|
-
* Creates a default ELO generator strategy.
|
|
181
|
-
* Used when filters are configured but no generator is specified.
|
|
182
|
-
*/
|
|
183
|
-
private makeDefaultEloStrategy(courseId: string): ContentNavigationStrategyData {
|
|
184
|
-
return {
|
|
185
|
-
_id: 'NAVIGATION_STRATEGY-ELO-default',
|
|
186
|
-
course: courseId,
|
|
187
|
-
docType: DocType.NAVIGATION_STRATEGY,
|
|
188
|
-
name: 'ELO (default)',
|
|
189
|
-
description: 'Default ELO-based generator',
|
|
190
|
-
implementingClass: Navigators.ELO,
|
|
191
|
-
serializedData: '',
|
|
192
|
-
};
|
|
193
|
-
}
|
|
194
193
|
}
|