@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
package/dist/impl/couch/index.js
CHANGED
|
@@ -627,6 +627,271 @@ var init_courseLookupDB = __esm({
|
|
|
627
627
|
}
|
|
628
628
|
});
|
|
629
629
|
|
|
630
|
+
// src/core/navigators/PipelineDebugger.ts
|
|
631
|
+
var PipelineDebugger_exports = {};
|
|
632
|
+
__export(PipelineDebugger_exports, {
|
|
633
|
+
buildRunReport: () => buildRunReport,
|
|
634
|
+
captureRun: () => captureRun,
|
|
635
|
+
mountPipelineDebugger: () => mountPipelineDebugger,
|
|
636
|
+
pipelineDebugAPI: () => pipelineDebugAPI
|
|
637
|
+
});
|
|
638
|
+
function getOrigin(card) {
|
|
639
|
+
const firstEntry = card.provenance[0];
|
|
640
|
+
if (!firstEntry) return "unknown";
|
|
641
|
+
const reason = firstEntry.reason?.toLowerCase() || "";
|
|
642
|
+
if (reason.includes("new card")) return "new";
|
|
643
|
+
if (reason.includes("review")) return "review";
|
|
644
|
+
return "unknown";
|
|
645
|
+
}
|
|
646
|
+
function captureRun(report) {
|
|
647
|
+
const fullReport = {
|
|
648
|
+
...report,
|
|
649
|
+
runId: `run-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
|
650
|
+
timestamp: /* @__PURE__ */ new Date()
|
|
651
|
+
};
|
|
652
|
+
runHistory.unshift(fullReport);
|
|
653
|
+
if (runHistory.length > MAX_RUNS) {
|
|
654
|
+
runHistory.pop();
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
function buildRunReport(courseId, courseName, generatorName, generators, generatedCount, filters, allCards, selectedCards) {
|
|
658
|
+
const selectedIds = new Set(selectedCards.map((c) => c.cardId));
|
|
659
|
+
const cards = allCards.map((card) => ({
|
|
660
|
+
cardId: card.cardId,
|
|
661
|
+
courseId: card.courseId,
|
|
662
|
+
origin: getOrigin(card),
|
|
663
|
+
finalScore: card.score,
|
|
664
|
+
provenance: card.provenance,
|
|
665
|
+
selected: selectedIds.has(card.cardId)
|
|
666
|
+
}));
|
|
667
|
+
const reviewsSelected = selectedCards.filter((c) => getOrigin(c) === "review").length;
|
|
668
|
+
const newSelected = selectedCards.filter((c) => getOrigin(c) === "new").length;
|
|
669
|
+
return {
|
|
670
|
+
courseId,
|
|
671
|
+
courseName,
|
|
672
|
+
generatorName,
|
|
673
|
+
generators,
|
|
674
|
+
generatedCount,
|
|
675
|
+
filters,
|
|
676
|
+
finalCount: selectedCards.length,
|
|
677
|
+
reviewsSelected,
|
|
678
|
+
newSelected,
|
|
679
|
+
cards
|
|
680
|
+
};
|
|
681
|
+
}
|
|
682
|
+
function formatProvenance(provenance) {
|
|
683
|
+
return provenance.map((p) => {
|
|
684
|
+
const actionSymbol = p.action === "generated" ? "\u{1F3B2}" : p.action === "boosted" ? "\u2B06\uFE0F" : p.action === "penalized" ? "\u2B07\uFE0F" : "\u27A1\uFE0F";
|
|
685
|
+
return ` ${actionSymbol} ${p.strategyName}: ${p.score.toFixed(3)} - ${p.reason}`;
|
|
686
|
+
}).join("\n");
|
|
687
|
+
}
|
|
688
|
+
function printRunSummary(run) {
|
|
689
|
+
console.group(`\u{1F50D} Pipeline Run: ${run.courseId} (${run.courseName || "unnamed"})`);
|
|
690
|
+
logger.info(`Run ID: ${run.runId}`);
|
|
691
|
+
logger.info(`Time: ${run.timestamp.toISOString()}`);
|
|
692
|
+
logger.info(`Generator: ${run.generatorName} \u2192 ${run.generatedCount} candidates`);
|
|
693
|
+
if (run.generators && run.generators.length > 0) {
|
|
694
|
+
console.group("Generator breakdown:");
|
|
695
|
+
for (const g of run.generators) {
|
|
696
|
+
logger.info(
|
|
697
|
+
` ${g.name}: ${g.cardCount} cards (${g.newCount} new, ${g.reviewCount} reviews, top: ${g.topScore.toFixed(2)})`
|
|
698
|
+
);
|
|
699
|
+
}
|
|
700
|
+
console.groupEnd();
|
|
701
|
+
}
|
|
702
|
+
if (run.filters.length > 0) {
|
|
703
|
+
console.group("Filter impact:");
|
|
704
|
+
for (const f of run.filters) {
|
|
705
|
+
logger.info(` ${f.name}: \u2191${f.boosted} \u2193${f.penalized} =${f.passed} \u2715${f.removed}`);
|
|
706
|
+
}
|
|
707
|
+
console.groupEnd();
|
|
708
|
+
}
|
|
709
|
+
logger.info(
|
|
710
|
+
`Result: ${run.finalCount} cards selected (${run.newSelected} new, ${run.reviewsSelected} reviews)`
|
|
711
|
+
);
|
|
712
|
+
console.groupEnd();
|
|
713
|
+
}
|
|
714
|
+
function mountPipelineDebugger() {
|
|
715
|
+
if (typeof window === "undefined") return;
|
|
716
|
+
const win = window;
|
|
717
|
+
win.skuilder = win.skuilder || {};
|
|
718
|
+
win.skuilder.pipeline = pipelineDebugAPI;
|
|
719
|
+
}
|
|
720
|
+
var MAX_RUNS, runHistory, pipelineDebugAPI;
|
|
721
|
+
var init_PipelineDebugger = __esm({
|
|
722
|
+
"src/core/navigators/PipelineDebugger.ts"() {
|
|
723
|
+
"use strict";
|
|
724
|
+
init_logger();
|
|
725
|
+
MAX_RUNS = 10;
|
|
726
|
+
runHistory = [];
|
|
727
|
+
pipelineDebugAPI = {
|
|
728
|
+
/**
|
|
729
|
+
* Get raw run history for programmatic access.
|
|
730
|
+
*/
|
|
731
|
+
get runs() {
|
|
732
|
+
return [...runHistory];
|
|
733
|
+
},
|
|
734
|
+
/**
|
|
735
|
+
* Show summary of a specific pipeline run.
|
|
736
|
+
*/
|
|
737
|
+
showRun(idOrIndex = 0) {
|
|
738
|
+
if (runHistory.length === 0) {
|
|
739
|
+
logger.info("[Pipeline Debug] No runs captured yet.");
|
|
740
|
+
return;
|
|
741
|
+
}
|
|
742
|
+
let run;
|
|
743
|
+
if (typeof idOrIndex === "number") {
|
|
744
|
+
run = runHistory[idOrIndex];
|
|
745
|
+
if (!run) {
|
|
746
|
+
logger.info(
|
|
747
|
+
`[Pipeline Debug] No run found at index ${idOrIndex}. History length: ${runHistory.length}`
|
|
748
|
+
);
|
|
749
|
+
return;
|
|
750
|
+
}
|
|
751
|
+
} else {
|
|
752
|
+
run = runHistory.find((r) => r.runId.endsWith(idOrIndex));
|
|
753
|
+
if (!run) {
|
|
754
|
+
logger.info(`[Pipeline Debug] No run found matching ID '${idOrIndex}'.`);
|
|
755
|
+
return;
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
printRunSummary(run);
|
|
759
|
+
},
|
|
760
|
+
/**
|
|
761
|
+
* Show summary of the last pipeline run.
|
|
762
|
+
*/
|
|
763
|
+
showLastRun() {
|
|
764
|
+
this.showRun(0);
|
|
765
|
+
},
|
|
766
|
+
/**
|
|
767
|
+
* Show detailed provenance for a specific card.
|
|
768
|
+
*/
|
|
769
|
+
showCard(cardId) {
|
|
770
|
+
for (const run of runHistory) {
|
|
771
|
+
const card = run.cards.find((c) => c.cardId === cardId);
|
|
772
|
+
if (card) {
|
|
773
|
+
console.group(`\u{1F3B4} Card: ${cardId}`);
|
|
774
|
+
logger.info(`Course: ${card.courseId}`);
|
|
775
|
+
logger.info(`Origin: ${card.origin}`);
|
|
776
|
+
logger.info(`Final score: ${card.finalScore.toFixed(3)}`);
|
|
777
|
+
logger.info(`Selected: ${card.selected ? "Yes \u2705" : "No \u274C"}`);
|
|
778
|
+
logger.info("Provenance:");
|
|
779
|
+
logger.info(formatProvenance(card.provenance));
|
|
780
|
+
console.groupEnd();
|
|
781
|
+
return;
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
logger.info(`[Pipeline Debug] Card '${cardId}' not found in recent runs.`);
|
|
785
|
+
},
|
|
786
|
+
/**
|
|
787
|
+
* Explain why reviews may or may not have been selected.
|
|
788
|
+
*/
|
|
789
|
+
explainReviews() {
|
|
790
|
+
if (runHistory.length === 0) {
|
|
791
|
+
logger.info("[Pipeline Debug] No runs captured yet.");
|
|
792
|
+
return;
|
|
793
|
+
}
|
|
794
|
+
console.group("\u{1F4CB} Review Selection Analysis");
|
|
795
|
+
for (const run of runHistory) {
|
|
796
|
+
console.group(`Run: ${run.courseId} @ ${run.timestamp.toLocaleTimeString()}`);
|
|
797
|
+
const allReviews = run.cards.filter((c) => c.origin === "review");
|
|
798
|
+
const selectedReviews = allReviews.filter((c) => c.selected);
|
|
799
|
+
if (allReviews.length === 0) {
|
|
800
|
+
logger.info("\u274C No reviews were generated. Check SRS logs for why.");
|
|
801
|
+
} else if (selectedReviews.length === 0) {
|
|
802
|
+
logger.info(`\u26A0\uFE0F ${allReviews.length} reviews generated but none selected.`);
|
|
803
|
+
logger.info("Possible reasons:");
|
|
804
|
+
const topNewScore = Math.max(
|
|
805
|
+
...run.cards.filter((c) => c.origin === "new" && c.selected).map((c) => c.finalScore),
|
|
806
|
+
0
|
|
807
|
+
);
|
|
808
|
+
const topReviewScore = Math.max(...allReviews.map((c) => c.finalScore), 0);
|
|
809
|
+
if (topReviewScore < topNewScore) {
|
|
810
|
+
logger.info(
|
|
811
|
+
` - New cards scored higher (top new: ${topNewScore.toFixed(2)}, top review: ${topReviewScore.toFixed(2)})`
|
|
812
|
+
);
|
|
813
|
+
}
|
|
814
|
+
const topReview = allReviews.sort((a, b) => b.finalScore - a.finalScore)[0];
|
|
815
|
+
if (topReview) {
|
|
816
|
+
logger.info(` - Top review score: ${topReview.finalScore.toFixed(3)}`);
|
|
817
|
+
logger.info(" - Its provenance:");
|
|
818
|
+
logger.info(formatProvenance(topReview.provenance));
|
|
819
|
+
}
|
|
820
|
+
} else {
|
|
821
|
+
logger.info(`\u2705 ${selectedReviews.length}/${allReviews.length} reviews selected.`);
|
|
822
|
+
logger.info("Top selected review:");
|
|
823
|
+
const topSelected = selectedReviews.sort((a, b) => b.finalScore - a.finalScore)[0];
|
|
824
|
+
logger.info(formatProvenance(topSelected.provenance));
|
|
825
|
+
}
|
|
826
|
+
console.groupEnd();
|
|
827
|
+
}
|
|
828
|
+
console.groupEnd();
|
|
829
|
+
},
|
|
830
|
+
/**
|
|
831
|
+
* Show all runs in compact format.
|
|
832
|
+
*/
|
|
833
|
+
listRuns() {
|
|
834
|
+
if (runHistory.length === 0) {
|
|
835
|
+
logger.info("[Pipeline Debug] No runs captured yet.");
|
|
836
|
+
return;
|
|
837
|
+
}
|
|
838
|
+
console.table(
|
|
839
|
+
runHistory.map((r) => ({
|
|
840
|
+
id: r.runId.slice(-8),
|
|
841
|
+
time: r.timestamp.toLocaleTimeString(),
|
|
842
|
+
course: r.courseName || r.courseId.slice(0, 8),
|
|
843
|
+
generated: r.generatedCount,
|
|
844
|
+
selected: r.finalCount,
|
|
845
|
+
new: r.newSelected,
|
|
846
|
+
reviews: r.reviewsSelected
|
|
847
|
+
}))
|
|
848
|
+
);
|
|
849
|
+
},
|
|
850
|
+
/**
|
|
851
|
+
* Export run history as JSON for bug reports.
|
|
852
|
+
*/
|
|
853
|
+
export() {
|
|
854
|
+
const json = JSON.stringify(runHistory, null, 2);
|
|
855
|
+
logger.info("[Pipeline Debug] Run history exported. Copy the returned string or use:");
|
|
856
|
+
logger.info(" copy(window.skuilder.pipeline.export())");
|
|
857
|
+
return json;
|
|
858
|
+
},
|
|
859
|
+
/**
|
|
860
|
+
* Clear run history.
|
|
861
|
+
*/
|
|
862
|
+
clear() {
|
|
863
|
+
runHistory.length = 0;
|
|
864
|
+
logger.info("[Pipeline Debug] Run history cleared.");
|
|
865
|
+
},
|
|
866
|
+
/**
|
|
867
|
+
* Show help.
|
|
868
|
+
*/
|
|
869
|
+
help() {
|
|
870
|
+
logger.info(`
|
|
871
|
+
\u{1F527} Pipeline Debug API
|
|
872
|
+
|
|
873
|
+
Commands:
|
|
874
|
+
.showLastRun() Show summary of most recent pipeline run
|
|
875
|
+
.showRun(id|index) Show summary of a specific run (by index or ID suffix)
|
|
876
|
+
.showCard(cardId) Show provenance trail for a specific card
|
|
877
|
+
.explainReviews() Analyze why reviews were/weren't selected
|
|
878
|
+
.listRuns() List all captured runs in table format
|
|
879
|
+
.export() Export run history as JSON for bug reports
|
|
880
|
+
.clear() Clear run history
|
|
881
|
+
.runs Access raw run history array
|
|
882
|
+
.help() Show this help message
|
|
883
|
+
|
|
884
|
+
Example:
|
|
885
|
+
window.skuilder.pipeline.showLastRun()
|
|
886
|
+
window.skuilder.pipeline.showRun(1)
|
|
887
|
+
window.skuilder.pipeline.showCard('abc123')
|
|
888
|
+
`);
|
|
889
|
+
}
|
|
890
|
+
};
|
|
891
|
+
mountPipelineDebugger();
|
|
892
|
+
}
|
|
893
|
+
});
|
|
894
|
+
|
|
630
895
|
// src/core/navigators/generators/CompositeGenerator.ts
|
|
631
896
|
var CompositeGenerator_exports = {};
|
|
632
897
|
__export(CompositeGenerator_exports, {
|
|
@@ -695,6 +960,24 @@ var init_CompositeGenerator = __esm({
|
|
|
695
960
|
const results = await Promise.all(
|
|
696
961
|
this.generators.map((g) => g.getWeightedCards(limit, context))
|
|
697
962
|
);
|
|
963
|
+
const generatorSummaries = [];
|
|
964
|
+
results.forEach((cards, index) => {
|
|
965
|
+
const gen = this.generators[index];
|
|
966
|
+
const genName = gen.name || `Generator ${index}`;
|
|
967
|
+
const newCards = cards.filter((c) => c.provenance[0]?.reason?.includes("new card"));
|
|
968
|
+
const reviewCards = cards.filter((c) => c.provenance[0]?.reason?.includes("review"));
|
|
969
|
+
if (cards.length > 0) {
|
|
970
|
+
const topScore = Math.max(...cards.map((c) => c.score)).toFixed(2);
|
|
971
|
+
const parts = [];
|
|
972
|
+
if (newCards.length > 0) parts.push(`${newCards.length} new`);
|
|
973
|
+
if (reviewCards.length > 0) parts.push(`${reviewCards.length} reviews`);
|
|
974
|
+
const breakdown = parts.length > 0 ? parts.join(", ") : `${cards.length} cards`;
|
|
975
|
+
generatorSummaries.push(`${genName}: ${breakdown} (top: ${topScore})`);
|
|
976
|
+
} else {
|
|
977
|
+
generatorSummaries.push(`${genName}: 0 cards`);
|
|
978
|
+
}
|
|
979
|
+
});
|
|
980
|
+
logger.info(`[Composite] Generator breakdown: ${generatorSummaries.join(" | ")}`);
|
|
698
981
|
const byCardId = /* @__PURE__ */ new Map();
|
|
699
982
|
results.forEach((cards, index) => {
|
|
700
983
|
const gen = this.generators[index];
|
|
@@ -812,6 +1095,7 @@ var init_elo = __esm({
|
|
|
812
1095
|
"use strict";
|
|
813
1096
|
init_navigators();
|
|
814
1097
|
import_common5 = require("@vue-skuilder/common");
|
|
1098
|
+
init_logger();
|
|
815
1099
|
ELONavigator = class extends ContentNavigator {
|
|
816
1100
|
/** Human-readable name for CardGenerator interface */
|
|
817
1101
|
name;
|
|
@@ -871,7 +1155,16 @@ var init_elo = __esm({
|
|
|
871
1155
|
};
|
|
872
1156
|
});
|
|
873
1157
|
scored.sort((a, b) => b.score - a.score);
|
|
874
|
-
|
|
1158
|
+
const result = scored.slice(0, limit);
|
|
1159
|
+
if (result.length > 0) {
|
|
1160
|
+
const topScores = result.slice(0, 3).map((c) => c.score.toFixed(2)).join(", ");
|
|
1161
|
+
logger.info(
|
|
1162
|
+
`[ELO] Course ${this.course.getCourseID()}: ${result.length} new cards (top scores: ${topScores})`
|
|
1163
|
+
);
|
|
1164
|
+
} else {
|
|
1165
|
+
logger.info(`[ELO] Course ${this.course.getCourseID()}: No new cards available`);
|
|
1166
|
+
}
|
|
1167
|
+
return result;
|
|
875
1168
|
}
|
|
876
1169
|
};
|
|
877
1170
|
}
|
|
@@ -890,19 +1183,37 @@ var srs_exports = {};
|
|
|
890
1183
|
__export(srs_exports, {
|
|
891
1184
|
default: () => SRSNavigator
|
|
892
1185
|
});
|
|
893
|
-
var import_moment, SRSNavigator;
|
|
1186
|
+
var import_moment, DEFAULT_HEALTHY_BACKLOG, MAX_BACKLOG_PRESSURE, SRSNavigator;
|
|
894
1187
|
var init_srs = __esm({
|
|
895
1188
|
"src/core/navigators/generators/srs.ts"() {
|
|
896
1189
|
"use strict";
|
|
897
1190
|
import_moment = __toESM(require("moment"), 1);
|
|
898
1191
|
init_navigators();
|
|
899
1192
|
init_logger();
|
|
1193
|
+
DEFAULT_HEALTHY_BACKLOG = 20;
|
|
1194
|
+
MAX_BACKLOG_PRESSURE = 0.5;
|
|
900
1195
|
SRSNavigator = class extends ContentNavigator {
|
|
901
1196
|
/** Human-readable name for CardGenerator interface */
|
|
902
1197
|
name;
|
|
1198
|
+
/** Healthy backlog threshold - when exceeded, backlog pressure kicks in */
|
|
1199
|
+
healthyBacklog;
|
|
903
1200
|
constructor(user, course, strategyData) {
|
|
904
1201
|
super(user, course, strategyData);
|
|
905
1202
|
this.name = strategyData?.name || "SRS";
|
|
1203
|
+
const config = this.parseConfig(strategyData?.serializedData);
|
|
1204
|
+
this.healthyBacklog = config.healthyBacklog ?? DEFAULT_HEALTHY_BACKLOG;
|
|
1205
|
+
}
|
|
1206
|
+
/**
|
|
1207
|
+
* Parse configuration from serialized JSON.
|
|
1208
|
+
*/
|
|
1209
|
+
parseConfig(serializedData) {
|
|
1210
|
+
if (!serializedData) return {};
|
|
1211
|
+
try {
|
|
1212
|
+
return JSON.parse(serializedData);
|
|
1213
|
+
} catch {
|
|
1214
|
+
logger.warn("[SRS] Failed to parse strategy config, using defaults");
|
|
1215
|
+
return {};
|
|
1216
|
+
}
|
|
906
1217
|
}
|
|
907
1218
|
/**
|
|
908
1219
|
* Get review cards scored by urgency.
|
|
@@ -910,6 +1221,7 @@ var init_srs = __esm({
|
|
|
910
1221
|
* Score formula combines:
|
|
911
1222
|
* - Relative overdueness: hoursOverdue / intervalHours
|
|
912
1223
|
* - Interval recency: exponential decay favoring shorter intervals
|
|
1224
|
+
* - Backlog pressure: boost when due reviews exceed healthy threshold
|
|
913
1225
|
*
|
|
914
1226
|
* Cards not yet due are excluded (not scored as 0).
|
|
915
1227
|
*
|
|
@@ -923,11 +1235,32 @@ var init_srs = __esm({
|
|
|
923
1235
|
if (!this.user || !this.course) {
|
|
924
1236
|
throw new Error("SRSNavigator requires user and course to be set");
|
|
925
1237
|
}
|
|
926
|
-
const
|
|
1238
|
+
const courseId = this.course.getCourseID();
|
|
1239
|
+
const reviews = await this.user.getPendingReviews(courseId);
|
|
927
1240
|
const now = import_moment.default.utc();
|
|
928
1241
|
const dueReviews = reviews.filter((r) => now.isAfter(import_moment.default.utc(r.reviewTime)));
|
|
1242
|
+
const backlogPressure = this.computeBacklogPressure(dueReviews.length);
|
|
1243
|
+
if (dueReviews.length > 0) {
|
|
1244
|
+
const pressureNote = backlogPressure > 0 ? ` [backlog pressure: +${backlogPressure.toFixed(2)}]` : ` [healthy backlog]`;
|
|
1245
|
+
logger.info(
|
|
1246
|
+
`[SRS] Course ${courseId}: ${dueReviews.length} reviews due now (of ${reviews.length} scheduled)${pressureNote}`
|
|
1247
|
+
);
|
|
1248
|
+
} else if (reviews.length > 0) {
|
|
1249
|
+
const sortedByDue = [...reviews].sort(
|
|
1250
|
+
(a, b) => import_moment.default.utc(a.reviewTime).diff(import_moment.default.utc(b.reviewTime))
|
|
1251
|
+
);
|
|
1252
|
+
const nextDue = sortedByDue[0];
|
|
1253
|
+
const nextDueTime = import_moment.default.utc(nextDue.reviewTime);
|
|
1254
|
+
const untilDue = import_moment.default.duration(nextDueTime.diff(now));
|
|
1255
|
+
const untilDueStr = untilDue.asHours() < 1 ? `${Math.round(untilDue.asMinutes())}m` : untilDue.asHours() < 24 ? `${Math.round(untilDue.asHours())}h` : `${Math.round(untilDue.asDays())}d`;
|
|
1256
|
+
logger.info(
|
|
1257
|
+
`[SRS] Course ${courseId}: 0 reviews due now (${reviews.length} scheduled, next in ${untilDueStr})`
|
|
1258
|
+
);
|
|
1259
|
+
} else {
|
|
1260
|
+
logger.info(`[SRS] Course ${courseId}: No reviews scheduled`);
|
|
1261
|
+
}
|
|
929
1262
|
const scored = dueReviews.map((review) => {
|
|
930
|
-
const { score, reason } = this.computeUrgencyScore(review, now);
|
|
1263
|
+
const { score, reason } = this.computeUrgencyScore(review, now, backlogPressure);
|
|
931
1264
|
return {
|
|
932
1265
|
cardId: review.cardId,
|
|
933
1266
|
courseId: review.courseId,
|
|
@@ -945,13 +1278,35 @@ var init_srs = __esm({
|
|
|
945
1278
|
]
|
|
946
1279
|
};
|
|
947
1280
|
});
|
|
948
|
-
logger.debug(`[srsNav] got ${scored.length} weighted cards`);
|
|
949
1281
|
return scored.sort((a, b) => b.score - a.score).slice(0, limit);
|
|
950
1282
|
}
|
|
1283
|
+
/**
|
|
1284
|
+
* Compute backlog pressure based on number of due reviews.
|
|
1285
|
+
*
|
|
1286
|
+
* Backlog pressure is 0 when at or below healthy threshold,
|
|
1287
|
+
* and increases linearly above it, maxing out at MAX_BACKLOG_PRESSURE.
|
|
1288
|
+
*
|
|
1289
|
+
* Examples (with default healthyBacklog=20):
|
|
1290
|
+
* - 10 due reviews → 0.00 (healthy)
|
|
1291
|
+
* - 20 due reviews → 0.00 (at threshold)
|
|
1292
|
+
* - 40 due reviews → 0.25 (2x threshold)
|
|
1293
|
+
* - 60 due reviews → 0.50 (3x threshold, maxed)
|
|
1294
|
+
*
|
|
1295
|
+
* @param dueCount - Number of reviews currently due
|
|
1296
|
+
* @returns Backlog pressure score to add to urgency (0 to MAX_BACKLOG_PRESSURE)
|
|
1297
|
+
*/
|
|
1298
|
+
computeBacklogPressure(dueCount) {
|
|
1299
|
+
if (dueCount <= this.healthyBacklog) {
|
|
1300
|
+
return 0;
|
|
1301
|
+
}
|
|
1302
|
+
const excess = dueCount - this.healthyBacklog;
|
|
1303
|
+
const pressure = excess / this.healthyBacklog * (MAX_BACKLOG_PRESSURE / 2);
|
|
1304
|
+
return Math.min(MAX_BACKLOG_PRESSURE, pressure);
|
|
1305
|
+
}
|
|
951
1306
|
/**
|
|
952
1307
|
* Compute urgency score for a review card.
|
|
953
1308
|
*
|
|
954
|
-
*
|
|
1309
|
+
* Three factors:
|
|
955
1310
|
* 1. Relative overdueness = hoursOverdue / intervalHours
|
|
956
1311
|
* - 2 days overdue on 3-day interval = 0.67 (urgent)
|
|
957
1312
|
* - 2 days overdue on 180-day interval = 0.01 (not urgent)
|
|
@@ -961,10 +1316,19 @@ var init_srs = __esm({
|
|
|
961
1316
|
* - 30 days (720h) → ~0.56
|
|
962
1317
|
* - 180 days → ~0.30
|
|
963
1318
|
*
|
|
964
|
-
*
|
|
965
|
-
*
|
|
1319
|
+
* 3. Backlog pressure = global boost when review backlog exceeds healthy threshold
|
|
1320
|
+
* - At healthy backlog: 0
|
|
1321
|
+
* - At 2x healthy: +0.25
|
|
1322
|
+
* - At 3x+ healthy: +0.50 (max)
|
|
1323
|
+
*
|
|
1324
|
+
* Combined: base 0.5 + (urgency factors * 0.45) + backlog pressure
|
|
1325
|
+
* Result range: 0.5 to 1.0 (uncapped to allow high-urgency reviews to compete with new cards)
|
|
1326
|
+
*
|
|
1327
|
+
* @param review - The scheduled card to score
|
|
1328
|
+
* @param now - Current time
|
|
1329
|
+
* @param backlogPressure - Pre-computed backlog pressure (0 to 0.5)
|
|
966
1330
|
*/
|
|
967
|
-
computeUrgencyScore(review, now) {
|
|
1331
|
+
computeUrgencyScore(review, now, backlogPressure) {
|
|
968
1332
|
const scheduledAt = import_moment.default.utc(review.scheduledAt);
|
|
969
1333
|
const due = import_moment.default.utc(review.reviewTime);
|
|
970
1334
|
const intervalHours = Math.max(1, due.diff(scheduledAt, "hours"));
|
|
@@ -973,8 +1337,19 @@ var init_srs = __esm({
|
|
|
973
1337
|
const recencyFactor = 0.3 + 0.7 * Math.exp(-intervalHours / 720);
|
|
974
1338
|
const overdueContribution = Math.min(1, Math.max(0, relativeOverdue));
|
|
975
1339
|
const urgency = overdueContribution * 0.5 + recencyFactor * 0.5;
|
|
976
|
-
const
|
|
977
|
-
const
|
|
1340
|
+
const baseScore = 0.5 + urgency * 0.45;
|
|
1341
|
+
const score = Math.min(1, baseScore + backlogPressure);
|
|
1342
|
+
const reasonParts = [
|
|
1343
|
+
`${Math.round(hoursOverdue)}h overdue`,
|
|
1344
|
+
`interval: ${Math.round(intervalHours)}h`,
|
|
1345
|
+
`relative: ${relativeOverdue.toFixed(2)}`,
|
|
1346
|
+
`recency: ${recencyFactor.toFixed(2)}`
|
|
1347
|
+
];
|
|
1348
|
+
if (backlogPressure > 0) {
|
|
1349
|
+
reasonParts.push(`backlog: +${backlogPressure.toFixed(2)}`);
|
|
1350
|
+
}
|
|
1351
|
+
reasonParts.push("review");
|
|
1352
|
+
const reason = reasonParts.join(", ");
|
|
978
1353
|
return { score, reason };
|
|
979
1354
|
}
|
|
980
1355
|
};
|
|
@@ -2004,10 +2379,23 @@ function logTagHydration(cards, tagsByCard) {
|
|
|
2004
2379
|
`[Pipeline] Tag hydration: ${cards.length} cards, ${cardsWithTags} have tags (${totalTags} total tags) - single batch query`
|
|
2005
2380
|
);
|
|
2006
2381
|
}
|
|
2007
|
-
function logExecutionSummary(generatorName, generatedCount, filterCount, finalCount, topScores) {
|
|
2382
|
+
function logExecutionSummary(generatorName, generatedCount, filterCount, finalCount, topScores, filterImpacts) {
|
|
2008
2383
|
const scoreDisplay = topScores.length > 0 ? topScores.map((s) => s.toFixed(2)).join(", ") : "none";
|
|
2384
|
+
let filterSummary = "";
|
|
2385
|
+
if (filterImpacts.length > 0) {
|
|
2386
|
+
const impacts = filterImpacts.map((f) => {
|
|
2387
|
+
const parts = [];
|
|
2388
|
+
if (f.boosted > 0) parts.push(`+${f.boosted}`);
|
|
2389
|
+
if (f.penalized > 0) parts.push(`-${f.penalized}`);
|
|
2390
|
+
if (f.passed > 0) parts.push(`=${f.passed}`);
|
|
2391
|
+
return `${f.name}: ${parts.join("/")}`;
|
|
2392
|
+
});
|
|
2393
|
+
filterSummary = `
|
|
2394
|
+
Filter impact: ${impacts.join(", ")}`;
|
|
2395
|
+
}
|
|
2009
2396
|
logger.info(
|
|
2010
|
-
`[Pipeline] Execution: ${generatorName} produced ${generatedCount} \u2192 ${filterCount} filters \u2192 ${finalCount} results (top scores: ${scoreDisplay})`
|
|
2397
|
+
`[Pipeline] Execution: ${generatorName} produced ${generatedCount} \u2192 ${filterCount} filters \u2192 ${finalCount} results (top scores: ${scoreDisplay})` + filterSummary + `
|
|
2398
|
+
\u{1F4A1} Inspect: window.skuilder.pipeline`
|
|
2011
2399
|
);
|
|
2012
2400
|
}
|
|
2013
2401
|
function logCardProvenance(cards, maxCards = 3) {
|
|
@@ -2032,6 +2420,7 @@ var init_Pipeline = __esm({
|
|
|
2032
2420
|
init_navigators();
|
|
2033
2421
|
init_logger();
|
|
2034
2422
|
init_orchestration();
|
|
2423
|
+
init_PipelineDebugger();
|
|
2035
2424
|
Pipeline = class extends ContentNavigator {
|
|
2036
2425
|
generator;
|
|
2037
2426
|
filters;
|
|
@@ -2079,12 +2468,49 @@ var init_Pipeline = __esm({
|
|
|
2079
2468
|
);
|
|
2080
2469
|
let cards = await this.generator.getWeightedCards(fetchLimit, context);
|
|
2081
2470
|
const generatedCount = cards.length;
|
|
2471
|
+
let generatorSummaries;
|
|
2472
|
+
if (this.generator.generators) {
|
|
2473
|
+
const genMap = /* @__PURE__ */ new Map();
|
|
2474
|
+
for (const card of cards) {
|
|
2475
|
+
const firstProv = card.provenance[0];
|
|
2476
|
+
if (firstProv) {
|
|
2477
|
+
const genName = firstProv.strategyName;
|
|
2478
|
+
if (!genMap.has(genName)) {
|
|
2479
|
+
genMap.set(genName, { cards: [] });
|
|
2480
|
+
}
|
|
2481
|
+
genMap.get(genName).cards.push(card);
|
|
2482
|
+
}
|
|
2483
|
+
}
|
|
2484
|
+
generatorSummaries = Array.from(genMap.entries()).map(([name, data]) => {
|
|
2485
|
+
const newCards = data.cards.filter((c) => c.provenance[0]?.reason?.includes("new card"));
|
|
2486
|
+
const reviewCards = data.cards.filter((c) => c.provenance[0]?.reason?.includes("review"));
|
|
2487
|
+
return {
|
|
2488
|
+
name,
|
|
2489
|
+
cardCount: data.cards.length,
|
|
2490
|
+
newCount: newCards.length,
|
|
2491
|
+
reviewCount: reviewCards.length,
|
|
2492
|
+
topScore: Math.max(...data.cards.map((c) => c.score), 0)
|
|
2493
|
+
};
|
|
2494
|
+
});
|
|
2495
|
+
}
|
|
2082
2496
|
logger.debug(`[Pipeline] Generator returned ${generatedCount} candidates`);
|
|
2083
2497
|
cards = await this.hydrateTags(cards);
|
|
2498
|
+
const allCardsBeforeFiltering = [...cards];
|
|
2499
|
+
const filterImpacts = [];
|
|
2084
2500
|
for (const filter of this.filters) {
|
|
2085
2501
|
const beforeCount = cards.length;
|
|
2502
|
+
const beforeScores = new Map(cards.map((c) => [c.cardId, c.score]));
|
|
2086
2503
|
cards = await filter.transform(cards, context);
|
|
2087
|
-
|
|
2504
|
+
let boosted = 0, penalized = 0, passed = 0;
|
|
2505
|
+
const removed = beforeCount - cards.length;
|
|
2506
|
+
for (const card of cards) {
|
|
2507
|
+
const before = beforeScores.get(card.cardId) ?? 0;
|
|
2508
|
+
if (card.score > before) boosted++;
|
|
2509
|
+
else if (card.score < before) penalized++;
|
|
2510
|
+
else passed++;
|
|
2511
|
+
}
|
|
2512
|
+
filterImpacts.push({ name: filter.name, boosted, penalized, passed, removed });
|
|
2513
|
+
logger.debug(`[Pipeline] Filter '${filter.name}': ${beforeScores.size} \u2192 ${cards.length} cards (\u2191${boosted} \u2193${penalized} =${passed})`);
|
|
2088
2514
|
}
|
|
2089
2515
|
cards = cards.filter((c) => c.score > 0);
|
|
2090
2516
|
cards.sort((a, b) => b.score - a.score);
|
|
@@ -2095,9 +2521,26 @@ var init_Pipeline = __esm({
|
|
|
2095
2521
|
generatedCount,
|
|
2096
2522
|
this.filters.length,
|
|
2097
2523
|
result.length,
|
|
2098
|
-
topScores
|
|
2524
|
+
topScores,
|
|
2525
|
+
filterImpacts
|
|
2099
2526
|
);
|
|
2100
2527
|
logCardProvenance(result, 3);
|
|
2528
|
+
try {
|
|
2529
|
+
const courseName = await this.course?.getCourseConfig().then((c) => c.name).catch(() => void 0);
|
|
2530
|
+
const report = buildRunReport(
|
|
2531
|
+
this.course?.getCourseID() || "unknown",
|
|
2532
|
+
courseName,
|
|
2533
|
+
this.generator.name,
|
|
2534
|
+
generatorSummaries,
|
|
2535
|
+
generatedCount,
|
|
2536
|
+
filterImpacts,
|
|
2537
|
+
allCardsBeforeFiltering,
|
|
2538
|
+
result
|
|
2539
|
+
);
|
|
2540
|
+
captureRun(report);
|
|
2541
|
+
} catch (e) {
|
|
2542
|
+
logger.debug(`[Pipeline] Failed to capture debug run: ${e}`);
|
|
2543
|
+
}
|
|
2101
2544
|
return result;
|
|
2102
2545
|
}
|
|
2103
2546
|
/**
|
|
@@ -2187,6 +2630,56 @@ var init_Pipeline = __esm({
|
|
|
2187
2630
|
}
|
|
2188
2631
|
});
|
|
2189
2632
|
|
|
2633
|
+
// src/core/navigators/defaults.ts
|
|
2634
|
+
var defaults_exports = {};
|
|
2635
|
+
__export(defaults_exports, {
|
|
2636
|
+
createDefaultEloStrategy: () => createDefaultEloStrategy,
|
|
2637
|
+
createDefaultPipeline: () => createDefaultPipeline,
|
|
2638
|
+
createDefaultSrsStrategy: () => createDefaultSrsStrategy
|
|
2639
|
+
});
|
|
2640
|
+
function createDefaultEloStrategy(courseId) {
|
|
2641
|
+
return {
|
|
2642
|
+
_id: "NAVIGATION_STRATEGY-ELO-default",
|
|
2643
|
+
docType: "NAVIGATION_STRATEGY" /* NAVIGATION_STRATEGY */,
|
|
2644
|
+
name: "ELO (default)",
|
|
2645
|
+
description: "Default ELO-based navigation strategy for new cards",
|
|
2646
|
+
implementingClass: "elo" /* ELO */,
|
|
2647
|
+
course: courseId,
|
|
2648
|
+
serializedData: ""
|
|
2649
|
+
};
|
|
2650
|
+
}
|
|
2651
|
+
function createDefaultSrsStrategy(courseId) {
|
|
2652
|
+
return {
|
|
2653
|
+
_id: "NAVIGATION_STRATEGY-SRS-default",
|
|
2654
|
+
docType: "NAVIGATION_STRATEGY" /* NAVIGATION_STRATEGY */,
|
|
2655
|
+
name: "SRS (default)",
|
|
2656
|
+
description: "Default SRS-based navigation strategy for reviews",
|
|
2657
|
+
implementingClass: "srs" /* SRS */,
|
|
2658
|
+
course: courseId,
|
|
2659
|
+
serializedData: ""
|
|
2660
|
+
};
|
|
2661
|
+
}
|
|
2662
|
+
function createDefaultPipeline(user, course) {
|
|
2663
|
+
const courseId = course.getCourseID();
|
|
2664
|
+
const eloNavigator = new ELONavigator(user, course, createDefaultEloStrategy(courseId));
|
|
2665
|
+
const srsNavigator = new SRSNavigator(user, course, createDefaultSrsStrategy(courseId));
|
|
2666
|
+
const compositeGenerator = new CompositeGenerator([eloNavigator, srsNavigator]);
|
|
2667
|
+
const eloDistanceFilter = createEloDistanceFilter();
|
|
2668
|
+
return new Pipeline(compositeGenerator, [eloDistanceFilter], user, course);
|
|
2669
|
+
}
|
|
2670
|
+
var init_defaults = __esm({
|
|
2671
|
+
"src/core/navigators/defaults.ts"() {
|
|
2672
|
+
"use strict";
|
|
2673
|
+
init_navigators();
|
|
2674
|
+
init_Pipeline();
|
|
2675
|
+
init_CompositeGenerator();
|
|
2676
|
+
init_elo();
|
|
2677
|
+
init_srs();
|
|
2678
|
+
init_eloDistance();
|
|
2679
|
+
init_types_legacy();
|
|
2680
|
+
}
|
|
2681
|
+
});
|
|
2682
|
+
|
|
2190
2683
|
// src/core/navigators/PipelineAssembler.ts
|
|
2191
2684
|
var PipelineAssembler_exports = {};
|
|
2192
2685
|
__export(PipelineAssembler_exports, {
|
|
@@ -2199,9 +2692,9 @@ var init_PipelineAssembler = __esm({
|
|
|
2199
2692
|
init_navigators();
|
|
2200
2693
|
init_WeightedFilter();
|
|
2201
2694
|
init_Pipeline();
|
|
2202
|
-
init_types_legacy();
|
|
2203
2695
|
init_logger();
|
|
2204
2696
|
init_CompositeGenerator();
|
|
2697
|
+
init_defaults();
|
|
2205
2698
|
PipelineAssembler = class {
|
|
2206
2699
|
/**
|
|
2207
2700
|
* Assembles a navigation pipeline from strategy documents.
|
|
@@ -2240,9 +2733,11 @@ var init_PipelineAssembler = __esm({
|
|
|
2240
2733
|
if (generatorStrategies.length === 0) {
|
|
2241
2734
|
if (filterStrategies.length > 0) {
|
|
2242
2735
|
logger.debug(
|
|
2243
|
-
"[PipelineAssembler] No generator found, using default ELO with configured filters"
|
|
2736
|
+
"[PipelineAssembler] No generator found, using default ELO and SRS with configured filters"
|
|
2244
2737
|
);
|
|
2245
|
-
|
|
2738
|
+
const courseId = course.getCourseID();
|
|
2739
|
+
generatorStrategies.push(createDefaultEloStrategy(courseId));
|
|
2740
|
+
generatorStrategies.push(createDefaultSrsStrategy(courseId));
|
|
2246
2741
|
} else {
|
|
2247
2742
|
warnings.push("No generator strategy found");
|
|
2248
2743
|
return {
|
|
@@ -2303,75 +2798,10 @@ var init_PipelineAssembler = __esm({
|
|
|
2303
2798
|
warnings
|
|
2304
2799
|
};
|
|
2305
2800
|
}
|
|
2306
|
-
/**
|
|
2307
|
-
* Creates a default ELO generator strategy.
|
|
2308
|
-
* Used when filters are configured but no generator is specified.
|
|
2309
|
-
*/
|
|
2310
|
-
makeDefaultEloStrategy(courseId) {
|
|
2311
|
-
return {
|
|
2312
|
-
_id: "NAVIGATION_STRATEGY-ELO-default",
|
|
2313
|
-
course: courseId,
|
|
2314
|
-
docType: "NAVIGATION_STRATEGY" /* NAVIGATION_STRATEGY */,
|
|
2315
|
-
name: "ELO (default)",
|
|
2316
|
-
description: "Default ELO-based generator",
|
|
2317
|
-
implementingClass: "elo" /* ELO */,
|
|
2318
|
-
serializedData: ""
|
|
2319
|
-
};
|
|
2320
|
-
}
|
|
2321
2801
|
};
|
|
2322
2802
|
}
|
|
2323
2803
|
});
|
|
2324
2804
|
|
|
2325
|
-
// src/core/navigators/defaults.ts
|
|
2326
|
-
var defaults_exports = {};
|
|
2327
|
-
__export(defaults_exports, {
|
|
2328
|
-
createDefaultEloStrategy: () => createDefaultEloStrategy,
|
|
2329
|
-
createDefaultPipeline: () => createDefaultPipeline,
|
|
2330
|
-
createDefaultSrsStrategy: () => createDefaultSrsStrategy
|
|
2331
|
-
});
|
|
2332
|
-
function createDefaultEloStrategy(courseId) {
|
|
2333
|
-
return {
|
|
2334
|
-
_id: "NAVIGATION_STRATEGY-ELO-default",
|
|
2335
|
-
docType: "NAVIGATION_STRATEGY" /* NAVIGATION_STRATEGY */,
|
|
2336
|
-
name: "ELO (default)",
|
|
2337
|
-
description: "Default ELO-based navigation strategy for new cards",
|
|
2338
|
-
implementingClass: "elo" /* ELO */,
|
|
2339
|
-
course: courseId,
|
|
2340
|
-
serializedData: ""
|
|
2341
|
-
};
|
|
2342
|
-
}
|
|
2343
|
-
function createDefaultSrsStrategy(courseId) {
|
|
2344
|
-
return {
|
|
2345
|
-
_id: "NAVIGATION_STRATEGY-SRS-default",
|
|
2346
|
-
docType: "NAVIGATION_STRATEGY" /* NAVIGATION_STRATEGY */,
|
|
2347
|
-
name: "SRS (default)",
|
|
2348
|
-
description: "Default SRS-based navigation strategy for reviews",
|
|
2349
|
-
implementingClass: "srs" /* SRS */,
|
|
2350
|
-
course: courseId,
|
|
2351
|
-
serializedData: ""
|
|
2352
|
-
};
|
|
2353
|
-
}
|
|
2354
|
-
function createDefaultPipeline(user, course) {
|
|
2355
|
-
const courseId = course.getCourseID();
|
|
2356
|
-
const eloNavigator = new ELONavigator(user, course, createDefaultEloStrategy(courseId));
|
|
2357
|
-
const srsNavigator = new SRSNavigator(user, course, createDefaultSrsStrategy(courseId));
|
|
2358
|
-
const compositeGenerator = new CompositeGenerator([eloNavigator, srsNavigator]);
|
|
2359
|
-
const eloDistanceFilter = createEloDistanceFilter();
|
|
2360
|
-
return new Pipeline(compositeGenerator, [eloDistanceFilter], user, course);
|
|
2361
|
-
}
|
|
2362
|
-
var init_defaults = __esm({
|
|
2363
|
-
"src/core/navigators/defaults.ts"() {
|
|
2364
|
-
"use strict";
|
|
2365
|
-
init_navigators();
|
|
2366
|
-
init_Pipeline();
|
|
2367
|
-
init_CompositeGenerator();
|
|
2368
|
-
init_elo();
|
|
2369
|
-
init_srs();
|
|
2370
|
-
init_eloDistance();
|
|
2371
|
-
init_types_legacy();
|
|
2372
|
-
}
|
|
2373
|
-
});
|
|
2374
|
-
|
|
2375
2805
|
// import("./**/*") in src/core/navigators/index.ts
|
|
2376
2806
|
var globImport;
|
|
2377
2807
|
var init_3 = __esm({
|
|
@@ -2379,6 +2809,7 @@ var init_3 = __esm({
|
|
|
2379
2809
|
globImport = __glob({
|
|
2380
2810
|
"./Pipeline.ts": () => Promise.resolve().then(() => (init_Pipeline(), Pipeline_exports)),
|
|
2381
2811
|
"./PipelineAssembler.ts": () => Promise.resolve().then(() => (init_PipelineAssembler(), PipelineAssembler_exports)),
|
|
2812
|
+
"./PipelineDebugger.ts": () => Promise.resolve().then(() => (init_PipelineDebugger(), PipelineDebugger_exports)),
|
|
2382
2813
|
"./defaults.ts": () => Promise.resolve().then(() => (init_defaults(), defaults_exports)),
|
|
2383
2814
|
"./filters/WeightedFilter.ts": () => Promise.resolve().then(() => (init_WeightedFilter(), WeightedFilter_exports)),
|
|
2384
2815
|
"./filters/eloDistance.ts": () => Promise.resolve().then(() => (init_eloDistance(), eloDistance_exports)),
|
|
@@ -2414,6 +2845,8 @@ __export(navigators_exports, {
|
|
|
2414
2845
|
initializeNavigatorRegistry: () => initializeNavigatorRegistry,
|
|
2415
2846
|
isFilter: () => isFilter,
|
|
2416
2847
|
isGenerator: () => isGenerator,
|
|
2848
|
+
mountPipelineDebugger: () => mountPipelineDebugger,
|
|
2849
|
+
pipelineDebugAPI: () => pipelineDebugAPI,
|
|
2417
2850
|
registerNavigator: () => registerNavigator
|
|
2418
2851
|
});
|
|
2419
2852
|
function registerNavigator(implementingClass, constructor) {
|
|
@@ -2480,6 +2913,7 @@ var navigatorRegistry, Navigators, NavigatorRole, NavigatorRoles, ContentNavigat
|
|
|
2480
2913
|
var init_navigators = __esm({
|
|
2481
2914
|
"src/core/navigators/index.ts"() {
|
|
2482
2915
|
"use strict";
|
|
2916
|
+
init_PipelineDebugger();
|
|
2483
2917
|
init_logger();
|
|
2484
2918
|
init_();
|
|
2485
2919
|
init_2();
|
|
@@ -4150,6 +4584,15 @@ var init_user_course_relDB = __esm({
|
|
|
4150
4584
|
void this.user.updateCourseSettings(this._courseId, updates);
|
|
4151
4585
|
}
|
|
4152
4586
|
}
|
|
4587
|
+
async getStrategyState(strategyKey) {
|
|
4588
|
+
return this.user.getStrategyState(this._courseId, strategyKey);
|
|
4589
|
+
}
|
|
4590
|
+
async putStrategyState(strategyKey, data) {
|
|
4591
|
+
return this.user.putStrategyState(this._courseId, strategyKey, data);
|
|
4592
|
+
}
|
|
4593
|
+
async deleteStrategyState(strategyKey) {
|
|
4594
|
+
return this.user.deleteStrategyState(this._courseId, strategyKey);
|
|
4595
|
+
}
|
|
4153
4596
|
async getReviewstoDate(targetDate) {
|
|
4154
4597
|
const allReviews = await this.user.getPendingReviews(this._courseId);
|
|
4155
4598
|
logger.debug(
|