@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.
Files changed (38) hide show
  1. package/dist/{contentSource-Cplhv3bJ.d.ts → contentSource-C-0t0y0V.d.ts} +7 -0
  2. package/dist/{contentSource-kI9_jwTu.d.cts → contentSource-jSkcOt2s.d.cts} +7 -0
  3. package/dist/core/index.d.cts +29 -4
  4. package/dist/core/index.d.ts +29 -4
  5. package/dist/core/index.js +132 -39
  6. package/dist/core/index.js.map +1 -1
  7. package/dist/core/index.mjs +130 -39
  8. package/dist/core/index.mjs.map +1 -1
  9. package/dist/{dataLayerProvider-DrBqOUa3.d.ts → dataLayerProvider-BB0oi9T0.d.ts} +1 -1
  10. package/dist/{dataLayerProvider-CiA2Rr0v.d.cts → dataLayerProvider-BDClIrFC.d.cts} +1 -1
  11. package/dist/impl/couch/index.d.cts +2 -2
  12. package/dist/impl/couch/index.d.ts +2 -2
  13. package/dist/impl/couch/index.js +128 -39
  14. package/dist/impl/couch/index.js.map +1 -1
  15. package/dist/impl/couch/index.mjs +128 -39
  16. package/dist/impl/couch/index.mjs.map +1 -1
  17. package/dist/impl/static/index.d.cts +2 -2
  18. package/dist/impl/static/index.d.ts +2 -2
  19. package/dist/impl/static/index.js +128 -39
  20. package/dist/impl/static/index.js.map +1 -1
  21. package/dist/impl/static/index.mjs +128 -39
  22. package/dist/impl/static/index.mjs.map +1 -1
  23. package/dist/index.d.cts +115 -81
  24. package/dist/index.d.ts +115 -81
  25. package/dist/index.js +371 -251
  26. package/dist/index.js.map +1 -1
  27. package/dist/index.mjs +369 -251
  28. package/dist/index.mjs.map +1 -1
  29. package/docs/navigators-architecture.md +29 -13
  30. package/package.json +3 -3
  31. package/src/core/interfaces/contentSource.ts +7 -0
  32. package/src/core/navigators/SrsDebugger.ts +53 -0
  33. package/src/core/navigators/generators/prescribed.ts +76 -9
  34. package/src/core/navigators/generators/srs.ts +81 -37
  35. package/src/core/navigators/index.ts +5 -0
  36. package/src/study/SessionController.ts +260 -249
  37. package/src/study/SessionDebugger.ts +15 -25
  38. 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-Cplhv3bJ.js';
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-kI9_jwTu.cjs';
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-kI9_jwTu.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-kI9_jwTu.cjs';
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-Cplhv3bJ.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-Cplhv3bJ.js';
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';
@@ -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 lets the pipeline's
2405
- * scoring + the durable per-skill boost order them. Ordering/emphasis is NOT
2406
- * this method's job; it only guarantees presence.
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: BASE_PRACTICE_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: BASE_PRACTICE_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, MAX_BACKLOG_PRESSURE, SRSNavigator;
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
- MAX_BACKLOG_PRESSURE = 0.5;
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 backlogPressure = this.computeBacklogPressure(dueReviews.length);
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 = backlogPressure > 0 ? ` [backlog pressure: +${backlogPressure.toFixed(2)}]` : ` [healthy backlog]`;
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, backlogPressure);
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
- return { cards: scored.sort((a, b) => b.score - a.score).slice(0, limit) };
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
- * Backlog pressure is 0 when at or below healthy threshold,
2750
- * and increases linearly above it, maxing out at MAX_BACKLOG_PRESSURE.
2833
+ * ×1.0 at or below the healthy threshold (no boost), increasing linearly above
2834
+ * it and maxing out at MAX_BACKLOG_MULTIPLIER at the healthy backlog.
2751
2835
  *
2752
- * Examples (with default healthyBacklog=20):
2753
- * - 10 due reviews → 0.00 (healthy)
2754
- * - 20 due reviews → 0.00 (at threshold)
2755
- * - 40 due reviews → 0.25 (2x threshold)
2756
- * - 60 due reviews → 0.50 (3x threshold, maxed)
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 Backlog pressure score to add to urgency (0 to MAX_BACKLOG_PRESSURE)
2843
+ * @returns Multiplier applied to review urgency (1.0 to MAX_BACKLOG_MULTIPLIER)
2760
2844
  */
2761
- computeBacklogPressure(dueCount) {
2845
+ computeBacklogMultiplier(dueCount) {
2762
2846
  if (dueCount <= this.healthyBacklog) {
2763
- return 0;
2847
+ return 1;
2764
2848
  }
2765
2849
  const excess = dueCount - this.healthyBacklog;
2766
- const pressure = excess / this.healthyBacklog * (MAX_BACKLOG_PRESSURE / 2);
2767
- return Math.min(MAX_BACKLOG_PRESSURE, pressure);
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 boost when review backlog exceeds healthy threshold
2783
- * - At healthy backlog: 0
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 + (urgency factors * 0.45) + backlog pressure
2788
- * Result range: 0.5 to 1.0 (uncapped to allow high-urgency reviews to compete with new cards)
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 backlogPressure - Pre-computed backlog pressure (0 to 0.5)
2877
+ * @param backlogMultiplier - Pre-computed backlog multiplier (1.0 to MAX_BACKLOG_MULTIPLIER)
2793
2878
  */
2794
- computeUrgencyScore(review, now, backlogPressure) {
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 = Math.min(1, baseScore + backlogPressure);
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 (backlogPressure > 0) {
2812
- reasonParts.push(`backlog: +${backlogPressure.toFixed(2)}`);
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();