@vue-skuilder/db 0.2.15 → 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.mjs CHANGED
@@ -1797,6 +1797,30 @@ var init_SrsDebugger = __esm({
1797
1797
  }
1798
1798
  });
1799
1799
 
1800
+ // src/core/navigators/StrategyPressureDebugger.ts
1801
+ var StrategyPressureDebugger_exports = {};
1802
+ __export(StrategyPressureDebugger_exports, {
1803
+ captureStrategyPressure: () => captureStrategyPressure,
1804
+ clearStrategyPressureDebug: () => clearStrategyPressureDebug,
1805
+ getStrategyPressureDebug: () => getStrategyPressureDebug
1806
+ });
1807
+ function captureStrategyPressure(snapshot) {
1808
+ snapshots2.set(`${snapshot.source}:${snapshot.courseId}`, snapshot);
1809
+ }
1810
+ function getStrategyPressureDebug() {
1811
+ return [...snapshots2.values()].sort((a, b) => b.timestamp - a.timestamp);
1812
+ }
1813
+ function clearStrategyPressureDebug() {
1814
+ snapshots2.clear();
1815
+ }
1816
+ var snapshots2;
1817
+ var init_StrategyPressureDebugger = __esm({
1818
+ "src/core/navigators/StrategyPressureDebugger.ts"() {
1819
+ "use strict";
1820
+ snapshots2 = /* @__PURE__ */ new Map();
1821
+ }
1822
+ });
1823
+
1800
1824
  // src/core/navigators/generators/CompositeGenerator.ts
1801
1825
  var CompositeGenerator_exports = {};
