@vue-skuilder/db 0.1.24 → 0.1.26
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/core/index.mjs
CHANGED
|
@@ -448,6 +448,15 @@ var init_user_course_relDB = __esm({
|
|
|
448
448
|
void this.user.updateCourseSettings(this._courseId, updates);
|
|
449
449
|
}
|
|
450
450
|
}
|
|
451
|
+
async getStrategyState(strategyKey) {
|
|
452
|
+
return this.user.getStrategyState(this._courseId, strategyKey);
|
|
453
|
+
}
|
|
454
|
+
async putStrategyState(strategyKey, data) {
|
|
455
|
+
return this.user.putStrategyState(this._courseId, strategyKey, data);
|
|
456
|
+
}
|
|
457
|
+
async deleteStrategyState(strategyKey) {
|
|
458
|
+
return this.user.deleteStrategyState(this._courseId, strategyKey);
|
|
459
|
+
}
|
|
451
460
|
async getReviewstoDate(targetDate) {
|
|
452
461
|
const allReviews = await this.user.getPendingReviews(this._courseId);
|
|
453
462
|
logger.debug(
|
|
@@ -687,6 +696,271 @@ var init_courseLookupDB = __esm({
|
|
|
687
696
|
}
|
|
688
697
|
});
|
|
689
698
|
|
|
699
|
+
// src/core/navigators/PipelineDebugger.ts
|
|
700
|
+
var PipelineDebugger_exports = {};
|
|
701
|
+
__export(PipelineDebugger_exports, {
|
|
702
|
+
buildRunReport: () => buildRunReport,
|
|
703
|
+
captureRun: () => captureRun,
|
|
704
|
+
mountPipelineDebugger: () => mountPipelineDebugger,
|
|
705
|
+
pipelineDebugAPI: () => pipelineDebugAPI
|
|
706
|
+
});
|
|
707
|
+
function getOrigin(card) {
|
|
708
|
+
const firstEntry = card.provenance[0];
|
|
709
|
+
if (!firstEntry) return "unknown";
|
|
710
|
+
const reason = firstEntry.reason?.toLowerCase() || "";
|
|
711
|
+
if (reason.includes("new card")) return "new";
|
|
712
|
+
if (reason.includes("review")) return "review";
|
|
713
|
+
return "unknown";
|
|
714
|
+
}
|
|
715
|
+
function captureRun(report) {
|
|
716
|
+
const fullReport = {
|
|
717
|
+
...report,
|
|
718
|
+
runId: `run-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
|
719
|
+
timestamp: /* @__PURE__ */ new Date()
|
|
720
|
+
};
|
|
721
|
+
runHistory.unshift(fullReport);
|
|
722
|
+
if (runHistory.length > MAX_RUNS) {
|
|
723
|
+
runHistory.pop();
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
function buildRunReport(courseId, courseName, generatorName, generators, generatedCount, filters, allCards, selectedCards) {
|
|
727
|
+
const selectedIds = new Set(selectedCards.map((c) => c.cardId));
|
|
728
|
+
const cards = allCards.map((card) => ({
|
|
729
|
+
cardId: card.cardId,
|
|
730
|
+
courseId: card.courseId,
|
|
731
|
+
origin: getOrigin(card),
|
|
732
|
+
finalScore: card.score,
|
|
733
|
+
provenance: card.provenance,
|
|
734
|
+
selected: selectedIds.has(card.cardId)
|
|
735
|
+
}));
|
|
736
|
+
const reviewsSelected = selectedCards.filter((c) => getOrigin(c) === "review").length;
|
|
737
|
+
const newSelected = selectedCards.filter((c) => getOrigin(c) === "new").length;
|
|
738
|
+
return {
|
|
739
|
+
courseId,
|
|
740
|
+
courseName,
|
|
741
|
+
generatorName,
|
|
742
|
+
generators,
|
|
743
|
+
generatedCount,
|
|
744
|
+
filters,
|
|
745
|
+
finalCount: selectedCards.length,
|
|
746
|
+
reviewsSelected,
|
|
747
|
+
newSelected,
|
|
748
|
+
cards
|
|
749
|
+
};
|
|
750
|
+
}
|
|
751
|
+
function formatProvenance(provenance) {
|
|
752
|
+
return provenance.map((p) => {
|
|
753
|
+
const actionSymbol = p.action === "generated" ? "\u{1F3B2}" : p.action === "boosted" ? "\u2B06\uFE0F" : p.action === "penalized" ? "\u2B07\uFE0F" : "\u27A1\uFE0F";
|
|
754
|
+
return ` ${actionSymbol} ${p.strategyName}: ${p.score.toFixed(3)} - ${p.reason}`;
|
|
755
|
+
}).join("\n");
|
|
756
|
+
}
|
|
757
|
+
function printRunSummary(run) {
|
|
758
|
+
console.group(`\u{1F50D} Pipeline Run: ${run.courseId} (${run.courseName || "unnamed"})`);
|
|
759
|
+
logger.info(`Run ID: ${run.runId}`);
|
|
760
|
+
logger.info(`Time: ${run.timestamp.toISOString()}`);
|
|
761
|
+
logger.info(`Generator: ${run.generatorName} \u2192 ${run.generatedCount} candidates`);
|
|
762
|
+
if (run.generators && run.generators.length > 0) {
|
|
763
|
+
console.group("Generator breakdown:");
|
|
764
|
+
for (const g of run.generators) {
|
|
765
|
+
logger.info(
|
|
766
|
+
` ${g.name}: ${g.cardCount} cards (${g.newCount} new, ${g.reviewCount} reviews, top: ${g.topScore.toFixed(2)})`
|
|
767
|
+
);
|
|
768
|
+
}
|
|
769
|
+
console.groupEnd();
|
|
770
|
+
}
|
|
771
|
+
if (run.filters.length > 0) {
|
|
772
|
+
console.group("Filter impact:");
|
|
773
|
+
for (const f of run.filters) {
|
|
774
|
+
logger.info(` ${f.name}: \u2191${f.boosted} \u2193${f.penalized} =${f.passed} \u2715${f.removed}`);
|
|
775
|
+
}
|
|
776
|
+
console.groupEnd();
|
|
777
|
+
}
|
|
778
|
+
logger.info(
|
|
779
|
+
`Result: ${run.finalCount} cards selected (${run.newSelected} new, ${run.reviewsSelected} reviews)`
|
|
780
|
+
);
|
|
781
|
+
console.groupEnd();
|
|
782
|
+
}
|
|
783
|
+
function mountPipelineDebugger() {
|
|
784
|
+
if (typeof window === "undefined") return;
|
|
785
|
+
const win = window;
|
|
786
|
+
win.skuilder = win.skuilder || {};
|
|
787
|
+
win.skuilder.pipeline = pipelineDebugAPI;
|
|
788
|
+
}
|
|
789
|
+
var MAX_RUNS, runHistory, pipelineDebugAPI;
|
|
790
|
+
var init_PipelineDebugger = __esm({
|
|
791
|
+
"src/core/navigators/PipelineDebugger.ts"() {
|
|
792
|
+
"use strict";
|
|
793
|
+
init_logger();
|
|
794
|
+
MAX_RUNS = 10;
|
|
795
|
+
runHistory = [];
|
|
796
|
+
pipelineDebugAPI = {
|
|
797
|
+
/**
|
|
798
|
+
* Get raw run history for programmatic access.
|
|
799
|
+
*/
|
|
800
|
+
get runs() {
|
|
801
|
+
return [...runHistory];
|
|
802
|
+
},
|
|
803
|
+
/**
|
|
804
|
+
* Show summary of a specific pipeline run.
|
|
805
|
+
*/
|
|
806
|
+
showRun(idOrIndex = 0) {
|
|
807
|
+
if (runHistory.length === 0) {
|
|
808
|
+
logger.info("[Pipeline Debug] No runs captured yet.");
|
|
809
|
+
return;
|
|
810
|
+
}
|
|
811
|
+
let run;
|
|
812
|
+
if (typeof idOrIndex === "number") {
|
|
813
|
+
run = runHistory[idOrIndex];
|
|
814
|
+
if (!run) {
|
|
815
|
+
logger.info(
|
|
816
|
+
`[Pipeline Debug] No run found at index ${idOrIndex}. History length: ${runHistory.length}`
|
|
817
|
+
);
|
|
818
|
+
return;
|
|
819
|
+
}
|
|
820
|
+
} else {
|
|
821
|
+
run = runHistory.find((r) => r.runId.endsWith(idOrIndex));
|
|
822
|
+
if (!run) {
|
|
823
|
+
logger.info(`[Pipeline Debug] No run found matching ID '${idOrIndex}'.`);
|
|
824
|
+
return;
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
printRunSummary(run);
|
|
828
|
+
},
|
|
829
|
+
/**
|
|
830
|
+
* Show summary of the last pipeline run.
|
|
831
|
+
*/
|
|
832
|
+
showLastRun() {
|
|
833
|
+
this.showRun(0);
|
|
834
|
+
},
|
|
835
|
+
/**
|
|
836
|
+
* Show detailed provenance for a specific card.
|
|
837
|
+
*/
|
|
838
|
+
showCard(cardId) {
|
|
839
|
+
for (const run of runHistory) {
|
|
840
|
+
const card = run.cards.find((c) => c.cardId === cardId);
|
|
841
|
+
if (card) {
|
|
842
|
+
console.group(`\u{1F3B4} Card: ${cardId}`);
|
|
843
|
+
logger.info(`Course: ${card.courseId}`);
|
|
844
|
+
logger.info(`Origin: ${card.origin}`);
|
|
845
|
+
logger.info(`Final score: ${card.finalScore.toFixed(3)}`);
|
|
846
|
+
logger.info(`Selected: ${card.selected ? "Yes \u2705" : "No \u274C"}`);
|
|
847
|
+
logger.info("Provenance:");
|
|
848
|
+
logger.info(formatProvenance(card.provenance));
|
|
849
|
+
console.groupEnd();
|
|
850
|
+
return;
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
logger.info(`[Pipeline Debug] Card '${cardId}' not found in recent runs.`);
|
|
854
|
+
},
|
|
855
|
+
/**
|
|
856
|
+
* Explain why reviews may or may not have been selected.
|
|
857
|
+
*/
|
|
858
|
+
explainReviews() {
|
|
859
|
+
if (runHistory.length === 0) {
|
|
860
|
+
logger.info("[Pipeline Debug] No runs captured yet.");
|
|
861
|
+
return;
|
|
862
|
+
}
|
|
863
|
+
console.group("\u{1F4CB} Review Selection Analysis");
|
|
864
|
+
for (const run of runHistory) {
|
|
865
|
+
console.group(`Run: ${run.courseId} @ ${run.timestamp.toLocaleTimeString()}`);
|
|
866
|
+
const allReviews = run.cards.filter((c) => c.origin === "review");
|
|
867
|
+
const selectedReviews = allReviews.filter((c) => c.selected);
|
|
868
|
+
if (allReviews.length === 0) {
|
|
869
|
+
logger.info("\u274C No reviews were generated. Check SRS logs for why.");
|
|
870
|
+
} else if (selectedReviews.length === 0) {
|
|
871
|
+
logger.info(`\u26A0\uFE0F ${allReviews.length} reviews generated but none selected.`);
|
|
872
|
+
logger.info("Possible reasons:");
|
|
873
|
+
const topNewScore = Math.max(
|
|
874
|
+
...run.cards.filter((c) => c.origin === "new" && c.selected).map((c) => c.finalScore),
|
|
875
|
+
0
|
|
876
|
+
);
|
|
877
|
+
const topReviewScore = Math.max(...allReviews.map((c) => c.finalScore), 0);
|
|
878
|
+
if (topReviewScore < topNewScore) {
|
|
879
|
+
logger.info(
|
|
880
|
+
` - New cards scored higher (top new: ${topNewScore.toFixed(2)}, top review: ${topReviewScore.toFixed(2)})`
|
|
881
|
+
);
|
|
882
|
+
}
|
|
883
|
+
const topReview = allReviews.sort((a, b) => b.finalScore - a.finalScore)[0];
|
|
884
|
+
if (topReview) {
|
|
885
|
+
logger.info(` - Top review score: ${topReview.finalScore.toFixed(3)}`);
|
|
886
|
+
logger.info(" - Its provenance:");
|
|
887
|
+
logger.info(formatProvenance(topReview.provenance));
|
|
888
|
+
}
|
|
889
|
+
} else {
|
|
890
|
+
logger.info(`\u2705 ${selectedReviews.length}/${allReviews.length} reviews selected.`);
|
|
891
|
+
logger.info("Top selected review:");
|
|
892
|
+
const topSelected = selectedReviews.sort((a, b) => b.finalScore - a.finalScore)[0];
|
|
893
|
+
logger.info(formatProvenance(topSelected.provenance));
|
|
894
|
+
}
|
|
895
|
+
console.groupEnd();
|
|
896
|
+
}
|
|
897
|
+
console.groupEnd();
|
|
898
|
+
},
|
|
899
|
+
/**
|
|
900
|
+
* Show all runs in compact format.
|
|
901
|
+
*/
|
|
902
|
+
listRuns() {
|
|
903
|
+
if (runHistory.length === 0) {
|
|
904
|
+
logger.info("[Pipeline Debug] No runs captured yet.");
|
|
905
|
+
return;
|
|
906
|
+
}
|
|
907
|
+
console.table(
|
|
908
|
+
runHistory.map((r) => ({
|
|
909
|
+
id: r.runId.slice(-8),
|
|
910
|
+
time: r.timestamp.toLocaleTimeString(),
|
|
911
|
+
course: r.courseName || r.courseId.slice(0, 8),
|
|
912
|
+
generated: r.generatedCount,
|
|
913
|
+
selected: r.finalCount,
|
|
914
|
+
new: r.newSelected,
|
|
915
|
+
reviews: r.reviewsSelected
|
|
916
|
+
}))
|
|
917
|
+
);
|
|
918
|
+
},
|
|
919
|
+
/**
|
|
920
|
+
* Export run history as JSON for bug reports.
|
|
921
|
+
*/
|
|
922
|
+
export() {
|
|
923
|
+
const json = JSON.stringify(runHistory, null, 2);
|
|
924
|
+
logger.info("[Pipeline Debug] Run history exported. Copy the returned string or use:");
|
|
925
|
+
logger.info(" copy(window.skuilder.pipeline.export())");
|
|
926
|
+
return json;
|
|
927
|
+
},
|
|
928
|
+
/**
|
|
929
|
+
* Clear run history.
|
|
930
|
+
*/
|
|
931
|
+
clear() {
|
|
932
|
+
runHistory.length = 0;
|
|
933
|
+
logger.info("[Pipeline Debug] Run history cleared.");
|
|
934
|
+
},
|
|
935
|
+
/**
|
|
936
|
+
* Show help.
|
|
937
|
+
*/
|
|
938
|
+
help() {
|
|
939
|
+
logger.info(`
|
|
940
|
+
\u{1F527} Pipeline Debug API
|
|
941
|
+
|
|
942
|
+
Commands:
|
|
943
|
+
.showLastRun() Show summary of most recent pipeline run
|
|
944
|
+
.showRun(id|index) Show summary of a specific run (by index or ID suffix)
|
|
945
|
+
.showCard(cardId) Show provenance trail for a specific card
|
|
946
|
+
.explainReviews() Analyze why reviews were/weren't selected
|
|
947
|
+
.listRuns() List all captured runs in table format
|
|
948
|
+
.export() Export run history as JSON for bug reports
|
|
949
|
+
.clear() Clear run history
|
|
950
|
+
.runs Access raw run history array
|
|
951
|
+
.help() Show this help message
|
|
952
|
+
|
|
953
|
+
Example:
|
|
954
|
+
window.skuilder.pipeline.showLastRun()
|
|
955
|
+
window.skuilder.pipeline.showRun(1)
|
|
956
|
+
window.skuilder.pipeline.showCard('abc123')
|
|
957
|
+
`);
|
|
958
|
+
}
|
|
959
|
+
};
|
|
960
|
+
mountPipelineDebugger();
|
|
961
|
+
}
|
|
962
|
+
});
|
|
963
|
+
|
|
690
964
|
// src/core/navigators/generators/CompositeGenerator.ts
|
|
691
965
|
var CompositeGenerator_exports = {};
|
|
692
966
|
__export(CompositeGenerator_exports, {
|
|
@@ -755,6 +1029,24 @@ var init_CompositeGenerator = __esm({
|
|
|
755
1029
|
const results = await Promise.all(
|
|
756
1030
|
this.generators.map((g) => g.getWeightedCards(limit, context))
|
|
757
1031
|
);
|
|
1032
|
+
const generatorSummaries = [];
|
|
1033
|
+
results.forEach((cards, index) => {
|
|
1034
|
+
const gen = this.generators[index];
|
|
1035
|
+
const genName = gen.name || `Generator ${index}`;
|
|
1036
|
+
const newCards = cards.filter((c) => c.provenance[0]?.reason?.includes("new card"));
|
|
1037
|
+
const reviewCards = cards.filter((c) => c.provenance[0]?.reason?.includes("review"));
|
|
1038
|
+
if (cards.length > 0) {
|
|
1039
|
+
const topScore = Math.max(...cards.map((c) => c.score)).toFixed(2);
|
|
1040
|
+
const parts = [];
|
|
1041
|
+
if (newCards.length > 0) parts.push(`${newCards.length} new`);
|
|
1042
|
+
if (reviewCards.length > 0) parts.push(`${reviewCards.length} reviews`);
|
|
1043
|
+
const breakdown = parts.length > 0 ? parts.join(", ") : `${cards.length} cards`;
|
|
1044
|
+
generatorSummaries.push(`${genName}: ${breakdown} (top: ${topScore})`);
|
|
1045
|
+
} else {
|
|
1046
|
+
generatorSummaries.push(`${genName}: 0 cards`);
|
|
1047
|
+
}
|
|
1048
|
+
});
|
|
1049
|
+
logger.info(`[Composite] Generator breakdown: ${generatorSummaries.join(" | ")}`);
|
|
758
1050
|
const byCardId = /* @__PURE__ */ new Map();
|
|
759
1051
|
results.forEach((cards, index) => {
|
|
760
1052
|
const gen = this.generators[index];
|
|
@@ -872,6 +1164,7 @@ var init_elo = __esm({
|
|
|
872
1164
|
"src/core/navigators/generators/elo.ts"() {
|
|
873
1165
|
"use strict";
|
|
874
1166
|
init_navigators();
|
|
1167
|
+
init_logger();
|
|
875
1168
|
ELONavigator = class extends ContentNavigator {
|
|
876
1169
|
/** Human-readable name for CardGenerator interface */
|
|
877
1170
|
name;
|
|
@@ -931,7 +1224,16 @@ var init_elo = __esm({
|
|
|
931
1224
|
};
|
|
932
1225
|
});
|
|
933
1226
|
scored.sort((a, b) => b.score - a.score);
|
|
934
|
-
|
|
1227
|
+
const result = scored.slice(0, limit);
|
|
1228
|
+
if (result.length > 0) {
|
|
1229
|
+
const topScores = result.slice(0, 3).map((c) => c.score.toFixed(2)).join(", ");
|
|
1230
|
+
logger.info(
|
|
1231
|
+
`[ELO] Course ${this.course.getCourseID()}: ${result.length} new cards (top scores: ${topScores})`
|
|
1232
|
+
);
|
|
1233
|
+
} else {
|
|
1234
|
+
logger.info(`[ELO] Course ${this.course.getCourseID()}: No new cards available`);
|
|
1235
|
+
}
|
|
1236
|
+
return result;
|
|
935
1237
|
}
|
|
936
1238
|
};
|
|
937
1239
|
}
|
|
@@ -951,18 +1253,36 @@ __export(srs_exports, {
|
|
|
951
1253
|
default: () => SRSNavigator
|
|
952
1254
|
});
|
|
953
1255
|
import moment3 from "moment";
|
|
954
|
-
var SRSNavigator;
|
|
1256
|
+
var DEFAULT_HEALTHY_BACKLOG, MAX_BACKLOG_PRESSURE, SRSNavigator;
|
|
955
1257
|
var init_srs = __esm({
|
|
956
1258
|
"src/core/navigators/generators/srs.ts"() {
|
|
957
1259
|
"use strict";
|
|
958
1260
|
init_navigators();
|
|
959
1261
|
init_logger();
|
|
1262
|
+
DEFAULT_HEALTHY_BACKLOG = 20;
|
|
1263
|
+
MAX_BACKLOG_PRESSURE = 0.5;
|
|
960
1264
|
SRSNavigator = class extends ContentNavigator {
|
|
961
1265
|
/** Human-readable name for CardGenerator interface */
|
|
962
1266
|
name;
|
|
1267
|
+
/** Healthy backlog threshold - when exceeded, backlog pressure kicks in */
|
|
1268
|
+
healthyBacklog;
|
|
963
1269
|
constructor(user, course, strategyData) {
|
|
964
1270
|
super(user, course, strategyData);
|
|
965
1271
|
this.name = strategyData?.name || "SRS";
|
|
1272
|
+
const config = this.parseConfig(strategyData?.serializedData);
|
|
1273
|
+
this.healthyBacklog = config.healthyBacklog ?? DEFAULT_HEALTHY_BACKLOG;
|
|
1274
|
+
}
|
|
1275
|
+
/**
|
|
1276
|
+
* Parse configuration from serialized JSON.
|
|
1277
|
+
*/
|
|
1278
|
+
parseConfig(serializedData) {
|
|
1279
|
+
if (!serializedData) return {};
|
|
1280
|
+
try {
|
|
1281
|
+
return JSON.parse(serializedData);
|
|
1282
|
+
} catch {
|
|
1283
|
+
logger.warn("[SRS] Failed to parse strategy config, using defaults");
|
|
1284
|
+
return {};
|
|
1285
|
+
}
|
|
966
1286
|
}
|
|
967
1287
|
/**
|
|
968
1288
|
* Get review cards scored by urgency.
|
|
@@ -970,6 +1290,7 @@ var init_srs = __esm({
|
|
|
970
1290
|
* Score formula combines:
|
|
971
1291
|
* - Relative overdueness: hoursOverdue / intervalHours
|
|
972
1292
|
* - Interval recency: exponential decay favoring shorter intervals
|
|
1293
|
+
* - Backlog pressure: boost when due reviews exceed healthy threshold
|
|
973
1294
|
*
|
|
974
1295
|
* Cards not yet due are excluded (not scored as 0).
|
|
975
1296
|
*
|
|
@@ -983,11 +1304,32 @@ var init_srs = __esm({
|
|
|
983
1304
|
if (!this.user || !this.course) {
|
|
984
1305
|
throw new Error("SRSNavigator requires user and course to be set");
|
|
985
1306
|
}
|
|
986
|
-
const
|
|
1307
|
+
const courseId = this.course.getCourseID();
|
|
1308
|
+
const reviews = await this.user.getPendingReviews(courseId);
|
|
987
1309
|
const now = moment3.utc();
|
|
988
1310
|
const dueReviews = reviews.filter((r) => now.isAfter(moment3.utc(r.reviewTime)));
|
|
1311
|
+
const backlogPressure = this.computeBacklogPressure(dueReviews.length);
|
|
1312
|
+
if (dueReviews.length > 0) {
|
|
1313
|
+
const pressureNote = backlogPressure > 0 ? ` [backlog pressure: +${backlogPressure.toFixed(2)}]` : ` [healthy backlog]`;
|
|
1314
|
+
logger.info(
|
|
1315
|
+
`[SRS] Course ${courseId}: ${dueReviews.length} reviews due now (of ${reviews.length} scheduled)${pressureNote}`
|
|
1316
|
+
);
|
|
1317
|
+
} else if (reviews.length > 0) {
|
|
1318
|
+
const sortedByDue = [...reviews].sort(
|
|
1319
|
+
(a, b) => moment3.utc(a.reviewTime).diff(moment3.utc(b.reviewTime))
|
|
1320
|
+
);
|
|
1321
|
+
const nextDue = sortedByDue[0];
|
|
1322
|
+
const nextDueTime = moment3.utc(nextDue.reviewTime);
|
|
1323
|
+
const untilDue = moment3.duration(nextDueTime.diff(now));
|
|
1324
|
+
const untilDueStr = untilDue.asHours() < 1 ? `${Math.round(untilDue.asMinutes())}m` : untilDue.asHours() < 24 ? `${Math.round(untilDue.asHours())}h` : `${Math.round(untilDue.asDays())}d`;
|
|
1325
|
+
logger.info(
|
|
1326
|
+
`[SRS] Course ${courseId}: 0 reviews due now (${reviews.length} scheduled, next in ${untilDueStr})`
|
|
1327
|
+
);
|
|
1328
|
+
} else {
|
|
1329
|
+
logger.info(`[SRS] Course ${courseId}: No reviews scheduled`);
|
|
1330
|
+
}
|
|
989
1331
|
const scored = dueReviews.map((review) => {
|
|
990
|
-
const { score, reason } = this.computeUrgencyScore(review, now);
|
|
1332
|
+
const { score, reason } = this.computeUrgencyScore(review, now, backlogPressure);
|
|
991
1333
|
return {
|
|
992
1334
|
cardId: review.cardId,
|
|
993
1335
|
courseId: review.courseId,
|
|
@@ -1005,13 +1347,35 @@ var init_srs = __esm({
|
|
|
1005
1347
|
]
|
|
1006
1348
|
};
|
|
1007
1349
|
});
|
|
1008
|
-
logger.debug(`[srsNav] got ${scored.length} weighted cards`);
|
|
1009
1350
|
return scored.sort((a, b) => b.score - a.score).slice(0, limit);
|
|
1010
1351
|
}
|
|
1352
|
+
/**
|
|
1353
|
+
* Compute backlog pressure based on number of due reviews.
|
|
1354
|
+
*
|
|
1355
|
+
* Backlog pressure is 0 when at or below healthy threshold,
|
|
1356
|
+
* and increases linearly above it, maxing out at MAX_BACKLOG_PRESSURE.
|
|
1357
|
+
*
|
|
1358
|
+
* Examples (with default healthyBacklog=20):
|
|
1359
|
+
* - 10 due reviews → 0.00 (healthy)
|
|
1360
|
+
* - 20 due reviews → 0.00 (at threshold)
|
|
1361
|
+
* - 40 due reviews → 0.25 (2x threshold)
|
|
1362
|
+
* - 60 due reviews → 0.50 (3x threshold, maxed)
|
|
1363
|
+
*
|
|
1364
|
+
* @param dueCount - Number of reviews currently due
|
|
1365
|
+
* @returns Backlog pressure score to add to urgency (0 to MAX_BACKLOG_PRESSURE)
|
|
1366
|
+
*/
|
|
1367
|
+
computeBacklogPressure(dueCount) {
|
|
1368
|
+
if (dueCount <= this.healthyBacklog) {
|
|
1369
|
+
return 0;
|
|
1370
|
+
}
|
|
1371
|
+
const excess = dueCount - this.healthyBacklog;
|
|
1372
|
+
const pressure = excess / this.healthyBacklog * (MAX_BACKLOG_PRESSURE / 2);
|
|
1373
|
+
return Math.min(MAX_BACKLOG_PRESSURE, pressure);
|
|
1374
|
+
}
|
|
1011
1375
|
/**
|
|
1012
1376
|
* Compute urgency score for a review card.
|
|
1013
1377
|
*
|
|
1014
|
-
*
|
|
1378
|
+
* Three factors:
|
|
1015
1379
|
* 1. Relative overdueness = hoursOverdue / intervalHours
|
|
1016
1380
|
* - 2 days overdue on 3-day interval = 0.67 (urgent)
|
|
1017
1381
|
* - 2 days overdue on 180-day interval = 0.01 (not urgent)
|
|
@@ -1021,10 +1385,19 @@ var init_srs = __esm({
|
|
|
1021
1385
|
* - 30 days (720h) → ~0.56
|
|
1022
1386
|
* - 180 days → ~0.30
|
|
1023
1387
|
*
|
|
1024
|
-
*
|
|
1025
|
-
*
|
|
1388
|
+
* 3. Backlog pressure = global boost when review backlog exceeds healthy threshold
|
|
1389
|
+
* - At healthy backlog: 0
|
|
1390
|
+
* - At 2x healthy: +0.25
|
|
1391
|
+
* - At 3x+ healthy: +0.50 (max)
|
|
1392
|
+
*
|
|
1393
|
+
* Combined: base 0.5 + (urgency factors * 0.45) + backlog pressure
|
|
1394
|
+
* Result range: 0.5 to 1.0 (uncapped to allow high-urgency reviews to compete with new cards)
|
|
1395
|
+
*
|
|
1396
|
+
* @param review - The scheduled card to score
|
|
1397
|
+
* @param now - Current time
|
|
1398
|
+
* @param backlogPressure - Pre-computed backlog pressure (0 to 0.5)
|
|
1026
1399
|
*/
|
|
1027
|
-
computeUrgencyScore(review, now) {
|
|
1400
|
+
computeUrgencyScore(review, now, backlogPressure) {
|
|
1028
1401
|
const scheduledAt = moment3.utc(review.scheduledAt);
|
|
1029
1402
|
const due = moment3.utc(review.reviewTime);
|
|
1030
1403
|
const intervalHours = Math.max(1, due.diff(scheduledAt, "hours"));
|
|
@@ -1033,8 +1406,19 @@ var init_srs = __esm({
|
|
|
1033
1406
|
const recencyFactor = 0.3 + 0.7 * Math.exp(-intervalHours / 720);
|
|
1034
1407
|
const overdueContribution = Math.min(1, Math.max(0, relativeOverdue));
|
|
1035
1408
|
const urgency = overdueContribution * 0.5 + recencyFactor * 0.5;
|
|
1036
|
-
const
|
|
1037
|
-
const
|
|
1409
|
+
const baseScore = 0.5 + urgency * 0.45;
|
|
1410
|
+
const score = Math.min(1, baseScore + backlogPressure);
|
|
1411
|
+
const reasonParts = [
|
|
1412
|
+
`${Math.round(hoursOverdue)}h overdue`,
|
|
1413
|
+
`interval: ${Math.round(intervalHours)}h`,
|
|
1414
|
+
`relative: ${relativeOverdue.toFixed(2)}`,
|
|
1415
|
+
`recency: ${recencyFactor.toFixed(2)}`
|
|
1416
|
+
];
|
|
1417
|
+
if (backlogPressure > 0) {
|
|
1418
|
+
reasonParts.push(`backlog: +${backlogPressure.toFixed(2)}`);
|
|
1419
|
+
}
|
|
1420
|
+
reasonParts.push("review");
|
|
1421
|
+
const reason = reasonParts.join(", ");
|
|
1038
1422
|
return { score, reason };
|
|
1039
1423
|
}
|
|
1040
1424
|
};
|
|
@@ -2311,10 +2695,23 @@ function logTagHydration(cards, tagsByCard) {
|
|
|
2311
2695
|
`[Pipeline] Tag hydration: ${cards.length} cards, ${cardsWithTags} have tags (${totalTags} total tags) - single batch query`
|
|
2312
2696
|
);
|
|
2313
2697
|
}
|
|
2314
|
-
function logExecutionSummary(generatorName, generatedCount, filterCount, finalCount, topScores) {
|
|
2698
|
+
function logExecutionSummary(generatorName, generatedCount, filterCount, finalCount, topScores, filterImpacts) {
|
|
2315
2699
|
const scoreDisplay = topScores.length > 0 ? topScores.map((s) => s.toFixed(2)).join(", ") : "none";
|
|
2700
|
+
let filterSummary = "";
|
|
2701
|
+
if (filterImpacts.length > 0) {
|
|
2702
|
+
const impacts = filterImpacts.map((f) => {
|
|
2703
|
+
const parts = [];
|
|
2704
|
+
if (f.boosted > 0) parts.push(`+${f.boosted}`);
|
|
2705
|
+
if (f.penalized > 0) parts.push(`-${f.penalized}`);
|
|
2706
|
+
if (f.passed > 0) parts.push(`=${f.passed}`);
|
|
2707
|
+
return `${f.name}: ${parts.join("/")}`;
|
|
2708
|
+
});
|
|
2709
|
+
filterSummary = `
|
|
2710
|
+
Filter impact: ${impacts.join(", ")}`;
|
|
2711
|
+
}
|
|
2316
2712
|
logger.info(
|
|
2317
|
-
`[Pipeline] Execution: ${generatorName} produced ${generatedCount} \u2192 ${filterCount} filters \u2192 ${finalCount} results (top scores: ${scoreDisplay})`
|
|
2713
|
+
`[Pipeline] Execution: ${generatorName} produced ${generatedCount} \u2192 ${filterCount} filters \u2192 ${finalCount} results (top scores: ${scoreDisplay})` + filterSummary + `
|
|
2714
|
+
\u{1F4A1} Inspect: window.skuilder.pipeline`
|
|
2318
2715
|
);
|
|
2319
2716
|
}
|
|
2320
2717
|
function logCardProvenance(cards, maxCards = 3) {
|
|
@@ -2338,6 +2735,7 @@ var init_Pipeline = __esm({
|
|
|
2338
2735
|
init_navigators();
|
|
2339
2736
|
init_logger();
|
|
2340
2737
|
init_orchestration();
|
|
2738
|
+
init_PipelineDebugger();
|
|
2341
2739
|
Pipeline = class extends ContentNavigator {
|
|
2342
2740
|
generator;
|
|
2343
2741
|
filters;
|
|
@@ -2385,12 +2783,49 @@ var init_Pipeline = __esm({
|
|
|
2385
2783
|
);
|
|
2386
2784
|
let cards = await this.generator.getWeightedCards(fetchLimit, context);
|
|
2387
2785
|
const generatedCount = cards.length;
|
|
2786
|
+
let generatorSummaries;
|
|
2787
|
+
if (this.generator.generators) {
|
|
2788
|
+
const genMap = /* @__PURE__ */ new Map();
|
|
2789
|
+
for (const card of cards) {
|
|
2790
|
+
const firstProv = card.provenance[0];
|
|
2791
|
+
if (firstProv) {
|
|
2792
|
+
const genName = firstProv.strategyName;
|
|
2793
|
+
if (!genMap.has(genName)) {
|
|
2794
|
+
genMap.set(genName, { cards: [] });
|
|
2795
|
+
}
|
|
2796
|
+
genMap.get(genName).cards.push(card);
|
|
2797
|
+
}
|
|
2798
|
+
}
|
|
2799
|
+
generatorSummaries = Array.from(genMap.entries()).map(([name, data]) => {
|
|
2800
|
+
const newCards = data.cards.filter((c) => c.provenance[0]?.reason?.includes("new card"));
|
|
2801
|
+
const reviewCards = data.cards.filter((c) => c.provenance[0]?.reason?.includes("review"));
|
|
2802
|
+
return {
|
|
2803
|
+
name,
|
|
2804
|
+
cardCount: data.cards.length,
|
|
2805
|
+
newCount: newCards.length,
|
|
2806
|
+
reviewCount: reviewCards.length,
|
|
2807
|
+
topScore: Math.max(...data.cards.map((c) => c.score), 0)
|
|
2808
|
+
};
|
|
2809
|
+
});
|
|
2810
|
+
}
|
|
2388
2811
|
logger.debug(`[Pipeline] Generator returned ${generatedCount} candidates`);
|
|
2389
2812
|
cards = await this.hydrateTags(cards);
|
|
2813
|
+
const allCardsBeforeFiltering = [...cards];
|
|
2814
|
+
const filterImpacts = [];
|
|
2390
2815
|
for (const filter of this.filters) {
|
|
2391
2816
|
const beforeCount = cards.length;
|
|
2817
|
+
const beforeScores = new Map(cards.map((c) => [c.cardId, c.score]));
|
|
2392
2818
|
cards = await filter.transform(cards, context);
|
|
2393
|
-
|
|
2819
|
+
let boosted = 0, penalized = 0, passed = 0;
|
|
2820
|
+
const removed = beforeCount - cards.length;
|
|
2821
|
+
for (const card of cards) {
|
|
2822
|
+
const before = beforeScores.get(card.cardId) ?? 0;
|
|
2823
|
+
if (card.score > before) boosted++;
|
|
2824
|
+
else if (card.score < before) penalized++;
|
|
2825
|
+
else passed++;
|
|
2826
|
+
}
|
|
2827
|
+
filterImpacts.push({ name: filter.name, boosted, penalized, passed, removed });
|
|
2828
|
+
logger.debug(`[Pipeline] Filter '${filter.name}': ${beforeScores.size} \u2192 ${cards.length} cards (\u2191${boosted} \u2193${penalized} =${passed})`);
|
|
2394
2829
|
}
|
|
2395
2830
|
cards = cards.filter((c) => c.score > 0);
|
|
2396
2831
|
cards.sort((a, b) => b.score - a.score);
|
|
@@ -2401,9 +2836,26 @@ var init_Pipeline = __esm({
|
|
|
2401
2836
|
generatedCount,
|
|
2402
2837
|
this.filters.length,
|
|
2403
2838
|
result.length,
|
|
2404
|
-
topScores
|
|
2839
|
+
topScores,
|
|
2840
|
+
filterImpacts
|
|
2405
2841
|
);
|
|
2406
2842
|
logCardProvenance(result, 3);
|
|
2843
|
+
try {
|
|
2844
|
+
const courseName = await this.course?.getCourseConfig().then((c) => c.name).catch(() => void 0);
|
|
2845
|
+
const report = buildRunReport(
|
|
2846
|
+
this.course?.getCourseID() || "unknown",
|
|
2847
|
+
courseName,
|
|
2848
|
+
this.generator.name,
|
|
2849
|
+
generatorSummaries,
|
|
2850
|
+
generatedCount,
|
|
2851
|
+
filterImpacts,
|
|
2852
|
+
allCardsBeforeFiltering,
|
|
2853
|
+
result
|
|
2854
|
+
);
|
|
2855
|
+
captureRun(report);
|
|
2856
|
+
} catch (e) {
|
|
2857
|
+
logger.debug(`[Pipeline] Failed to capture debug run: ${e}`);
|
|
2858
|
+
}
|
|
2407
2859
|
return result;
|
|
2408
2860
|
}
|
|
2409
2861
|
/**
|
|
@@ -2493,6 +2945,56 @@ var init_Pipeline = __esm({
|
|
|
2493
2945
|
}
|
|
2494
2946
|
});
|
|
2495
2947
|
|
|
2948
|
+
// src/core/navigators/defaults.ts
|
|
2949
|
+
var defaults_exports = {};
|
|
2950
|
+
__export(defaults_exports, {
|
|
2951
|
+
createDefaultEloStrategy: () => createDefaultEloStrategy,
|
|
2952
|
+
createDefaultPipeline: () => createDefaultPipeline,
|
|
2953
|
+
createDefaultSrsStrategy: () => createDefaultSrsStrategy
|
|
2954
|
+
});
|
|
2955
|
+
function createDefaultEloStrategy(courseId) {
|
|
2956
|
+
return {
|
|
2957
|
+
_id: "NAVIGATION_STRATEGY-ELO-default",
|
|
2958
|
+
docType: "NAVIGATION_STRATEGY" /* NAVIGATION_STRATEGY */,
|
|
2959
|
+
name: "ELO (default)",
|
|
2960
|
+
description: "Default ELO-based navigation strategy for new cards",
|
|
2961
|
+
implementingClass: "elo" /* ELO */,
|
|
2962
|
+
course: courseId,
|
|
2963
|
+
serializedData: ""
|
|
2964
|
+
};
|
|
2965
|
+
}
|
|
2966
|
+
function createDefaultSrsStrategy(courseId) {
|
|
2967
|
+
return {
|
|
2968
|
+
_id: "NAVIGATION_STRATEGY-SRS-default",
|
|
2969
|
+
docType: "NAVIGATION_STRATEGY" /* NAVIGATION_STRATEGY */,
|
|
2970
|
+
name: "SRS (default)",
|
|
2971
|
+
description: "Default SRS-based navigation strategy for reviews",
|
|
2972
|
+
implementingClass: "srs" /* SRS */,
|
|
2973
|
+
course: courseId,
|
|
2974
|
+
serializedData: ""
|
|
2975
|
+
};
|
|
2976
|
+
}
|
|
2977
|
+
function createDefaultPipeline(user, course) {
|
|
2978
|
+
const courseId = course.getCourseID();
|
|
2979
|
+
const eloNavigator = new ELONavigator(user, course, createDefaultEloStrategy(courseId));
|
|
2980
|
+
const srsNavigator = new SRSNavigator(user, course, createDefaultSrsStrategy(courseId));
|
|
2981
|
+
const compositeGenerator = new CompositeGenerator([eloNavigator, srsNavigator]);
|
|
2982
|
+
const eloDistanceFilter = createEloDistanceFilter();
|
|
2983
|
+
return new Pipeline(compositeGenerator, [eloDistanceFilter], user, course);
|
|
2984
|
+
}
|
|
2985
|
+
var init_defaults = __esm({
|
|
2986
|
+
"src/core/navigators/defaults.ts"() {
|
|
2987
|
+
"use strict";
|
|
2988
|
+
init_navigators();
|
|
2989
|
+
init_Pipeline();
|
|
2990
|
+
init_CompositeGenerator();
|
|
2991
|
+
init_elo();
|
|
2992
|
+
init_srs();
|
|
2993
|
+
init_eloDistance();
|
|
2994
|
+
init_types_legacy();
|
|
2995
|
+
}
|
|
2996
|
+
});
|
|
2997
|
+
|
|
2496
2998
|
// src/core/navigators/PipelineAssembler.ts
|
|
2497
2999
|
var PipelineAssembler_exports = {};
|
|
2498
3000
|
__export(PipelineAssembler_exports, {
|
|
@@ -2505,9 +3007,9 @@ var init_PipelineAssembler = __esm({
|
|
|
2505
3007
|
init_navigators();
|
|
2506
3008
|
init_WeightedFilter();
|
|
2507
3009
|
init_Pipeline();
|
|
2508
|
-
init_types_legacy();
|
|
2509
3010
|
init_logger();
|
|
2510
3011
|
init_CompositeGenerator();
|
|
3012
|
+
init_defaults();
|
|
2511
3013
|
PipelineAssembler = class {
|
|
2512
3014
|
/**
|
|
2513
3015
|
* Assembles a navigation pipeline from strategy documents.
|
|
@@ -2546,9 +3048,11 @@ var init_PipelineAssembler = __esm({
|
|
|
2546
3048
|
if (generatorStrategies.length === 0) {
|
|
2547
3049
|
if (filterStrategies.length > 0) {
|
|
2548
3050
|
logger.debug(
|
|
2549
|
-
"[PipelineAssembler] No generator found, using default ELO with configured filters"
|
|
3051
|
+
"[PipelineAssembler] No generator found, using default ELO and SRS with configured filters"
|
|
2550
3052
|
);
|
|
2551
|
-
|
|
3053
|
+
const courseId = course.getCourseID();
|
|
3054
|
+
generatorStrategies.push(createDefaultEloStrategy(courseId));
|
|
3055
|
+
generatorStrategies.push(createDefaultSrsStrategy(courseId));
|
|
2552
3056
|
} else {
|
|
2553
3057
|
warnings.push("No generator strategy found");
|
|
2554
3058
|
return {
|
|
@@ -2609,75 +3113,10 @@ var init_PipelineAssembler = __esm({
|
|
|
2609
3113
|
warnings
|
|
2610
3114
|
};
|
|
2611
3115
|
}
|
|
2612
|
-
/**
|
|
2613
|
-
* Creates a default ELO generator strategy.
|
|
2614
|
-
* Used when filters are configured but no generator is specified.
|
|
2615
|
-
*/
|
|
2616
|
-
makeDefaultEloStrategy(courseId) {
|
|
2617
|
-
return {
|
|
2618
|
-
_id: "NAVIGATION_STRATEGY-ELO-default",
|
|
2619
|
-
course: courseId,
|
|
2620
|
-
docType: "NAVIGATION_STRATEGY" /* NAVIGATION_STRATEGY */,
|
|
2621
|
-
name: "ELO (default)",
|
|
2622
|
-
description: "Default ELO-based generator",
|
|
2623
|
-
implementingClass: "elo" /* ELO */,
|
|
2624
|
-
serializedData: ""
|
|
2625
|
-
};
|
|
2626
|
-
}
|
|
2627
3116
|
};
|
|
2628
3117
|
}
|
|
2629
3118
|
});
|
|
2630
3119
|
|
|
2631
|
-
// src/core/navigators/defaults.ts
|
|
2632
|
-
var defaults_exports = {};
|
|
2633
|
-
__export(defaults_exports, {
|
|
2634
|
-
createDefaultEloStrategy: () => createDefaultEloStrategy,
|
|
2635
|
-
createDefaultPipeline: () => createDefaultPipeline,
|
|
2636
|
-
createDefaultSrsStrategy: () => createDefaultSrsStrategy
|
|
2637
|
-
});
|
|
2638
|
-
function createDefaultEloStrategy(courseId) {
|
|
2639
|
-
return {
|
|
2640
|
-
_id: "NAVIGATION_STRATEGY-ELO-default",
|
|
2641
|
-
docType: "NAVIGATION_STRATEGY" /* NAVIGATION_STRATEGY */,
|
|
2642
|
-
name: "ELO (default)",
|
|
2643
|
-
description: "Default ELO-based navigation strategy for new cards",
|
|
2644
|
-
implementingClass: "elo" /* ELO */,
|
|
2645
|
-
course: courseId,
|
|
2646
|
-
serializedData: ""
|
|
2647
|
-
};
|
|
2648
|
-
}
|
|
2649
|
-
function createDefaultSrsStrategy(courseId) {
|
|
2650
|
-
return {
|
|
2651
|
-
_id: "NAVIGATION_STRATEGY-SRS-default",
|
|
2652
|
-
docType: "NAVIGATION_STRATEGY" /* NAVIGATION_STRATEGY */,
|
|
2653
|
-
name: "SRS (default)",
|
|
2654
|
-
description: "Default SRS-based navigation strategy for reviews",
|
|
2655
|
-
implementingClass: "srs" /* SRS */,
|
|
2656
|
-
course: courseId,
|
|
2657
|
-
serializedData: ""
|
|
2658
|
-
};
|
|
2659
|
-
}
|
|
2660
|
-
function createDefaultPipeline(user, course) {
|
|
2661
|
-
const courseId = course.getCourseID();
|
|
2662
|
-
const eloNavigator = new ELONavigator(user, course, createDefaultEloStrategy(courseId));
|
|
2663
|
-
const srsNavigator = new SRSNavigator(user, course, createDefaultSrsStrategy(courseId));
|
|
2664
|
-
const compositeGenerator = new CompositeGenerator([eloNavigator, srsNavigator]);
|
|
2665
|
-
const eloDistanceFilter = createEloDistanceFilter();
|
|
2666
|
-
return new Pipeline(compositeGenerator, [eloDistanceFilter], user, course);
|
|
2667
|
-
}
|
|
2668
|
-
var init_defaults = __esm({
|
|
2669
|
-
"src/core/navigators/defaults.ts"() {
|
|
2670
|
-
"use strict";
|
|
2671
|
-
init_navigators();
|
|
2672
|
-
init_Pipeline();
|
|
2673
|
-
init_CompositeGenerator();
|
|
2674
|
-
init_elo();
|
|
2675
|
-
init_srs();
|
|
2676
|
-
init_eloDistance();
|
|
2677
|
-
init_types_legacy();
|
|
2678
|
-
}
|
|
2679
|
-
});
|
|
2680
|
-
|
|
2681
3120
|
// import("./**/*") in src/core/navigators/index.ts
|
|
2682
3121
|
var globImport;
|
|
2683
3122
|
var init_3 = __esm({
|
|
@@ -2685,6 +3124,7 @@ var init_3 = __esm({
|
|
|
2685
3124
|
globImport = __glob({
|
|
2686
3125
|
"./Pipeline.ts": () => Promise.resolve().then(() => (init_Pipeline(), Pipeline_exports)),
|
|
2687
3126
|
"./PipelineAssembler.ts": () => Promise.resolve().then(() => (init_PipelineAssembler(), PipelineAssembler_exports)),
|
|
3127
|
+
"./PipelineDebugger.ts": () => Promise.resolve().then(() => (init_PipelineDebugger(), PipelineDebugger_exports)),
|
|
2688
3128
|
"./defaults.ts": () => Promise.resolve().then(() => (init_defaults(), defaults_exports)),
|
|
2689
3129
|
"./filters/WeightedFilter.ts": () => Promise.resolve().then(() => (init_WeightedFilter(), WeightedFilter_exports)),
|
|
2690
3130
|
"./filters/eloDistance.ts": () => Promise.resolve().then(() => (init_eloDistance(), eloDistance_exports)),
|
|
@@ -2720,6 +3160,8 @@ __export(navigators_exports, {
|
|
|
2720
3160
|
initializeNavigatorRegistry: () => initializeNavigatorRegistry,
|
|
2721
3161
|
isFilter: () => isFilter,
|
|
2722
3162
|
isGenerator: () => isGenerator,
|
|
3163
|
+
mountPipelineDebugger: () => mountPipelineDebugger,
|
|
3164
|
+
pipelineDebugAPI: () => pipelineDebugAPI,
|
|
2723
3165
|
registerNavigator: () => registerNavigator
|
|
2724
3166
|
});
|
|
2725
3167
|
function registerNavigator(implementingClass, constructor) {
|
|
@@ -2786,6 +3228,7 @@ var navigatorRegistry, Navigators, NavigatorRole, NavigatorRoles, ContentNavigat
|
|
|
2786
3228
|
var init_navigators = __esm({
|
|
2787
3229
|
"src/core/navigators/index.ts"() {
|
|
2788
3230
|
"use strict";
|
|
3231
|
+
init_PipelineDebugger();
|
|
2789
3232
|
init_logger();
|
|
2790
3233
|
init_();
|
|
2791
3234
|
init_2();
|
|
@@ -5387,7 +5830,9 @@ export {
|
|
|
5387
5830
|
isQuestionRecord,
|
|
5388
5831
|
isReview,
|
|
5389
5832
|
log,
|
|
5833
|
+
mountPipelineDebugger,
|
|
5390
5834
|
parseCardHistoryID,
|
|
5835
|
+
pipelineDebugAPI,
|
|
5391
5836
|
recordUserOutcome,
|
|
5392
5837
|
registerNavigator,
|
|
5393
5838
|
runPeriodUpdate,
|