@vue-skuilder/db 0.2.7 → 0.2.9
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-Cplhv3bJ.d.ts → contentSource-C-0t0y0V.d.ts} +7 -0
- package/dist/{contentSource-kI9_jwTu.d.cts → contentSource-jSkcOt2s.d.cts} +7 -0
- package/dist/core/index.d.cts +67 -4
- package/dist/core/index.d.ts +67 -4
- package/dist/core/index.js +201 -39
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.mjs +198 -39
- package/dist/core/index.mjs.map +1 -1
- package/dist/{dataLayerProvider-DrBqOUa3.d.ts → dataLayerProvider-BB0oi9T0.d.ts} +1 -1
- package/dist/{dataLayerProvider-CiA2Rr0v.d.cts → dataLayerProvider-BDClIrFC.d.cts} +1 -1
- package/dist/impl/couch/index.d.cts +2 -2
- package/dist/impl/couch/index.d.ts +2 -2
- package/dist/impl/couch/index.js +195 -39
- package/dist/impl/couch/index.js.map +1 -1
- package/dist/impl/couch/index.mjs +195 -39
- package/dist/impl/couch/index.mjs.map +1 -1
- package/dist/impl/static/index.d.cts +2 -2
- package/dist/impl/static/index.d.ts +2 -2
- package/dist/impl/static/index.js +195 -39
- package/dist/impl/static/index.js.map +1 -1
- package/dist/impl/static/index.mjs +195 -39
- package/dist/impl/static/index.mjs.map +1 -1
- package/dist/index.d.cts +115 -81
- package/dist/index.d.ts +115 -81
- package/dist/index.js +440 -251
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +437 -251
- package/dist/index.mjs.map +1 -1
- package/docs/navigators-architecture.md +29 -13
- package/package.json +3 -3
- package/src/core/interfaces/contentSource.ts +7 -0
- package/src/core/navigators/Pipeline.ts +93 -1
- package/src/core/navigators/PipelineDebugger.ts +11 -1
- package/src/core/navigators/SrsDebugger.ts +53 -0
- package/src/core/navigators/generators/prescribed.ts +76 -9
- package/src/core/navigators/generators/srs.ts +81 -37
- package/src/core/navigators/index.ts +9 -0
- package/src/study/SessionController.ts +260 -249
- package/src/study/SessionDebugger.ts +15 -25
- package/src/study/SessionOverlay.ts +108 -13
package/dist/core/index.js
CHANGED
|
@@ -814,6 +814,7 @@ __export(PipelineDebugger_exports, {
|
|
|
814
814
|
buildRunReport: () => buildRunReport,
|
|
815
815
|
captureRun: () => captureRun,
|
|
816
816
|
clearRunHistory: () => clearRunHistory,
|
|
817
|
+
getActivePipeline: () => getActivePipeline,
|
|
817
818
|
mountPipelineDebugger: () => mountPipelineDebugger,
|
|
818
819
|
pipelineDebugAPI: () => pipelineDebugAPI,
|
|
819
820
|
registerPipelineForDebug: () => registerPipelineForDebug
|
|
@@ -821,6 +822,9 @@ __export(PipelineDebugger_exports, {
|
|
|
821
822
|
function registerPipelineForDebug(pipeline) {
|
|
822
823
|
_activePipeline = pipeline;
|
|
823
824
|
}
|
|
825
|
+
function getActivePipeline() {
|
|
826
|
+
return _activePipeline;
|
|
827
|
+
}
|
|
824
828
|
function clearRunHistory() {
|
|
825
829
|
runHistory.length = 0;
|
|
826
830
|
}
|
|
@@ -1636,6 +1640,30 @@ Example:
|
|
|
1636
1640
|
}
|
|
1637
1641
|
});
|
|
1638
1642
|
|
|
1643
|
+
// src/core/navigators/SrsDebugger.ts
|
|
1644
|
+
var SrsDebugger_exports = {};
|
|
1645
|
+
__export(SrsDebugger_exports, {
|
|
1646
|
+
captureSrsBacklog: () => captureSrsBacklog,
|
|
1647
|
+
clearSrsBacklogDebug: () => clearSrsBacklogDebug,
|
|
1648
|
+
getSrsBacklogDebug: () => getSrsBacklogDebug
|
|
1649
|
+
});
|
|
1650
|
+
function captureSrsBacklog(snapshot) {
|
|
1651
|
+
snapshots.set(snapshot.courseId, snapshot);
|
|
1652
|
+
}
|
|
1653
|
+
function getSrsBacklogDebug() {
|
|
1654
|
+
return [...snapshots.values()].sort((a, b) => b.timestamp - a.timestamp);
|
|
1655
|
+
}
|
|
1656
|
+
function clearSrsBacklogDebug() {
|
|
1657
|
+
snapshots.clear();
|
|
1658
|
+
}
|
|
1659
|
+
var snapshots;
|
|
1660
|
+
var init_SrsDebugger = __esm({
|
|
1661
|
+
"src/core/navigators/SrsDebugger.ts"() {
|
|
1662
|
+
"use strict";
|
|
1663
|
+
snapshots = /* @__PURE__ */ new Map();
|
|
1664
|
+
}
|
|
1665
|
+
});
|
|
1666
|
+
|
|
1639
1667
|
// src/core/navigators/generators/CompositeGenerator.ts
|
|
1640
1668
|
var CompositeGenerator_exports = {};
|
|
1641
1669
|
__export(CompositeGenerator_exports, {
|
|
@@ -2003,7 +2031,7 @@ function shuffleInPlace(arr) {
|
|
|
2003
2031
|
function pickTopByScore(cards, limit) {
|
|
2004
2032
|
return [...cards].sort((a, b) => b.score - a.score || a.cardId.localeCompare(b.cardId)).slice(0, limit);
|
|
2005
2033
|
}
|
|
2006
|
-
var DEFAULT_FRESHNESS_WINDOW, DEFAULT_MAX_DIRECT_PER_RUN, DEFAULT_MAX_SUPPORT_PER_RUN, DEFAULT_HIERARCHY_DEPTH, DEFAULT_MIN_COUNT, DEFAULT_PRACTICE_MIN_COUNT, DEFAULT_MAX_PRACTICE_PER_RUN, BASE_TARGET_SCORE, BASE_SUPPORT_SCORE, DISCOVERED_SUPPORT_SCORE, BASE_PRACTICE_SCORE, MAX_TARGET_MULTIPLIER, MAX_SUPPORT_MULTIPLIER, PRESCRIBED_DEBUG_VERSION, PrescribedCardsGenerator;
|
|
2034
|
+
var DEFAULT_FRESHNESS_WINDOW, DEFAULT_MAX_DIRECT_PER_RUN, DEFAULT_MAX_SUPPORT_PER_RUN, DEFAULT_HIERARCHY_DEPTH, DEFAULT_MIN_COUNT, DEFAULT_PRACTICE_MIN_COUNT, DEFAULT_MAX_PRACTICE_PER_RUN, BASE_TARGET_SCORE, BASE_SUPPORT_SCORE, DISCOVERED_SUPPORT_SCORE, BASE_PRACTICE_SCORE, PRACTICE_BASE_MULT, MAX_PRACTICE_MULTIPLIER, PRACTICE_STALENESS_BUMP_PER_DAY, MAX_TARGET_MULTIPLIER, MAX_SUPPORT_MULTIPLIER, PRESCRIBED_DEBUG_VERSION, PrescribedCardsGenerator;
|
|
2007
2035
|
var init_prescribed = __esm({
|
|
2008
2036
|
"src/core/navigators/generators/prescribed.ts"() {
|
|
2009
2037
|
"use strict";
|
|
@@ -2020,6 +2048,9 @@ var init_prescribed = __esm({
|
|
|
2020
2048
|
BASE_SUPPORT_SCORE = 0.8;
|
|
2021
2049
|
DISCOVERED_SUPPORT_SCORE = 12;
|
|
2022
2050
|
BASE_PRACTICE_SCORE = 1;
|
|
2051
|
+
PRACTICE_BASE_MULT = 2;
|
|
2052
|
+
MAX_PRACTICE_MULTIPLIER = 4;
|
|
2053
|
+
PRACTICE_STALENESS_BUMP_PER_DAY = 0.5;
|
|
2023
2054
|
MAX_TARGET_MULTIPLIER = 8;
|
|
2024
2055
|
MAX_SUPPORT_MULTIPLIER = 4;
|
|
2025
2056
|
PRESCRIBED_DEBUG_VERSION = "testversion-prescribed-v3";
|
|
@@ -2079,6 +2110,8 @@ var init_prescribed = __esm({
|
|
|
2079
2110
|
const emitted = [];
|
|
2080
2111
|
const emittedIds = /* @__PURE__ */ new Set();
|
|
2081
2112
|
const groupRuntimes = [];
|
|
2113
|
+
const priorPracticeDebt = progress.practiceDebt ?? {};
|
|
2114
|
+
const nextPracticeDebt = {};
|
|
2082
2115
|
for (const group of this.config.groups) {
|
|
2083
2116
|
const runtime = this.buildGroupRuntimeState({
|
|
2084
2117
|
group,
|
|
@@ -2136,10 +2169,13 @@ var init_prescribed = __esm({
|
|
|
2136
2169
|
userTagElo,
|
|
2137
2170
|
userGlobalElo,
|
|
2138
2171
|
activeIds,
|
|
2139
|
-
seenIds
|
|
2172
|
+
seenIds,
|
|
2173
|
+
priorPracticeDebt,
|
|
2174
|
+
nextPracticeDebt
|
|
2140
2175
|
});
|
|
2141
2176
|
emitted.push(...directCards, ...supportCards, ...discoveredSupportCards, ...practiceCards);
|
|
2142
2177
|
}
|
|
2178
|
+
nextState.practiceDebt = nextPracticeDebt;
|
|
2143
2179
|
const hintSummary = this.buildSupportHintSummary(groupRuntimes);
|
|
2144
2180
|
const hints = Object.keys(hintSummary.boostTags).length > 0 ? {
|
|
2145
2181
|
boostTags: hintSummary.boostTags,
|
|
@@ -2473,9 +2509,16 @@ var init_prescribed = __esm({
|
|
|
2473
2509
|
* `practiceMinCount`), this resolves cards carrying that tag and emits them
|
|
2474
2510
|
* into the candidate pool. It exists because global-ELO retrieval
|
|
2475
2511
|
* systematically fails to fetch the (low-ELO) drill cards for a
|
|
2476
|
-
* freshly-introduced skill — putting them in the pool here
|
|
2477
|
-
*
|
|
2478
|
-
*
|
|
2512
|
+
* freshly-introduced skill — putting them in the pool here guarantees presence.
|
|
2513
|
+
*
|
|
2514
|
+
* Emphasis is a **practice-debt pressure** (parallel to SRS backlog pressure):
|
|
2515
|
+
* cards score `base × multiplier`, where the multiplier starts at
|
|
2516
|
+
* PRACTICE_BASE_MULT (so a few reps land promptly post-intro, competing with
|
|
2517
|
+
* pressured reviews) and escalates by how long the debt has stayed open
|
|
2518
|
+
* (per-tag, time-based via `priorPracticeDebt`/`nextPracticeDebt`), clamped at
|
|
2519
|
+
* MAX_PRACTICE_MULTIPLIER. The debt is durable and self-discharges the instant
|
|
2520
|
+
* the skill reaches `practiceMinCount` — so this no longer relies on the
|
|
2521
|
+
* session-scoped intro boost to actually surface.
|
|
2479
2522
|
*
|
|
2480
2523
|
* Fully data-driven: the unlock relation comes from the hierarchy config and
|
|
2481
2524
|
* practice-status from per-tag ELO. No card-id or tag-namespace hard-coding.
|
|
@@ -2490,7 +2533,9 @@ var init_prescribed = __esm({
|
|
|
2490
2533
|
userTagElo,
|
|
2491
2534
|
userGlobalElo,
|
|
2492
2535
|
activeIds,
|
|
2493
|
-
seenIds
|
|
2536
|
+
seenIds,
|
|
2537
|
+
priorPracticeDebt,
|
|
2538
|
+
nextPracticeDebt
|
|
2494
2539
|
} = args;
|
|
2495
2540
|
const patterns = group.practiceTagPatterns ?? [];
|
|
2496
2541
|
if (patterns.length === 0) return [];
|
|
@@ -2500,6 +2545,20 @@ var init_prescribed = __esm({
|
|
|
2500
2545
|
(tag) => patterns.some((p) => matchesTagPattern(tag, p)) && this.isUnlockedGatedSkill(tag, hierarchyConfigs, userTagElo, userGlobalElo) && (userTagElo[tag]?.count ?? 0) < practiceMinCount
|
|
2501
2546
|
);
|
|
2502
2547
|
if (practiceTags.length === 0) return [];
|
|
2548
|
+
const now = Date.now();
|
|
2549
|
+
const DAY_MS = 24 * 60 * 60 * 1e3;
|
|
2550
|
+
const tagMultiplier = /* @__PURE__ */ new Map();
|
|
2551
|
+
for (const tag of practiceTags) {
|
|
2552
|
+
const firstOwedAt = priorPracticeDebt[tag] ?? isoNow();
|
|
2553
|
+
nextPracticeDebt[tag] = firstOwedAt;
|
|
2554
|
+
const staleDays = Math.max(0, (now - new Date(firstOwedAt).getTime()) / DAY_MS);
|
|
2555
|
+
const mult = clamp(
|
|
2556
|
+
PRACTICE_BASE_MULT + staleDays * PRACTICE_STALENESS_BUMP_PER_DAY,
|
|
2557
|
+
PRACTICE_BASE_MULT,
|
|
2558
|
+
MAX_PRACTICE_MULTIPLIER
|
|
2559
|
+
);
|
|
2560
|
+
tagMultiplier.set(tag, mult);
|
|
2561
|
+
}
|
|
2503
2562
|
const practiceCardIds = this.findDiscoveredSupportCards({
|
|
2504
2563
|
supportTags: practiceTags,
|
|
2505
2564
|
cardsByTag,
|
|
@@ -2515,18 +2574,25 @@ var init_prescribed = __esm({
|
|
|
2515
2574
|
const cards = [];
|
|
2516
2575
|
for (const cardId of practiceCardIds) {
|
|
2517
2576
|
emittedIds.add(cardId);
|
|
2577
|
+
let mult = PRACTICE_BASE_MULT;
|
|
2578
|
+
for (const tag of practiceTags) {
|
|
2579
|
+
if (cardsByTag.get(tag)?.includes(cardId) ?? false) {
|
|
2580
|
+
mult = Math.max(mult, tagMultiplier.get(tag) ?? PRACTICE_BASE_MULT);
|
|
2581
|
+
}
|
|
2582
|
+
}
|
|
2583
|
+
const score = BASE_PRACTICE_SCORE * mult;
|
|
2518
2584
|
cards.push({
|
|
2519
2585
|
cardId,
|
|
2520
2586
|
courseId,
|
|
2521
|
-
score
|
|
2587
|
+
score,
|
|
2522
2588
|
provenance: [
|
|
2523
2589
|
{
|
|
2524
2590
|
strategy: "prescribed",
|
|
2525
2591
|
strategyName: this.strategyName || this.name,
|
|
2526
2592
|
strategyId: this.strategyId || "NAVIGATION_STRATEGY-prescribed",
|
|
2527
2593
|
action: "generated",
|
|
2528
|
-
score
|
|
2529
|
-
reason: `mode=practice;group=${group.id};underPracticedSkills=${practiceTags.length};practiceTags=${practiceTags.slice(0, 8).join("|")}${practiceTags.length > 8 ? "|\u2026" : ""};testversion=${PRESCRIBED_DEBUG_VERSION}`
|
|
2594
|
+
score,
|
|
2595
|
+
reason: `mode=practice;group=${group.id};debtMult=\xD7${mult.toFixed(2)};underPracticedSkills=${practiceTags.length};practiceTags=${practiceTags.slice(0, 8).join("|")}${practiceTags.length > 8 ? "|\u2026" : ""};testversion=${PRESCRIBED_DEBUG_VERSION}`
|
|
2530
2596
|
}
|
|
2531
2597
|
]
|
|
2532
2598
|
});
|
|
@@ -2699,15 +2765,16 @@ var srs_exports = {};
|
|
|
2699
2765
|
__export(srs_exports, {
|
|
2700
2766
|
default: () => SRSNavigator
|
|
2701
2767
|
});
|
|
2702
|
-
var import_moment3, DEFAULT_HEALTHY_BACKLOG,
|
|
2768
|
+
var import_moment3, DEFAULT_HEALTHY_BACKLOG, MAX_BACKLOG_MULTIPLIER, SRSNavigator;
|
|
2703
2769
|
var init_srs = __esm({
|
|
2704
2770
|
"src/core/navigators/generators/srs.ts"() {
|
|
2705
2771
|
"use strict";
|
|
2706
2772
|
import_moment3 = __toESM(require("moment"), 1);
|
|
2707
2773
|
init_navigators();
|
|
2774
|
+
init_SrsDebugger();
|
|
2708
2775
|
init_logger();
|
|
2709
2776
|
DEFAULT_HEALTHY_BACKLOG = 20;
|
|
2710
|
-
|
|
2777
|
+
MAX_BACKLOG_MULTIPLIER = 2;
|
|
2711
2778
|
SRSNavigator = class extends ContentNavigator {
|
|
2712
2779
|
/** Human-readable name for CardGenerator interface */
|
|
2713
2780
|
name;
|
|
@@ -2774,9 +2841,18 @@ var init_srs = __esm({
|
|
|
2774
2841
|
}
|
|
2775
2842
|
}
|
|
2776
2843
|
}
|
|
2777
|
-
const
|
|
2844
|
+
const backlogMultiplier = this.computeBacklogMultiplier(dueReviews.length);
|
|
2845
|
+
const notDue = reviews.filter((r) => !now.isAfter(import_moment3.default.utc(r.reviewTime)));
|
|
2846
|
+
let nextDueIn = null;
|
|
2847
|
+
if (notDue.length > 0) {
|
|
2848
|
+
const next = notDue.reduce(
|
|
2849
|
+
(a, b) => import_moment3.default.utc(a.reviewTime).isBefore(import_moment3.default.utc(b.reviewTime)) ? a : b
|
|
2850
|
+
);
|
|
2851
|
+
const until = import_moment3.default.duration(import_moment3.default.utc(next.reviewTime).diff(now));
|
|
2852
|
+
nextDueIn = until.asHours() < 1 ? `${Math.round(until.asMinutes())}m` : until.asHours() < 24 ? `${Math.round(until.asHours())}h` : `${Math.round(until.asDays())}d`;
|
|
2853
|
+
}
|
|
2778
2854
|
if (dueReviews.length > 0) {
|
|
2779
|
-
const pressureNote =
|
|
2855
|
+
const pressureNote = backlogMultiplier > 1 ? ` [backlog pressure: \xD7${backlogMultiplier.toFixed(2)}]` : ` [healthy backlog]`;
|
|
2780
2856
|
logger.info(
|
|
2781
2857
|
`[SRS] Course ${courseId}: ${dueReviews.length} reviews due now (of ${reviews.length} scheduled)${pressureNote}`
|
|
2782
2858
|
);
|
|
@@ -2795,7 +2871,7 @@ var init_srs = __esm({
|
|
|
2795
2871
|
logger.info(`[SRS] Course ${courseId}: No reviews scheduled`);
|
|
2796
2872
|
}
|
|
2797
2873
|
const scored = dueReviews.map((review) => {
|
|
2798
|
-
const { score, reason } = this.computeUrgencyScore(review, now,
|
|
2874
|
+
const { score, reason } = this.computeUrgencyScore(review, now, backlogMultiplier);
|
|
2799
2875
|
return {
|
|
2800
2876
|
cardId: review.cardId,
|
|
2801
2877
|
courseId: review.courseId,
|
|
@@ -2813,30 +2889,42 @@ var init_srs = __esm({
|
|
|
2813
2889
|
]
|
|
2814
2890
|
};
|
|
2815
2891
|
});
|
|
2816
|
-
|
|
2892
|
+
const sorted = scored.sort((a, b) => b.score - a.score);
|
|
2893
|
+
captureSrsBacklog({
|
|
2894
|
+
courseId,
|
|
2895
|
+
scheduledTotal: reviews.length,
|
|
2896
|
+
dueNow: dueReviews.length,
|
|
2897
|
+
healthyBacklog: this.healthyBacklog,
|
|
2898
|
+
backlogMultiplier,
|
|
2899
|
+
maxBacklogMultiplier: MAX_BACKLOG_MULTIPLIER,
|
|
2900
|
+
topReviewScore: sorted.length > 0 ? sorted[0].score : null,
|
|
2901
|
+
nextDueIn,
|
|
2902
|
+
timestamp: Date.now()
|
|
2903
|
+
});
|
|
2904
|
+
return { cards: sorted.slice(0, limit) };
|
|
2817
2905
|
}
|
|
2818
2906
|
/**
|
|
2819
|
-
* Compute backlog pressure based on number of due reviews.
|
|
2907
|
+
* Compute the multiplicative backlog pressure based on number of due reviews.
|
|
2820
2908
|
*
|
|
2821
|
-
*
|
|
2822
|
-
* and
|
|
2909
|
+
* ×1.0 at or below the healthy threshold (no boost), increasing linearly above
|
|
2910
|
+
* it and maxing out at MAX_BACKLOG_MULTIPLIER at 3× the healthy backlog.
|
|
2823
2911
|
*
|
|
2824
|
-
* Examples (with default healthyBacklog=20):
|
|
2825
|
-
* - 10 due reviews →
|
|
2826
|
-
* - 20 due reviews →
|
|
2827
|
-
* - 40 due reviews →
|
|
2828
|
-
* - 60 due reviews →
|
|
2912
|
+
* Examples (with default healthyBacklog=20, MAX_BACKLOG_MULTIPLIER=2.0):
|
|
2913
|
+
* - 10 due reviews → ×1.00 (healthy)
|
|
2914
|
+
* - 20 due reviews → ×1.00 (at threshold)
|
|
2915
|
+
* - 40 due reviews → ×1.50 (2x threshold)
|
|
2916
|
+
* - 60 due reviews → ×2.00 (3x threshold, maxed)
|
|
2829
2917
|
*
|
|
2830
2918
|
* @param dueCount - Number of reviews currently due
|
|
2831
|
-
* @returns
|
|
2919
|
+
* @returns Multiplier applied to review urgency (1.0 to MAX_BACKLOG_MULTIPLIER)
|
|
2832
2920
|
*/
|
|
2833
|
-
|
|
2921
|
+
computeBacklogMultiplier(dueCount) {
|
|
2834
2922
|
if (dueCount <= this.healthyBacklog) {
|
|
2835
|
-
return
|
|
2923
|
+
return 1;
|
|
2836
2924
|
}
|
|
2837
2925
|
const excess = dueCount - this.healthyBacklog;
|
|
2838
|
-
const
|
|
2839
|
-
return Math.min(
|
|
2926
|
+
const multiplier = 1 + excess / this.healthyBacklog * ((MAX_BACKLOG_MULTIPLIER - 1) / 2);
|
|
2927
|
+
return Math.min(MAX_BACKLOG_MULTIPLIER, multiplier);
|
|
2840
2928
|
}
|
|
2841
2929
|
/**
|
|
2842
2930
|
* Compute urgency score for a review card.
|
|
@@ -2851,19 +2939,20 @@ var init_srs = __esm({
|
|
|
2851
2939
|
* - 30 days (720h) → ~0.56
|
|
2852
2940
|
* - 180 days → ~0.30
|
|
2853
2941
|
*
|
|
2854
|
-
* 3. Backlog pressure = global
|
|
2855
|
-
*
|
|
2856
|
-
* - At 2x healthy: +0.25
|
|
2857
|
-
* - At 3x+ healthy: +0.50 (max)
|
|
2942
|
+
* 3. Backlog pressure = global *multiplier* when review backlog exceeds the
|
|
2943
|
+
* healthy threshold (×1.0 healthy → up to MAX_BACKLOG_MULTIPLIER at 3×).
|
|
2858
2944
|
*
|
|
2859
|
-
* Combined: base 0.5 +
|
|
2860
|
-
*
|
|
2945
|
+
* Combined: (base 0.5 + urgency factors * 0.45) × backlog multiplier.
|
|
2946
|
+
* Per-card range before pressure: ~0.57–0.95. NOT clamped to 1.0 — under a
|
|
2947
|
+
* heavy backlog reviews scale onto the open scale to compete with (and exceed)
|
|
2948
|
+
* new cards; what keeps them from running away is the bounded multiplier, not
|
|
2949
|
+
* a hard ceiling.
|
|
2861
2950
|
*
|
|
2862
2951
|
* @param review - The scheduled card to score
|
|
2863
2952
|
* @param now - Current time
|
|
2864
|
-
* @param
|
|
2953
|
+
* @param backlogMultiplier - Pre-computed backlog multiplier (1.0 to MAX_BACKLOG_MULTIPLIER)
|
|
2865
2954
|
*/
|
|
2866
|
-
computeUrgencyScore(review, now,
|
|
2955
|
+
computeUrgencyScore(review, now, backlogMultiplier) {
|
|
2867
2956
|
const scheduledAt = import_moment3.default.utc(review.scheduledAt);
|
|
2868
2957
|
const due = import_moment3.default.utc(review.reviewTime);
|
|
2869
2958
|
const intervalHours = Math.max(1, due.diff(scheduledAt, "hours"));
|
|
@@ -2873,15 +2962,15 @@ var init_srs = __esm({
|
|
|
2873
2962
|
const overdueContribution = Math.min(1, Math.max(0, relativeOverdue));
|
|
2874
2963
|
const urgency = overdueContribution * 0.5 + recencyFactor * 0.5;
|
|
2875
2964
|
const baseScore = 0.5 + urgency * 0.45;
|
|
2876
|
-
const score =
|
|
2965
|
+
const score = baseScore * backlogMultiplier;
|
|
2877
2966
|
const reasonParts = [
|
|
2878
2967
|
`${Math.round(hoursOverdue)}h overdue`,
|
|
2879
2968
|
`interval: ${Math.round(intervalHours)}h`,
|
|
2880
2969
|
`relative: ${relativeOverdue.toFixed(2)}`,
|
|
2881
2970
|
`recency: ${recencyFactor.toFixed(2)}`
|
|
2882
2971
|
];
|
|
2883
|
-
if (
|
|
2884
|
-
reasonParts.push(`backlog:
|
|
2972
|
+
if (backlogMultiplier > 1) {
|
|
2973
|
+
reasonParts.push(`backlog: \xD7${backlogMultiplier.toFixed(2)}`);
|
|
2885
2974
|
}
|
|
2886
2975
|
reasonParts.push("review");
|
|
2887
2976
|
const reason = reasonParts.join(", ");
|
|
@@ -4853,6 +4942,68 @@ var init_Pipeline = __esm({
|
|
|
4853
4942
|
// ---------------------------------------------------------------------------
|
|
4854
4943
|
// Card-space diagnostic
|
|
4855
4944
|
// ---------------------------------------------------------------------------
|
|
4945
|
+
/**
|
|
4946
|
+
* Commit-free forecast: score the user's full card space through the filter
|
|
4947
|
+
* chain and return the cards that are currently *reachable* (score >=
|
|
4948
|
+
* threshold), optionally nudged by caller-supplied hints and/or restricted
|
|
4949
|
+
* to cards the user hasn't seen yet.
|
|
4950
|
+
*
|
|
4951
|
+
* This is a GENERIC primitive — it returns scored, tag-hydrated cards and
|
|
4952
|
+
* stops there. It has no knowledge of any particular tag convention; callers
|
|
4953
|
+
* decide what the surviving cards mean (e.g. filter to their own "intro"
|
|
4954
|
+
* tag family). Nothing is written and no session is started.
|
|
4955
|
+
*
|
|
4956
|
+
* The optional `hints` are the "out-of-band kick": they run through the same
|
|
4957
|
+
* {@link applyHints} path a live replan uses, so the two semantics carry over —
|
|
4958
|
+
* - `boostTags`/`boostCards` reweight *within* gating (a gated score-0 card
|
|
4959
|
+
* stays out), and
|
|
4960
|
+
* - `requireTags`/`requireCards` inject from the full pre-filter pool,
|
|
4961
|
+
* *bypassing* gating (use when you want a card regardless of reachability).
|
|
4962
|
+
* Note `unseenOnly` is applied LAST, so it can drop a `require`d card that the
|
|
4963
|
+
* user has already seen — pass `unseenOnly: false` if that matters.
|
|
4964
|
+
*
|
|
4965
|
+
* Cost note: like {@link diagnoseCardSpace}, this scans every card through the
|
|
4966
|
+
* filters, so it's heavier than a normal replan. Intended for one-shot
|
|
4967
|
+
* out-of-band use (e.g. a session-end "what's next" snapshot), not the hot path.
|
|
4968
|
+
*
|
|
4969
|
+
* @param opts.hints Optional ephemeral hints to apply after the filter chain.
|
|
4970
|
+
* @param opts.unseenOnly Only return cards the user hasn't encountered (default true).
|
|
4971
|
+
* @param opts.threshold Min score to count as reachable (default 0.10).
|
|
4972
|
+
* @param opts.limit Optional cap on results (already sorted desc).
|
|
4973
|
+
*/
|
|
4974
|
+
async forecast(opts) {
|
|
4975
|
+
const threshold = opts?.threshold ?? 0.1;
|
|
4976
|
+
const unseenOnly = opts?.unseenOnly ?? true;
|
|
4977
|
+
const courseId = this.course.getCourseID();
|
|
4978
|
+
const allCardIds = await this.course.getAllCardIds();
|
|
4979
|
+
let cards = allCardIds.map((cardId) => ({
|
|
4980
|
+
cardId,
|
|
4981
|
+
courseId,
|
|
4982
|
+
score: 1,
|
|
4983
|
+
provenance: []
|
|
4984
|
+
}));
|
|
4985
|
+
cards = await this.hydrateTags(cards);
|
|
4986
|
+
const fullPool = cards.slice();
|
|
4987
|
+
const context = await this.buildContext();
|
|
4988
|
+
for (const filter of this.filters) {
|
|
4989
|
+
cards = await filter.transform(cards, context);
|
|
4990
|
+
}
|
|
4991
|
+
if (opts?.hints) {
|
|
4992
|
+
cards = this.applyHints(cards, opts.hints, fullPool);
|
|
4993
|
+
}
|
|
4994
|
+
cards = cards.filter((c) => c.score >= threshold);
|
|
4995
|
+
if (unseenOnly) {
|
|
4996
|
+
let encountered;
|
|
4997
|
+
try {
|
|
4998
|
+
encountered = new Set(await this.user.getSeenCards(courseId));
|
|
4999
|
+
} catch {
|
|
5000
|
+
encountered = /* @__PURE__ */ new Set();
|
|
5001
|
+
}
|
|
5002
|
+
cards = cards.filter((c) => !encountered.has(c.cardId));
|
|
5003
|
+
}
|
|
5004
|
+
cards.sort((a, b) => b.score - a.score);
|
|
5005
|
+
return opts?.limit ? cards.slice(0, opts.limit) : cards;
|
|
5006
|
+
}
|
|
4856
5007
|
/**
|
|
4857
5008
|
* Scan every card in the course through the filter chain and report
|
|
4858
5009
|
* how many are "well indicated" (score >= threshold) for the current user.
|
|
@@ -5117,6 +5268,7 @@ var init_3 = __esm({
|
|
|
5117
5268
|
"./Pipeline.ts": () => Promise.resolve().then(() => (init_Pipeline(), Pipeline_exports)),
|
|
5118
5269
|
"./PipelineAssembler.ts": () => Promise.resolve().then(() => (init_PipelineAssembler(), PipelineAssembler_exports)),
|
|
5119
5270
|
"./PipelineDebugger.ts": () => Promise.resolve().then(() => (init_PipelineDebugger(), PipelineDebugger_exports)),
|
|
5271
|
+
"./SrsDebugger.ts": () => Promise.resolve().then(() => (init_SrsDebugger(), SrsDebugger_exports)),
|
|
5120
5272
|
"./defaults.ts": () => Promise.resolve().then(() => (init_defaults(), defaults_exports)),
|
|
5121
5273
|
"./diversityRerank.ts": () => Promise.resolve().then(() => (init_diversityRerank(), diversityRerank_exports)),
|
|
5122
5274
|
"./filters/WeightedFilter.ts": () => Promise.resolve().then(() => (init_WeightedFilter(), WeightedFilter_exports)),
|
|
@@ -5149,11 +5301,14 @@ __export(navigators_exports, {
|
|
|
5149
5301
|
NavigatorRole: () => NavigatorRole,
|
|
5150
5302
|
NavigatorRoles: () => NavigatorRoles,
|
|
5151
5303
|
Navigators: () => Navigators,
|
|
5304
|
+
clearSrsBacklogDebug: () => clearSrsBacklogDebug,
|
|
5152
5305
|
diversityRerank: () => diversityRerank,
|
|
5306
|
+
getActivePipeline: () => getActivePipeline,
|
|
5153
5307
|
getCardOrigin: () => getCardOrigin,
|
|
5154
5308
|
getRegisteredNavigator: () => getRegisteredNavigator,
|
|
5155
5309
|
getRegisteredNavigatorNames: () => getRegisteredNavigatorNames,
|
|
5156
5310
|
getRegisteredNavigatorRole: () => getRegisteredNavigatorRole,
|
|
5311
|
+
getSrsBacklogDebug: () => getSrsBacklogDebug,
|
|
5157
5312
|
hasRegisteredNavigator: () => hasRegisteredNavigator,
|
|
5158
5313
|
initializeNavigatorRegistry: () => initializeNavigatorRegistry,
|
|
5159
5314
|
isFilter: () => isFilter,
|
|
@@ -5235,6 +5390,7 @@ var init_navigators = __esm({
|
|
|
5235
5390
|
"use strict";
|
|
5236
5391
|
init_diversityRerank();
|
|
5237
5392
|
init_PipelineDebugger();
|
|
5393
|
+
init_SrsDebugger();
|
|
5238
5394
|
init_logger();
|
|
5239
5395
|
init_();
|
|
5240
5396
|
init_2();
|
|
@@ -8253,6 +8409,7 @@ __export(core_exports, {
|
|
|
8253
8409
|
aggregateOutcomesForGradient: () => aggregateOutcomesForGradient,
|
|
8254
8410
|
areQuestionRecords: () => areQuestionRecords,
|
|
8255
8411
|
buildStrategyStateId: () => buildStrategyStateId,
|
|
8412
|
+
clearSrsBacklogDebug: () => clearSrsBacklogDebug,
|
|
8256
8413
|
computeDeviation: () => computeDeviation,
|
|
8257
8414
|
computeEffectiveWeight: () => computeEffectiveWeight,
|
|
8258
8415
|
computeOutcomeSignal: () => computeOutcomeSignal,
|
|
@@ -8261,12 +8418,14 @@ __export(core_exports, {
|
|
|
8261
8418
|
createOrchestrationContext: () => createOrchestrationContext,
|
|
8262
8419
|
diversityRerank: () => diversityRerank,
|
|
8263
8420
|
docIsDeleted: () => docIsDeleted,
|
|
8421
|
+
getActivePipeline: () => getActivePipeline,
|
|
8264
8422
|
getCardHistoryID: () => getCardHistoryID,
|
|
8265
8423
|
getCardOrigin: () => getCardOrigin,
|
|
8266
8424
|
getDefaultLearnableWeight: () => getDefaultLearnableWeight,
|
|
8267
8425
|
getRegisteredNavigator: () => getRegisteredNavigator,
|
|
8268
8426
|
getRegisteredNavigatorNames: () => getRegisteredNavigatorNames,
|
|
8269
8427
|
getRegisteredNavigatorRole: () => getRegisteredNavigatorRole,
|
|
8428
|
+
getSrsBacklogDebug: () => getSrsBacklogDebug,
|
|
8270
8429
|
getStudySource: () => getStudySource,
|
|
8271
8430
|
hasRegisteredNavigator: () => hasRegisteredNavigator,
|
|
8272
8431
|
importParsedCards: () => importParsedCards,
|
|
@@ -8321,6 +8480,7 @@ init_core();
|
|
|
8321
8480
|
aggregateOutcomesForGradient,
|
|
8322
8481
|
areQuestionRecords,
|
|
8323
8482
|
buildStrategyStateId,
|
|
8483
|
+
clearSrsBacklogDebug,
|
|
8324
8484
|
computeDeviation,
|
|
8325
8485
|
computeEffectiveWeight,
|
|
8326
8486
|
computeOutcomeSignal,
|
|
@@ -8329,12 +8489,14 @@ init_core();
|
|
|
8329
8489
|
createOrchestrationContext,
|
|
8330
8490
|
diversityRerank,
|
|
8331
8491
|
docIsDeleted,
|
|
8492
|
+
getActivePipeline,
|
|
8332
8493
|
getCardHistoryID,
|
|
8333
8494
|
getCardOrigin,
|
|
8334
8495
|
getDefaultLearnableWeight,
|
|
8335
8496
|
getRegisteredNavigator,
|
|
8336
8497
|
getRegisteredNavigatorNames,
|
|
8337
8498
|
getRegisteredNavigatorRole,
|
|
8499
|
+
getSrsBacklogDebug,
|
|
8338
8500
|
getStudySource,
|
|
8339
8501
|
hasRegisteredNavigator,
|
|
8340
8502
|
importParsedCards,
|