@vue-skuilder/db 0.1.24 → 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-BotbOOfX.d.ts → contentSource-BmnmvH8C.d.ts} +41 -0
- package/dist/{contentSource-C90LH-OH.d.cts → contentSource-DfBbaLA-.d.cts} +41 -0
- package/dist/core/index.d.cts +94 -4
- package/dist/core/index.d.ts +94 -4
- package/dist/core/index.js +530 -83
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.mjs +528 -83
- package/dist/core/index.mjs.map +1 -1
- package/dist/{dataLayerProvider-DGKp4zFB.d.cts → dataLayerProvider-BeRXVMs5.d.cts} +1 -1
- package/dist/{dataLayerProvider-SBpz9jQf.d.ts → dataLayerProvider-CG9GfaAY.d.ts} +1 -1
- package/dist/impl/couch/index.d.cts +2 -2
- package/dist/impl/couch/index.d.ts +2 -2
- package/dist/impl/couch/index.js +526 -83
- package/dist/impl/couch/index.js.map +1 -1
- package/dist/impl/couch/index.mjs +526 -83
- package/dist/impl/couch/index.mjs.map +1 -1
- package/dist/impl/static/index.d.cts +2 -2
- package/dist/impl/static/index.d.ts +2 -2
- package/dist/impl/static/index.js +526 -83
- package/dist/impl/static/index.js.map +1 -1
- package/dist/impl/static/index.mjs +526 -83
- package/dist/impl/static/index.mjs.map +1 -1
- package/dist/index.d.cts +247 -14
- package/dist/index.d.ts +247 -14
- package/dist/index.js +1419 -140
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1409 -137
- package/dist/index.mjs.map +1 -1
- package/docs/navigators-architecture.md +22 -4
- package/docs/todo-review-urgency-adaptation.md +205 -0
- package/package.json +3 -3
- package/src/core/interfaces/userDB.ts +44 -0
- package/src/core/navigators/Pipeline.ts +86 -5
- package/src/core/navigators/PipelineAssembler.ts +7 -21
- package/src/core/navigators/PipelineDebugger.ts +426 -0
- package/src/core/navigators/generators/CompositeGenerator.ts +21 -0
- package/src/core/navigators/generators/elo.ts +14 -1
- package/src/core/navigators/generators/srs.ts +146 -18
- package/src/core/navigators/index.ts +9 -0
- package/src/impl/couch/user-course-relDB.ts +12 -0
- package/src/study/MixerDebugger.ts +555 -0
- package/src/study/SessionController.ts +95 -19
- 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/PipelineAssembler.test.ts +4 -4
|
@@ -427,6 +427,15 @@ var init_user_course_relDB = __esm({
|
|
|
427
427
|
void this.user.updateCourseSettings(this._courseId, updates);
|
|
428
428
|
}
|
|
429
429
|
}
|
|
430
|
+
async getStrategyState(strategyKey) {
|
|
431
|
+
return this.user.getStrategyState(this._courseId, strategyKey);
|
|
432
|
+
}
|
|
433
|
+
async putStrategyState(strategyKey, data) {
|
|
434
|
+
return this.user.putStrategyState(this._courseId, strategyKey, data);
|
|
435
|
+
}
|
|
436
|
+
async deleteStrategyState(strategyKey) {
|
|
437
|
+
return this.user.deleteStrategyState(this._courseId, strategyKey);
|
|
438
|
+
}
|
|
430
439
|
async getReviewstoDate(targetDate) {
|
|
431
440
|
const allReviews = await this.user.getPendingReviews(this._courseId);
|
|
432
441
|
logger.debug(
|
|
@@ -497,6 +506,271 @@ var init_courseLookupDB = __esm({
|
|
|
497
506
|
}
|
|
498
507
|
});
|
|
499
508
|
|
|
509
|
+
// src/core/navigators/PipelineDebugger.ts
|
|
510
|
+
var PipelineDebugger_exports = {};
|
|
511
|
+
__export(PipelineDebugger_exports, {
|
|
512
|
+
buildRunReport: () => buildRunReport,
|
|
513
|
+
captureRun: () => captureRun,
|
|
514
|
+
mountPipelineDebugger: () => mountPipelineDebugger,
|
|
515
|
+
pipelineDebugAPI: () => pipelineDebugAPI
|
|
516
|
+
});
|
|
517
|
+
function getOrigin(card) {
|
|
518
|
+
const firstEntry = card.provenance[0];
|
|
519
|
+
if (!firstEntry) return "unknown";
|
|
520
|
+
const reason = firstEntry.reason?.toLowerCase() || "";
|
|
521
|
+
if (reason.includes("new card")) return "new";
|
|
522
|
+
if (reason.includes("review")) return "review";
|
|
523
|
+
return "unknown";
|
|
524
|
+
}
|
|
525
|
+
function captureRun(report) {
|
|
526
|
+
const fullReport = {
|
|
527
|
+
...report,
|
|
528
|
+
runId: `run-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
|
529
|
+
timestamp: /* @__PURE__ */ new Date()
|
|
530
|
+
};
|
|
531
|
+
runHistory.unshift(fullReport);
|
|
532
|
+
if (runHistory.length > MAX_RUNS) {
|
|
533
|
+
runHistory.pop();
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
function buildRunReport(courseId, courseName, generatorName, generators, generatedCount, filters, allCards, selectedCards) {
|
|
537
|
+
const selectedIds = new Set(selectedCards.map((c) => c.cardId));
|
|
538
|
+
const cards = allCards.map((card) => ({
|
|
539
|
+
cardId: card.cardId,
|
|
540
|
+
courseId: card.courseId,
|
|
541
|
+
origin: getOrigin(card),
|
|
542
|
+
finalScore: card.score,
|
|
543
|
+
provenance: card.provenance,
|
|
544
|
+
selected: selectedIds.has(card.cardId)
|
|
545
|
+
}));
|
|
546
|
+
const reviewsSelected = selectedCards.filter((c) => getOrigin(c) === "review").length;
|
|
547
|
+
const newSelected = selectedCards.filter((c) => getOrigin(c) === "new").length;
|
|
548
|
+
return {
|
|
549
|
+
courseId,
|
|
550
|
+
courseName,
|
|
551
|
+
generatorName,
|
|
552
|
+
generators,
|
|
553
|
+
generatedCount,
|
|
554
|
+
filters,
|
|
555
|
+
finalCount: selectedCards.length,
|
|
556
|
+
reviewsSelected,
|
|
557
|
+
newSelected,
|
|
558
|
+
cards
|
|
559
|
+
};
|
|
560
|
+
}
|
|
561
|
+
function formatProvenance(provenance) {
|
|
562
|
+
return provenance.map((p) => {
|
|
563
|
+
const actionSymbol = p.action === "generated" ? "\u{1F3B2}" : p.action === "boosted" ? "\u2B06\uFE0F" : p.action === "penalized" ? "\u2B07\uFE0F" : "\u27A1\uFE0F";
|
|
564
|
+
return ` ${actionSymbol} ${p.strategyName}: ${p.score.toFixed(3)} - ${p.reason}`;
|
|
565
|
+
}).join("\n");
|
|
566
|
+
}
|
|
567
|
+
function printRunSummary(run) {
|
|
568
|
+
console.group(`\u{1F50D} Pipeline Run: ${run.courseId} (${run.courseName || "unnamed"})`);
|
|
569
|
+
logger.info(`Run ID: ${run.runId}`);
|
|
570
|
+
logger.info(`Time: ${run.timestamp.toISOString()}`);
|
|
571
|
+
logger.info(`Generator: ${run.generatorName} \u2192 ${run.generatedCount} candidates`);
|
|
572
|
+
if (run.generators && run.generators.length > 0) {
|
|
573
|
+
console.group("Generator breakdown:");
|
|
574
|
+
for (const g of run.generators) {
|
|
575
|
+
logger.info(
|
|
576
|
+
` ${g.name}: ${g.cardCount} cards (${g.newCount} new, ${g.reviewCount} reviews, top: ${g.topScore.toFixed(2)})`
|
|
577
|
+
);
|
|
578
|
+
}
|
|
579
|
+
console.groupEnd();
|
|
580
|
+
}
|
|
581
|
+
if (run.filters.length > 0) {
|
|
582
|
+
console.group("Filter impact:");
|
|
583
|
+
for (const f of run.filters) {
|
|
584
|
+
logger.info(` ${f.name}: \u2191${f.boosted} \u2193${f.penalized} =${f.passed} \u2715${f.removed}`);
|
|
585
|
+
}
|
|
586
|
+
console.groupEnd();
|
|
587
|
+
}
|
|
588
|
+
logger.info(
|
|
589
|
+
`Result: ${run.finalCount} cards selected (${run.newSelected} new, ${run.reviewsSelected} reviews)`
|
|
590
|
+
);
|
|
591
|
+
console.groupEnd();
|
|
592
|
+
}
|
|
593
|
+
function mountPipelineDebugger() {
|
|
594
|
+
if (typeof window === "undefined") return;
|
|
595
|
+
const win = window;
|
|
596
|
+
win.skuilder = win.skuilder || {};
|
|
597
|
+
win.skuilder.pipeline = pipelineDebugAPI;
|
|
598
|
+
}
|
|
599
|
+
var MAX_RUNS, runHistory, pipelineDebugAPI;
|
|
600
|
+
var init_PipelineDebugger = __esm({
|
|
601
|
+
"src/core/navigators/PipelineDebugger.ts"() {
|
|
602
|
+
"use strict";
|
|
603
|
+
init_logger();
|
|
604
|
+
MAX_RUNS = 10;
|
|
605
|
+
runHistory = [];
|
|
606
|
+
pipelineDebugAPI = {
|
|
607
|
+
/**
|
|
608
|
+
* Get raw run history for programmatic access.
|
|
609
|
+
*/
|
|
610
|
+
get runs() {
|
|
611
|
+
return [...runHistory];
|
|
612
|
+
},
|
|
613
|
+
/**
|
|
614
|
+
* Show summary of a specific pipeline run.
|
|
615
|
+
*/
|
|
616
|
+
showRun(idOrIndex = 0) {
|
|
617
|
+
if (runHistory.length === 0) {
|
|
618
|
+
logger.info("[Pipeline Debug] No runs captured yet.");
|
|
619
|
+
return;
|
|
620
|
+
}
|
|
621
|
+
let run;
|
|
622
|
+
if (typeof idOrIndex === "number") {
|
|
623
|
+
run = runHistory[idOrIndex];
|
|
624
|
+
if (!run) {
|
|
625
|
+
logger.info(
|
|
626
|
+
`[Pipeline Debug] No run found at index ${idOrIndex}. History length: ${runHistory.length}`
|
|
627
|
+
);
|
|
628
|
+
return;
|
|
629
|
+
}
|
|
630
|
+
} else {
|
|
631
|
+
run = runHistory.find((r) => r.runId.endsWith(idOrIndex));
|
|
632
|
+
if (!run) {
|
|
633
|
+
logger.info(`[Pipeline Debug] No run found matching ID '${idOrIndex}'.`);
|
|
634
|
+
return;
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
printRunSummary(run);
|
|
638
|
+
},
|
|
639
|
+
/**
|
|
640
|
+
* Show summary of the last pipeline run.
|
|
641
|
+
*/
|
|
642
|
+
showLastRun() {
|
|
643
|
+
this.showRun(0);
|
|
644
|
+
},
|
|
645
|
+
/**
|
|
646
|
+
* Show detailed provenance for a specific card.
|
|
647
|
+
*/
|
|
648
|
+
showCard(cardId) {
|
|
649
|
+
for (const run of runHistory) {
|
|
650
|
+
const card = run.cards.find((c) => c.cardId === cardId);
|
|
651
|
+
if (card) {
|
|
652
|
+
console.group(`\u{1F3B4} Card: ${cardId}`);
|
|
653
|
+
logger.info(`Course: ${card.courseId}`);
|
|
654
|
+
logger.info(`Origin: ${card.origin}`);
|
|
655
|
+
logger.info(`Final score: ${card.finalScore.toFixed(3)}`);
|
|
656
|
+
logger.info(`Selected: ${card.selected ? "Yes \u2705" : "No \u274C"}`);
|
|
657
|
+
logger.info("Provenance:");
|
|
658
|
+
logger.info(formatProvenance(card.provenance));
|
|
659
|
+
console.groupEnd();
|
|
660
|
+
return;
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
logger.info(`[Pipeline Debug] Card '${cardId}' not found in recent runs.`);
|
|
664
|
+
},
|
|
665
|
+
/**
|
|
666
|
+
* Explain why reviews may or may not have been selected.
|
|
667
|
+
*/
|
|
668
|
+
explainReviews() {
|
|
669
|
+
if (runHistory.length === 0) {
|
|
670
|
+
logger.info("[Pipeline Debug] No runs captured yet.");
|
|
671
|
+
return;
|
|
672
|
+
}
|
|
673
|
+
console.group("\u{1F4CB} Review Selection Analysis");
|
|
674
|
+
for (const run of runHistory) {
|
|
675
|
+
console.group(`Run: ${run.courseId} @ ${run.timestamp.toLocaleTimeString()}`);
|
|
676
|
+
const allReviews = run.cards.filter((c) => c.origin === "review");
|
|
677
|
+
const selectedReviews = allReviews.filter((c) => c.selected);
|
|
678
|
+
if (allReviews.length === 0) {
|
|
679
|
+
logger.info("\u274C No reviews were generated. Check SRS logs for why.");
|
|
680
|
+
} else if (selectedReviews.length === 0) {
|
|
681
|
+
logger.info(`\u26A0\uFE0F ${allReviews.length} reviews generated but none selected.`);
|
|
682
|
+
logger.info("Possible reasons:");
|
|
683
|
+
const topNewScore = Math.max(
|
|
684
|
+
...run.cards.filter((c) => c.origin === "new" && c.selected).map((c) => c.finalScore),
|
|
685
|
+
0
|
|
686
|
+
);
|
|
687
|
+
const topReviewScore = Math.max(...allReviews.map((c) => c.finalScore), 0);
|
|
688
|
+
if (topReviewScore < topNewScore) {
|
|
689
|
+
logger.info(
|
|
690
|
+
` - New cards scored higher (top new: ${topNewScore.toFixed(2)}, top review: ${topReviewScore.toFixed(2)})`
|
|
691
|
+
);
|
|
692
|
+
}
|
|
693
|
+
const topReview = allReviews.sort((a, b) => b.finalScore - a.finalScore)[0];
|
|
694
|
+
if (topReview) {
|
|
695
|
+
logger.info(` - Top review score: ${topReview.finalScore.toFixed(3)}`);
|
|
696
|
+
logger.info(" - Its provenance:");
|
|
697
|
+
logger.info(formatProvenance(topReview.provenance));
|
|
698
|
+
}
|
|
699
|
+
} else {
|
|
700
|
+
logger.info(`\u2705 ${selectedReviews.length}/${allReviews.length} reviews selected.`);
|
|
701
|
+
logger.info("Top selected review:");
|
|
702
|
+
const topSelected = selectedReviews.sort((a, b) => b.finalScore - a.finalScore)[0];
|
|
703
|
+
logger.info(formatProvenance(topSelected.provenance));
|
|
704
|
+
}
|
|
705
|
+
console.groupEnd();
|
|
706
|
+
}
|
|
707
|
+
console.groupEnd();
|
|
708
|
+
},
|
|
709
|
+
/**
|
|
710
|
+
* Show all runs in compact format.
|
|
711
|
+
*/
|
|
712
|
+
listRuns() {
|
|
713
|
+
if (runHistory.length === 0) {
|
|
714
|
+
logger.info("[Pipeline Debug] No runs captured yet.");
|
|
715
|
+
return;
|
|
716
|
+
}
|
|
717
|
+
console.table(
|
|
718
|
+
runHistory.map((r) => ({
|
|
719
|
+
id: r.runId.slice(-8),
|
|
720
|
+
time: r.timestamp.toLocaleTimeString(),
|
|
721
|
+
course: r.courseName || r.courseId.slice(0, 8),
|
|
722
|
+
generated: r.generatedCount,
|
|
723
|
+
selected: r.finalCount,
|
|
724
|
+
new: r.newSelected,
|
|
725
|
+
reviews: r.reviewsSelected
|
|
726
|
+
}))
|
|
727
|
+
);
|
|
728
|
+
},
|
|
729
|
+
/**
|
|
730
|
+
* Export run history as JSON for bug reports.
|
|
731
|
+
*/
|
|
732
|
+
export() {
|
|
733
|
+
const json = JSON.stringify(runHistory, null, 2);
|
|
734
|
+
logger.info("[Pipeline Debug] Run history exported. Copy the returned string or use:");
|
|
735
|
+
logger.info(" copy(window.skuilder.pipeline.export())");
|
|
736
|
+
return json;
|
|
737
|
+
},
|
|
738
|
+
/**
|
|
739
|
+
* Clear run history.
|
|
740
|
+
*/
|
|
741
|
+
clear() {
|
|
742
|
+
runHistory.length = 0;
|
|
743
|
+
logger.info("[Pipeline Debug] Run history cleared.");
|
|
744
|
+
},
|
|
745
|
+
/**
|
|
746
|
+
* Show help.
|
|
747
|
+
*/
|
|
748
|
+
help() {
|
|
749
|
+
logger.info(`
|
|
750
|
+
\u{1F527} Pipeline Debug API
|
|
751
|
+
|
|
752
|
+
Commands:
|
|
753
|
+
.showLastRun() Show summary of most recent pipeline run
|
|
754
|
+
.showRun(id|index) Show summary of a specific run (by index or ID suffix)
|
|
755
|
+
.showCard(cardId) Show provenance trail for a specific card
|
|
756
|
+
.explainReviews() Analyze why reviews were/weren't selected
|
|
757
|
+
.listRuns() List all captured runs in table format
|
|
758
|
+
.export() Export run history as JSON for bug reports
|
|
759
|
+
.clear() Clear run history
|
|
760
|
+
.runs Access raw run history array
|
|
761
|
+
.help() Show this help message
|
|
762
|
+
|
|
763
|
+
Example:
|
|
764
|
+
window.skuilder.pipeline.showLastRun()
|
|
765
|
+
window.skuilder.pipeline.showRun(1)
|
|
766
|
+
window.skuilder.pipeline.showCard('abc123')
|
|
767
|
+
`);
|
|
768
|
+
}
|
|
769
|
+
};
|
|
770
|
+
mountPipelineDebugger();
|
|
771
|
+
}
|
|
772
|
+
});
|
|
773
|
+
|
|
500
774
|
// src/core/navigators/generators/CompositeGenerator.ts
|
|
501
775
|
var CompositeGenerator_exports = {};
|
|
502
776
|
__export(CompositeGenerator_exports, {
|
|
@@ -565,6 +839,24 @@ var init_CompositeGenerator = __esm({
|
|
|
565
839
|
const results = await Promise.all(
|
|
566
840
|
this.generators.map((g) => g.getWeightedCards(limit, context))
|
|
567
841
|
);
|
|
842
|
+
const generatorSummaries = [];
|
|
843
|
+
results.forEach((cards, index) => {
|
|
844
|
+
const gen = this.generators[index];
|
|
845
|
+
const genName = gen.name || `Generator ${index}`;
|
|
846
|
+
const newCards = cards.filter((c) => c.provenance[0]?.reason?.includes("new card"));
|
|
847
|
+
const reviewCards = cards.filter((c) => c.provenance[0]?.reason?.includes("review"));
|
|
848
|
+
if (cards.length > 0) {
|
|
849
|
+
const topScore = Math.max(...cards.map((c) => c.score)).toFixed(2);
|
|
850
|
+
const parts = [];
|
|
851
|
+
if (newCards.length > 0) parts.push(`${newCards.length} new`);
|
|
852
|
+
if (reviewCards.length > 0) parts.push(`${reviewCards.length} reviews`);
|
|
853
|
+
const breakdown = parts.length > 0 ? parts.join(", ") : `${cards.length} cards`;
|
|
854
|
+
generatorSummaries.push(`${genName}: ${breakdown} (top: ${topScore})`);
|
|
855
|
+
} else {
|
|
856
|
+
generatorSummaries.push(`${genName}: 0 cards`);
|
|
857
|
+
}
|
|
858
|
+
});
|
|
859
|
+
logger.info(`[Composite] Generator breakdown: ${generatorSummaries.join(" | ")}`);
|
|
568
860
|
const byCardId = /* @__PURE__ */ new Map();
|
|
569
861
|
results.forEach((cards, index) => {
|
|
570
862
|
const gen = this.generators[index];
|
|
@@ -682,6 +974,7 @@ var init_elo = __esm({
|
|
|
682
974
|
"use strict";
|
|
683
975
|
init_navigators();
|
|
684
976
|
import_common5 = require("@vue-skuilder/common");
|
|
977
|
+
init_logger();
|
|
685
978
|
ELONavigator = class extends ContentNavigator {
|
|
686
979
|
/** Human-readable name for CardGenerator interface */
|
|
687
980
|
name;
|
|
@@ -741,7 +1034,16 @@ var init_elo = __esm({
|
|
|
741
1034
|
};
|
|
742
1035
|
});
|
|
743
1036
|
scored.sort((a, b) => b.score - a.score);
|
|
744
|
-
|
|
1037
|
+
const result = scored.slice(0, limit);
|
|
1038
|
+
if (result.length > 0) {
|
|
1039
|
+
const topScores = result.slice(0, 3).map((c) => c.score.toFixed(2)).join(", ");
|
|
1040
|
+
logger.info(
|
|
1041
|
+
`[ELO] Course ${this.course.getCourseID()}: ${result.length} new cards (top scores: ${topScores})`
|
|
1042
|
+
);
|
|
1043
|
+
} else {
|
|
1044
|
+
logger.info(`[ELO] Course ${this.course.getCourseID()}: No new cards available`);
|
|
1045
|
+
}
|
|
1046
|
+
return result;
|
|
745
1047
|
}
|
|
746
1048
|
};
|
|
747
1049
|
}
|
|
@@ -760,19 +1062,37 @@ var srs_exports = {};
|
|
|
760
1062
|
__export(srs_exports, {
|
|
761
1063
|
default: () => SRSNavigator
|
|
762
1064
|
});
|
|
763
|
-
var import_moment3, SRSNavigator;
|
|
1065
|
+
var import_moment3, DEFAULT_HEALTHY_BACKLOG, MAX_BACKLOG_PRESSURE, SRSNavigator;
|
|
764
1066
|
var init_srs = __esm({
|
|
765
1067
|
"src/core/navigators/generators/srs.ts"() {
|
|
766
1068
|
"use strict";
|
|
767
1069
|
import_moment3 = __toESM(require("moment"), 1);
|
|
768
1070
|
init_navigators();
|
|
769
1071
|
init_logger();
|
|
1072
|
+
DEFAULT_HEALTHY_BACKLOG = 20;
|
|
1073
|
+
MAX_BACKLOG_PRESSURE = 0.5;
|
|
770
1074
|
SRSNavigator = class extends ContentNavigator {
|
|
771
1075
|
/** Human-readable name for CardGenerator interface */
|
|
772
1076
|
name;
|
|
1077
|
+
/** Healthy backlog threshold - when exceeded, backlog pressure kicks in */
|
|
1078
|
+
healthyBacklog;
|
|
773
1079
|
constructor(user, course, strategyData) {
|
|
774
1080
|
super(user, course, strategyData);
|
|
775
1081
|
this.name = strategyData?.name || "SRS";
|
|
1082
|
+
const config = this.parseConfig(strategyData?.serializedData);
|
|
1083
|
+
this.healthyBacklog = config.healthyBacklog ?? DEFAULT_HEALTHY_BACKLOG;
|
|
1084
|
+
}
|
|
1085
|
+
/**
|
|
1086
|
+
* Parse configuration from serialized JSON.
|
|
1087
|
+
*/
|
|
1088
|
+
parseConfig(serializedData) {
|
|
1089
|
+
if (!serializedData) return {};
|
|
1090
|
+
try {
|
|
1091
|
+
return JSON.parse(serializedData);
|
|
1092
|
+
} catch {
|
|
1093
|
+
logger.warn("[SRS] Failed to parse strategy config, using defaults");
|
|
1094
|
+
return {};
|
|
1095
|
+
}
|
|
776
1096
|
}
|
|
777
1097
|
/**
|
|
778
1098
|
* Get review cards scored by urgency.
|
|
@@ -780,6 +1100,7 @@ var init_srs = __esm({
|
|
|
780
1100
|
* Score formula combines:
|
|
781
1101
|
* - Relative overdueness: hoursOverdue / intervalHours
|
|
782
1102
|
* - Interval recency: exponential decay favoring shorter intervals
|
|
1103
|
+
* - Backlog pressure: boost when due reviews exceed healthy threshold
|
|
783
1104
|
*
|
|
784
1105
|
* Cards not yet due are excluded (not scored as 0).
|
|
785
1106
|
*
|
|
@@ -793,11 +1114,32 @@ var init_srs = __esm({
|
|
|
793
1114
|
if (!this.user || !this.course) {
|
|
794
1115
|
throw new Error("SRSNavigator requires user and course to be set");
|
|
795
1116
|
}
|
|
796
|
-
const
|
|
1117
|
+
const courseId = this.course.getCourseID();
|
|
1118
|
+
const reviews = await this.user.getPendingReviews(courseId);
|
|
797
1119
|
const now = import_moment3.default.utc();
|
|
798
1120
|
const dueReviews = reviews.filter((r) => now.isAfter(import_moment3.default.utc(r.reviewTime)));
|
|
1121
|
+
const backlogPressure = this.computeBacklogPressure(dueReviews.length);
|
|
1122
|
+
if (dueReviews.length > 0) {
|
|
1123
|
+
const pressureNote = backlogPressure > 0 ? ` [backlog pressure: +${backlogPressure.toFixed(2)}]` : ` [healthy backlog]`;
|
|
1124
|
+
logger.info(
|
|
1125
|
+
`[SRS] Course ${courseId}: ${dueReviews.length} reviews due now (of ${reviews.length} scheduled)${pressureNote}`
|
|
1126
|
+
);
|
|
1127
|
+
} else if (reviews.length > 0) {
|
|
1128
|
+
const sortedByDue = [...reviews].sort(
|
|
1129
|
+
(a, b) => import_moment3.default.utc(a.reviewTime).diff(import_moment3.default.utc(b.reviewTime))
|
|
1130
|
+
);
|
|
1131
|
+
const nextDue = sortedByDue[0];
|
|
1132
|
+
const nextDueTime = import_moment3.default.utc(nextDue.reviewTime);
|
|
1133
|
+
const untilDue = import_moment3.default.duration(nextDueTime.diff(now));
|
|
1134
|
+
const untilDueStr = untilDue.asHours() < 1 ? `${Math.round(untilDue.asMinutes())}m` : untilDue.asHours() < 24 ? `${Math.round(untilDue.asHours())}h` : `${Math.round(untilDue.asDays())}d`;
|
|
1135
|
+
logger.info(
|
|
1136
|
+
`[SRS] Course ${courseId}: 0 reviews due now (${reviews.length} scheduled, next in ${untilDueStr})`
|
|
1137
|
+
);
|
|
1138
|
+
} else {
|
|
1139
|
+
logger.info(`[SRS] Course ${courseId}: No reviews scheduled`);
|
|
1140
|
+
}
|
|
799
1141
|
const scored = dueReviews.map((review) => {
|
|
800
|
-
const { score, reason } = this.computeUrgencyScore(review, now);
|
|
1142
|
+
const { score, reason } = this.computeUrgencyScore(review, now, backlogPressure);
|
|
801
1143
|
return {
|
|
802
1144
|
cardId: review.cardId,
|
|
803
1145
|
courseId: review.courseId,
|
|
@@ -815,13 +1157,35 @@ var init_srs = __esm({
|
|
|
815
1157
|
]
|
|
816
1158
|
};
|
|
817
1159
|
});
|
|
818
|
-
logger.debug(`[srsNav] got ${scored.length} weighted cards`);
|
|
819
1160
|
return scored.sort((a, b) => b.score - a.score).slice(0, limit);
|
|
820
1161
|
}
|
|
1162
|
+
/**
|
|
1163
|
+
* Compute backlog pressure based on number of due reviews.
|
|
1164
|
+
*
|
|
1165
|
+
* Backlog pressure is 0 when at or below healthy threshold,
|
|
1166
|
+
* and increases linearly above it, maxing out at MAX_BACKLOG_PRESSURE.
|
|
1167
|
+
*
|
|
1168
|
+
* Examples (with default healthyBacklog=20):
|
|
1169
|
+
* - 10 due reviews → 0.00 (healthy)
|
|
1170
|
+
* - 20 due reviews → 0.00 (at threshold)
|
|
1171
|
+
* - 40 due reviews → 0.25 (2x threshold)
|
|
1172
|
+
* - 60 due reviews → 0.50 (3x threshold, maxed)
|
|
1173
|
+
*
|
|
1174
|
+
* @param dueCount - Number of reviews currently due
|
|
1175
|
+
* @returns Backlog pressure score to add to urgency (0 to MAX_BACKLOG_PRESSURE)
|
|
1176
|
+
*/
|
|
1177
|
+
computeBacklogPressure(dueCount) {
|
|
1178
|
+
if (dueCount <= this.healthyBacklog) {
|
|
1179
|
+
return 0;
|
|
1180
|
+
}
|
|
1181
|
+
const excess = dueCount - this.healthyBacklog;
|
|
1182
|
+
const pressure = excess / this.healthyBacklog * (MAX_BACKLOG_PRESSURE / 2);
|
|
1183
|
+
return Math.min(MAX_BACKLOG_PRESSURE, pressure);
|
|
1184
|
+
}
|
|
821
1185
|
/**
|
|
822
1186
|
* Compute urgency score for a review card.
|
|
823
1187
|
*
|
|
824
|
-
*
|
|
1188
|
+
* Three factors:
|
|
825
1189
|
* 1. Relative overdueness = hoursOverdue / intervalHours
|
|
826
1190
|
* - 2 days overdue on 3-day interval = 0.67 (urgent)
|
|
827
1191
|
* - 2 days overdue on 180-day interval = 0.01 (not urgent)
|
|
@@ -831,10 +1195,19 @@ var init_srs = __esm({
|
|
|
831
1195
|
* - 30 days (720h) → ~0.56
|
|
832
1196
|
* - 180 days → ~0.30
|
|
833
1197
|
*
|
|
834
|
-
*
|
|
835
|
-
*
|
|
1198
|
+
* 3. Backlog pressure = global boost when review backlog exceeds healthy threshold
|
|
1199
|
+
* - At healthy backlog: 0
|
|
1200
|
+
* - At 2x healthy: +0.25
|
|
1201
|
+
* - At 3x+ healthy: +0.50 (max)
|
|
1202
|
+
*
|
|
1203
|
+
* Combined: base 0.5 + (urgency factors * 0.45) + backlog pressure
|
|
1204
|
+
* Result range: 0.5 to 1.0 (uncapped to allow high-urgency reviews to compete with new cards)
|
|
1205
|
+
*
|
|
1206
|
+
* @param review - The scheduled card to score
|
|
1207
|
+
* @param now - Current time
|
|
1208
|
+
* @param backlogPressure - Pre-computed backlog pressure (0 to 0.5)
|
|
836
1209
|
*/
|
|
837
|
-
computeUrgencyScore(review, now) {
|
|
1210
|
+
computeUrgencyScore(review, now, backlogPressure) {
|
|
838
1211
|
const scheduledAt = import_moment3.default.utc(review.scheduledAt);
|
|
839
1212
|
const due = import_moment3.default.utc(review.reviewTime);
|
|
840
1213
|
const intervalHours = Math.max(1, due.diff(scheduledAt, "hours"));
|
|
@@ -843,8 +1216,19 @@ var init_srs = __esm({
|
|
|
843
1216
|
const recencyFactor = 0.3 + 0.7 * Math.exp(-intervalHours / 720);
|
|
844
1217
|
const overdueContribution = Math.min(1, Math.max(0, relativeOverdue));
|
|
845
1218
|
const urgency = overdueContribution * 0.5 + recencyFactor * 0.5;
|
|
846
|
-
const
|
|
847
|
-
const
|
|
1219
|
+
const baseScore = 0.5 + urgency * 0.45;
|
|
1220
|
+
const score = Math.min(1, baseScore + backlogPressure);
|
|
1221
|
+
const reasonParts = [
|
|
1222
|
+
`${Math.round(hoursOverdue)}h overdue`,
|
|
1223
|
+
`interval: ${Math.round(intervalHours)}h`,
|
|
1224
|
+
`relative: ${relativeOverdue.toFixed(2)}`,
|
|
1225
|
+
`recency: ${recencyFactor.toFixed(2)}`
|
|
1226
|
+
];
|
|
1227
|
+
if (backlogPressure > 0) {
|
|
1228
|
+
reasonParts.push(`backlog: +${backlogPressure.toFixed(2)}`);
|
|
1229
|
+
}
|
|
1230
|
+
reasonParts.push("review");
|
|
1231
|
+
const reason = reasonParts.join(", ");
|
|
848
1232
|
return { score, reason };
|
|
849
1233
|
}
|
|
850
1234
|
};
|
|
@@ -1874,10 +2258,23 @@ function logTagHydration(cards, tagsByCard) {
|
|
|
1874
2258
|
`[Pipeline] Tag hydration: ${cards.length} cards, ${cardsWithTags} have tags (${totalTags} total tags) - single batch query`
|
|
1875
2259
|
);
|
|
1876
2260
|
}
|
|
1877
|
-
function logExecutionSummary(generatorName, generatedCount, filterCount, finalCount, topScores) {
|
|
2261
|
+
function logExecutionSummary(generatorName, generatedCount, filterCount, finalCount, topScores, filterImpacts) {
|
|
1878
2262
|
const scoreDisplay = topScores.length > 0 ? topScores.map((s) => s.toFixed(2)).join(", ") : "none";
|
|
2263
|
+
let filterSummary = "";
|
|
2264
|
+
if (filterImpacts.length > 0) {
|
|
2265
|
+
const impacts = filterImpacts.map((f) => {
|
|
2266
|
+
const parts = [];
|
|
2267
|
+
if (f.boosted > 0) parts.push(`+${f.boosted}`);
|
|
2268
|
+
if (f.penalized > 0) parts.push(`-${f.penalized}`);
|
|
2269
|
+
if (f.passed > 0) parts.push(`=${f.passed}`);
|
|
2270
|
+
return `${f.name}: ${parts.join("/")}`;
|
|
2271
|
+
});
|
|
2272
|
+
filterSummary = `
|
|
2273
|
+
Filter impact: ${impacts.join(", ")}`;
|
|
2274
|
+
}
|
|
1879
2275
|
logger.info(
|
|
1880
|
-
`[Pipeline] Execution: ${generatorName} produced ${generatedCount} \u2192 ${filterCount} filters \u2192 ${finalCount} results (top scores: ${scoreDisplay})`
|
|
2276
|
+
`[Pipeline] Execution: ${generatorName} produced ${generatedCount} \u2192 ${filterCount} filters \u2192 ${finalCount} results (top scores: ${scoreDisplay})` + filterSummary + `
|
|
2277
|
+
\u{1F4A1} Inspect: window.skuilder.pipeline`
|
|
1881
2278
|
);
|
|
1882
2279
|
}
|
|
1883
2280
|
function logCardProvenance(cards, maxCards = 3) {
|
|
@@ -1902,6 +2299,7 @@ var init_Pipeline = __esm({
|
|
|
1902
2299
|
init_navigators();
|
|
1903
2300
|
init_logger();
|
|
1904
2301
|
init_orchestration();
|
|
2302
|
+
init_PipelineDebugger();
|
|
1905
2303
|
Pipeline = class extends ContentNavigator {
|
|
1906
2304
|
generator;
|
|
1907
2305
|
filters;
|
|
@@ -1949,12 +2347,49 @@ var init_Pipeline = __esm({
|
|
|
1949
2347
|
);
|
|
1950
2348
|
let cards = await this.generator.getWeightedCards(fetchLimit, context);
|
|
1951
2349
|
const generatedCount = cards.length;
|
|
2350
|
+
let generatorSummaries;
|
|
2351
|
+
if (this.generator.generators) {
|
|
2352
|
+
const genMap = /* @__PURE__ */ new Map();
|
|
2353
|
+
for (const card of cards) {
|
|
2354
|
+
const firstProv = card.provenance[0];
|
|
2355
|
+
if (firstProv) {
|
|
2356
|
+
const genName = firstProv.strategyName;
|
|
2357
|
+
if (!genMap.has(genName)) {
|
|
2358
|
+
genMap.set(genName, { cards: [] });
|
|
2359
|
+
}
|
|
2360
|
+
genMap.get(genName).cards.push(card);
|
|
2361
|
+
}
|
|
2362
|
+
}
|
|
2363
|
+
generatorSummaries = Array.from(genMap.entries()).map(([name, data]) => {
|
|
2364
|
+
const newCards = data.cards.filter((c) => c.provenance[0]?.reason?.includes("new card"));
|
|
2365
|
+
const reviewCards = data.cards.filter((c) => c.provenance[0]?.reason?.includes("review"));
|
|
2366
|
+
return {
|
|
2367
|
+
name,
|
|
2368
|
+
cardCount: data.cards.length,
|
|
2369
|
+
newCount: newCards.length,
|
|
2370
|
+
reviewCount: reviewCards.length,
|
|
2371
|
+
topScore: Math.max(...data.cards.map((c) => c.score), 0)
|
|
2372
|
+
};
|
|
2373
|
+
});
|
|
2374
|
+
}
|
|
1952
2375
|
logger.debug(`[Pipeline] Generator returned ${generatedCount} candidates`);
|
|
1953
2376
|
cards = await this.hydrateTags(cards);
|
|
2377
|
+
const allCardsBeforeFiltering = [...cards];
|
|
2378
|
+
const filterImpacts = [];
|
|
1954
2379
|
for (const filter of this.filters) {
|
|
1955
2380
|
const beforeCount = cards.length;
|
|
2381
|
+
const beforeScores = new Map(cards.map((c) => [c.cardId, c.score]));
|
|
1956
2382
|
cards = await filter.transform(cards, context);
|
|
1957
|
-
|
|
2383
|
+
let boosted = 0, penalized = 0, passed = 0;
|
|
2384
|
+
const removed = beforeCount - cards.length;
|
|
2385
|
+
for (const card of cards) {
|
|
2386
|
+
const before = beforeScores.get(card.cardId) ?? 0;
|
|
2387
|
+
if (card.score > before) boosted++;
|
|
2388
|
+
else if (card.score < before) penalized++;
|
|
2389
|
+
else passed++;
|
|
2390
|
+
}
|
|
2391
|
+
filterImpacts.push({ name: filter.name, boosted, penalized, passed, removed });
|
|
2392
|
+
logger.debug(`[Pipeline] Filter '${filter.name}': ${beforeScores.size} \u2192 ${cards.length} cards (\u2191${boosted} \u2193${penalized} =${passed})`);
|
|
1958
2393
|
}
|
|
1959
2394
|
cards = cards.filter((c) => c.score > 0);
|
|
1960
2395
|
cards.sort((a, b) => b.score - a.score);
|
|
@@ -1965,9 +2400,26 @@ var init_Pipeline = __esm({
|
|
|
1965
2400
|
generatedCount,
|
|
1966
2401
|
this.filters.length,
|
|
1967
2402
|
result.length,
|
|
1968
|
-
topScores
|
|
2403
|
+
topScores,
|
|
2404
|
+
filterImpacts
|
|
1969
2405
|
);
|
|
1970
2406
|
logCardProvenance(result, 3);
|
|
2407
|
+
try {
|
|
2408
|
+
const courseName = await this.course?.getCourseConfig().then((c) => c.name).catch(() => void 0);
|
|
2409
|
+
const report = buildRunReport(
|
|
2410
|
+
this.course?.getCourseID() || "unknown",
|
|
2411
|
+
courseName,
|
|
2412
|
+
this.generator.name,
|
|
2413
|
+
generatorSummaries,
|
|
2414
|
+
generatedCount,
|
|
2415
|
+
filterImpacts,
|
|
2416
|
+
allCardsBeforeFiltering,
|
|
2417
|
+
result
|
|
2418
|
+
);
|
|
2419
|
+
captureRun(report);
|
|
2420
|
+
} catch (e) {
|
|
2421
|
+
logger.debug(`[Pipeline] Failed to capture debug run: ${e}`);
|
|
2422
|
+
}
|
|
1971
2423
|
return result;
|
|
1972
2424
|
}
|
|
1973
2425
|
/**
|
|
@@ -2057,6 +2509,56 @@ var init_Pipeline = __esm({
|
|
|
2057
2509
|
}
|
|
2058
2510
|
});
|
|
2059
2511
|
|
|
2512
|
+
// src/core/navigators/defaults.ts
|
|
2513
|
+
var defaults_exports = {};
|
|
2514
|
+
__export(defaults_exports, {
|
|
2515
|
+
createDefaultEloStrategy: () => createDefaultEloStrategy,
|
|
2516
|
+
createDefaultPipeline: () => createDefaultPipeline,
|
|
2517
|
+
createDefaultSrsStrategy: () => createDefaultSrsStrategy
|
|
2518
|
+
});
|
|
2519
|
+
function createDefaultEloStrategy(courseId) {
|
|
2520
|
+
return {
|
|
2521
|
+
_id: "NAVIGATION_STRATEGY-ELO-default",
|
|
2522
|
+
docType: "NAVIGATION_STRATEGY" /* NAVIGATION_STRATEGY */,
|
|
2523
|
+
name: "ELO (default)",
|
|
2524
|
+
description: "Default ELO-based navigation strategy for new cards",
|
|
2525
|
+
implementingClass: "elo" /* ELO */,
|
|
2526
|
+
course: courseId,
|
|
2527
|
+
serializedData: ""
|
|
2528
|
+
};
|
|
2529
|
+
}
|
|
2530
|
+
function createDefaultSrsStrategy(courseId) {
|
|
2531
|
+
return {
|
|
2532
|
+
_id: "NAVIGATION_STRATEGY-SRS-default",
|
|
2533
|
+
docType: "NAVIGATION_STRATEGY" /* NAVIGATION_STRATEGY */,
|
|
2534
|
+
name: "SRS (default)",
|
|
2535
|
+
description: "Default SRS-based navigation strategy for reviews",
|
|
2536
|
+
implementingClass: "srs" /* SRS */,
|
|
2537
|
+
course: courseId,
|
|
2538
|
+
serializedData: ""
|
|
2539
|
+
};
|
|
2540
|
+
}
|
|
2541
|
+
function createDefaultPipeline(user, course) {
|
|
2542
|
+
const courseId = course.getCourseID();
|
|
2543
|
+
const eloNavigator = new ELONavigator(user, course, createDefaultEloStrategy(courseId));
|
|
2544
|
+
const srsNavigator = new SRSNavigator(user, course, createDefaultSrsStrategy(courseId));
|
|
2545
|
+
const compositeGenerator = new CompositeGenerator([eloNavigator, srsNavigator]);
|
|
2546
|
+
const eloDistanceFilter = createEloDistanceFilter();
|
|
2547
|
+
return new Pipeline(compositeGenerator, [eloDistanceFilter], user, course);
|
|
2548
|
+
}
|
|
2549
|
+
var init_defaults = __esm({
|
|
2550
|
+
"src/core/navigators/defaults.ts"() {
|
|
2551
|
+
"use strict";
|
|
2552
|
+
init_navigators();
|
|
2553
|
+
init_Pipeline();
|
|
2554
|
+
init_CompositeGenerator();
|
|
2555
|
+
init_elo();
|
|
2556
|
+
init_srs();
|
|
2557
|
+
init_eloDistance();
|
|
2558
|
+
init_types_legacy();
|
|
2559
|
+
}
|
|
2560
|
+
});
|
|
2561
|
+
|
|
2060
2562
|
// src/core/navigators/PipelineAssembler.ts
|
|
2061
2563
|
var PipelineAssembler_exports = {};
|
|
2062
2564
|
__export(PipelineAssembler_exports, {
|
|
@@ -2069,9 +2571,9 @@ var init_PipelineAssembler = __esm({
|
|
|
2069
2571
|
init_navigators();
|
|
2070
2572
|
init_WeightedFilter();
|
|
2071
2573
|
init_Pipeline();
|
|
2072
|
-
init_types_legacy();
|
|
2073
2574
|
init_logger();
|
|
2074
2575
|
init_CompositeGenerator();
|
|
2576
|
+
init_defaults();
|
|
2075
2577
|
PipelineAssembler = class {
|
|
2076
2578
|
/**
|
|
2077
2579
|
* Assembles a navigation pipeline from strategy documents.
|
|
@@ -2110,9 +2612,11 @@ var init_PipelineAssembler = __esm({
|
|
|
2110
2612
|
if (generatorStrategies.length === 0) {
|
|
2111
2613
|
if (filterStrategies.length > 0) {
|
|
2112
2614
|
logger.debug(
|
|
2113
|
-
"[PipelineAssembler] No generator found, using default ELO with configured filters"
|
|
2615
|
+
"[PipelineAssembler] No generator found, using default ELO and SRS with configured filters"
|
|
2114
2616
|
);
|
|
2115
|
-
|
|
2617
|
+
const courseId = course.getCourseID();
|
|
2618
|
+
generatorStrategies.push(createDefaultEloStrategy(courseId));
|
|
2619
|
+
generatorStrategies.push(createDefaultSrsStrategy(courseId));
|
|
2116
2620
|
} else {
|
|
2117
2621
|
warnings.push("No generator strategy found");
|
|
2118
2622
|
return {
|
|
@@ -2173,75 +2677,10 @@ var init_PipelineAssembler = __esm({
|
|
|
2173
2677
|
warnings
|
|
2174
2678
|
};
|
|
2175
2679
|
}
|
|
2176
|
-
/**
|
|
2177
|
-
* Creates a default ELO generator strategy.
|
|
2178
|
-
* Used when filters are configured but no generator is specified.
|
|
2179
|
-
*/
|
|
2180
|
-
makeDefaultEloStrategy(courseId) {
|
|
2181
|
-
return {
|
|
2182
|
-
_id: "NAVIGATION_STRATEGY-ELO-default",
|
|
2183
|
-
course: courseId,
|
|
2184
|
-
docType: "NAVIGATION_STRATEGY" /* NAVIGATION_STRATEGY */,
|
|
2185
|
-
name: "ELO (default)",
|
|
2186
|
-
description: "Default ELO-based generator",
|
|
2187
|
-
implementingClass: "elo" /* ELO */,
|
|
2188
|
-
serializedData: ""
|
|
2189
|
-
};
|
|
2190
|
-
}
|
|
2191
2680
|
};
|
|
2192
2681
|
}
|
|
2193
2682
|
});
|
|
2194
2683
|
|
|
2195
|
-
// src/core/navigators/defaults.ts
|
|
2196
|
-
var defaults_exports = {};
|
|
2197
|
-
__export(defaults_exports, {
|
|
2198
|
-
createDefaultEloStrategy: () => createDefaultEloStrategy,
|
|
2199
|
-
createDefaultPipeline: () => createDefaultPipeline,
|
|
2200
|
-
createDefaultSrsStrategy: () => createDefaultSrsStrategy
|
|
2201
|
-
});
|
|
2202
|
-
function createDefaultEloStrategy(courseId) {
|
|
2203
|
-
return {
|
|
2204
|
-
_id: "NAVIGATION_STRATEGY-ELO-default",
|
|
2205
|
-
docType: "NAVIGATION_STRATEGY" /* NAVIGATION_STRATEGY */,
|
|
2206
|
-
name: "ELO (default)",
|
|
2207
|
-
description: "Default ELO-based navigation strategy for new cards",
|
|
2208
|
-
implementingClass: "elo" /* ELO */,
|
|
2209
|
-
course: courseId,
|
|
2210
|
-
serializedData: ""
|
|
2211
|
-
};
|
|
2212
|
-
}
|
|
2213
|
-
function createDefaultSrsStrategy(courseId) {
|
|
2214
|
-
return {
|
|
2215
|
-
_id: "NAVIGATION_STRATEGY-SRS-default",
|
|
2216
|
-
docType: "NAVIGATION_STRATEGY" /* NAVIGATION_STRATEGY */,
|
|
2217
|
-
name: "SRS (default)",
|
|
2218
|
-
description: "Default SRS-based navigation strategy for reviews",
|
|
2219
|
-
implementingClass: "srs" /* SRS */,
|
|
2220
|
-
course: courseId,
|
|
2221
|
-
serializedData: ""
|
|
2222
|
-
};
|
|
2223
|
-
}
|
|
2224
|
-
function createDefaultPipeline(user, course) {
|
|
2225
|
-
const courseId = course.getCourseID();
|
|
2226
|
-
const eloNavigator = new ELONavigator(user, course, createDefaultEloStrategy(courseId));
|
|
2227
|
-
const srsNavigator = new SRSNavigator(user, course, createDefaultSrsStrategy(courseId));
|
|
2228
|
-
const compositeGenerator = new CompositeGenerator([eloNavigator, srsNavigator]);
|
|
2229
|
-
const eloDistanceFilter = createEloDistanceFilter();
|
|
2230
|
-
return new Pipeline(compositeGenerator, [eloDistanceFilter], user, course);
|
|
2231
|
-
}
|
|
2232
|
-
var init_defaults = __esm({
|
|
2233
|
-
"src/core/navigators/defaults.ts"() {
|
|
2234
|
-
"use strict";
|
|
2235
|
-
init_navigators();
|
|
2236
|
-
init_Pipeline();
|
|
2237
|
-
init_CompositeGenerator();
|
|
2238
|
-
init_elo();
|
|
2239
|
-
init_srs();
|
|
2240
|
-
init_eloDistance();
|
|
2241
|
-
init_types_legacy();
|
|
2242
|
-
}
|
|
2243
|
-
});
|
|
2244
|
-
|
|
2245
2684
|
// import("./**/*") in src/core/navigators/index.ts
|
|
2246
2685
|
var globImport;
|
|
2247
2686
|
var init_3 = __esm({
|
|
@@ -2249,6 +2688,7 @@ var init_3 = __esm({
|
|
|
2249
2688
|
globImport = __glob({
|
|
2250
2689
|
"./Pipeline.ts": () => Promise.resolve().then(() => (init_Pipeline(), Pipeline_exports)),
|
|
2251
2690
|
"./PipelineAssembler.ts": () => Promise.resolve().then(() => (init_PipelineAssembler(), PipelineAssembler_exports)),
|
|
2691
|
+
"./PipelineDebugger.ts": () => Promise.resolve().then(() => (init_PipelineDebugger(), PipelineDebugger_exports)),
|
|
2252
2692
|
"./defaults.ts": () => Promise.resolve().then(() => (init_defaults(), defaults_exports)),
|
|
2253
2693
|
"./filters/WeightedFilter.ts": () => Promise.resolve().then(() => (init_WeightedFilter(), WeightedFilter_exports)),
|
|
2254
2694
|
"./filters/eloDistance.ts": () => Promise.resolve().then(() => (init_eloDistance(), eloDistance_exports)),
|
|
@@ -2284,6 +2724,8 @@ __export(navigators_exports, {
|
|
|
2284
2724
|
initializeNavigatorRegistry: () => initializeNavigatorRegistry,
|
|
2285
2725
|
isFilter: () => isFilter,
|
|
2286
2726
|
isGenerator: () => isGenerator,
|
|
2727
|
+
mountPipelineDebugger: () => mountPipelineDebugger,
|
|
2728
|
+
pipelineDebugAPI: () => pipelineDebugAPI,
|
|
2287
2729
|
registerNavigator: () => registerNavigator
|
|
2288
2730
|
});
|
|
2289
2731
|
function registerNavigator(implementingClass, constructor) {
|
|
@@ -2350,6 +2792,7 @@ var navigatorRegistry, Navigators, NavigatorRole, NavigatorRoles, ContentNavigat
|
|
|
2350
2792
|
var init_navigators = __esm({
|
|
2351
2793
|
"src/core/navigators/index.ts"() {
|
|
2352
2794
|
"use strict";
|
|
2795
|
+
init_PipelineDebugger();
|
|
2353
2796
|
init_logger();
|
|
2354
2797
|
init_();
|
|
2355
2798
|
init_2();
|