@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
@@ -1192,6 +1192,13 @@ interface StudySessionItem {
1192
1192
  cardID: string;
1193
1193
  courseID: string;
1194
1194
  elo?: number;
1195
+ /**
1196
+ * Pipeline suitability score at queue-build time, carried for observability
1197
+ * (the debug overlay renders the now-load-bearing supply ranking). `+INF`
1198
+ * marks a mandatory required card. Not used for any draw decision — the
1199
+ * supplyQ is already ordered, so the controller draws front-to-back.
1200
+ */
1201
+ score?: number;
1195
1202
  }
1196
1203
  interface ContentSourceID {
1197
1204
  type: 'course' | 'classroom';
@@ -1192,6 +1192,13 @@ interface StudySessionItem {
1192
1192
  cardID: string;
1193
1193
  courseID: string;
1194
1194
  elo?: number;
1195
+ /**
1196
+ * Pipeline suitability score at queue-build time, carried for observability
1197
+ * (the debug overlay renders the now-load-bearing supply ranking). `+INF`
1198
+ * marks a mandatory required card. Not used for any draw decision — the
1199
+ * supplyQ is already ordered, so the controller draws front-to-back.
1200
+ */
1201
+ score?: number;
1195
1202
  }
1196
1203
  interface ContentSourceID {
1197
1204
  type: 'course' | 'classroom';
@@ -1,6 +1,6 @@
1
- import { a7 as LearnableWeight, M as UserOutcomeRecord, a8 as OrchestrationContext, W as WeightedCard, U as UserDBInterface, C as CourseDBInterface, R as ReplanHints, Z as StrategyContribution } from '../contentSource-kI9_jwTu.cjs';
2
- export { K as ActivityRecord, A as AdminDBInterface, v as AssignedCard, g as AssignedContent, u as AssignedCourse, t as AssignedTag, a4 as CardGenerator, a6 as CardGeneratorFactory, c as ClassroomDBInterface, F as ClassroomRegistration, E as ClassroomRegistrationDesignation, H as ClassroomRegistrationDoc, e as ContentNavigationStrategyData, f as ContentNavigator, q as ContentSourceID, d as CourseInfo, L as CourseRegistration, s as CourseRegistrationDoc, b as CoursesDBInterface, a5 as GeneratorContext, G as GeneratorResult, N as NavigatorConstructor, a0 as NavigatorRole, a1 as NavigatorRoles, $ as Navigators, j as ScheduledCard, I as SessionTrackingData, i as StudentClassroomDBInterface, h as StudyContentSource, k as StudySessionFailedItem, l as StudySessionFailedNewItem, m as StudySessionFailedReviewItem, S as StudySessionItem, n as StudySessionNewItem, o as StudySessionReviewItem, T as TeacherClassroomDBInterface, J as UserConfig, z as UserCourseSetting, y as UserCourseSettings, x as UserDBAuthenticator, a as UserDBReader, w as UserDBWriter, B as UsrCrsDataInterface, a9 as computeDeviation, ab as computeEffectiveWeight, aa as computeSpread, ac as createOrchestrationContext, _ as getCardOrigin, P as getRegisteredNavigator, X as getRegisteredNavigatorNames, V as getRegisteredNavigatorRole, r as getStudySource, Q as hasRegisteredNavigator, Y as initializeNavigatorRegistry, a3 as isFilter, a2 as isGenerator, p as isReview, O as registerNavigator } from '../contentSource-kI9_jwTu.cjs';
3
- export { D as DataLayerProvider } from '../dataLayerProvider-CiA2Rr0v.cjs';
1
+ import { a7 as LearnableWeight, M as UserOutcomeRecord, a8 as OrchestrationContext, W as WeightedCard, U as UserDBInterface, C as CourseDBInterface, R as ReplanHints, Z as StrategyContribution } from '../contentSource-jSkcOt2s.cjs';
2
+ export { K as ActivityRecord, A as AdminDBInterface, v as AssignedCard, g as AssignedContent, u as AssignedCourse, t as AssignedTag, a4 as CardGenerator, a6 as CardGeneratorFactory, c as ClassroomDBInterface, F as ClassroomRegistration, E as ClassroomRegistrationDesignation, H as ClassroomRegistrationDoc, e as ContentNavigationStrategyData, f as ContentNavigator, q as ContentSourceID, d as CourseInfo, L as CourseRegistration, s as CourseRegistrationDoc, b as CoursesDBInterface, a5 as GeneratorContext, G as GeneratorResult, N as NavigatorConstructor, a0 as NavigatorRole, a1 as NavigatorRoles, $ as Navigators, j as ScheduledCard, I as SessionTrackingData, i as StudentClassroomDBInterface, h as StudyContentSource, k as StudySessionFailedItem, l as StudySessionFailedNewItem, m as StudySessionFailedReviewItem, S as StudySessionItem, n as StudySessionNewItem, o as StudySessionReviewItem, T as TeacherClassroomDBInterface, J as UserConfig, z as UserCourseSetting, y as UserCourseSettings, x as UserDBAuthenticator, a as UserDBReader, w as UserDBWriter, B as UsrCrsDataInterface, a9 as computeDeviation, ab as computeEffectiveWeight, aa as computeSpread, ac as createOrchestrationContext, _ as getCardOrigin, P as getRegisteredNavigator, X as getRegisteredNavigatorNames, V as getRegisteredNavigatorRole, r as getStudySource, Q as hasRegisteredNavigator, Y as initializeNavigatorRegistry, a3 as isFilter, a2 as isGenerator, p as isReview, O as registerNavigator } from '../contentSource-jSkcOt2s.cjs';
3
+ export { D as DataLayerProvider } from '../dataLayerProvider-BDClIrFC.cjs';
4
4
  import { D as DocType, d as QuestionRecord, b as DocTypePrefixes, C as CardHistory, c as CardRecord } from '../types-legacy-4tlwHnXo.cjs';
5
5
  export { e as CardData, f as CourseListData, h as DataShapeData, g as DisplayableData, F as Field, G as GuestUsername, Q as QualifiedCardID, i as QuestionData, S as SkuilderCourseData, a as Tag, T as TagStub, l as log } from '../types-legacy-4tlwHnXo.cjs';
6
6
  import { DataShape, ParsedCard } from '@vue-skuilder/common';
@@ -12,6 +12,31 @@ declare abstract class Loggable {
12
12
  protected error(...args: unknown[]): void;
13
13
  }
14
14
 
15
+ /** Per-course snapshot of SRS backlog state, captured on each SRS generator run. */
16
+ interface SrsBacklogDebug {
17
+ courseId: string;
18
+ /** Total reviews scheduled for this course (due + not-yet-due). */
19
+ scheduledTotal: number;
20
+ /** Reviews eligible (due) right now. */
21
+ dueNow: number;
22
+ /** Healthy backlog threshold; multiplier is ×1.0 at or below this. */
23
+ healthyBacklog: number;
24
+ /** Global multiplier applied to every due review's urgency this run (1.0 → max). */
25
+ backlogMultiplier: number;
26
+ /** Max achievable backlog multiplier (the cap), for headroom context. */
27
+ maxBacklogMultiplier: number;
28
+ /** Highest review score produced this run (post-multiplier; can exceed 1.0); null if none due. */
29
+ topReviewScore: number | null;
30
+ /** Human-readable time until the next review comes due, or null if some are due now. */
31
+ nextDueIn: string | null;
32
+ /** Epoch ms of capture. */
33
+ timestamp: number;
34
+ }
35
+ /** Current backlog snapshot for every course seen, newest-first. */
36
+ declare function getSrsBacklogDebug(): SrsBacklogDebug[];
37
+ /** Drop all captured snapshots (called on session start, alongside pipeline history). */
38
+ declare function clearSrsBacklogDebug(): void;
39
+
15
40
  /**
16
41
  * Snapshot of the learning state for a strategy.
17
42
  *
@@ -780,4 +805,4 @@ declare const userDBDebugAPI: {
780
805
  */
781
806
  declare function mountUserDBDebugger(): void;
782
807
 
783
- export { type BulkCardProcessorConfig, type CardFilter, type CardFilterFactory, CardHistory, CardRecord, CourseDBInterface, DIVERSITY_FLOOR, DIVERSITY_STRENGTH, type DiversityRerankOptions, DocType, DocTypePrefixes, type FilterContext, type FilterImpact, type GeneratorSummary, type GradientObservation, type GradientResult, type ImportResult, LearnableWeight, Loggable, OrchestrationContext, type PeriodUpdateInput, type PeriodUpdateResult, type PipelineForecaster, type PipelineRunReport, QuestionRecord, ReplanHints, type SignalConfig, StrategyContribution, type StrategyLearningState, type StrategyStateDoc, type StrategyStateId, UserDBInterface, UserOutcomeRecord, WeightedCard, aggregateOutcomesForGradient, areQuestionRecords, buildStrategyStateId, computeOutcomeSignal, computeStrategyGradient, diversityRerank, docIsDeleted, getActivePipeline, getCardHistoryID, getDefaultLearnableWeight, importParsedCards, isQuestionRecord, mountPipelineDebugger, mountUserDBDebugger, parseCardHistoryID, pipelineDebugAPI, recordUserOutcome, runPeriodUpdate, scoreAccuracyInZone, updateLearningState, updateStrategyWeight, userDBDebugAPI, validateProcessorConfig };
808
+ export { type BulkCardProcessorConfig, type CardFilter, type CardFilterFactory, CardHistory, CardRecord, CourseDBInterface, DIVERSITY_FLOOR, DIVERSITY_STRENGTH, type DiversityRerankOptions, DocType, DocTypePrefixes, type FilterContext, type FilterImpact, type GeneratorSummary, type GradientObservation, type GradientResult, type ImportResult, LearnableWeight, Loggable, OrchestrationContext, type PeriodUpdateInput, type PeriodUpdateResult, type PipelineForecaster, type PipelineRunReport, QuestionRecord, ReplanHints, type SignalConfig, type SrsBacklogDebug, StrategyContribution, type StrategyLearningState, type StrategyStateDoc, type StrategyStateId, UserDBInterface, UserOutcomeRecord, WeightedCard, aggregateOutcomesForGradient, areQuestionRecords, buildStrategyStateId, clearSrsBacklogDebug, computeOutcomeSignal, computeStrategyGradient, diversityRerank, docIsDeleted, getActivePipeline, getCardHistoryID, getDefaultLearnableWeight, getSrsBacklogDebug, importParsedCards, isQuestionRecord, mountPipelineDebugger, mountUserDBDebugger, parseCardHistoryID, pipelineDebugAPI, recordUserOutcome, runPeriodUpdate, scoreAccuracyInZone, updateLearningState, updateStrategyWeight, userDBDebugAPI, validateProcessorConfig };
@@ -1,6 +1,6 @@
1
- import { a7 as LearnableWeight, M as UserOutcomeRecord, a8 as OrchestrationContext, W as WeightedCard, U as UserDBInterface, C as CourseDBInterface, R as ReplanHints, Z as StrategyContribution } from '../contentSource-Cplhv3bJ.js';
2
- export { K as ActivityRecord, A as AdminDBInterface, v as AssignedCard, g as AssignedContent, u as AssignedCourse, t as AssignedTag, a4 as CardGenerator, a6 as CardGeneratorFactory, c as ClassroomDBInterface, F as ClassroomRegistration, E as ClassroomRegistrationDesignation, H as ClassroomRegistrationDoc, e as ContentNavigationStrategyData, f as ContentNavigator, q as ContentSourceID, d as CourseInfo, L as CourseRegistration, s as CourseRegistrationDoc, b as CoursesDBInterface, a5 as GeneratorContext, G as GeneratorResult, N as NavigatorConstructor, a0 as NavigatorRole, a1 as NavigatorRoles, $ as Navigators, j as ScheduledCard, I as SessionTrackingData, i as StudentClassroomDBInterface, h as StudyContentSource, k as StudySessionFailedItem, l as StudySessionFailedNewItem, m as StudySessionFailedReviewItem, S as StudySessionItem, n as StudySessionNewItem, o as StudySessionReviewItem, T as TeacherClassroomDBInterface, J as UserConfig, z as UserCourseSetting, y as UserCourseSettings, x as UserDBAuthenticator, a as UserDBReader, w as UserDBWriter, B as UsrCrsDataInterface, a9 as computeDeviation, ab as computeEffectiveWeight, aa as computeSpread, ac as createOrchestrationContext, _ as getCardOrigin, P as getRegisteredNavigator, X as getRegisteredNavigatorNames, V as getRegisteredNavigatorRole, r as getStudySource, Q as hasRegisteredNavigator, Y as initializeNavigatorRegistry, a3 as isFilter, a2 as isGenerator, p as isReview, O as registerNavigator } from '../contentSource-Cplhv3bJ.js';
3
- export { D as DataLayerProvider } from '../dataLayerProvider-DrBqOUa3.js';
1
+ import { a7 as LearnableWeight, M as UserOutcomeRecord, a8 as OrchestrationContext, W as WeightedCard, U as UserDBInterface, C as CourseDBInterface, R as ReplanHints, Z as StrategyContribution } from '../contentSource-C-0t0y0V.js';
2
+ export { K as ActivityRecord, A as AdminDBInterface, v as AssignedCard, g as AssignedContent, u as AssignedCourse, t as AssignedTag, a4 as CardGenerator, a6 as CardGeneratorFactory, c as ClassroomDBInterface, F as ClassroomRegistration, E as ClassroomRegistrationDesignation, H as ClassroomRegistrationDoc, e as ContentNavigationStrategyData, f as ContentNavigator, q as ContentSourceID, d as CourseInfo, L as CourseRegistration, s as CourseRegistrationDoc, b as CoursesDBInterface, a5 as GeneratorContext, G as GeneratorResult, N as NavigatorConstructor, a0 as NavigatorRole, a1 as NavigatorRoles, $ as Navigators, j as ScheduledCard, I as SessionTrackingData, i as StudentClassroomDBInterface, h as StudyContentSource, k as StudySessionFailedItem, l as StudySessionFailedNewItem, m as StudySessionFailedReviewItem, S as StudySessionItem, n as StudySessionNewItem, o as StudySessionReviewItem, T as TeacherClassroomDBInterface, J as UserConfig, z as UserCourseSetting, y as UserCourseSettings, x as UserDBAuthenticator, a as UserDBReader, w as UserDBWriter, B as UsrCrsDataInterface, a9 as computeDeviation, ab as computeEffectiveWeight, aa as computeSpread, ac as createOrchestrationContext, _ as getCardOrigin, P as getRegisteredNavigator, X as getRegisteredNavigatorNames, V as getRegisteredNavigatorRole, r as getStudySource, Q as hasRegisteredNavigator, Y as initializeNavigatorRegistry, a3 as isFilter, a2 as isGenerator, p as isReview, O as registerNavigator } from '../contentSource-C-0t0y0V.js';
3
+ export { D as DataLayerProvider } from '../dataLayerProvider-BB0oi9T0.js';
4
4
  import { D as DocType, d as QuestionRecord, b as DocTypePrefixes, C as CardHistory, c as CardRecord } from '../types-legacy-4tlwHnXo.js';
5
5
  export { e as CardData, f as CourseListData, h as DataShapeData, g as DisplayableData, F as Field, G as GuestUsername, Q as QualifiedCardID, i as QuestionData, S as SkuilderCourseData, a as Tag, T as TagStub, l as log } from '../types-legacy-4tlwHnXo.js';
6
6
  import { DataShape, ParsedCard } from '@vue-skuilder/common';
@@ -12,6 +12,31 @@ declare abstract class Loggable {
12
12
  protected error(...args: unknown[]): void;
13
13
  }
14
14
 
15
+ /** Per-course snapshot of SRS backlog state, captured on each SRS generator run. */
16
+ interface SrsBacklogDebug {
17
+ courseId: string;
18
+ /** Total reviews scheduled for this course (due + not-yet-due). */
19
+ scheduledTotal: number;
20
+ /** Reviews eligible (due) right now. */
21
+ dueNow: number;
22
+ /** Healthy backlog threshold; multiplier is ×1.0 at or below this. */
23
+ healthyBacklog: number;
24
+ /** Global multiplier applied to every due review's urgency this run (1.0 → max). */
25
+ backlogMultiplier: number;
26
+ /** Max achievable backlog multiplier (the cap), for headroom context. */
27
+ maxBacklogMultiplier: number;
28
+ /** Highest review score produced this run (post-multiplier; can exceed 1.0); null if none due. */
29
+ topReviewScore: number | null;
30
+ /** Human-readable time until the next review comes due, or null if some are due now. */
31
+ nextDueIn: string | null;
32
+ /** Epoch ms of capture. */
33
+ timestamp: number;
34
+ }
35
+ /** Current backlog snapshot for every course seen, newest-first. */
36
+ declare function getSrsBacklogDebug(): SrsBacklogDebug[];
37
+ /** Drop all captured snapshots (called on session start, alongside pipeline history). */
38
+ declare function clearSrsBacklogDebug(): void;
39
+
15
40
  /**
16
41
  * Snapshot of the learning state for a strategy.
17
42
  *
@@ -780,4 +805,4 @@ declare const userDBDebugAPI: {
780
805
  */
781
806
  declare function mountUserDBDebugger(): void;
782
807
 
783
- export { type BulkCardProcessorConfig, type CardFilter, type CardFilterFactory, CardHistory, CardRecord, CourseDBInterface, DIVERSITY_FLOOR, DIVERSITY_STRENGTH, type DiversityRerankOptions, DocType, DocTypePrefixes, type FilterContext, type FilterImpact, type GeneratorSummary, type GradientObservation, type GradientResult, type ImportResult, LearnableWeight, Loggable, OrchestrationContext, type PeriodUpdateInput, type PeriodUpdateResult, type PipelineForecaster, type PipelineRunReport, QuestionRecord, ReplanHints, type SignalConfig, StrategyContribution, type StrategyLearningState, type StrategyStateDoc, type StrategyStateId, UserDBInterface, UserOutcomeRecord, WeightedCard, aggregateOutcomesForGradient, areQuestionRecords, buildStrategyStateId, computeOutcomeSignal, computeStrategyGradient, diversityRerank, docIsDeleted, getActivePipeline, getCardHistoryID, getDefaultLearnableWeight, importParsedCards, isQuestionRecord, mountPipelineDebugger, mountUserDBDebugger, parseCardHistoryID, pipelineDebugAPI, recordUserOutcome, runPeriodUpdate, scoreAccuracyInZone, updateLearningState, updateStrategyWeight, userDBDebugAPI, validateProcessorConfig };
808
+ export { type BulkCardProcessorConfig, type CardFilter, type CardFilterFactory, CardHistory, CardRecord, CourseDBInterface, DIVERSITY_FLOOR, DIVERSITY_STRENGTH, type DiversityRerankOptions, DocType, DocTypePrefixes, type FilterContext, type FilterImpact, type GeneratorSummary, type GradientObservation, type GradientResult, type ImportResult, LearnableWeight, Loggable, OrchestrationContext, type PeriodUpdateInput, type PeriodUpdateResult, type PipelineForecaster, type PipelineRunReport, QuestionRecord, ReplanHints, type SignalConfig, type SrsBacklogDebug, StrategyContribution, type StrategyLearningState, type StrategyStateDoc, type StrategyStateId, UserDBInterface, UserOutcomeRecord, WeightedCard, aggregateOutcomesForGradient, areQuestionRecords, buildStrategyStateId, clearSrsBacklogDebug, computeOutcomeSignal, computeStrategyGradient, diversityRerank, docIsDeleted, getActivePipeline, getCardHistoryID, getDefaultLearnableWeight, getSrsBacklogDebug, importParsedCards, isQuestionRecord, mountPipelineDebugger, mountUserDBDebugger, parseCardHistoryID, pipelineDebugAPI, recordUserOutcome, runPeriodUpdate, scoreAccuracyInZone, updateLearningState, updateStrategyWeight, userDBDebugAPI, validateProcessorConfig };
@@ -1640,6 +1640,30 @@ Example:
1640
1640
  }
1641
1641
  });
