@vue-skuilder/db 0.2.16 → 0.2.17

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/index.d.cts CHANGED
@@ -3,8 +3,8 @@ export { K as ActivityRecord, A as AdminDBInterface, v as AssignedCard, g as Ass
3
3
  import { D as DataLayerProvider } from './dataLayerProvider-CpwpT1rM.cjs';
4
4
  import { C as CardHistory, c as CardRecord, d as QuestionRecord } from './types-legacy-4tlwHnXo.cjs';
5
5
  export { e as CardData, f as CourseListData, h as DataShapeData, g as DisplayableData, D as DocType, b as DocTypePrefixes, 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
- import { SrsBacklogDebug, Loggable } from './core/index.cjs';
7
- export { BulkCardProcessorConfig, CardFilter, CardFilterFactory, DIVERSITY_FLOOR, DIVERSITY_STRENGTH, DiversityRerankOptions, FilterContext, FilterImpact, GeneratorSummary, GradientObservation, GradientResult, ImportResult, PeriodUpdateInput, PeriodUpdateResult, PipelineForecaster, PipelineRunReport, SignalConfig, StrategyLearningState, StrategyStateDoc, StrategyStateId, 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 } from './core/index.cjs';
6
+ import { SrsBacklogDebug, StrategyPressureDebug, Loggable } from './core/index.cjs';
7
+ export { BulkCardProcessorConfig, CardFilter, CardFilterFactory, DIVERSITY_FLOOR, DIVERSITY_STRENGTH, DiversityRerankOptions, FilterContext, FilterImpact, GeneratorSummary, GradientObservation, GradientResult, ImportResult, PeriodUpdateInput, PeriodUpdateResult, PipelineForecaster, PipelineRunReport, PressureGaugeDebug, SignalConfig, StrategyLearningState, StrategyStateDoc, StrategyStateId, aggregateOutcomesForGradient, areQuestionRecords, buildStrategyStateId, captureStrategyPressure, clearSrsBacklogDebug, clearStrategyPressureDebug, computeOutcomeSignal, computeStrategyGradient, diversityRerank, docIsDeleted, getActivePipeline, getCardHistoryID, getDefaultLearnableWeight, getSrsBacklogDebug, getStrategyPressureDebug, importParsedCards, isQuestionRecord, mountPipelineDebugger, mountUserDBDebugger, parseCardHistoryID, pipelineDebugAPI, recordUserOutcome, runPeriodUpdate, scoreAccuracyInZone, updateLearningState, updateStrategyWeight, userDBDebugAPI, validateProcessorConfig } from './core/index.cjs';
8
8
  import { TaggedPerformance, TagFilter, DataShape, CourseConfig } from '@vue-skuilder/common';
9
9
  import { S as StaticCourseManifest } from './types-BFUa1pa3.cjs';
10
10
  export { A as AttachmentData, C as ChunkMetadata, D as DesignDocument, I as IndexMetadata, a as PackedCourseData, P as PackerConfig } from './types-BFUa1pa3.cjs';
@@ -365,6 +365,8 @@ interface SessionDebugSnapshot {
365
365
  failedQ: SessionQueueDebug;
366
366
  /** SRS backlog state per course (drives the "is review starvation permanent?" read). */
367
367
  reviewBacklog: SrsBacklogDebug[];
368
+ /** Per-strategy backpressure gauges (prescribed pressure, practice debt, ...). */
369
+ strategyPressure: StrategyPressureDebug[];
368
370
  /** Every card the learner has interacted with this session, draw order. */
369
371
  drawnCards: SessionDrawnCardDebug[];
370
372
  }
@@ -1521,4 +1523,4 @@ interface CouchDbUserDoc extends PouchDB.Authentication.User {
1521
1523
  entitlements: UserEntitlements;
1522
1524
  }
1523
1525
 
1524
- export { type ActiveSessionHandle, type AggregatedDocument, type AttachmentUploadResult, CardHistory, type CardPresentation, CardRecord, type CouchDbUserDoc, CourseDBInterface, CourseLookup, CourseRegistrationDoc, type CustomQuestionsData, DEFAULT_MIGRATION_OPTIONS, type DataLayerConfig, DataLayerProvider, type DocumentCounts, ENV, type Entitlement, FileSystemAdapter, GeneratorResult, Loggable, type MigrationOptions, type MigrationResult, type MixerCardInfo, type MixerRunReport, NOT_SET, type OutcomeObserver, type ProcessedDataShape, type ProcessedQuestionData, QuestionRecord, type QueueSnapshot, QuotaRoundRobinMixer, REVIEW_TIME_FORMAT, ReplanHints, type ReplanOptions, type ResponseResult, type RestoreProgress, type SessionAction, SessionController, type SessionControls, type SessionDebugSnapshot, type SessionDebugTarget, type SessionDrawnCardDebug, type SessionOutcome, type SessionQueueDebug, type SessionQueueItemDebug, type SessionRunReport, type SourceBatch, type SourceMixer, type SourceSelectionBreakdown, type SourceSummary, SrsBacklogDebug, StaticCourseManifest, type StaticCourseValidation, StaticToCouchDBMigrator, StudyContentSource, StudySessionItem, type StudySessionRecord, TagFilteredContentSource, type UserAccountStatus, UserDBInterface, type UserEntitlements, type ValidationIssue, type ValidationResult, WeightedCard, _resetDataLayer, captureMixerRun, clearStaleSessionDebugState, endSessionTracking, ensureAppDataDirectory, getActiveController, getAppDataDirectory, getDataLayer, getDbPath, initializeDataDirectory, initializeDataLayer, isDataShapeRegistered, isDataShapeSchemaAvailable, isQuestionTypeRegistered, makeScheduledCardId, mixerDebugAPI, mountMixerDebugger, mountSessionDebugger, processCustomQuestionsData, recordCardPresentation, registerActiveController, registerBlanksCard, registerCustomQuestionTypes, registerDataShape, registerQuestionType, registerSeedData, removeCustomQuestionTypes, removeDataShape, removeQuestionType, sessionDebugAPI, snapshotQueues, startSessionTracking, toggleSessionOverlay, validateMigration, validateStaticCourse };
1526
+ export { type ActiveSessionHandle, type AggregatedDocument, type AttachmentUploadResult, CardHistory, type CardPresentation, CardRecord, type CouchDbUserDoc, CourseDBInterface, CourseLookup, CourseRegistrationDoc, type CustomQuestionsData, DEFAULT_MIGRATION_OPTIONS, type DataLayerConfig, DataLayerProvider, type DocumentCounts, ENV, type Entitlement, FileSystemAdapter, GeneratorResult, Loggable, type MigrationOptions, type MigrationResult, type MixerCardInfo, type MixerRunReport, NOT_SET, type OutcomeObserver, type ProcessedDataShape, type ProcessedQuestionData, QuestionRecord, type QueueSnapshot, QuotaRoundRobinMixer, REVIEW_TIME_FORMAT, ReplanHints, type ReplanOptions, type ResponseResult, type RestoreProgress, type SessionAction, SessionController, type SessionControls, type SessionDebugSnapshot, type SessionDebugTarget, type SessionDrawnCardDebug, type SessionOutcome, type SessionQueueDebug, type SessionQueueItemDebug, type SessionRunReport, type SourceBatch, type SourceMixer, type SourceSelectionBreakdown, type SourceSummary, SrsBacklogDebug, StaticCourseManifest, type StaticCourseValidation, StaticToCouchDBMigrator, StrategyPressureDebug, StudyContentSource, StudySessionItem, type StudySessionRecord, TagFilteredContentSource, type UserAccountStatus, UserDBInterface, type UserEntitlements, type ValidationIssue, type ValidationResult, WeightedCard, _resetDataLayer, captureMixerRun, clearStaleSessionDebugState, endSessionTracking, ensureAppDataDirectory, getActiveController, getAppDataDirectory, getDataLayer, getDbPath, initializeDataDirectory, initializeDataLayer, isDataShapeRegistered, isDataShapeSchemaAvailable, isQuestionTypeRegistered, makeScheduledCardId, mixerDebugAPI, mountMixerDebugger, mountSessionDebugger, processCustomQuestionsData, recordCardPresentation, registerActiveController, registerBlanksCard, registerCustomQuestionTypes, registerDataShape, registerQuestionType, registerSeedData, removeCustomQuestionTypes, removeDataShape, removeQuestionType, sessionDebugAPI, snapshotQueues, startSessionTracking, toggleSessionOverlay, validateMigration, validateStaticCourse };
package/dist/index.d.ts CHANGED
@@ -3,8 +3,8 @@ export { K as ActivityRecord, A as AdminDBInterface, v as AssignedCard, g as Ass
3
3
  import { D as DataLayerProvider } from './dataLayerProvider-BWayUIoK.js';
4
4
  import { C as CardHistory, c as CardRecord, d as QuestionRecord } from './types-legacy-4tlwHnXo.js';
5
5
  export { e as CardData, f as CourseListData, h as DataShapeData, g as DisplayableData, D as DocType, b as DocTypePrefixes, 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
- import { SrsBacklogDebug, Loggable } from './core/index.js';
7
- export { BulkCardProcessorConfig, CardFilter, CardFilterFactory, DIVERSITY_FLOOR, DIVERSITY_STRENGTH, DiversityRerankOptions, FilterContext, FilterImpact, GeneratorSummary, GradientObservation, GradientResult, ImportResult, PeriodUpdateInput, PeriodUpdateResult, PipelineForecaster, PipelineRunReport, SignalConfig, StrategyLearningState, StrategyStateDoc, StrategyStateId, 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 } from './core/index.js';
6
+ import { SrsBacklogDebug, StrategyPressureDebug, Loggable } from './core/index.js';
7
+ export { BulkCardProcessorConfig, CardFilter, CardFilterFactory, DIVERSITY_FLOOR, DIVERSITY_STRENGTH, DiversityRerankOptions, FilterContext, FilterImpact, GeneratorSummary, GradientObservation, GradientResult, ImportResult, PeriodUpdateInput, PeriodUpdateResult, PipelineForecaster, PipelineRunReport, PressureGaugeDebug, SignalConfig, StrategyLearningState, StrategyStateDoc, StrategyStateId, aggregateOutcomesForGradient, areQuestionRecords, buildStrategyStateId, captureStrategyPressure, clearSrsBacklogDebug, clearStrategyPressureDebug, computeOutcomeSignal, computeStrategyGradient, diversityRerank, docIsDeleted, getActivePipeline, getCardHistoryID, getDefaultLearnableWeight, getSrsBacklogDebug, getStrategyPressureDebug, importParsedCards, isQuestionRecord, mountPipelineDebugger, mountUserDBDebugger, parseCardHistoryID, pipelineDebugAPI, recordUserOutcome, runPeriodUpdate, scoreAccuracyInZone, updateLearningState, updateStrategyWeight, userDBDebugAPI, validateProcessorConfig } from './core/index.js';
8
8
  import { TaggedPerformance, TagFilter, DataShape, CourseConfig } from '@vue-skuilder/common';
9
9
  import { S as StaticCourseManifest } from './types-CHgpWQAY.js';
10
10
  export { A as AttachmentData, C as ChunkMetadata, D as DesignDocument, I as IndexMetadata, a as PackedCourseData, P as PackerConfig } from './types-CHgpWQAY.js';
@@ -365,6 +365,8 @@ interface SessionDebugSnapshot {
365
365
  failedQ: SessionQueueDebug;
366
366
  /** SRS backlog state per course (drives the "is review starvation permanent?" read). */
367
367
  reviewBacklog: SrsBacklogDebug[];
368
+ /** Per-strategy backpressure gauges (prescribed pressure, practice debt, ...). */
369
+ strategyPressure: StrategyPressureDebug[];
368
370
  /** Every card the learner has interacted with this session, draw order. */
369
371
  drawnCards: SessionDrawnCardDebug[];
370
372
  }
@@ -1521,4 +1523,4 @@ interface CouchDbUserDoc extends PouchDB.Authentication.User {
1521
1523
  entitlements: UserEntitlements;
1522
1524
  }
1523
1525
 
1524
- export { type ActiveSessionHandle, type AggregatedDocument, type AttachmentUploadResult, CardHistory, type CardPresentation, CardRecord, type CouchDbUserDoc, CourseDBInterface, CourseLookup, CourseRegistrationDoc, type CustomQuestionsData, DEFAULT_MIGRATION_OPTIONS, type DataLayerConfig, DataLayerProvider, type DocumentCounts, ENV, type Entitlement, FileSystemAdapter, GeneratorResult, Loggable, type MigrationOptions, type MigrationResult, type MixerCardInfo, type MixerRunReport, NOT_SET, type OutcomeObserver, type ProcessedDataShape, type ProcessedQuestionData, QuestionRecord, type QueueSnapshot, QuotaRoundRobinMixer, REVIEW_TIME_FORMAT, ReplanHints, type ReplanOptions, type ResponseResult, type RestoreProgress, type SessionAction, SessionController, type SessionControls, type SessionDebugSnapshot, type SessionDebugTarget, type SessionDrawnCardDebug, type SessionOutcome, type SessionQueueDebug, type SessionQueueItemDebug, type SessionRunReport, type SourceBatch, type SourceMixer, type SourceSelectionBreakdown, type SourceSummary, SrsBacklogDebug, StaticCourseManifest, type StaticCourseValidation, StaticToCouchDBMigrator, StudyContentSource, StudySessionItem, type StudySessionRecord, TagFilteredContentSource, type UserAccountStatus, UserDBInterface, type UserEntitlements, type ValidationIssue, type ValidationResult, WeightedCard, _resetDataLayer, captureMixerRun, clearStaleSessionDebugState, endSessionTracking, ensureAppDataDirectory, getActiveController, getAppDataDirectory, getDataLayer, getDbPath, initializeDataDirectory, initializeDataLayer, isDataShapeRegistered, isDataShapeSchemaAvailable, isQuestionTypeRegistered, makeScheduledCardId, mixerDebugAPI, mountMixerDebugger, mountSessionDebugger, processCustomQuestionsData, recordCardPresentation, registerActiveController, registerBlanksCard, registerCustomQuestionTypes, registerDataShape, registerQuestionType, registerSeedData, removeCustomQuestionTypes, removeDataShape, removeQuestionType, sessionDebugAPI, snapshotQueues, startSessionTracking, toggleSessionOverlay, validateMigration, validateStaticCourse };
1526
+ export { type ActiveSessionHandle, type AggregatedDocument, type AttachmentUploadResult, CardHistory, type CardPresentation, CardRecord, type CouchDbUserDoc, CourseDBInterface, CourseLookup, CourseRegistrationDoc, type CustomQuestionsData, DEFAULT_MIGRATION_OPTIONS, type DataLayerConfig, DataLayerProvider, type DocumentCounts, ENV, type Entitlement, FileSystemAdapter, GeneratorResult, Loggable, type MigrationOptions, type MigrationResult, type MixerCardInfo, type MixerRunReport, NOT_SET, type OutcomeObserver, type ProcessedDataShape, type ProcessedQuestionData, QuestionRecord, type QueueSnapshot, QuotaRoundRobinMixer, REVIEW_TIME_FORMAT, ReplanHints, type ReplanOptions, type ResponseResult, type RestoreProgress, type SessionAction, SessionController, type SessionControls, type SessionDebugSnapshot, type SessionDebugTarget, type SessionDrawnCardDebug, type SessionOutcome, type SessionQueueDebug, type SessionQueueItemDebug, type SessionRunReport, type SourceBatch, type SourceMixer, type SourceSelectionBreakdown, type SourceSummary, SrsBacklogDebug, StaticCourseManifest, type StaticCourseValidation, StaticToCouchDBMigrator, StrategyPressureDebug, StudyContentSource, StudySessionItem, type StudySessionRecord, TagFilteredContentSource, type UserAccountStatus, UserDBInterface, type UserEntitlements, type ValidationIssue, type ValidationResult, WeightedCard, _resetDataLayer, captureMixerRun, clearStaleSessionDebugState, endSessionTracking, ensureAppDataDirectory, getActiveController, getAppDataDirectory, getDataLayer, getDbPath, initializeDataDirectory, initializeDataLayer, isDataShapeRegistered, isDataShapeSchemaAvailable, isQuestionTypeRegistered, makeScheduledCardId, mixerDebugAPI, mountMixerDebugger, mountSessionDebugger, processCustomQuestionsData, recordCardPresentation, registerActiveController, registerBlanksCard, registerCustomQuestionTypes, registerDataShape, registerQuestionType, registerSeedData, removeCustomQuestionTypes, removeDataShape, removeQuestionType, sessionDebugAPI, snapshotQueues, startSessionTracking, toggleSessionOverlay, validateMigration, validateStaticCourse };
package/dist/index.js CHANGED
@@ -1820,6 +1820,30 @@ var init_SrsDebugger = __esm({
1820
1820
  }
1821
1821
  });
1822
1822
 
1823
+ // src/core/navigators/StrategyPressureDebugger.ts
1824
+ var StrategyPressureDebugger_exports = {};
1825
+ __export(StrategyPressureDebugger_exports, {
1826
+ captureStrategyPressure: () => captureStrategyPressure,
1827
+ clearStrategyPressureDebug: () => clearStrategyPressureDebug,
1828
+ getStrategyPressureDebug: () => getStrategyPressureDebug
1829
+ });
1830
+ function captureStrategyPressure(snapshot) {
1831
+ snapshots2.set(`${snapshot.source}:${snapshot.courseId}`, snapshot);
1832
+ }
1833
+ function getStrategyPressureDebug() {
1834
+ return [...snapshots2.values()].sort((a, b) => b.timestamp - a.timestamp);
1835
+ }
1836
+ function clearStrategyPressureDebug() {
1837
+ snapshots2.clear();
1838
+ }
1839
+ var snapshots2;
1840
+ var init_StrategyPressureDebugger = __esm({
1841
+ "src/core/navigators/StrategyPressureDebugger.ts"() {
1842
+ "use strict";
1843
+ snapshots2 = /* @__PURE__ */ new Map();
1844
+ }
1845
+ });
1846
+
1823
1847
  // src/core/navigators/generators/CompositeGenerator.ts
1824
1848
  var CompositeGenerator_exports = {};
1825
1849
  __export(CompositeGenerator_exports, {
@@ -2184,6 +2208,13 @@ function shuffleInPlace(arr) {
2184
2208
  [arr[i], arr[j]] = [arr[j], arr[i]];
2185
2209
  }
2186
2210
  }
2211
+ function formatAge(ms) {
2212
+ const minutes = Math.max(0, Math.round(ms / 6e4));
2213
+ if (minutes < 60) return `${minutes}m`;
2214
+ const hours = Math.round(minutes / 60);
2215
+ if (hours < 24) return `${hours}h`;
2216
+ return `${Math.round(hours / 24)}d`;
2217
+ }
2187
2218
  function pickTopByScore(cards, limit) {
2188
2219
  return [...cards].sort((a, b) => b.score - a.score || a.cardId.localeCompare(b.cardId)).slice(0, limit);
2189
2220
  }
@@ -2192,6 +2223,7 @@ var init_prescribed = __esm({
2192
2223
  "src/core/navigators/generators/prescribed.ts"() {
2193
2224
  "use strict";
2194
2225
  init_navigators();
2226
+ init_StrategyPressureDebugger();
2195
2227
  init_logger();
2196
2228
  DEFAULT_FRESHNESS_WINDOW = 3;
2197
2229
  DEFAULT_MAX_DIRECT_PER_RUN = 3;
@@ -2268,6 +2300,7 @@ var init_prescribed = __esm({
2268
2300
  const groupRuntimes = [];
2269
2301
  const priorPracticeDebt = progress.practiceDebt ?? {};
2270
2302
  const nextPracticeDebt = {};
2303
+ const practiceDebtsByGroup = /* @__PURE__ */ new Map();
2271
2304
  for (const group of this.config.groups) {
2272
2305
  const runtime = this.buildGroupRuntimeState({
2273
2306
  group,
@@ -2316,7 +2349,7 @@ var init_prescribed = __esm({
2316
2349
  courseId,
2317
2350
  emittedIds
2318
2351
  );
2319
- const practiceCards = this.buildPracticeCards({
2352
+ const practice = this.buildPracticeCards({
2320
2353
  group,
2321
2354
  courseId,
2322
2355
  emittedIds,
@@ -2329,7 +2362,8 @@ var init_prescribed = __esm({
2329
2362
  priorPracticeDebt,
2330
2363
  nextPracticeDebt
2331
2364
  });
2332
- emitted.push(...directCards, ...supportCards, ...discoveredSupportCards, ...practiceCards);
2365
+ practiceDebtsByGroup.set(group.id, practice.debts);
2366
+ emitted.push(...directCards, ...supportCards, ...discoveredSupportCards, ...practice.cards);
2333
2367
  }
2334
2368
  nextState.practiceDebt = nextPracticeDebt;
2335
2369
  const hintSummary = this.buildSupportHintSummary(groupRuntimes);
@@ -2345,6 +2379,7 @@ var init_prescribed = __esm({
2345
2379
  } else {
2346
2380
  logger.info("[Prescribed] No hints to emit (no blocked targets or no support tags)");
2347
2381
  }
2382
+ this.capturePressureSnapshot(courseId, groupRuntimes, practiceDebtsByGroup, emitted, hints);
2348
2383
  if (emitted.length === 0) {
2349
2384
  logger.info(
2350
2385
  "[Prescribed] 0 cards emitted (all targets blocked, authored/discovered support candidates exhausted)" + (hints ? " \u2014 boost hints emitted but may not survive filters" : "")
@@ -2406,6 +2441,60 @@ var init_prescribed = __esm({
2406
2441
  supportTags: [...supportTags].sort()
2407
2442
  };
2408
2443
  }
2444
+ /**
2445
+ * Translate this run's per-group runtimes and practice debts into gauges on
2446
+ * the generic strategy-pressure debug channel (rendered by the live session
2447
+ * overlay's "strategy backpressure" section).
2448
+ */
2449
+ capturePressureSnapshot(courseId, groupRuntimes, practiceDebtsByGroup, emitted, hints) {
2450
+ const now = Date.now();
2451
+ const gauges = [];
2452
+ for (const runtime of groupRuntimes) {
2453
+ const group = runtime.group;
2454
+ const window2 = group.freshnessWindowSessions ?? DEFAULT_FRESHNESS_WINDOW;
2455
+ gauges.push({
2456
+ id: `group:${group.id}:target`,
2457
+ label: `${group.id} targets`,
2458
+ multiplier: runtime.pressureMultiplier,
2459
+ max: MAX_TARGET_MULTIPLIER,
2460
+ detail: `${runtime.pendingTargets.length} pending (${runtime.surfaceableTargets.length} surfaceable, ${runtime.blockedTargets.length} blocked) \xB7 sinceSurfaced ${runtime.sessionsSinceSurfaced}/${window2}`,
2461
+ items: runtime.blockedTargets.map((cardId) => ({ label: cardId, value: "blocked" }))
2462
+ });
2463
+ if (runtime.blockedTargets.length > 0) {
2464
+ const mode = runtime.supportCandidates.length > 0 ? "direct-support" : runtime.discoveredSupportCandidates.length > 0 ? "inserted-support" : "boost-only";
2465
+ gauges.push({
2466
+ id: `group:${group.id}:support`,
2467
+ label: `${group.id} support`,
2468
+ multiplier: runtime.supportMultiplier,
2469
+ max: MAX_SUPPORT_MULTIPLIER,
2470
+ detail: `mode=${mode} \xB7 ${runtime.supportTags.length} support tag(s)`,
2471
+ items: runtime.supportTags.map((tag) => ({ label: tag }))
2472
+ });
2473
+ }
2474
+ const debts = practiceDebtsByGroup.get(group.id) ?? [];
2475
+ if (debts.length > 0) {
2476
+ gauges.push({
2477
+ id: `group:${group.id}:practice-debt`,
2478
+ label: `${group.id} practice debt`,
2479
+ multiplier: Math.max(...debts.map((d) => d.multiplier)),
2480
+ max: MAX_PRACTICE_MULTIPLIER,
2481
+ detail: `${debts.length} under-practiced skill(s)`,
2482
+ items: debts.map((d) => ({
2483
+ label: d.tag,
2484
+ value: `\xD7${d.multiplier.toFixed(2)} \xB7 open ${formatAge(now - new Date(d.firstOwedAt).getTime())}`
2485
+ }))
2486
+ });
2487
+ }
2488
+ }
2489
+ captureStrategyPressure({
2490
+ source: "prescribed",
2491
+ courseId,
2492
+ gauges,
2493
+ topScore: emitted.length > 0 ? Math.max(...emitted.map((c) => c.score)) : null,
2494
+ hintsLabel: hints?._label,
2495
+ timestamp: now
2496
+ });
2497
+ }
2409
2498
  parseConfig(serializedData) {
2410
2499
  try {
2411
2500
  const parsed = JSON.parse(serializedData);
@@ -2562,6 +2651,7 @@ var init_prescribed = __esm({
2562
2651
  supportTags: [...supportTags],
2563
2652
  pressureMultiplier,
2564
2653
  supportMultiplier,
2654
+ sessionsSinceSurfaced,
2565
2655
  debugVersion: PRESCRIBED_DEBUG_VERSION
2566
2656
  };
2567
2657
  }
@@ -2694,13 +2784,13 @@ var init_prescribed = __esm({
2694
2784
  nextPracticeDebt
2695
2785
  } = args;
2696
2786
  const patterns = group.practiceTagPatterns ?? [];
2697
- if (patterns.length === 0) return [];
2787
+ if (patterns.length === 0) return { cards: [], debts: [] };
2698
2788
  const practiceMinCount = group.practiceMinCount ?? DEFAULT_PRACTICE_MIN_COUNT;
2699
2789
  const maxPractice = group.maxPracticeCardsPerRun ?? DEFAULT_MAX_PRACTICE_PER_RUN;
2700
2790
  const practiceTags = [...cardsByTag.keys()].filter(
2701
2791
  (tag) => patterns.some((p) => matchesTagPattern(tag, p)) && this.isUnlockedGatedSkill(tag, hierarchyConfigs, userTagElo, userGlobalElo) && (userTagElo[tag]?.count ?? 0) < practiceMinCount
2702
2792
  );
2703
- if (practiceTags.length === 0) return [];
2793
+ if (practiceTags.length === 0) return { cards: [], debts: [] };
2704
2794
  const now = Date.now();
2705
2795
  const DAY_MS = 24 * 60 * 60 * 1e3;
2706
2796
  const tagMultiplier = /* @__PURE__ */ new Map();
@@ -2715,6 +2805,11 @@ var init_prescribed = __esm({
2715
2805
  );
2716
2806
  tagMultiplier.set(tag, mult);
2717
2807
  }
2808
+ const debts = practiceTags.map((tag) => ({
2809
+ tag,
2810
+ multiplier: tagMultiplier.get(tag) ?? PRACTICE_BASE_MULT,
2811
+ firstOwedAt: nextPracticeDebt[tag]
2812
+ })).sort((a, b) => b.multiplier - a.multiplier || a.tag.localeCompare(b.tag));
2718
2813
  const practiceCardIds = this.findDiscoveredSupportCards({
2719
2814
  supportTags: practiceTags,
2720
2815
  cardsByTag,
@@ -2723,7 +2818,7 @@ var init_prescribed = __esm({
2723
2818
  excludedIds: emittedIds,
2724
2819
  limit: maxPractice
2725
2820
  });
2726
- if (practiceCardIds.length === 0) return [];
2821
+ if (practiceCardIds.length === 0) return { cards: [], debts };
2727
2822
  logger.info(
2728
2823
  `[Prescribed] Group '${group.id}' practice: ${practiceTags.length} unlocked under-practiced skill(s), emitting ${practiceCardIds.length} drill card(s)`
2729
2824
  );
@@ -2753,7 +2848,7 @@ var init_prescribed = __esm({
2753
2848
  ]
2754
2849
  });
2755
2850
  }
2756
- return cards;
2851
+ return { cards, debts };
2757
2852
  }
2758
2853
  /**
2759
2854
  * True for a skill that was *gated and is now reached*: it has at least one
@@ -5430,6 +5525,7 @@ var init_3 = __esm({
5430
5525
  "./PipelineAssembler.ts": () => Promise.resolve().then(() => (init_PipelineAssembler(), PipelineAssembler_exports)),
5431
5526
  "./PipelineDebugger.ts": () => Promise.resolve().then(() => (init_PipelineDebugger(), PipelineDebugger_exports)),
5432
5527
  "./SrsDebugger.ts": () => Promise.resolve().then(() => (init_SrsDebugger(), SrsDebugger_exports)),
5528
+ "./StrategyPressureDebugger.ts": () => Promise.resolve().then(() => (init_StrategyPressureDebugger(), StrategyPressureDebugger_exports)),
5433
5529
  "./defaults.ts": () => Promise.resolve().then(() => (init_defaults(), defaults_exports)),
5434
5530
  "./diversityRerank.ts": () => Promise.resolve().then(() => (init_diversityRerank(), diversityRerank_exports)),
5435
5531
  "./filters/WeightedFilter.ts": () => Promise.resolve().then(() => (init_WeightedFilter(), WeightedFilter_exports)),
@@ -5462,7 +5558,9 @@ __export(navigators_exports, {
5462
5558
  NavigatorRole: () => NavigatorRole,
5463
5559
  NavigatorRoles: () => NavigatorRoles,
5464
5560
  Navigators: () => Navigators,
5561
+ captureStrategyPressure: () => captureStrategyPressure,
5465
5562
  clearSrsBacklogDebug: () => clearSrsBacklogDebug,
5563
+ clearStrategyPressureDebug: () => clearStrategyPressureDebug,
5466
5564
  diversityRerank: () => diversityRerank,
5467
5565
  getActivePipeline: () => getActivePipeline,
5468
5566
  getCardOrigin: () => getCardOrigin,
@@ -5470,6 +5568,7 @@ __export(navigators_exports, {
5470
5568
  getRegisteredNavigatorNames: () => getRegisteredNavigatorNames,
5471
5569
  getRegisteredNavigatorRole: () => getRegisteredNavigatorRole,
5472
5570
  getSrsBacklogDebug: () => getSrsBacklogDebug,
5571
+ getStrategyPressureDebug: () => getStrategyPressureDebug,
5473
5572
  hasRegisteredNavigator: () => hasRegisteredNavigator,
5474
5573
  initializeNavigatorRegistry: () => initializeNavigatorRegistry,
5475
5574
  isFilter: () => isFilter,
@@ -5552,6 +5651,7 @@ var init_navigators = __esm({
5552
5651
  init_diversityRerank();
5553
5652
  init_PipelineDebugger();
5554
5653
  init_SrsDebugger();
5654
+ init_StrategyPressureDebugger();
5555
5655
  init_logger();
5556
5656
  init_();
5557
5657
  init_2();
@@ -10589,8 +10689,10 @@ __export(index_exports, {
10589
10689
  areQuestionRecords: () => areQuestionRecords,
10590
10690
  buildStrategyStateId: () => buildStrategyStateId,
10591
10691
  captureMixerRun: () => captureMixerRun,
10692
+ captureStrategyPressure: () => captureStrategyPressure,
10592
10693
  clearSrsBacklogDebug: () => clearSrsBacklogDebug,
10593
10694
  clearStaleSessionDebugState: () => clearStaleSessionDebugState,
10695
+ clearStrategyPressureDebug: () => clearStrategyPressureDebug,
10594
10696
  computeDeviation: () => computeDeviation,
10595
10697
  computeEffectiveWeight: () => computeEffectiveWeight,
10596
10698
  computeOutcomeSignal: () => computeOutcomeSignal,
@@ -10613,6 +10715,7 @@ __export(index_exports, {
10613
10715
  getRegisteredNavigatorNames: () => getRegisteredNavigatorNames,
10614
10716
  getRegisteredNavigatorRole: () => getRegisteredNavigatorRole,
10615
10717
  getSrsBacklogDebug: () => getSrsBacklogDebug,
10718
+ getStrategyPressureDebug: () => getStrategyPressureDebug,
10616
10719
  getStudySource: () => getStudySource,
10617
10720
  hasRegisteredNavigator: () => hasRegisteredNavigator,
10618
10721
  importParsedCards: () => importParsedCards,
@@ -13585,6 +13688,7 @@ mountMixerDebugger();
13585
13688
  init_logger();
13586
13689
  init_PipelineDebugger();
13587
13690
  init_SrsDebugger();
13691
+ init_StrategyPressureDebugger();
13588
13692
 
13589
13693
  // src/study/SessionOverlay.ts
13590
13694
  init_logger();
@@ -13675,7 +13779,7 @@ function render() {
13675
13779
  attachHandlers();
13676
13780
  return;
13677
13781
  }
13678
- overlayEl.innerHTML = headerHtml() + replanHtml(s) + metaHtml(s) + hintsHtml(s.sessionHints) + backlogHtml(s.reviewBacklog) + queueHtml("supplyQ", "supplyQ", s.supplyQ) + queueHtml("failedQ", "failedQ", s.failedQ) + drawnHtml("drawn", s.drawnCards);
13782
+ overlayEl.innerHTML = headerHtml() + replanHtml(s) + metaHtml(s) + hintsHtml(s.sessionHints) + backlogHtml(s.reviewBacklog) + strategyPressureHtml(s.strategyPressure) + queueHtml("supplyQ", "supplyQ", s.supplyQ) + queueHtml("failedQ", "failedQ", s.failedQ) + drawnHtml("drawn", s.drawnCards);
13679
13783
  attachHandlers();
13680
13784
  }
13681
13785
  function attachHandlers() {
@@ -13779,6 +13883,46 @@ function backlogHtml(backlog) {
13779
13883
  }).join("");
13780
13884
  return `<div style="margin-bottom:6px"><div style="color:#93c5fd">review backpressure</div>${rows}</div>`;
13781
13885
  }
13886
+ function pressureColor(multiplier, max) {
13887
+ if (multiplier <= 1.001) return "#86efac";
13888
+ if (max !== void 0 && max > 1) {
13889
+ return (multiplier - 1) / (max - 1) >= 0.75 ? "#fca5a5" : "#fcd34d";
13890
+ }
13891
+ return multiplier >= 3 ? "#fca5a5" : "#fcd34d";
13892
+ }
13893
+ function toggleKey(raw) {
13894
+ return raw.replace(/[^\w:.|-]/g, "_");
13895
+ }
13896
+ function gaugeHtml(sectionKey, g) {
13897
+ const cap = g.max !== void 0 ? `<span style="opacity:.5">/${g.max}</span>` : "";
13898
+ const detail = g.detail ? ` <span style="opacity:.6">${esc(g.detail)}</span>` : "";
13899
+ let html = `<div style="margin-left:6px">${esc(g.label)} <span style="color:${pressureColor(g.multiplier, g.max)}">\xD7${g.multiplier.toFixed(2)}</span>${cap}${detail}`;
13900
+ const items = g.items ?? [];
13901
+ if (items.length > 0) {
13902
+ const key = toggleKey(`sp|${sectionKey}|${g.id}`);
13903
+ const isOpen = !!expanded[key];
13904
+ const shown = isOpen ? items : items.slice(0, INLINE_THRESHOLD);
13905
+ html += `<div style="margin-left:6px">` + shown.map(
13906
+ (it) => `<div style="white-space:nowrap;opacity:.75">${esc(it.label)}${it.value ? ` <span style="opacity:.6">${esc(it.value)}</span>` : ""}</div>`
13907
+ ).join("");
13908
+ if (items.length > INLINE_THRESHOLD) {
13909
+ const footer = isOpen ? "\u25BE show less" : `\u2026 +${items.length - shown.length} more`;
13910
+ html += `<div data-q="${key}" style="cursor:pointer;opacity:.6">${footer}</div>`;
13911
+ }
13912
+ html += `</div>`;
13913
+ }
13914
+ return html + `</div>`;
13915
+ }
13916
+ function strategyPressureHtml(list) {
13917
+ if (!list.length) return "";
13918
+ const sections = list.map((s) => {
13919
+ const sectionKey = `${s.source}:${s.courseId.slice(0, 8)}`;
13920
+ const top = s.topScore !== null && s.topScore !== void 0 ? s.topScore.toFixed(2) : "\u2014";
13921
+ const hints = s.hintsLabel ? `<div style="margin-left:6px;opacity:.6">hints: ${esc(s.hintsLabel)}</div>` : "";
13922
+ return `<div style="margin-left:6px"><span style="opacity:.7">${esc(s.source)} \xB7 ${esc(s.courseId.slice(0, 8))}</span> <span style="opacity:.6">top score ${top}</span>` + s.gauges.map((g) => gaugeHtml(sectionKey, g)).join("") + hints + `</div>`;
13923
+ }).join("");
13924
+ return `<div style="margin-bottom:6px"><div style="color:#fdba74">strategy backpressure</div>${sections}</div>`;
13925
+ }
13782
13926
  function fmtScore(score) {
13783
13927
  if (score === void 0) return "";
13784
13928
  if (!Number.isFinite(score)) return "REQ";
@@ -13879,6 +14023,24 @@ function snapshotToText(s) {
13879
14023
  );
13880
14024
  }
13881
14025
  }
14026
+ if (s.strategyPressure.length) {
14027
+ lines.push("");
14028
+ lines.push("strategy backpressure:");
14029
+ for (const sp of s.strategyPressure) {
14030
+ const top = sp.topScore !== null && sp.topScore !== void 0 ? sp.topScore.toFixed(2) : "\u2014";
14031
+ lines.push(` ${sp.source} (${sp.courseId.slice(0, 8)}): top score ${top}`);
14032
+ for (const g of sp.gauges) {
14033
+ const cap = g.max !== void 0 ? `/${g.max}` : "";
14034
+ lines.push(
14035
+ ` ${g.label} \xD7${g.multiplier.toFixed(2)}${cap}${g.detail ? ` \u2014 ${g.detail}` : ""}`
14036
+ );
14037
+ for (const it of g.items ?? []) {
14038
+ lines.push(` ${it.label}${it.value ? ` (${it.value})` : ""}`);
14039
+ }
14040
+ }
14041
+ if (sp.hintsLabel) lines.push(` hints: ${sp.hintsLabel}`);
14042
+ }
14043
+ }
13882
14044
  const queueText = (label, q) => {
13883
14045
  lines.push("");
13884
14046
  lines.push(`${label}: ${q.length} (drawn ${q.dequeueCount})`);
@@ -13916,6 +14078,7 @@ var MAX_HISTORY = 5;
13916
14078
  function clearStaleSessionDebugState() {
13917
14079
  clearRunHistory();
13918
14080
  clearSrsBacklogDebug();
14081
+ clearStrategyPressureDebug();
13919
14082
  }
13920
14083
  function startSessionTracking(supplyQLength, failedQLength) {
13921
14084
  const sessionId = `session-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
@@ -14604,6 +14767,7 @@ var SessionController = class _SessionController extends Loggable {
14604
14767
  supplyQ: describe(this.supplyQ),
14605
14768
  failedQ: describe(this.failedQ),
14606
14769
  reviewBacklog: getSrsBacklogDebug(),
14770
+ strategyPressure: getStrategyPressureDebug(),
14607
14771
  drawnCards
14608
14772
  };
14609
14773
  }
@@ -15305,8 +15469,10 @@ init_userDBHelpers();
15305
15469
  areQuestionRecords,
15306
15470
  buildStrategyStateId,
15307
15471
  captureMixerRun,
15472
+ captureStrategyPressure,
15308
15473
  clearSrsBacklogDebug,
15309
15474
  clearStaleSessionDebugState,
15475
+ clearStrategyPressureDebug,
15310
15476
  computeDeviation,
15311
15477
  computeEffectiveWeight,
15312
15478
  computeOutcomeSignal,
@@ -15329,6 +15495,7 @@ init_userDBHelpers();
15329
15495
  getRegisteredNavigatorNames,
15330
15496
  getRegisteredNavigatorRole,
15331
15497
  getSrsBacklogDebug,
15498
+ getStrategyPressureDebug,
15332
15499
  getStudySource,
15333
15500
  hasRegisteredNavigator,
15334
15501
  importParsedCards,