1802
1826
  __export(CompositeGenerator_exports, {
@@ -2161,6 +2185,13 @@ function shuffleInPlace(arr) {
2161
2185
  [arr[i], arr[j]] = [arr[j], arr[i]];
2162
2186
  }
2163
2187
  }
2188
+ function formatAge(ms) {
2189
+ const minutes = Math.max(0, Math.round(ms / 6e4));
2190
+ if (minutes < 60) return `${minutes}m`;
2191
+ const hours = Math.round(minutes / 60);
2192
+ if (hours < 24) return `${hours}h`;
2193
+ return `${Math.round(hours / 24)}d`;
2194
+ }
2164
2195
  function pickTopByScore(cards, limit) {
2165
2196
  return [...cards].sort((a, b) => b.score - a.score || a.cardId.localeCompare(b.cardId)).slice(0, limit);
2166
2197
  }
@@ -2169,6 +2200,7 @@ var init_prescribed = __esm({
2169
2200
  "src/core/navigators/generators/prescribed.ts"() {
2170
2201
  "use strict";
2171
2202
  init_navigators();
2203
+ init_StrategyPressureDebugger();
2172
2204
  init_logger();
2173
2205
  DEFAULT_FRESHNESS_WINDOW = 3;
2174
2206
  DEFAULT_MAX_DIRECT_PER_RUN = 3;
@@ -2245,6 +2277,7 @@ var init_prescribed = __esm({
2245
2277
  const groupRuntimes = [];
2246
2278
  const priorPracticeDebt = progress.practiceDebt ?? {};
2247
2279
  const nextPracticeDebt = {};
2280
+ const practiceDebtsByGroup = /* @__PURE__ */ new Map();
2248
2281
  for (const group of this.config.groups) {
2249
2282
  const runtime = this.buildGroupRuntimeState({
2250
2283
  group,
@@ -2293,7 +2326,7 @@ var init_prescribed = __esm({
2293
2326
  courseId,
2294
2327
  emittedIds
2295
2328
  );
2296
- const practiceCards = this.buildPracticeCards({
2329
+ const practice = this.buildPracticeCards({
2297
2330
  group,
2298
2331
  courseId,
2299
2332
  emittedIds,
@@ -2306,7 +2339,8 @@ var init_prescribed = __esm({
2306
2339
  priorPracticeDebt,
2307
2340
  nextPracticeDebt
2308
2341
  });
2309
- emitted.push(...directCards, ...supportCards, ...discoveredSupportCards, ...practiceCards);
2342
+ practiceDebtsByGroup.set(group.id, practice.debts);
2343
+ emitted.push(...directCards, ...supportCards, ...discoveredSupportCards, ...practice.cards);
2310
2344
  }
2311
2345
  nextState.practiceDebt = nextPracticeDebt;
2312
2346
  const hintSummary = this.buildSupportHintSummary(groupRuntimes);
@@ -2322,6 +2356,7 @@ var init_prescribed = __esm({
2322
2356
  } else {
2323
2357
  logger.info("[Prescribed] No hints to emit (no blocked targets or no support tags)");
2324
2358
  }
2359
+ this.capturePressureSnapshot(courseId, groupRuntimes, practiceDebtsByGroup, emitted, hints);
2325
2360
  if (emitted.length === 0) {
2326
2361
  logger.info(
2327
2362
  "[Prescribed] 0 cards emitted (all targets blocked, authored/discovered support candidates exhausted)" + (hints ? " \u2014 boost hints emitted but may not survive filters" : "")
@@ -2383,6 +2418,60 @@ var init_prescribed = __esm({
2383
2418
  supportTags: [...supportTags].sort()
2384
2419
  };
2385
2420
  }
2421
+ /**
2422
+ * Translate this run's per-group runtimes and practice debts into gauges on
2423
+ * the generic strategy-pressure debug channel (rendered by the live session
2424
+ * overlay's "strategy backpressure" section).
2425
+ */
2426
+ capturePressureSnapshot(courseId, groupRuntimes, practiceDebtsByGroup, emitted, hints) {
2427
+ const now = Date.now();
2428
+ const gauges = [];
2429
+ for (const runtime of groupRuntimes) {
2430
+ const group = runtime.group;
2431
+ const window2 = group.freshnessWindowSessions ?? DEFAULT_FRESHNESS_WINDOW;
2432
+ gauges.push({
2433
+ id: `group:${group.id}:target`,
2434
+ label: `${group.id} targets`,
2435
+ multiplier: runtime.pressureMultiplier,
2436
+ max: MAX_TARGET_MULTIPLIER,
2437
+ detail: `${runtime.pendingTargets.length} pending (${runtime.surfaceableTargets.length} surfaceable, ${runtime.blockedTargets.length} blocked) \xB7 sinceSurfaced ${runtime.sessionsSinceSurfaced}/${window2}`,
2438
+ items: runtime.blockedTargets.map((cardId) => ({ label: cardId, value: "blocked" }))
2439
+ });
2440
+ if (runtime.blockedTargets.length > 0) {
2441
+ const mode = runtime.supportCandidates.length > 0 ? "direct-support" : runtime.discoveredSupportCandidates.length > 0 ? "inserted-support" : "boost-only";
2442
+ gauges.push({
2443
+ id: `group:${group.id}:support`,
2444
+ label: `${group.id} support`,
2445
+ multiplier: runtime.supportMultiplier,
2446
+ max: MAX_SUPPORT_MULTIPLIER,
2447
+ detail: `mode=${mode} \xB7 ${runtime.supportTags.length} support tag(s)`,
2448
+ items: runtime.supportTags.map((tag) => ({ label: tag }))
2449
+ });
2450
+ }
2451
+ const debts = practiceDebtsByGroup.get(group.id) ?? [];
2452
+ if (debts.length > 0) {
2453
+ gauges.push({
2454
+ id: `group:${group.id}:practice-debt`,
2455
+ label: `${group.id} practice debt`,
2456
+ multiplier: Math.max(...debts.map((d) => d.multiplier)),
2457
+ max: MAX_PRACTICE_MULTIPLIER,
2458
+ detail: `${debts.length} under-practiced skill(s)`,
2459
+ items: debts.map((d) => ({
2460
+ label: d.tag,
2461
+ value: `\xD7${d.multiplier.toFixed(2)} \xB7 open ${formatAge(now - new Date(d.firstOwedAt).getTime())}`
2462
+ }))
2463
+ });
2464
+ }
2465
+ }
2466
+ captureStrategyPressure({
2467
+ source: "prescribed",
2468
+ courseId,
2469
+ gauges,
2470
+ topScore: emitted.length > 0 ? Math.max(...emitted.map((c) => c.score)) : null,
2471
+ hintsLabel: hints?._label,
2472
+ timestamp: now
2473
+ });
2474
+ }
2386
2475
  parseConfig(serializedData) {
2387
2476
  try {
2388
2477
  const parsed = JSON.parse(serializedData);
@@ -2539,6 +2628,7 @@ var init_prescribed = __esm({
2539
2628
  supportTags: [...supportTags],
2540
2629
  pressureMultiplier,
2541
2630
  supportMultiplier,
2631
+ sessionsSinceSurfaced,
2542
2632
  debugVersion: PRESCRIBED_DEBUG_VERSION
2543
2633
  };
2544
2634
  }
@@ -2671,13 +2761,13 @@ var init_prescribed = __esm({
2671
2761
  nextPracticeDebt
2672
2762
  } = args;
2673
2763
  const patterns = group.practiceTagPatterns ?? [];
2674
- if (patterns.length === 0) return [];
2764
+ if (patterns.length === 0) return { cards: [], debts: [] };
2675
2765
  const practiceMinCount = group.practiceMinCount ?? DEFAULT_PRACTICE_MIN_COUNT;
2676
2766
  const maxPractice = group.maxPracticeCardsPerRun ?? DEFAULT_MAX_PRACTICE_PER_RUN;
2677
2767
  const practiceTags = [...cardsByTag.keys()].filter(
2678
2768
  (tag) => patterns.some((p) => matchesTagPattern(tag, p)) && this.isUnlockedGatedSkill(tag, hierarchyConfigs, userTagElo, userGlobalElo) && (userTagElo[tag]?.count ?? 0) < practiceMinCount
2679
2769
  );
2680
- if (practiceTags.length === 0) return [];
2770
+ if (practiceTags.length === 0) return { cards: [], debts: [] };
2681
2771
  const now = Date.now();
2682
2772
  const DAY_MS = 24 * 60 * 60 * 1e3;
2683
2773
  const tagMultiplier = /* @__PURE__ */ new Map();
@@ -2692,6 +2782,11 @@ var init_prescribed = __esm({
2692
2782
  );
2693
2783
  tagMultiplier.set(tag, mult);
2694
2784
  }
2785
+ const debts = practiceTags.map((tag) => ({
2786
+ tag,
2787
+ multiplier: tagMultiplier.get(tag) ?? PRACTICE_BASE_MULT,
2788
+ firstOwedAt: nextPracticeDebt[tag]
2789
+ })).sort((a, b) => b.multiplier - a.multiplier || a.tag.localeCompare(b.tag));
2695
2790
  const practiceCardIds = this.findDiscoveredSupportCards({
2696
2791
  supportTags: practiceTags,
2697
2792
  cardsByTag,
@@ -2700,7 +2795,7 @@ var init_prescribed = __esm({
2700
2795
  excludedIds: emittedIds,
2701
2796
  limit: maxPractice
2702
2797
  });
2703
- if (practiceCardIds.length === 0) return [];
2798
+ if (practiceCardIds.length === 0) return { cards: [], debts };
2704
2799
  logger.info(
2705
2800
  `[Prescribed] Group '${group.id}' practice: ${practiceTags.length} unlocked under-practiced skill(s), emitting ${practiceCardIds.length} drill card(s)`
2706
2801
  );
@@ -2730,7 +2825,7 @@ var init_prescribed = __esm({
2730
2825
  ]
2731
2826
  });
2732
2827
  }
2733
- return cards;
2828
+ return { cards, debts };
2734
2829
  }
2735
2830
  /**
2736
2831
  * True for a skill that was *gated and is now reached*: it has at least one
@@ -5407,6 +5502,7 @@ var init_3 = __esm({
5407
5502
  "./PipelineAssembler.ts": () => Promise.resolve().then(() => (init_PipelineAssembler(), PipelineAssembler_exports)),
5408
5503
  "./PipelineDebugger.ts": () => Promise.resolve().then(() => (init_PipelineDebugger(), PipelineDebugger_exports)),
5409
5504
  "./SrsDebugger.ts": () => Promise.resolve().then(() => (init_SrsDebugger(), SrsDebugger_exports)),
5505
+ "./StrategyPressureDebugger.ts": () => Promise.resolve().then(() => (init_StrategyPressureDebugger(), StrategyPressureDebugger_exports)),
5410
5506
  "./defaults.ts": () => Promise.resolve().then(() => (init_defaults(), defaults_exports)),
5411
5507
  "./diversityRerank.ts": () => Promise.resolve().then(() => (init_diversityRerank(), diversityRerank_exports)),
5412
5508
  "./filters/WeightedFilter.ts": () => Promise.resolve().then(() => (init_WeightedFilter(), WeightedFilter_exports)),
@@ -5439,7 +5535,9 @@ __export(navigators_exports, {
5439
5535
  NavigatorRole: () => NavigatorRole,
5440
5536
  NavigatorRoles: () => NavigatorRoles,
5441
5537
  Navigators: () => Navigators,
5538
+ captureStrategyPressure: () => captureStrategyPressure,
5442
5539
  clearSrsBacklogDebug: () => clearSrsBacklogDebug,
5540
+ clearStrategyPressureDebug: () => clearStrategyPressureDebug,
5443
5541
  diversityRerank: () => diversityRerank,
5444
5542
  getActivePipeline: () => getActivePipeline,
5445
5543
  getCardOrigin: () => getCardOrigin,
@@ -5447,6 +5545,7 @@ __export(navigators_exports, {
5447
5545
  getRegisteredNavigatorNames: () => getRegisteredNavigatorNames,
5448
5546
  getRegisteredNavigatorRole: () => getRegisteredNavigatorRole,
5449
5547
  getSrsBacklogDebug: () => getSrsBacklogDebug,
5548
+ getStrategyPressureDebug: () => getStrategyPressureDebug,
5450
5549
  hasRegisteredNavigator: () => hasRegisteredNavigator,
5451
5550
  initializeNavigatorRegistry: () => initializeNavigatorRegistry,
5452
5551
  isFilter: () => isFilter,
@@ -5529,6 +5628,7 @@ var init_navigators = __esm({
5529
5628
  init_diversityRerank();
5530
5629
  init_PipelineDebugger();
5531
5630
  init_SrsDebugger();
5631
+ init_StrategyPressureDebugger();
5532
5632
  init_logger();
5533
5633
  init_();
5534
5634
  init_2();
@@ -11545,7 +11645,7 @@ var CardHydrationService = class {
11545
11645
  }
11546
11646
  const data = dataDocs.map(displayableDataToViewData).reverse();
11547
11647
  this.hydratedCards.set(item.cardID, {
11548
- item,
11648
+ item: { ...item, elo: toCourseElo8(cardData.elo).global.score },
11549
11649
  view,
11550
11650
  data,
11551
11651
  tags: tagsByCard.get(item.cardID) ?? []
@@ -13471,6 +13571,7 @@ mountMixerDebugger();
13471
13571
  init_logger();
13472
13572
  init_PipelineDebugger();
13473
13573
  init_SrsDebugger();
13574
+ init_StrategyPressureDebugger();
13474
13575
 
13475
13576
  // src/study/SessionOverlay.ts
13476
13577
  init_logger();
@@ -13561,7 +13662,7 @@ function render() {
13561
13662
  attachHandlers();
13562
13663
  return;
13563
13664
  }
13564
- 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);
13665
+ 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);
13565
13666
  attachHandlers();
13566
13667
  }
13567
13668
  function attachHandlers() {
@@ -13665,6 +13766,46 @@ function backlogHtml(backlog) {
13665
13766
  }).join("");
13666
13767
  return `<div style="margin-bottom:6px"><div style="color:#93c5fd">review backpressure</div>${rows}</div>`;
13667
13768
  }
13769
+ function pressureColor(multiplier, max) {
13770
+ if (multiplier <= 1.001) return "#86efac";
13771
+ if (max !== void 0 && max > 1) {
13772
+ return (multiplier - 1) / (max - 1) >= 0.75 ? "#fca5a5" : "#fcd34d";
13773
+ }
13774
+ return multiplier >= 3 ? "#fca5a5" : "#fcd34d";
13775
+ }
13776
+ function toggleKey(raw) {
13777
+ return raw.replace(/[^\w:.|-]/g, "_");
13778
+ }
13779
+ function gaugeHtml(sectionKey, g) {
13780
+ const cap = g.max !== void 0 ? `<span style="opacity:.5">/${g.max}</span>` : "";
13781
+ const detail = g.detail ? ` <span style="opacity:.6">${esc(g.detail)}</span>` : "";
13782
+ 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}`;
13783
+ const items = g.items ?? [];
13784
+ if (items.length > 0) {
13785
+ const key = toggleKey(`sp|${sectionKey}|${g.id}`);
13786
+ const isOpen = !!expanded[key];
13787
+ const shown = isOpen ? items : items.slice(0, INLINE_THRESHOLD);
13788
+ html += `<div style="margin-left:6px">` + shown.map(
13789
+ (it) => `<div style="white-space:nowrap;opacity:.75">${esc(it.label)}${it.value ? ` <span style="opacity:.6">${esc(it.value)}</span>` : ""}</div>`
13790
+ ).join("");
13791
+ if (items.length > INLINE_THRESHOLD) {
13792
+ const footer = isOpen ? "\u25BE show less" : `\u2026 +${items.length - shown.length} more`;
13793
+ html += `<div data-q="${key}" style="cursor:pointer;opacity:.6">${footer}</div>`;
13794
+ }
13795
+ html += `</div>`;
13796
+ }
13797
+ return html + `</div>`;
13798
+ }
13799
+ function strategyPressureHtml(list) {
13800
+ if (!list.length) return "";
13801
+ const sections = list.map((s) => {
13802
+ const sectionKey = `${s.source}:${s.courseId.slice(0, 8)}`;
13803
+ const top = s.topScore !== null && s.topScore !== void 0 ? s.topScore.toFixed(2) : "\u2014";
13804
+ const hints = s.hintsLabel ? `<div style="margin-left:6px;opacity:.6">hints: ${esc(s.hintsLabel)}</div>` : "";
13805
+ 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>`;
13806
+ }).join("");
13807
+ return `<div style="margin-bottom:6px"><div style="color:#fdba74">strategy backpressure</div>${sections}</div>`;
13808
+ }
13668
13809
  function fmtScore(score) {
13669
13810
  if (score === void 0) return "";
13670
13811
  if (!Number.isFinite(score)) return "REQ";
@@ -13765,6 +13906,24 @@ function snapshotToText(s) {
13765
13906
  );
13766
13907
  }
13767
13908
  }
13909
+ if (s.strategyPressure.length) {
13910
+ lines.push("");
13911
+ lines.push("strategy backpressure:");
13912
+ for (const sp of s.strategyPressure) {
13913
+ const top = sp.topScore !== null && sp.topScore !== void 0 ? sp.topScore.toFixed(2) : "\u2014";
13914
+ lines.push(` ${sp.source} (${sp.courseId.slice(0, 8)}): top score ${top}`);
13915
+ for (const g of sp.gauges) {
13916
+ const cap = g.max !== void 0 ? `/${g.max}` : "";
13917
+ lines.push(
13918
+ ` ${g.label} \xD7${g.multiplier.toFixed(2)}${cap}${g.detail ? ` \u2014 ${g.detail}` : ""}`
13919
+ );
13920
+ for (const it of g.items ?? []) {
13921
+ lines.push(` ${it.label}${it.value ? ` (${it.value})` : ""}`);
13922
+ }
13923
+ }
13924
+ if (sp.hintsLabel) lines.push(` hints: ${sp.hintsLabel}`);
13925
+ }
13926
+ }
13768
13927
  const queueText = (label, q) => {
13769
13928
  lines.push("");
13770
13929
  lines.push(`${label}: ${q.length} (drawn ${q.dequeueCount})`);
@@ -13802,6 +13961,7 @@ var MAX_HISTORY = 5;
13802
13961
  function clearStaleSessionDebugState() {
13803
13962
  clearRunHistory();
13804
13963
  clearSrsBacklogDebug();
13964
+ clearStrategyPressureDebug();
13805
13965
  }
13806
13966
  function startSessionTracking(supplyQLength, failedQLength) {
13807
13967
  const sessionId = `session-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
@@ -14490,6 +14650,7 @@ var SessionController = class _SessionController extends Loggable {
14490
14650
  supplyQ: describe(this.supplyQ),
14491
14651
  failedQ: describe(this.failedQ),
14492
14652
  reviewBacklog: getSrsBacklogDebug(),
14653
+ strategyPressure: getStrategyPressureDebug(),
14493
14654
  drawnCards
14494
14655
  };
14495
14656
  }
@@ -15190,8 +15351,10 @@ export {
15190
15351
  areQuestionRecords,
15191
15352
  buildStrategyStateId,
15192
15353
  captureMixerRun,
15354
+ captureStrategyPressure,
15193
15355
  clearSrsBacklogDebug,
15194
15356
  clearStaleSessionDebugState,
15357
+ clearStrategyPressureDebug,
15195
15358
  computeDeviation,
15196
15359
  computeEffectiveWeight,
15197
15360
  computeOutcomeSignal,
@@ -15214,6 +15377,7 @@ export {
15214
15377
  getRegisteredNavigatorNames,
15215
15378
  getRegisteredNavigatorRole,
15216
15379
  getSrsBacklogDebug,
15380
+ getStrategyPressureDebug,
15217
15381
  getStudySource,
15218
15382
  hasRegisteredNavigator,
15219
15383
  importParsedCards,