@vue-skuilder/db 0.1.32-c → 0.1.32-e
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/{dataLayerProvider-BAn-LRh5.d.ts → contentSource-BMlMwSiG.d.cts} +202 -626
- package/dist/{dataLayerProvider-BJqBlMIl.d.cts → contentSource-Ht3N2f-y.d.ts} +202 -626
- package/dist/core/index.d.cts +23 -84
- package/dist/core/index.d.ts +23 -84
- package/dist/core/index.js +476 -1819
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.mjs +456 -1803
- package/dist/core/index.mjs.map +1 -1
- package/dist/dataLayerProvider-BEqB8VBR.d.cts +67 -0
- package/dist/dataLayerProvider-DObSXjnf.d.ts +67 -0
- package/dist/impl/couch/index.d.cts +5 -5
- package/dist/impl/couch/index.d.ts +5 -5
- package/dist/impl/couch/index.js +484 -1827
- package/dist/impl/couch/index.js.map +1 -1
- package/dist/impl/couch/index.mjs +460 -1807
- package/dist/impl/couch/index.mjs.map +1 -1
- package/dist/impl/static/index.d.cts +5 -4
- package/dist/impl/static/index.d.ts +5 -4
- package/dist/impl/static/index.js +458 -1801
- package/dist/impl/static/index.js.map +1 -1
- package/dist/impl/static/index.mjs +437 -1784
- package/dist/impl/static/index.mjs.map +1 -1
- package/dist/{index-X6wHrURm.d.ts → index-BWvO-_rJ.d.ts} +1 -1
- package/dist/{index-m8MMGxxR.d.cts → index-Ba7hYbHj.d.cts} +1 -1
- package/dist/index.d.cts +461 -11
- package/dist/index.d.ts +461 -11
- package/dist/index.js +9239 -9159
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +9129 -9049
- package/dist/index.mjs.map +1 -1
- package/dist/{types-DZ5dUqbL.d.ts → types-CJrLM1Ew.d.ts} +1 -1
- package/dist/{types-ZL8tOPQZ.d.cts → types-W8n-B6HG.d.cts} +1 -1
- package/dist/{types-legacy-C7r0T4OV.d.cts → types-legacy-JXDxinpU.d.cts} +1 -1
- package/dist/{types-legacy-C7r0T4OV.d.ts → types-legacy-JXDxinpU.d.ts} +1 -1
- package/dist/util/packer/index.d.cts +3 -3
- package/dist/util/packer/index.d.ts +3 -3
- package/package.json +2 -2
- package/src/core/interfaces/contentSource.ts +2 -3
- package/src/core/navigators/Pipeline.ts +60 -6
- package/src/core/navigators/PipelineDebugger.ts +103 -0
- package/src/core/navigators/filters/hierarchyDefinition.ts +2 -1
- package/src/core/navigators/filters/interferenceMitigator.ts +2 -1
- package/src/core/navigators/filters/relativePriority.ts +2 -1
- package/src/core/navigators/filters/userTagPreference.ts +2 -1
- package/src/core/navigators/generators/CompositeGenerator.ts +58 -5
- package/src/core/navigators/generators/elo.ts +7 -7
- package/src/core/navigators/generators/prescribed.ts +124 -35
- package/src/core/navigators/generators/srs.ts +3 -4
- package/src/core/navigators/generators/types.ts +48 -2
- package/src/core/navigators/index.ts +3 -3
- package/src/impl/couch/classroomDB.ts +4 -3
- package/src/impl/couch/courseDB.ts +3 -3
- package/src/impl/static/courseDB.ts +3 -3
- package/src/study/SessionController.ts +5 -27
- package/src/study/TagFilteredContentSource.ts +4 -3
|
@@ -718,6 +718,81 @@ var init_PipelineDebugger = __esm({
|
|
|
718
718
|
}
|
|
719
719
|
console.groupEnd();
|
|
720
720
|
},
|
|
721
|
+
/**
|
|
722
|
+
* Show prescribed-related cards from the most recent run.
|
|
723
|
+
*
|
|
724
|
+
* Highlights:
|
|
725
|
+
* - cards directly generated by the prescribed strategy
|
|
726
|
+
* - blocked prescribed targets mentioned in provenance
|
|
727
|
+
* - support tags resolved for blocked targets
|
|
728
|
+
*
|
|
729
|
+
* @param groupId - Optional prescribed group ID filter (e.g. 'intro-core')
|
|
730
|
+
*/
|
|
731
|
+
showPrescribed(groupId) {
|
|
732
|
+
if (runHistory.length === 0) {
|
|
733
|
+
logger.info("[Pipeline Debug] No runs captured yet.");
|
|
734
|
+
return;
|
|
735
|
+
}
|
|
736
|
+
const run = runHistory[0];
|
|
737
|
+
const prescribedCards = run.cards.filter(
|
|
738
|
+
(c) => c.provenance.some((p) => p.strategy === "prescribed")
|
|
739
|
+
);
|
|
740
|
+
console.group(`\u{1F9ED} Prescribed Debug (${run.courseId})`);
|
|
741
|
+
if (prescribedCards.length === 0) {
|
|
742
|
+
logger.info("No prescribed-generated cards were present in the most recent run.");
|
|
743
|
+
console.groupEnd();
|
|
744
|
+
return;
|
|
745
|
+
}
|
|
746
|
+
const rows = prescribedCards.map((card) => {
|
|
747
|
+
const prescribedProv = card.provenance.find((p) => p.strategy === "prescribed");
|
|
748
|
+
const reason = prescribedProv?.reason ?? "";
|
|
749
|
+
const parsedGroup = reason.match(/group=([^;]+)/)?.[1] ?? "unknown";
|
|
750
|
+
const mode = reason.match(/mode=([^;]+)/)?.[1] ?? "unknown";
|
|
751
|
+
const blocked = reason.match(/blocked=([^;]+)/)?.[1] ?? "unknown";
|
|
752
|
+
const blockedTargets = reason.match(/blockedTargets=([^;]+)/)?.[1] ?? "none";
|
|
753
|
+
const supportTags = reason.match(/supportTags=([^;]+)/)?.[1] ?? "none";
|
|
754
|
+
const multiplier = reason.match(/multiplier=([^;]+)/)?.[1] ?? "unknown";
|
|
755
|
+
return {
|
|
756
|
+
group: parsedGroup,
|
|
757
|
+
mode,
|
|
758
|
+
cardId: card.cardId,
|
|
759
|
+
selected: card.selected ? "yes" : "no",
|
|
760
|
+
finalScore: card.finalScore.toFixed(3),
|
|
761
|
+
blocked,
|
|
762
|
+
blockedTargets,
|
|
763
|
+
supportTags,
|
|
764
|
+
multiplier
|
|
765
|
+
};
|
|
766
|
+
}).filter((row) => !groupId || row.group === groupId).sort((a, b) => Number(b.finalScore) - Number(a.finalScore));
|
|
767
|
+
if (rows.length === 0) {
|
|
768
|
+
logger.info(
|
|
769
|
+
`[Pipeline Debug] No prescribed cards matched group '${groupId}' in the most recent run.`
|
|
770
|
+
);
|
|
771
|
+
console.groupEnd();
|
|
772
|
+
return;
|
|
773
|
+
}
|
|
774
|
+
console.table(rows);
|
|
775
|
+
const selectedRows = rows.filter((r) => r.selected === "yes");
|
|
776
|
+
const blockedTargetSet = /* @__PURE__ */ new Set();
|
|
777
|
+
const supportTagSet = /* @__PURE__ */ new Set();
|
|
778
|
+
for (const row of rows) {
|
|
779
|
+
if (row.blockedTargets && row.blockedTargets !== "none") {
|
|
780
|
+
row.blockedTargets.split("|").filter(Boolean).forEach((t) => blockedTargetSet.add(t));
|
|
781
|
+
}
|
|
782
|
+
if (row.supportTags && row.supportTags !== "none") {
|
|
783
|
+
row.supportTags.split("|").filter(Boolean).forEach((t) => supportTagSet.add(t));
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
logger.info(`Prescribed cards in run: ${rows.length}`);
|
|
787
|
+
logger.info(`Selected prescribed cards: ${selectedRows.length}`);
|
|
788
|
+
logger.info(
|
|
789
|
+
`Blocked prescribed targets referenced: ${blockedTargetSet.size > 0 ? [...blockedTargetSet].join(", ") : "none"}`
|
|
790
|
+
);
|
|
791
|
+
logger.info(
|
|
792
|
+
`Resolved support tags referenced: ${supportTagSet.size > 0 ? [...supportTagSet].join(", ") : "none"}`
|
|
793
|
+
);
|
|
794
|
+
console.groupEnd();
|
|
795
|
+
},
|
|
721
796
|
/**
|
|
722
797
|
* Show all runs in compact format.
|
|
723
798
|
*/
|
|
@@ -866,6 +941,7 @@ Commands:
|
|
|
866
941
|
.diagnoseCardSpace() Scan full card space through filters (async)
|
|
867
942
|
.showRegistry() Show navigator registry (classes + roles)
|
|
868
943
|
.showStrategies() Show registry + strategy mapping from last run
|
|
944
|
+
.showPrescribed(id?) Show prescribed-generated cards and blocked/support details from last run
|
|
869
945
|
.listRuns() List all captured runs in table format
|
|
870
946
|
.export() Export run history as JSON for bug reports
|
|
871
947
|
.clear() Clear run history
|
|
@@ -889,6 +965,44 @@ __export(CompositeGenerator_exports, {
|
|
|
889
965
|
AggregationMode: () => AggregationMode,
|
|
890
966
|
default: () => CompositeGenerator
|
|
891
967
|
});
|
|
968
|
+
function mergeHints(allHints) {
|
|
969
|
+
const defined = allHints.filter((h) => h !== void 0);
|
|
970
|
+
if (defined.length === 0) return void 0;
|
|
971
|
+
const merged = {};
|
|
972
|
+
const boostTags = {};
|
|
973
|
+
for (const hints of defined) {
|
|
974
|
+
for (const [pattern, factor] of Object.entries(hints.boostTags ?? {})) {
|
|
975
|
+
boostTags[pattern] = (boostTags[pattern] ?? 1) * factor;
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
if (Object.keys(boostTags).length > 0) {
|
|
979
|
+
merged.boostTags = boostTags;
|
|
980
|
+
}
|
|
981
|
+
const boostCards = {};
|
|
982
|
+
for (const hints of defined) {
|
|
983
|
+
for (const [pattern, factor] of Object.entries(hints.boostCards ?? {})) {
|
|
984
|
+
boostCards[pattern] = (boostCards[pattern] ?? 1) * factor;
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
if (Object.keys(boostCards).length > 0) {
|
|
988
|
+
merged.boostCards = boostCards;
|
|
989
|
+
}
|
|
990
|
+
const concatUnique = (field) => {
|
|
991
|
+
const values = defined.flatMap((h) => h[field] ?? []);
|
|
992
|
+
if (values.length > 0) {
|
|
993
|
+
merged[field] = [...new Set(values)];
|
|
994
|
+
}
|
|
995
|
+
};
|
|
996
|
+
concatUnique("requireTags");
|
|
997
|
+
concatUnique("requireCards");
|
|
998
|
+
concatUnique("excludeTags");
|
|
999
|
+
concatUnique("excludeCards");
|
|
1000
|
+
const labels = defined.map((h) => h._label).filter(Boolean);
|
|
1001
|
+
if (labels.length > 0) {
|
|
1002
|
+
merged._label = labels.join("; ");
|
|
1003
|
+
}
|
|
1004
|
+
return Object.keys(merged).length > 0 ? merged : void 0;
|
|
1005
|
+
}
|
|
892
1006
|
var AggregationMode, DEFAULT_AGGREGATION_MODE, FREQUENCY_BOOST_FACTOR, CompositeGenerator;
|
|
893
1007
|
var init_CompositeGenerator = __esm({
|
|
894
1008
|
"src/core/navigators/generators/CompositeGenerator.ts"() {
|
|
@@ -952,17 +1066,18 @@ var init_CompositeGenerator = __esm({
|
|
|
952
1066
|
this.generators.map((g) => g.getWeightedCards(limit, context))
|
|
953
1067
|
);
|
|
954
1068
|
const generatorSummaries = [];
|
|
955
|
-
results.forEach((
|
|
1069
|
+
results.forEach((result, index) => {
|
|
1070
|
+
const cards2 = result.cards;
|
|
956
1071
|
const gen = this.generators[index];
|
|
957
1072
|
const genName = gen.name || `Generator ${index}`;
|
|
958
|
-
const newCards =
|
|
959
|
-
const reviewCards =
|
|
960
|
-
if (
|
|
961
|
-
const topScore = Math.max(...
|
|
1073
|
+
const newCards = cards2.filter((c) => c.provenance[0]?.reason?.includes("new card"));
|
|
1074
|
+
const reviewCards = cards2.filter((c) => c.provenance[0]?.reason?.includes("review"));
|
|
1075
|
+
if (cards2.length > 0) {
|
|
1076
|
+
const topScore = Math.max(...cards2.map((c) => c.score)).toFixed(2);
|
|
962
1077
|
const parts = [];
|
|
963
1078
|
if (newCards.length > 0) parts.push(`${newCards.length} new`);
|
|
964
1079
|
if (reviewCards.length > 0) parts.push(`${reviewCards.length} reviews`);
|
|
965
|
-
const breakdown = parts.length > 0 ? parts.join(", ") : `${
|
|
1080
|
+
const breakdown = parts.length > 0 ? parts.join(", ") : `${cards2.length} cards`;
|
|
966
1081
|
generatorSummaries.push(`${genName}: ${breakdown} (top: ${topScore})`);
|
|
967
1082
|
} else {
|
|
968
1083
|
generatorSummaries.push(`${genName}: 0 cards`);
|
|
@@ -970,7 +1085,8 @@ var init_CompositeGenerator = __esm({
|
|
|
970
1085
|
});
|
|
971
1086
|
logger.info(`[Composite] Generator breakdown: ${generatorSummaries.join(" | ")}`);
|
|
972
1087
|
const byCardId = /* @__PURE__ */ new Map();
|
|
973
|
-
results.forEach((
|
|
1088
|
+
results.forEach((result, index) => {
|
|
1089
|
+
const cards2 = result.cards;
|
|
974
1090
|
const gen = this.generators[index];
|
|
975
1091
|
let weight = gen.learnable?.weight ?? 1;
|
|
976
1092
|
let deviation;
|
|
@@ -981,7 +1097,7 @@ var init_CompositeGenerator = __esm({
|
|
|
981
1097
|
deviation = context.orchestration.getDeviation(strategyId);
|
|
982
1098
|
}
|
|
983
1099
|
}
|
|
984
|
-
for (const card of
|
|
1100
|
+
for (const card of cards2) {
|
|
985
1101
|
if (card.provenance.length > 0) {
|
|
986
1102
|
card.provenance[0].effectiveWeight = weight;
|
|
987
1103
|
card.provenance[0].deviation = deviation;
|
|
@@ -993,15 +1109,15 @@ var init_CompositeGenerator = __esm({
|
|
|
993
1109
|
});
|
|
994
1110
|
const merged = [];
|
|
995
1111
|
for (const [, items] of byCardId) {
|
|
996
|
-
const
|
|
1112
|
+
const cards2 = items.map((i) => i.card);
|
|
997
1113
|
const aggregatedScore = this.aggregateScores(items);
|
|
998
1114
|
const finalScore = Math.min(1, aggregatedScore);
|
|
999
|
-
const mergedProvenance =
|
|
1000
|
-
const initialScore =
|
|
1115
|
+
const mergedProvenance = cards2.flatMap((c) => c.provenance);
|
|
1116
|
+
const initialScore = cards2[0].score;
|
|
1001
1117
|
const action = finalScore > initialScore ? "boosted" : finalScore < initialScore ? "penalized" : "passed";
|
|
1002
1118
|
const reason = this.buildAggregationReason(items, finalScore);
|
|
1003
1119
|
merged.push({
|
|
1004
|
-
...
|
|
1120
|
+
...cards2[0],
|
|
1005
1121
|
score: finalScore,
|
|
1006
1122
|
provenance: [
|
|
1007
1123
|
...mergedProvenance,
|
|
@@ -1016,7 +1132,9 @@ var init_CompositeGenerator = __esm({
|
|
|
1016
1132
|
]
|
|
1017
1133
|
});
|
|
1018
1134
|
}
|
|
1019
|
-
|
|
1135
|
+
const cards = merged.sort((a, b) => b.score - a.score).slice(0, limit);
|
|
1136
|
+
const hints = mergeHints(results.map((result) => result.hints));
|
|
1137
|
+
return { cards, hints };
|
|
1020
1138
|
}
|
|
1021
1139
|
/**
|
|
1022
1140
|
* Build human-readable reason for score aggregation.
|
|
@@ -1147,16 +1265,16 @@ var init_elo = __esm({
|
|
|
1147
1265
|
};
|
|
1148
1266
|
});
|
|
1149
1267
|
scored.sort((a, b) => b.score - a.score);
|
|
1150
|
-
const
|
|
1151
|
-
if (
|
|
1152
|
-
const topScores =
|
|
1268
|
+
const cards = scored.slice(0, limit);
|
|
1269
|
+
if (cards.length > 0) {
|
|
1270
|
+
const topScores = cards.slice(0, 3).map((c) => c.score.toFixed(2)).join(", ");
|
|
1153
1271
|
logger.info(
|
|
1154
|
-
`[ELO] Course ${this.course.getCourseID()}: ${
|
|
1272
|
+
`[ELO] Course ${this.course.getCourseID()}: ${cards.length} new cards (top scores: ${topScores})`
|
|
1155
1273
|
);
|
|
1156
1274
|
} else {
|
|
1157
1275
|
logger.info(`[ELO] Course ${this.course.getCourseID()}: No new cards available`);
|
|
1158
1276
|
}
|
|
1159
|
-
return
|
|
1277
|
+
return { cards };
|
|
1160
1278
|
}
|
|
1161
1279
|
};
|
|
1162
1280
|
}
|
|
@@ -1193,7 +1311,7 @@ function matchesTagPattern(tag, pattern) {
|
|
|
1193
1311
|
function pickTopByScore(cards, limit) {
|
|
1194
1312
|
return [...cards].sort((a, b) => b.score - a.score || a.cardId.localeCompare(b.cardId)).slice(0, limit);
|
|
1195
1313
|
}
|
|
1196
|
-
var DEFAULT_FRESHNESS_WINDOW, DEFAULT_MAX_DIRECT_PER_RUN, DEFAULT_MAX_SUPPORT_PER_RUN, DEFAULT_HIERARCHY_DEPTH, DEFAULT_MIN_COUNT, BASE_TARGET_SCORE, BASE_SUPPORT_SCORE, MAX_TARGET_MULTIPLIER, MAX_SUPPORT_MULTIPLIER, LOCKED_TAG_PREFIXES, LESSON_GATE_PENALTY_TAG_HINT, PrescribedCardsGenerator;
|
|
1314
|
+
var DEFAULT_FRESHNESS_WINDOW, DEFAULT_MAX_DIRECT_PER_RUN, DEFAULT_MAX_SUPPORT_PER_RUN, DEFAULT_HIERARCHY_DEPTH, DEFAULT_MIN_COUNT, BASE_TARGET_SCORE, BASE_SUPPORT_SCORE, MAX_TARGET_MULTIPLIER, MAX_SUPPORT_MULTIPLIER, LOCKED_TAG_PREFIXES, LESSON_GATE_PENALTY_TAG_HINT, PRESCRIBED_DEBUG_VERSION, PrescribedCardsGenerator;
|
|
1197
1315
|
var init_prescribed = __esm({
|
|
1198
1316
|
"src/core/navigators/generators/prescribed.ts"() {
|
|
1199
1317
|
"use strict";
|
|
@@ -1210,6 +1328,7 @@ var init_prescribed = __esm({
|
|
|
1210
1328
|
MAX_SUPPORT_MULTIPLIER = 4;
|
|
1211
1329
|
LOCKED_TAG_PREFIXES = ["concept:"];
|
|
1212
1330
|
LESSON_GATE_PENALTY_TAG_HINT = "concept:";
|
|
1331
|
+
PRESCRIBED_DEBUG_VERSION = "testversion-prescribed-v2";
|
|
1213
1332
|
PrescribedCardsGenerator = class extends ContentNavigator {
|
|
1214
1333
|
name;
|
|
1215
1334
|
config;
|
|
@@ -1226,7 +1345,7 @@ var init_prescribed = __esm({
|
|
|
1226
1345
|
}
|
|
1227
1346
|
async getWeightedCards(limit, context) {
|
|
1228
1347
|
if (this.config.groups.length === 0 || limit <= 0) {
|
|
1229
|
-
return [];
|
|
1348
|
+
return { cards: [] };
|
|
1230
1349
|
}
|
|
1231
1350
|
const courseId = this.course.getCourseID();
|
|
1232
1351
|
const activeCards = await this.user.getActiveCards();
|
|
@@ -1251,6 +1370,7 @@ var init_prescribed = __esm({
|
|
|
1251
1370
|
};
|
|
1252
1371
|
const emitted = [];
|
|
1253
1372
|
const emittedIds = /* @__PURE__ */ new Set();
|
|
1373
|
+
const groupRuntimes = [];
|
|
1254
1374
|
for (const group of this.config.groups) {
|
|
1255
1375
|
const runtime = this.buildGroupRuntimeState({
|
|
1256
1376
|
group,
|
|
@@ -1262,6 +1382,7 @@ var init_prescribed = __esm({
|
|
|
1262
1382
|
userTagElo,
|
|
1263
1383
|
userGlobalElo
|
|
1264
1384
|
});
|
|
1385
|
+
groupRuntimes.push(runtime);
|
|
1265
1386
|
nextState.groups[group.id] = this.buildNextGroupState(runtime, progress.groups[group.id]);
|
|
1266
1387
|
const directCards = this.buildDirectTargetCards(
|
|
1267
1388
|
runtime,
|
|
@@ -1275,12 +1396,17 @@ var init_prescribed = __esm({
|
|
|
1275
1396
|
);
|
|
1276
1397
|
emitted.push(...directCards, ...supportCards);
|
|
1277
1398
|
}
|
|
1399
|
+
const hintSummary = this.buildSupportHintSummary(groupRuntimes);
|
|
1400
|
+
const hints = Object.keys(hintSummary.boostTags).length > 0 ? {
|
|
1401
|
+
boostTags: hintSummary.boostTags,
|
|
1402
|
+
_label: `prescribed-support (${hintSummary.supportTags.length} tags; blocked=${hintSummary.blockedTargetIds.length}; testversion=${PRESCRIBED_DEBUG_VERSION})`
|
|
1403
|
+
} : void 0;
|
|
1278
1404
|
if (emitted.length === 0) {
|
|
1279
1405
|
logger.debug("[Prescribed] No prescribed targets/support emitted this run");
|
|
1280
1406
|
await this.putStrategyState(nextState).catch((e) => {
|
|
1281
1407
|
logger.debug(`[Prescribed] Failed to persist empty-state update: ${e}`);
|
|
1282
1408
|
});
|
|
1283
|
-
return [];
|
|
1409
|
+
return hints ? { cards: [], hints } : { cards: [] };
|
|
1284
1410
|
}
|
|
1285
1411
|
const finalCards = pickTopByScore(emitted, limit);
|
|
1286
1412
|
const surfacedByGroup = /* @__PURE__ */ new Map();
|
|
@@ -1311,7 +1437,27 @@ var init_prescribed = __esm({
|
|
|
1311
1437
|
logger.info(
|
|
1312
1438
|
`[Prescribed] Emitting ${finalCards.length} cards (${finalCards.filter((c) => c.provenance[0]?.reason.includes("mode=target")).length} target, ${finalCards.filter((c) => c.provenance[0]?.reason.includes("mode=support")).length} support)`
|
|
1313
1439
|
);
|
|
1314
|
-
return finalCards;
|
|
1440
|
+
return hints ? { cards: finalCards, hints } : { cards: finalCards };
|
|
1441
|
+
}
|
|
1442
|
+
buildSupportHintSummary(groupRuntimes) {
|
|
1443
|
+
const boostTags = {};
|
|
1444
|
+
const blockedTargetIds = /* @__PURE__ */ new Set();
|
|
1445
|
+
const supportTags = /* @__PURE__ */ new Set();
|
|
1446
|
+
for (const runtime of groupRuntimes) {
|
|
1447
|
+
if (runtime.blockedTargets.length === 0 || runtime.supportTags.length === 0) {
|
|
1448
|
+
continue;
|
|
1449
|
+
}
|
|
1450
|
+
runtime.blockedTargets.forEach((cardId) => blockedTargetIds.add(cardId));
|
|
1451
|
+
for (const tag of runtime.supportTags) {
|
|
1452
|
+
supportTags.add(tag);
|
|
1453
|
+
boostTags[tag] = (boostTags[tag] ?? 1) * runtime.supportMultiplier;
|
|
1454
|
+
}
|
|
1455
|
+
}
|
|
1456
|
+
return {
|
|
1457
|
+
boostTags,
|
|
1458
|
+
blockedTargetIds: [...blockedTargetIds].sort(),
|
|
1459
|
+
supportTags: [...supportTags].sort()
|
|
1460
|
+
};
|
|
1315
1461
|
}
|
|
1316
1462
|
parseConfig(serializedData) {
|
|
1317
1463
|
try {
|
|
@@ -1394,7 +1540,22 @@ var init_prescribed = __esm({
|
|
|
1394
1540
|
group.hierarchyWalk?.enabled !== false,
|
|
1395
1541
|
group.hierarchyWalk?.maxDepth ?? DEFAULT_HIERARCHY_DEPTH
|
|
1396
1542
|
);
|
|
1397
|
-
|
|
1543
|
+
const introTags = tags.filter((tag) => tag.startsWith("gpc:intro:"));
|
|
1544
|
+
const exposeTags = new Set(tags.filter((tag) => tag.startsWith("gpc:expose:")));
|
|
1545
|
+
for (const introTag of introTags) {
|
|
1546
|
+
const suffix = introTag.slice("gpc:intro:".length);
|
|
1547
|
+
if (suffix) {
|
|
1548
|
+
exposeTags.add(`gpc:expose:${suffix}`);
|
|
1549
|
+
}
|
|
1550
|
+
}
|
|
1551
|
+
const unmetExposeTags = [...exposeTags].filter((tag) => {
|
|
1552
|
+
const tagElo = userTagElo[tag];
|
|
1553
|
+
return !tagElo || tagElo.count < DEFAULT_MIN_COUNT;
|
|
1554
|
+
});
|
|
1555
|
+
if (unmetExposeTags.length > 0) {
|
|
1556
|
+
unmetExposeTags.forEach((tag) => supportTags.add(tag));
|
|
1557
|
+
}
|
|
1558
|
+
if (resolution.blocked || unmetExposeTags.length > 0) {
|
|
1398
1559
|
blockedTargets.push(cardId);
|
|
1399
1560
|
resolution.supportTags.forEach((t) => supportTags.add(t));
|
|
1400
1561
|
} else {
|
|
@@ -1424,7 +1585,8 @@ var init_prescribed = __esm({
|
|
|
1424
1585
|
supportCandidates,
|
|
1425
1586
|
supportTags: [...supportTags],
|
|
1426
1587
|
pressureMultiplier,
|
|
1427
|
-
supportMultiplier
|
|
1588
|
+
supportMultiplier,
|
|
1589
|
+
debugVersion: PRESCRIBED_DEBUG_VERSION
|
|
1428
1590
|
};
|
|
1429
1591
|
}
|
|
1430
1592
|
buildNextGroupState(runtime, prior) {
|
|
@@ -1432,6 +1594,7 @@ var init_prescribed = __esm({
|
|
|
1432
1594
|
const surfacedThisRun = false;
|
|
1433
1595
|
return {
|
|
1434
1596
|
encounteredCardIds: [...runtime.encounteredTargets].sort(),
|
|
1597
|
+
pendingTargetIds: [...runtime.pendingTargets].sort(),
|
|
1435
1598
|
lastSurfacedAt: prior?.lastSurfacedAt ?? null,
|
|
1436
1599
|
sessionsSinceSurfaced: surfacedThisRun ? 0 : carriedSessions + 1,
|
|
1437
1600
|
lastSupportAt: prior?.lastSupportAt ?? null,
|
|
@@ -1456,7 +1619,7 @@ var init_prescribed = __esm({
|
|
|
1456
1619
|
strategyId: this.strategyId || "NAVIGATION_STRATEGY-prescribed",
|
|
1457
1620
|
action: "generated",
|
|
1458
1621
|
score: BASE_TARGET_SCORE * runtime.pressureMultiplier,
|
|
1459
|
-
reason: `mode=target;group=${runtime.group.id};pending=${runtime.pendingTargets.length};surfaceable=${runtime.surfaceableTargets.length};blocked=${runtime.blockedTargets.length};multiplier=${runtime.pressureMultiplier.toFixed(2)}`
|
|
1622
|
+
reason: `mode=target;group=${runtime.group.id};pending=${runtime.pendingTargets.length};surfaceable=${runtime.surfaceableTargets.length};blocked=${runtime.blockedTargets.length};blockedTargets=${runtime.blockedTargets.join("|") || "none"};supportTags=${runtime.supportTags.join("|") || "none"};multiplier=${runtime.pressureMultiplier.toFixed(2)};testversion=${runtime.debugVersion}`
|
|
1460
1623
|
}
|
|
1461
1624
|
]
|
|
1462
1625
|
});
|
|
@@ -1483,7 +1646,7 @@ var init_prescribed = __esm({
|
|
|
1483
1646
|
strategyId: this.strategyId || "NAVIGATION_STRATEGY-prescribed",
|
|
1484
1647
|
action: "generated",
|
|
1485
1648
|
score: BASE_SUPPORT_SCORE * runtime.supportMultiplier,
|
|
1486
|
-
reason: `mode=support;group=${runtime.group.id};blocked=${runtime.blockedTargets.length};supportTags=${runtime.supportTags.join("|") || "none"};multiplier=${runtime.supportMultiplier.toFixed(2)}`
|
|
1649
|
+
reason: `mode=support;group=${runtime.group.id};pending=${runtime.pendingTargets.length};blocked=${runtime.blockedTargets.length};blockedTargets=${runtime.blockedTargets.join("|") || "none"};supportCard=${cardId};supportTags=${runtime.supportTags.join("|") || "none"};multiplier=${runtime.supportMultiplier.toFixed(2)};testversion=${runtime.debugVersion}`
|
|
1487
1650
|
}
|
|
1488
1651
|
]
|
|
1489
1652
|
});
|
|
@@ -1513,35 +1676,43 @@ var init_prescribed = __esm({
|
|
|
1513
1676
|
return [...candidates];
|
|
1514
1677
|
}
|
|
1515
1678
|
resolveBlockedSupportTags(targetTags, hierarchyConfigs, userTagElo, userGlobalElo, hierarchyWalkEnabled, maxDepth) {
|
|
1516
|
-
if (!hierarchyWalkEnabled || targetTags.length === 0 || hierarchyConfigs.length === 0) {
|
|
1517
|
-
return {
|
|
1518
|
-
blocked: false,
|
|
1519
|
-
supportTags: []
|
|
1520
|
-
};
|
|
1521
|
-
}
|
|
1522
1679
|
const supportTags = /* @__PURE__ */ new Set();
|
|
1523
1680
|
let blocked = false;
|
|
1524
1681
|
for (const targetTag of targetTags) {
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
)
|
|
1531
|
-
|
|
1532
|
-
|
|
1682
|
+
const prereqSets = hierarchyConfigs.map((hierarchy) => hierarchy.prerequisites[targetTag]).filter((prereqs) => Array.isArray(prereqs) && prereqs.length > 0);
|
|
1683
|
+
if (prereqSets.length === 0) {
|
|
1684
|
+
continue;
|
|
1685
|
+
}
|
|
1686
|
+
const tagBlocked = prereqSets.some(
|
|
1687
|
+
(prereqs) => prereqs.some((pr) => !this.isPrerequisiteMet(pr, userTagElo[pr.tag], userGlobalElo))
|
|
1688
|
+
);
|
|
1689
|
+
if (!tagBlocked) {
|
|
1690
|
+
continue;
|
|
1691
|
+
}
|
|
1692
|
+
blocked = true;
|
|
1693
|
+
if (!hierarchyWalkEnabled) {
|
|
1694
|
+
for (const prereqs of prereqSets) {
|
|
1695
|
+
for (const prereq of prereqs) {
|
|
1696
|
+
if (!this.isPrerequisiteMet(prereq, userTagElo[prereq.tag], userGlobalElo)) {
|
|
1697
|
+
supportTags.add(prereq.tag);
|
|
1698
|
+
}
|
|
1699
|
+
}
|
|
1533
1700
|
}
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1701
|
+
continue;
|
|
1702
|
+
}
|
|
1703
|
+
for (const prereqs of prereqSets) {
|
|
1704
|
+
for (const prereq of prereqs) {
|
|
1705
|
+
if (!this.isPrerequisiteMet(prereq, userTagElo[prereq.tag], userGlobalElo)) {
|
|
1706
|
+
this.collectSupportTagsRecursive(
|
|
1707
|
+
prereq.tag,
|
|
1708
|
+
hierarchyConfigs,
|
|
1709
|
+
userTagElo,
|
|
1710
|
+
userGlobalElo,
|
|
1711
|
+
maxDepth,
|
|
1712
|
+
/* @__PURE__ */ new Set(),
|
|
1713
|
+
supportTags
|
|
1714
|
+
);
|
|
1715
|
+
}
|
|
1545
1716
|
}
|
|
1546
1717
|
}
|
|
1547
1718
|
}
|
|
@@ -1696,7 +1867,7 @@ var init_srs = __esm({
|
|
|
1696
1867
|
]
|
|
1697
1868
|
};
|
|
1698
1869
|
});
|
|
1699
|
-
return scored.sort((a, b) => b.score - a.score).slice(0, limit);
|
|
1870
|
+
return { cards: scored.sort((a, b) => b.score - a.score).slice(0, limit) };
|
|
1700
1871
|
}
|
|
1701
1872
|
/**
|
|
1702
1873
|
* Compute backlog pressure based on number of due reviews.
|
|
@@ -2711,1734 +2882,176 @@ var init_relativePriority = __esm({
|
|
|
2711
2882
|
strategyId: this.strategyId || "NAVIGATION_STRATEGY-priority",
|
|
2712
2883
|
action,
|
|
2713
2884
|
score: finalScore,
|
|
2714
|
-
reason
|
|
2715
|
-
}
|
|
2716
|
-
]
|
|
2717
|
-
};
|
|
2718
|
-
})
|
|
2719
|
-
);
|
|
2720
|
-
return adjusted;
|
|
2721
|
-
}
|
|
2722
|
-
/**
|
|
2723
|
-
* Legacy getWeightedCards - now throws as filters should not be used as generators.
|
|
2724
|
-
*
|
|
2725
|
-
* Use transform() via Pipeline instead.
|
|
2726
|
-
*/
|
|
2727
|
-
async getWeightedCards(_limit) {
|
|
2728
|
-
throw new Error(
|
|
2729
|
-
"RelativePriorityNavigator is a filter and should not be used as a generator. Use Pipeline with a generator and this filter via transform()."
|
|
2730
|
-
);
|
|
2731
|
-
}
|
|
2732
|
-
};
|
|
2733
|
-
}
|
|
2734
|
-
});
|
|
2735
|
-
|
|
2736
|
-
// src/core/navigators/filters/types.ts
|
|
2737
|
-
var types_exports2 = {};
|
|
2738
|
-
var init_types2 = __esm({
|
|
2739
|
-
"src/core/navigators/filters/types.ts"() {
|
|
2740
|
-
"use strict";
|
|
2741
|
-
}
|
|
2742
|
-
});
|
|
2743
|
-
|
|
2744
|
-
// src/core/navigators/filters/userGoalStub.ts
|
|
2745
|
-
var userGoalStub_exports = {};
|
|
2746
|
-
__export(userGoalStub_exports, {
|
|
2747
|
-
USER_GOAL_NAVIGATOR_STUB: () => USER_GOAL_NAVIGATOR_STUB
|
|
2748
|
-
});
|
|
2749
|
-
var USER_GOAL_NAVIGATOR_STUB;
|
|
2750
|
-
var init_userGoalStub = __esm({
|
|
2751
|
-
"src/core/navigators/filters/userGoalStub.ts"() {
|
|
2752
|
-
"use strict";
|
|
2753
|
-
USER_GOAL_NAVIGATOR_STUB = true;
|
|
2754
|
-
}
|
|
2755
|
-
});
|
|
2756
|
-
|
|
2757
|
-
// import("./filters/**/*") in src/core/navigators/index.ts
|
|
2758
|
-
var globImport_filters;
|
|
2759
|
-
var init_2 = __esm({
|
|
2760
|
-
'import("./filters/**/*") in src/core/navigators/index.ts'() {
|
|
2761
|
-
globImport_filters = __glob({
|
|
2762
|
-
"./filters/WeightedFilter.ts": () => Promise.resolve().then(() => (init_WeightedFilter(), WeightedFilter_exports)),
|
|
2763
|
-
"./filters/eloDistance.ts": () => Promise.resolve().then(() => (init_eloDistance(), eloDistance_exports)),
|
|
2764
|
-
"./filters/hierarchyDefinition.ts": () => Promise.resolve().then(() => (init_hierarchyDefinition(), hierarchyDefinition_exports)),
|
|
2765
|
-
"./filters/index.ts": () => Promise.resolve().then(() => (init_filters(), filters_exports)),
|
|
2766
|
-
"./filters/inferredPreferenceStub.ts": () => Promise.resolve().then(() => (init_inferredPreferenceStub(), inferredPreferenceStub_exports)),
|
|
2767
|
-
"./filters/interferenceMitigator.ts": () => Promise.resolve().then(() => (init_interferenceMitigator(), interferenceMitigator_exports)),
|
|
2768
|
-
"./filters/relativePriority.ts": () => Promise.resolve().then(() => (init_relativePriority(), relativePriority_exports)),
|
|
2769
|
-
"./filters/types.ts": () => Promise.resolve().then(() => (init_types2(), types_exports2)),
|
|
2770
|
-
"./filters/userGoalStub.ts": () => Promise.resolve().then(() => (init_userGoalStub(), userGoalStub_exports)),
|
|
2771
|
-
"./filters/userTagPreference.ts": () => Promise.resolve().then(() => (init_userTagPreference(), userTagPreference_exports))
|
|
2772
|
-
});
|
|
2773
|
-
}
|
|
2774
|
-
});
|
|
2775
|
-
|
|
2776
|
-
// src/core/orchestration/gradient.ts
|
|
2777
|
-
var init_gradient = __esm({
|
|
2778
|
-
"src/core/orchestration/gradient.ts"() {
|
|
2779
|
-
"use strict";
|
|
2780
|
-
init_logger();
|
|
2781
|
-
}
|
|
2782
|
-
});
|
|
2783
|
-
|
|
2784
|
-
// src/core/orchestration/learning.ts
|
|
2785
|
-
var init_learning = __esm({
|
|
2786
|
-
"src/core/orchestration/learning.ts"() {
|
|
2787
|
-
"use strict";
|
|
2788
|
-
init_contentNavigationStrategy();
|
|
2789
|
-
init_types_legacy();
|
|
2790
|
-
init_logger();
|
|
2791
|
-
}
|
|
2792
|
-
});
|
|
2793
|
-
|
|
2794
|
-
// src/core/orchestration/signal.ts
|
|
2795
|
-
var init_signal = __esm({
|
|
2796
|
-
"src/core/orchestration/signal.ts"() {
|
|
2797
|
-
"use strict";
|
|
2798
|
-
}
|
|
2799
|
-
});
|
|
2800
|
-
|
|
2801
|
-
// src/core/orchestration/recording.ts
|
|
2802
|
-
var init_recording = __esm({
|
|
2803
|
-
"src/core/orchestration/recording.ts"() {
|
|
2804
|
-
"use strict";
|
|
2805
|
-
init_signal();
|
|
2806
|
-
init_types_legacy();
|
|
2807
|
-
init_logger();
|
|
2808
|
-
}
|
|
2809
|
-
});
|
|
2810
|
-
|
|
2811
|
-
// src/core/orchestration/index.ts
|
|
2812
|
-
function fnv1a(str) {
|
|
2813
|
-
let hash = 2166136261;
|
|
2814
|
-
for (let i = 0; i < str.length; i++) {
|
|
2815
|
-
hash ^= str.charCodeAt(i);
|
|
2816
|
-
hash = Math.imul(hash, 16777619);
|
|
2817
|
-
}
|
|
2818
|
-
return hash >>> 0;
|
|
2819
|
-
}
|
|
2820
|
-
function computeDeviation(userId, strategyId, salt) {
|
|
2821
|
-
const input = `${userId}:${strategyId}:${salt}`;
|
|
2822
|
-
const hash = fnv1a(input);
|
|
2823
|
-
const normalized = hash / 4294967296;
|
|
2824
|
-
return normalized * 2 - 1;
|
|
2825
|
-
}
|
|
2826
|
-
function computeSpread(confidence) {
|
|
2827
|
-
const clampedConfidence = Math.max(0, Math.min(1, confidence));
|
|
2828
|
-
return MAX_SPREAD - clampedConfidence * (MAX_SPREAD - MIN_SPREAD);
|
|
2829
|
-
}
|
|
2830
|
-
function computeEffectiveWeight(learnable, userId, strategyId, salt) {
|
|
2831
|
-
const deviation = computeDeviation(userId, strategyId, salt);
|
|
2832
|
-
const spread = computeSpread(learnable.confidence);
|
|
2833
|
-
const adjustment = deviation * spread * learnable.weight;
|
|
2834
|
-
const effective = learnable.weight + adjustment;
|
|
2835
|
-
return Math.max(MIN_WEIGHT, Math.min(MAX_WEIGHT, effective));
|
|
2836
|
-
}
|
|
2837
|
-
async function createOrchestrationContext(user, course) {
|
|
2838
|
-
let courseConfig;
|
|
2839
|
-
try {
|
|
2840
|
-
courseConfig = await course.getCourseConfig();
|
|
2841
|
-
} catch (e) {
|
|
2842
|
-
logger.error(`[Orchestration] Failed to load course config: ${e}`);
|
|
2843
|
-
courseConfig = {
|
|
2844
|
-
name: "Unknown",
|
|
2845
|
-
description: "",
|
|
2846
|
-
public: false,
|
|
2847
|
-
deleted: false,
|
|
2848
|
-
creator: "",
|
|
2849
|
-
admins: [],
|
|
2850
|
-
moderators: [],
|
|
2851
|
-
dataShapes: [],
|
|
2852
|
-
questionTypes: [],
|
|
2853
|
-
orchestration: { salt: "default" }
|
|
2854
|
-
};
|
|
2855
|
-
}
|
|
2856
|
-
const userId = user.getUsername();
|
|
2857
|
-
const salt = courseConfig.orchestration?.salt || "default_salt";
|
|
2858
|
-
return {
|
|
2859
|
-
user,
|
|
2860
|
-
course,
|
|
2861
|
-
userId,
|
|
2862
|
-
courseConfig,
|
|
2863
|
-
getEffectiveWeight(strategyId, learnable) {
|
|
2864
|
-
return computeEffectiveWeight(learnable, userId, strategyId, salt);
|
|
2865
|
-
},
|
|
2866
|
-
getDeviation(strategyId) {
|
|
2867
|
-
return computeDeviation(userId, strategyId, salt);
|
|
2868
|
-
}
|
|
2869
|
-
};
|
|
2870
|
-
}
|
|
2871
|
-
var MIN_SPREAD, MAX_SPREAD, MIN_WEIGHT, MAX_WEIGHT;
|
|
2872
|
-
var init_orchestration = __esm({
|
|
2873
|
-
"src/core/orchestration/index.ts"() {
|
|
2874
|
-
"use strict";
|
|
2875
|
-
init_logger();
|
|
2876
|
-
init_gradient();
|
|
2877
|
-
init_learning();
|
|
2878
|
-
init_signal();
|
|
2879
|
-
init_recording();
|
|
2880
|
-
MIN_SPREAD = 0.1;
|
|
2881
|
-
MAX_SPREAD = 0.5;
|
|
2882
|
-
MIN_WEIGHT = 0.1;
|
|
2883
|
-
MAX_WEIGHT = 3;
|
|
2884
|
-
}
|
|
2885
|
-
});
|
|
2886
|
-
|
|
2887
|
-
// src/study/SpacedRepetition.ts
|
|
2888
|
-
import moment4 from "moment";
|
|
2889
|
-
import { isTaggedPerformance } from "@vue-skuilder/common";
|
|
2890
|
-
var duration;
|
|
2891
|
-
var init_SpacedRepetition = __esm({
|
|
2892
|
-
"src/study/SpacedRepetition.ts"() {
|
|
2893
|
-
"use strict";
|
|
2894
|
-
init_util();
|
|
2895
|
-
init_logger();
|
|
2896
|
-
duration = moment4.duration;
|
|
2897
|
-
}
|
|
2898
|
-
});
|
|
2899
|
-
|
|
2900
|
-
// src/study/services/SrsService.ts
|
|
2901
|
-
import moment5 from "moment";
|
|
2902
|
-
var init_SrsService = __esm({
|
|
2903
|
-
"src/study/services/SrsService.ts"() {
|
|
2904
|
-
"use strict";
|
|
2905
|
-
init_couch();
|
|
2906
|
-
init_SpacedRepetition();
|
|
2907
|
-
init_logger();
|
|
2908
|
-
}
|
|
2909
|
-
});
|
|
2910
|
-
|
|
2911
|
-
// src/study/services/EloService.ts
|
|
2912
|
-
import {
|
|
2913
|
-
adjustCourseScores,
|
|
2914
|
-
adjustCourseScoresPerTag,
|
|
2915
|
-
toCourseElo as toCourseElo5
|
|
2916
|
-
} from "@vue-skuilder/common";
|
|
2917
|
-
var init_EloService = __esm({
|
|
2918
|
-
"src/study/services/EloService.ts"() {
|
|
2919
|
-
"use strict";
|
|
2920
|
-
init_logger();
|
|
2921
|
-
}
|
|
2922
|
-
});
|
|
2923
|
-
|
|
2924
|
-
// src/study/services/ResponseProcessor.ts
|
|
2925
|
-
import { isTaggedPerformance as isTaggedPerformance2 } from "@vue-skuilder/common";
|
|
2926
|
-
var init_ResponseProcessor = __esm({
|
|
2927
|
-
"src/study/services/ResponseProcessor.ts"() {
|
|
2928
|
-
"use strict";
|
|
2929
|
-
init_core();
|
|
2930
|
-
init_logger();
|
|
2931
|
-
}
|
|
2932
|
-
});
|
|
2933
|
-
|
|
2934
|
-
// src/study/services/CardHydrationService.ts
|
|
2935
|
-
import {
|
|
2936
|
-
displayableDataToViewData,
|
|
2937
|
-
isCourseElo,
|
|
2938
|
-
toCourseElo as toCourseElo6
|
|
2939
|
-
} from "@vue-skuilder/common";
|
|
2940
|
-
var init_CardHydrationService = __esm({
|
|
2941
|
-
"src/study/services/CardHydrationService.ts"() {
|
|
2942
|
-
"use strict";
|
|
2943
|
-
init_logger();
|
|
2944
|
-
}
|
|
2945
|
-
});
|
|
2946
|
-
|
|
2947
|
-
// src/study/ItemQueue.ts
|
|
2948
|
-
var init_ItemQueue = __esm({
|
|
2949
|
-
"src/study/ItemQueue.ts"() {
|
|
2950
|
-
"use strict";
|
|
2951
|
-
}
|
|
2952
|
-
});
|
|
2953
|
-
|
|
2954
|
-
// src/util/packer/types.ts
|
|
2955
|
-
var init_types3 = __esm({
|
|
2956
|
-
"src/util/packer/types.ts"() {
|
|
2957
|
-
"use strict";
|
|
2958
|
-
}
|
|
2959
|
-
});
|
|
2960
|
-
|
|
2961
|
-
// src/util/packer/CouchDBToStaticPacker.ts
|
|
2962
|
-
var init_CouchDBToStaticPacker = __esm({
|
|
2963
|
-
"src/util/packer/CouchDBToStaticPacker.ts"() {
|
|
2964
|
-
"use strict";
|
|
2965
|
-
init_types_legacy();
|
|
2966
|
-
init_logger();
|
|
2967
|
-
}
|
|
2968
|
-
});
|
|
2969
|
-
|
|
2970
|
-
// src/util/packer/index.ts
|
|
2971
|
-
var init_packer = __esm({
|
|
2972
|
-
"src/util/packer/index.ts"() {
|
|
2973
|
-
"use strict";
|
|
2974
|
-
init_types3();
|
|
2975
|
-
init_CouchDBToStaticPacker();
|
|
2976
|
-
}
|
|
2977
|
-
});
|
|
2978
|
-
|
|
2979
|
-
// src/util/migrator/types.ts
|
|
2980
|
-
var DEFAULT_MIGRATION_OPTIONS;
|
|
2981
|
-
var init_types4 = __esm({
|
|
2982
|
-
"src/util/migrator/types.ts"() {
|
|
2983
|
-
"use strict";
|
|
2984
|
-
DEFAULT_MIGRATION_OPTIONS = {
|
|
2985
|
-
chunkBatchSize: 100,
|
|
2986
|
-
validateRoundTrip: false,
|
|
2987
|
-
cleanupOnFailure: true,
|
|
2988
|
-
timeout: 3e5
|
|
2989
|
-
// 5 minutes
|
|
2990
|
-
};
|
|
2991
|
-
}
|
|
2992
|
-
});
|
|
2993
|
-
|
|
2994
|
-
// src/util/migrator/FileSystemAdapter.ts
|
|
2995
|
-
var FileSystemError;
|
|
2996
|
-
var init_FileSystemAdapter = __esm({
|
|
2997
|
-
"src/util/migrator/FileSystemAdapter.ts"() {
|
|
2998
|
-
"use strict";
|
|
2999
|
-
FileSystemError = class extends Error {
|
|
3000
|
-
constructor(message, operation, filePath, cause) {
|
|
3001
|
-
super(message);
|
|
3002
|
-
this.operation = operation;
|
|
3003
|
-
this.filePath = filePath;
|
|
3004
|
-
this.cause = cause;
|
|
3005
|
-
this.name = "FileSystemError";
|
|
3006
|
-
}
|
|
3007
|
-
};
|
|
3008
|
-
}
|
|
3009
|
-
});
|
|
3010
|
-
|
|
3011
|
-
// src/util/migrator/validation.ts
|
|
3012
|
-
async function validateStaticCourse(staticPath, fs) {
|
|
3013
|
-
const validation = {
|
|
3014
|
-
valid: true,
|
|
3015
|
-
manifestExists: false,
|
|
3016
|
-
chunksExist: false,
|
|
3017
|
-
attachmentsExist: false,
|
|
3018
|
-
errors: [],
|
|
3019
|
-
warnings: []
|
|
3020
|
-
};
|
|
3021
|
-
try {
|
|
3022
|
-
if (fs) {
|
|
3023
|
-
const stats = await fs.stat(staticPath);
|
|
3024
|
-
if (!stats.isDirectory()) {
|
|
3025
|
-
validation.errors.push(`Path is not a directory: ${staticPath}`);
|
|
3026
|
-
validation.valid = false;
|
|
3027
|
-
return validation;
|
|
3028
|
-
}
|
|
3029
|
-
} else if (!nodeFS) {
|
|
3030
|
-
validation.errors.push("File system access not available - validation skipped");
|
|
3031
|
-
validation.valid = false;
|
|
3032
|
-
return validation;
|
|
3033
|
-
} else {
|
|
3034
|
-
const stats = await nodeFS.promises.stat(staticPath);
|
|
3035
|
-
if (!stats.isDirectory()) {
|
|
3036
|
-
validation.errors.push(`Path is not a directory: ${staticPath}`);
|
|
3037
|
-
validation.valid = false;
|
|
3038
|
-
return validation;
|
|
3039
|
-
}
|
|
3040
|
-
}
|
|
3041
|
-
let manifestPath = `${staticPath}/manifest.json`;
|
|
3042
|
-
try {
|
|
3043
|
-
if (fs) {
|
|
3044
|
-
manifestPath = fs.joinPath(staticPath, "manifest.json");
|
|
3045
|
-
if (await fs.exists(manifestPath)) {
|
|
3046
|
-
validation.manifestExists = true;
|
|
3047
|
-
const manifestContent = await fs.readFile(manifestPath);
|
|
3048
|
-
const manifest = JSON.parse(manifestContent);
|
|
3049
|
-
validation.courseId = manifest.courseId;
|
|
3050
|
-
validation.courseName = manifest.courseName;
|
|
3051
|
-
if (!manifest.version || !manifest.courseId || !manifest.chunks || !Array.isArray(manifest.chunks)) {
|
|
3052
|
-
validation.errors.push("Invalid manifest structure");
|
|
3053
|
-
validation.valid = false;
|
|
3054
|
-
}
|
|
3055
|
-
} else {
|
|
3056
|
-
validation.errors.push(`Manifest not found: ${manifestPath}`);
|
|
3057
|
-
validation.valid = false;
|
|
3058
|
-
}
|
|
3059
|
-
} else {
|
|
3060
|
-
manifestPath = `${staticPath}/manifest.json`;
|
|
3061
|
-
await nodeFS.promises.access(manifestPath);
|
|
3062
|
-
validation.manifestExists = true;
|
|
3063
|
-
const manifestContent = await nodeFS.promises.readFile(manifestPath, "utf8");
|
|
3064
|
-
const manifest = JSON.parse(manifestContent);
|
|
3065
|
-
validation.courseId = manifest.courseId;
|
|
3066
|
-
validation.courseName = manifest.courseName;
|
|
3067
|
-
if (!manifest.version || !manifest.courseId || !manifest.chunks || !Array.isArray(manifest.chunks)) {
|
|
3068
|
-
validation.errors.push("Invalid manifest structure");
|
|
3069
|
-
validation.valid = false;
|
|
3070
|
-
}
|
|
3071
|
-
}
|
|
3072
|
-
} catch (error) {
|
|
3073
|
-
const errorMessage = error instanceof FileSystemError ? error.message : `Manifest not found or invalid: ${manifestPath}`;
|
|
3074
|
-
validation.errors.push(errorMessage);
|
|
3075
|
-
validation.valid = false;
|
|
3076
|
-
}
|
|
3077
|
-
let chunksPath = `${staticPath}/chunks`;
|
|
3078
|
-
try {
|
|
3079
|
-
if (fs) {
|
|
3080
|
-
chunksPath = fs.joinPath(staticPath, "chunks");
|
|
3081
|
-
if (await fs.exists(chunksPath)) {
|
|
3082
|
-
const chunksStats = await fs.stat(chunksPath);
|
|
3083
|
-
if (chunksStats.isDirectory()) {
|
|
3084
|
-
validation.chunksExist = true;
|
|
3085
|
-
} else {
|
|
3086
|
-
validation.errors.push(`Chunks path is not a directory: ${chunksPath}`);
|
|
3087
|
-
validation.valid = false;
|
|
3088
|
-
}
|
|
3089
|
-
} else {
|
|
3090
|
-
validation.errors.push(`Chunks directory not found: ${chunksPath}`);
|
|
3091
|
-
validation.valid = false;
|
|
3092
|
-
}
|
|
3093
|
-
} else {
|
|
3094
|
-
chunksPath = `${staticPath}/chunks`;
|
|
3095
|
-
const chunksStats = await nodeFS.promises.stat(chunksPath);
|
|
3096
|
-
if (chunksStats.isDirectory()) {
|
|
3097
|
-
validation.chunksExist = true;
|
|
3098
|
-
} else {
|
|
3099
|
-
validation.errors.push(`Chunks path is not a directory: ${chunksPath}`);
|
|
3100
|
-
validation.valid = false;
|
|
3101
|
-
}
|
|
3102
|
-
}
|
|
3103
|
-
} catch (error) {
|
|
3104
|
-
const errorMessage = error instanceof FileSystemError ? error.message : `Chunks directory not found: ${chunksPath}`;
|
|
3105
|
-
validation.errors.push(errorMessage);
|
|
3106
|
-
validation.valid = false;
|
|
3107
|
-
}
|
|
3108
|
-
let attachmentsPath;
|
|
3109
|
-
try {
|
|
3110
|
-
if (fs) {
|
|
3111
|
-
attachmentsPath = fs.joinPath(staticPath, "attachments");
|
|
3112
|
-
if (await fs.exists(attachmentsPath)) {
|
|
3113
|
-
const attachmentsStats = await fs.stat(attachmentsPath);
|
|
3114
|
-
if (attachmentsStats.isDirectory()) {
|
|
3115
|
-
validation.attachmentsExist = true;
|
|
3116
|
-
}
|
|
3117
|
-
} else {
|
|
3118
|
-
validation.warnings.push(
|
|
3119
|
-
`Attachments directory not found: ${attachmentsPath} (this is OK if course has no attachments)`
|
|
3120
|
-
);
|
|
3121
|
-
}
|
|
3122
|
-
} else {
|
|
3123
|
-
attachmentsPath = `${staticPath}/attachments`;
|
|
3124
|
-
const attachmentsStats = await nodeFS.promises.stat(attachmentsPath);
|
|
3125
|
-
if (attachmentsStats.isDirectory()) {
|
|
3126
|
-
validation.attachmentsExist = true;
|
|
3127
|
-
}
|
|
3128
|
-
}
|
|
3129
|
-
} catch (error) {
|
|
3130
|
-
attachmentsPath = attachmentsPath || `${staticPath}/attachments`;
|
|
3131
|
-
const warningMessage = error instanceof FileSystemError ? error.message : `Attachments directory not found: ${attachmentsPath} (this is OK if course has no attachments)`;
|
|
3132
|
-
validation.warnings.push(warningMessage);
|
|
3133
|
-
}
|
|
3134
|
-
} catch (error) {
|
|
3135
|
-
validation.errors.push(
|
|
3136
|
-
`Failed to validate static course: ${error instanceof Error ? error.message : String(error)}`
|
|
3137
|
-
);
|
|
3138
|
-
validation.valid = false;
|
|
3139
|
-
}
|
|
3140
|
-
return validation;
|
|
3141
|
-
}
|
|
3142
|
-
async function validateMigration(targetDB, expectedCounts, manifest) {
|
|
3143
|
-
const validation = {
|
|
3144
|
-
valid: true,
|
|
3145
|
-
documentCountMatch: false,
|
|
3146
|
-
attachmentIntegrity: false,
|
|
3147
|
-
viewFunctionality: false,
|
|
3148
|
-
issues: []
|
|
3149
|
-
};
|
|
3150
|
-
try {
|
|
3151
|
-
logger.info("Starting migration validation...");
|
|
3152
|
-
const actualCounts = await getActualDocumentCounts(targetDB);
|
|
3153
|
-
validation.documentCountMatch = compareDocumentCounts(
|
|
3154
|
-
expectedCounts,
|
|
3155
|
-
actualCounts,
|
|
3156
|
-
validation.issues
|
|
3157
|
-
);
|
|
3158
|
-
await validateCourseConfig(targetDB, manifest, validation.issues);
|
|
3159
|
-
validation.viewFunctionality = await validateViews(targetDB, manifest, validation.issues);
|
|
3160
|
-
validation.attachmentIntegrity = await validateAttachmentIntegrity(targetDB, validation.issues);
|
|
3161
|
-
validation.valid = validation.documentCountMatch && validation.viewFunctionality && validation.attachmentIntegrity;
|
|
3162
|
-
logger.info(`Migration validation completed. Valid: ${validation.valid}`);
|
|
3163
|
-
if (validation.issues.length > 0) {
|
|
3164
|
-
logger.info(`Validation issues: ${validation.issues.length}`);
|
|
3165
|
-
validation.issues.forEach((issue) => {
|
|
3166
|
-
if (issue.type === "error") {
|
|
3167
|
-
logger.error(`${issue.category}: ${issue.message}`);
|
|
3168
|
-
} else {
|
|
3169
|
-
logger.warn(`${issue.category}: ${issue.message}`);
|
|
3170
|
-
}
|
|
3171
|
-
});
|
|
3172
|
-
}
|
|
3173
|
-
} catch (error) {
|
|
3174
|
-
validation.valid = false;
|
|
3175
|
-
validation.issues.push({
|
|
3176
|
-
type: "error",
|
|
3177
|
-
category: "metadata",
|
|
3178
|
-
message: `Validation failed: ${error instanceof Error ? error.message : String(error)}`
|
|
3179
|
-
});
|
|
3180
|
-
}
|
|
3181
|
-
return validation;
|
|
3182
|
-
}
|
|
3183
|
-
async function getActualDocumentCounts(db) {
|
|
3184
|
-
const counts = {};
|
|
3185
|
-
try {
|
|
3186
|
-
const allDocs = await db.allDocs({ include_docs: true });
|
|
3187
|
-
for (const row of allDocs.rows) {
|
|
3188
|
-
if (row.id.startsWith("_design/")) {
|
|
3189
|
-
counts["_design"] = (counts["_design"] || 0) + 1;
|
|
3190
|
-
continue;
|
|
3191
|
-
}
|
|
3192
|
-
const doc = row.doc;
|
|
3193
|
-
if (doc && doc.docType) {
|
|
3194
|
-
counts[doc.docType] = (counts[doc.docType] || 0) + 1;
|
|
3195
|
-
} else {
|
|
3196
|
-
counts["unknown"] = (counts["unknown"] || 0) + 1;
|
|
3197
|
-
}
|
|
3198
|
-
}
|
|
3199
|
-
} catch (error) {
|
|
3200
|
-
logger.error("Failed to get actual document counts:", error);
|
|
3201
|
-
}
|
|
3202
|
-
return counts;
|
|
3203
|
-
}
|
|
3204
|
-
function compareDocumentCounts(expected, actual, issues) {
|
|
3205
|
-
let countsMatch = true;
|
|
3206
|
-
for (const [docType, expectedCount] of Object.entries(expected)) {
|
|
3207
|
-
const actualCount = actual[docType] || 0;
|
|
3208
|
-
if (actualCount !== expectedCount) {
|
|
3209
|
-
countsMatch = false;
|
|
3210
|
-
issues.push({
|
|
3211
|
-
type: "error",
|
|
3212
|
-
category: "documents",
|
|
3213
|
-
message: `Document count mismatch for ${docType}: expected ${expectedCount}, got ${actualCount}`
|
|
3214
|
-
});
|
|
3215
|
-
}
|
|
3216
|
-
}
|
|
3217
|
-
for (const [docType, actualCount] of Object.entries(actual)) {
|
|
3218
|
-
if (!expected[docType] && docType !== "_design") {
|
|
3219
|
-
issues.push({
|
|
3220
|
-
type: "warning",
|
|
3221
|
-
category: "documents",
|
|
3222
|
-
message: `Unexpected document type found: ${docType} (${actualCount} documents)`
|
|
3223
|
-
});
|
|
3224
|
-
}
|
|
3225
|
-
}
|
|
3226
|
-
return countsMatch;
|
|
3227
|
-
}
|
|
3228
|
-
async function validateCourseConfig(db, manifest, issues) {
|
|
3229
|
-
try {
|
|
3230
|
-
const courseConfig = await db.get("CourseConfig");
|
|
3231
|
-
if (!courseConfig) {
|
|
3232
|
-
issues.push({
|
|
3233
|
-
type: "error",
|
|
3234
|
-
category: "course_config",
|
|
3235
|
-
message: "CourseConfig document not found after migration"
|
|
3236
|
-
});
|
|
3237
|
-
return;
|
|
3238
|
-
}
|
|
3239
|
-
if (!courseConfig.courseID) {
|
|
3240
|
-
issues.push({
|
|
3241
|
-
type: "warning",
|
|
3242
|
-
category: "course_config",
|
|
3243
|
-
message: "CourseConfig document missing courseID field"
|
|
3244
|
-
});
|
|
3245
|
-
}
|
|
3246
|
-
if (courseConfig.courseID !== manifest.courseId) {
|
|
3247
|
-
issues.push({
|
|
3248
|
-
type: "warning",
|
|
3249
|
-
category: "course_config",
|
|
3250
|
-
message: `CourseConfig courseID mismatch: expected ${manifest.courseId}, got ${courseConfig.courseID}`
|
|
3251
|
-
});
|
|
3252
|
-
}
|
|
3253
|
-
logger.debug("CourseConfig document validation passed");
|
|
3254
|
-
} catch (error) {
|
|
3255
|
-
if (error.status === 404) {
|
|
3256
|
-
issues.push({
|
|
3257
|
-
type: "error",
|
|
3258
|
-
category: "course_config",
|
|
3259
|
-
message: "CourseConfig document not found in database"
|
|
3260
|
-
});
|
|
3261
|
-
} else {
|
|
3262
|
-
issues.push({
|
|
3263
|
-
type: "error",
|
|
3264
|
-
category: "course_config",
|
|
3265
|
-
message: `Failed to validate CourseConfig document: ${error instanceof Error ? error.message : String(error)}`
|
|
3266
|
-
});
|
|
3267
|
-
}
|
|
3268
|
-
}
|
|
3269
|
-
}
|
|
3270
|
-
async function validateViews(db, manifest, issues) {
|
|
3271
|
-
let viewsValid = true;
|
|
3272
|
-
try {
|
|
3273
|
-
for (const designDoc of manifest.designDocs) {
|
|
3274
|
-
try {
|
|
3275
|
-
const doc = await db.get(designDoc._id);
|
|
3276
|
-
if (!doc) {
|
|
3277
|
-
viewsValid = false;
|
|
3278
|
-
issues.push({
|
|
3279
|
-
type: "error",
|
|
3280
|
-
category: "views",
|
|
3281
|
-
message: `Design document not found: ${designDoc._id}`
|
|
3282
|
-
});
|
|
3283
|
-
continue;
|
|
3284
|
-
}
|
|
3285
|
-
for (const viewName of Object.keys(designDoc.views)) {
|
|
3286
|
-
try {
|
|
3287
|
-
const viewPath = `${designDoc._id}/${viewName}`;
|
|
3288
|
-
await db.query(viewPath, { limit: 1 });
|
|
3289
|
-
} catch (viewError) {
|
|
3290
|
-
viewsValid = false;
|
|
3291
|
-
issues.push({
|
|
3292
|
-
type: "error",
|
|
3293
|
-
category: "views",
|
|
3294
|
-
message: `View not accessible: ${designDoc._id}/${viewName} - ${viewError}`
|
|
3295
|
-
});
|
|
3296
|
-
}
|
|
3297
|
-
}
|
|
3298
|
-
} catch (error) {
|
|
3299
|
-
viewsValid = false;
|
|
3300
|
-
issues.push({
|
|
3301
|
-
type: "error",
|
|
3302
|
-
category: "views",
|
|
3303
|
-
message: `Failed to validate design document ${designDoc._id}: ${error}`
|
|
3304
|
-
});
|
|
3305
|
-
}
|
|
3306
|
-
}
|
|
3307
|
-
} catch (error) {
|
|
3308
|
-
viewsValid = false;
|
|
3309
|
-
issues.push({
|
|
3310
|
-
type: "error",
|
|
3311
|
-
category: "views",
|
|
3312
|
-
message: `View validation failed: ${error instanceof Error ? error.message : String(error)}`
|
|
3313
|
-
});
|
|
3314
|
-
}
|
|
3315
|
-
return viewsValid;
|
|
3316
|
-
}
|
|
3317
|
-
async function validateAttachmentIntegrity(db, issues) {
|
|
3318
|
-
let attachmentsValid = true;
|
|
3319
|
-
try {
|
|
3320
|
-
const allDocs = await db.allDocs({
|
|
3321
|
-
include_docs: true,
|
|
3322
|
-
limit: 10
|
|
3323
|
-
// Sample first 10 documents for performance
|
|
3324
|
-
});
|
|
3325
|
-
let attachmentCount = 0;
|
|
3326
|
-
let validAttachments = 0;
|
|
3327
|
-
for (const row of allDocs.rows) {
|
|
3328
|
-
const doc = row.doc;
|
|
3329
|
-
if (doc && doc._attachments) {
|
|
3330
|
-
for (const [attachmentName, _attachmentMeta] of Object.entries(doc._attachments)) {
|
|
3331
|
-
attachmentCount++;
|
|
3332
|
-
try {
|
|
3333
|
-
const attachment = await db.getAttachment(doc._id, attachmentName);
|
|
3334
|
-
if (attachment) {
|
|
3335
|
-
validAttachments++;
|
|
3336
|
-
}
|
|
3337
|
-
} catch (attachmentError) {
|
|
3338
|
-
attachmentsValid = false;
|
|
3339
|
-
issues.push({
|
|
3340
|
-
type: "error",
|
|
3341
|
-
category: "attachments",
|
|
3342
|
-
message: `Attachment not accessible: ${doc._id}/${attachmentName} - ${attachmentError}`
|
|
3343
|
-
});
|
|
3344
|
-
}
|
|
3345
|
-
}
|
|
3346
|
-
}
|
|
3347
|
-
}
|
|
3348
|
-
if (attachmentCount === 0) {
|
|
3349
|
-
issues.push({
|
|
3350
|
-
type: "warning",
|
|
3351
|
-
category: "attachments",
|
|
3352
|
-
message: "No attachments found in sampled documents"
|
|
3353
|
-
});
|
|
3354
|
-
} else {
|
|
3355
|
-
logger.info(`Validated ${validAttachments}/${attachmentCount} sampled attachments`);
|
|
3356
|
-
}
|
|
3357
|
-
} catch (error) {
|
|
3358
|
-
attachmentsValid = false;
|
|
3359
|
-
issues.push({
|
|
3360
|
-
type: "error",
|
|
3361
|
-
category: "attachments",
|
|
3362
|
-
message: `Attachment validation failed: ${error instanceof Error ? error.message : String(error)}`
|
|
3363
|
-
});
|
|
3364
|
-
}
|
|
3365
|
-
return attachmentsValid;
|
|
3366
|
-
}
|
|
3367
|
-
var nodeFS;
|
|
3368
|
-
var init_validation = __esm({
|
|
3369
|
-
"src/util/migrator/validation.ts"() {
|
|
3370
|
-
"use strict";
|
|
3371
|
-
init_logger();
|
|
3372
|
-
init_FileSystemAdapter();
|
|
3373
|
-
nodeFS = null;
|
|
3374
|
-
try {
|
|
3375
|
-
if (typeof window === "undefined" && typeof process !== "undefined" && process.versions?.node) {
|
|
3376
|
-
nodeFS = eval("require")("fs");
|
|
3377
|
-
nodeFS.promises = nodeFS.promises || eval("require")("fs").promises;
|
|
3378
|
-
}
|
|
3379
|
-
} catch {
|
|
3380
|
-
}
|
|
3381
|
-
}
|
|
3382
|
-
});
|
|
3383
|
-
|
|
3384
|
-
// src/util/migrator/StaticToCouchDBMigrator.ts
|
|
3385
|
-
var nodeFS2, nodePath, StaticToCouchDBMigrator;
|
|
3386
|
-
var init_StaticToCouchDBMigrator = __esm({
|
|
3387
|
-
"src/util/migrator/StaticToCouchDBMigrator.ts"() {
|
|
3388
|
-
"use strict";
|
|
3389
|
-
init_logger();
|
|
3390
|
-
init_types4();
|
|
3391
|
-
init_validation();
|
|
3392
|
-
init_FileSystemAdapter();
|
|
3393
|
-
nodeFS2 = null;
|
|
3394
|
-
nodePath = null;
|
|
3395
|
-
try {
|
|
3396
|
-
if (typeof window === "undefined" && typeof process !== "undefined" && process.versions?.node) {
|
|
3397
|
-
nodeFS2 = eval("require")("fs");
|
|
3398
|
-
nodePath = eval("require")("path");
|
|
3399
|
-
nodeFS2.promises = nodeFS2.promises || eval("require")("fs").promises;
|
|
3400
|
-
}
|
|
3401
|
-
} catch {
|
|
3402
|
-
}
|
|
3403
|
-
StaticToCouchDBMigrator = class {
|
|
3404
|
-
options;
|
|
3405
|
-
progressCallback;
|
|
3406
|
-
fs;
|
|
3407
|
-
constructor(options = {}, fileSystemAdapter) {
|
|
3408
|
-
this.options = {
|
|
3409
|
-
...DEFAULT_MIGRATION_OPTIONS,
|
|
3410
|
-
...options
|
|
3411
|
-
};
|
|
3412
|
-
this.fs = fileSystemAdapter;
|
|
3413
|
-
}
|
|
3414
|
-
/**
|
|
3415
|
-
* Set a progress callback to receive updates during migration
|
|
3416
|
-
*/
|
|
3417
|
-
setProgressCallback(callback) {
|
|
3418
|
-
this.progressCallback = callback;
|
|
3419
|
-
}
|
|
3420
|
-
/**
|
|
3421
|
-
* Migrate a static course to CouchDB
|
|
3422
|
-
*/
|
|
3423
|
-
async migrateCourse(staticPath, targetDB) {
|
|
3424
|
-
const startTime = Date.now();
|
|
3425
|
-
const result = {
|
|
3426
|
-
success: false,
|
|
3427
|
-
documentsRestored: 0,
|
|
3428
|
-
attachmentsRestored: 0,
|
|
3429
|
-
designDocsRestored: 0,
|
|
3430
|
-
courseConfigRestored: 0,
|
|
3431
|
-
errors: [],
|
|
3432
|
-
warnings: [],
|
|
3433
|
-
migrationTime: 0
|
|
3434
|
-
};
|
|
3435
|
-
try {
|
|
3436
|
-
logger.info(`Starting migration from ${staticPath} to CouchDB`);
|
|
3437
|
-
this.reportProgress("manifest", 0, 1, "Validating static course...");
|
|
3438
|
-
const validation = await validateStaticCourse(staticPath, this.fs);
|
|
3439
|
-
if (!validation.valid) {
|
|
3440
|
-
result.errors.push(...validation.errors);
|
|
3441
|
-
throw new Error(`Static course validation failed: ${validation.errors.join(", ")}`);
|
|
3442
|
-
}
|
|
3443
|
-
result.warnings.push(...validation.warnings);
|
|
3444
|
-
this.reportProgress("manifest", 1, 1, "Loading course manifest...");
|
|
3445
|
-
const manifest = await this.loadManifest(staticPath);
|
|
3446
|
-
logger.info(`Loaded manifest for course: ${manifest.courseId} (${manifest.courseName})`);
|
|
3447
|
-
this.reportProgress(
|
|
3448
|
-
"design_docs",
|
|
3449
|
-
0,
|
|
3450
|
-
manifest.designDocs.length,
|
|
3451
|
-
"Restoring design documents..."
|
|
3452
|
-
);
|
|
3453
|
-
const designDocResults = await this.restoreDesignDocuments(manifest.designDocs, targetDB);
|
|
3454
|
-
result.designDocsRestored = designDocResults.restored;
|
|
3455
|
-
result.errors.push(...designDocResults.errors);
|
|
3456
|
-
result.warnings.push(...designDocResults.warnings);
|
|
3457
|
-
this.reportProgress("course_config", 0, 1, "Restoring CourseConfig document...");
|
|
3458
|
-
const courseConfigResults = await this.restoreCourseConfig(manifest, targetDB);
|
|
3459
|
-
result.courseConfigRestored = courseConfigResults.restored;
|
|
3460
|
-
result.errors.push(...courseConfigResults.errors);
|
|
3461
|
-
result.warnings.push(...courseConfigResults.warnings);
|
|
3462
|
-
this.reportProgress("course_config", 1, 1, "CourseConfig document restored");
|
|
3463
|
-
const expectedCounts = this.calculateExpectedCounts(manifest);
|
|
3464
|
-
this.reportProgress(
|
|
3465
|
-
"documents",
|
|
3466
|
-
0,
|
|
3467
|
-
manifest.documentCount,
|
|
3468
|
-
"Aggregating documents from chunks..."
|
|
3469
|
-
);
|
|
3470
|
-
const documents = await this.aggregateDocuments(staticPath, manifest);
|
|
3471
|
-
const filteredDocuments = documents.filter((doc) => doc._id !== "CourseConfig");
|
|
3472
|
-
if (documents.length !== filteredDocuments.length) {
|
|
3473
|
-
result.warnings.push(
|
|
3474
|
-
`Filtered out ${documents.length - filteredDocuments.length} CourseConfig document(s) from chunks to prevent conflicts`
|
|
3475
|
-
);
|
|
3476
|
-
}
|
|
3477
|
-
this.reportProgress(
|
|
3478
|
-
"documents",
|
|
3479
|
-
filteredDocuments.length,
|
|
3480
|
-
manifest.documentCount,
|
|
3481
|
-
"Uploading documents to CouchDB..."
|
|
3482
|
-
);
|
|
3483
|
-
const docResults = await this.uploadDocuments(filteredDocuments, targetDB);
|
|
3484
|
-
result.documentsRestored = docResults.restored;
|
|
3485
|
-
result.errors.push(...docResults.errors);
|
|
3486
|
-
result.warnings.push(...docResults.warnings);
|
|
3487
|
-
const docsWithAttachments = documents.filter(
|
|
3488
|
-
(doc) => doc._attachments && Object.keys(doc._attachments).length > 0
|
|
3489
|
-
);
|
|
3490
|
-
this.reportProgress("attachments", 0, docsWithAttachments.length, "Uploading attachments...");
|
|
3491
|
-
const attachmentResults = await this.uploadAttachments(
|
|
3492
|
-
staticPath,
|
|
3493
|
-
docsWithAttachments,
|
|
3494
|
-
targetDB
|
|
3495
|
-
);
|
|
3496
|
-
result.attachmentsRestored = attachmentResults.restored;
|
|
3497
|
-
result.errors.push(...attachmentResults.errors);
|
|
3498
|
-
result.warnings.push(...attachmentResults.warnings);
|
|
3499
|
-
if (this.options.validateRoundTrip) {
|
|
3500
|
-
this.reportProgress("validation", 0, 1, "Validating migration...");
|
|
3501
|
-
const validationResult = await validateMigration(targetDB, expectedCounts, manifest);
|
|
3502
|
-
if (!validationResult.valid) {
|
|
3503
|
-
result.warnings.push("Migration validation found issues");
|
|
3504
|
-
validationResult.issues.forEach((issue) => {
|
|
3505
|
-
if (issue.type === "error") {
|
|
3506
|
-
result.errors.push(`Validation: ${issue.message}`);
|
|
3507
|
-
} else {
|
|
3508
|
-
result.warnings.push(`Validation: ${issue.message}`);
|
|
3509
|
-
}
|
|
3510
|
-
});
|
|
3511
|
-
}
|
|
3512
|
-
this.reportProgress("validation", 1, 1, "Migration validation completed");
|
|
3513
|
-
}
|
|
3514
|
-
result.success = result.errors.length === 0;
|
|
3515
|
-
result.migrationTime = Date.now() - startTime;
|
|
3516
|
-
logger.info(`Migration completed in ${result.migrationTime}ms`);
|
|
3517
|
-
logger.info(`Documents restored: ${result.documentsRestored}`);
|
|
3518
|
-
logger.info(`Attachments restored: ${result.attachmentsRestored}`);
|
|
3519
|
-
logger.info(`Design docs restored: ${result.designDocsRestored}`);
|
|
3520
|
-
logger.info(`CourseConfig restored: ${result.courseConfigRestored}`);
|
|
3521
|
-
if (result.errors.length > 0) {
|
|
3522
|
-
logger.error(`Migration completed with ${result.errors.length} errors`);
|
|
3523
|
-
}
|
|
3524
|
-
if (result.warnings.length > 0) {
|
|
3525
|
-
logger.warn(`Migration completed with ${result.warnings.length} warnings`);
|
|
3526
|
-
}
|
|
3527
|
-
} catch (error) {
|
|
3528
|
-
result.success = false;
|
|
3529
|
-
result.migrationTime = Date.now() - startTime;
|
|
3530
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
3531
|
-
result.errors.push(`Migration failed: ${errorMessage}`);
|
|
3532
|
-
logger.error("Migration failed:", error);
|
|
3533
|
-
if (this.options.cleanupOnFailure) {
|
|
3534
|
-
try {
|
|
3535
|
-
await this.cleanupFailedMigration(targetDB);
|
|
3536
|
-
} catch (cleanupError) {
|
|
3537
|
-
logger.error("Failed to cleanup after migration failure:", cleanupError);
|
|
3538
|
-
result.warnings.push("Failed to cleanup after migration failure");
|
|
3539
|
-
}
|
|
3540
|
-
}
|
|
3541
|
-
}
|
|
3542
|
-
return result;
|
|
3543
|
-
}
|
|
3544
|
-
/**
|
|
3545
|
-
* Load and parse the manifest file
|
|
3546
|
-
*/
|
|
3547
|
-
async loadManifest(staticPath) {
|
|
3548
|
-
try {
|
|
3549
|
-
let manifestContent;
|
|
3550
|
-
let manifestPath;
|
|
3551
|
-
if (this.fs) {
|
|
3552
|
-
manifestPath = this.fs.joinPath(staticPath, "manifest.json");
|
|
3553
|
-
manifestContent = await this.fs.readFile(manifestPath);
|
|
3554
|
-
} else {
|
|
3555
|
-
manifestPath = nodeFS2 && nodePath ? nodePath.join(staticPath, "manifest.json") : `${staticPath}/manifest.json`;
|
|
3556
|
-
if (nodeFS2 && this.isLocalPath(staticPath)) {
|
|
3557
|
-
manifestContent = await nodeFS2.promises.readFile(manifestPath, "utf8");
|
|
3558
|
-
} else {
|
|
3559
|
-
const response = await fetch(manifestPath);
|
|
3560
|
-
if (!response.ok) {
|
|
3561
|
-
throw new Error(`Failed to fetch manifest: ${response.status} ${response.statusText}`);
|
|
3562
|
-
}
|
|
3563
|
-
manifestContent = await response.text();
|
|
3564
|
-
}
|
|
3565
|
-
}
|
|
3566
|
-
const manifest = JSON.parse(manifestContent);
|
|
3567
|
-
if (!manifest.version || !manifest.courseId || !manifest.chunks) {
|
|
3568
|
-
throw new Error("Invalid manifest structure");
|
|
3569
|
-
}
|
|
3570
|
-
return manifest;
|
|
3571
|
-
} catch (error) {
|
|
3572
|
-
const errorMessage = error instanceof FileSystemError ? error.message : `Failed to load manifest: ${error instanceof Error ? error.message : String(error)}`;
|
|
3573
|
-
throw new Error(errorMessage);
|
|
3574
|
-
}
|
|
3575
|
-
}
|
|
3576
|
-
/**
|
|
3577
|
-
* Restore design documents to CouchDB
|
|
3578
|
-
*/
|
|
3579
|
-
async restoreDesignDocuments(designDocs, db) {
|
|
3580
|
-
const result = { restored: 0, errors: [], warnings: [] };
|
|
3581
|
-
for (let i = 0; i < designDocs.length; i++) {
|
|
3582
|
-
const designDoc = designDocs[i];
|
|
3583
|
-
this.reportProgress("design_docs", i, designDocs.length, `Restoring ${designDoc._id}...`);
|
|
3584
|
-
try {
|
|
3585
|
-
let existingDoc;
|
|
3586
|
-
try {
|
|
3587
|
-
existingDoc = await db.get(designDoc._id);
|
|
3588
|
-
} catch {
|
|
3589
|
-
}
|
|
3590
|
-
const docToInsert = {
|
|
3591
|
-
_id: designDoc._id,
|
|
3592
|
-
views: designDoc.views
|
|
3593
|
-
};
|
|
3594
|
-
if (existingDoc) {
|
|
3595
|
-
docToInsert._rev = existingDoc._rev;
|
|
3596
|
-
logger.debug(`Updating existing design document: ${designDoc._id}`);
|
|
3597
|
-
} else {
|
|
3598
|
-
logger.debug(`Creating new design document: ${designDoc._id}`);
|
|
3599
|
-
}
|
|
3600
|
-
await db.put(docToInsert);
|
|
3601
|
-
result.restored++;
|
|
3602
|
-
} catch (error) {
|
|
3603
|
-
const errorMessage = `Failed to restore design document ${designDoc._id}: ${error instanceof Error ? error.message : String(error)}`;
|
|
3604
|
-
result.errors.push(errorMessage);
|
|
3605
|
-
logger.error(errorMessage);
|
|
3606
|
-
}
|
|
3607
|
-
}
|
|
3608
|
-
this.reportProgress(
|
|
3609
|
-
"design_docs",
|
|
3610
|
-
designDocs.length,
|
|
3611
|
-
designDocs.length,
|
|
3612
|
-
`Restored ${result.restored} design documents`
|
|
3613
|
-
);
|
|
3614
|
-
return result;
|
|
3615
|
-
}
|
|
3616
|
-
/**
|
|
3617
|
-
* Aggregate documents from all chunks
|
|
3618
|
-
*/
|
|
3619
|
-
async aggregateDocuments(staticPath, manifest) {
|
|
3620
|
-
const allDocuments = [];
|
|
3621
|
-
const documentMap = /* @__PURE__ */ new Map();
|
|
3622
|
-
for (let i = 0; i < manifest.chunks.length; i++) {
|
|
3623
|
-
const chunk = manifest.chunks[i];
|
|
3624
|
-
this.reportProgress(
|
|
3625
|
-
"documents",
|
|
3626
|
-
allDocuments.length,
|
|
3627
|
-
manifest.documentCount,
|
|
3628
|
-
`Loading chunk ${chunk.id}...`
|
|
3629
|
-
);
|
|
3630
|
-
try {
|
|
3631
|
-
const documents = await this.loadChunk(staticPath, chunk);
|
|
3632
|
-
for (const doc of documents) {
|
|
3633
|
-
if (!doc._id) {
|
|
3634
|
-
logger.warn(`Document without _id found in chunk ${chunk.id}, skipping`);
|
|
3635
|
-
continue;
|
|
3636
|
-
}
|
|
3637
|
-
if (documentMap.has(doc._id)) {
|
|
3638
|
-
logger.warn(`Duplicate document ID found: ${doc._id}, using latest version`);
|
|
3639
|
-
}
|
|
3640
|
-
documentMap.set(doc._id, doc);
|
|
3641
|
-
}
|
|
3642
|
-
} catch (error) {
|
|
3643
|
-
throw new Error(
|
|
3644
|
-
`Failed to load chunk ${chunk.id}: ${error instanceof Error ? error.message : String(error)}`
|
|
3645
|
-
);
|
|
3646
|
-
}
|
|
3647
|
-
}
|
|
3648
|
-
allDocuments.push(...documentMap.values());
|
|
3649
|
-
logger.info(
|
|
3650
|
-
`Aggregated ${allDocuments.length} unique documents from ${manifest.chunks.length} chunks`
|
|
3651
|
-
);
|
|
3652
|
-
return allDocuments;
|
|
3653
|
-
}
|
|
3654
|
-
/**
|
|
3655
|
-
* Load documents from a single chunk file
|
|
3656
|
-
*/
|
|
3657
|
-
async loadChunk(staticPath, chunk) {
|
|
3658
|
-
try {
|
|
3659
|
-
let chunkContent;
|
|
3660
|
-
let chunkPath;
|
|
3661
|
-
if (this.fs) {
|
|
3662
|
-
chunkPath = this.fs.joinPath(staticPath, chunk.path);
|
|
3663
|
-
chunkContent = await this.fs.readFile(chunkPath);
|
|
3664
|
-
} else {
|
|
3665
|
-
chunkPath = nodeFS2 && nodePath ? nodePath.join(staticPath, chunk.path) : `${staticPath}/${chunk.path}`;
|
|
3666
|
-
if (nodeFS2 && this.isLocalPath(staticPath)) {
|
|
3667
|
-
chunkContent = await nodeFS2.promises.readFile(chunkPath, "utf8");
|
|
3668
|
-
} else {
|
|
3669
|
-
const response = await fetch(chunkPath);
|
|
3670
|
-
if (!response.ok) {
|
|
3671
|
-
throw new Error(`Failed to fetch chunk: ${response.status} ${response.statusText}`);
|
|
3672
|
-
}
|
|
3673
|
-
chunkContent = await response.text();
|
|
3674
|
-
}
|
|
3675
|
-
}
|
|
3676
|
-
const documents = JSON.parse(chunkContent);
|
|
3677
|
-
if (!Array.isArray(documents)) {
|
|
3678
|
-
throw new Error("Chunk file does not contain an array of documents");
|
|
3679
|
-
}
|
|
3680
|
-
return documents;
|
|
3681
|
-
} catch (error) {
|
|
3682
|
-
const errorMessage = error instanceof FileSystemError ? error.message : `Failed to load chunk: ${error instanceof Error ? error.message : String(error)}`;
|
|
3683
|
-
throw new Error(errorMessage);
|
|
3684
|
-
}
|
|
3685
|
-
}
|
|
3686
|
-
/**
|
|
3687
|
-
* Upload documents to CouchDB in batches
|
|
3688
|
-
*/
|
|
3689
|
-
async uploadDocuments(documents, db) {
|
|
3690
|
-
const result = { restored: 0, errors: [], warnings: [] };
|
|
3691
|
-
const batchSize = this.options.chunkBatchSize;
|
|
3692
|
-
for (let i = 0; i < documents.length; i += batchSize) {
|
|
3693
|
-
const batch = documents.slice(i, i + batchSize);
|
|
3694
|
-
this.reportProgress(
|
|
3695
|
-
"documents",
|
|
3696
|
-
i,
|
|
3697
|
-
documents.length,
|
|
3698
|
-
`Uploading batch ${Math.floor(i / batchSize) + 1}...`
|
|
3699
|
-
);
|
|
3700
|
-
try {
|
|
3701
|
-
const docsToInsert = batch.map((doc) => {
|
|
3702
|
-
const cleanDoc = { ...doc };
|
|
3703
|
-
delete cleanDoc._rev;
|
|
3704
|
-
delete cleanDoc._attachments;
|
|
3705
|
-
return cleanDoc;
|
|
3706
|
-
});
|
|
3707
|
-
const bulkResult = await db.bulkDocs(docsToInsert);
|
|
3708
|
-
for (let j = 0; j < bulkResult.length; j++) {
|
|
3709
|
-
const docResult = bulkResult[j];
|
|
3710
|
-
const originalDoc = batch[j];
|
|
3711
|
-
if ("error" in docResult) {
|
|
3712
|
-
const errorMessage = `Failed to upload document ${originalDoc._id}: ${docResult.error} - ${docResult.reason}`;
|
|
3713
|
-
result.errors.push(errorMessage);
|
|
3714
|
-
logger.error(errorMessage);
|
|
3715
|
-
} else {
|
|
3716
|
-
result.restored++;
|
|
3717
|
-
}
|
|
3718
|
-
}
|
|
3719
|
-
} catch (error) {
|
|
3720
|
-
let errorMessage;
|
|
3721
|
-
if (error instanceof Error) {
|
|
3722
|
-
errorMessage = `Failed to upload document batch starting at index ${i}: ${error.message}`;
|
|
3723
|
-
} else if (error && typeof error === "object" && "message" in error) {
|
|
3724
|
-
errorMessage = `Failed to upload document batch starting at index ${i}: ${error.message}`;
|
|
3725
|
-
} else {
|
|
3726
|
-
errorMessage = `Failed to upload document batch starting at index ${i}: ${JSON.stringify(error)}`;
|
|
3727
|
-
}
|
|
3728
|
-
result.errors.push(errorMessage);
|
|
3729
|
-
logger.error(errorMessage);
|
|
3730
|
-
}
|
|
3731
|
-
}
|
|
3732
|
-
this.reportProgress(
|
|
3733
|
-
"documents",
|
|
3734
|
-
documents.length,
|
|
3735
|
-
documents.length,
|
|
3736
|
-
`Uploaded ${result.restored} documents`
|
|
3737
|
-
);
|
|
3738
|
-
return result;
|
|
3739
|
-
}
|
|
3740
|
-
/**
|
|
3741
|
-
* Upload attachments from filesystem to CouchDB
|
|
3742
|
-
*/
|
|
3743
|
-
async uploadAttachments(staticPath, documents, db) {
|
|
3744
|
-
const result = { restored: 0, errors: [], warnings: [] };
|
|
3745
|
-
let processedDocs = 0;
|
|
3746
|
-
for (const doc of documents) {
|
|
3747
|
-
this.reportProgress(
|
|
3748
|
-
"attachments",
|
|
3749
|
-
processedDocs,
|
|
3750
|
-
documents.length,
|
|
3751
|
-
`Processing attachments for ${doc._id}...`
|
|
3752
|
-
);
|
|
3753
|
-
processedDocs++;
|
|
3754
|
-
if (!doc._attachments) {
|
|
3755
|
-
continue;
|
|
3756
|
-
}
|
|
3757
|
-
for (const [attachmentName, attachmentMeta] of Object.entries(doc._attachments)) {
|
|
3758
|
-
try {
|
|
3759
|
-
const uploadResult = await this.uploadSingleAttachment(
|
|
3760
|
-
staticPath,
|
|
3761
|
-
doc._id,
|
|
3762
|
-
attachmentName,
|
|
3763
|
-
attachmentMeta,
|
|
3764
|
-
db
|
|
3765
|
-
);
|
|
3766
|
-
if (uploadResult.success) {
|
|
3767
|
-
result.restored++;
|
|
3768
|
-
} else {
|
|
3769
|
-
result.errors.push(uploadResult.error || "Unknown attachment upload error");
|
|
3770
|
-
}
|
|
3771
|
-
} catch (error) {
|
|
3772
|
-
const errorMessage = `Failed to upload attachment ${doc._id}/${attachmentName}: ${error instanceof Error ? error.message : String(error)}`;
|
|
3773
|
-
result.errors.push(errorMessage);
|
|
3774
|
-
logger.error(errorMessage);
|
|
3775
|
-
}
|
|
3776
|
-
}
|
|
3777
|
-
}
|
|
3778
|
-
this.reportProgress(
|
|
3779
|
-
"attachments",
|
|
3780
|
-
documents.length,
|
|
3781
|
-
documents.length,
|
|
3782
|
-
`Uploaded ${result.restored} attachments`
|
|
3783
|
-
);
|
|
3784
|
-
return result;
|
|
3785
|
-
}
|
|
3786
|
-
/**
|
|
3787
|
-
* Upload a single attachment file
|
|
3788
|
-
*/
|
|
3789
|
-
async uploadSingleAttachment(staticPath, docId, attachmentName, attachmentMeta, db) {
|
|
3790
|
-
const result = {
|
|
3791
|
-
success: false,
|
|
3792
|
-
attachmentName,
|
|
3793
|
-
docId
|
|
3794
|
-
};
|
|
3795
|
-
try {
|
|
3796
|
-
if (!attachmentMeta.path) {
|
|
3797
|
-
result.error = "Attachment metadata missing file path";
|
|
3798
|
-
return result;
|
|
3799
|
-
}
|
|
3800
|
-
let attachmentData;
|
|
3801
|
-
let attachmentPath;
|
|
3802
|
-
if (this.fs) {
|
|
3803
|
-
attachmentPath = this.fs.joinPath(staticPath, attachmentMeta.path);
|
|
3804
|
-
attachmentData = await this.fs.readBinary(attachmentPath);
|
|
3805
|
-
} else {
|
|
3806
|
-
attachmentPath = nodeFS2 && nodePath ? nodePath.join(staticPath, attachmentMeta.path) : `${staticPath}/${attachmentMeta.path}`;
|
|
3807
|
-
if (nodeFS2 && this.isLocalPath(staticPath)) {
|
|
3808
|
-
attachmentData = await nodeFS2.promises.readFile(attachmentPath);
|
|
3809
|
-
} else {
|
|
3810
|
-
const response = await fetch(attachmentPath);
|
|
3811
|
-
if (!response.ok) {
|
|
3812
|
-
result.error = `Failed to fetch attachment: ${response.status} ${response.statusText}`;
|
|
3813
|
-
return result;
|
|
3814
|
-
}
|
|
3815
|
-
attachmentData = await response.arrayBuffer();
|
|
3816
|
-
}
|
|
3817
|
-
}
|
|
3818
|
-
const doc = await db.get(docId);
|
|
3819
|
-
await db.putAttachment(
|
|
3820
|
-
docId,
|
|
3821
|
-
attachmentName,
|
|
3822
|
-
doc._rev,
|
|
3823
|
-
attachmentData,
|
|
3824
|
-
// PouchDB accepts both ArrayBuffer and Buffer
|
|
3825
|
-
attachmentMeta.content_type
|
|
3826
|
-
);
|
|
3827
|
-
result.success = true;
|
|
3828
|
-
} catch (error) {
|
|
3829
|
-
result.error = error instanceof Error ? error.message : String(error);
|
|
3830
|
-
}
|
|
3831
|
-
return result;
|
|
3832
|
-
}
|
|
3833
|
-
/**
|
|
3834
|
-
* Restore CourseConfig document from manifest
|
|
3835
|
-
*/
|
|
3836
|
-
async restoreCourseConfig(manifest, targetDB) {
|
|
3837
|
-
const results = {
|
|
3838
|
-
restored: 0,
|
|
3839
|
-
errors: [],
|
|
3840
|
-
warnings: []
|
|
3841
|
-
};
|
|
3842
|
-
try {
|
|
3843
|
-
if (!manifest.courseConfig) {
|
|
3844
|
-
results.warnings.push(
|
|
3845
|
-
"No courseConfig found in manifest, skipping CourseConfig document creation"
|
|
3846
|
-
);
|
|
3847
|
-
return results;
|
|
3848
|
-
}
|
|
3849
|
-
const courseConfigDoc = {
|
|
3850
|
-
_id: "CourseConfig",
|
|
3851
|
-
...manifest.courseConfig,
|
|
3852
|
-
courseID: manifest.courseId
|
|
3853
|
-
};
|
|
3854
|
-
delete courseConfigDoc._rev;
|
|
3855
|
-
await targetDB.put(courseConfigDoc);
|
|
3856
|
-
results.restored = 1;
|
|
3857
|
-
logger.info(`CourseConfig document created for course: ${manifest.courseId}`);
|
|
3858
|
-
} catch (error) {
|
|
3859
|
-
const errorMessage = error instanceof Error ? error.message : JSON.stringify(error);
|
|
3860
|
-
results.errors.push(`Failed to restore CourseConfig: ${errorMessage}`);
|
|
3861
|
-
logger.error("CourseConfig restoration failed:", error);
|
|
3862
|
-
}
|
|
3863
|
-
return results;
|
|
3864
|
-
}
|
|
3865
|
-
/**
|
|
3866
|
-
* Calculate expected document counts from manifest
|
|
3867
|
-
*/
|
|
3868
|
-
calculateExpectedCounts(manifest) {
|
|
3869
|
-
const counts = {};
|
|
3870
|
-
for (const chunk of manifest.chunks) {
|
|
3871
|
-
counts[chunk.docType] = (counts[chunk.docType] || 0) + chunk.documentCount;
|
|
3872
|
-
}
|
|
3873
|
-
if (manifest.designDocs.length > 0) {
|
|
3874
|
-
counts["_design"] = manifest.designDocs.length;
|
|
3875
|
-
}
|
|
3876
|
-
return counts;
|
|
3877
|
-
}
|
|
3878
|
-
/**
|
|
3879
|
-
* Clean up database after failed migration
|
|
3880
|
-
*/
|
|
3881
|
-
async cleanupFailedMigration(db) {
|
|
3882
|
-
logger.info("Cleaning up failed migration...");
|
|
3883
|
-
try {
|
|
3884
|
-
const allDocs = await db.allDocs();
|
|
3885
|
-
const docsToDelete = allDocs.rows.map((row) => ({
|
|
3886
|
-
_id: row.id,
|
|
3887
|
-
_rev: row.value.rev,
|
|
3888
|
-
_deleted: true
|
|
3889
|
-
}));
|
|
3890
|
-
if (docsToDelete.length > 0) {
|
|
3891
|
-
await db.bulkDocs(docsToDelete);
|
|
3892
|
-
logger.info(`Cleaned up ${docsToDelete.length} documents from failed migration`);
|
|
3893
|
-
}
|
|
3894
|
-
} catch (error) {
|
|
3895
|
-
logger.error("Failed to cleanup documents:", error);
|
|
3896
|
-
throw error;
|
|
3897
|
-
}
|
|
3898
|
-
}
|
|
3899
|
-
/**
|
|
3900
|
-
* Report progress to callback if available
|
|
3901
|
-
*/
|
|
3902
|
-
reportProgress(phase, current, total, message) {
|
|
3903
|
-
if (this.progressCallback) {
|
|
3904
|
-
this.progressCallback({
|
|
3905
|
-
phase,
|
|
3906
|
-
current,
|
|
3907
|
-
total,
|
|
3908
|
-
message
|
|
3909
|
-
});
|
|
3910
|
-
}
|
|
3911
|
-
}
|
|
3912
|
-
/**
|
|
3913
|
-
* Check if a path is a local file path (vs URL)
|
|
3914
|
-
*/
|
|
3915
|
-
isLocalPath(path2) {
|
|
3916
|
-
return !path2.startsWith("http://") && !path2.startsWith("https://");
|
|
3917
|
-
}
|
|
3918
|
-
};
|
|
3919
|
-
}
|
|
3920
|
-
});
|
|
3921
|
-
|
|
3922
|
-
// src/util/migrator/index.ts
|
|
3923
|
-
var init_migrator = __esm({
|
|
3924
|
-
"src/util/migrator/index.ts"() {
|
|
3925
|
-
"use strict";
|
|
3926
|
-
init_StaticToCouchDBMigrator();
|
|
3927
|
-
init_validation();
|
|
3928
|
-
init_FileSystemAdapter();
|
|
3929
|
-
}
|
|
3930
|
-
});
|
|
3931
|
-
|
|
3932
|
-
// src/util/index.ts
|
|
3933
|
-
var init_util2 = __esm({
|
|
3934
|
-
"src/util/index.ts"() {
|
|
3935
|
-
"use strict";
|
|
3936
|
-
init_Loggable();
|
|
3937
|
-
init_packer();
|
|
3938
|
-
init_migrator();
|
|
3939
|
-
init_dataDirectory();
|
|
3940
|
-
}
|
|
3941
|
-
});
|
|
3942
|
-
|
|
3943
|
-
// src/study/SourceMixer.ts
|
|
3944
|
-
var init_SourceMixer = __esm({
|
|
3945
|
-
"src/study/SourceMixer.ts"() {
|
|
3946
|
-
"use strict";
|
|
3947
|
-
}
|
|
3948
|
-
});
|
|
3949
|
-
|
|
3950
|
-
// src/study/MixerDebugger.ts
|
|
3951
|
-
function printMixerSummary(run) {
|
|
3952
|
-
console.group(`\u{1F3A8} Mixer Run: ${run.mixerType}`);
|
|
3953
|
-
logger.info(`Run ID: ${run.runId}`);
|
|
3954
|
-
logger.info(`Time: ${run.timestamp.toISOString()}`);
|
|
3955
|
-
logger.info(
|
|
3956
|
-
`Config: limit=${run.requestedLimit}${run.quotaPerSource ? `, quota/source=${run.quotaPerSource}` : ""}`
|
|
3957
|
-
);
|
|
3958
|
-
console.group(`\u{1F4E5} Input: ${run.sourceSummaries.length} sources`);
|
|
3959
|
-
for (const src of run.sourceSummaries) {
|
|
3960
|
-
logger.info(
|
|
3961
|
-
` ${src.sourceName || src.sourceId}: ${src.totalCards} cards (${src.reviewCount} reviews, ${src.newCount} new)`
|
|
3962
|
-
);
|
|
3963
|
-
logger.info(` Score range: [${src.scoreRange[0].toFixed(2)}, ${src.scoreRange[1].toFixed(2)}], avg: ${src.avgScore.toFixed(2)}`);
|
|
3964
|
-
}
|
|
3965
|
-
console.groupEnd();
|
|
3966
|
-
console.group(`\u{1F4E4} Output: ${run.finalCount} cards selected (${run.reviewsSelected} reviews, ${run.newSelected} new)`);
|
|
3967
|
-
for (const breakdown of run.sourceBreakdowns) {
|
|
3968
|
-
const name = breakdown.sourceName || breakdown.sourceId;
|
|
3969
|
-
logger.info(
|
|
3970
|
-
` ${name}: ${breakdown.totalSelected} selected (${breakdown.reviewsSelected} reviews, ${breakdown.newSelected} new) - ${breakdown.selectionRate.toFixed(1)}% selection rate`
|
|
3971
|
-
);
|
|
3972
|
-
}
|
|
3973
|
-
console.groupEnd();
|
|
3974
|
-
console.groupEnd();
|
|
3975
|
-
}
|
|
3976
|
-
function mountMixerDebugger() {
|
|
3977
|
-
if (typeof window === "undefined") return;
|
|
3978
|
-
const win = window;
|
|
3979
|
-
win.skuilder = win.skuilder || {};
|
|
3980
|
-
win.skuilder.mixer = mixerDebugAPI;
|
|
3981
|
-
}
|
|
3982
|
-
var runHistory2, mixerDebugAPI;
|
|
3983
|
-
var init_MixerDebugger = __esm({
|
|
3984
|
-
"src/study/MixerDebugger.ts"() {
|
|
3985
|
-
"use strict";
|
|
3986
|
-
init_logger();
|
|
3987
|
-
init_navigators();
|
|
3988
|
-
runHistory2 = [];
|
|
3989
|
-
mixerDebugAPI = {
|
|
3990
|
-
/**
|
|
3991
|
-
* Get raw run history for programmatic access.
|
|
3992
|
-
*/
|
|
3993
|
-
get runs() {
|
|
3994
|
-
return [...runHistory2];
|
|
3995
|
-
},
|
|
3996
|
-
/**
|
|
3997
|
-
* Show summary of a specific mixer run.
|
|
3998
|
-
*/
|
|
3999
|
-
showRun(idOrIndex = 0) {
|
|
4000
|
-
if (runHistory2.length === 0) {
|
|
4001
|
-
logger.info("[Mixer Debug] No runs captured yet.");
|
|
4002
|
-
return;
|
|
4003
|
-
}
|
|
4004
|
-
let run;
|
|
4005
|
-
if (typeof idOrIndex === "number") {
|
|
4006
|
-
run = runHistory2[idOrIndex];
|
|
4007
|
-
if (!run) {
|
|
4008
|
-
logger.info(`[Mixer Debug] No run found at index ${idOrIndex}. History length: ${runHistory2.length}`);
|
|
4009
|
-
return;
|
|
4010
|
-
}
|
|
4011
|
-
} else {
|
|
4012
|
-
run = runHistory2.find((r) => r.runId.endsWith(idOrIndex));
|
|
4013
|
-
if (!run) {
|
|
4014
|
-
logger.info(`[Mixer Debug] No run found matching ID '${idOrIndex}'.`);
|
|
4015
|
-
return;
|
|
4016
|
-
}
|
|
4017
|
-
}
|
|
4018
|
-
printMixerSummary(run);
|
|
4019
|
-
},
|
|
4020
|
-
/**
|
|
4021
|
-
* Show summary of the last mixer run.
|
|
4022
|
-
*/
|
|
4023
|
-
showLastMix() {
|
|
4024
|
-
this.showRun(0);
|
|
4025
|
-
},
|
|
4026
|
-
/**
|
|
4027
|
-
* Explain source balance in the last run.
|
|
4028
|
-
*/
|
|
4029
|
-
explainSourceBalance() {
|
|
4030
|
-
if (runHistory2.length === 0) {
|
|
4031
|
-
logger.info("[Mixer Debug] No runs captured yet.");
|
|
4032
|
-
return;
|
|
4033
|
-
}
|
|
4034
|
-
const run = runHistory2[0];
|
|
4035
|
-
console.group("\u2696\uFE0F Source Balance Analysis");
|
|
4036
|
-
logger.info(`Mixer: ${run.mixerType}`);
|
|
4037
|
-
logger.info(`Requested limit: ${run.requestedLimit}`);
|
|
4038
|
-
if (run.quotaPerSource) {
|
|
4039
|
-
logger.info(`Quota per source: ${run.quotaPerSource}`);
|
|
4040
|
-
}
|
|
4041
|
-
console.group("Input Distribution:");
|
|
4042
|
-
for (const src of run.sourceSummaries) {
|
|
4043
|
-
const name = src.sourceName || src.sourceId;
|
|
4044
|
-
logger.info(`${name}:`);
|
|
4045
|
-
logger.info(` Provided: ${src.totalCards} cards (${src.reviewCount} reviews, ${src.newCount} new)`);
|
|
4046
|
-
logger.info(` Score range: [${src.scoreRange[0].toFixed(2)}, ${src.scoreRange[1].toFixed(2)}]`);
|
|
4047
|
-
}
|
|
4048
|
-
console.groupEnd();
|
|
4049
|
-
console.group("Selection Results:");
|
|
4050
|
-
for (const breakdown of run.sourceBreakdowns) {
|
|
4051
|
-
const name = breakdown.sourceName || breakdown.sourceId;
|
|
4052
|
-
logger.info(`${name}:`);
|
|
4053
|
-
logger.info(
|
|
4054
|
-
` Selected: ${breakdown.totalSelected}/${breakdown.reviewsProvided + breakdown.newProvided} (${breakdown.selectionRate.toFixed(1)}%)`
|
|
4055
|
-
);
|
|
4056
|
-
logger.info(` Reviews: ${breakdown.reviewsSelected}/${breakdown.reviewsProvided}`);
|
|
4057
|
-
logger.info(` New: ${breakdown.newSelected}/${breakdown.newProvided}`);
|
|
4058
|
-
if (breakdown.reviewsProvided > 0 && breakdown.reviewsSelected === 0) {
|
|
4059
|
-
logger.info(` \u26A0\uFE0F Had reviews but none selected!`);
|
|
4060
|
-
}
|
|
4061
|
-
if (breakdown.totalSelected === 0 && breakdown.reviewsProvided + breakdown.newProvided > 0) {
|
|
4062
|
-
logger.info(` \u26A0\uFE0F Had cards but none selected!`);
|
|
4063
|
-
}
|
|
4064
|
-
}
|
|
4065
|
-
console.groupEnd();
|
|
4066
|
-
const selectionRates = run.sourceBreakdowns.map((b) => b.selectionRate);
|
|
4067
|
-
const avgRate = selectionRates.reduce((a, b) => a + b, 0) / selectionRates.length;
|
|
4068
|
-
const maxDeviation = Math.max(...selectionRates.map((r) => Math.abs(r - avgRate)));
|
|
4069
|
-
if (maxDeviation > 20) {
|
|
4070
|
-
logger.info(`
|
|
4071
|
-
\u26A0\uFE0F Significant imbalance detected (max deviation: ${maxDeviation.toFixed(1)}%)`);
|
|
4072
|
-
logger.info("Possible causes:");
|
|
4073
|
-
logger.info(" - Score range differences between sources");
|
|
4074
|
-
logger.info(" - One source has much better quality cards");
|
|
4075
|
-
logger.info(" - Different card availability (reviews vs new)");
|
|
4076
|
-
}
|
|
4077
|
-
console.groupEnd();
|
|
4078
|
-
},
|
|
4079
|
-
/**
|
|
4080
|
-
* Compare score distributions across sources.
|
|
4081
|
-
*/
|
|
4082
|
-
compareScores() {
|
|
4083
|
-
if (runHistory2.length === 0) {
|
|
4084
|
-
logger.info("[Mixer Debug] No runs captured yet.");
|
|
4085
|
-
return;
|
|
4086
|
-
}
|
|
4087
|
-
const run = runHistory2[0];
|
|
4088
|
-
console.group("\u{1F4CA} Score Distribution Comparison");
|
|
4089
|
-
console.table(
|
|
4090
|
-
run.sourceSummaries.map((src) => ({
|
|
4091
|
-
source: src.sourceName || src.sourceId,
|
|
4092
|
-
cards: src.totalCards,
|
|
4093
|
-
min: src.bottomScore.toFixed(3),
|
|
4094
|
-
max: src.topScore.toFixed(3),
|
|
4095
|
-
avg: src.avgScore.toFixed(3),
|
|
4096
|
-
range: (src.topScore - src.bottomScore).toFixed(3)
|
|
4097
|
-
}))
|
|
4098
|
-
);
|
|
4099
|
-
const ranges = run.sourceSummaries.map((s) => s.topScore - s.bottomScore);
|
|
4100
|
-
const avgScores = run.sourceSummaries.map((s) => s.avgScore);
|
|
4101
|
-
const rangeDiff = Math.max(...ranges) - Math.min(...ranges);
|
|
4102
|
-
const avgDiff = Math.max(...avgScores) - Math.min(...avgScores);
|
|
4103
|
-
if (rangeDiff > 0.3 || avgDiff > 0.2) {
|
|
4104
|
-
logger.info("\n\u26A0\uFE0F Significant score distribution differences detected");
|
|
4105
|
-
logger.info(
|
|
4106
|
-
"This may cause one source to dominate selection if using global sorting (not quota-based)"
|
|
4107
|
-
);
|
|
4108
|
-
}
|
|
4109
|
-
console.groupEnd();
|
|
4110
|
-
},
|
|
4111
|
-
/**
|
|
4112
|
-
* Show detailed information for a specific card.
|
|
4113
|
-
*/
|
|
4114
|
-
showCard(cardId) {
|
|
4115
|
-
for (const run of runHistory2) {
|
|
4116
|
-
const card = run.cards.find((c) => c.cardId === cardId);
|
|
4117
|
-
if (card) {
|
|
4118
|
-
const source = run.sourceSummaries.find((s) => s.sourceIndex === card.sourceIndex);
|
|
4119
|
-
console.group(`\u{1F3B4} Card: ${cardId}`);
|
|
4120
|
-
logger.info(`Course: ${card.courseId}`);
|
|
4121
|
-
logger.info(`Source: ${source?.sourceName || source?.sourceId || "unknown"}`);
|
|
4122
|
-
logger.info(`Origin: ${card.origin}`);
|
|
4123
|
-
logger.info(`Score: ${card.score.toFixed(3)}`);
|
|
4124
|
-
if (card.rankInSource) {
|
|
4125
|
-
logger.info(`Rank in source: #${card.rankInSource}`);
|
|
4126
|
-
}
|
|
4127
|
-
if (card.rankInMix) {
|
|
4128
|
-
logger.info(`Rank in mixed results: #${card.rankInMix}`);
|
|
4129
|
-
}
|
|
4130
|
-
logger.info(`Selected: ${card.selected ? "Yes \u2705" : "No \u274C"}`);
|
|
4131
|
-
if (!card.selected && card.rankInSource) {
|
|
4132
|
-
logger.info("\nWhy not selected:");
|
|
4133
|
-
if (run.quotaPerSource && card.rankInSource > run.quotaPerSource) {
|
|
4134
|
-
logger.info(` - Ranked #${card.rankInSource} in source, but quota was ${run.quotaPerSource}`);
|
|
4135
|
-
}
|
|
4136
|
-
logger.info(" - Check score compared to selected cards using .showRun()");
|
|
4137
|
-
}
|
|
4138
|
-
console.groupEnd();
|
|
4139
|
-
return;
|
|
4140
|
-
}
|
|
4141
|
-
}
|
|
4142
|
-
logger.info(`[Mixer Debug] Card '${cardId}' not found in recent runs.`);
|
|
4143
|
-
},
|
|
4144
|
-
/**
|
|
4145
|
-
* Show all runs in compact format.
|
|
4146
|
-
*/
|
|
4147
|
-
listRuns() {
|
|
4148
|
-
if (runHistory2.length === 0) {
|
|
4149
|
-
logger.info("[Mixer Debug] No runs captured yet.");
|
|
4150
|
-
return;
|
|
4151
|
-
}
|
|
4152
|
-
console.table(
|
|
4153
|
-
runHistory2.map((r) => ({
|
|
4154
|
-
id: r.runId.slice(-8),
|
|
4155
|
-
time: r.timestamp.toLocaleTimeString(),
|
|
4156
|
-
mixer: r.mixerType,
|
|
4157
|
-
sources: r.sourceSummaries.length,
|
|
4158
|
-
selected: r.finalCount,
|
|
4159
|
-
reviews: r.reviewsSelected,
|
|
4160
|
-
new: r.newSelected
|
|
4161
|
-
}))
|
|
2885
|
+
reason
|
|
2886
|
+
}
|
|
2887
|
+
]
|
|
2888
|
+
};
|
|
2889
|
+
})
|
|
4162
2890
|
);
|
|
4163
|
-
|
|
4164
|
-
|
|
4165
|
-
* Export run history as JSON for bug reports.
|
|
4166
|
-
*/
|
|
4167
|
-
export() {
|
|
4168
|
-
const json = JSON.stringify(runHistory2, null, 2);
|
|
4169
|
-
logger.info("[Mixer Debug] Run history exported. Copy the returned string or use:");
|
|
4170
|
-
logger.info(" copy(window.skuilder.mixer.export())");
|
|
4171
|
-
return json;
|
|
4172
|
-
},
|
|
4173
|
-
/**
|
|
4174
|
-
* Clear run history.
|
|
4175
|
-
*/
|
|
4176
|
-
clear() {
|
|
4177
|
-
runHistory2.length = 0;
|
|
4178
|
-
logger.info("[Mixer Debug] Run history cleared.");
|
|
4179
|
-
},
|
|
2891
|
+
return adjusted;
|
|
2892
|
+
}
|
|
4180
2893
|
/**
|
|
4181
|
-
*
|
|
2894
|
+
* Legacy getWeightedCards - now throws as filters should not be used as generators.
|
|
2895
|
+
*
|
|
2896
|
+
* Use transform() via Pipeline instead.
|
|
4182
2897
|
*/
|
|
4183
|
-
|
|
4184
|
-
|
|
4185
|
-
|
|
4186
|
-
|
|
4187
|
-
Commands:
|
|
4188
|
-
.showLastMix() Show summary of most recent mixer run
|
|
4189
|
-
.showRun(id|index) Show summary of a specific run (by index or ID suffix)
|
|
4190
|
-
.explainSourceBalance() Analyze source balance and selection patterns
|
|
4191
|
-
.compareScores() Compare score distributions across sources
|
|
4192
|
-
.showCard(cardId) Show mixer decisions for a specific card
|
|
4193
|
-
.listRuns() List all captured runs in table format
|
|
4194
|
-
.export() Export run history as JSON for bug reports
|
|
4195
|
-
.clear() Clear run history
|
|
4196
|
-
.runs Access raw run history array
|
|
4197
|
-
.help() Show this help message
|
|
4198
|
-
|
|
4199
|
-
Example:
|
|
4200
|
-
window.skuilder.mixer.showLastMix()
|
|
4201
|
-
window.skuilder.mixer.explainSourceBalance()
|
|
4202
|
-
window.skuilder.mixer.compareScores()
|
|
4203
|
-
`);
|
|
2898
|
+
async getWeightedCards(_limit) {
|
|
2899
|
+
throw new Error(
|
|
2900
|
+
"RelativePriorityNavigator is a filter and should not be used as a generator. Use Pipeline with a generator and this filter via transform()."
|
|
2901
|
+
);
|
|
4204
2902
|
}
|
|
4205
2903
|
};
|
|
4206
|
-
mountMixerDebugger();
|
|
4207
2904
|
}
|
|
4208
2905
|
});
|
|
4209
2906
|
|
|
4210
|
-
// src/
|
|
4211
|
-
|
|
4212
|
-
|
|
4213
|
-
|
|
4214
|
-
|
|
4215
|
-
}
|
|
4216
|
-
const latest = activeSession.queueSnapshots[activeSession.queueSnapshots.length - 1] || activeSession.initialQueues;
|
|
4217
|
-
console.group("\u{1F4CA} Current Queue State");
|
|
4218
|
-
logger.info(`Review Queue: ${latest.reviewQLength} cards`);
|
|
4219
|
-
if (latest.reviewQNext3 && latest.reviewQNext3.length > 0) {
|
|
4220
|
-
logger.info(` Next: ${latest.reviewQNext3.join(", ")}`);
|
|
4221
|
-
}
|
|
4222
|
-
logger.info(`New Queue: ${latest.newQLength} cards`);
|
|
4223
|
-
if (latest.newQNext3 && latest.newQNext3.length > 0) {
|
|
4224
|
-
logger.info(` Next: ${latest.newQNext3.join(", ")}`);
|
|
2907
|
+
// src/core/navigators/filters/types.ts
|
|
2908
|
+
var types_exports2 = {};
|
|
2909
|
+
var init_types2 = __esm({
|
|
2910
|
+
"src/core/navigators/filters/types.ts"() {
|
|
2911
|
+
"use strict";
|
|
4225
2912
|
}
|
|
4226
|
-
|
|
4227
|
-
|
|
4228
|
-
|
|
4229
|
-
|
|
4230
|
-
|
|
4231
|
-
|
|
4232
|
-
|
|
4233
|
-
|
|
2913
|
+
});
|
|
2914
|
+
|
|
2915
|
+
// src/core/navigators/filters/userGoalStub.ts
|
|
2916
|
+
var userGoalStub_exports = {};
|
|
2917
|
+
__export(userGoalStub_exports, {
|
|
2918
|
+
USER_GOAL_NAVIGATOR_STUB: () => USER_GOAL_NAVIGATOR_STUB
|
|
2919
|
+
});
|
|
2920
|
+
var USER_GOAL_NAVIGATOR_STUB;
|
|
2921
|
+
var init_userGoalStub = __esm({
|
|
2922
|
+
"src/core/navigators/filters/userGoalStub.ts"() {
|
|
2923
|
+
"use strict";
|
|
2924
|
+
USER_GOAL_NAVIGATOR_STUB = true;
|
|
4234
2925
|
}
|
|
4235
|
-
|
|
4236
|
-
|
|
4237
|
-
|
|
4238
|
-
|
|
2926
|
+
});
|
|
2927
|
+
|
|
2928
|
+
// import("./filters/**/*") in src/core/navigators/index.ts
|
|
2929
|
+
var globImport_filters;
|
|
2930
|
+
var init_2 = __esm({
|
|
2931
|
+
'import("./filters/**/*") in src/core/navigators/index.ts'() {
|
|
2932
|
+
globImport_filters = __glob({
|
|
2933
|
+
"./filters/WeightedFilter.ts": () => Promise.resolve().then(() => (init_WeightedFilter(), WeightedFilter_exports)),
|
|
2934
|
+
"./filters/eloDistance.ts": () => Promise.resolve().then(() => (init_eloDistance(), eloDistance_exports)),
|
|
2935
|
+
"./filters/hierarchyDefinition.ts": () => Promise.resolve().then(() => (init_hierarchyDefinition(), hierarchyDefinition_exports)),
|
|
2936
|
+
"./filters/index.ts": () => Promise.resolve().then(() => (init_filters(), filters_exports)),
|
|
2937
|
+
"./filters/inferredPreferenceStub.ts": () => Promise.resolve().then(() => (init_inferredPreferenceStub(), inferredPreferenceStub_exports)),
|
|
2938
|
+
"./filters/interferenceMitigator.ts": () => Promise.resolve().then(() => (init_interferenceMitigator(), interferenceMitigator_exports)),
|
|
2939
|
+
"./filters/relativePriority.ts": () => Promise.resolve().then(() => (init_relativePriority(), relativePriority_exports)),
|
|
2940
|
+
"./filters/types.ts": () => Promise.resolve().then(() => (init_types2(), types_exports2)),
|
|
2941
|
+
"./filters/userGoalStub.ts": () => Promise.resolve().then(() => (init_userGoalStub(), userGoalStub_exports)),
|
|
2942
|
+
"./filters/userTagPreference.ts": () => Promise.resolve().then(() => (init_userTagPreference(), userTagPreference_exports))
|
|
2943
|
+
});
|
|
4239
2944
|
}
|
|
4240
|
-
|
|
4241
|
-
|
|
4242
|
-
|
|
4243
|
-
|
|
4244
|
-
|
|
4245
|
-
|
|
4246
|
-
|
|
4247
|
-
queue: p.queueSource,
|
|
4248
|
-
score: p.score?.toFixed(3) || "-",
|
|
4249
|
-
time: p.timestamp.toLocaleTimeString()
|
|
4250
|
-
}))
|
|
4251
|
-
);
|
|
2945
|
+
});
|
|
2946
|
+
|
|
2947
|
+
// src/core/orchestration/gradient.ts
|
|
2948
|
+
var init_gradient = __esm({
|
|
2949
|
+
"src/core/orchestration/gradient.ts"() {
|
|
2950
|
+
"use strict";
|
|
2951
|
+
init_logger();
|
|
4252
2952
|
}
|
|
4253
|
-
|
|
4254
|
-
|
|
4255
|
-
|
|
4256
|
-
|
|
4257
|
-
|
|
4258
|
-
|
|
4259
|
-
|
|
2953
|
+
});
|
|
2954
|
+
|
|
2955
|
+
// src/core/orchestration/learning.ts
|
|
2956
|
+
var init_learning = __esm({
|
|
2957
|
+
"src/core/orchestration/learning.ts"() {
|
|
2958
|
+
"use strict";
|
|
2959
|
+
init_contentNavigationStrategy();
|
|
2960
|
+
init_types_legacy();
|
|
2961
|
+
init_logger();
|
|
4260
2962
|
}
|
|
4261
|
-
|
|
4262
|
-
|
|
4263
|
-
|
|
4264
|
-
|
|
4265
|
-
|
|
4266
|
-
|
|
4267
|
-
if (!courseOrigins.has(name)) {
|
|
4268
|
-
courseOrigins.set(name, { review: 0, new: 0, failed: 0 });
|
|
4269
|
-
}
|
|
4270
|
-
const origins = courseOrigins.get(name);
|
|
4271
|
-
origins[p.origin]++;
|
|
4272
|
-
});
|
|
4273
|
-
logger.info("Course distribution:");
|
|
4274
|
-
console.table(
|
|
4275
|
-
Array.from(courseCounts.entries()).map(([course, count]) => {
|
|
4276
|
-
const origins = courseOrigins.get(course);
|
|
4277
|
-
return {
|
|
4278
|
-
course,
|
|
4279
|
-
total: count,
|
|
4280
|
-
reviews: origins.review,
|
|
4281
|
-
new: origins.new,
|
|
4282
|
-
failed: origins.failed,
|
|
4283
|
-
percentage: (count / session.presentations.length * 100).toFixed(1) + "%"
|
|
4284
|
-
};
|
|
4285
|
-
})
|
|
4286
|
-
);
|
|
4287
|
-
if (session.presentations.length > 0) {
|
|
4288
|
-
logger.info("\nPresentation sequence (first 20):");
|
|
4289
|
-
const sequence = session.presentations.slice(0, 20).map((p, idx) => `${idx + 1}. ${p.courseName || p.courseId.slice(0, 8)} (${p.origin})`).join("\n");
|
|
4290
|
-
logger.info(sequence);
|
|
2963
|
+
});
|
|
2964
|
+
|
|
2965
|
+
// src/core/orchestration/signal.ts
|
|
2966
|
+
var init_signal = __esm({
|
|
2967
|
+
"src/core/orchestration/signal.ts"() {
|
|
2968
|
+
"use strict";
|
|
4291
2969
|
}
|
|
4292
|
-
|
|
4293
|
-
|
|
4294
|
-
|
|
4295
|
-
|
|
4296
|
-
|
|
4297
|
-
|
|
4298
|
-
|
|
4299
|
-
|
|
4300
|
-
|
|
4301
|
-
currentCluster = 1;
|
|
4302
|
-
}
|
|
2970
|
+
});
|
|
2971
|
+
|
|
2972
|
+
// src/core/orchestration/recording.ts
|
|
2973
|
+
var init_recording = __esm({
|
|
2974
|
+
"src/core/orchestration/recording.ts"() {
|
|
2975
|
+
"use strict";
|
|
2976
|
+
init_signal();
|
|
2977
|
+
init_types_legacy();
|
|
2978
|
+
init_logger();
|
|
4303
2979
|
}
|
|
4304
|
-
|
|
4305
|
-
|
|
4306
|
-
|
|
4307
|
-
|
|
2980
|
+
});
|
|
2981
|
+
|
|
2982
|
+
// src/core/orchestration/index.ts
|
|
2983
|
+
function fnv1a(str) {
|
|
2984
|
+
let hash = 2166136261;
|
|
2985
|
+
for (let i = 0; i < str.length; i++) {
|
|
2986
|
+
hash ^= str.charCodeAt(i);
|
|
2987
|
+
hash = Math.imul(hash, 16777619);
|
|
4308
2988
|
}
|
|
4309
|
-
|
|
2989
|
+
return hash >>> 0;
|
|
4310
2990
|
}
|
|
4311
|
-
function
|
|
4312
|
-
|
|
4313
|
-
const
|
|
4314
|
-
|
|
4315
|
-
|
|
2991
|
+
function computeDeviation(userId, strategyId, salt) {
|
|
2992
|
+
const input = `${userId}:${strategyId}:${salt}`;
|
|
2993
|
+
const hash = fnv1a(input);
|
|
2994
|
+
const normalized = hash / 4294967296;
|
|
2995
|
+
return normalized * 2 - 1;
|
|
4316
2996
|
}
|
|
4317
|
-
|
|
4318
|
-
|
|
4319
|
-
|
|
4320
|
-
|
|
4321
|
-
|
|
4322
|
-
|
|
4323
|
-
|
|
4324
|
-
|
|
4325
|
-
|
|
4326
|
-
|
|
4327
|
-
|
|
4328
|
-
|
|
4329
|
-
|
|
4330
|
-
|
|
4331
|
-
|
|
4332
|
-
|
|
4333
|
-
|
|
4334
|
-
|
|
4335
|
-
|
|
4336
|
-
|
|
4337
|
-
|
|
4338
|
-
|
|
4339
|
-
|
|
4340
|
-
|
|
4341
|
-
|
|
4342
|
-
|
|
4343
|
-
|
|
4344
|
-
|
|
4345
|
-
*/
|
|
4346
|
-
showHistory(sessionIndex = 0) {
|
|
4347
|
-
showPresentationHistory(sessionIndex);
|
|
4348
|
-
},
|
|
4349
|
-
/**
|
|
4350
|
-
* Analyze course interleaving pattern.
|
|
4351
|
-
*/
|
|
4352
|
-
showInterleaving(sessionIndex = 0) {
|
|
4353
|
-
showInterleaving(sessionIndex);
|
|
4354
|
-
},
|
|
4355
|
-
/**
|
|
4356
|
-
* List all tracked sessions.
|
|
4357
|
-
*/
|
|
4358
|
-
listSessions() {
|
|
4359
|
-
if (activeSession) {
|
|
4360
|
-
logger.info(`Active session: ${activeSession.sessionId} (${activeSession.presentations.length} cards presented)`);
|
|
4361
|
-
}
|
|
4362
|
-
if (sessionHistory.length === 0) {
|
|
4363
|
-
logger.info("[Session Debug] No completed sessions in history.");
|
|
4364
|
-
return;
|
|
4365
|
-
}
|
|
4366
|
-
console.table(
|
|
4367
|
-
sessionHistory.map((s, idx) => ({
|
|
4368
|
-
index: idx,
|
|
4369
|
-
id: s.sessionId.slice(-8),
|
|
4370
|
-
started: s.startTime.toLocaleTimeString(),
|
|
4371
|
-
ended: s.endTime?.toLocaleTimeString() || "incomplete",
|
|
4372
|
-
cards: s.presentations.length
|
|
4373
|
-
}))
|
|
4374
|
-
);
|
|
4375
|
-
},
|
|
4376
|
-
/**
|
|
4377
|
-
* Export session history as JSON for bug reports.
|
|
4378
|
-
*/
|
|
4379
|
-
export() {
|
|
4380
|
-
const data = {
|
|
4381
|
-
active: activeSession,
|
|
4382
|
-
history: sessionHistory
|
|
4383
|
-
};
|
|
4384
|
-
const json = JSON.stringify(data, null, 2);
|
|
4385
|
-
logger.info("[Session Debug] Session data exported. Copy the returned string or use:");
|
|
4386
|
-
logger.info(" copy(window.skuilder.session.export())");
|
|
4387
|
-
return json;
|
|
4388
|
-
},
|
|
4389
|
-
/**
|
|
4390
|
-
* Clear session history.
|
|
4391
|
-
*/
|
|
4392
|
-
clear() {
|
|
4393
|
-
sessionHistory.length = 0;
|
|
4394
|
-
logger.info("[Session Debug] Session history cleared.");
|
|
4395
|
-
},
|
|
4396
|
-
/**
|
|
4397
|
-
* Show help.
|
|
4398
|
-
*/
|
|
4399
|
-
help() {
|
|
4400
|
-
logger.info(`
|
|
4401
|
-
\u{1F3AF} Session Debug API
|
|
4402
|
-
|
|
4403
|
-
Commands:
|
|
4404
|
-
.showQueue() Show current queue state (active session only)
|
|
4405
|
-
.showHistory(index?) Show presentation history (0=current/last, 1=previous, etc)
|
|
4406
|
-
.showInterleaving(index?) Analyze course interleaving pattern
|
|
4407
|
-
.listSessions() List all tracked sessions
|
|
4408
|
-
.export() Export session data as JSON for bug reports
|
|
4409
|
-
.clear() Clear session history
|
|
4410
|
-
.sessions Access raw session history array
|
|
4411
|
-
.active Access active session (if any)
|
|
4412
|
-
.help() Show this help message
|
|
4413
|
-
|
|
4414
|
-
Example:
|
|
4415
|
-
window.skuilder.session.showHistory()
|
|
4416
|
-
window.skuilder.session.showInterleaving()
|
|
4417
|
-
window.skuilder.session.showQueue()
|
|
4418
|
-
`);
|
|
4419
|
-
}
|
|
2997
|
+
function computeSpread(confidence) {
|
|
2998
|
+
const clampedConfidence = Math.max(0, Math.min(1, confidence));
|
|
2999
|
+
return MAX_SPREAD - clampedConfidence * (MAX_SPREAD - MIN_SPREAD);
|
|
3000
|
+
}
|
|
3001
|
+
function computeEffectiveWeight(learnable, userId, strategyId, salt) {
|
|
3002
|
+
const deviation = computeDeviation(userId, strategyId, salt);
|
|
3003
|
+
const spread = computeSpread(learnable.confidence);
|
|
3004
|
+
const adjustment = deviation * spread * learnable.weight;
|
|
3005
|
+
const effective = learnable.weight + adjustment;
|
|
3006
|
+
return Math.max(MIN_WEIGHT, Math.min(MAX_WEIGHT, effective));
|
|
3007
|
+
}
|
|
3008
|
+
async function createOrchestrationContext(user, course) {
|
|
3009
|
+
let courseConfig;
|
|
3010
|
+
try {
|
|
3011
|
+
courseConfig = await course.getCourseConfig();
|
|
3012
|
+
} catch (e) {
|
|
3013
|
+
logger.error(`[Orchestration] Failed to load course config: ${e}`);
|
|
3014
|
+
courseConfig = {
|
|
3015
|
+
name: "Unknown",
|
|
3016
|
+
description: "",
|
|
3017
|
+
public: false,
|
|
3018
|
+
deleted: false,
|
|
3019
|
+
creator: "",
|
|
3020
|
+
admins: [],
|
|
3021
|
+
moderators: [],
|
|
3022
|
+
dataShapes: [],
|
|
3023
|
+
questionTypes: [],
|
|
3024
|
+
orchestration: { salt: "default" }
|
|
4420
3025
|
};
|
|
4421
|
-
mountSessionDebugger();
|
|
4422
3026
|
}
|
|
4423
|
-
|
|
4424
|
-
|
|
4425
|
-
|
|
4426
|
-
|
|
4427
|
-
|
|
3027
|
+
const userId = user.getUsername();
|
|
3028
|
+
const salt = courseConfig.orchestration?.salt || "default_salt";
|
|
3029
|
+
return {
|
|
3030
|
+
user,
|
|
3031
|
+
course,
|
|
3032
|
+
userId,
|
|
3033
|
+
courseConfig,
|
|
3034
|
+
getEffectiveWeight(strategyId, learnable) {
|
|
3035
|
+
return computeEffectiveWeight(learnable, userId, strategyId, salt);
|
|
3036
|
+
},
|
|
3037
|
+
getDeviation(strategyId) {
|
|
3038
|
+
return computeDeviation(userId, strategyId, salt);
|
|
3039
|
+
}
|
|
3040
|
+
};
|
|
3041
|
+
}
|
|
3042
|
+
var MIN_SPREAD, MAX_SPREAD, MIN_WEIGHT, MAX_WEIGHT;
|
|
3043
|
+
var init_orchestration = __esm({
|
|
3044
|
+
"src/core/orchestration/index.ts"() {
|
|
4428
3045
|
"use strict";
|
|
4429
|
-
init_SrsService();
|
|
4430
|
-
init_EloService();
|
|
4431
|
-
init_ResponseProcessor();
|
|
4432
|
-
init_CardHydrationService();
|
|
4433
|
-
init_ItemQueue();
|
|
4434
|
-
init_couch();
|
|
4435
|
-
init_recording();
|
|
4436
|
-
init_util2();
|
|
4437
|
-
init_navigators();
|
|
4438
|
-
init_SourceMixer();
|
|
4439
|
-
init_MixerDebugger();
|
|
4440
|
-
init_SessionDebugger();
|
|
4441
3046
|
init_logger();
|
|
3047
|
+
init_gradient();
|
|
3048
|
+
init_learning();
|
|
3049
|
+
init_signal();
|
|
3050
|
+
init_recording();
|
|
3051
|
+
MIN_SPREAD = 0.1;
|
|
3052
|
+
MAX_SPREAD = 0.5;
|
|
3053
|
+
MIN_WEIGHT = 0.1;
|
|
3054
|
+
MAX_WEIGHT = 3;
|
|
4442
3055
|
}
|
|
4443
3056
|
});
|
|
4444
3057
|
|
|
@@ -4447,7 +3060,7 @@ var Pipeline_exports = {};
|
|
|
4447
3060
|
__export(Pipeline_exports, {
|
|
4448
3061
|
Pipeline: () => Pipeline
|
|
4449
3062
|
});
|
|
4450
|
-
import { toCourseElo as
|
|
3063
|
+
import { toCourseElo as toCourseElo5 } from "@vue-skuilder/common";
|
|
4451
3064
|
function globToRegex(pattern) {
|
|
4452
3065
|
const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&");
|
|
4453
3066
|
const withWildcards = escaped.replace(/\*/g, ".*");
|
|
@@ -4460,6 +3073,44 @@ function globMatch(value, pattern) {
|
|
|
4460
3073
|
function cardMatchesTagPattern(card, pattern) {
|
|
4461
3074
|
return (card.tags ?? []).some((tag) => globMatch(tag, pattern));
|
|
4462
3075
|
}
|
|
3076
|
+
function mergeHints2(allHints) {
|
|
3077
|
+
const defined = allHints.filter((h) => h !== null && h !== void 0);
|
|
3078
|
+
if (defined.length === 0) return void 0;
|
|
3079
|
+
const merged = {};
|
|
3080
|
+
const boostTags = {};
|
|
3081
|
+
for (const hints of defined) {
|
|
3082
|
+
for (const [pattern, factor] of Object.entries(hints.boostTags ?? {})) {
|
|
3083
|
+
boostTags[pattern] = (boostTags[pattern] ?? 1) * factor;
|
|
3084
|
+
}
|
|
3085
|
+
}
|
|
3086
|
+
if (Object.keys(boostTags).length > 0) {
|
|
3087
|
+
merged.boostTags = boostTags;
|
|
3088
|
+
}
|
|
3089
|
+
const boostCards = {};
|
|
3090
|
+
for (const hints of defined) {
|
|
3091
|
+
for (const [pattern, factor] of Object.entries(hints.boostCards ?? {})) {
|
|
3092
|
+
boostCards[pattern] = (boostCards[pattern] ?? 1) * factor;
|
|
3093
|
+
}
|
|
3094
|
+
}
|
|
3095
|
+
if (Object.keys(boostCards).length > 0) {
|
|
3096
|
+
merged.boostCards = boostCards;
|
|
3097
|
+
}
|
|
3098
|
+
const concatUnique = (field) => {
|
|
3099
|
+
const values = defined.flatMap((h) => h[field] ?? []);
|
|
3100
|
+
if (values.length > 0) {
|
|
3101
|
+
merged[field] = [...new Set(values)];
|
|
3102
|
+
}
|
|
3103
|
+
};
|
|
3104
|
+
concatUnique("requireTags");
|
|
3105
|
+
concatUnique("requireCards");
|
|
3106
|
+
concatUnique("excludeTags");
|
|
3107
|
+
concatUnique("excludeCards");
|
|
3108
|
+
const labels = defined.map((h) => h._label).filter(Boolean);
|
|
3109
|
+
if (labels.length > 0) {
|
|
3110
|
+
merged._label = labels.join("; ");
|
|
3111
|
+
}
|
|
3112
|
+
return Object.keys(merged).length > 0 ? merged : void 0;
|
|
3113
|
+
}
|
|
4463
3114
|
function logPipelineConfig(generator, filters) {
|
|
4464
3115
|
const filterList = filters.length > 0 ? "\n - " + filters.map((f) => f.name).join("\n - ") : " none";
|
|
4465
3116
|
logger.info(
|
|
@@ -4531,7 +3182,6 @@ var init_Pipeline = __esm({
|
|
|
4531
3182
|
init_logger();
|
|
4532
3183
|
init_orchestration();
|
|
4533
3184
|
init_PipelineDebugger();
|
|
4534
|
-
init_SessionController();
|
|
4535
3185
|
VERBOSE_RESULTS = true;
|
|
4536
3186
|
Pipeline = class extends ContentNavigator {
|
|
4537
3187
|
generator;
|
|
@@ -4611,9 +3261,12 @@ var init_Pipeline = __esm({
|
|
|
4611
3261
|
logger.debug(
|
|
4612
3262
|
`[Pipeline] Fetching ${fetchLimit} candidates from generator '${this.generator.name}'`
|
|
4613
3263
|
);
|
|
4614
|
-
|
|
3264
|
+
const generatorResult = await this.generator.getWeightedCards(fetchLimit, context);
|
|
3265
|
+
let cards = generatorResult.cards;
|
|
4615
3266
|
const tGenerate = performance.now();
|
|
4616
3267
|
const generatedCount = cards.length;
|
|
3268
|
+
const mergedHints = mergeHints2([this._ephemeralHints, generatorResult.hints]);
|
|
3269
|
+
this._ephemeralHints = mergedHints ?? null;
|
|
4617
3270
|
let generatorSummaries;
|
|
4618
3271
|
if (this.generator.generators) {
|
|
4619
3272
|
const genMap = /* @__PURE__ */ new Map();
|
|
@@ -4699,7 +3352,7 @@ var init_Pipeline = __esm({
|
|
|
4699
3352
|
} catch (e) {
|
|
4700
3353
|
logger.debug(`[Pipeline] Failed to capture debug run: ${e}`);
|
|
4701
3354
|
}
|
|
4702
|
-
return result;
|
|
3355
|
+
return { cards: result };
|
|
4703
3356
|
}
|
|
4704
3357
|
/**
|
|
4705
3358
|
* Batch hydrate tags for all cards.
|
|
@@ -4854,7 +3507,7 @@ var init_Pipeline = __esm({
|
|
|
4854
3507
|
let userElo = 1e3;
|
|
4855
3508
|
try {
|
|
4856
3509
|
const courseReg = await this.user.getCourseRegDoc(this.course.getCourseID());
|
|
4857
|
-
const courseElo =
|
|
3510
|
+
const courseElo = toCourseElo5(courseReg.elo);
|
|
4858
3511
|
userElo = courseElo.global.score;
|
|
4859
3512
|
} catch (e) {
|
|
4860
3513
|
logger.debug(`[Pipeline] Could not get user ELO, using default: ${e}`);
|
|
@@ -4915,7 +3568,7 @@ var init_Pipeline = __esm({
|
|
|
4915
3568
|
*/
|
|
4916
3569
|
async getTagEloStatus(tagFilter) {
|
|
4917
3570
|
const courseReg = await this.user.getCourseRegDoc(this.course.getCourseID());
|
|
4918
|
-
const courseElo =
|
|
3571
|
+
const courseElo = toCourseElo5(courseReg.elo);
|
|
4919
3572
|
const result = {};
|
|
4920
3573
|
if (!tagFilter) {
|
|
4921
3574
|
for (const [tag, data] of Object.entries(courseElo.tags)) {
|
|
@@ -5517,7 +4170,7 @@ import {
|
|
|
5517
4170
|
EloToNumber,
|
|
5518
4171
|
Status,
|
|
5519
4172
|
blankCourseElo as blankCourseElo2,
|
|
5520
|
-
toCourseElo as
|
|
4173
|
+
toCourseElo as toCourseElo6
|
|
5521
4174
|
} from "@vue-skuilder/common";
|
|
5522
4175
|
var init_courseDB = __esm({
|
|
5523
4176
|
"src/impl/couch/courseDB.ts"() {
|
|
@@ -5536,7 +4189,7 @@ var init_courseDB = __esm({
|
|
|
5536
4189
|
});
|
|
5537
4190
|
|
|
5538
4191
|
// src/impl/couch/classroomDB.ts
|
|
5539
|
-
import
|
|
4192
|
+
import moment4 from "moment";
|
|
5540
4193
|
var init_classroomDB2 = __esm({
|
|
5541
4194
|
"src/impl/couch/classroomDB.ts"() {
|
|
5542
4195
|
"use strict";
|
|
@@ -5598,7 +4251,7 @@ var init_CouchDBSyncStrategy = __esm({
|
|
|
5598
4251
|
|
|
5599
4252
|
// src/impl/couch/index.ts
|
|
5600
4253
|
import fetch3 from "cross-fetch";
|
|
5601
|
-
import
|
|
4254
|
+
import moment5 from "moment";
|
|
5602
4255
|
import process2 from "process";
|
|
5603
4256
|
function createPouchDBConfig() {
|
|
5604
4257
|
const hasExplicitCredentials = ENV.COUCHDB_USERNAME && ENV.COUCHDB_PASSWORD;
|
|
@@ -5651,7 +4304,7 @@ var init_couch = __esm({
|
|
|
5651
4304
|
|
|
5652
4305
|
// src/impl/common/BaseUserDB.ts
|
|
5653
4306
|
import { Status as Status3 } from "@vue-skuilder/common";
|
|
5654
|
-
import
|
|
4307
|
+
import moment6 from "moment";
|
|
5655
4308
|
function accomodateGuest() {
|
|
5656
4309
|
logger.log("[funnel] accomodateGuest() called");
|
|
5657
4310
|
if (typeof localStorage === "undefined") {
|
|
@@ -6094,7 +4747,7 @@ Currently logged-in as ${this._username}.`
|
|
|
6094
4747
|
);
|
|
6095
4748
|
return reviews.rows.filter((r) => {
|
|
6096
4749
|
if (r.id.startsWith(DocTypePrefixes["SCHEDULED_CARD" /* SCHEDULED_CARD */])) {
|
|
6097
|
-
const date =
|
|
4750
|
+
const date = moment6.utc(
|
|
6098
4751
|
r.id.substr(DocTypePrefixes["SCHEDULED_CARD" /* SCHEDULED_CARD */].length),
|
|
6099
4752
|
REVIEW_TIME_FORMAT
|
|
6100
4753
|
);
|
|
@@ -6107,11 +4760,11 @@ Currently logged-in as ${this._username}.`
|
|
|
6107
4760
|
}).map((r) => r.doc);
|
|
6108
4761
|
}
|
|
6109
4762
|
async getReviewsForcast(daysCount) {
|
|
6110
|
-
const time =
|
|
4763
|
+
const time = moment6.utc().add(daysCount, "days");
|
|
6111
4764
|
return this.getReviewstoDate(time);
|
|
6112
4765
|
}
|
|
6113
4766
|
async getPendingReviews(course_id) {
|
|
6114
|
-
const now =
|
|
4767
|
+
const now = moment6.utc();
|
|
6115
4768
|
return this.getReviewstoDate(now, course_id);
|
|
6116
4769
|
}
|
|
6117
4770
|
async getScheduledReviewCount(course_id) {
|
|
@@ -6398,7 +5051,7 @@ Currently logged-in as ${this._username}.`
|
|
|
6398
5051
|
*/
|
|
6399
5052
|
async putCardRecord(record) {
|
|
6400
5053
|
const cardHistoryID = getCardHistoryID(record.courseID, record.cardID);
|
|
6401
|
-
record.timeStamp =
|
|
5054
|
+
record.timeStamp = moment6.utc(record.timeStamp).toString();
|
|
6402
5055
|
try {
|
|
6403
5056
|
const cardHistory = await this.update(
|
|
6404
5057
|
cardHistoryID,
|
|
@@ -6414,7 +5067,7 @@ Currently logged-in as ${this._username}.`
|
|
|
6414
5067
|
const ret = {
|
|
6415
5068
|
...record2
|
|
6416
5069
|
};
|
|
6417
|
-
ret.timeStamp =
|
|
5070
|
+
ret.timeStamp = moment6.utc(record2.timeStamp);
|
|
6418
5071
|
return ret;
|
|
6419
5072
|
});
|
|
6420
5073
|
return cardHistory;
|
|
@@ -6829,7 +5482,7 @@ var init_cardProcessor = __esm({
|
|
|
6829
5482
|
});
|
|
6830
5483
|
|
|
6831
5484
|
// src/core/bulkImport/types.ts
|
|
6832
|
-
var
|
|
5485
|
+
var init_types3 = __esm({
|
|
6833
5486
|
"src/core/bulkImport/types.ts"() {
|
|
6834
5487
|
"use strict";
|
|
6835
5488
|
}
|
|
@@ -6840,7 +5493,7 @@ var init_bulkImport = __esm({
|
|
|
6840
5493
|
"src/core/bulkImport/index.ts"() {
|
|
6841
5494
|
"use strict";
|
|
6842
5495
|
init_cardProcessor();
|
|
6843
|
-
|
|
5496
|
+
init_types3();
|
|
6844
5497
|
}
|
|
6845
5498
|
});
|
|
6846
5499
|
|
|
@@ -7192,7 +5845,7 @@ var init_core = __esm({
|
|
|
7192
5845
|
});
|
|
7193
5846
|
|
|
7194
5847
|
// src/impl/static/StaticDataUnpacker.ts
|
|
7195
|
-
var pathUtils,
|
|
5848
|
+
var pathUtils, nodeFS, StaticDataUnpacker;
|
|
7196
5849
|
var init_StaticDataUnpacker = __esm({
|
|
7197
5850
|
"src/impl/static/StaticDataUnpacker.ts"() {
|
|
7198
5851
|
"use strict";
|
|
@@ -7209,10 +5862,10 @@ var init_StaticDataUnpacker = __esm({
|
|
|
7209
5862
|
return false;
|
|
7210
5863
|
}
|
|
7211
5864
|
};
|
|
7212
|
-
|
|
5865
|
+
nodeFS = null;
|
|
7213
5866
|
try {
|
|
7214
5867
|
if (typeof window === "undefined" && typeof process !== "undefined" && process.versions?.node) {
|
|
7215
|
-
|
|
5868
|
+
nodeFS = eval("require")("fs");
|
|
7216
5869
|
}
|
|
7217
5870
|
} catch {
|
|
7218
5871
|
}
|
|
@@ -7399,8 +6052,8 @@ var init_StaticDataUnpacker = __esm({
|
|
|
7399
6052
|
const chunkPath = `${this.basePath}/${chunk.path}`;
|
|
7400
6053
|
logger.debug(`Loading chunk from ${chunkPath}`);
|
|
7401
6054
|
let documents;
|
|
7402
|
-
if (this.isLocalPath(chunkPath) &&
|
|
7403
|
-
const fileContent = await
|
|
6055
|
+
if (this.isLocalPath(chunkPath) && nodeFS) {
|
|
6056
|
+
const fileContent = await nodeFS.promises.readFile(chunkPath, "utf8");
|
|
7404
6057
|
documents = JSON.parse(fileContent);
|
|
7405
6058
|
} else {
|
|
7406
6059
|
const response = await fetch(chunkPath);
|
|
@@ -7438,8 +6091,8 @@ var init_StaticDataUnpacker = __esm({
|
|
|
7438
6091
|
const indexPath = `${this.basePath}/${indexMeta.path}`;
|
|
7439
6092
|
logger.debug(`Loading index from ${indexPath}`);
|
|
7440
6093
|
let indexData;
|
|
7441
|
-
if (this.isLocalPath(indexPath) &&
|
|
7442
|
-
const fileContent = await
|
|
6094
|
+
if (this.isLocalPath(indexPath) && nodeFS) {
|
|
6095
|
+
const fileContent = await nodeFS.promises.readFile(indexPath, "utf8");
|
|
7443
6096
|
indexData = JSON.parse(fileContent);
|
|
7444
6097
|
} else {
|
|
7445
6098
|
const response = await fetch(indexPath);
|
|
@@ -7514,8 +6167,8 @@ var init_StaticDataUnpacker = __esm({
|
|
|
7514
6167
|
return null;
|
|
7515
6168
|
}
|
|
7516
6169
|
try {
|
|
7517
|
-
if (this.isLocalPath(attachmentPath) &&
|
|
7518
|
-
const buffer = await
|
|
6170
|
+
if (this.isLocalPath(attachmentPath) && nodeFS) {
|
|
6171
|
+
const buffer = await nodeFS.promises.readFile(attachmentPath);
|
|
7519
6172
|
return buffer;
|
|
7520
6173
|
} else {
|
|
7521
6174
|
const response = await fetch(attachmentPath);
|