@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.mjs
CHANGED
|
@@ -791,6 +791,7 @@ __export(PipelineDebugger_exports, {
|
|
|
791
791
|
buildRunReport: () => buildRunReport,
|
|
792
792
|
captureRun: () => captureRun,
|
|
793
793
|
clearRunHistory: () => clearRunHistory,
|
|
794
|
+
getActivePipeline: () => getActivePipeline,
|
|
794
795
|
mountPipelineDebugger: () => mountPipelineDebugger,
|
|
795
796
|
pipelineDebugAPI: () => pipelineDebugAPI,
|
|
796
797
|
registerPipelineForDebug: () => registerPipelineForDebug
|
|
@@ -798,6 +799,9 @@ __export(PipelineDebugger_exports, {
|
|
|
798
799
|
function registerPipelineForDebug(pipeline) {
|
|
799
800
|
_activePipeline = pipeline;
|
|
800
801
|
}
|
|
802
|
+
function getActivePipeline() {
|
|
803
|
+
return _activePipeline;
|
|
804
|
+
}
|
|
801
805
|
function clearRunHistory() {
|
|
802
806
|
runHistory.length = 0;
|
|
803
807
|
}
|
|
@@ -1613,6 +1617,30 @@ Example:
|
|
|
1613
1617
|
}
|
|
1614
1618
|
});
|
|
1615
1619
|
|
|
1620
|
+
// src/core/navigators/SrsDebugger.ts
|
|
1621
|
+
var SrsDebugger_exports = {};
|
|
1622
|
+
__export(SrsDebugger_exports, {
|
|
1623
|
+
captureSrsBacklog: () => captureSrsBacklog,
|
|
1624
|
+
clearSrsBacklogDebug: () => clearSrsBacklogDebug,
|
|
1625
|
+
getSrsBacklogDebug: () => getSrsBacklogDebug
|
|
1626
|
+
});
|
|
1627
|
+
function captureSrsBacklog(snapshot) {
|
|
1628
|
+
snapshots.set(snapshot.courseId, snapshot);
|
|
1629
|
+
}
|
|
1630
|
+
function getSrsBacklogDebug() {
|
|
1631
|
+
return [...snapshots.values()].sort((a, b) => b.timestamp - a.timestamp);
|
|
1632
|
+
}
|
|
1633
|
+
function clearSrsBacklogDebug() {
|
|
1634
|
+
snapshots.clear();
|
|
1635
|
+
}
|
|
1636
|
+
var snapshots;
|
|
1637
|
+
var init_SrsDebugger = __esm({
|
|
1638
|
+
"src/core/navigators/SrsDebugger.ts"() {
|
|
1639
|
+
"use strict";
|
|
1640
|
+
snapshots = /* @__PURE__ */ new Map();
|
|
1641
|
+
}
|
|
1642
|
+
});
|
|
1643
|
+
|
|
1616
1644
|
// src/core/navigators/generators/CompositeGenerator.ts
|
|
1617
1645
|
var CompositeGenerator_exports = {};
|
|
1618
1646
|
__export(CompositeGenerator_exports, {
|
|
@@ -1980,7 +2008,7 @@ function shuffleInPlace(arr) {
|
|
|
1980
2008
|
function pickTopByScore(cards, limit) {
|
|
1981
2009
|
return [...cards].sort((a, b) => b.score - a.score || a.cardId.localeCompare(b.cardId)).slice(0, limit);
|
|
1982
2010
|
}
|
|
1983
|
-
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;
|
|
2011
|
+
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;
|
|
1984
2012
|
var init_prescribed = __esm({
|
|
1985
2013
|
"src/core/navigators/generators/prescribed.ts"() {
|
|
1986
2014
|
"use strict";
|
|
@@ -1997,6 +2025,9 @@ var init_prescribed = __esm({
|
|
|
1997
2025
|
BASE_SUPPORT_SCORE = 0.8;
|
|
1998
2026
|
DISCOVERED_SUPPORT_SCORE = 12;
|
|
1999
2027
|
BASE_PRACTICE_SCORE = 1;
|
|
2028
|
+
PRACTICE_BASE_MULT = 2;
|
|
2029
|
+
MAX_PRACTICE_MULTIPLIER = 4;
|
|
2030
|
+
PRACTICE_STALENESS_BUMP_PER_DAY = 0.5;
|
|
2000
2031
|
MAX_TARGET_MULTIPLIER = 8;
|
|
2001
2032
|
MAX_SUPPORT_MULTIPLIER = 4;
|
|
2002
2033
|
PRESCRIBED_DEBUG_VERSION = "testversion-prescribed-v3";
|
|
@@ -2056,6 +2087,8 @@ var init_prescribed = __esm({
|
|
|
2056
2087
|
const emitted = [];
|
|
2057
2088
|
const emittedIds = /* @__PURE__ */ new Set();
|
|
2058
2089
|
const groupRuntimes = [];
|
|
2090
|
+
const priorPracticeDebt = progress.practiceDebt ?? {};
|
|
2091
|
+
const nextPracticeDebt = {};
|
|
2059
2092
|
for (const group of this.config.groups) {
|
|
2060
2093
|
const runtime = this.buildGroupRuntimeState({
|
|
2061
2094
|
group,
|
|
@@ -2113,10 +2146,13 @@ var init_prescribed = __esm({
|
|
|
2113
2146
|
userTagElo,
|
|
2114
2147
|
userGlobalElo,
|
|
2115
2148
|
activeIds,
|
|
2116
|
-
seenIds
|
|
2149
|
+
seenIds,
|
|
2150
|
+
priorPracticeDebt,
|
|
2151
|
+
nextPracticeDebt
|
|
2117
2152
|
});
|
|
2118
2153
|
emitted.push(...directCards, ...supportCards, ...discoveredSupportCards, ...practiceCards);
|
|
2119
2154
|
}
|
|
2155
|
+
nextState.practiceDebt = nextPracticeDebt;
|
|
2120
2156
|
const hintSummary = this.buildSupportHintSummary(groupRuntimes);
|
|
2121
2157
|
const hints = Object.keys(hintSummary.boostTags).length > 0 ? {
|
|
2122
2158
|
boostTags: hintSummary.boostTags,
|
|
@@ -2450,9 +2486,16 @@ var init_prescribed = __esm({
|
|
|
2450
2486
|
* `practiceMinCount`), this resolves cards carrying that tag and emits them
|
|
2451
2487
|
* into the candidate pool. It exists because global-ELO retrieval
|
|
2452
2488
|
* systematically fails to fetch the (low-ELO) drill cards for a
|
|
2453
|
-
* freshly-introduced skill — putting them in the pool here
|
|
2454
|
-
*
|
|
2455
|
-
*
|
|
2489
|
+
* freshly-introduced skill — putting them in the pool here guarantees presence.
|
|
2490
|
+
*
|
|
2491
|
+
* Emphasis is a **practice-debt pressure** (parallel to SRS backlog pressure):
|
|
2492
|
+
* cards score `base × multiplier`, where the multiplier starts at
|
|
2493
|
+
* PRACTICE_BASE_MULT (so a few reps land promptly post-intro, competing with
|
|
2494
|
+
* pressured reviews) and escalates by how long the debt has stayed open
|
|
2495
|
+
* (per-tag, time-based via `priorPracticeDebt`/`nextPracticeDebt`), clamped at
|
|
2496
|
+
* MAX_PRACTICE_MULTIPLIER. The debt is durable and self-discharges the instant
|
|
2497
|
+
* the skill reaches `practiceMinCount` — so this no longer relies on the
|
|
2498
|
+
* session-scoped intro boost to actually surface.
|
|
2456
2499
|
*
|
|
2457
2500
|
* Fully data-driven: the unlock relation comes from the hierarchy config and
|
|
2458
2501
|
* practice-status from per-tag ELO. No card-id or tag-namespace hard-coding.
|
|
@@ -2467,7 +2510,9 @@ var init_prescribed = __esm({
|
|
|
2467
2510
|
userTagElo,
|
|
2468
2511
|
userGlobalElo,
|
|
2469
2512
|
activeIds,
|
|
2470
|
-
seenIds
|
|
2513
|
+
seenIds,
|
|
2514
|
+
priorPracticeDebt,
|
|
2515
|
+
nextPracticeDebt
|
|
2471
2516
|
} = args;
|
|
2472
2517
|
const patterns = group.practiceTagPatterns ?? [];
|
|
2473
2518
|
if (patterns.length === 0) return [];
|
|
@@ -2477,6 +2522,20 @@ var init_prescribed = __esm({
|
|
|
2477
2522
|
(tag) => patterns.some((p) => matchesTagPattern(tag, p)) && this.isUnlockedGatedSkill(tag, hierarchyConfigs, userTagElo, userGlobalElo) && (userTagElo[tag]?.count ?? 0) < practiceMinCount
|
|
2478
2523
|
);
|
|
2479
2524
|
if (practiceTags.length === 0) return [];
|
|
2525
|
+
const now = Date.now();
|
|
2526
|
+
const DAY_MS = 24 * 60 * 60 * 1e3;
|
|
2527
|
+
const tagMultiplier = /* @__PURE__ */ new Map();
|
|
2528
|
+
for (const tag of practiceTags) {
|
|
2529
|
+
const firstOwedAt = priorPracticeDebt[tag] ?? isoNow();
|
|
2530
|
+
nextPracticeDebt[tag] = firstOwedAt;
|
|
2531
|
+
const staleDays = Math.max(0, (now - new Date(firstOwedAt).getTime()) / DAY_MS);
|
|
2532
|
+
const mult = clamp(
|
|
2533
|
+
PRACTICE_BASE_MULT + staleDays * PRACTICE_STALENESS_BUMP_PER_DAY,
|
|
2534
|
+
PRACTICE_BASE_MULT,
|
|
2535
|
+
MAX_PRACTICE_MULTIPLIER
|
|
2536
|
+
);
|
|
2537
|
+
tagMultiplier.set(tag, mult);
|
|
2538
|
+
}
|
|
2480
2539
|
const practiceCardIds = this.findDiscoveredSupportCards({
|
|
2481
2540
|
supportTags: practiceTags,
|
|
2482
2541
|
cardsByTag,
|
|
@@ -2492,18 +2551,25 @@ var init_prescribed = __esm({
|
|
|
2492
2551
|
const cards = [];
|
|
2493
2552
|
for (const cardId of practiceCardIds) {
|
|
2494
2553
|
emittedIds.add(cardId);
|
|
2554
|
+
let mult = PRACTICE_BASE_MULT;
|
|
2555
|
+
for (const tag of practiceTags) {
|
|
2556
|
+
if (cardsByTag.get(tag)?.includes(cardId) ?? false) {
|
|
2557
|
+
mult = Math.max(mult, tagMultiplier.get(tag) ?? PRACTICE_BASE_MULT);
|
|
2558
|
+
}
|
|
2559
|
+
}
|
|
2560
|
+
const score = BASE_PRACTICE_SCORE * mult;
|
|
2495
2561
|
cards.push({
|
|
2496
2562
|
cardId,
|
|
2497
2563
|
courseId,
|
|
2498
|
-
score
|
|
2564
|
+
score,
|
|
2499
2565
|
provenance: [
|
|
2500
2566
|
{
|
|
2501
2567
|
strategy: "prescribed",
|
|
2502
2568
|
strategyName: this.strategyName || this.name,
|
|
2503
2569
|
strategyId: this.strategyId || "NAVIGATION_STRATEGY-prescribed",
|
|
2504
2570
|
action: "generated",
|
|
2505
|
-
score
|
|
2506
|
-
reason: `mode=practice;group=${group.id};underPracticedSkills=${practiceTags.length};practiceTags=${practiceTags.slice(0, 8).join("|")}${practiceTags.length > 8 ? "|\u2026" : ""};testversion=${PRESCRIBED_DEBUG_VERSION}`
|
|
2571
|
+
score,
|
|
2572
|
+
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}`
|
|
2507
2573
|
}
|
|
2508
2574
|
]
|
|
2509
2575
|
});
|
|
@@ -2677,14 +2743,15 @@ __export(srs_exports, {
|
|
|
2677
2743
|
default: () => SRSNavigator
|
|
2678
2744
|
});
|
|
2679
2745
|
import moment3 from "moment";
|
|
2680
|
-
var DEFAULT_HEALTHY_BACKLOG,
|
|
2746
|
+
var DEFAULT_HEALTHY_BACKLOG, MAX_BACKLOG_MULTIPLIER, SRSNavigator;
|
|
2681
2747
|
var init_srs = __esm({
|
|
2682
2748
|
"src/core/navigators/generators/srs.ts"() {
|
|
2683
2749
|
"use strict";
|
|
2684
2750
|
init_navigators();
|
|
2751
|
+
init_SrsDebugger();
|
|
2685
2752
|
init_logger();
|
|
2686
2753
|
DEFAULT_HEALTHY_BACKLOG = 20;
|
|
2687
|
-
|
|
2754
|
+
MAX_BACKLOG_MULTIPLIER = 2;
|
|
2688
2755
|
SRSNavigator = class extends ContentNavigator {
|
|
2689
2756
|
/** Human-readable name for CardGenerator interface */
|
|
2690
2757
|
name;
|
|
@@ -2751,9 +2818,18 @@ var init_srs = __esm({
|
|
|
2751
2818
|
}
|
|
2752
2819
|
}
|
|
2753
2820
|
}
|
|
2754
|
-
const
|
|
2821
|
+
const backlogMultiplier = this.computeBacklogMultiplier(dueReviews.length);
|
|
2822
|
+
const notDue = reviews.filter((r) => !now.isAfter(moment3.utc(r.reviewTime)));
|
|
2823
|
+
let nextDueIn = null;
|
|
2824
|
+
if (notDue.length > 0) {
|
|
2825
|
+
const next = notDue.reduce(
|
|
2826
|
+
(a, b) => moment3.utc(a.reviewTime).isBefore(moment3.utc(b.reviewTime)) ? a : b
|
|
2827
|
+
);
|
|
2828
|
+
const until = moment3.duration(moment3.utc(next.reviewTime).diff(now));
|
|
2829
|
+
nextDueIn = until.asHours() < 1 ? `${Math.round(until.asMinutes())}m` : until.asHours() < 24 ? `${Math.round(until.asHours())}h` : `${Math.round(until.asDays())}d`;
|
|
2830
|
+
}
|
|
2755
2831
|
if (dueReviews.length > 0) {
|
|
2756
|
-
const pressureNote =
|
|
2832
|
+
const pressureNote = backlogMultiplier > 1 ? ` [backlog pressure: \xD7${backlogMultiplier.toFixed(2)}]` : ` [healthy backlog]`;
|
|
2757
2833
|
logger.info(
|
|
2758
2834
|
`[SRS] Course ${courseId}: ${dueReviews.length} reviews due now (of ${reviews.length} scheduled)${pressureNote}`
|
|
2759
2835
|
);
|
|
@@ -2772,7 +2848,7 @@ var init_srs = __esm({
|
|
|
2772
2848
|
logger.info(`[SRS] Course ${courseId}: No reviews scheduled`);
|
|
2773
2849
|
}
|
|
2774
2850
|
const scored = dueReviews.map((review) => {
|
|
2775
|
-
const { score, reason } = this.computeUrgencyScore(review, now,
|
|
2851
|
+
const { score, reason } = this.computeUrgencyScore(review, now, backlogMultiplier);
|
|
2776
2852
|
return {
|
|
2777
2853
|
cardId: review.cardId,
|
|
2778
2854
|
courseId: review.courseId,
|
|
@@ -2790,30 +2866,42 @@ var init_srs = __esm({
|
|
|
2790
2866
|
]
|
|
2791
2867
|
};
|
|
2792
2868
|
});
|
|
2793
|
-
|
|
2869
|
+
const sorted = scored.sort((a, b) => b.score - a.score);
|
|
2870
|
+
captureSrsBacklog({
|
|
2871
|
+
courseId,
|
|
2872
|
+
scheduledTotal: reviews.length,
|
|
2873
|
+
dueNow: dueReviews.length,
|
|
2874
|
+
healthyBacklog: this.healthyBacklog,
|
|
2875
|
+
backlogMultiplier,
|
|
2876
|
+
maxBacklogMultiplier: MAX_BACKLOG_MULTIPLIER,
|
|
2877
|
+
topReviewScore: sorted.length > 0 ? sorted[0].score : null,
|
|
2878
|
+
nextDueIn,
|
|
2879
|
+
timestamp: Date.now()
|
|
2880
|
+
});
|
|
2881
|
+
return { cards: sorted.slice(0, limit) };
|
|
2794
2882
|
}
|
|
2795
2883
|
/**
|
|
2796
|
-
* Compute backlog pressure based on number of due reviews.
|
|
2884
|
+
* Compute the multiplicative backlog pressure based on number of due reviews.
|
|
2797
2885
|
*
|
|
2798
|
-
*
|
|
2799
|
-
* and
|
|
2886
|
+
* ×1.0 at or below the healthy threshold (no boost), increasing linearly above
|
|
2887
|
+
* it and maxing out at MAX_BACKLOG_MULTIPLIER at 3× the healthy backlog.
|
|
2800
2888
|
*
|
|
2801
|
-
* Examples (with default healthyBacklog=20):
|
|
2802
|
-
* - 10 due reviews →
|
|
2803
|
-
* - 20 due reviews →
|
|
2804
|
-
* - 40 due reviews →
|
|
2805
|
-
* - 60 due reviews →
|
|
2889
|
+
* Examples (with default healthyBacklog=20, MAX_BACKLOG_MULTIPLIER=2.0):
|
|
2890
|
+
* - 10 due reviews → ×1.00 (healthy)
|
|
2891
|
+
* - 20 due reviews → ×1.00 (at threshold)
|
|
2892
|
+
* - 40 due reviews → ×1.50 (2x threshold)
|
|
2893
|
+
* - 60 due reviews → ×2.00 (3x threshold, maxed)
|
|
2806
2894
|
*
|
|
2807
2895
|
* @param dueCount - Number of reviews currently due
|
|
2808
|
-
* @returns
|
|
2896
|
+
* @returns Multiplier applied to review urgency (1.0 to MAX_BACKLOG_MULTIPLIER)
|
|
2809
2897
|
*/
|
|
2810
|
-
|
|
2898
|
+
computeBacklogMultiplier(dueCount) {
|
|
2811
2899
|
if (dueCount <= this.healthyBacklog) {
|
|
2812
|
-
return
|
|
2900
|
+
return 1;
|
|
2813
2901
|
}
|
|
2814
2902
|
const excess = dueCount - this.healthyBacklog;
|
|
2815
|
-
const
|
|
2816
|
-
return Math.min(
|
|
2903
|
+
const multiplier = 1 + excess / this.healthyBacklog * ((MAX_BACKLOG_MULTIPLIER - 1) / 2);
|
|
2904
|
+
return Math.min(MAX_BACKLOG_MULTIPLIER, multiplier);
|
|
2817
2905
|
}
|
|
2818
2906
|
/**
|
|
2819
2907
|
* Compute urgency score for a review card.
|
|
@@ -2828,19 +2916,20 @@ var init_srs = __esm({
|
|
|
2828
2916
|
* - 30 days (720h) → ~0.56
|
|
2829
2917
|
* - 180 days → ~0.30
|
|
2830
2918
|
*
|
|
2831
|
-
* 3. Backlog pressure = global
|
|
2832
|
-
*
|
|
2833
|
-
* - At 2x healthy: +0.25
|
|
2834
|
-
* - At 3x+ healthy: +0.50 (max)
|
|
2919
|
+
* 3. Backlog pressure = global *multiplier* when review backlog exceeds the
|
|
2920
|
+
* healthy threshold (×1.0 healthy → up to MAX_BACKLOG_MULTIPLIER at 3×).
|
|
2835
2921
|
*
|
|
2836
|
-
* Combined: base 0.5 +
|
|
2837
|
-
*
|
|
2922
|
+
* Combined: (base 0.5 + urgency factors * 0.45) × backlog multiplier.
|
|
2923
|
+
* Per-card range before pressure: ~0.57–0.95. NOT clamped to 1.0 — under a
|
|
2924
|
+
* heavy backlog reviews scale onto the open scale to compete with (and exceed)
|
|
2925
|
+
* new cards; what keeps them from running away is the bounded multiplier, not
|
|
2926
|
+
* a hard ceiling.
|
|
2838
2927
|
*
|
|
2839
2928
|
* @param review - The scheduled card to score
|
|
2840
2929
|
* @param now - Current time
|
|
2841
|
-
* @param
|
|
2930
|
+
* @param backlogMultiplier - Pre-computed backlog multiplier (1.0 to MAX_BACKLOG_MULTIPLIER)
|
|
2842
2931
|
*/
|
|
2843
|
-
computeUrgencyScore(review, now,
|
|
2932
|
+
computeUrgencyScore(review, now, backlogMultiplier) {
|
|
2844
2933
|
const scheduledAt = moment3.utc(review.scheduledAt);
|
|
2845
2934
|
const due = moment3.utc(review.reviewTime);
|
|
2846
2935
|
const intervalHours = Math.max(1, due.diff(scheduledAt, "hours"));
|
|
@@ -2850,15 +2939,15 @@ var init_srs = __esm({
|
|
|
2850
2939
|
const overdueContribution = Math.min(1, Math.max(0, relativeOverdue));
|
|
2851
2940
|
const urgency = overdueContribution * 0.5 + recencyFactor * 0.5;
|
|
2852
2941
|
const baseScore = 0.5 + urgency * 0.45;
|
|
2853
|
-
const score =
|
|
2942
|
+
const score = baseScore * backlogMultiplier;
|
|
2854
2943
|
const reasonParts = [
|
|
2855
2944
|
`${Math.round(hoursOverdue)}h overdue`,
|
|
2856
2945
|
`interval: ${Math.round(intervalHours)}h`,
|
|
2857
2946
|
`relative: ${relativeOverdue.toFixed(2)}`,
|
|
2858
2947
|
`recency: ${recencyFactor.toFixed(2)}`
|
|
2859
2948
|
];
|
|
2860
|
-
if (
|
|
2861
|
-
reasonParts.push(`backlog:
|
|
2949
|
+
if (backlogMultiplier > 1) {
|
|
2950
|
+
reasonParts.push(`backlog: \xD7${backlogMultiplier.toFixed(2)}`);
|
|
2862
2951
|
}
|
|
2863
2952
|
reasonParts.push("review");
|
|
2864
2953
|
const reason = reasonParts.join(", ");
|
|
@@ -4830,6 +4919,68 @@ var init_Pipeline = __esm({
|
|
|
4830
4919
|
// ---------------------------------------------------------------------------
|
|
4831
4920
|
// Card-space diagnostic
|
|
4832
4921
|
// ---------------------------------------------------------------------------
|
|
4922
|
+
/**
|
|
4923
|
+
* Commit-free forecast: score the user's full card space through the filter
|
|
4924
|
+
* chain and return the cards that are currently *reachable* (score >=
|
|
4925
|
+
* threshold), optionally nudged by caller-supplied hints and/or restricted
|
|
4926
|
+
* to cards the user hasn't seen yet.
|
|
4927
|
+
*
|
|
4928
|
+
* This is a GENERIC primitive — it returns scored, tag-hydrated cards and
|
|
4929
|
+
* stops there. It has no knowledge of any particular tag convention; callers
|
|
4930
|
+
* decide what the surviving cards mean (e.g. filter to their own "intro"
|
|
4931
|
+
* tag family). Nothing is written and no session is started.
|
|
4932
|
+
*
|
|
4933
|
+
* The optional `hints` are the "out-of-band kick": they run through the same
|
|
4934
|
+
* {@link applyHints} path a live replan uses, so the two semantics carry over —
|
|
4935
|
+
* - `boostTags`/`boostCards` reweight *within* gating (a gated score-0 card
|
|
4936
|
+
* stays out), and
|
|
4937
|
+
* - `requireTags`/`requireCards` inject from the full pre-filter pool,
|
|
4938
|
+
* *bypassing* gating (use when you want a card regardless of reachability).
|
|
4939
|
+
* Note `unseenOnly` is applied LAST, so it can drop a `require`d card that the
|
|
4940
|
+
* user has already seen — pass `unseenOnly: false` if that matters.
|
|
4941
|
+
*
|
|
4942
|
+
* Cost note: like {@link diagnoseCardSpace}, this scans every card through the
|
|
4943
|
+
* filters, so it's heavier than a normal replan. Intended for one-shot
|
|
4944
|
+
* out-of-band use (e.g. a session-end "what's next" snapshot), not the hot path.
|
|
4945
|
+
*
|
|
4946
|
+
* @param opts.hints Optional ephemeral hints to apply after the filter chain.
|
|
4947
|
+
* @param opts.unseenOnly Only return cards the user hasn't encountered (default true).
|
|
4948
|
+
* @param opts.threshold Min score to count as reachable (default 0.10).
|
|
4949
|
+
* @param opts.limit Optional cap on results (already sorted desc).
|
|
4950
|
+
*/
|
|
4951
|
+
async forecast(opts) {
|
|
4952
|
+
const threshold = opts?.threshold ?? 0.1;
|
|
4953
|
+
const unseenOnly = opts?.unseenOnly ?? true;
|
|
4954
|
+
const courseId = this.course.getCourseID();
|
|
4955
|
+
const allCardIds = await this.course.getAllCardIds();
|
|
4956
|
+
let cards = allCardIds.map((cardId) => ({
|
|
4957
|
+
cardId,
|
|
4958
|
+
courseId,
|
|
4959
|
+
score: 1,
|
|
4960
|
+
provenance: []
|
|
4961
|
+
}));
|
|
4962
|
+
cards = await this.hydrateTags(cards);
|
|
4963
|
+
const fullPool = cards.slice();
|
|
4964
|
+
const context = await this.buildContext();
|
|
4965
|
+
for (const filter of this.filters) {
|
|
4966
|
+
cards = await filter.transform(cards, context);
|
|
4967
|
+
}
|
|
4968
|
+
if (opts?.hints) {
|
|
4969
|
+
cards = this.applyHints(cards, opts.hints, fullPool);
|
|
4970
|
+
}
|
|
4971
|
+
cards = cards.filter((c) => c.score >= threshold);
|
|
4972
|
+
if (unseenOnly) {
|
|
4973
|
+
let encountered;
|
|
4974
|
+
try {
|
|
4975
|
+
encountered = new Set(await this.user.getSeenCards(courseId));
|
|
4976
|
+
} catch {
|
|
4977
|
+
encountered = /* @__PURE__ */ new Set();
|
|
4978
|
+
}
|
|
4979
|
+
cards = cards.filter((c) => !encountered.has(c.cardId));
|
|
4980
|
+
}
|
|
4981
|
+
cards.sort((a, b) => b.score - a.score);
|
|
4982
|
+
return opts?.limit ? cards.slice(0, opts.limit) : cards;
|
|
4983
|
+
}
|
|
4833
4984
|
/**
|
|
4834
4985
|
* Scan every card in the course through the filter chain and report
|
|
4835
4986
|
* how many are "well indicated" (score >= threshold) for the current user.
|
|
@@ -5094,6 +5245,7 @@ var init_3 = __esm({
|
|
|
5094
5245
|
"./Pipeline.ts": () => Promise.resolve().then(() => (init_Pipeline(), Pipeline_exports)),
|
|
5095
5246
|
"./PipelineAssembler.ts": () => Promise.resolve().then(() => (init_PipelineAssembler(), PipelineAssembler_exports)),
|
|
5096
5247
|
"./PipelineDebugger.ts": () => Promise.resolve().then(() => (init_PipelineDebugger(), PipelineDebugger_exports)),
|
|
5248
|
+
"./SrsDebugger.ts": () => Promise.resolve().then(() => (init_SrsDebugger(), SrsDebugger_exports)),
|
|
5097
5249
|
"./defaults.ts": () => Promise.resolve().then(() => (init_defaults(), defaults_exports)),
|
|
5098
5250
|
"./diversityRerank.ts": () => Promise.resolve().then(() => (init_diversityRerank(), diversityRerank_exports)),
|
|
5099
5251
|
"./filters/WeightedFilter.ts": () => Promise.resolve().then(() => (init_WeightedFilter(), WeightedFilter_exports)),
|
|
@@ -5126,11 +5278,14 @@ __export(navigators_exports, {
|
|
|
5126
5278
|
NavigatorRole: () => NavigatorRole,
|
|
5127
5279
|
NavigatorRoles: () => NavigatorRoles,
|
|
5128
5280
|
Navigators: () => Navigators,
|
|
5281
|
+
clearSrsBacklogDebug: () => clearSrsBacklogDebug,
|
|
5129
5282
|
diversityRerank: () => diversityRerank,
|
|
5283
|
+
getActivePipeline: () => getActivePipeline,
|
|
5130
5284
|
getCardOrigin: () => getCardOrigin,
|
|
5131
5285
|
getRegisteredNavigator: () => getRegisteredNavigator,
|
|
5132
5286
|
getRegisteredNavigatorNames: () => getRegisteredNavigatorNames,
|
|
5133
5287
|
getRegisteredNavigatorRole: () => getRegisteredNavigatorRole,
|
|
5288
|
+
getSrsBacklogDebug: () => getSrsBacklogDebug,
|
|
5134
5289
|
hasRegisteredNavigator: () => hasRegisteredNavigator,
|
|
5135
5290
|
initializeNavigatorRegistry: () => initializeNavigatorRegistry,
|
|
5136
5291
|
isFilter: () => isFilter,
|
|
@@ -5212,6 +5367,7 @@ var init_navigators = __esm({
|
|
|
5212
5367
|
"use strict";
|
|
5213
5368
|
init_diversityRerank();
|
|
5214
5369
|
init_PipelineDebugger();
|
|
5370
|
+
init_SrsDebugger();
|
|
5215
5371
|
init_logger();
|
|
5216
5372
|
init_();
|
|
5217
5373
|
init_2();
|
|
@@ -8246,6 +8402,7 @@ export {
|
|
|
8246
8402
|
aggregateOutcomesForGradient,
|
|
8247
8403
|
areQuestionRecords,
|
|
8248
8404
|
buildStrategyStateId,
|
|
8405
|
+
clearSrsBacklogDebug,
|
|
8249
8406
|
computeDeviation,
|
|
8250
8407
|
computeEffectiveWeight,
|
|
8251
8408
|
computeOutcomeSignal,
|
|
@@ -8254,12 +8411,14 @@ export {
|
|
|
8254
8411
|
createOrchestrationContext,
|
|
8255
8412
|
diversityRerank,
|
|
8256
8413
|
docIsDeleted,
|
|
8414
|
+
getActivePipeline,
|
|
8257
8415
|
getCardHistoryID,
|
|
8258
8416
|
getCardOrigin,
|
|
8259
8417
|
getDefaultLearnableWeight,
|
|
8260
8418
|
getRegisteredNavigator,
|
|
8261
8419
|
getRegisteredNavigatorNames,
|
|
8262
8420
|
getRegisteredNavigatorRole,
|
|
8421
|
+
getSrsBacklogDebug,
|
|
8263
8422
|
getStudySource,
|
|
8264
8423
|
hasRegisteredNavigator,
|
|
8265
8424
|
importParsedCards,
|