@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.mjs
CHANGED
|
@@ -1913,6 +1913,7 @@ var init_hierarchyDefinition = __esm({
|
|
|
1913
1913
|
"src/core/navigators/filters/hierarchyDefinition.ts"() {
|
|
1914
1914
|
"use strict";
|
|
1915
1915
|
init_navigators();
|
|
1916
|
+
init_logger();
|
|
1916
1917
|
DEFAULT_MIN_COUNT = 3;
|
|
1917
1918
|
HierarchyDefinitionNavigator = class extends ContentNavigator {
|
|
1918
1919
|
config;
|
|
@@ -2080,6 +2081,9 @@ var init_hierarchyDefinition = __esm({
|
|
|
2080
2081
|
finalScore *= maxBoost;
|
|
2081
2082
|
action = "boosted";
|
|
2082
2083
|
finalReason = `${reason} | preReqBoost \xD7${maxBoost.toFixed(2)} for ${boostedPrereqs.join(", ")}`;
|
|
2084
|
+
logger.info(
|
|
2085
|
+
`[HierarchyDefinition] preReqBoost \xD7${maxBoost.toFixed(2)} applied to card ${card.cardId} via tags [${boostedPrereqs.join(", ")}] (score: ${card.score.toFixed(3)} \u2192 ${finalScore.toFixed(3)})`
|
|
2086
|
+
);
|
|
2083
2087
|
}
|
|
2084
2088
|
}
|
|
2085
2089
|
gated.push({
|
|
@@ -2589,7 +2593,7 @@ var init_relativePriority = __esm({
|
|
|
2589
2593
|
const cardTags = card.tags ?? [];
|
|
2590
2594
|
const priority = this.computeCardPriority(cardTags);
|
|
2591
2595
|
const boostFactor = this.computeBoostFactor(priority);
|
|
2592
|
-
const finalScore = Math.max(0,
|
|
2596
|
+
const finalScore = Math.max(0, card.score * boostFactor);
|
|
2593
2597
|
const action = boostFactor > 1 ? "boosted" : boostFactor < 1 ? "penalized" : "passed";
|
|
2594
2598
|
const reason = this.buildPriorityReason(cardTags, priority, boostFactor, finalScore);
|
|
2595
2599
|
return {
|
|
@@ -3354,7 +3358,7 @@ var init_Pipeline = __esm({
|
|
|
3354
3358
|
card.provenance.push({
|
|
3355
3359
|
strategy: "ephemeralHint",
|
|
3356
3360
|
strategyId: "ephemeral-hint",
|
|
3357
|
-
strategyName: "Replan Hint",
|
|
3361
|
+
strategyName: hints._label ? `Replan Hint (${hints._label})` : "Replan Hint",
|
|
3358
3362
|
action: "boosted",
|
|
3359
3363
|
score: card.score,
|
|
3360
3364
|
reason: `boostTag ${pattern} \xD7${factor}`
|
|
@@ -3371,7 +3375,7 @@ var init_Pipeline = __esm({
|
|
|
3371
3375
|
card.provenance.push({
|
|
3372
3376
|
strategy: "ephemeralHint",
|
|
3373
3377
|
strategyId: "ephemeral-hint",
|
|
3374
|
-
strategyName: "Replan Hint",
|
|
3378
|
+
strategyName: hints._label ? `Replan Hint (${hints._label})` : "Replan Hint",
|
|
3375
3379
|
action: "boosted",
|
|
3376
3380
|
score: card.score,
|
|
3377
3381
|
reason: `boostCard ${pattern} \xD7${factor}`
|
|
@@ -3381,6 +3385,7 @@ var init_Pipeline = __esm({
|
|
|
3381
3385
|
}
|
|
3382
3386
|
}
|
|
3383
3387
|
const cardIds = new Set(cards.map((c) => c.cardId));
|
|
3388
|
+
const hintLabel = hints._label ? `Replan Hint (${hints._label})` : "Replan Hint";
|
|
3384
3389
|
const inject = (card, reason) => {
|
|
3385
3390
|
if (!cardIds.has(card.cardId)) {
|
|
3386
3391
|
const floorScore = Math.max(card.score, 1);
|
|
@@ -3392,7 +3397,7 @@ var init_Pipeline = __esm({
|
|
|
3392
3397
|
{
|
|
3393
3398
|
strategy: "ephemeralHint",
|
|
3394
3399
|
strategyId: "ephemeral-hint",
|
|
3395
|
-
strategyName:
|
|
3400
|
+
strategyName: hintLabel,
|
|
3396
3401
|
action: "boosted",
|
|
3397
3402
|
score: floorScore,
|
|
3398
3403
|
reason
|
|
@@ -4646,10 +4651,18 @@ ${above.rows.map((r) => ` ${r.id}-${r.key}
|
|
|
4646
4651
|
* @param limit - Maximum number of cards to return
|
|
4647
4652
|
* @returns Cards sorted by score descending
|
|
4648
4653
|
*/
|
|
4654
|
+
_pendingHints = null;
|
|
4655
|
+
setEphemeralHints(hints) {
|
|
4656
|
+
this._pendingHints = hints;
|
|
4657
|
+
}
|
|
4649
4658
|
async getWeightedCards(limit) {
|
|
4650
4659
|
const u = await this._getCurrentUser();
|
|
4651
4660
|
try {
|
|
4652
4661
|
const navigator = await this.createNavigator(u);
|
|
4662
|
+
if (this._pendingHints) {
|
|
4663
|
+
navigator.setEphemeralHints(this._pendingHints);
|
|
4664
|
+
this._pendingHints = null;
|
|
4665
|
+
}
|
|
4653
4666
|
return navigator.getWeightedCards(limit);
|
|
4654
4667
|
} catch (e) {
|
|
4655
4668
|
logger.error(`[courseDB] Error getting weighted cards: ${e}`);
|
|
@@ -7640,9 +7653,17 @@ var init_courseDB2 = __esm({
|
|
|
7640
7653
|
}
|
|
7641
7654
|
}
|
|
7642
7655
|
// Study Content Source implementation
|
|
7656
|
+
_pendingHints = null;
|
|
7657
|
+
setEphemeralHints(hints) {
|
|
7658
|
+
this._pendingHints = hints;
|
|
7659
|
+
}
|
|
7643
7660
|
async getWeightedCards(limit) {
|
|
7644
7661
|
try {
|
|
7645
7662
|
const navigator = await this.createNavigator(this.userDB);
|
|
7663
|
+
if (this._pendingHints) {
|
|
7664
|
+
navigator.setEphemeralHints(this._pendingHints);
|
|
7665
|
+
this._pendingHints = null;
|
|
7666
|
+
}
|
|
7646
7667
|
return navigator.getWeightedCards(limit);
|
|
7647
7668
|
} catch (e) {
|
|
7648
7669
|
logger.error(`[static/courseDB] Error getting weighted cards: ${e}`);
|
|
@@ -9319,8 +9340,9 @@ var ResponseProcessor = class {
|
|
|
9319
9340
|
}
|
|
9320
9341
|
try {
|
|
9321
9342
|
const history = await cardHistory;
|
|
9343
|
+
let result;
|
|
9322
9344
|
if (cardRecord.isCorrect) {
|
|
9323
|
-
|
|
9345
|
+
result = this.processCorrectResponse(
|
|
9324
9346
|
cardRecord,
|
|
9325
9347
|
history,
|
|
9326
9348
|
studySessionItem,
|
|
@@ -9330,7 +9352,7 @@ var ResponseProcessor = class {
|
|
|
9330
9352
|
cardId
|
|
9331
9353
|
);
|
|
9332
9354
|
} else {
|
|
9333
|
-
|
|
9355
|
+
result = this.processIncorrectResponse(
|
|
9334
9356
|
cardRecord,
|
|
9335
9357
|
history,
|
|
9336
9358
|
courseRegistrationDoc,
|
|
@@ -9342,6 +9364,18 @@ var ResponseProcessor = class {
|
|
|
9342
9364
|
sessionViews
|
|
9343
9365
|
);
|
|
9344
9366
|
}
|
|
9367
|
+
if (cardRecord.deferAdvance && result.shouldLoadNextCard) {
|
|
9368
|
+
logger.info(
|
|
9369
|
+
"[ResponseProcessor] deferAdvance requested \u2014 suppressing navigation, action stashed:",
|
|
9370
|
+
{ nextCardAction: result.nextCardAction }
|
|
9371
|
+
);
|
|
9372
|
+
result = {
|
|
9373
|
+
...result,
|
|
9374
|
+
shouldLoadNextCard: false,
|
|
9375
|
+
deferred: true
|
|
9376
|
+
};
|
|
9377
|
+
}
|
|
9378
|
+
return result;
|
|
9345
9379
|
} catch (e) {
|
|
9346
9380
|
logger.error("[ResponseProcessor] Failed to load card history", { e, cardId });
|
|
9347
9381
|
throw e;
|
|
@@ -11836,6 +11870,12 @@ var SessionController = class _SessionController extends Loggable {
|
|
|
11836
11870
|
mixer;
|
|
11837
11871
|
dataLayer;
|
|
11838
11872
|
courseNameCache = /* @__PURE__ */ new Map();
|
|
11873
|
+
/**
|
|
11874
|
+
* Default pipeline batch size for new-card planning.
|
|
11875
|
+
* Set via constructor options; falls back to 20 when not specified.
|
|
11876
|
+
* Individual replans can override via `ReplanOptions.limit`.
|
|
11877
|
+
*/
|
|
11878
|
+
_defaultBatchLimit = 20;
|
|
11839
11879
|
sources;
|
|
11840
11880
|
// dataLayer and getViewComponent now injected into CardHydrationService
|
|
11841
11881
|
_sessionRecord = [];
|
|
@@ -11860,6 +11900,20 @@ var SessionController = class _SessionController extends Loggable {
|
|
|
11860
11900
|
* (user state has changed from completing good cards).
|
|
11861
11901
|
*/
|
|
11862
11902
|
_wellIndicatedRemaining = 0;
|
|
11903
|
+
/**
|
|
11904
|
+
* When true, suppresses the quality-based auto-replan trigger in
|
|
11905
|
+
* nextCard(). Set after a burst replan (small limit) to prevent the
|
|
11906
|
+
* auto-replan from clobbering the burst cards before they're consumed.
|
|
11907
|
+
* Cleared when the depletion-triggered replan fires (newQ exhausted).
|
|
11908
|
+
*/
|
|
11909
|
+
_suppressQualityReplan = false;
|
|
11910
|
+
/**
|
|
11911
|
+
* Guards against infinite depletion-triggered replans. Set to true
|
|
11912
|
+
* when a depletion replan fires; cleared when a replan produces
|
|
11913
|
+
* content (newQ.length > 0 after replan) or when an explicit
|
|
11914
|
+
* (non-auto) replan is requested.
|
|
11915
|
+
*/
|
|
11916
|
+
_depletionReplanAttempted = false;
|
|
11863
11917
|
startTime;
|
|
11864
11918
|
endTime;
|
|
11865
11919
|
_secondsRemaining;
|
|
@@ -11884,8 +11938,12 @@ var SessionController = class _SessionController extends Loggable {
|
|
|
11884
11938
|
* @param dataLayer - Data layer provider
|
|
11885
11939
|
* @param getViewComponent - Function to resolve view components
|
|
11886
11940
|
* @param mixer - Optional source mixer strategy (defaults to QuotaRoundRobinMixer)
|
|
11941
|
+
* @param options - Optional session-level configuration
|
|
11942
|
+
* @param options.defaultBatchLimit - Default pipeline batch size (default: 20).
|
|
11943
|
+
* Smaller values for newer users cause more frequent replans, keeping plans
|
|
11944
|
+
* aligned with rapidly-changing user state.
|
|
11887
11945
|
*/
|
|
11888
|
-
constructor(sources, time, dataLayer, getViewComponent, mixer) {
|
|
11946
|
+
constructor(sources, time, dataLayer, getViewComponent, mixer, options) {
|
|
11889
11947
|
super();
|
|
11890
11948
|
this.dataLayer = dataLayer;
|
|
11891
11949
|
this.mixer = mixer || new QuotaRoundRobinMixer();
|
|
@@ -11903,9 +11961,13 @@ var SessionController = class _SessionController extends Loggable {
|
|
|
11903
11961
|
this.startTime = /* @__PURE__ */ new Date();
|
|
11904
11962
|
this._secondsRemaining = time;
|
|
11905
11963
|
this.endTime = new Date(this.startTime.valueOf() + 1e3 * this._secondsRemaining);
|
|
11964
|
+
if (options?.defaultBatchLimit !== void 0) {
|
|
11965
|
+
this._defaultBatchLimit = options.defaultBatchLimit;
|
|
11966
|
+
}
|
|
11906
11967
|
this.log(`Session constructed:
|
|
11907
11968
|
startTime: ${this.startTime}
|
|
11908
|
-
endTime: ${this.endTime}
|
|
11969
|
+
endTime: ${this.endTime}
|
|
11970
|
+
defaultBatchLimit: ${this._defaultBatchLimit}`);
|
|
11909
11971
|
}
|
|
11910
11972
|
tick() {
|
|
11911
11973
|
this._secondsRemaining = Math.floor((this.endTime.valueOf() - Date.now()) / 1e3);
|
|
@@ -11981,25 +12043,47 @@ var SessionController = class _SessionController extends Loggable {
|
|
|
11981
12043
|
* Typical trigger: application-level code (e.g. after a GPC intro completion)
|
|
11982
12044
|
* calls this to ensure newly-unlocked content appears in the session.
|
|
11983
12045
|
*/
|
|
11984
|
-
async requestReplan(
|
|
12046
|
+
async requestReplan(options) {
|
|
12047
|
+
const opts = this.normalizeReplanOptions(options);
|
|
12048
|
+
if (opts.hints || opts.label || opts.limit) {
|
|
12049
|
+
this._depletionReplanAttempted = false;
|
|
12050
|
+
}
|
|
11985
12051
|
if (this._replanPromise) {
|
|
11986
12052
|
this.log("Replan already in progress, awaiting existing replan");
|
|
11987
12053
|
return this._replanPromise;
|
|
11988
12054
|
}
|
|
11989
|
-
if (hints) {
|
|
12055
|
+
if (opts.hints) {
|
|
12056
|
+
const hintsWithLabel = opts.label ? { ...opts.hints, _label: opts.label } : opts.hints;
|
|
11990
12057
|
for (const source of this.sources) {
|
|
11991
|
-
|
|
11992
|
-
source.setEphemeralHints?.(hints);
|
|
12058
|
+
source.setEphemeralHints?.(hintsWithLabel);
|
|
11993
12059
|
}
|
|
11994
12060
|
}
|
|
11995
|
-
|
|
11996
|
-
this.
|
|
12061
|
+
const labelTag = opts.label ? ` [${opts.label}]` : "";
|
|
12062
|
+
this.log(
|
|
12063
|
+
`Mid-session replan requested${labelTag} (limit: ${opts.limit ?? "default"}, mode: ${opts.mode ?? "replace"}${opts.hints ? ", with hints" : ""})`
|
|
12064
|
+
);
|
|
12065
|
+
this._replanPromise = this._executeReplan(opts);
|
|
11997
12066
|
try {
|
|
11998
12067
|
await this._replanPromise;
|
|
11999
12068
|
} finally {
|
|
12000
12069
|
this._replanPromise = null;
|
|
12001
12070
|
}
|
|
12002
12071
|
}
|
|
12072
|
+
/**
|
|
12073
|
+
* Normalise the requestReplan argument. Accepts either a ReplanOptions
|
|
12074
|
+
* object (new API) or a plain Record<string, unknown> (legacy callers
|
|
12075
|
+
* that passed hints directly). Distinguishes the two by checking for
|
|
12076
|
+
* the presence of ReplanOptions-specific keys.
|
|
12077
|
+
*/
|
|
12078
|
+
normalizeReplanOptions(input) {
|
|
12079
|
+
if (!input) return {};
|
|
12080
|
+
const replanKeys = ["hints", "limit", "mode", "label"];
|
|
12081
|
+
const inputKeys = Object.keys(input);
|
|
12082
|
+
if (inputKeys.some((k) => replanKeys.includes(k))) {
|
|
12083
|
+
return input;
|
|
12084
|
+
}
|
|
12085
|
+
return { hints: input };
|
|
12086
|
+
}
|
|
12003
12087
|
/** Minimum well-indicated cards before an additive retry is attempted */
|
|
12004
12088
|
static MIN_WELL_INDICATED = 5;
|
|
12005
12089
|
/**
|
|
@@ -12018,16 +12102,32 @@ var SessionController = class _SessionController extends Loggable {
|
|
|
12018
12102
|
* pass all hierarchy filters, one additive retry is attempted — merging
|
|
12019
12103
|
* any new high-quality candidates into the front of the queue.
|
|
12020
12104
|
*/
|
|
12021
|
-
async _executeReplan() {
|
|
12022
|
-
const
|
|
12105
|
+
async _executeReplan(opts = {}) {
|
|
12106
|
+
const limit = opts.limit;
|
|
12107
|
+
const mode = opts.mode ?? "replace";
|
|
12108
|
+
const wellIndicated = await this.getWeightedContent({
|
|
12109
|
+
replan: true,
|
|
12110
|
+
additive: mode === "merge",
|
|
12111
|
+
limit
|
|
12112
|
+
});
|
|
12023
12113
|
this._wellIndicatedRemaining = wellIndicated;
|
|
12114
|
+
if (limit !== void 0 && limit < this._defaultBatchLimit) {
|
|
12115
|
+
this._suppressQualityReplan = true;
|
|
12116
|
+
this.log(`[Replan] Burst mode (limit=${limit}): suppressing quality-based auto-replan`);
|
|
12117
|
+
} else {
|
|
12118
|
+
this._suppressQualityReplan = false;
|
|
12119
|
+
}
|
|
12024
12120
|
if (wellIndicated >= 0 && wellIndicated < _SessionController.MIN_WELL_INDICATED) {
|
|
12025
12121
|
this.log(
|
|
12026
12122
|
`[Replan] Only ${wellIndicated}/${_SessionController.MIN_WELL_INDICATED} well-indicated cards after replan`
|
|
12027
12123
|
);
|
|
12028
12124
|
}
|
|
12125
|
+
if (this.newQ.length > 0) {
|
|
12126
|
+
this._depletionReplanAttempted = false;
|
|
12127
|
+
}
|
|
12029
12128
|
await this.hydrationService.ensureHydratedCards();
|
|
12030
|
-
|
|
12129
|
+
const labelTag = opts.label ? ` [${opts.label}]` : "";
|
|
12130
|
+
this.log(`Replan complete${labelTag}: newQ now has ${this.newQ.length} cards (mode=${mode})`);
|
|
12031
12131
|
snapshotQueues(this.reviewQ.length, this.newQ.length, this.failedQ.length);
|
|
12032
12132
|
}
|
|
12033
12133
|
addTime(seconds) {
|
|
@@ -12087,7 +12187,9 @@ var SessionController = class _SessionController extends Loggable {
|
|
|
12087
12187
|
cardIds: this.hydrationService.getHydratedCardIds()
|
|
12088
12188
|
},
|
|
12089
12189
|
replan: {
|
|
12090
|
-
inProgress: this._replanPromise !== null
|
|
12190
|
+
inProgress: this._replanPromise !== null,
|
|
12191
|
+
suppressQualityReplan: this._suppressQualityReplan,
|
|
12192
|
+
defaultBatchLimit: this._defaultBatchLimit
|
|
12091
12193
|
}
|
|
12092
12194
|
};
|
|
12093
12195
|
}
|
|
@@ -12114,7 +12216,7 @@ var SessionController = class _SessionController extends Loggable {
|
|
|
12114
12216
|
async getWeightedContent(options) {
|
|
12115
12217
|
const replan = options?.replan ?? false;
|
|
12116
12218
|
const additive = options?.additive ?? false;
|
|
12117
|
-
const limit =
|
|
12219
|
+
const limit = options?.limit ?? this._defaultBatchLimit;
|
|
12118
12220
|
const batches = [];
|
|
12119
12221
|
for (let i = 0; i < this.sources.length; i++) {
|
|
12120
12222
|
const source = this.sources[i];
|
|
@@ -12295,10 +12397,26 @@ var SessionController = class _SessionController extends Loggable {
|
|
|
12295
12397
|
this.log("nextCard: awaiting in-flight replan before drawing");
|
|
12296
12398
|
await this._replanPromise;
|
|
12297
12399
|
}
|
|
12400
|
+
if (this.newQ.length <= 1 && this._secondsRemaining > 0 && !this._replanPromise && !this._depletionReplanAttempted) {
|
|
12401
|
+
this._suppressQualityReplan = false;
|
|
12402
|
+
this._depletionReplanAttempted = true;
|
|
12403
|
+
const otherContent = this.reviewQ.length + this.failedQ.length;
|
|
12404
|
+
if (this.newQ.length === 0 && otherContent === 0) {
|
|
12405
|
+
this.log(
|
|
12406
|
+
`[AutoReplan:depletion] All queues empty with ${this._secondsRemaining}s remaining. Awaiting replan.`
|
|
12407
|
+
);
|
|
12408
|
+
await this.requestReplan();
|
|
12409
|
+
} else {
|
|
12410
|
+
this.log(
|
|
12411
|
+
`[AutoReplan:depletion] newQ has ${this.newQ.length} card(s) (${otherContent} in other queues) with ${this._secondsRemaining}s remaining. Triggering background replan.`
|
|
12412
|
+
);
|
|
12413
|
+
void this.requestReplan();
|
|
12414
|
+
}
|
|
12415
|
+
}
|
|
12298
12416
|
const REPLAN_BUFFER = 3;
|
|
12299
|
-
if (this._wellIndicatedRemaining <= REPLAN_BUFFER && this.newQ.length > 0 && !this._replanPromise) {
|
|
12417
|
+
if (!this._suppressQualityReplan && this._wellIndicatedRemaining <= REPLAN_BUFFER && this.newQ.length > 0 && !this._replanPromise) {
|
|
12300
12418
|
this.log(
|
|
12301
|
-
`[AutoReplan] ${this._wellIndicatedRemaining} well-indicated cards remaining (newQ: ${this.newQ.length}). Triggering background replan.`
|
|
12419
|
+
`[AutoReplan:quality] ${this._wellIndicatedRemaining} well-indicated cards remaining (newQ: ${this.newQ.length}). Triggering background replan.`
|
|
12302
12420
|
);
|
|
12303
12421
|
void this.requestReplan();
|
|
12304
12422
|
}
|