@vue-skuilder/db 0.1.31-b → 0.1.31
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{contentSource-ygoFw9oV.d.ts → contentSource-Bdwkvqa8.d.ts} +16 -0
- package/dist/{contentSource-B7nXusjk.d.cts → contentSource-DF1nUbPQ.d.cts} +16 -0
- package/dist/core/index.d.cts +34 -3
- package/dist/core/index.d.ts +34 -3
- package/dist/core/index.js +510 -50
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.mjs +510 -50
- package/dist/core/index.mjs.map +1 -1
- package/dist/{dataLayerProvider-BW7HvkMt.d.cts → dataLayerProvider-BKmVoyJR.d.ts} +20 -1
- package/dist/{dataLayerProvider-BfXUVDuG.d.ts → dataLayerProvider-BQdfJuBN.d.cts} +20 -1
- package/dist/impl/couch/index.d.cts +156 -4
- package/dist/impl/couch/index.d.ts +156 -4
- package/dist/impl/couch/index.js +730 -41
- package/dist/impl/couch/index.js.map +1 -1
- package/dist/impl/couch/index.mjs +729 -41
- package/dist/impl/couch/index.mjs.map +1 -1
- package/dist/impl/static/index.d.cts +3 -2
- package/dist/impl/static/index.d.ts +3 -2
- package/dist/impl/static/index.js +467 -31
- package/dist/impl/static/index.js.map +1 -1
- package/dist/impl/static/index.mjs +467 -31
- package/dist/impl/static/index.mjs.map +1 -1
- package/dist/index.d.cts +64 -3
- package/dist/index.d.ts +64 -3
- package/dist/index.js +948 -72
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +948 -72
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
- package/src/core/interfaces/contentSource.ts +6 -0
- package/src/core/interfaces/courseDB.ts +6 -0
- package/src/core/interfaces/dataLayerProvider.ts +20 -0
- package/src/core/navigators/Pipeline.ts +414 -9
- package/src/core/navigators/PipelineAssembler.ts +23 -18
- package/src/core/navigators/PipelineDebugger.ts +35 -1
- package/src/core/navigators/filters/hierarchyDefinition.ts +78 -8
- package/src/core/navigators/generators/prescribed.ts +95 -0
- package/src/core/navigators/index.ts +12 -0
- package/src/impl/common/BaseUserDB.ts +4 -1
- package/src/impl/couch/CourseSyncService.ts +356 -0
- package/src/impl/couch/PouchDataLayerProvider.ts +21 -1
- package/src/impl/couch/courseDB.ts +60 -13
- package/src/impl/couch/index.ts +1 -0
- package/src/impl/static/courseDB.ts +5 -0
- package/src/study/ItemQueue.ts +42 -0
- package/src/study/SessionController.ts +195 -22
- package/src/study/SpacedRepetition.ts +3 -1
- package/tests/core/navigators/Pipeline.test.ts +1 -1
- package/tests/core/navigators/PipelineAssembler.test.ts +15 -14
|
@@ -528,8 +528,12 @@ __export(PipelineDebugger_exports, {
|
|
|
528
528
|
buildRunReport: () => buildRunReport,
|
|
529
529
|
captureRun: () => captureRun,
|
|
530
530
|
mountPipelineDebugger: () => mountPipelineDebugger,
|
|
531
|
-
pipelineDebugAPI: () => pipelineDebugAPI
|
|
531
|
+
pipelineDebugAPI: () => pipelineDebugAPI,
|
|
532
|
+
registerPipelineForDebug: () => registerPipelineForDebug
|
|
532
533
|
});
|
|
534
|
+
function registerPipelineForDebug(pipeline) {
|
|
535
|
+
_activePipeline = pipeline;
|
|
536
|
+
}
|
|
533
537
|
function getOrigin(card) {
|
|
534
538
|
const firstEntry = card.provenance[0];
|
|
535
539
|
if (!firstEntry) return "unknown";
|
|
@@ -557,6 +561,7 @@ function buildRunReport(courseId, courseName, generatorName, generators, generat
|
|
|
557
561
|
origin: getOrigin(card),
|
|
558
562
|
finalScore: card.score,
|
|
559
563
|
provenance: card.provenance,
|
|
564
|
+
tags: card.tags,
|
|
560
565
|
selected: selectedIds.has(card.cardId)
|
|
561
566
|
}));
|
|
562
567
|
const reviewsSelected = selectedCards.filter((c) => getOrigin(c) === "review").length;
|
|
@@ -612,12 +617,13 @@ function mountPipelineDebugger() {
|
|
|
612
617
|
win.skuilder = win.skuilder || {};
|
|
613
618
|
win.skuilder.pipeline = pipelineDebugAPI;
|
|
614
619
|
}
|
|
615
|
-
var MAX_RUNS, runHistory, pipelineDebugAPI;
|
|
620
|
+
var _activePipeline, MAX_RUNS, runHistory, pipelineDebugAPI;
|
|
616
621
|
var init_PipelineDebugger = __esm({
|
|
617
622
|
"src/core/navigators/PipelineDebugger.ts"() {
|
|
618
623
|
"use strict";
|
|
619
624
|
init_navigators();
|
|
620
625
|
init_logger();
|
|
626
|
+
_activePipeline = null;
|
|
621
627
|
MAX_RUNS = 10;
|
|
622
628
|
runHistory = [];
|
|
623
629
|
pipelineDebugAPI = {
|
|
@@ -819,6 +825,21 @@ var init_PipelineDebugger = __esm({
|
|
|
819
825
|
}
|
|
820
826
|
console.groupEnd();
|
|
821
827
|
},
|
|
828
|
+
/**
|
|
829
|
+
* Scan the full card space through the filter chain for the current user.
|
|
830
|
+
*
|
|
831
|
+
* Reports how many cards are well-indicated and how many are new.
|
|
832
|
+
* Use this to understand how the search space grows during onboarding.
|
|
833
|
+
*
|
|
834
|
+
* @param threshold - Score threshold for "well indicated" (default 0.10)
|
|
835
|
+
*/
|
|
836
|
+
async diagnoseCardSpace(threshold) {
|
|
837
|
+
if (!_activePipeline) {
|
|
838
|
+
logger.info("[Pipeline Debug] No active pipeline. Run a session first.");
|
|
839
|
+
return null;
|
|
840
|
+
}
|
|
841
|
+
return _activePipeline.diagnoseCardSpace({ threshold });
|
|
842
|
+
},
|
|
822
843
|
/**
|
|
823
844
|
* Show help.
|
|
824
845
|
*/
|
|
@@ -831,6 +852,7 @@ Commands:
|
|
|
831
852
|
.showRun(id|index) Show summary of a specific run (by index or ID suffix)
|
|
832
853
|
.showCard(cardId) Show provenance trail for a specific card
|
|
833
854
|
.explainReviews() Analyze why reviews were/weren't selected
|
|
855
|
+
.diagnoseCardSpace() Scan full card space through filters (async)
|
|
834
856
|
.showRegistry() Show navigator registry (classes + roles)
|
|
835
857
|
.showStrategies() Show registry + strategy mapping from last run
|
|
836
858
|
.listRuns() List all captured runs in table format
|
|
@@ -842,7 +864,7 @@ Commands:
|
|
|
842
864
|
Example:
|
|
843
865
|
window.skuilder.pipeline.showLastRun()
|
|
844
866
|
window.skuilder.pipeline.showRun(1)
|
|
845
|
-
window.skuilder.pipeline.
|
|
867
|
+
await window.skuilder.pipeline.diagnoseCardSpace()
|
|
846
868
|
`);
|
|
847
869
|
}
|
|
848
870
|
};
|
|
@@ -1137,6 +1159,69 @@ var init_generators = __esm({
|
|
|
1137
1159
|
}
|
|
1138
1160
|
});
|
|
1139
1161
|
|
|
1162
|
+
// src/core/navigators/generators/prescribed.ts
|
|
1163
|
+
var prescribed_exports = {};
|
|
1164
|
+
__export(prescribed_exports, {
|
|
1165
|
+
default: () => PrescribedCardsGenerator
|
|
1166
|
+
});
|
|
1167
|
+
var PrescribedCardsGenerator;
|
|
1168
|
+
var init_prescribed = __esm({
|
|
1169
|
+
"src/core/navigators/generators/prescribed.ts"() {
|
|
1170
|
+
"use strict";
|
|
1171
|
+
init_navigators();
|
|
1172
|
+
init_logger();
|
|
1173
|
+
PrescribedCardsGenerator = class extends ContentNavigator {
|
|
1174
|
+
name;
|
|
1175
|
+
config;
|
|
1176
|
+
constructor(user, course, strategyData) {
|
|
1177
|
+
super(user, course, strategyData);
|
|
1178
|
+
this.name = strategyData.name || "Prescribed Cards";
|
|
1179
|
+
try {
|
|
1180
|
+
const parsed = JSON.parse(strategyData.serializedData);
|
|
1181
|
+
this.config = { cardIds: parsed.cardIds || [] };
|
|
1182
|
+
} catch {
|
|
1183
|
+
this.config = { cardIds: [] };
|
|
1184
|
+
}
|
|
1185
|
+
logger.debug(
|
|
1186
|
+
`[Prescribed] Initialized with ${this.config.cardIds.length} prescribed cards`
|
|
1187
|
+
);
|
|
1188
|
+
}
|
|
1189
|
+
async getWeightedCards(limit, _context) {
|
|
1190
|
+
if (this.config.cardIds.length === 0) {
|
|
1191
|
+
return [];
|
|
1192
|
+
}
|
|
1193
|
+
const courseId = this.course.getCourseID();
|
|
1194
|
+
const activeCards = await this.user.getActiveCards();
|
|
1195
|
+
const activeIds = new Set(activeCards.map((ac) => ac.cardID));
|
|
1196
|
+
const eligibleIds = this.config.cardIds.filter((id) => !activeIds.has(id));
|
|
1197
|
+
if (eligibleIds.length === 0) {
|
|
1198
|
+
logger.debug("[Prescribed] All prescribed cards already active, returning empty");
|
|
1199
|
+
return [];
|
|
1200
|
+
}
|
|
1201
|
+
const cards = eligibleIds.slice(0, limit).map((cardId) => ({
|
|
1202
|
+
cardId,
|
|
1203
|
+
courseId,
|
|
1204
|
+
score: 1,
|
|
1205
|
+
provenance: [
|
|
1206
|
+
{
|
|
1207
|
+
strategy: "prescribed",
|
|
1208
|
+
strategyName: this.strategyName || this.name,
|
|
1209
|
+
strategyId: this.strategyId || "NAVIGATION_STRATEGY-prescribed",
|
|
1210
|
+
action: "generated",
|
|
1211
|
+
score: 1,
|
|
1212
|
+
reason: `Prescribed card (${eligibleIds.length} eligible of ${this.config.cardIds.length} configured)`
|
|
1213
|
+
}
|
|
1214
|
+
]
|
|
1215
|
+
}));
|
|
1216
|
+
logger.info(
|
|
1217
|
+
`[Prescribed] Emitting ${cards.length} cards (${eligibleIds.length} eligible, ${activeIds.size} already active)`
|
|
1218
|
+
);
|
|
1219
|
+
return cards;
|
|
1220
|
+
}
|
|
1221
|
+
};
|
|
1222
|
+
}
|
|
1223
|
+
});
|
|
1224
|
+
|
|
1140
1225
|
// src/core/navigators/generators/srs.ts
|
|
1141
1226
|
var srs_exports = {};
|
|
1142
1227
|
__export(srs_exports, {
|
|
@@ -1331,6 +1416,7 @@ var init_ = __esm({
|
|
|
1331
1416
|
"./generators/CompositeGenerator.ts": () => Promise.resolve().then(() => (init_CompositeGenerator(), CompositeGenerator_exports)),
|
|
1332
1417
|
"./generators/elo.ts": () => Promise.resolve().then(() => (init_elo(), elo_exports)),
|
|
1333
1418
|
"./generators/index.ts": () => Promise.resolve().then(() => (init_generators(), generators_exports)),
|
|
1419
|
+
"./generators/prescribed.ts": () => Promise.resolve().then(() => (init_prescribed(), prescribed_exports)),
|
|
1334
1420
|
"./generators/srs.ts": () => Promise.resolve().then(() => (init_srs(), srs_exports)),
|
|
1335
1421
|
"./generators/types.ts": () => Promise.resolve().then(() => (init_types(), types_exports))
|
|
1336
1422
|
});
|
|
@@ -1531,6 +1617,8 @@ var init_hierarchyDefinition = __esm({
|
|
|
1531
1617
|
if (userTagElo.count < minCount) return false;
|
|
1532
1618
|
if (prereq.masteryThreshold?.minElo !== void 0) {
|
|
1533
1619
|
return userTagElo.score >= prereq.masteryThreshold.minElo;
|
|
1620
|
+
} else if (prereq.masteryThreshold?.minCount !== void 0) {
|
|
1621
|
+
return true;
|
|
1534
1622
|
} else {
|
|
1535
1623
|
return userTagElo.score >= userGlobalElo;
|
|
1536
1624
|
}
|
|
@@ -1606,14 +1694,38 @@ var init_hierarchyDefinition = __esm({
|
|
|
1606
1694
|
};
|
|
1607
1695
|
}
|
|
1608
1696
|
}
|
|
1697
|
+
/**
|
|
1698
|
+
* Build a map of prereq tag → max configured boost for all *closed* gates.
|
|
1699
|
+
*
|
|
1700
|
+
* When a gate is closed (prereqs unmet), cards carrying that gate's prereq
|
|
1701
|
+
* tags get boosted — steering the pipeline toward content that helps unlock
|
|
1702
|
+
* the gated material. Once the gate opens, the boost disappears.
|
|
1703
|
+
*/
|
|
1704
|
+
getPreReqBoosts(unlockedTags, masteredTags) {
|
|
1705
|
+
const boosts = /* @__PURE__ */ new Map();
|
|
1706
|
+
for (const [tagId, prereqs] of Object.entries(this.config.prerequisites)) {
|
|
1707
|
+
if (unlockedTags.has(tagId)) continue;
|
|
1708
|
+
for (const prereq of prereqs) {
|
|
1709
|
+
if (!prereq.preReqBoost || prereq.preReqBoost <= 1) continue;
|
|
1710
|
+
if (masteredTags.has(prereq.tag)) continue;
|
|
1711
|
+
const existing = boosts.get(prereq.tag) ?? 1;
|
|
1712
|
+
boosts.set(prereq.tag, Math.max(existing, prereq.preReqBoost));
|
|
1713
|
+
}
|
|
1714
|
+
}
|
|
1715
|
+
return boosts;
|
|
1716
|
+
}
|
|
1609
1717
|
/**
|
|
1610
1718
|
* CardFilter.transform implementation.
|
|
1611
1719
|
*
|
|
1612
|
-
*
|
|
1720
|
+
* Two effects:
|
|
1721
|
+
* 1. Cards with locked tags receive score * 0.05 (gating penalty)
|
|
1722
|
+
* 2. Cards carrying prereq tags of closed gates receive a configured
|
|
1723
|
+
* boost (preReqBoost), steering toward content that unlocks gates
|
|
1613
1724
|
*/
|
|
1614
1725
|
async transform(cards, context) {
|
|
1615
1726
|
const masteredTags = await this.getMasteredTags(context);
|
|
1616
1727
|
const unlockedTags = this.getUnlockedTags(masteredTags);
|
|
1728
|
+
const preReqBoosts = this.getPreReqBoosts(unlockedTags, masteredTags);
|
|
1617
1729
|
const gated = [];
|
|
1618
1730
|
for (const card of cards) {
|
|
1619
1731
|
const { isUnlocked, reason } = await this.checkCardUnlock(
|
|
@@ -1622,9 +1734,27 @@ var init_hierarchyDefinition = __esm({
|
|
|
1622
1734
|
unlockedTags,
|
|
1623
1735
|
masteredTags
|
|
1624
1736
|
);
|
|
1625
|
-
const LOCKED_PENALTY = 0.
|
|
1626
|
-
|
|
1627
|
-
|
|
1737
|
+
const LOCKED_PENALTY = 0.02;
|
|
1738
|
+
let finalScore = isUnlocked ? card.score : card.score * LOCKED_PENALTY;
|
|
1739
|
+
let action = isUnlocked ? "passed" : "penalized";
|
|
1740
|
+
let finalReason = reason;
|
|
1741
|
+
if (isUnlocked && preReqBoosts.size > 0) {
|
|
1742
|
+
const cardTags = card.tags ?? [];
|
|
1743
|
+
let maxBoost = 1;
|
|
1744
|
+
const boostedPrereqs = [];
|
|
1745
|
+
for (const tag of cardTags) {
|
|
1746
|
+
const boost = preReqBoosts.get(tag);
|
|
1747
|
+
if (boost && boost > maxBoost) {
|
|
1748
|
+
maxBoost = boost;
|
|
1749
|
+
boostedPrereqs.push(tag);
|
|
1750
|
+
}
|
|
1751
|
+
}
|
|
1752
|
+
if (maxBoost > 1) {
|
|
1753
|
+
finalScore *= maxBoost;
|
|
1754
|
+
action = "boosted";
|
|
1755
|
+
finalReason = `${reason} | preReqBoost \xD7${maxBoost.toFixed(2)} for ${boostedPrereqs.join(", ")}`;
|
|
1756
|
+
}
|
|
1757
|
+
}
|
|
1628
1758
|
gated.push({
|
|
1629
1759
|
...card,
|
|
1630
1760
|
score: finalScore,
|
|
@@ -1636,7 +1766,7 @@ var init_hierarchyDefinition = __esm({
|
|
|
1636
1766
|
strategyId: this.strategyId || "NAVIGATION_STRATEGY-hierarchy",
|
|
1637
1767
|
action,
|
|
1638
1768
|
score: finalScore,
|
|
1639
|
-
reason
|
|
1769
|
+
reason: finalReason
|
|
1640
1770
|
}
|
|
1641
1771
|
]
|
|
1642
1772
|
});
|
|
@@ -2324,6 +2454,18 @@ var Pipeline_exports = {};
|
|
|
2324
2454
|
__export(Pipeline_exports, {
|
|
2325
2455
|
Pipeline: () => Pipeline
|
|
2326
2456
|
});
|
|
2457
|
+
function globToRegex(pattern) {
|
|
2458
|
+
const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&");
|
|
2459
|
+
const withWildcards = escaped.replace(/\*/g, ".*");
|
|
2460
|
+
return new RegExp(`^${withWildcards}$`);
|
|
2461
|
+
}
|
|
2462
|
+
function globMatch(value, pattern) {
|
|
2463
|
+
if (!pattern.includes("*")) return value === pattern;
|
|
2464
|
+
return globToRegex(pattern).test(value);
|
|
2465
|
+
}
|
|
2466
|
+
function cardMatchesTagPattern(card, pattern) {
|
|
2467
|
+
return (card.tags ?? []).some((tag) => globMatch(tag, pattern));
|
|
2468
|
+
}
|
|
2327
2469
|
function logPipelineConfig(generator, filters) {
|
|
2328
2470
|
const filterList = filters.length > 0 ? "\n - " + filters.map((f) => f.name).join("\n - ") : " none";
|
|
2329
2471
|
logger.info(
|
|
@@ -2358,6 +2500,21 @@ function logExecutionSummary(generatorName, generatedCount, filterCount, finalCo
|
|
|
2358
2500
|
\u{1F4A1} Inspect: window.skuilder.pipeline`
|
|
2359
2501
|
);
|
|
2360
2502
|
}
|
|
2503
|
+
function logResultCards(cards) {
|
|
2504
|
+
if (!VERBOSE_RESULTS || cards.length === 0) return;
|
|
2505
|
+
logger.info(`[Pipeline] Results (${cards.length} cards):`);
|
|
2506
|
+
for (let i = 0; i < cards.length; i++) {
|
|
2507
|
+
const c = cards[i];
|
|
2508
|
+
const tags = c.tags?.slice(0, 3).join(", ") || "";
|
|
2509
|
+
const filters = c.provenance.filter((p) => p.strategy === "hierarchyDefinition" || p.strategy === "priorityDefinition" || p.strategy === "interferenceFilter" || p.strategy === "letterGating" || p.strategy === "ephemeralHint").map((p) => {
|
|
2510
|
+
const arrow = p.action === "boosted" ? "\u2191" : p.action === "penalized" ? "\u2193" : "=";
|
|
2511
|
+
return `${p.strategyName}${arrow}${p.score.toFixed(2)}`;
|
|
2512
|
+
}).join(" | ");
|
|
2513
|
+
logger.info(
|
|
2514
|
+
`[Pipeline] ${String(i + 1).padStart(2)}. ${c.score.toFixed(4)} ${c.cardId} [${tags}]${filters ? ` {${filters}}` : ""}`
|
|
2515
|
+
);
|
|
2516
|
+
}
|
|
2517
|
+
}
|
|
2361
2518
|
function logCardProvenance(cards, maxCards = 3) {
|
|
2362
2519
|
const cardsToLog = cards.slice(0, maxCards);
|
|
2363
2520
|
logger.debug(`[Pipeline] Provenance for top ${cardsToLog.length} cards:`);
|
|
@@ -2372,7 +2529,7 @@ function logCardProvenance(cards, maxCards = 3) {
|
|
|
2372
2529
|
}
|
|
2373
2530
|
}
|
|
2374
2531
|
}
|
|
2375
|
-
var import_common8, Pipeline;
|
|
2532
|
+
var import_common8, VERBOSE_RESULTS, Pipeline;
|
|
2376
2533
|
var init_Pipeline = __esm({
|
|
2377
2534
|
"src/core/navigators/Pipeline.ts"() {
|
|
2378
2535
|
"use strict";
|
|
@@ -2381,9 +2538,31 @@ var init_Pipeline = __esm({
|
|
|
2381
2538
|
init_logger();
|
|
2382
2539
|
init_orchestration();
|
|
2383
2540
|
init_PipelineDebugger();
|
|
2541
|
+
VERBOSE_RESULTS = true;
|
|
2384
2542
|
Pipeline = class extends ContentNavigator {
|
|
2385
2543
|
generator;
|
|
2386
2544
|
filters;
|
|
2545
|
+
/**
|
|
2546
|
+
* Cached orchestration context. Course config and salt don't change within
|
|
2547
|
+
* a page load, so we build the orchestration context once and reuse it on
|
|
2548
|
+
* subsequent getWeightedCards() calls (e.g. mid-session replans).
|
|
2549
|
+
*
|
|
2550
|
+
* This eliminates a remote getCourseConfig() round trip per pipeline run.
|
|
2551
|
+
*/
|
|
2552
|
+
_cachedOrchestration = null;
|
|
2553
|
+
/**
|
|
2554
|
+
* Persistent tag cache. Maps cardId → tag names.
|
|
2555
|
+
*
|
|
2556
|
+
* Tags are static within a session (they're set at card generation time),
|
|
2557
|
+
* so we cache them across pipeline runs. On replans, many of the same cards
|
|
2558
|
+
* reappear — cache hits avoid redundant remote getAppliedTagsBatch() queries.
|
|
2559
|
+
*/
|
|
2560
|
+
_tagCache = /* @__PURE__ */ new Map();
|
|
2561
|
+
/**
|
|
2562
|
+
* One-shot replan hints. Applied after the filter chain on the next
|
|
2563
|
+
* getWeightedCards() call, then cleared.
|
|
2564
|
+
*/
|
|
2565
|
+
_ephemeralHints = null;
|
|
2387
2566
|
/**
|
|
2388
2567
|
* Create a new pipeline.
|
|
2389
2568
|
*
|
|
@@ -2404,6 +2583,17 @@ var init_Pipeline = __esm({
|
|
|
2404
2583
|
logger.error(`[pipeline] Failed to lookup courseCfg: ${e}`);
|
|
2405
2584
|
});
|
|
2406
2585
|
logPipelineConfig(generator, filters);
|
|
2586
|
+
registerPipelineForDebug(this);
|
|
2587
|
+
}
|
|
2588
|
+
/**
|
|
2589
|
+
* Set one-shot hints for the next pipeline run.
|
|
2590
|
+
* Consumed after one getWeightedCards() call, then cleared.
|
|
2591
|
+
*
|
|
2592
|
+
* Overrides ContentNavigator.setEphemeralHints() no-op.
|
|
2593
|
+
*/
|
|
2594
|
+
setEphemeralHints(hints) {
|
|
2595
|
+
this._ephemeralHints = hints;
|
|
2596
|
+
logger.info(`[Pipeline] Ephemeral hints set: ${JSON.stringify(hints)}`);
|
|
2407
2597
|
}
|
|
2408
2598
|
/**
|
|
2409
2599
|
* Get weighted cards by running generator and applying filters.
|
|
@@ -2420,13 +2610,15 @@ var init_Pipeline = __esm({
|
|
|
2420
2610
|
* @returns Cards sorted by score descending
|
|
2421
2611
|
*/
|
|
2422
2612
|
async getWeightedCards(limit) {
|
|
2613
|
+
const t0 = performance.now();
|
|
2423
2614
|
const context = await this.buildContext();
|
|
2424
|
-
const
|
|
2425
|
-
const fetchLimit =
|
|
2615
|
+
const tContext = performance.now();
|
|
2616
|
+
const fetchLimit = 500;
|
|
2426
2617
|
logger.debug(
|
|
2427
2618
|
`[Pipeline] Fetching ${fetchLimit} candidates from generator '${this.generator.name}'`
|
|
2428
2619
|
);
|
|
2429
2620
|
let cards = await this.generator.getWeightedCards(fetchLimit, context);
|
|
2621
|
+
const tGenerate = performance.now();
|
|
2430
2622
|
const generatedCount = cards.length;
|
|
2431
2623
|
let generatorSummaries;
|
|
2432
2624
|
if (this.generator.generators) {
|
|
@@ -2455,6 +2647,7 @@ var init_Pipeline = __esm({
|
|
|
2455
2647
|
}
|
|
2456
2648
|
logger.debug(`[Pipeline] Generator returned ${generatedCount} candidates`);
|
|
2457
2649
|
cards = await this.hydrateTags(cards);
|
|
2650
|
+
const tHydrate = performance.now();
|
|
2458
2651
|
const allCardsBeforeFiltering = [...cards];
|
|
2459
2652
|
const filterImpacts = [];
|
|
2460
2653
|
for (const filter of this.filters) {
|
|
@@ -2473,8 +2666,17 @@ var init_Pipeline = __esm({
|
|
|
2473
2666
|
logger.debug(`[Pipeline] Filter '${filter.name}': ${beforeScores.size} \u2192 ${cards.length} cards (\u2191${boosted} \u2193${penalized} =${passed})`);
|
|
2474
2667
|
}
|
|
2475
2668
|
cards = cards.filter((c) => c.score > 0);
|
|
2669
|
+
const hints = this._ephemeralHints;
|
|
2670
|
+
if (hints) {
|
|
2671
|
+
this._ephemeralHints = null;
|
|
2672
|
+
cards = this.applyHints(cards, hints, allCardsBeforeFiltering);
|
|
2673
|
+
}
|
|
2476
2674
|
cards.sort((a, b) => b.score - a.score);
|
|
2675
|
+
const tFilter = performance.now();
|
|
2477
2676
|
const result = cards.slice(0, limit);
|
|
2677
|
+
logger.info(
|
|
2678
|
+
`[Pipeline:timing] total=${(tFilter - t0).toFixed(0)}ms (context=${(tContext - t0).toFixed(0)} generate=${(tGenerate - tContext).toFixed(0)} hydrate=${(tHydrate - tGenerate).toFixed(0)} filter=${(tFilter - tHydrate).toFixed(0)})`
|
|
2679
|
+
);
|
|
2478
2680
|
const topScores = result.slice(0, 3).map((c) => c.score);
|
|
2479
2681
|
logExecutionSummary(
|
|
2480
2682
|
this.generator.name,
|
|
@@ -2484,6 +2686,7 @@ var init_Pipeline = __esm({
|
|
|
2484
2686
|
topScores,
|
|
2485
2687
|
filterImpacts
|
|
2486
2688
|
);
|
|
2689
|
+
logResultCards(result);
|
|
2487
2690
|
logCardProvenance(result, 3);
|
|
2488
2691
|
try {
|
|
2489
2692
|
const courseName = await this.course?.getCourseConfig().then((c) => c.name).catch(() => void 0);
|
|
@@ -2510,6 +2713,10 @@ var init_Pipeline = __esm({
|
|
|
2510
2713
|
* to the WeightedCard objects. Filters can then use card.tags instead of
|
|
2511
2714
|
* making individual getAppliedTags() calls.
|
|
2512
2715
|
*
|
|
2716
|
+
* Uses a persistent tag cache across pipeline runs — tags are static within
|
|
2717
|
+
* a session, so cards seen in a prior run (e.g. before a replan) don't
|
|
2718
|
+
* require a second DB query.
|
|
2719
|
+
*
|
|
2513
2720
|
* @param cards - Cards to hydrate
|
|
2514
2721
|
* @returns Cards with tags populated
|
|
2515
2722
|
*/
|
|
@@ -2517,14 +2724,128 @@ var init_Pipeline = __esm({
|
|
|
2517
2724
|
if (cards.length === 0) {
|
|
2518
2725
|
return cards;
|
|
2519
2726
|
}
|
|
2520
|
-
const
|
|
2521
|
-
const
|
|
2727
|
+
const uncachedIds = [];
|
|
2728
|
+
for (const card of cards) {
|
|
2729
|
+
if (!this._tagCache.has(card.cardId)) {
|
|
2730
|
+
uncachedIds.push(card.cardId);
|
|
2731
|
+
}
|
|
2732
|
+
}
|
|
2733
|
+
if (uncachedIds.length > 0) {
|
|
2734
|
+
const freshTags = await this.course.getAppliedTagsBatch(uncachedIds);
|
|
2735
|
+
for (const [cardId, tags] of freshTags) {
|
|
2736
|
+
this._tagCache.set(cardId, tags);
|
|
2737
|
+
}
|
|
2738
|
+
}
|
|
2739
|
+
const tagsByCard = /* @__PURE__ */ new Map();
|
|
2740
|
+
for (const card of cards) {
|
|
2741
|
+
tagsByCard.set(card.cardId, this._tagCache.get(card.cardId) ?? []);
|
|
2742
|
+
}
|
|
2522
2743
|
logTagHydration(cards, tagsByCard);
|
|
2523
2744
|
return cards.map((card) => ({
|
|
2524
2745
|
...card,
|
|
2525
|
-
tags:
|
|
2746
|
+
tags: this._tagCache.get(card.cardId) ?? []
|
|
2526
2747
|
}));
|
|
2527
2748
|
}
|
|
2749
|
+
// ---------------------------------------------------------------------------
|
|
2750
|
+
// Ephemeral hints application
|
|
2751
|
+
// ---------------------------------------------------------------------------
|
|
2752
|
+
/**
|
|
2753
|
+
* Apply one-shot replan hints to the post-filter card set.
|
|
2754
|
+
*
|
|
2755
|
+
* Order of operations:
|
|
2756
|
+
* 1. Exclude (remove unwanted cards)
|
|
2757
|
+
* 2. Boost (multiply scores)
|
|
2758
|
+
* 3. Require (inject must-have cards from the full pre-filter pool)
|
|
2759
|
+
*
|
|
2760
|
+
* @param cards - Post-filter cards (score > 0)
|
|
2761
|
+
* @param hints - The ephemeral hints to apply
|
|
2762
|
+
* @param allCards - Full pre-filter card pool (for require injection)
|
|
2763
|
+
*/
|
|
2764
|
+
applyHints(cards, hints, allCards) {
|
|
2765
|
+
const beforeCount = cards.length;
|
|
2766
|
+
if (hints.excludeCards?.length) {
|
|
2767
|
+
cards = cards.filter(
|
|
2768
|
+
(c) => !hints.excludeCards.some((pat) => globMatch(c.cardId, pat))
|
|
2769
|
+
);
|
|
2770
|
+
}
|
|
2771
|
+
if (hints.excludeTags?.length) {
|
|
2772
|
+
cards = cards.filter(
|
|
2773
|
+
(c) => !hints.excludeTags.some((pat) => cardMatchesTagPattern(c, pat))
|
|
2774
|
+
);
|
|
2775
|
+
}
|
|
2776
|
+
if (hints.boostTags) {
|
|
2777
|
+
for (const [pattern, factor] of Object.entries(hints.boostTags)) {
|
|
2778
|
+
for (const card of cards) {
|
|
2779
|
+
if (cardMatchesTagPattern(card, pattern)) {
|
|
2780
|
+
card.score *= factor;
|
|
2781
|
+
card.provenance.push({
|
|
2782
|
+
strategy: "ephemeralHint",
|
|
2783
|
+
strategyId: "ephemeral-hint",
|
|
2784
|
+
strategyName: "Replan Hint",
|
|
2785
|
+
action: "boosted",
|
|
2786
|
+
score: card.score,
|
|
2787
|
+
reason: `boostTag ${pattern} \xD7${factor}`
|
|
2788
|
+
});
|
|
2789
|
+
}
|
|
2790
|
+
}
|
|
2791
|
+
}
|
|
2792
|
+
}
|
|
2793
|
+
if (hints.boostCards) {
|
|
2794
|
+
for (const [pattern, factor] of Object.entries(hints.boostCards)) {
|
|
2795
|
+
for (const card of cards) {
|
|
2796
|
+
if (globMatch(card.cardId, pattern)) {
|
|
2797
|
+
card.score *= factor;
|
|
2798
|
+
card.provenance.push({
|
|
2799
|
+
strategy: "ephemeralHint",
|
|
2800
|
+
strategyId: "ephemeral-hint",
|
|
2801
|
+
strategyName: "Replan Hint",
|
|
2802
|
+
action: "boosted",
|
|
2803
|
+
score: card.score,
|
|
2804
|
+
reason: `boostCard ${pattern} \xD7${factor}`
|
|
2805
|
+
});
|
|
2806
|
+
}
|
|
2807
|
+
}
|
|
2808
|
+
}
|
|
2809
|
+
}
|
|
2810
|
+
const cardIds = new Set(cards.map((c) => c.cardId));
|
|
2811
|
+
const inject = (card, reason) => {
|
|
2812
|
+
if (!cardIds.has(card.cardId)) {
|
|
2813
|
+
const floorScore = Math.max(card.score, 1);
|
|
2814
|
+
cards.push({
|
|
2815
|
+
...card,
|
|
2816
|
+
score: floorScore,
|
|
2817
|
+
provenance: [
|
|
2818
|
+
...card.provenance,
|
|
2819
|
+
{
|
|
2820
|
+
strategy: "ephemeralHint",
|
|
2821
|
+
strategyId: "ephemeral-hint",
|
|
2822
|
+
strategyName: "Replan Hint",
|
|
2823
|
+
action: "boosted",
|
|
2824
|
+
score: floorScore,
|
|
2825
|
+
reason
|
|
2826
|
+
}
|
|
2827
|
+
]
|
|
2828
|
+
});
|
|
2829
|
+
cardIds.add(card.cardId);
|
|
2830
|
+
}
|
|
2831
|
+
};
|
|
2832
|
+
if (hints.requireCards?.length) {
|
|
2833
|
+
for (const pattern of hints.requireCards) {
|
|
2834
|
+
for (const card of allCards) {
|
|
2835
|
+
if (globMatch(card.cardId, pattern)) inject(card, `requireCard ${pattern}`);
|
|
2836
|
+
}
|
|
2837
|
+
}
|
|
2838
|
+
}
|
|
2839
|
+
if (hints.requireTags?.length) {
|
|
2840
|
+
for (const pattern of hints.requireTags) {
|
|
2841
|
+
for (const card of allCards) {
|
|
2842
|
+
if (cardMatchesTagPattern(card, pattern)) inject(card, `requireTag ${pattern}`);
|
|
2843
|
+
}
|
|
2844
|
+
}
|
|
2845
|
+
}
|
|
2846
|
+
logger.info(`[Pipeline] Hints applied: ${beforeCount} \u2192 ${cards.length} cards`);
|
|
2847
|
+
return cards;
|
|
2848
|
+
}
|
|
2528
2849
|
/**
|
|
2529
2850
|
* Build shared context for generator and filters.
|
|
2530
2851
|
*
|
|
@@ -2542,7 +2863,10 @@ var init_Pipeline = __esm({
|
|
|
2542
2863
|
} catch (e) {
|
|
2543
2864
|
logger.debug(`[Pipeline] Could not get user ELO, using default: ${e}`);
|
|
2544
2865
|
}
|
|
2545
|
-
|
|
2866
|
+
if (!this._cachedOrchestration) {
|
|
2867
|
+
this._cachedOrchestration = await createOrchestrationContext(this.user, this.course);
|
|
2868
|
+
}
|
|
2869
|
+
const orchestration = this._cachedOrchestration;
|
|
2546
2870
|
return {
|
|
2547
2871
|
user: this.user,
|
|
2548
2872
|
course: this.course,
|
|
@@ -2586,6 +2910,87 @@ var init_Pipeline = __esm({
|
|
|
2586
2910
|
}
|
|
2587
2911
|
return [...new Set(ids)];
|
|
2588
2912
|
}
|
|
2913
|
+
// ---------------------------------------------------------------------------
|
|
2914
|
+
// Card-space diagnostic
|
|
2915
|
+
// ---------------------------------------------------------------------------
|
|
2916
|
+
/**
|
|
2917
|
+
* Scan every card in the course through the filter chain and report
|
|
2918
|
+
* how many are "well indicated" (score >= threshold) for the current user.
|
|
2919
|
+
*
|
|
2920
|
+
* Also reports how many well-indicated cards the user has NOT yet encountered.
|
|
2921
|
+
*
|
|
2922
|
+
* Exposed via `window.skuilder.pipeline.diagnoseCardSpace()`.
|
|
2923
|
+
*/
|
|
2924
|
+
async diagnoseCardSpace(opts) {
|
|
2925
|
+
const THRESHOLD = opts?.threshold ?? 0.1;
|
|
2926
|
+
const t0 = performance.now();
|
|
2927
|
+
const allCardIds = await this.course.getAllCardIds();
|
|
2928
|
+
let cards = allCardIds.map((cardId) => ({
|
|
2929
|
+
cardId,
|
|
2930
|
+
courseId: this.course.getCourseID(),
|
|
2931
|
+
score: 1,
|
|
2932
|
+
provenance: []
|
|
2933
|
+
}));
|
|
2934
|
+
cards = await this.hydrateTags(cards);
|
|
2935
|
+
const context = await this.buildContext();
|
|
2936
|
+
const filterBreakdown = [];
|
|
2937
|
+
for (const filter of this.filters) {
|
|
2938
|
+
cards = await filter.transform(cards, context);
|
|
2939
|
+
const wi = cards.filter((c) => c.score >= THRESHOLD).length;
|
|
2940
|
+
filterBreakdown.push({ name: filter.name, wellIndicated: wi });
|
|
2941
|
+
}
|
|
2942
|
+
const wellIndicated = cards.filter((c) => c.score >= THRESHOLD);
|
|
2943
|
+
const wellIndicatedIds = new Set(wellIndicated.map((c) => c.cardId));
|
|
2944
|
+
let encounteredIds;
|
|
2945
|
+
try {
|
|
2946
|
+
const courseId = this.course.getCourseID();
|
|
2947
|
+
const seenCards = await this.user.getSeenCards(courseId);
|
|
2948
|
+
encounteredIds = new Set(seenCards);
|
|
2949
|
+
} catch {
|
|
2950
|
+
encounteredIds = /* @__PURE__ */ new Set();
|
|
2951
|
+
}
|
|
2952
|
+
const wellIndicatedNew = wellIndicated.filter((c) => !encounteredIds.has(c.cardId));
|
|
2953
|
+
const byType = /* @__PURE__ */ new Map();
|
|
2954
|
+
for (const card of cards) {
|
|
2955
|
+
const type = card.cardId.split("-")[1] || "unknown";
|
|
2956
|
+
if (!byType.has(type)) {
|
|
2957
|
+
byType.set(type, { total: 0, wellIndicated: 0, new: 0 });
|
|
2958
|
+
}
|
|
2959
|
+
const entry = byType.get(type);
|
|
2960
|
+
entry.total++;
|
|
2961
|
+
if (card.score >= THRESHOLD) {
|
|
2962
|
+
entry.wellIndicated++;
|
|
2963
|
+
if (!encounteredIds.has(card.cardId)) entry.new++;
|
|
2964
|
+
}
|
|
2965
|
+
}
|
|
2966
|
+
const elapsed = performance.now() - t0;
|
|
2967
|
+
const result = {
|
|
2968
|
+
totalCards: allCardIds.length,
|
|
2969
|
+
threshold: THRESHOLD,
|
|
2970
|
+
wellIndicated: wellIndicatedIds.size,
|
|
2971
|
+
encountered: encounteredIds.size,
|
|
2972
|
+
wellIndicatedNew: wellIndicatedNew.length,
|
|
2973
|
+
byType: Object.fromEntries(byType),
|
|
2974
|
+
filterBreakdown,
|
|
2975
|
+
elapsedMs: Math.round(elapsed)
|
|
2976
|
+
};
|
|
2977
|
+
logger.info(`[Pipeline:diagnose] Card space scan (${result.elapsedMs}ms):`);
|
|
2978
|
+
logger.info(`[Pipeline:diagnose] Total cards: ${result.totalCards}`);
|
|
2979
|
+
logger.info(`[Pipeline:diagnose] Well-indicated (score >= ${THRESHOLD}): ${result.wellIndicated}`);
|
|
2980
|
+
logger.info(`[Pipeline:diagnose] Encountered: ${result.encountered}`);
|
|
2981
|
+
logger.info(`[Pipeline:diagnose] Well-indicated & new: ${result.wellIndicatedNew}`);
|
|
2982
|
+
logger.info(`[Pipeline:diagnose] By type:`);
|
|
2983
|
+
for (const [type, counts] of byType) {
|
|
2984
|
+
logger.info(
|
|
2985
|
+
`[Pipeline:diagnose] ${type}: ${counts.wellIndicated}/${counts.total} well-indicated, ${counts.new} new`
|
|
2986
|
+
);
|
|
2987
|
+
}
|
|
2988
|
+
logger.info(`[Pipeline:diagnose] After each filter:`);
|
|
2989
|
+
for (const fb of filterBreakdown) {
|
|
2990
|
+
logger.info(`[Pipeline:diagnose] ${fb.name}: ${fb.wellIndicated} well-indicated`);
|
|
2991
|
+
}
|
|
2992
|
+
return result;
|
|
2993
|
+
}
|
|
2589
2994
|
};
|
|
2590
2995
|
}
|
|
2591
2996
|
});
|
|
@@ -2690,23 +3095,25 @@ var init_PipelineAssembler = __esm({
|
|
|
2690
3095
|
warnings.push(`Unknown strategy type '${s.implementingClass}', skipping: ${s.name}`);
|
|
2691
3096
|
}
|
|
2692
3097
|
}
|
|
3098
|
+
const courseId = course.getCourseID();
|
|
3099
|
+
const hasElo = generatorStrategies.some((s) => s.implementingClass === "elo" /* ELO */);
|
|
3100
|
+
const hasSrs = generatorStrategies.some((s) => s.implementingClass === "srs" /* SRS */);
|
|
3101
|
+
if (!hasElo) {
|
|
3102
|
+
logger.debug("[PipelineAssembler] No ELO generator configured, adding default");
|
|
3103
|
+
generatorStrategies.push(createDefaultEloStrategy(courseId));
|
|
3104
|
+
}
|
|
3105
|
+
if (!hasSrs) {
|
|
3106
|
+
logger.debug("[PipelineAssembler] No SRS generator configured, adding default");
|
|
3107
|
+
generatorStrategies.push(createDefaultSrsStrategy(courseId));
|
|
3108
|
+
}
|
|
2693
3109
|
if (generatorStrategies.length === 0) {
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
} else {
|
|
2702
|
-
warnings.push("No generator strategy found");
|
|
2703
|
-
return {
|
|
2704
|
-
pipeline: null,
|
|
2705
|
-
generatorStrategies: [],
|
|
2706
|
-
filterStrategies: [],
|
|
2707
|
-
warnings
|
|
2708
|
-
};
|
|
2709
|
-
}
|
|
3110
|
+
warnings.push("No generator strategy found");
|
|
3111
|
+
return {
|
|
3112
|
+
pipeline: null,
|
|
3113
|
+
generatorStrategies: [],
|
|
3114
|
+
filterStrategies: [],
|
|
3115
|
+
warnings
|
|
3116
|
+
};
|
|
2710
3117
|
}
|
|
2711
3118
|
let generator;
|
|
2712
3119
|
if (generatorStrategies.length === 1) {
|
|
@@ -2784,6 +3191,7 @@ var init_3 = __esm({
|
|
|
2784
3191
|
"./generators/CompositeGenerator.ts": () => Promise.resolve().then(() => (init_CompositeGenerator(), CompositeGenerator_exports)),
|
|
2785
3192
|
"./generators/elo.ts": () => Promise.resolve().then(() => (init_elo(), elo_exports)),
|
|
2786
3193
|
"./generators/index.ts": () => Promise.resolve().then(() => (init_generators(), generators_exports)),
|
|
3194
|
+
"./generators/prescribed.ts": () => Promise.resolve().then(() => (init_prescribed(), prescribed_exports)),
|
|
2787
3195
|
"./generators/srs.ts": () => Promise.resolve().then(() => (init_srs(), srs_exports)),
|
|
2788
3196
|
"./generators/types.ts": () => Promise.resolve().then(() => (init_types(), types_exports)),
|
|
2789
3197
|
"./index.ts": () => Promise.resolve().then(() => (init_navigators(), navigators_exports))
|
|
@@ -2832,8 +3240,10 @@ async function initializeNavigatorRegistry() {
|
|
|
2832
3240
|
Promise.resolve().then(() => (init_elo(), elo_exports)),
|
|
2833
3241
|
Promise.resolve().then(() => (init_srs(), srs_exports))
|
|
2834
3242
|
]);
|
|
3243
|
+
const prescribedModule = await Promise.resolve().then(() => (init_prescribed(), prescribed_exports));
|
|
2835
3244
|
registerNavigator("elo", eloModule.default);
|
|
2836
3245
|
registerNavigator("srs", srsModule.default);
|
|
3246
|
+
registerNavigator("prescribed", prescribedModule.default);
|
|
2837
3247
|
const [
|
|
2838
3248
|
hierarchyModule,
|
|
2839
3249
|
interferenceModule,
|
|
@@ -2888,6 +3298,7 @@ var init_navigators = __esm({
|
|
|
2888
3298
|
Navigators = /* @__PURE__ */ ((Navigators2) => {
|
|
2889
3299
|
Navigators2["ELO"] = "elo";
|
|
2890
3300
|
Navigators2["SRS"] = "srs";
|
|
3301
|
+
Navigators2["PRESCRIBED"] = "prescribed";
|
|
2891
3302
|
Navigators2["HIERARCHY"] = "hierarchyDefinition";
|
|
2892
3303
|
Navigators2["INTERFERENCE"] = "interferenceMitigator";
|
|
2893
3304
|
Navigators2["RELATIVE_PRIORITY"] = "relativePriority";
|
|
@@ -2902,6 +3313,7 @@ var init_navigators = __esm({
|
|
|
2902
3313
|
NavigatorRoles = {
|
|
2903
3314
|
["elo" /* ELO */]: "generator" /* GENERATOR */,
|
|
2904
3315
|
["srs" /* SRS */]: "generator" /* GENERATOR */,
|
|
3316
|
+
["prescribed" /* PRESCRIBED */]: "generator" /* GENERATOR */,
|
|
2905
3317
|
["hierarchyDefinition" /* HIERARCHY */]: "filter" /* FILTER */,
|
|
2906
3318
|
["interferenceMitigator" /* INTERFERENCE */]: "filter" /* FILTER */,
|
|
2907
3319
|
["relativePriority" /* RELATIVE_PRIORITY */]: "filter" /* FILTER */,
|
|
@@ -3066,6 +3478,12 @@ var init_navigators = __esm({
|
|
|
3066
3478
|
async getWeightedCards(_limit) {
|
|
3067
3479
|
throw new Error(`${this.constructor.name} must implement getWeightedCards(). `);
|
|
3068
3480
|
}
|
|
3481
|
+
/**
|
|
3482
|
+
* Set ephemeral hints for the next pipeline run.
|
|
3483
|
+
* No-op for non-Pipeline navigators. Pipeline overrides this.
|
|
3484
|
+
*/
|
|
3485
|
+
setEphemeralHints(_hints) {
|
|
3486
|
+
}
|
|
3069
3487
|
};
|
|
3070
3488
|
}
|
|
3071
3489
|
});
|
|
@@ -3116,6 +3534,16 @@ var init_adminDB2 = __esm({
|
|
|
3116
3534
|
}
|
|
3117
3535
|
});
|
|
3118
3536
|
|
|
3537
|
+
// src/impl/couch/CourseSyncService.ts
|
|
3538
|
+
var init_CourseSyncService = __esm({
|
|
3539
|
+
"src/impl/couch/CourseSyncService.ts"() {
|
|
3540
|
+
"use strict";
|
|
3541
|
+
init_pouchdb_setup();
|
|
3542
|
+
init_couch();
|
|
3543
|
+
init_logger();
|
|
3544
|
+
}
|
|
3545
|
+
});
|
|
3546
|
+
|
|
3119
3547
|
// src/impl/couch/auth.ts
|
|
3120
3548
|
var import_cross_fetch;
|
|
3121
3549
|
var init_auth = __esm({
|
|
@@ -3179,6 +3607,7 @@ var init_couch = __esm({
|
|
|
3179
3607
|
init_classroomDB2();
|
|
3180
3608
|
init_courseAPI();
|
|
3181
3609
|
init_courseDB();
|
|
3610
|
+
init_CourseSyncService();
|
|
3182
3611
|
init_CouchDBSyncStrategy();
|
|
3183
3612
|
isBrowser = typeof window !== "undefined";
|
|
3184
3613
|
if (isBrowser) {
|
|
@@ -3478,6 +3907,9 @@ Currently logged-in as ${this._username}.`
|
|
|
3478
3907
|
const id = row.id;
|
|
3479
3908
|
return id.startsWith(DocTypePrefixes["CARDRECORD" /* CARDRECORD */]) || // Card interaction history
|
|
3480
3909
|
id.startsWith(DocTypePrefixes["SCHEDULED_CARD" /* SCHEDULED_CARD */]) || // Scheduled reviews
|
|
3910
|
+
id.startsWith(DocTypePrefixes["STRATEGY_STATE" /* STRATEGY_STATE */]) || // Strategy state (user prefs, progression)
|
|
3911
|
+
id.startsWith(DocTypePrefixes["USER_OUTCOME" /* USER_OUTCOME */]) || // Evolutionary orchestration outcomes
|
|
3912
|
+
id.startsWith(DocTypePrefixes["STRATEGY_LEARNING_STATE" /* STRATEGY_LEARNING_STATE */]) || // Strategy learning state
|
|
3481
3913
|
id === _BaseUser.DOC_IDS.COURSE_REGISTRATIONS || // Course registrations
|
|
3482
3914
|
id === _BaseUser.DOC_IDS.CLASSROOM_REGISTRATIONS || // Classroom registrations
|
|
3483
3915
|
id === _BaseUser.DOC_IDS.CONFIG;
|
|
@@ -5333,6 +5765,10 @@ var init_courseDB3 = __esm({
|
|
|
5333
5765
|
}
|
|
5334
5766
|
return tagsByCard;
|
|
5335
5767
|
}
|
|
5768
|
+
async getAllCardIds() {
|
|
5769
|
+
const tagsIndex = await this.unpacker.getTagsIndex();
|
|
5770
|
+
return Object.keys(tagsIndex.byCard);
|
|
5771
|
+
}
|
|
5336
5772
|
async addTagToCard(_cardId, _tagId) {
|
|
5337
5773
|
throw new Error("Cannot modify tags in static mode");
|
|
5338
5774
|
}
|