1642
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
+
1643
1667
  // src/core/navigators/generators/CompositeGenerator.ts
1644
1668
  var CompositeGenerator_exports = {};
1645
1669
  __export(CompositeGenerator_exports, {
@@ -2007,7 +2031,7 @@ function shuffleInPlace(arr) {
2007
2031
  function pickTopByScore(cards, limit) {
2008
2032
  return [...cards].sort((a, b) => b.score - a.score || a.cardId.localeCompare(b.cardId)).slice(0, limit);
2009
2033
  }
2010
- 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;
2011
2035
  var init_prescribed = __esm({
2012
2036
  "src/core/navigators/generators/prescribed.ts"() {
2013
2037
  "use strict";
@@ -2024,6 +2048,9 @@ var init_prescribed = __esm({
2024
2048
  BASE_SUPPORT_SCORE = 0.8;
2025
2049
  DISCOVERED_SUPPORT_SCORE = 12;
2026
2050
  BASE_PRACTICE_SCORE = 1;
2051
+ PRACTICE_BASE_MULT = 2;
2052
+ MAX_PRACTICE_MULTIPLIER = 4;
2053
+ PRACTICE_STALENESS_BUMP_PER_DAY = 0.5;
2027
2054
  MAX_TARGET_MULTIPLIER = 8;
2028
2055
  MAX_SUPPORT_MULTIPLIER = 4;
2029
2056
  PRESCRIBED_DEBUG_VERSION = "testversion-prescribed-v3";
@@ -2083,6 +2110,8 @@ var init_prescribed = __esm({
2083
2110
  const emitted = [];
2084
2111
  const emittedIds = /* @__PURE__ */ new Set();
2085
2112
  const groupRuntimes = [];
2113
+ const priorPracticeDebt = progress.practiceDebt ?? {};
2114
+ const nextPracticeDebt = {};
2086
2115
  for (const group of this.config.groups) {
2087
2116
  const runtime = this.buildGroupRuntimeState({
2088
2117
  group,
@@ -2140,10 +2169,13 @@ var init_prescribed = __esm({
2140
2169
  userTagElo,
2141
2170
  userGlobalElo,
2142
2171
  activeIds,
2143
- seenIds
2172
+ seenIds,
2173
+ priorPracticeDebt,
2174
+ nextPracticeDebt
2144
2175
  });
2145
2176
  emitted.push(...directCards, ...supportCards, ...discoveredSupportCards, ...practiceCards);
2146
2177
  }
2178
+ nextState.practiceDebt = nextPracticeDebt;
2147
2179
  const hintSummary = this.buildSupportHintSummary(groupRuntimes);
2148
2180
  const hints = Object.keys(hintSummary.boostTags).length > 0 ? {
2149
2181
  boostTags: hintSummary.boostTags,
@@ -2477,9 +2509,16 @@ var init_prescribed = __esm({
2477
2509
  * `practiceMinCount`), this resolves cards carrying that tag and emits them
2478
2510
  * into the candidate pool. It exists because global-ELO retrieval
2479
2511
  * systematically fails to fetch the (low-ELO) drill cards for a
2480
- * freshly-introduced skill — putting them in the pool here lets the pipeline's
2481
- * scoring + the durable per-skill boost order them. Ordering/emphasis is NOT
2482
- * this method's job; it only guarantees presence.
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.
2483
2522
  *
2484
2523
  * Fully data-driven: the unlock relation comes from the hierarchy config and
2485
2524
  * practice-status from per-tag ELO. No card-id or tag-namespace hard-coding.
@@ -2494,7 +2533,9 @@ var init_prescribed = __esm({
2494
2533
  userTagElo,
2495
2534
  userGlobalElo,
2496
2535
  activeIds,
2497
- seenIds
2536
+ seenIds,
2537
+ priorPracticeDebt,
2538
+ nextPracticeDebt
2498
2539
  } = args;
2499
2540
  const patterns = group.practiceTagPatterns ?? [];
2500
2541
  if (patterns.length === 0) return [];
@@ -2504,6 +2545,20 @@ var init_prescribed = __esm({
2504
2545
  (tag) => patterns.some((p) => matchesTagPattern(tag, p)) && this.isUnlockedGatedSkill(tag, hierarchyConfigs, userTagElo, userGlobalElo) && (userTagElo[tag]?.count ?? 0) < practiceMinCount
2505
2546
  );
2506
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
+ }
2507
2562
  const practiceCardIds = this.findDiscoveredSupportCards({
2508
2563
  supportTags: practiceTags,
2509
2564
  cardsByTag,
@@ -2519,18 +2574,25 @@ var init_prescribed = __esm({
2519
2574
  const cards = [];
2520
2575
  for (const cardId of practiceCardIds) {
2521
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;
2522
2584
  cards.push({
2523
2585
  cardId,
2524
2586
  courseId,
2525
- score: BASE_PRACTICE_SCORE,
2587
+ score,
2526
2588
  provenance: [
2527
2589
  {
2528
2590
  strategy: "prescribed",
2529
2591
  strategyName: this.strategyName || this.name,
2530
2592
  strategyId: this.strategyId || "NAVIGATION_STRATEGY-prescribed",
2531
2593
  action: "generated",
2532
- score: BASE_PRACTICE_SCORE,
2533
- 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}`
2534
2596
  }
2535
2597
  ]
2536
2598
  });
@@ -2703,15 +2765,16 @@ var srs_exports = {};
2703
2765
  __export(srs_exports, {
2704
2766
  default: () => SRSNavigator
2705
2767
  });
2706
- var import_moment3, DEFAULT_HEALTHY_BACKLOG, MAX_BACKLOG_PRESSURE, SRSNavigator;
2768
+ var import_moment3, DEFAULT_HEALTHY_BACKLOG, MAX_BACKLOG_MULTIPLIER, SRSNavigator;
2707
2769
  var init_srs = __esm({
2708
2770
  "src/core/navigators/generators/srs.ts"() {
2709
2771
  "use strict";
2710
2772
  import_moment3 = __toESM(require("moment"), 1);
2711
2773
  init_navigators();
2774
+ init_SrsDebugger();
2712
2775
  init_logger();
2713
2776
  DEFAULT_HEALTHY_BACKLOG = 20;
2714
- MAX_BACKLOG_PRESSURE = 0.5;
2777
+ MAX_BACKLOG_MULTIPLIER = 2;
2715
2778
  SRSNavigator = class extends ContentNavigator {
2716
2779
  /** Human-readable name for CardGenerator interface */
2717
2780
  name;
@@ -2778,9 +2841,18 @@ var init_srs = __esm({
2778
2841
  }
2779
2842
  }
2780
2843
  }
2781
- const backlogPressure = this.computeBacklogPressure(dueReviews.length);
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
+ }
2782
2854
  if (dueReviews.length > 0) {
2783
- const pressureNote = backlogPressure > 0 ? ` [backlog pressure: +${backlogPressure.toFixed(2)}]` : ` [healthy backlog]`;
2855
+ const pressureNote = backlogMultiplier > 1 ? ` [backlog pressure: \xD7${backlogMultiplier.toFixed(2)}]` : ` [healthy backlog]`;
2784
2856
  logger.info(
2785
2857
  `[SRS] Course ${courseId}: ${dueReviews.length} reviews due now (of ${reviews.length} scheduled)${pressureNote}`
2786
2858
  );
@@ -2799,7 +2871,7 @@ var init_srs = __esm({
2799
2871
  logger.info(`[SRS] Course ${courseId}: No reviews scheduled`);
2800
2872
  }
2801
2873
  const scored = dueReviews.map((review) => {
2802
- const { score, reason } = this.computeUrgencyScore(review, now, backlogPressure);
2874
+ const { score, reason } = this.computeUrgencyScore(review, now, backlogMultiplier);
2803
2875
  return {
2804
2876
  cardId: review.cardId,
2805
2877
  courseId: review.courseId,
@@ -2817,30 +2889,42 @@ var init_srs = __esm({
2817
2889
  ]
2818
2890
  };
2819
2891
  });
2820
- return { cards: scored.sort((a, b) => b.score - a.score).slice(0, limit) };
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) };
2821
2905
  }
2822
2906
  /**
2823
- * Compute backlog pressure based on number of due reviews.
2907
+ * Compute the multiplicative backlog pressure based on number of due reviews.
2824
2908
  *
2825
- * Backlog pressure is 0 when at or below healthy threshold,
2826
- * and increases linearly above it, maxing out at MAX_BACKLOG_PRESSURE.
2909
+ * ×1.0 at or below the healthy threshold (no boost), increasing linearly above
2910
+ * it and maxing out at MAX_BACKLOG_MULTIPLIER at the healthy backlog.
2827
2911
  *
2828
- * Examples (with default healthyBacklog=20):
2829
- * - 10 due reviews → 0.00 (healthy)
2830
- * - 20 due reviews → 0.00 (at threshold)
2831
- * - 40 due reviews → 0.25 (2x threshold)
2832
- * - 60 due reviews → 0.50 (3x threshold, maxed)
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)
2833
2917
  *
2834
2918
  * @param dueCount - Number of reviews currently due
2835
- * @returns Backlog pressure score to add to urgency (0 to MAX_BACKLOG_PRESSURE)
2919
+ * @returns Multiplier applied to review urgency (1.0 to MAX_BACKLOG_MULTIPLIER)
2836
2920
  */
2837
- computeBacklogPressure(dueCount) {
2921
+ computeBacklogMultiplier(dueCount) {
2838
2922
  if (dueCount <= this.healthyBacklog) {
2839
- return 0;
2923
+ return 1;
2840
2924
  }
2841
2925
  const excess = dueCount - this.healthyBacklog;
2842
- const pressure = excess / this.healthyBacklog * (MAX_BACKLOG_PRESSURE / 2);
2843
- return Math.min(MAX_BACKLOG_PRESSURE, pressure);
2926
+ const multiplier = 1 + excess / this.healthyBacklog * ((MAX_BACKLOG_MULTIPLIER - 1) / 2);
2927
+ return Math.min(MAX_BACKLOG_MULTIPLIER, multiplier);
2844
2928
  }
2845
2929
  /**
2846
2930
  * Compute urgency score for a review card.
@@ -2855,19 +2939,20 @@ var init_srs = __esm({
2855
2939
  * - 30 days (720h) → ~0.56
2856
2940
  * - 180 days → ~0.30
2857
2941
  *
2858
- * 3. Backlog pressure = global boost when review backlog exceeds healthy threshold
2859
- * - At healthy backlog: 0
2860
- * - At 2x healthy: +0.25
2861
- * - 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×).
2862
2944
  *
2863
- * Combined: base 0.5 + (urgency factors * 0.45) + backlog pressure
2864
- * Result range: 0.5 to 1.0 (uncapped to allow high-urgency reviews to compete with new cards)
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.
2865
2950
  *
2866
2951
  * @param review - The scheduled card to score
2867
2952
  * @param now - Current time
2868
- * @param backlogPressure - Pre-computed backlog pressure (0 to 0.5)
2953
+ * @param backlogMultiplier - Pre-computed backlog multiplier (1.0 to MAX_BACKLOG_MULTIPLIER)
2869
2954
  */
2870
- computeUrgencyScore(review, now, backlogPressure) {
2955
+ computeUrgencyScore(review, now, backlogMultiplier) {
2871
2956
  const scheduledAt = import_moment3.default.utc(review.scheduledAt);
2872
2957
  const due = import_moment3.default.utc(review.reviewTime);
2873
2958
  const intervalHours = Math.max(1, due.diff(scheduledAt, "hours"));
@@ -2877,15 +2962,15 @@ var init_srs = __esm({
2877
2962
  const overdueContribution = Math.min(1, Math.max(0, relativeOverdue));
2878
2963
  const urgency = overdueContribution * 0.5 + recencyFactor * 0.5;
2879
2964
  const baseScore = 0.5 + urgency * 0.45;
2880
- const score = Math.min(1, baseScore + backlogPressure);
2965
+ const score = baseScore * backlogMultiplier;
2881
2966
  const reasonParts = [
2882
2967
  `${Math.round(hoursOverdue)}h overdue`,
2883
2968
  `interval: ${Math.round(intervalHours)}h`,
2884
2969
  `relative: ${relativeOverdue.toFixed(2)}`,
2885
2970
  `recency: ${recencyFactor.toFixed(2)}`
2886
2971
  ];
2887
- if (backlogPressure > 0) {
2888
- reasonParts.push(`backlog: +${backlogPressure.toFixed(2)}`);
2972
+ if (backlogMultiplier > 1) {
2973
+ reasonParts.push(`backlog: \xD7${backlogMultiplier.toFixed(2)}`);
2889
2974
  }
2890
2975
  reasonParts.push("review");
2891
2976
  const reason = reasonParts.join(", ");
@@ -5183,6 +5268,7 @@ var init_3 = __esm({
5183
5268
  "./Pipeline.ts": () => Promise.resolve().then(() => (init_Pipeline(), Pipeline_exports)),
5184
5269
  "./PipelineAssembler.ts": () => Promise.resolve().then(() => (init_PipelineAssembler(), PipelineAssembler_exports)),
5185
5270
  "./PipelineDebugger.ts": () => Promise.resolve().then(() => (init_PipelineDebugger(), PipelineDebugger_exports)),
5271
+ "./SrsDebugger.ts": () => Promise.resolve().then(() => (init_SrsDebugger(), SrsDebugger_exports)),
5186
5272
  "./defaults.ts": () => Promise.resolve().then(() => (init_defaults(), defaults_exports)),
5187
5273
  "./diversityRerank.ts": () => Promise.resolve().then(() => (init_diversityRerank(), diversityRerank_exports)),
5188
5274
  "./filters/WeightedFilter.ts": () => Promise.resolve().then(() => (init_WeightedFilter(), WeightedFilter_exports)),
@@ -5215,12 +5301,14 @@ __export(navigators_exports, {
5215
5301
  NavigatorRole: () => NavigatorRole,
5216
5302
  NavigatorRoles: () => NavigatorRoles,
5217
5303
  Navigators: () => Navigators,
5304
+ clearSrsBacklogDebug: () => clearSrsBacklogDebug,
5218
5305
  diversityRerank: () => diversityRerank,
5219
5306
  getActivePipeline: () => getActivePipeline,
5220
5307
  getCardOrigin: () => getCardOrigin,
5221
5308
  getRegisteredNavigator: () => getRegisteredNavigator,
5222
5309
  getRegisteredNavigatorNames: () => getRegisteredNavigatorNames,
5223
5310
  getRegisteredNavigatorRole: () => getRegisteredNavigatorRole,
5311
+ getSrsBacklogDebug: () => getSrsBacklogDebug,
5224
5312
  hasRegisteredNavigator: () => hasRegisteredNavigator,
5225
5313
  initializeNavigatorRegistry: () => initializeNavigatorRegistry,
5226
5314
  isFilter: () => isFilter,
@@ -5302,6 +5390,7 @@ var init_navigators = __esm({
5302
5390
  "use strict";
5303
5391
  init_diversityRerank();
5304
5392
  init_PipelineDebugger();
5393
+ init_SrsDebugger();
5305
5394
  init_logger();
5306
5395
  init_();
5307
5396
  init_2();
@@ -8320,6 +8409,7 @@ __export(core_exports, {
8320
8409
  aggregateOutcomesForGradient: () => aggregateOutcomesForGradient,
8321
8410
  areQuestionRecords: () => areQuestionRecords,
8322
8411
  buildStrategyStateId: () => buildStrategyStateId,
8412
+ clearSrsBacklogDebug: () => clearSrsBacklogDebug,
8323
8413
  computeDeviation: () => computeDeviation,
8324
8414
  computeEffectiveWeight: () => computeEffectiveWeight,
8325
8415
  computeOutcomeSignal: () => computeOutcomeSignal,
@@ -8335,6 +8425,7 @@ __export(core_exports, {
8335
8425
  getRegisteredNavigator: () => getRegisteredNavigator,
8336
8426
  getRegisteredNavigatorNames: () => getRegisteredNavigatorNames,
8337
8427
  getRegisteredNavigatorRole: () => getRegisteredNavigatorRole,
8428
+ getSrsBacklogDebug: () => getSrsBacklogDebug,
8338
8429
  getStudySource: () => getStudySource,
8339
8430
  hasRegisteredNavigator: () => hasRegisteredNavigator,
8340
8431
  importParsedCards: () => importParsedCards,
@@ -8389,6 +8480,7 @@ init_core();
8389
8480
  aggregateOutcomesForGradient,
8390
8481
  areQuestionRecords,
8391
8482
  buildStrategyStateId,
8483
+ clearSrsBacklogDebug,
8392
8484
  computeDeviation,
8393
8485
  computeEffectiveWeight,
8394
8486
  computeOutcomeSignal,
@@ -8404,6 +8496,7 @@ init_core();
8404
8496
  getRegisteredNavigator,
8405
8497
  getRegisteredNavigatorNames,
8406
8498
  getRegisteredNavigatorRole,
8499
+ getSrsBacklogDebug,
8407
8500
  getStudySource,
8408
8501
  hasRegisteredNavigator,
8409
8502
  importParsedCards,