@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
|
@@ -605,6 +605,271 @@ var init_courseLookupDB = __esm({
|
|
|
605
605
|
}
|
|
606
606
|
});
|
|
607
607
|
|
|
608
|
+
// src/core/navigators/PipelineDebugger.ts
|
|
609
|
+
var PipelineDebugger_exports = {};
|
|
610
|
+
__export(PipelineDebugger_exports, {
|
|
611
|
+
buildRunReport: () => buildRunReport,
|
|
612
|
+
captureRun: () => captureRun,
|
|
613
|
+
mountPipelineDebugger: () => mountPipelineDebugger,
|
|
614
|
+
pipelineDebugAPI: () => pipelineDebugAPI
|
|
615
|
+
});
|
|
616
|
+
function getOrigin(card) {
|
|
617
|
+
const firstEntry = card.provenance[0];
|
|
618
|
+
if (!firstEntry) return "unknown";
|
|
619
|
+
const reason = firstEntry.reason?.toLowerCase() || "";
|
|
620
|
+
if (reason.includes("new card")) return "new";
|
|
621
|
+
if (reason.includes("review")) return "review";
|
|
622
|
+
return "unknown";
|
|
623
|
+
}
|
|
624
|
+
function captureRun(report) {
|
|
625
|
+
const fullReport = {
|
|
626
|
+
...report,
|
|
627
|
+
runId: `run-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
|
628
|
+
timestamp: /* @__PURE__ */ new Date()
|
|
629
|
+
};
|
|
630
|
+
runHistory.unshift(fullReport);
|
|
631
|
+
if (runHistory.length > MAX_RUNS) {
|
|
632
|
+
runHistory.pop();
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
function buildRunReport(courseId, courseName, generatorName, generators, generatedCount, filters, allCards, selectedCards) {
|
|
636
|
+
const selectedIds = new Set(selectedCards.map((c) => c.cardId));
|
|
637
|
+
const cards = allCards.map((card) => ({
|
|
638
|
+
cardId: card.cardId,
|
|
639
|
+
courseId: card.courseId,
|
|
640
|
+
origin: getOrigin(card),
|
|
641
|
+
finalScore: card.score,
|
|
642
|
+
provenance: card.provenance,
|
|
643
|
+
selected: selectedIds.has(card.cardId)
|
|
644
|
+
}));
|
|
645
|
+
const reviewsSelected = selectedCards.filter((c) => getOrigin(c) === "review").length;
|
|
646
|
+
const newSelected = selectedCards.filter((c) => getOrigin(c) === "new").length;
|
|
647
|
+
return {
|
|
648
|
+
courseId,
|
|
649
|
+
courseName,
|
|
650
|
+
generatorName,
|
|
651
|
+
generators,
|
|
652
|
+
generatedCount,
|
|
653
|
+
filters,
|
|
654
|
+
finalCount: selectedCards.length,
|
|
655
|
+
reviewsSelected,
|
|
656
|
+
newSelected,
|
|
657
|
+
cards
|
|
658
|
+
};
|
|
659
|
+
}
|
|
660
|
+
function formatProvenance(provenance) {
|
|
661
|
+
return provenance.map((p) => {
|
|
662
|
+
const actionSymbol = p.action === "generated" ? "\u{1F3B2}" : p.action === "boosted" ? "\u2B06\uFE0F" : p.action === "penalized" ? "\u2B07\uFE0F" : "\u27A1\uFE0F";
|
|
663
|
+
return ` ${actionSymbol} ${p.strategyName}: ${p.score.toFixed(3)} - ${p.reason}`;
|
|
664
|
+
}).join("\n");
|
|
665
|
+
}
|
|
666
|
+
function printRunSummary(run) {
|
|
667
|
+
console.group(`\u{1F50D} Pipeline Run: ${run.courseId} (${run.courseName || "unnamed"})`);
|
|
668
|
+
logger.info(`Run ID: ${run.runId}`);
|
|
669
|
+
logger.info(`Time: ${run.timestamp.toISOString()}`);
|
|
670
|
+
logger.info(`Generator: ${run.generatorName} \u2192 ${run.generatedCount} candidates`);
|
|
671
|
+
if (run.generators && run.generators.length > 0) {
|
|
672
|
+
console.group("Generator breakdown:");
|
|
673
|
+
for (const g of run.generators) {
|
|
674
|
+
logger.info(
|
|
675
|
+
` ${g.name}: ${g.cardCount} cards (${g.newCount} new, ${g.reviewCount} reviews, top: ${g.topScore.toFixed(2)})`
|
|
676
|
+
);
|
|
677
|
+
}
|
|
678
|
+
console.groupEnd();
|
|
679
|
+
}
|
|
680
|
+
if (run.filters.length > 0) {
|
|
681
|
+
console.group("Filter impact:");
|
|
682
|
+
for (const f of run.filters) {
|
|
683
|
+
logger.info(` ${f.name}: \u2191${f.boosted} \u2193${f.penalized} =${f.passed} \u2715${f.removed}`);
|
|
684
|
+
}
|
|
685
|
+
console.groupEnd();
|
|
686
|
+
}
|
|
687
|
+
logger.info(
|
|
688
|
+
`Result: ${run.finalCount} cards selected (${run.newSelected} new, ${run.reviewsSelected} reviews)`
|
|
689
|
+
);
|
|
690
|
+
console.groupEnd();
|
|
691
|
+
}
|
|
692
|
+
function mountPipelineDebugger() {
|
|
693
|
+
if (typeof window === "undefined") return;
|
|
694
|
+
const win = window;
|
|
695
|
+
win.skuilder = win.skuilder || {};
|
|
696
|
+
win.skuilder.pipeline = pipelineDebugAPI;
|
|
697
|
+
}
|
|
698
|
+
var MAX_RUNS, runHistory, pipelineDebugAPI;
|
|
699
|
+
var init_PipelineDebugger = __esm({
|
|
700
|
+
"src/core/navigators/PipelineDebugger.ts"() {
|
|
701
|
+
"use strict";
|
|
702
|
+
init_logger();
|
|
703
|
+
MAX_RUNS = 10;
|
|
704
|
+
runHistory = [];
|
|
705
|
+
pipelineDebugAPI = {
|
|
706
|
+
/**
|
|
707
|
+
* Get raw run history for programmatic access.
|
|
708
|
+
*/
|
|
709
|
+
get runs() {
|
|
710
|
+
return [...runHistory];
|
|
711
|
+
},
|
|
712
|
+
/**
|
|
713
|
+
* Show summary of a specific pipeline run.
|
|
714
|
+
*/
|
|
715
|
+
showRun(idOrIndex = 0) {
|
|
716
|
+
if (runHistory.length === 0) {
|
|
717
|
+
logger.info("[Pipeline Debug] No runs captured yet.");
|
|
718
|
+
return;
|
|
719
|
+
}
|
|
720
|
+
let run;
|
|
721
|
+
if (typeof idOrIndex === "number") {
|
|
722
|
+
run = runHistory[idOrIndex];
|
|
723
|
+
if (!run) {
|
|
724
|
+
logger.info(
|
|
725
|
+
`[Pipeline Debug] No run found at index ${idOrIndex}. History length: ${runHistory.length}`
|
|
726
|
+
);
|
|
727
|
+
return;
|
|
728
|
+
}
|
|
729
|
+
} else {
|
|
730
|
+
run = runHistory.find((r) => r.runId.endsWith(idOrIndex));
|
|
731
|
+
if (!run) {
|
|
732
|
+
logger.info(`[Pipeline Debug] No run found matching ID '${idOrIndex}'.`);
|
|
733
|
+
return;
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
printRunSummary(run);
|
|
737
|
+
},
|
|
738
|
+
/**
|
|
739
|
+
* Show summary of the last pipeline run.
|
|
740
|
+
*/
|
|
741
|
+
showLastRun() {
|
|
742
|
+
this.showRun(0);
|
|
743
|
+
},
|
|
744
|
+
/**
|
|
745
|
+
* Show detailed provenance for a specific card.
|
|
746
|
+
*/
|
|
747
|
+
showCard(cardId) {
|
|
748
|
+
for (const run of runHistory) {
|
|
749
|
+
const card = run.cards.find((c) => c.cardId === cardId);
|
|
750
|
+
if (card) {
|
|
751
|
+
console.group(`\u{1F3B4} Card: ${cardId}`);
|
|
752
|
+
logger.info(`Course: ${card.courseId}`);
|
|
753
|
+
logger.info(`Origin: ${card.origin}`);
|
|
754
|
+
logger.info(`Final score: ${card.finalScore.toFixed(3)}`);
|
|
755
|
+
logger.info(`Selected: ${card.selected ? "Yes \u2705" : "No \u274C"}`);
|
|
756
|
+
logger.info("Provenance:");
|
|
757
|
+
logger.info(formatProvenance(card.provenance));
|
|
758
|
+
console.groupEnd();
|
|
759
|
+
return;
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
logger.info(`[Pipeline Debug] Card '${cardId}' not found in recent runs.`);
|
|
763
|
+
},
|
|
764
|
+
/**
|
|
765
|
+
* Explain why reviews may or may not have been selected.
|
|
766
|
+
*/
|
|
767
|
+
explainReviews() {
|
|
768
|
+
if (runHistory.length === 0) {
|
|
769
|
+
logger.info("[Pipeline Debug] No runs captured yet.");
|
|
770
|
+
return;
|
|
771
|
+
}
|
|
772
|
+
console.group("\u{1F4CB} Review Selection Analysis");
|
|
773
|
+
for (const run of runHistory) {
|
|
774
|
+
console.group(`Run: ${run.courseId} @ ${run.timestamp.toLocaleTimeString()}`);
|
|
775
|
+
const allReviews = run.cards.filter((c) => c.origin === "review");
|
|
776
|
+
const selectedReviews = allReviews.filter((c) => c.selected);
|
|
777
|
+
if (allReviews.length === 0) {
|
|
778
|
+
logger.info("\u274C No reviews were generated. Check SRS logs for why.");
|
|
779
|
+
} else if (selectedReviews.length === 0) {
|
|
780
|
+
logger.info(`\u26A0\uFE0F ${allReviews.length} reviews generated but none selected.`);
|
|
781
|
+
logger.info("Possible reasons:");
|
|
782
|
+
const topNewScore = Math.max(
|
|
783
|
+
...run.cards.filter((c) => c.origin === "new" && c.selected).map((c) => c.finalScore),
|
|
784
|
+
0
|
|
785
|
+
);
|
|
786
|
+
const topReviewScore = Math.max(...allReviews.map((c) => c.finalScore), 0);
|
|
787
|
+
if (topReviewScore < topNewScore) {
|
|
788
|
+
logger.info(
|
|
789
|
+
` - New cards scored higher (top new: ${topNewScore.toFixed(2)}, top review: ${topReviewScore.toFixed(2)})`
|
|
790
|
+
);
|
|
791
|
+
}
|
|
792
|
+
const topReview = allReviews.sort((a, b) => b.finalScore - a.finalScore)[0];
|
|
793
|
+
if (topReview) {
|
|
794
|
+
logger.info(` - Top review score: ${topReview.finalScore.toFixed(3)}`);
|
|
795
|
+
logger.info(" - Its provenance:");
|
|
796
|
+
logger.info(formatProvenance(topReview.provenance));
|
|
797
|
+
}
|
|
798
|
+
} else {
|
|
799
|
+
logger.info(`\u2705 ${selectedReviews.length}/${allReviews.length} reviews selected.`);
|
|
800
|
+
logger.info("Top selected review:");
|
|
801
|
+
const topSelected = selectedReviews.sort((a, b) => b.finalScore - a.finalScore)[0];
|
|
802
|
+
logger.info(formatProvenance(topSelected.provenance));
|
|
803
|
+
}
|
|
804
|
+
console.groupEnd();
|
|
805
|
+
}
|
|
806
|
+
console.groupEnd();
|
|
807
|
+
},
|
|
808
|
+
/**
|
|
809
|
+
* Show all runs in compact format.
|
|
810
|
+
*/
|
|
811
|
+
listRuns() {
|
|
812
|
+
if (runHistory.length === 0) {
|
|
813
|
+
logger.info("[Pipeline Debug] No runs captured yet.");
|
|
814
|
+
return;
|
|
815
|
+
}
|
|
816
|
+
console.table(
|
|
817
|
+
runHistory.map((r) => ({
|
|
818
|
+
id: r.runId.slice(-8),
|
|
819
|
+
time: r.timestamp.toLocaleTimeString(),
|
|
820
|
+
course: r.courseName || r.courseId.slice(0, 8),
|
|
821
|
+
generated: r.generatedCount,
|
|
822
|
+
selected: r.finalCount,
|
|
823
|
+
new: r.newSelected,
|
|
824
|
+
reviews: r.reviewsSelected
|
|
825
|
+
}))
|
|
826
|
+
);
|
|
827
|
+
},
|
|
828
|
+
/**
|
|
829
|
+
* Export run history as JSON for bug reports.
|
|
830
|
+
*/
|
|
831
|
+
export() {
|
|
832
|
+
const json = JSON.stringify(runHistory, null, 2);
|
|
833
|
+
logger.info("[Pipeline Debug] Run history exported. Copy the returned string or use:");
|
|
834
|
+
logger.info(" copy(window.skuilder.pipeline.export())");
|
|
835
|
+
return json;
|
|
836
|
+
},
|
|
837
|
+
/**
|
|
838
|
+
* Clear run history.
|
|
839
|
+
*/
|
|
840
|
+
clear() {
|
|
841
|
+
runHistory.length = 0;
|
|
842
|
+
logger.info("[Pipeline Debug] Run history cleared.");
|
|
843
|
+
},
|
|
844
|
+
/**
|
|
845
|
+
* Show help.
|
|
846
|
+
*/
|
|
847
|
+
help() {
|
|
848
|
+
logger.info(`
|
|
849
|
+
\u{1F527} Pipeline Debug API
|
|
850
|
+
|
|
851
|
+
Commands:
|
|
852
|
+
.showLastRun() Show summary of most recent pipeline run
|
|
853
|
+
.showRun(id|index) Show summary of a specific run (by index or ID suffix)
|
|
854
|
+
.showCard(cardId) Show provenance trail for a specific card
|
|
855
|
+
.explainReviews() Analyze why reviews were/weren't selected
|
|
856
|
+
.listRuns() List all captured runs in table format
|
|
857
|
+
.export() Export run history as JSON for bug reports
|
|
858
|
+
.clear() Clear run history
|
|
859
|
+
.runs Access raw run history array
|
|
860
|
+
.help() Show this help message
|
|
861
|
+
|
|
862
|
+
Example:
|
|
863
|
+
window.skuilder.pipeline.showLastRun()
|
|
864
|
+
window.skuilder.pipeline.showRun(1)
|
|
865
|
+
window.skuilder.pipeline.showCard('abc123')
|
|
866
|
+
`);
|
|
867
|
+
}
|
|
868
|
+
};
|
|
869
|
+
mountPipelineDebugger();
|
|
870
|
+
}
|
|
871
|
+
});
|
|
872
|
+
|
|
608
873
|
// src/core/navigators/generators/CompositeGenerator.ts
|
|
609
874
|
var CompositeGenerator_exports = {};
|
|
610
875
|
__export(CompositeGenerator_exports, {
|
|
@@ -673,6 +938,24 @@ var init_CompositeGenerator = __esm({
|
|
|
673
938
|
const results = await Promise.all(
|
|
674
939
|
this.generators.map((g) => g.getWeightedCards(limit, context))
|
|
675
940
|
);
|
|
941
|
+
const generatorSummaries = [];
|
|
942
|
+
results.forEach((cards, index) => {
|
|
943
|
+
const gen = this.generators[index];
|
|
944
|
+
const genName = gen.name || `Generator ${index}`;
|
|
945
|
+
const newCards = cards.filter((c) => c.provenance[0]?.reason?.includes("new card"));
|
|
946
|
+
const reviewCards = cards.filter((c) => c.provenance[0]?.reason?.includes("review"));
|
|
947
|
+
if (cards.length > 0) {
|
|
948
|
+
const topScore = Math.max(...cards.map((c) => c.score)).toFixed(2);
|
|
949
|
+
const parts = [];
|
|
950
|
+
if (newCards.length > 0) parts.push(`${newCards.length} new`);
|
|
951
|
+
if (reviewCards.length > 0) parts.push(`${reviewCards.length} reviews`);
|
|
952
|
+
const breakdown = parts.length > 0 ? parts.join(", ") : `${cards.length} cards`;
|
|
953
|
+
generatorSummaries.push(`${genName}: ${breakdown} (top: ${topScore})`);
|
|
954
|
+
} else {
|
|
955
|
+
generatorSummaries.push(`${genName}: 0 cards`);
|
|
956
|
+
}
|
|
957
|
+
});
|
|
958
|
+
logger.info(`[Composite] Generator breakdown: ${generatorSummaries.join(" | ")}`);
|
|
676
959
|
const byCardId = /* @__PURE__ */ new Map();
|
|
677
960
|
results.forEach((cards, index) => {
|
|
678
961
|
const gen = this.generators[index];
|
|
@@ -790,6 +1073,7 @@ var init_elo = __esm({
|
|
|
790
1073
|
"src/core/navigators/generators/elo.ts"() {
|
|
791
1074
|
"use strict";
|
|
792
1075
|
init_navigators();
|
|
1076
|
+
init_logger();
|
|
793
1077
|
ELONavigator = class extends ContentNavigator {
|
|
794
1078
|
/** Human-readable name for CardGenerator interface */
|
|
795
1079
|
name;
|
|
@@ -849,7 +1133,16 @@ var init_elo = __esm({
|
|
|
849
1133
|
};
|
|
850
1134
|
});
|
|
851
1135
|
scored.sort((a, b) => b.score - a.score);
|
|
852
|
-
|
|
1136
|
+
const result = scored.slice(0, limit);
|
|
1137
|
+
if (result.length > 0) {
|
|
1138
|
+
const topScores = result.slice(0, 3).map((c) => c.score.toFixed(2)).join(", ");
|
|
1139
|
+
logger.info(
|
|
1140
|
+
`[ELO] Course ${this.course.getCourseID()}: ${result.length} new cards (top scores: ${topScores})`
|
|
1141
|
+
);
|
|
1142
|
+
} else {
|
|
1143
|
+
logger.info(`[ELO] Course ${this.course.getCourseID()}: No new cards available`);
|
|
1144
|
+
}
|
|
1145
|
+
return result;
|
|
853
1146
|
}
|
|
854
1147
|
};
|
|
855
1148
|
}
|
|
@@ -869,18 +1162,36 @@ __export(srs_exports, {
|
|
|
869
1162
|
default: () => SRSNavigator
|
|
870
1163
|
});
|
|
871
1164
|
import moment from "moment";
|
|
872
|
-
var SRSNavigator;
|
|
1165
|
+
var DEFAULT_HEALTHY_BACKLOG, MAX_BACKLOG_PRESSURE, SRSNavigator;
|
|
873
1166
|
var init_srs = __esm({
|
|
874
1167
|
"src/core/navigators/generators/srs.ts"() {
|
|
875
1168
|
"use strict";
|
|
876
1169
|
init_navigators();
|
|
877
1170
|
init_logger();
|
|
1171
|
+
DEFAULT_HEALTHY_BACKLOG = 20;
|
|
1172
|
+
MAX_BACKLOG_PRESSURE = 0.5;
|
|
878
1173
|
SRSNavigator = class extends ContentNavigator {
|
|
879
1174
|
/** Human-readable name for CardGenerator interface */
|
|
880
1175
|
name;
|
|
1176
|
+
/** Healthy backlog threshold - when exceeded, backlog pressure kicks in */
|
|
1177
|
+
healthyBacklog;
|
|
881
1178
|
constructor(user, course, strategyData) {
|
|
882
1179
|
super(user, course, strategyData);
|
|
883
1180
|
this.name = strategyData?.name || "SRS";
|
|
1181
|
+
const config = this.parseConfig(strategyData?.serializedData);
|
|
1182
|
+
this.healthyBacklog = config.healthyBacklog ?? DEFAULT_HEALTHY_BACKLOG;
|
|
1183
|
+
}
|
|
1184
|
+
/**
|
|
1185
|
+
* Parse configuration from serialized JSON.
|
|
1186
|
+
*/
|
|
1187
|
+
parseConfig(serializedData) {
|
|
1188
|
+
if (!serializedData) return {};
|
|
1189
|
+
try {
|
|
1190
|
+
return JSON.parse(serializedData);
|
|
1191
|
+
} catch {
|
|
1192
|
+
logger.warn("[SRS] Failed to parse strategy config, using defaults");
|
|
1193
|
+
return {};
|
|
1194
|
+
}
|
|
884
1195
|
}
|
|
885
1196
|
/**
|
|
886
1197
|
* Get review cards scored by urgency.
|
|
@@ -888,6 +1199,7 @@ var init_srs = __esm({
|
|
|
888
1199
|
* Score formula combines:
|
|
889
1200
|
* - Relative overdueness: hoursOverdue / intervalHours
|
|
890
1201
|
* - Interval recency: exponential decay favoring shorter intervals
|
|
1202
|
+
* - Backlog pressure: boost when due reviews exceed healthy threshold
|
|
891
1203
|
*
|
|
892
1204
|
* Cards not yet due are excluded (not scored as 0).
|
|
893
1205
|
*
|
|
@@ -901,11 +1213,32 @@ var init_srs = __esm({
|
|
|
901
1213
|
if (!this.user || !this.course) {
|
|
902
1214
|
throw new Error("SRSNavigator requires user and course to be set");
|
|
903
1215
|
}
|
|
904
|
-
const
|
|
1216
|
+
const courseId = this.course.getCourseID();
|
|
1217
|
+
const reviews = await this.user.getPendingReviews(courseId);
|
|
905
1218
|
const now = moment.utc();
|
|
906
1219
|
const dueReviews = reviews.filter((r) => now.isAfter(moment.utc(r.reviewTime)));
|
|
1220
|
+
const backlogPressure = this.computeBacklogPressure(dueReviews.length);
|
|
1221
|
+
if (dueReviews.length > 0) {
|
|
1222
|
+
const pressureNote = backlogPressure > 0 ? ` [backlog pressure: +${backlogPressure.toFixed(2)}]` : ` [healthy backlog]`;
|
|
1223
|
+
logger.info(
|
|
1224
|
+
`[SRS] Course ${courseId}: ${dueReviews.length} reviews due now (of ${reviews.length} scheduled)${pressureNote}`
|
|
1225
|
+
);
|
|
1226
|
+
} else if (reviews.length > 0) {
|
|
1227
|
+
const sortedByDue = [...reviews].sort(
|
|
1228
|
+
(a, b) => moment.utc(a.reviewTime).diff(moment.utc(b.reviewTime))
|
|
1229
|
+
);
|
|
1230
|
+
const nextDue = sortedByDue[0];
|
|
1231
|
+
const nextDueTime = moment.utc(nextDue.reviewTime);
|
|
1232
|
+
const untilDue = moment.duration(nextDueTime.diff(now));
|
|
1233
|
+
const untilDueStr = untilDue.asHours() < 1 ? `${Math.round(untilDue.asMinutes())}m` : untilDue.asHours() < 24 ? `${Math.round(untilDue.asHours())}h` : `${Math.round(untilDue.asDays())}d`;
|
|
1234
|
+
logger.info(
|
|
1235
|
+
`[SRS] Course ${courseId}: 0 reviews due now (${reviews.length} scheduled, next in ${untilDueStr})`
|
|
1236
|
+
);
|
|
1237
|
+
} else {
|
|
1238
|
+
logger.info(`[SRS] Course ${courseId}: No reviews scheduled`);
|
|
1239
|
+
}
|
|
907
1240
|
const scored = dueReviews.map((review) => {
|
|
908
|
-
const { score, reason } = this.computeUrgencyScore(review, now);
|
|
1241
|
+
const { score, reason } = this.computeUrgencyScore(review, now, backlogPressure);
|
|
909
1242
|
return {
|
|
910
1243
|
cardId: review.cardId,
|
|
911
1244
|
courseId: review.courseId,
|
|
@@ -923,13 +1256,35 @@ var init_srs = __esm({
|
|
|
923
1256
|
]
|
|
924
1257
|
};
|
|
925
1258
|
});
|
|
926
|
-
logger.debug(`[srsNav] got ${scored.length} weighted cards`);
|
|
927
1259
|
return scored.sort((a, b) => b.score - a.score).slice(0, limit);
|
|
928
1260
|
}
|
|
1261
|
+
/**
|
|
1262
|
+
* Compute backlog pressure based on number of due reviews.
|
|
1263
|
+
*
|
|
1264
|
+
* Backlog pressure is 0 when at or below healthy threshold,
|
|
1265
|
+
* and increases linearly above it, maxing out at MAX_BACKLOG_PRESSURE.
|
|
1266
|
+
*
|
|
1267
|
+
* Examples (with default healthyBacklog=20):
|
|
1268
|
+
* - 10 due reviews → 0.00 (healthy)
|
|
1269
|
+
* - 20 due reviews → 0.00 (at threshold)
|
|
1270
|
+
* - 40 due reviews → 0.25 (2x threshold)
|
|
1271
|
+
* - 60 due reviews → 0.50 (3x threshold, maxed)
|
|
1272
|
+
*
|
|
1273
|
+
* @param dueCount - Number of reviews currently due
|
|
1274
|
+
* @returns Backlog pressure score to add to urgency (0 to MAX_BACKLOG_PRESSURE)
|
|
1275
|
+
*/
|
|
1276
|
+
computeBacklogPressure(dueCount) {
|
|
1277
|
+
if (dueCount <= this.healthyBacklog) {
|
|
1278
|
+
return 0;
|
|
1279
|
+
}
|
|
1280
|
+
const excess = dueCount - this.healthyBacklog;
|
|
1281
|
+
const pressure = excess / this.healthyBacklog * (MAX_BACKLOG_PRESSURE / 2);
|
|
1282
|
+
return Math.min(MAX_BACKLOG_PRESSURE, pressure);
|
|
1283
|
+
}
|
|
929
1284
|
/**
|
|
930
1285
|
* Compute urgency score for a review card.
|
|
931
1286
|
*
|
|
932
|
-
*
|
|
1287
|
+
* Three factors:
|
|
933
1288
|
* 1. Relative overdueness = hoursOverdue / intervalHours
|
|
934
1289
|
* - 2 days overdue on 3-day interval = 0.67 (urgent)
|
|
935
1290
|
* - 2 days overdue on 180-day interval = 0.01 (not urgent)
|
|
@@ -939,10 +1294,19 @@ var init_srs = __esm({
|
|
|
939
1294
|
* - 30 days (720h) → ~0.56
|
|
940
1295
|
* - 180 days → ~0.30
|
|
941
1296
|
*
|
|
942
|
-
*
|
|
943
|
-
*
|
|
1297
|
+
* 3. Backlog pressure = global boost when review backlog exceeds healthy threshold
|
|
1298
|
+
* - At healthy backlog: 0
|
|
1299
|
+
* - At 2x healthy: +0.25
|
|
1300
|
+
* - At 3x+ healthy: +0.50 (max)
|
|
1301
|
+
*
|
|
1302
|
+
* Combined: base 0.5 + (urgency factors * 0.45) + backlog pressure
|
|
1303
|
+
* Result range: 0.5 to 1.0 (uncapped to allow high-urgency reviews to compete with new cards)
|
|
1304
|
+
*
|
|
1305
|
+
* @param review - The scheduled card to score
|
|
1306
|
+
* @param now - Current time
|
|
1307
|
+
* @param backlogPressure - Pre-computed backlog pressure (0 to 0.5)
|
|
944
1308
|
*/
|
|
945
|
-
computeUrgencyScore(review, now) {
|
|
1309
|
+
computeUrgencyScore(review, now, backlogPressure) {
|
|
946
1310
|
const scheduledAt = moment.utc(review.scheduledAt);
|
|
947
1311
|
const due = moment.utc(review.reviewTime);
|
|
948
1312
|
const intervalHours = Math.max(1, due.diff(scheduledAt, "hours"));
|
|
@@ -951,8 +1315,19 @@ var init_srs = __esm({
|
|
|
951
1315
|
const recencyFactor = 0.3 + 0.7 * Math.exp(-intervalHours / 720);
|
|
952
1316
|
const overdueContribution = Math.min(1, Math.max(0, relativeOverdue));
|
|
953
1317
|
const urgency = overdueContribution * 0.5 + recencyFactor * 0.5;
|
|
954
|
-
const
|
|
955
|
-
const
|
|
1318
|
+
const baseScore = 0.5 + urgency * 0.45;
|
|
1319
|
+
const score = Math.min(1, baseScore + backlogPressure);
|
|
1320
|
+
const reasonParts = [
|
|
1321
|
+
`${Math.round(hoursOverdue)}h overdue`,
|
|
1322
|
+
`interval: ${Math.round(intervalHours)}h`,
|
|
1323
|
+
`relative: ${relativeOverdue.toFixed(2)}`,
|
|
1324
|
+
`recency: ${recencyFactor.toFixed(2)}`
|
|
1325
|
+
];
|
|
1326
|
+
if (backlogPressure > 0) {
|
|
1327
|
+
reasonParts.push(`backlog: +${backlogPressure.toFixed(2)}`);
|
|
1328
|
+
}
|
|
1329
|
+
reasonParts.push("review");
|
|
1330
|
+
const reason = reasonParts.join(", ");
|
|
956
1331
|
return { score, reason };
|
|
957
1332
|
}
|
|
958
1333
|
};
|
|
@@ -1983,10 +2358,23 @@ function logTagHydration(cards, tagsByCard) {
|
|
|
1983
2358
|
`[Pipeline] Tag hydration: ${cards.length} cards, ${cardsWithTags} have tags (${totalTags} total tags) - single batch query`
|
|
1984
2359
|
);
|
|
1985
2360
|
}
|
|
1986
|
-
function logExecutionSummary(generatorName, generatedCount, filterCount, finalCount, topScores) {
|
|
2361
|
+
function logExecutionSummary(generatorName, generatedCount, filterCount, finalCount, topScores, filterImpacts) {
|
|
1987
2362
|
const scoreDisplay = topScores.length > 0 ? topScores.map((s) => s.toFixed(2)).join(", ") : "none";
|
|
2363
|
+
let filterSummary = "";
|
|
2364
|
+
if (filterImpacts.length > 0) {
|
|
2365
|
+
const impacts = filterImpacts.map((f) => {
|
|
2366
|
+
const parts = [];
|
|
2367
|
+
if (f.boosted > 0) parts.push(`+${f.boosted}`);
|
|
2368
|
+
if (f.penalized > 0) parts.push(`-${f.penalized}`);
|
|
2369
|
+
if (f.passed > 0) parts.push(`=${f.passed}`);
|
|
2370
|
+
return `${f.name}: ${parts.join("/")}`;
|
|
2371
|
+
});
|
|
2372
|
+
filterSummary = `
|
|
2373
|
+
Filter impact: ${impacts.join(", ")}`;
|
|
2374
|
+
}
|
|
1988
2375
|
logger.info(
|
|
1989
|
-
`[Pipeline] Execution: ${generatorName} produced ${generatedCount} \u2192 ${filterCount} filters \u2192 ${finalCount} results (top scores: ${scoreDisplay})`
|
|
2376
|
+
`[Pipeline] Execution: ${generatorName} produced ${generatedCount} \u2192 ${filterCount} filters \u2192 ${finalCount} results (top scores: ${scoreDisplay})` + filterSummary + `
|
|
2377
|
+
\u{1F4A1} Inspect: window.skuilder.pipeline`
|
|
1990
2378
|
);
|
|
1991
2379
|
}
|
|
1992
2380
|
function logCardProvenance(cards, maxCards = 3) {
|
|
@@ -2010,6 +2398,7 @@ var init_Pipeline = __esm({
|
|
|
2010
2398
|
init_navigators();
|
|
2011
2399
|
init_logger();
|
|
2012
2400
|
init_orchestration();
|
|
2401
|
+
init_PipelineDebugger();
|
|
2013
2402
|
Pipeline = class extends ContentNavigator {
|
|
2014
2403
|
generator;
|
|
2015
2404
|
filters;
|
|
@@ -2057,12 +2446,49 @@ var init_Pipeline = __esm({
|
|
|
2057
2446
|
);
|
|
2058
2447
|
let cards = await this.generator.getWeightedCards(fetchLimit, context);
|
|
2059
2448
|
const generatedCount = cards.length;
|
|
2449
|
+
let generatorSummaries;
|
|
2450
|
+
if (this.generator.generators) {
|
|
2451
|
+
const genMap = /* @__PURE__ */ new Map();
|
|
2452
|
+
for (const card of cards) {
|
|
2453
|
+
const firstProv = card.provenance[0];
|
|
2454
|
+
if (firstProv) {
|
|
2455
|
+
const genName = firstProv.strategyName;
|
|
2456
|
+
if (!genMap.has(genName)) {
|
|
2457
|
+
genMap.set(genName, { cards: [] });
|
|
2458
|
+
}
|
|
2459
|
+
genMap.get(genName).cards.push(card);
|
|
2460
|
+
}
|
|
2461
|
+
}
|
|
2462
|
+
generatorSummaries = Array.from(genMap.entries()).map(([name, data]) => {
|
|
2463
|
+
const newCards = data.cards.filter((c) => c.provenance[0]?.reason?.includes("new card"));
|
|
2464
|
+
const reviewCards = data.cards.filter((c) => c.provenance[0]?.reason?.includes("review"));
|
|
2465
|
+
return {
|
|
2466
|
+
name,
|
|
2467
|
+
cardCount: data.cards.length,
|
|
2468
|
+
newCount: newCards.length,
|
|
2469
|
+
reviewCount: reviewCards.length,
|
|
2470
|
+
topScore: Math.max(...data.cards.map((c) => c.score), 0)
|
|
2471
|
+
};
|
|
2472
|
+
});
|
|
2473
|
+
}
|
|
2060
2474
|
logger.debug(`[Pipeline] Generator returned ${generatedCount} candidates`);
|
|
2061
2475
|
cards = await this.hydrateTags(cards);
|
|
2476
|
+
const allCardsBeforeFiltering = [...cards];
|
|
2477
|
+
const filterImpacts = [];
|
|
2062
2478
|
for (const filter of this.filters) {
|
|
2063
2479
|
const beforeCount = cards.length;
|
|
2480
|
+
const beforeScores = new Map(cards.map((c) => [c.cardId, c.score]));
|
|
2064
2481
|
cards = await filter.transform(cards, context);
|
|
2065
|
-
|
|
2482
|
+
let boosted = 0, penalized = 0, passed = 0;
|
|
2483
|
+
const removed = beforeCount - cards.length;
|
|
2484
|
+
for (const card of cards) {
|
|
2485
|
+
const before = beforeScores.get(card.cardId) ?? 0;
|
|
2486
|
+
if (card.score > before) boosted++;
|
|
2487
|
+
else if (card.score < before) penalized++;
|
|
2488
|
+
else passed++;
|
|
2489
|
+
}
|
|
2490
|
+
filterImpacts.push({ name: filter.name, boosted, penalized, passed, removed });
|
|
2491
|
+
logger.debug(`[Pipeline] Filter '${filter.name}': ${beforeScores.size} \u2192 ${cards.length} cards (\u2191${boosted} \u2193${penalized} =${passed})`);
|
|
2066
2492
|
}
|
|
2067
2493
|
cards = cards.filter((c) => c.score > 0);
|
|
2068
2494
|
cards.sort((a, b) => b.score - a.score);
|
|
@@ -2073,9 +2499,26 @@ var init_Pipeline = __esm({
|
|
|
2073
2499
|
generatedCount,
|
|
2074
2500
|
this.filters.length,
|
|
2075
2501
|
result.length,
|
|
2076
|
-
topScores
|
|
2502
|
+
topScores,
|
|
2503
|
+
filterImpacts
|
|
2077
2504
|
);
|
|
2078
2505
|
logCardProvenance(result, 3);
|
|
2506
|
+
try {
|
|
2507
|
+
const courseName = await this.course?.getCourseConfig().then((c) => c.name).catch(() => void 0);
|
|
2508
|
+
const report = buildRunReport(
|
|
2509
|
+
this.course?.getCourseID() || "unknown",
|
|
2510
|
+
courseName,
|
|
2511
|
+
this.generator.name,
|
|
2512
|
+
generatorSummaries,
|
|
2513
|
+
generatedCount,
|
|
2514
|
+
filterImpacts,
|
|
2515
|
+
allCardsBeforeFiltering,
|
|
2516
|
+
result
|
|
2517
|
+
);
|
|
2518
|
+
captureRun(report);
|
|
2519
|
+
} catch (e) {
|
|
2520
|
+
logger.debug(`[Pipeline] Failed to capture debug run: ${e}`);
|
|
2521
|
+
}
|
|
2079
2522
|
return result;
|
|
2080
2523
|
}
|
|
2081
2524
|
/**
|
|
@@ -2165,6 +2608,56 @@ var init_Pipeline = __esm({
|
|
|
2165
2608
|
}
|
|
2166
2609
|
});
|
|
2167
2610
|
|
|
2611
|
+
// src/core/navigators/defaults.ts
|
|
2612
|
+
var defaults_exports = {};
|
|
2613
|
+
__export(defaults_exports, {
|
|
2614
|
+
createDefaultEloStrategy: () => createDefaultEloStrategy,
|
|
2615
|
+
createDefaultPipeline: () => createDefaultPipeline,
|
|
2616
|
+
createDefaultSrsStrategy: () => createDefaultSrsStrategy
|
|
2617
|
+
});
|
|
2618
|
+
function createDefaultEloStrategy(courseId) {
|
|
2619
|
+
return {
|
|
2620
|
+
_id: "NAVIGATION_STRATEGY-ELO-default",
|
|
2621
|
+
docType: "NAVIGATION_STRATEGY" /* NAVIGATION_STRATEGY */,
|
|
2622
|
+
name: "ELO (default)",
|
|
2623
|
+
description: "Default ELO-based navigation strategy for new cards",
|
|
2624
|
+
implementingClass: "elo" /* ELO */,
|
|
2625
|
+
course: courseId,
|
|
2626
|
+
serializedData: ""
|
|
2627
|
+
};
|
|
2628
|
+
}
|
|
2629
|
+
function createDefaultSrsStrategy(courseId) {
|
|
2630
|
+
return {
|
|
2631
|
+
_id: "NAVIGATION_STRATEGY-SRS-default",
|
|
2632
|
+
docType: "NAVIGATION_STRATEGY" /* NAVIGATION_STRATEGY */,
|
|
2633
|
+
name: "SRS (default)",
|
|
2634
|
+
description: "Default SRS-based navigation strategy for reviews",
|
|
2635
|
+
implementingClass: "srs" /* SRS */,
|
|
2636
|
+
course: courseId,
|
|
2637
|
+
serializedData: ""
|
|
2638
|
+
};
|
|
2639
|
+
}
|
|
2640
|
+
function createDefaultPipeline(user, course) {
|
|
2641
|
+
const courseId = course.getCourseID();
|
|
2642
|
+
const eloNavigator = new ELONavigator(user, course, createDefaultEloStrategy(courseId));
|
|
2643
|
+
const srsNavigator = new SRSNavigator(user, course, createDefaultSrsStrategy(courseId));
|
|
2644
|
+
const compositeGenerator = new CompositeGenerator([eloNavigator, srsNavigator]);
|
|
2645
|
+
const eloDistanceFilter = createEloDistanceFilter();
|
|
2646
|
+
return new Pipeline(compositeGenerator, [eloDistanceFilter], user, course);
|
|
2647
|
+
}
|
|
2648
|
+
var init_defaults = __esm({
|
|
2649
|
+
"src/core/navigators/defaults.ts"() {
|
|
2650
|
+
"use strict";
|
|
2651
|
+
init_navigators();
|
|
2652
|
+
init_Pipeline();
|
|
2653
|
+
init_CompositeGenerator();
|
|
2654
|
+
init_elo();
|
|
2655
|
+
init_srs();
|
|
2656
|
+
init_eloDistance();
|
|
2657
|
+
init_types_legacy();
|
|
2658
|
+
}
|
|
2659
|
+
});
|
|
2660
|
+
|
|
2168
2661
|
// src/core/navigators/PipelineAssembler.ts
|
|
2169
2662
|
var PipelineAssembler_exports = {};
|
|
2170
2663
|
__export(PipelineAssembler_exports, {
|
|
@@ -2177,9 +2670,9 @@ var init_PipelineAssembler = __esm({
|
|
|
2177
2670
|
init_navigators();
|
|
2178
2671
|
init_WeightedFilter();
|
|
2179
2672
|
init_Pipeline();
|
|
2180
|
-
init_types_legacy();
|
|
2181
2673
|
init_logger();
|
|
2182
2674
|
init_CompositeGenerator();
|
|
2675
|
+
init_defaults();
|
|
2183
2676
|
PipelineAssembler = class {
|
|
2184
2677
|
/**
|
|
2185
2678
|
* Assembles a navigation pipeline from strategy documents.
|
|
@@ -2218,9 +2711,11 @@ var init_PipelineAssembler = __esm({
|
|
|
2218
2711
|
if (generatorStrategies.length === 0) {
|
|
2219
2712
|
if (filterStrategies.length > 0) {
|
|
2220
2713
|
logger.debug(
|
|
2221
|
-
"[PipelineAssembler] No generator found, using default ELO with configured filters"
|
|
2714
|
+
"[PipelineAssembler] No generator found, using default ELO and SRS with configured filters"
|
|
2222
2715
|
);
|
|
2223
|
-
|
|
2716
|
+
const courseId = course.getCourseID();
|
|
2717
|
+
generatorStrategies.push(createDefaultEloStrategy(courseId));
|
|
2718
|
+
generatorStrategies.push(createDefaultSrsStrategy(courseId));
|
|
2224
2719
|
} else {
|
|
2225
2720
|
warnings.push("No generator strategy found");
|
|
2226
2721
|
return {
|
|
@@ -2281,75 +2776,10 @@ var init_PipelineAssembler = __esm({
|
|
|
2281
2776
|
warnings
|
|
2282
2777
|
};
|
|
2283
2778
|
}
|
|
2284
|
-
/**
|
|
2285
|
-
* Creates a default ELO generator strategy.
|
|
2286
|
-
* Used when filters are configured but no generator is specified.
|
|
2287
|
-
*/
|
|
2288
|
-
makeDefaultEloStrategy(courseId) {
|
|
2289
|
-
return {
|
|
2290
|
-
_id: "NAVIGATION_STRATEGY-ELO-default",
|
|
2291
|
-
course: courseId,
|
|
2292
|
-
docType: "NAVIGATION_STRATEGY" /* NAVIGATION_STRATEGY */,
|
|
2293
|
-
name: "ELO (default)",
|
|
2294
|
-
description: "Default ELO-based generator",
|
|
2295
|
-
implementingClass: "elo" /* ELO */,
|
|
2296
|
-
serializedData: ""
|
|
2297
|
-
};
|
|
2298
|
-
}
|
|
2299
2779
|
};
|
|
2300
2780
|
}
|
|
2301
2781
|
});
|
|
2302
2782
|
|
|
2303
|
-
// src/core/navigators/defaults.ts
|
|
2304
|
-
var defaults_exports = {};
|
|
2305
|
-
__export(defaults_exports, {
|
|
2306
|
-
createDefaultEloStrategy: () => createDefaultEloStrategy,
|
|
2307
|
-
createDefaultPipeline: () => createDefaultPipeline,
|
|
2308
|
-
createDefaultSrsStrategy: () => createDefaultSrsStrategy
|
|
2309
|
-
});
|
|
2310
|
-
function createDefaultEloStrategy(courseId) {
|
|
2311
|
-
return {
|
|
2312
|
-
_id: "NAVIGATION_STRATEGY-ELO-default",
|
|
2313
|
-
docType: "NAVIGATION_STRATEGY" /* NAVIGATION_STRATEGY */,
|
|
2314
|
-
name: "ELO (default)",
|
|
2315
|
-
description: "Default ELO-based navigation strategy for new cards",
|
|
2316
|
-
implementingClass: "elo" /* ELO */,
|
|
2317
|
-
course: courseId,
|
|
2318
|
-
serializedData: ""
|
|
2319
|
-
};
|
|
2320
|
-
}
|
|
2321
|
-
function createDefaultSrsStrategy(courseId) {
|
|
2322
|
-
return {
|
|
2323
|
-
_id: "NAVIGATION_STRATEGY-SRS-default",
|
|
2324
|
-
docType: "NAVIGATION_STRATEGY" /* NAVIGATION_STRATEGY */,
|
|
2325
|
-
name: "SRS (default)",
|
|
2326
|
-
description: "Default SRS-based navigation strategy for reviews",
|
|
2327
|
-
implementingClass: "srs" /* SRS */,
|
|
2328
|
-
course: courseId,
|
|
2329
|
-
serializedData: ""
|
|
2330
|
-
};
|
|
2331
|
-
}
|
|
2332
|
-
function createDefaultPipeline(user, course) {
|
|
2333
|
-
const courseId = course.getCourseID();
|
|
2334
|
-
const eloNavigator = new ELONavigator(user, course, createDefaultEloStrategy(courseId));
|
|
2335
|
-
const srsNavigator = new SRSNavigator(user, course, createDefaultSrsStrategy(courseId));
|
|
2336
|
-
const compositeGenerator = new CompositeGenerator([eloNavigator, srsNavigator]);
|
|
2337
|
-
const eloDistanceFilter = createEloDistanceFilter();
|
|
2338
|
-
return new Pipeline(compositeGenerator, [eloDistanceFilter], user, course);
|
|
2339
|
-
}
|
|
2340
|
-
var init_defaults = __esm({
|
|
2341
|
-
"src/core/navigators/defaults.ts"() {
|
|
2342
|
-
"use strict";
|
|
2343
|
-
init_navigators();
|
|
2344
|
-
init_Pipeline();
|
|
2345
|
-
init_CompositeGenerator();
|
|
2346
|
-
init_elo();
|
|
2347
|
-
init_srs();
|
|
2348
|
-
init_eloDistance();
|
|
2349
|
-
init_types_legacy();
|
|
2350
|
-
}
|
|
2351
|
-
});
|
|
2352
|
-
|
|
2353
2783
|
// import("./**/*") in src/core/navigators/index.ts
|
|
2354
2784
|
var globImport;
|
|
2355
2785
|
var init_3 = __esm({
|
|
@@ -2357,6 +2787,7 @@ var init_3 = __esm({
|
|
|
2357
2787
|
globImport = __glob({
|
|
2358
2788
|
"./Pipeline.ts": () => Promise.resolve().then(() => (init_Pipeline(), Pipeline_exports)),
|
|
2359
2789
|
"./PipelineAssembler.ts": () => Promise.resolve().then(() => (init_PipelineAssembler(), PipelineAssembler_exports)),
|
|
2790
|
+
"./PipelineDebugger.ts": () => Promise.resolve().then(() => (init_PipelineDebugger(), PipelineDebugger_exports)),
|
|
2360
2791
|
"./defaults.ts": () => Promise.resolve().then(() => (init_defaults(), defaults_exports)),
|
|
2361
2792
|
"./filters/WeightedFilter.ts": () => Promise.resolve().then(() => (init_WeightedFilter(), WeightedFilter_exports)),
|
|
2362
2793
|
"./filters/eloDistance.ts": () => Promise.resolve().then(() => (init_eloDistance(), eloDistance_exports)),
|
|
@@ -2392,6 +2823,8 @@ __export(navigators_exports, {
|
|
|
2392
2823
|
initializeNavigatorRegistry: () => initializeNavigatorRegistry,
|
|
2393
2824
|
isFilter: () => isFilter,
|
|
2394
2825
|
isGenerator: () => isGenerator,
|
|
2826
|
+
mountPipelineDebugger: () => mountPipelineDebugger,
|
|
2827
|
+
pipelineDebugAPI: () => pipelineDebugAPI,
|
|
2395
2828
|
registerNavigator: () => registerNavigator
|
|
2396
2829
|
});
|
|
2397
2830
|
function registerNavigator(implementingClass, constructor) {
|
|
@@ -2458,6 +2891,7 @@ var navigatorRegistry, Navigators, NavigatorRole, NavigatorRoles, ContentNavigat
|
|
|
2458
2891
|
var init_navigators = __esm({
|
|
2459
2892
|
"src/core/navigators/index.ts"() {
|
|
2460
2893
|
"use strict";
|
|
2894
|
+
init_PipelineDebugger();
|
|
2461
2895
|
init_logger();
|
|
2462
2896
|
init_();
|
|
2463
2897
|
init_2();
|
|
@@ -4130,6 +4564,15 @@ var init_user_course_relDB = __esm({
|
|
|
4130
4564
|
void this.user.updateCourseSettings(this._courseId, updates);
|
|
4131
4565
|
}
|
|
4132
4566
|
}
|
|
4567
|
+
async getStrategyState(strategyKey) {
|
|
4568
|
+
return this.user.getStrategyState(this._courseId, strategyKey);
|
|
4569
|
+
}
|
|
4570
|
+
async putStrategyState(strategyKey, data) {
|
|
4571
|
+
return this.user.putStrategyState(this._courseId, strategyKey, data);
|
|
4572
|
+
}
|
|
4573
|
+
async deleteStrategyState(strategyKey) {
|
|
4574
|
+
return this.user.deleteStrategyState(this._courseId, strategyKey);
|
|
4575
|
+
}
|
|
4133
4576
|
async getReviewstoDate(targetDate) {
|
|
4134
4577
|
const allReviews = await this.user.getPendingReviews(this._courseId);
|
|
4135
4578
|
logger.debug(
|