@vue-skuilder/db 0.2.8 → 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 +29 -4
- package/dist/core/index.d.ts +29 -4
- package/dist/core/index.js +132 -39
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.mjs +130 -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 +128 -39
- package/dist/impl/couch/index.js.map +1 -1
- package/dist/impl/couch/index.mjs +128 -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 +128 -39
- package/dist/impl/static/index.js.map +1 -1
- package/dist/impl/static/index.mjs +128 -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 +371 -251
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +369 -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/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 +5 -0
- package/src/study/SessionController.ts +260 -249
- package/src/study/SessionDebugger.ts +15 -25
- package/src/study/SessionOverlay.ts +108 -13
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { U as UserDBInterface, a as UserDBReader, C as CourseDBInterface, b as CoursesDBInterface, c as ClassroomDBInterface, A as AdminDBInterface } from './contentSource-
|
|
1
|
+
import { U as UserDBInterface, a as UserDBReader, C as CourseDBInterface, b as CoursesDBInterface, c as ClassroomDBInterface, A as AdminDBInterface } from './contentSource-C-0t0y0V.js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Main factory interface for data access
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { U as UserDBInterface, a as UserDBReader, C as CourseDBInterface, b as CoursesDBInterface, c as ClassroomDBInterface, A as AdminDBInterface } from './contentSource-
|
|
1
|
+
import { U as UserDBInterface, a as UserDBReader, C as CourseDBInterface, b as CoursesDBInterface, c as ClassroomDBInterface, A as AdminDBInterface } from './contentSource-jSkcOt2s.cjs';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Main factory interface for data access
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { T as TagStub, a as Tag, S as SkuilderCourseData, Q as QualifiedCardID } from '../../types-legacy-4tlwHnXo.cjs';
|
|
2
2
|
import { Moment } from 'moment';
|
|
3
|
-
import { A as AdminDBInterface, g as AssignedContent, h as StudyContentSource, i as StudentClassroomDBInterface, U as UserDBInterface, G as GeneratorResult, T as TeacherClassroomDBInterface, b as CoursesDBInterface, C as CourseDBInterface, d as CourseInfo, D as DataLayerResult, e as ContentNavigationStrategyData, f as ContentNavigator, R as ReplanHints, S as StudySessionItem, j as ScheduledCard } from '../../contentSource-
|
|
4
|
-
export { q as ContentSourceID, k as StudySessionFailedItem, l as StudySessionFailedNewItem, m as StudySessionFailedReviewItem, n as StudySessionNewItem, o as StudySessionReviewItem, r as getStudySource, p as isReview } from '../../contentSource-
|
|
3
|
+
import { A as AdminDBInterface, g as AssignedContent, h as StudyContentSource, i as StudentClassroomDBInterface, U as UserDBInterface, G as GeneratorResult, T as TeacherClassroomDBInterface, b as CoursesDBInterface, C as CourseDBInterface, d as CourseInfo, D as DataLayerResult, e as ContentNavigationStrategyData, f as ContentNavigator, R as ReplanHints, S as StudySessionItem, j as ScheduledCard } from '../../contentSource-jSkcOt2s.cjs';
|
|
4
|
+
export { q as ContentSourceID, k as StudySessionFailedItem, l as StudySessionFailedNewItem, m as StudySessionFailedReviewItem, n as StudySessionNewItem, o as StudySessionReviewItem, r as getStudySource, p as isReview } from '../../contentSource-jSkcOt2s.cjs';
|
|
5
5
|
import * as _vue_skuilder_common from '@vue-skuilder/common';
|
|
6
6
|
import { ClassroomConfig, DataShape, CourseElo, CourseConfig as CourseConfig$1 } from '@vue-skuilder/common';
|
|
7
7
|
import { S as SyncStrategy, A as AccountCreationResult, a as AuthenticationResult } from '../../SyncStrategy-CyATpyLQ.cjs';
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { T as TagStub, a as Tag, S as SkuilderCourseData, Q as QualifiedCardID } from '../../types-legacy-4tlwHnXo.js';
|
|
2
2
|
import { Moment } from 'moment';
|
|
3
|
-
import { A as AdminDBInterface, g as AssignedContent, h as StudyContentSource, i as StudentClassroomDBInterface, U as UserDBInterface, G as GeneratorResult, T as TeacherClassroomDBInterface, b as CoursesDBInterface, C as CourseDBInterface, d as CourseInfo, D as DataLayerResult, e as ContentNavigationStrategyData, f as ContentNavigator, R as ReplanHints, S as StudySessionItem, j as ScheduledCard } from '../../contentSource-
|
|
4
|
-
export { q as ContentSourceID, k as StudySessionFailedItem, l as StudySessionFailedNewItem, m as StudySessionFailedReviewItem, n as StudySessionNewItem, o as StudySessionReviewItem, r as getStudySource, p as isReview } from '../../contentSource-
|
|
3
|
+
import { A as AdminDBInterface, g as AssignedContent, h as StudyContentSource, i as StudentClassroomDBInterface, U as UserDBInterface, G as GeneratorResult, T as TeacherClassroomDBInterface, b as CoursesDBInterface, C as CourseDBInterface, d as CourseInfo, D as DataLayerResult, e as ContentNavigationStrategyData, f as ContentNavigator, R as ReplanHints, S as StudySessionItem, j as ScheduledCard } from '../../contentSource-C-0t0y0V.js';
|
|
4
|
+
export { q as ContentSourceID, k as StudySessionFailedItem, l as StudySessionFailedNewItem, m as StudySessionFailedReviewItem, n as StudySessionNewItem, o as StudySessionReviewItem, r as getStudySource, p as isReview } from '../../contentSource-C-0t0y0V.js';
|
|
5
5
|
import * as _vue_skuilder_common from '@vue-skuilder/common';
|
|
6
6
|
import { ClassroomConfig, DataShape, CourseElo, CourseConfig as CourseConfig$1 } from '@vue-skuilder/common';
|
|
7
7
|
import { S as SyncStrategy, A as AccountCreationResult, a as AuthenticationResult } from '../../SyncStrategy-CyATpyLQ.js';
|
package/dist/impl/couch/index.js
CHANGED
|
@@ -1564,6 +1564,30 @@ Example:
|
|
|
1564
1564
|
}
|
|
1565
1565
|
});
|
|
1566
1566
|
|
|
1567
|
+
// src/core/navigators/SrsDebugger.ts
|
|
1568
|
+
var SrsDebugger_exports = {};
|
|
1569
|
+
__export(SrsDebugger_exports, {
|
|
1570
|
+
captureSrsBacklog: () => captureSrsBacklog,
|
|
1571
|
+
clearSrsBacklogDebug: () => clearSrsBacklogDebug,
|
|
1572
|
+
getSrsBacklogDebug: () => getSrsBacklogDebug
|
|
1573
|
+
});
|
|
1574
|
+
function captureSrsBacklog(snapshot) {
|
|
1575
|
+
snapshots.set(snapshot.courseId, snapshot);
|
|
1576
|
+
}
|
|
1577
|
+
function getSrsBacklogDebug() {
|
|
1578
|
+
return [...snapshots.values()].sort((a, b) => b.timestamp - a.timestamp);
|
|
1579
|
+
}
|
|
1580
|
+
function clearSrsBacklogDebug() {
|
|
1581
|
+
snapshots.clear();
|
|
1582
|
+
}
|
|
1583
|
+
var snapshots;
|
|
1584
|
+
var init_SrsDebugger = __esm({
|
|
1585
|
+
"src/core/navigators/SrsDebugger.ts"() {
|
|
1586
|
+
"use strict";
|
|
1587
|
+
snapshots = /* @__PURE__ */ new Map();
|
|
1588
|
+
}
|
|
1589
|
+
});
|
|
1590
|
+
|
|
1567
1591
|
// src/core/navigators/generators/CompositeGenerator.ts
|
|
1568
1592
|
var CompositeGenerator_exports = {};
|
|
1569
1593
|
__export(CompositeGenerator_exports, {
|
|
@@ -1931,7 +1955,7 @@ function shuffleInPlace(arr) {
|
|
|
1931
1955
|
function pickTopByScore(cards, limit) {
|
|
1932
1956
|
return [...cards].sort((a, b) => b.score - a.score || a.cardId.localeCompare(b.cardId)).slice(0, limit);
|
|
1933
1957
|
}
|
|
1934
|
-
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;
|
|
1958
|
+
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;
|
|
1935
1959
|
var init_prescribed = __esm({
|
|
1936
1960
|
"src/core/navigators/generators/prescribed.ts"() {
|
|
1937
1961
|
"use strict";
|
|
@@ -1948,6 +1972,9 @@ var init_prescribed = __esm({
|
|
|
1948
1972
|
BASE_SUPPORT_SCORE = 0.8;
|
|
1949
1973
|
DISCOVERED_SUPPORT_SCORE = 12;
|
|
1950
1974
|
BASE_PRACTICE_SCORE = 1;
|
|
1975
|
+
PRACTICE_BASE_MULT = 2;
|
|
1976
|
+
MAX_PRACTICE_MULTIPLIER = 4;
|
|
1977
|
+
PRACTICE_STALENESS_BUMP_PER_DAY = 0.5;
|
|
1951
1978
|
MAX_TARGET_MULTIPLIER = 8;
|
|
1952
1979
|
MAX_SUPPORT_MULTIPLIER = 4;
|
|
1953
1980
|
PRESCRIBED_DEBUG_VERSION = "testversion-prescribed-v3";
|
|
@@ -2007,6 +2034,8 @@ var init_prescribed = __esm({
|
|
|
2007
2034
|
const emitted = [];
|
|
2008
2035
|
const emittedIds = /* @__PURE__ */ new Set();
|
|
2009
2036
|
const groupRuntimes = [];
|
|
2037
|
+
const priorPracticeDebt = progress.practiceDebt ?? {};
|
|
2038
|
+
const nextPracticeDebt = {};
|
|
2010
2039
|
for (const group of this.config.groups) {
|
|
2011
2040
|
const runtime = this.buildGroupRuntimeState({
|
|
2012
2041
|
group,
|
|
@@ -2064,10 +2093,13 @@ var init_prescribed = __esm({
|
|
|
2064
2093
|
userTagElo,
|
|
2065
2094
|
userGlobalElo,
|
|
2066
2095
|
activeIds,
|
|
2067
|
-
seenIds
|
|
2096
|
+
seenIds,
|
|
2097
|
+
priorPracticeDebt,
|
|
2098
|
+
nextPracticeDebt
|
|
2068
2099
|
});
|
|
2069
2100
|
emitted.push(...directCards, ...supportCards, ...discoveredSupportCards, ...practiceCards);
|
|
2070
2101
|
}
|
|
2102
|
+
nextState.practiceDebt = nextPracticeDebt;
|
|
2071
2103
|
const hintSummary = this.buildSupportHintSummary(groupRuntimes);
|
|
2072
2104
|
const hints = Object.keys(hintSummary.boostTags).length > 0 ? {
|
|
2073
2105
|
boostTags: hintSummary.boostTags,
|
|
@@ -2401,9 +2433,16 @@ var init_prescribed = __esm({
|
|
|
2401
2433
|
* `practiceMinCount`), this resolves cards carrying that tag and emits them
|
|
2402
2434
|
* into the candidate pool. It exists because global-ELO retrieval
|
|
2403
2435
|
* systematically fails to fetch the (low-ELO) drill cards for a
|
|
2404
|
-
* freshly-introduced skill — putting them in the pool here
|
|
2405
|
-
*
|
|
2406
|
-
*
|
|
2436
|
+
* freshly-introduced skill — putting them in the pool here guarantees presence.
|
|
2437
|
+
*
|
|
2438
|
+
* Emphasis is a **practice-debt pressure** (parallel to SRS backlog pressure):
|
|
2439
|
+
* cards score `base × multiplier`, where the multiplier starts at
|
|
2440
|
+
* PRACTICE_BASE_MULT (so a few reps land promptly post-intro, competing with
|
|
2441
|
+
* pressured reviews) and escalates by how long the debt has stayed open
|
|
2442
|
+
* (per-tag, time-based via `priorPracticeDebt`/`nextPracticeDebt`), clamped at
|
|
2443
|
+
* MAX_PRACTICE_MULTIPLIER. The debt is durable and self-discharges the instant
|
|
2444
|
+
* the skill reaches `practiceMinCount` — so this no longer relies on the
|
|
2445
|
+
* session-scoped intro boost to actually surface.
|
|
2407
2446
|
*
|
|
2408
2447
|
* Fully data-driven: the unlock relation comes from the hierarchy config and
|
|
2409
2448
|
* practice-status from per-tag ELO. No card-id or tag-namespace hard-coding.
|
|
@@ -2418,7 +2457,9 @@ var init_prescribed = __esm({
|
|
|
2418
2457
|
userTagElo,
|
|
2419
2458
|
userGlobalElo,
|
|
2420
2459
|
activeIds,
|
|
2421
|
-
seenIds
|
|
2460
|
+
seenIds,
|
|
2461
|
+
priorPracticeDebt,
|
|
2462
|
+
nextPracticeDebt
|
|
2422
2463
|
} = args;
|
|
2423
2464
|
const patterns = group.practiceTagPatterns ?? [];
|
|
2424
2465
|
if (patterns.length === 0) return [];
|
|
@@ -2428,6 +2469,20 @@ var init_prescribed = __esm({
|
|
|
2428
2469
|
(tag) => patterns.some((p) => matchesTagPattern(tag, p)) && this.isUnlockedGatedSkill(tag, hierarchyConfigs, userTagElo, userGlobalElo) && (userTagElo[tag]?.count ?? 0) < practiceMinCount
|
|
2429
2470
|
);
|
|
2430
2471
|
if (practiceTags.length === 0) return [];
|
|
2472
|
+
const now = Date.now();
|
|
2473
|
+
const DAY_MS = 24 * 60 * 60 * 1e3;
|
|
2474
|
+
const tagMultiplier = /* @__PURE__ */ new Map();
|
|
2475
|
+
for (const tag of practiceTags) {
|
|
2476
|
+
const firstOwedAt = priorPracticeDebt[tag] ?? isoNow();
|
|
2477
|
+
nextPracticeDebt[tag] = firstOwedAt;
|
|
2478
|
+
const staleDays = Math.max(0, (now - new Date(firstOwedAt).getTime()) / DAY_MS);
|
|
2479
|
+
const mult = clamp(
|
|
2480
|
+
PRACTICE_BASE_MULT + staleDays * PRACTICE_STALENESS_BUMP_PER_DAY,
|
|
2481
|
+
PRACTICE_BASE_MULT,
|
|
2482
|
+
MAX_PRACTICE_MULTIPLIER
|
|
2483
|
+
);
|
|
2484
|
+
tagMultiplier.set(tag, mult);
|
|
2485
|
+
}
|
|
2431
2486
|
const practiceCardIds = this.findDiscoveredSupportCards({
|
|
2432
2487
|
supportTags: practiceTags,
|
|
2433
2488
|
cardsByTag,
|
|
@@ -2443,18 +2498,25 @@ var init_prescribed = __esm({
|
|
|
2443
2498
|
const cards = [];
|
|
2444
2499
|
for (const cardId of practiceCardIds) {
|
|
2445
2500
|
emittedIds.add(cardId);
|
|
2501
|
+
let mult = PRACTICE_BASE_MULT;
|
|
2502
|
+
for (const tag of practiceTags) {
|
|
2503
|
+
if (cardsByTag.get(tag)?.includes(cardId) ?? false) {
|
|
2504
|
+
mult = Math.max(mult, tagMultiplier.get(tag) ?? PRACTICE_BASE_MULT);
|
|
2505
|
+
}
|
|
2506
|
+
}
|
|
2507
|
+
const score = BASE_PRACTICE_SCORE * mult;
|
|
2446
2508
|
cards.push({
|
|
2447
2509
|
cardId,
|
|
2448
2510
|
courseId,
|
|
2449
|
-
score
|
|
2511
|
+
score,
|
|
2450
2512
|
provenance: [
|
|
2451
2513
|
{
|
|
2452
2514
|
strategy: "prescribed",
|
|
2453
2515
|
strategyName: this.strategyName || this.name,
|
|
2454
2516
|
strategyId: this.strategyId || "NAVIGATION_STRATEGY-prescribed",
|
|
2455
2517
|
action: "generated",
|
|
2456
|
-
score
|
|
2457
|
-
reason: `mode=practice;group=${group.id};underPracticedSkills=${practiceTags.length};practiceTags=${practiceTags.slice(0, 8).join("|")}${practiceTags.length > 8 ? "|\u2026" : ""};testversion=${PRESCRIBED_DEBUG_VERSION}`
|
|
2518
|
+
score,
|
|
2519
|
+
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}`
|
|
2458
2520
|
}
|
|
2459
2521
|
]
|
|
2460
2522
|
});
|
|
@@ -2627,15 +2689,16 @@ var srs_exports = {};
|
|
|
2627
2689
|
__export(srs_exports, {
|
|
2628
2690
|
default: () => SRSNavigator
|
|
2629
2691
|
});
|
|
2630
|
-
var import_moment, DEFAULT_HEALTHY_BACKLOG,
|
|
2692
|
+
var import_moment, DEFAULT_HEALTHY_BACKLOG, MAX_BACKLOG_MULTIPLIER, SRSNavigator;
|
|
2631
2693
|
var init_srs = __esm({
|
|
2632
2694
|
"src/core/navigators/generators/srs.ts"() {
|
|
2633
2695
|
"use strict";
|
|
2634
2696
|
import_moment = __toESM(require("moment"), 1);
|
|
2635
2697
|
init_navigators();
|
|
2698
|
+
init_SrsDebugger();
|
|
2636
2699
|
init_logger();
|
|
2637
2700
|
DEFAULT_HEALTHY_BACKLOG = 20;
|
|
2638
|
-
|
|
2701
|
+
MAX_BACKLOG_MULTIPLIER = 2;
|
|
2639
2702
|
SRSNavigator = class extends ContentNavigator {
|
|
2640
2703
|
/** Human-readable name for CardGenerator interface */
|
|
2641
2704
|
name;
|
|
@@ -2702,9 +2765,18 @@ var init_srs = __esm({
|
|
|
2702
2765
|
}
|
|
2703
2766
|
}
|
|
2704
2767
|
}
|
|
2705
|
-
const
|
|
2768
|
+
const backlogMultiplier = this.computeBacklogMultiplier(dueReviews.length);
|
|
2769
|
+
const notDue = reviews.filter((r) => !now.isAfter(import_moment.default.utc(r.reviewTime)));
|
|
2770
|
+
let nextDueIn = null;
|
|
2771
|
+
if (notDue.length > 0) {
|
|
2772
|
+
const next = notDue.reduce(
|
|
2773
|
+
(a, b) => import_moment.default.utc(a.reviewTime).isBefore(import_moment.default.utc(b.reviewTime)) ? a : b
|
|
2774
|
+
);
|
|
2775
|
+
const until = import_moment.default.duration(import_moment.default.utc(next.reviewTime).diff(now));
|
|
2776
|
+
nextDueIn = until.asHours() < 1 ? `${Math.round(until.asMinutes())}m` : until.asHours() < 24 ? `${Math.round(until.asHours())}h` : `${Math.round(until.asDays())}d`;
|
|
2777
|
+
}
|
|
2706
2778
|
if (dueReviews.length > 0) {
|
|
2707
|
-
const pressureNote =
|
|
2779
|
+
const pressureNote = backlogMultiplier > 1 ? ` [backlog pressure: \xD7${backlogMultiplier.toFixed(2)}]` : ` [healthy backlog]`;
|
|
2708
2780
|
logger.info(
|
|
2709
2781
|
`[SRS] Course ${courseId}: ${dueReviews.length} reviews due now (of ${reviews.length} scheduled)${pressureNote}`
|
|
2710
2782
|
);
|
|
@@ -2723,7 +2795,7 @@ var init_srs = __esm({
|
|
|
2723
2795
|
logger.info(`[SRS] Course ${courseId}: No reviews scheduled`);
|
|
2724
2796
|
}
|
|
2725
2797
|
const scored = dueReviews.map((review) => {
|
|
2726
|
-
const { score, reason } = this.computeUrgencyScore(review, now,
|
|
2798
|
+
const { score, reason } = this.computeUrgencyScore(review, now, backlogMultiplier);
|
|
2727
2799
|
return {
|
|
2728
2800
|
cardId: review.cardId,
|
|
2729
2801
|
courseId: review.courseId,
|
|
@@ -2741,30 +2813,42 @@ var init_srs = __esm({
|
|
|
2741
2813
|
]
|
|
2742
2814
|
};
|
|
2743
2815
|
});
|
|
2744
|
-
|
|
2816
|
+
const sorted = scored.sort((a, b) => b.score - a.score);
|
|
2817
|
+
captureSrsBacklog({
|
|
2818
|
+
courseId,
|
|
2819
|
+
scheduledTotal: reviews.length,
|
|
2820
|
+
dueNow: dueReviews.length,
|
|
2821
|
+
healthyBacklog: this.healthyBacklog,
|
|
2822
|
+
backlogMultiplier,
|
|
2823
|
+
maxBacklogMultiplier: MAX_BACKLOG_MULTIPLIER,
|
|
2824
|
+
topReviewScore: sorted.length > 0 ? sorted[0].score : null,
|
|
2825
|
+
nextDueIn,
|
|
2826
|
+
timestamp: Date.now()
|
|
2827
|
+
});
|
|
2828
|
+
return { cards: sorted.slice(0, limit) };
|
|
2745
2829
|
}
|
|
2746
2830
|
/**
|
|
2747
|
-
* Compute backlog pressure based on number of due reviews.
|
|
2831
|
+
* Compute the multiplicative backlog pressure based on number of due reviews.
|
|
2748
2832
|
*
|
|
2749
|
-
*
|
|
2750
|
-
* and
|
|
2833
|
+
* ×1.0 at or below the healthy threshold (no boost), increasing linearly above
|
|
2834
|
+
* it and maxing out at MAX_BACKLOG_MULTIPLIER at 3× the healthy backlog.
|
|
2751
2835
|
*
|
|
2752
|
-
* Examples (with default healthyBacklog=20):
|
|
2753
|
-
* - 10 due reviews →
|
|
2754
|
-
* - 20 due reviews →
|
|
2755
|
-
* - 40 due reviews →
|
|
2756
|
-
* - 60 due reviews →
|
|
2836
|
+
* Examples (with default healthyBacklog=20, MAX_BACKLOG_MULTIPLIER=2.0):
|
|
2837
|
+
* - 10 due reviews → ×1.00 (healthy)
|
|
2838
|
+
* - 20 due reviews → ×1.00 (at threshold)
|
|
2839
|
+
* - 40 due reviews → ×1.50 (2x threshold)
|
|
2840
|
+
* - 60 due reviews → ×2.00 (3x threshold, maxed)
|
|
2757
2841
|
*
|
|
2758
2842
|
* @param dueCount - Number of reviews currently due
|
|
2759
|
-
* @returns
|
|
2843
|
+
* @returns Multiplier applied to review urgency (1.0 to MAX_BACKLOG_MULTIPLIER)
|
|
2760
2844
|
*/
|
|
2761
|
-
|
|
2845
|
+
computeBacklogMultiplier(dueCount) {
|
|
2762
2846
|
if (dueCount <= this.healthyBacklog) {
|
|
2763
|
-
return
|
|
2847
|
+
return 1;
|
|
2764
2848
|
}
|
|
2765
2849
|
const excess = dueCount - this.healthyBacklog;
|
|
2766
|
-
const
|
|
2767
|
-
return Math.min(
|
|
2850
|
+
const multiplier = 1 + excess / this.healthyBacklog * ((MAX_BACKLOG_MULTIPLIER - 1) / 2);
|
|
2851
|
+
return Math.min(MAX_BACKLOG_MULTIPLIER, multiplier);
|
|
2768
2852
|
}
|
|
2769
2853
|
/**
|
|
2770
2854
|
* Compute urgency score for a review card.
|
|
@@ -2779,19 +2863,20 @@ var init_srs = __esm({
|
|
|
2779
2863
|
* - 30 days (720h) → ~0.56
|
|
2780
2864
|
* - 180 days → ~0.30
|
|
2781
2865
|
*
|
|
2782
|
-
* 3. Backlog pressure = global
|
|
2783
|
-
*
|
|
2784
|
-
* - At 2x healthy: +0.25
|
|
2785
|
-
* - At 3x+ healthy: +0.50 (max)
|
|
2866
|
+
* 3. Backlog pressure = global *multiplier* when review backlog exceeds the
|
|
2867
|
+
* healthy threshold (×1.0 healthy → up to MAX_BACKLOG_MULTIPLIER at 3×).
|
|
2786
2868
|
*
|
|
2787
|
-
* Combined: base 0.5 +
|
|
2788
|
-
*
|
|
2869
|
+
* Combined: (base 0.5 + urgency factors * 0.45) × backlog multiplier.
|
|
2870
|
+
* Per-card range before pressure: ~0.57–0.95. NOT clamped to 1.0 — under a
|
|
2871
|
+
* heavy backlog reviews scale onto the open scale to compete with (and exceed)
|
|
2872
|
+
* new cards; what keeps them from running away is the bounded multiplier, not
|
|
2873
|
+
* a hard ceiling.
|
|
2789
2874
|
*
|
|
2790
2875
|
* @param review - The scheduled card to score
|
|
2791
2876
|
* @param now - Current time
|
|
2792
|
-
* @param
|
|
2877
|
+
* @param backlogMultiplier - Pre-computed backlog multiplier (1.0 to MAX_BACKLOG_MULTIPLIER)
|
|
2793
2878
|
*/
|
|
2794
|
-
computeUrgencyScore(review, now,
|
|
2879
|
+
computeUrgencyScore(review, now, backlogMultiplier) {
|
|
2795
2880
|
const scheduledAt = import_moment.default.utc(review.scheduledAt);
|
|
2796
2881
|
const due = import_moment.default.utc(review.reviewTime);
|
|
2797
2882
|
const intervalHours = Math.max(1, due.diff(scheduledAt, "hours"));
|
|
@@ -2801,15 +2886,15 @@ var init_srs = __esm({
|
|
|
2801
2886
|
const overdueContribution = Math.min(1, Math.max(0, relativeOverdue));
|
|
2802
2887
|
const urgency = overdueContribution * 0.5 + recencyFactor * 0.5;
|
|
2803
2888
|
const baseScore = 0.5 + urgency * 0.45;
|
|
2804
|
-
const score =
|
|
2889
|
+
const score = baseScore * backlogMultiplier;
|
|
2805
2890
|
const reasonParts = [
|
|
2806
2891
|
`${Math.round(hoursOverdue)}h overdue`,
|
|
2807
2892
|
`interval: ${Math.round(intervalHours)}h`,
|
|
2808
2893
|
`relative: ${relativeOverdue.toFixed(2)}`,
|
|
2809
2894
|
`recency: ${recencyFactor.toFixed(2)}`
|
|
2810
2895
|
];
|
|
2811
|
-
if (
|
|
2812
|
-
reasonParts.push(`backlog:
|
|
2896
|
+
if (backlogMultiplier > 1) {
|
|
2897
|
+
reasonParts.push(`backlog: \xD7${backlogMultiplier.toFixed(2)}`);
|
|
2813
2898
|
}
|
|
2814
2899
|
reasonParts.push("review");
|
|
2815
2900
|
const reason = reasonParts.join(", ");
|
|
@@ -4861,6 +4946,7 @@ var init_3 = __esm({
|
|
|
4861
4946
|
"./Pipeline.ts": () => Promise.resolve().then(() => (init_Pipeline(), Pipeline_exports)),
|
|
4862
4947
|
"./PipelineAssembler.ts": () => Promise.resolve().then(() => (init_PipelineAssembler(), PipelineAssembler_exports)),
|
|
4863
4948
|
"./PipelineDebugger.ts": () => Promise.resolve().then(() => (init_PipelineDebugger(), PipelineDebugger_exports)),
|
|
4949
|
+
"./SrsDebugger.ts": () => Promise.resolve().then(() => (init_SrsDebugger(), SrsDebugger_exports)),
|
|
4864
4950
|
"./defaults.ts": () => Promise.resolve().then(() => (init_defaults(), defaults_exports)),
|
|
4865
4951
|
"./diversityRerank.ts": () => Promise.resolve().then(() => (init_diversityRerank(), diversityRerank_exports)),
|
|
4866
4952
|
"./filters/WeightedFilter.ts": () => Promise.resolve().then(() => (init_WeightedFilter(), WeightedFilter_exports)),
|
|
@@ -4893,12 +4979,14 @@ __export(navigators_exports, {
|
|
|
4893
4979
|
NavigatorRole: () => NavigatorRole,
|
|
4894
4980
|
NavigatorRoles: () => NavigatorRoles,
|
|
4895
4981
|
Navigators: () => Navigators,
|
|
4982
|
+
clearSrsBacklogDebug: () => clearSrsBacklogDebug,
|
|
4896
4983
|
diversityRerank: () => diversityRerank,
|
|
4897
4984
|
getActivePipeline: () => getActivePipeline,
|
|
4898
4985
|
getCardOrigin: () => getCardOrigin,
|
|
4899
4986
|
getRegisteredNavigator: () => getRegisteredNavigator,
|
|
4900
4987
|
getRegisteredNavigatorNames: () => getRegisteredNavigatorNames,
|
|
4901
4988
|
getRegisteredNavigatorRole: () => getRegisteredNavigatorRole,
|
|
4989
|
+
getSrsBacklogDebug: () => getSrsBacklogDebug,
|
|
4902
4990
|
hasRegisteredNavigator: () => hasRegisteredNavigator,
|
|
4903
4991
|
initializeNavigatorRegistry: () => initializeNavigatorRegistry,
|
|
4904
4992
|
isFilter: () => isFilter,
|
|
@@ -4980,6 +5068,7 @@ var init_navigators = __esm({
|
|
|
4980
5068
|
"use strict";
|
|
4981
5069
|
init_diversityRerank();
|
|
4982
5070
|
init_PipelineDebugger();
|
|
5071
|
+
init_SrsDebugger();
|
|
4983
5072
|
init_logger();
|
|
4984
5073
|
init_();
|
|
4985
5074
|
init_2();
|