@vue-skuilder/db 0.1.31 → 0.1.32-b
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/core/index.js +17 -4
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.mjs +17 -4
- package/dist/core/index.mjs.map +1 -1
- package/dist/impl/couch/index.d.cts +2 -0
- package/dist/impl/couch/index.d.ts +2 -0
- package/dist/impl/couch/index.js +17 -4
- package/dist/impl/couch/index.js.map +1 -1
- package/dist/impl/couch/index.mjs +17 -4
- package/dist/impl/couch/index.mjs.map +1 -1
- package/dist/impl/static/index.d.cts +2 -0
- package/dist/impl/static/index.d.ts +2 -0
- package/dist/impl/static/index.js +17 -4
- package/dist/impl/static/index.js.map +1 -1
- package/dist/impl/static/index.mjs +17 -4
- package/dist/impl/static/index.mjs.map +1 -1
- package/dist/index.d.cts +74 -4
- package/dist/index.d.ts +74 -4
- package/dist/index.js +139 -21
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +139 -21
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
- package/src/core/navigators/Pipeline.ts +11 -3
- package/src/core/navigators/filters/hierarchyDefinition.ts +4 -0
- package/src/core/navigators/filters/relativePriority.ts +7 -1
- package/src/impl/couch/courseDB.ts +10 -0
- package/src/impl/static/courseDB.ts +12 -0
- package/src/study/SessionController.ts +210 -20
- package/src/study/services/ResponseProcessor.ts +22 -2
package/dist/index.d.cts
CHANGED
|
@@ -307,6 +307,34 @@ declare class QuotaRoundRobinMixer implements SourceMixer {
|
|
|
307
307
|
mix(batches: SourceBatch[], limit: number): WeightedCard[];
|
|
308
308
|
}
|
|
309
309
|
|
|
310
|
+
/**
|
|
311
|
+
* Options for requesting a mid-session replan.
|
|
312
|
+
*
|
|
313
|
+
* All fields are optional — callers can pass just the fields they need.
|
|
314
|
+
* When omitted, defaults match the existing behaviour (full 20-card
|
|
315
|
+
* replace with no hints).
|
|
316
|
+
*/
|
|
317
|
+
interface ReplanOptions {
|
|
318
|
+
/** Scoring hints forwarded to the pipeline (boost/exclude/require). */
|
|
319
|
+
hints?: Record<string, unknown>;
|
|
320
|
+
/**
|
|
321
|
+
* Maximum number of new cards to return from the pipeline.
|
|
322
|
+
* Default: 20 (the standard session batch size).
|
|
323
|
+
*/
|
|
324
|
+
limit?: number;
|
|
325
|
+
/**
|
|
326
|
+
* How to integrate the new cards into the existing newQ.
|
|
327
|
+
* - `'replace'` (default): atomically swap the entire newQ.
|
|
328
|
+
* - `'merge'`: insert new cards at the front, keeping existing cards.
|
|
329
|
+
*/
|
|
330
|
+
mode?: 'replace' | 'merge';
|
|
331
|
+
/**
|
|
332
|
+
* Human-readable label for debugging / provenance.
|
|
333
|
+
* Appears in console logs and in card provenance entries created
|
|
334
|
+
* by ephemeral hint application.
|
|
335
|
+
*/
|
|
336
|
+
label?: string;
|
|
337
|
+
}
|
|
310
338
|
interface StudySessionRecord {
|
|
311
339
|
card: {
|
|
312
340
|
course_id: string;
|
|
@@ -321,6 +349,13 @@ type SessionAction = 'dismiss-success' | 'dismiss-failed' | 'marked-failed' | 'd
|
|
|
321
349
|
interface ResponseResult {
|
|
322
350
|
nextCardAction: Exclude<SessionAction, 'dismiss-error'> | 'none';
|
|
323
351
|
shouldLoadNextCard: boolean;
|
|
352
|
+
/**
|
|
353
|
+
* When true, the card requested deferred advancement via `deferAdvance`.
|
|
354
|
+
* The record was logged and ELO updated, but navigation was suppressed.
|
|
355
|
+
* StudySession should stash `nextCardAction` and wait for a
|
|
356
|
+
* `ready-to-advance` event from the card before calling `nextCard()`.
|
|
357
|
+
*/
|
|
358
|
+
deferred?: boolean;
|
|
324
359
|
isCorrect: boolean;
|
|
325
360
|
performanceScore?: number;
|
|
326
361
|
shouldClearFeedbackShadow: boolean;
|
|
@@ -337,6 +372,12 @@ declare class SessionController<TView = unknown> extends Loggable {
|
|
|
337
372
|
private mixer;
|
|
338
373
|
private dataLayer;
|
|
339
374
|
private courseNameCache;
|
|
375
|
+
/**
|
|
376
|
+
* Default pipeline batch size for new-card planning.
|
|
377
|
+
* Set via constructor options; falls back to 20 when not specified.
|
|
378
|
+
* Individual replans can override via `ReplanOptions.limit`.
|
|
379
|
+
*/
|
|
380
|
+
private _defaultBatchLimit;
|
|
340
381
|
private sources;
|
|
341
382
|
private _sessionRecord;
|
|
342
383
|
set sessionRecord(r: StudySessionRecord[]);
|
|
@@ -356,6 +397,20 @@ declare class SessionController<TView = unknown> extends Loggable {
|
|
|
356
397
|
* (user state has changed from completing good cards).
|
|
357
398
|
*/
|
|
358
399
|
private _wellIndicatedRemaining;
|
|
400
|
+
/**
|
|
401
|
+
* When true, suppresses the quality-based auto-replan trigger in
|
|
402
|
+
* nextCard(). Set after a burst replan (small limit) to prevent the
|
|
403
|
+
* auto-replan from clobbering the burst cards before they're consumed.
|
|
404
|
+
* Cleared when the depletion-triggered replan fires (newQ exhausted).
|
|
405
|
+
*/
|
|
406
|
+
private _suppressQualityReplan;
|
|
407
|
+
/**
|
|
408
|
+
* Guards against infinite depletion-triggered replans. Set to true
|
|
409
|
+
* when a depletion replan fires; cleared when a replan produces
|
|
410
|
+
* content (newQ.length > 0 after replan) or when an explicit
|
|
411
|
+
* (non-auto) replan is requested.
|
|
412
|
+
*/
|
|
413
|
+
private _depletionReplanAttempted;
|
|
359
414
|
private startTime;
|
|
360
415
|
private endTime;
|
|
361
416
|
private _secondsRemaining;
|
|
@@ -369,8 +424,14 @@ declare class SessionController<TView = unknown> extends Loggable {
|
|
|
369
424
|
* @param dataLayer - Data layer provider
|
|
370
425
|
* @param getViewComponent - Function to resolve view components
|
|
371
426
|
* @param mixer - Optional source mixer strategy (defaults to QuotaRoundRobinMixer)
|
|
372
|
-
|
|
373
|
-
|
|
427
|
+
* @param options - Optional session-level configuration
|
|
428
|
+
* @param options.defaultBatchLimit - Default pipeline batch size (default: 20).
|
|
429
|
+
* Smaller values for newer users cause more frequent replans, keeping plans
|
|
430
|
+
* aligned with rapidly-changing user state.
|
|
431
|
+
*/
|
|
432
|
+
constructor(sources: StudyContentSource[], time: number, dataLayer: DataLayerProvider, getViewComponent: (viewId: string) => TView, mixer?: SourceMixer, options?: {
|
|
433
|
+
defaultBatchLimit?: number;
|
|
434
|
+
});
|
|
374
435
|
private tick;
|
|
375
436
|
/**
|
|
376
437
|
* Returns a rough, erring toward conservative, guess at
|
|
@@ -401,7 +462,14 @@ declare class SessionController<TView = unknown> extends Loggable {
|
|
|
401
462
|
* Typical trigger: application-level code (e.g. after a GPC intro completion)
|
|
402
463
|
* calls this to ensure newly-unlocked content appears in the session.
|
|
403
464
|
*/
|
|
404
|
-
requestReplan(
|
|
465
|
+
requestReplan(options?: ReplanOptions | Record<string, unknown>): Promise<void>;
|
|
466
|
+
/**
|
|
467
|
+
* Normalise the requestReplan argument. Accepts either a ReplanOptions
|
|
468
|
+
* object (new API) or a plain Record<string, unknown> (legacy callers
|
|
469
|
+
* that passed hints directly). Distinguishes the two by checking for
|
|
470
|
+
* the presence of ReplanOptions-specific keys.
|
|
471
|
+
*/
|
|
472
|
+
private normalizeReplanOptions;
|
|
405
473
|
/** Minimum well-indicated cards before an additive retry is attempted */
|
|
406
474
|
private static readonly MIN_WELL_INDICATED;
|
|
407
475
|
/**
|
|
@@ -467,6 +535,8 @@ declare class SessionController<TView = unknown> extends Loggable {
|
|
|
467
535
|
};
|
|
468
536
|
replan: {
|
|
469
537
|
inProgress: boolean;
|
|
538
|
+
suppressQualityReplan: boolean;
|
|
539
|
+
defaultBatchLimit: number;
|
|
470
540
|
};
|
|
471
541
|
};
|
|
472
542
|
/**
|
|
@@ -1017,4 +1087,4 @@ interface CouchDbUserDoc extends PouchDB.Authentication.User {
|
|
|
1017
1087
|
entitlements: UserEntitlements;
|
|
1018
1088
|
}
|
|
1019
1089
|
|
|
1020
|
-
export { type AggregatedDocument, type AttachmentUploadResult, CardHistory, type CardPresentation, CardRecord, type CouchDbUserDoc, CourseDBInterface, CourseLookup, CourseRegistrationDoc, type CustomQuestionsData, DEFAULT_MIGRATION_OPTIONS, type DataLayerConfig, DataLayerProvider, type DocumentCounts, ENV, type Entitlement, FileSystemAdapter, Loggable, type MigrationOptions, type MigrationResult, type MixerCardInfo, type MixerRunReport, NOT_SET, type ProcessedDataShape, type ProcessedQuestionData, type QueueSnapshot, QuotaRoundRobinMixer, type ResponseResult, type RestoreProgress, type SessionAction, SessionController, type SessionRunReport, type SourceBatch, type SourceMixer, type SourceSelectionBreakdown, type SourceSummary, StaticCourseManifest, type StaticCourseValidation, StaticToCouchDBMigrator, StudyContentSource, StudySessionItem, type StudySessionRecord, TagFilteredContentSource, type UserAccountStatus, UserDBInterface, type UserEntitlements, type ValidationIssue, type ValidationResult, WeightedCard, _resetDataLayer, captureMixerRun, endSessionTracking, ensureAppDataDirectory, getAppDataDirectory, getDataLayer, getDbPath, initializeDataDirectory, initializeDataLayer, isDataShapeRegistered, isDataShapeSchemaAvailable, isQuestionTypeRegistered, mixerDebugAPI, mountMixerDebugger, mountSessionDebugger, processCustomQuestionsData, recordCardPresentation, registerBlanksCard, registerCustomQuestionTypes, registerDataShape, registerQuestionType, registerSeedData, removeCustomQuestionTypes, removeDataShape, removeQuestionType, sessionDebugAPI, snapshotQueues, startSessionTracking, validateMigration, validateStaticCourse };
|
|
1090
|
+
export { type AggregatedDocument, type AttachmentUploadResult, CardHistory, type CardPresentation, CardRecord, type CouchDbUserDoc, CourseDBInterface, CourseLookup, CourseRegistrationDoc, type CustomQuestionsData, DEFAULT_MIGRATION_OPTIONS, type DataLayerConfig, DataLayerProvider, type DocumentCounts, ENV, type Entitlement, FileSystemAdapter, Loggable, type MigrationOptions, type MigrationResult, type MixerCardInfo, type MixerRunReport, NOT_SET, type ProcessedDataShape, type ProcessedQuestionData, type QueueSnapshot, QuotaRoundRobinMixer, type ReplanOptions, type ResponseResult, type RestoreProgress, type SessionAction, SessionController, type SessionRunReport, type SourceBatch, type SourceMixer, type SourceSelectionBreakdown, type SourceSummary, StaticCourseManifest, type StaticCourseValidation, StaticToCouchDBMigrator, StudyContentSource, StudySessionItem, type StudySessionRecord, TagFilteredContentSource, type UserAccountStatus, UserDBInterface, type UserEntitlements, type ValidationIssue, type ValidationResult, WeightedCard, _resetDataLayer, captureMixerRun, endSessionTracking, ensureAppDataDirectory, getAppDataDirectory, getDataLayer, getDbPath, initializeDataDirectory, initializeDataLayer, isDataShapeRegistered, isDataShapeSchemaAvailable, isQuestionTypeRegistered, mixerDebugAPI, mountMixerDebugger, mountSessionDebugger, processCustomQuestionsData, recordCardPresentation, registerBlanksCard, registerCustomQuestionTypes, registerDataShape, registerQuestionType, registerSeedData, removeCustomQuestionTypes, removeDataShape, removeQuestionType, sessionDebugAPI, snapshotQueues, startSessionTracking, validateMigration, validateStaticCourse };
|
package/dist/index.d.ts
CHANGED
|
@@ -307,6 +307,34 @@ declare class QuotaRoundRobinMixer implements SourceMixer {
|
|
|
307
307
|
mix(batches: SourceBatch[], limit: number): WeightedCard[];
|
|
308
308
|
}
|
|
309
309
|
|
|
310
|
+
/**
|
|
311
|
+
* Options for requesting a mid-session replan.
|
|
312
|
+
*
|
|
313
|
+
* All fields are optional — callers can pass just the fields they need.
|
|
314
|
+
* When omitted, defaults match the existing behaviour (full 20-card
|
|
315
|
+
* replace with no hints).
|
|
316
|
+
*/
|
|
317
|
+
interface ReplanOptions {
|
|
318
|
+
/** Scoring hints forwarded to the pipeline (boost/exclude/require). */
|
|
319
|
+
hints?: Record<string, unknown>;
|
|
320
|
+
/**
|
|
321
|
+
* Maximum number of new cards to return from the pipeline.
|
|
322
|
+
* Default: 20 (the standard session batch size).
|
|
323
|
+
*/
|
|
324
|
+
limit?: number;
|
|
325
|
+
/**
|
|
326
|
+
* How to integrate the new cards into the existing newQ.
|
|
327
|
+
* - `'replace'` (default): atomically swap the entire newQ.
|
|
328
|
+
* - `'merge'`: insert new cards at the front, keeping existing cards.
|
|
329
|
+
*/
|
|
330
|
+
mode?: 'replace' | 'merge';
|
|
331
|
+
/**
|
|
332
|
+
* Human-readable label for debugging / provenance.
|
|
333
|
+
* Appears in console logs and in card provenance entries created
|
|
334
|
+
* by ephemeral hint application.
|
|
335
|
+
*/
|
|
336
|
+
label?: string;
|
|
337
|
+
}
|
|
310
338
|
interface StudySessionRecord {
|
|
311
339
|
card: {
|
|
312
340
|
course_id: string;
|
|
@@ -321,6 +349,13 @@ type SessionAction = 'dismiss-success' | 'dismiss-failed' | 'marked-failed' | 'd
|
|
|
321
349
|
interface ResponseResult {
|
|
322
350
|
nextCardAction: Exclude<SessionAction, 'dismiss-error'> | 'none';
|
|
323
351
|
shouldLoadNextCard: boolean;
|
|
352
|
+
/**
|
|
353
|
+
* When true, the card requested deferred advancement via `deferAdvance`.
|
|
354
|
+
* The record was logged and ELO updated, but navigation was suppressed.
|
|
355
|
+
* StudySession should stash `nextCardAction` and wait for a
|
|
356
|
+
* `ready-to-advance` event from the card before calling `nextCard()`.
|
|
357
|
+
*/
|
|
358
|
+
deferred?: boolean;
|
|
324
359
|
isCorrect: boolean;
|
|
325
360
|
performanceScore?: number;
|
|
326
361
|
shouldClearFeedbackShadow: boolean;
|
|
@@ -337,6 +372,12 @@ declare class SessionController<TView = unknown> extends Loggable {
|
|
|
337
372
|
private mixer;
|
|
338
373
|
private dataLayer;
|
|
339
374
|
private courseNameCache;
|
|
375
|
+
/**
|
|
376
|
+
* Default pipeline batch size for new-card planning.
|
|
377
|
+
* Set via constructor options; falls back to 20 when not specified.
|
|
378
|
+
* Individual replans can override via `ReplanOptions.limit`.
|
|
379
|
+
*/
|
|
380
|
+
private _defaultBatchLimit;
|
|
340
381
|
private sources;
|
|
341
382
|
private _sessionRecord;
|
|
342
383
|
set sessionRecord(r: StudySessionRecord[]);
|
|
@@ -356,6 +397,20 @@ declare class SessionController<TView = unknown> extends Loggable {
|
|
|
356
397
|
* (user state has changed from completing good cards).
|
|
357
398
|
*/
|
|
358
399
|
private _wellIndicatedRemaining;
|
|
400
|
+
/**
|
|
401
|
+
* When true, suppresses the quality-based auto-replan trigger in
|
|
402
|
+
* nextCard(). Set after a burst replan (small limit) to prevent the
|
|
403
|
+
* auto-replan from clobbering the burst cards before they're consumed.
|
|
404
|
+
* Cleared when the depletion-triggered replan fires (newQ exhausted).
|
|
405
|
+
*/
|
|
406
|
+
private _suppressQualityReplan;
|
|
407
|
+
/**
|
|
408
|
+
* Guards against infinite depletion-triggered replans. Set to true
|
|
409
|
+
* when a depletion replan fires; cleared when a replan produces
|
|
410
|
+
* content (newQ.length > 0 after replan) or when an explicit
|
|
411
|
+
* (non-auto) replan is requested.
|
|
412
|
+
*/
|
|
413
|
+
private _depletionReplanAttempted;
|
|
359
414
|
private startTime;
|
|
360
415
|
private endTime;
|
|
361
416
|
private _secondsRemaining;
|
|
@@ -369,8 +424,14 @@ declare class SessionController<TView = unknown> extends Loggable {
|
|
|
369
424
|
* @param dataLayer - Data layer provider
|
|
370
425
|
* @param getViewComponent - Function to resolve view components
|
|
371
426
|
* @param mixer - Optional source mixer strategy (defaults to QuotaRoundRobinMixer)
|
|
372
|
-
|
|
373
|
-
|
|
427
|
+
* @param options - Optional session-level configuration
|
|
428
|
+
* @param options.defaultBatchLimit - Default pipeline batch size (default: 20).
|
|
429
|
+
* Smaller values for newer users cause more frequent replans, keeping plans
|
|
430
|
+
* aligned with rapidly-changing user state.
|
|
431
|
+
*/
|
|
432
|
+
constructor(sources: StudyContentSource[], time: number, dataLayer: DataLayerProvider, getViewComponent: (viewId: string) => TView, mixer?: SourceMixer, options?: {
|
|
433
|
+
defaultBatchLimit?: number;
|
|
434
|
+
});
|
|
374
435
|
private tick;
|
|
375
436
|
/**
|
|
376
437
|
* Returns a rough, erring toward conservative, guess at
|
|
@@ -401,7 +462,14 @@ declare class SessionController<TView = unknown> extends Loggable {
|
|
|
401
462
|
* Typical trigger: application-level code (e.g. after a GPC intro completion)
|
|
402
463
|
* calls this to ensure newly-unlocked content appears in the session.
|
|
403
464
|
*/
|
|
404
|
-
requestReplan(
|
|
465
|
+
requestReplan(options?: ReplanOptions | Record<string, unknown>): Promise<void>;
|
|
466
|
+
/**
|
|
467
|
+
* Normalise the requestReplan argument. Accepts either a ReplanOptions
|
|
468
|
+
* object (new API) or a plain Record<string, unknown> (legacy callers
|
|
469
|
+
* that passed hints directly). Distinguishes the two by checking for
|
|
470
|
+
* the presence of ReplanOptions-specific keys.
|
|
471
|
+
*/
|
|
472
|
+
private normalizeReplanOptions;
|
|
405
473
|
/** Minimum well-indicated cards before an additive retry is attempted */
|
|
406
474
|
private static readonly MIN_WELL_INDICATED;
|
|
407
475
|
/**
|
|
@@ -467,6 +535,8 @@ declare class SessionController<TView = unknown> extends Loggable {
|
|
|
467
535
|
};
|
|
468
536
|
replan: {
|
|
469
537
|
inProgress: boolean;
|
|
538
|
+
suppressQualityReplan: boolean;
|
|
539
|
+
defaultBatchLimit: number;
|
|
470
540
|
};
|
|
471
541
|
};
|
|
472
542
|
/**
|
|
@@ -1017,4 +1087,4 @@ interface CouchDbUserDoc extends PouchDB.Authentication.User {
|
|
|
1017
1087
|
entitlements: UserEntitlements;
|
|
1018
1088
|
}
|
|
1019
1089
|
|
|
1020
|
-
export { type AggregatedDocument, type AttachmentUploadResult, CardHistory, type CardPresentation, CardRecord, type CouchDbUserDoc, CourseDBInterface, CourseLookup, CourseRegistrationDoc, type CustomQuestionsData, DEFAULT_MIGRATION_OPTIONS, type DataLayerConfig, DataLayerProvider, type DocumentCounts, ENV, type Entitlement, FileSystemAdapter, Loggable, type MigrationOptions, type MigrationResult, type MixerCardInfo, type MixerRunReport, NOT_SET, type ProcessedDataShape, type ProcessedQuestionData, type QueueSnapshot, QuotaRoundRobinMixer, type ResponseResult, type RestoreProgress, type SessionAction, SessionController, type SessionRunReport, type SourceBatch, type SourceMixer, type SourceSelectionBreakdown, type SourceSummary, StaticCourseManifest, type StaticCourseValidation, StaticToCouchDBMigrator, StudyContentSource, StudySessionItem, type StudySessionRecord, TagFilteredContentSource, type UserAccountStatus, UserDBInterface, type UserEntitlements, type ValidationIssue, type ValidationResult, WeightedCard, _resetDataLayer, captureMixerRun, endSessionTracking, ensureAppDataDirectory, getAppDataDirectory, getDataLayer, getDbPath, initializeDataDirectory, initializeDataLayer, isDataShapeRegistered, isDataShapeSchemaAvailable, isQuestionTypeRegistered, mixerDebugAPI, mountMixerDebugger, mountSessionDebugger, processCustomQuestionsData, recordCardPresentation, registerBlanksCard, registerCustomQuestionTypes, registerDataShape, registerQuestionType, registerSeedData, removeCustomQuestionTypes, removeDataShape, removeQuestionType, sessionDebugAPI, snapshotQueues, startSessionTracking, validateMigration, validateStaticCourse };
|
|
1090
|
+
export { type AggregatedDocument, type AttachmentUploadResult, CardHistory, type CardPresentation, CardRecord, type CouchDbUserDoc, CourseDBInterface, CourseLookup, CourseRegistrationDoc, type CustomQuestionsData, DEFAULT_MIGRATION_OPTIONS, type DataLayerConfig, DataLayerProvider, type DocumentCounts, ENV, type Entitlement, FileSystemAdapter, Loggable, type MigrationOptions, type MigrationResult, type MixerCardInfo, type MixerRunReport, NOT_SET, type ProcessedDataShape, type ProcessedQuestionData, type QueueSnapshot, QuotaRoundRobinMixer, type ReplanOptions, type ResponseResult, type RestoreProgress, type SessionAction, SessionController, type SessionRunReport, type SourceBatch, type SourceMixer, type SourceSelectionBreakdown, type SourceSummary, StaticCourseManifest, type StaticCourseValidation, StaticToCouchDBMigrator, StudyContentSource, StudySessionItem, type StudySessionRecord, TagFilteredContentSource, type UserAccountStatus, UserDBInterface, type UserEntitlements, type ValidationIssue, type ValidationResult, WeightedCard, _resetDataLayer, captureMixerRun, endSessionTracking, ensureAppDataDirectory, getAppDataDirectory, getDataLayer, getDbPath, initializeDataDirectory, initializeDataLayer, isDataShapeRegistered, isDataShapeSchemaAvailable, isQuestionTypeRegistered, mixerDebugAPI, mountMixerDebugger, mountSessionDebugger, processCustomQuestionsData, recordCardPresentation, registerBlanksCard, registerCustomQuestionTypes, registerDataShape, registerQuestionType, registerSeedData, removeCustomQuestionTypes, removeDataShape, removeQuestionType, sessionDebugAPI, snapshotQueues, startSessionTracking, validateMigration, validateStaticCourse };
|
package/dist/index.js
CHANGED
|
@@ -1936,6 +1936,7 @@ var init_hierarchyDefinition = __esm({
|
|
|
1936
1936
|
"use strict";
|
|
1937
1937
|
init_navigators();
|
|
1938
1938
|
import_common6 = require("@vue-skuilder/common");
|
|
1939
|
+
init_logger();
|
|
1939
1940
|
DEFAULT_MIN_COUNT = 3;
|
|
1940
1941
|
HierarchyDefinitionNavigator = class extends ContentNavigator {
|
|
1941
1942
|
config;
|
|
@@ -2103,6 +2104,9 @@ var init_hierarchyDefinition = __esm({
|
|
|
2103
2104
|
finalScore *= maxBoost;
|
|
2104
2105
|
action = "boosted";
|
|
2105
2106
|
finalReason = `${reason} | preReqBoost \xD7${maxBoost.toFixed(2)} for ${boostedPrereqs.join(", ")}`;
|
|
2107
|
+
logger.info(
|
|
2108
|
+
`[HierarchyDefinition] preReqBoost \xD7${maxBoost.toFixed(2)} applied to card ${card.cardId} via tags [${boostedPrereqs.join(", ")}] (score: ${card.score.toFixed(3)} \u2192 ${finalScore.toFixed(3)})`
|
|
2109
|
+
);
|
|
2106
2110
|
}
|
|
2107
2111
|
}
|
|
2108
2112
|
gated.push({
|
|
@@ -2612,7 +2616,7 @@ var init_relativePriority = __esm({
|
|
|
2612
2616
|
const cardTags = card.tags ?? [];
|
|
2613
2617
|
const priority = this.computeCardPriority(cardTags);
|
|
2614
2618
|
const boostFactor = this.computeBoostFactor(priority);
|
|
2615
|
-
const finalScore = Math.max(0,
|
|
2619
|
+
const finalScore = Math.max(0, card.score * boostFactor);
|
|
2616
2620
|
const action = boostFactor > 1 ? "boosted" : boostFactor < 1 ? "penalized" : "passed";
|
|
2617
2621
|
const reason = this.buildPriorityReason(cardTags, priority, boostFactor, finalScore);
|
|
2618
2622
|
return {
|
|
@@ -3377,7 +3381,7 @@ var init_Pipeline = __esm({
|
|
|
3377
3381
|
card.provenance.push({
|
|
3378
3382
|
strategy: "ephemeralHint",
|
|
3379
3383
|
strategyId: "ephemeral-hint",
|
|
3380
|
-
strategyName: "Replan Hint",
|
|
3384
|
+
strategyName: hints._label ? `Replan Hint (${hints._label})` : "Replan Hint",
|
|
3381
3385
|
action: "boosted",
|
|
3382
3386
|
score: card.score,
|
|
3383
3387
|
reason: `boostTag ${pattern} \xD7${factor}`
|
|
@@ -3394,7 +3398,7 @@ var init_Pipeline = __esm({
|
|
|
3394
3398
|
card.provenance.push({
|
|
3395
3399
|
strategy: "ephemeralHint",
|
|
3396
3400
|
strategyId: "ephemeral-hint",
|
|
3397
|
-
strategyName: "Replan Hint",
|
|
3401
|
+
strategyName: hints._label ? `Replan Hint (${hints._label})` : "Replan Hint",
|
|
3398
3402
|
action: "boosted",
|
|
3399
3403
|
score: card.score,
|
|
3400
3404
|
reason: `boostCard ${pattern} \xD7${factor}`
|
|
@@ -3404,6 +3408,7 @@ var init_Pipeline = __esm({
|
|
|
3404
3408
|
}
|
|
3405
3409
|
}
|
|
3406
3410
|
const cardIds = new Set(cards.map((c) => c.cardId));
|
|
3411
|
+
const hintLabel = hints._label ? `Replan Hint (${hints._label})` : "Replan Hint";
|
|
3407
3412
|
const inject = (card, reason) => {
|
|
3408
3413
|
if (!cardIds.has(card.cardId)) {
|
|
3409
3414
|
const floorScore = Math.max(card.score, 1);
|
|
@@ -3415,7 +3420,7 @@ var init_Pipeline = __esm({
|
|
|
3415
3420
|
{
|
|
3416
3421
|
strategy: "ephemeralHint",
|
|
3417
3422
|
strategyId: "ephemeral-hint",
|
|
3418
|
-
strategyName:
|
|
3423
|
+
strategyName: hintLabel,
|
|
3419
3424
|
action: "boosted",
|
|
3420
3425
|
score: floorScore,
|
|
3421
3426
|
reason
|
|
@@ -4664,10 +4669,18 @@ ${above.rows.map((r) => ` ${r.id}-${r.key}
|
|
|
4664
4669
|
* @param limit - Maximum number of cards to return
|
|
4665
4670
|
* @returns Cards sorted by score descending
|
|
4666
4671
|
*/
|
|
4672
|
+
_pendingHints = null;
|
|
4673
|
+
setEphemeralHints(hints) {
|
|
4674
|
+
this._pendingHints = hints;
|
|
4675
|
+
}
|
|
4667
4676
|
async getWeightedCards(limit) {
|
|
4668
4677
|
const u = await this._getCurrentUser();
|
|
4669
4678
|
try {
|
|
4670
4679
|
const navigator = await this.createNavigator(u);
|
|
4680
|
+
if (this._pendingHints) {
|
|
4681
|
+
navigator.setEphemeralHints(this._pendingHints);
|
|
4682
|
+
this._pendingHints = null;
|
|
4683
|
+
}
|
|
4671
4684
|
return navigator.getWeightedCards(limit);
|
|
4672
4685
|
} catch (e) {
|
|
4673
4686
|
logger.error(`[courseDB] Error getting weighted cards: ${e}`);
|
|
@@ -7659,9 +7672,17 @@ var init_courseDB2 = __esm({
|
|
|
7659
7672
|
}
|
|
7660
7673
|
}
|
|
7661
7674
|
// Study Content Source implementation
|
|
7675
|
+
_pendingHints = null;
|
|
7676
|
+
setEphemeralHints(hints) {
|
|
7677
|
+
this._pendingHints = hints;
|
|
7678
|
+
}
|
|
7662
7679
|
async getWeightedCards(limit) {
|
|
7663
7680
|
try {
|
|
7664
7681
|
const navigator = await this.createNavigator(this.userDB);
|
|
7682
|
+
if (this._pendingHints) {
|
|
7683
|
+
navigator.setEphemeralHints(this._pendingHints);
|
|
7684
|
+
this._pendingHints = null;
|
|
7685
|
+
}
|
|
7665
7686
|
return navigator.getWeightedCards(limit);
|
|
7666
7687
|
} catch (e) {
|
|
7667
7688
|
logger.error(`[static/courseDB] Error getting weighted cards: ${e}`);
|
|
@@ -9425,8 +9446,9 @@ var ResponseProcessor = class {
|
|
|
9425
9446
|
}
|
|
9426
9447
|
try {
|
|
9427
9448
|
const history = await cardHistory;
|
|
9449
|
+
let result;
|
|
9428
9450
|
if (cardRecord.isCorrect) {
|
|
9429
|
-
|
|
9451
|
+
result = this.processCorrectResponse(
|
|
9430
9452
|
cardRecord,
|
|
9431
9453
|
history,
|
|
9432
9454
|
studySessionItem,
|
|
@@ -9436,7 +9458,7 @@ var ResponseProcessor = class {
|
|
|
9436
9458
|
cardId
|
|
9437
9459
|
);
|
|
9438
9460
|
} else {
|
|
9439
|
-
|
|
9461
|
+
result = this.processIncorrectResponse(
|
|
9440
9462
|
cardRecord,
|
|
9441
9463
|
history,
|
|
9442
9464
|
courseRegistrationDoc,
|
|
@@ -9448,6 +9470,18 @@ var ResponseProcessor = class {
|
|
|
9448
9470
|
sessionViews
|
|
9449
9471
|
);
|
|
9450
9472
|
}
|
|
9473
|
+
if (cardRecord.deferAdvance && result.shouldLoadNextCard) {
|
|
9474
|
+
logger.info(
|
|
9475
|
+
"[ResponseProcessor] deferAdvance requested \u2014 suppressing navigation, action stashed:",
|
|
9476
|
+
{ nextCardAction: result.nextCardAction }
|
|
9477
|
+
);
|
|
9478
|
+
result = {
|
|
9479
|
+
...result,
|
|
9480
|
+
shouldLoadNextCard: false,
|
|
9481
|
+
deferred: true
|
|
9482
|
+
};
|
|
9483
|
+
}
|
|
9484
|
+
return result;
|
|
9451
9485
|
} catch (e) {
|
|
9452
9486
|
logger.error("[ResponseProcessor] Failed to load card history", { e, cardId });
|
|
9453
9487
|
throw e;
|
|
@@ -11938,6 +11972,12 @@ var SessionController = class _SessionController extends Loggable {
|
|
|
11938
11972
|
mixer;
|
|
11939
11973
|
dataLayer;
|
|
11940
11974
|
courseNameCache = /* @__PURE__ */ new Map();
|
|
11975
|
+
/**
|
|
11976
|
+
* Default pipeline batch size for new-card planning.
|
|
11977
|
+
* Set via constructor options; falls back to 20 when not specified.
|
|
11978
|
+
* Individual replans can override via `ReplanOptions.limit`.
|
|
11979
|
+
*/
|
|
11980
|
+
_defaultBatchLimit = 20;
|
|
11941
11981
|
sources;
|
|
11942
11982
|
// dataLayer and getViewComponent now injected into CardHydrationService
|
|
11943
11983
|
_sessionRecord = [];
|
|
@@ -11962,6 +12002,20 @@ var SessionController = class _SessionController extends Loggable {
|
|
|
11962
12002
|
* (user state has changed from completing good cards).
|
|
11963
12003
|
*/
|
|
11964
12004
|
_wellIndicatedRemaining = 0;
|
|
12005
|
+
/**
|
|
12006
|
+
* When true, suppresses the quality-based auto-replan trigger in
|
|
12007
|
+
* nextCard(). Set after a burst replan (small limit) to prevent the
|
|
12008
|
+
* auto-replan from clobbering the burst cards before they're consumed.
|
|
12009
|
+
* Cleared when the depletion-triggered replan fires (newQ exhausted).
|
|
12010
|
+
*/
|
|
12011
|
+
_suppressQualityReplan = false;
|
|
12012
|
+
/**
|
|
12013
|
+
* Guards against infinite depletion-triggered replans. Set to true
|
|
12014
|
+
* when a depletion replan fires; cleared when a replan produces
|
|
12015
|
+
* content (newQ.length > 0 after replan) or when an explicit
|
|
12016
|
+
* (non-auto) replan is requested.
|
|
12017
|
+
*/
|
|
12018
|
+
_depletionReplanAttempted = false;
|
|
11965
12019
|
startTime;
|
|
11966
12020
|
endTime;
|
|
11967
12021
|
_secondsRemaining;
|
|
@@ -11986,8 +12040,12 @@ var SessionController = class _SessionController extends Loggable {
|
|
|
11986
12040
|
* @param dataLayer - Data layer provider
|
|
11987
12041
|
* @param getViewComponent - Function to resolve view components
|
|
11988
12042
|
* @param mixer - Optional source mixer strategy (defaults to QuotaRoundRobinMixer)
|
|
12043
|
+
* @param options - Optional session-level configuration
|
|
12044
|
+
* @param options.defaultBatchLimit - Default pipeline batch size (default: 20).
|
|
12045
|
+
* Smaller values for newer users cause more frequent replans, keeping plans
|
|
12046
|
+
* aligned with rapidly-changing user state.
|
|
11989
12047
|
*/
|
|
11990
|
-
constructor(sources, time, dataLayer, getViewComponent, mixer) {
|
|
12048
|
+
constructor(sources, time, dataLayer, getViewComponent, mixer, options) {
|
|
11991
12049
|
super();
|
|
11992
12050
|
this.dataLayer = dataLayer;
|
|
11993
12051
|
this.mixer = mixer || new QuotaRoundRobinMixer();
|
|
@@ -12005,9 +12063,13 @@ var SessionController = class _SessionController extends Loggable {
|
|
|
12005
12063
|
this.startTime = /* @__PURE__ */ new Date();
|
|
12006
12064
|
this._secondsRemaining = time;
|
|
12007
12065
|
this.endTime = new Date(this.startTime.valueOf() + 1e3 * this._secondsRemaining);
|
|
12066
|
+
if (options?.defaultBatchLimit !== void 0) {
|
|
12067
|
+
this._defaultBatchLimit = options.defaultBatchLimit;
|
|
12068
|
+
}
|
|
12008
12069
|
this.log(`Session constructed:
|
|
12009
12070
|
startTime: ${this.startTime}
|
|
12010
|
-
endTime: ${this.endTime}
|
|
12071
|
+
endTime: ${this.endTime}
|
|
12072
|
+
defaultBatchLimit: ${this._defaultBatchLimit}`);
|
|
12011
12073
|
}
|
|
12012
12074
|
tick() {
|
|
12013
12075
|
this._secondsRemaining = Math.floor((this.endTime.valueOf() - Date.now()) / 1e3);
|
|
@@ -12083,25 +12145,47 @@ var SessionController = class _SessionController extends Loggable {
|
|
|
12083
12145
|
* Typical trigger: application-level code (e.g. after a GPC intro completion)
|
|
12084
12146
|
* calls this to ensure newly-unlocked content appears in the session.
|
|
12085
12147
|
*/
|
|
12086
|
-
async requestReplan(
|
|
12148
|
+
async requestReplan(options) {
|
|
12149
|
+
const opts = this.normalizeReplanOptions(options);
|
|
12150
|
+
if (opts.hints || opts.label || opts.limit) {
|
|
12151
|
+
this._depletionReplanAttempted = false;
|
|
12152
|
+
}
|
|
12087
12153
|
if (this._replanPromise) {
|
|
12088
12154
|
this.log("Replan already in progress, awaiting existing replan");
|
|
12089
12155
|
return this._replanPromise;
|
|
12090
12156
|
}
|
|
12091
|
-
if (hints) {
|
|
12157
|
+
if (opts.hints) {
|
|
12158
|
+
const hintsWithLabel = opts.label ? { ...opts.hints, _label: opts.label } : opts.hints;
|
|
12092
12159
|
for (const source of this.sources) {
|
|
12093
|
-
|
|
12094
|
-
source.setEphemeralHints?.(hints);
|
|
12160
|
+
source.setEphemeralHints?.(hintsWithLabel);
|
|
12095
12161
|
}
|
|
12096
12162
|
}
|
|
12097
|
-
|
|
12098
|
-
this.
|
|
12163
|
+
const labelTag = opts.label ? ` [${opts.label}]` : "";
|
|
12164
|
+
this.log(
|
|
12165
|
+
`Mid-session replan requested${labelTag} (limit: ${opts.limit ?? "default"}, mode: ${opts.mode ?? "replace"}${opts.hints ? ", with hints" : ""})`
|
|
12166
|
+
);
|
|
12167
|
+
this._replanPromise = this._executeReplan(opts);
|
|
12099
12168
|
try {
|
|
12100
12169
|
await this._replanPromise;
|
|
12101
12170
|
} finally {
|
|
12102
12171
|
this._replanPromise = null;
|
|
12103
12172
|
}
|
|
12104
12173
|
}
|
|
12174
|
+
/**
|
|
12175
|
+
* Normalise the requestReplan argument. Accepts either a ReplanOptions
|
|
12176
|
+
* object (new API) or a plain Record<string, unknown> (legacy callers
|
|
12177
|
+
* that passed hints directly). Distinguishes the two by checking for
|
|
12178
|
+
* the presence of ReplanOptions-specific keys.
|
|
12179
|
+
*/
|
|
12180
|
+
normalizeReplanOptions(input) {
|
|
12181
|
+
if (!input) return {};
|
|
12182
|
+
const replanKeys = ["hints", "limit", "mode", "label"];
|
|
12183
|
+
const inputKeys = Object.keys(input);
|
|
12184
|
+
if (inputKeys.some((k) => replanKeys.includes(k))) {
|
|
12185
|
+
return input;
|
|
12186
|
+
}
|
|
12187
|
+
return { hints: input };
|
|
12188
|
+
}
|
|
12105
12189
|
/** Minimum well-indicated cards before an additive retry is attempted */
|
|
12106
12190
|
static MIN_WELL_INDICATED = 5;
|
|
12107
12191
|
/**
|
|
@@ -12120,16 +12204,32 @@ var SessionController = class _SessionController extends Loggable {
|
|
|
12120
12204
|
* pass all hierarchy filters, one additive retry is attempted — merging
|
|
12121
12205
|
* any new high-quality candidates into the front of the queue.
|
|
12122
12206
|
*/
|
|
12123
|
-
async _executeReplan() {
|
|
12124
|
-
const
|
|
12207
|
+
async _executeReplan(opts = {}) {
|
|
12208
|
+
const limit = opts.limit;
|
|
12209
|
+
const mode = opts.mode ?? "replace";
|
|
12210
|
+
const wellIndicated = await this.getWeightedContent({
|
|
12211
|
+
replan: true,
|
|
12212
|
+
additive: mode === "merge",
|
|
12213
|
+
limit
|
|
12214
|
+
});
|
|
12125
12215
|
this._wellIndicatedRemaining = wellIndicated;
|
|
12216
|
+
if (limit !== void 0 && limit < this._defaultBatchLimit) {
|
|
12217
|
+
this._suppressQualityReplan = true;
|
|
12218
|
+
this.log(`[Replan] Burst mode (limit=${limit}): suppressing quality-based auto-replan`);
|
|
12219
|
+
} else {
|
|
12220
|
+
this._suppressQualityReplan = false;
|
|
12221
|
+
}
|
|
12126
12222
|
if (wellIndicated >= 0 && wellIndicated < _SessionController.MIN_WELL_INDICATED) {
|
|
12127
12223
|
this.log(
|
|
12128
12224
|
`[Replan] Only ${wellIndicated}/${_SessionController.MIN_WELL_INDICATED} well-indicated cards after replan`
|
|
12129
12225
|
);
|
|
12130
12226
|
}
|
|
12227
|
+
if (this.newQ.length > 0) {
|
|
12228
|
+
this._depletionReplanAttempted = false;
|
|
12229
|
+
}
|
|
12131
12230
|
await this.hydrationService.ensureHydratedCards();
|
|
12132
|
-
|
|
12231
|
+
const labelTag = opts.label ? ` [${opts.label}]` : "";
|
|
12232
|
+
this.log(`Replan complete${labelTag}: newQ now has ${this.newQ.length} cards (mode=${mode})`);
|
|
12133
12233
|
snapshotQueues(this.reviewQ.length, this.newQ.length, this.failedQ.length);
|
|
12134
12234
|
}
|
|
12135
12235
|
addTime(seconds) {
|
|
@@ -12189,7 +12289,9 @@ var SessionController = class _SessionController extends Loggable {
|
|
|
12189
12289
|
cardIds: this.hydrationService.getHydratedCardIds()
|
|
12190
12290
|
},
|
|
12191
12291
|
replan: {
|
|
12192
|
-
inProgress: this._replanPromise !== null
|
|
12292
|
+
inProgress: this._replanPromise !== null,
|
|
12293
|
+
suppressQualityReplan: this._suppressQualityReplan,
|
|
12294
|
+
defaultBatchLimit: this._defaultBatchLimit
|
|
12193
12295
|
}
|
|
12194
12296
|
};
|
|
12195
12297
|
}
|
|
@@ -12216,7 +12318,7 @@ var SessionController = class _SessionController extends Loggable {
|
|
|
12216
12318
|
async getWeightedContent(options) {
|
|
12217
12319
|
const replan = options?.replan ?? false;
|
|
12218
12320
|
const additive = options?.additive ?? false;
|
|
12219
|
-
const limit =
|
|
12321
|
+
const limit = options?.limit ?? this._defaultBatchLimit;
|
|
12220
12322
|
const batches = [];
|
|
12221
12323
|
for (let i = 0; i < this.sources.length; i++) {
|
|
12222
12324
|
const source = this.sources[i];
|
|
@@ -12397,10 +12499,26 @@ var SessionController = class _SessionController extends Loggable {
|
|
|
12397
12499
|
this.log("nextCard: awaiting in-flight replan before drawing");
|
|
12398
12500
|
await this._replanPromise;
|
|
12399
12501
|
}
|
|
12502
|
+
if (this.newQ.length <= 1 && this._secondsRemaining > 0 && !this._replanPromise && !this._depletionReplanAttempted) {
|
|
12503
|
+
this._suppressQualityReplan = false;
|
|
12504
|
+
this._depletionReplanAttempted = true;
|
|
12505
|
+
const otherContent = this.reviewQ.length + this.failedQ.length;
|
|
12506
|
+
if (this.newQ.length === 0 && otherContent === 0) {
|
|
12507
|
+
this.log(
|
|
12508
|
+
`[AutoReplan:depletion] All queues empty with ${this._secondsRemaining}s remaining. Awaiting replan.`
|
|
12509
|
+
);
|
|
12510
|
+
await this.requestReplan();
|
|
12511
|
+
} else {
|
|
12512
|
+
this.log(
|
|
12513
|
+
`[AutoReplan:depletion] newQ has ${this.newQ.length} card(s) (${otherContent} in other queues) with ${this._secondsRemaining}s remaining. Triggering background replan.`
|
|
12514
|
+
);
|
|
12515
|
+
void this.requestReplan();
|
|
12516
|
+
}
|
|
12517
|
+
}
|
|
12400
12518
|
const REPLAN_BUFFER = 3;
|
|
12401
|
-
if (this._wellIndicatedRemaining <= REPLAN_BUFFER && this.newQ.length > 0 && !this._replanPromise) {
|
|
12519
|
+
if (!this._suppressQualityReplan && this._wellIndicatedRemaining <= REPLAN_BUFFER && this.newQ.length > 0 && !this._replanPromise) {
|
|
12402
12520
|
this.log(
|
|
12403
|
-
`[AutoReplan] ${this._wellIndicatedRemaining} well-indicated cards remaining (newQ: ${this.newQ.length}). Triggering background replan.`
|
|
12521
|
+
`[AutoReplan:quality] ${this._wellIndicatedRemaining} well-indicated cards remaining (newQ: ${this.newQ.length}). Triggering background replan.`
|
|
12404
12522
|
);
|
|
12405
12523
|
void this.requestReplan();
|
|
12406
12524
|
}
|