@vue-skuilder/db 0.2.7 → 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 (40) 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 +67 -4
  4. package/dist/core/index.d.ts +67 -4
  5. package/dist/core/index.js +201 -39
  6. package/dist/core/index.js.map +1 -1
  7. package/dist/core/index.mjs +198 -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 +195 -39
  14. package/dist/impl/couch/index.js.map +1 -1
  15. package/dist/impl/couch/index.mjs +195 -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 +195 -39
  20. package/dist/impl/static/index.js.map +1 -1
  21. package/dist/impl/static/index.mjs +195 -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 +440 -251
  26. package/dist/index.js.map +1 -1
  27. package/dist/index.mjs +437 -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/Pipeline.ts +93 -1
  33. package/src/core/navigators/PipelineDebugger.ts +11 -1
  34. package/src/core/navigators/SrsDebugger.ts +53 -0
  35. package/src/core/navigators/generators/prescribed.ts +76 -9
  36. package/src/core/navigators/generators/srs.ts +81 -37
  37. package/src/core/navigators/index.ts +9 -0
  38. package/src/study/SessionController.ts +260 -249
  39. package/src/study/SessionDebugger.ts +15 -25
  40. package/src/study/SessionOverlay.ts +108 -13
@@ -3,6 +3,7 @@ import type { ScheduledCard } from '../../types/user';
3
3
  import type { CourseDBInterface } from '../../interfaces/courseDB';
4
4
  import type { UserDBInterface } from '../../interfaces/userDB';
5
5
  import { ContentNavigator } from '../index';
6
+ import { captureSrsBacklog } from '../SrsDebugger';
6
7
  import type { ContentNavigationStrategyData } from '../../types/contentNavigationStrategy';
7
8
  import type { CardGenerator, GeneratorContext, GeneratorResult } from './types';
8
9
  import { logger } from '@db/util/logger';
@@ -41,10 +42,19 @@ import { logger } from '@db/util/logger';
41
42
  const DEFAULT_HEALTHY_BACKLOG = 20;
42
43
 
43
44
  /**
44
- * Maximum backlog pressure contribution to score.
45
- * At 3x healthy backlog, pressure maxes out.
45
+ * Maximum backlog pressure as a *multiplier* on review urgency.
46
+ *
47
+ * Backlog pressure is multiplicative (×1.0 at/below healthy, scaling up as the
48
+ * due pile grows, maxing here at 3× healthy backlog). It replaces an older
49
+ * additive +0..+0.5 term that was a [0,1]-era modifier — once review scores
50
+ * stopped being clamped to 1.0 and new cards could be boosted well past it
51
+ * (e.g. an intro ×5 → 7+), a flat +0.5 was both too small to compete and mostly
52
+ * eaten by the old 1.0 clamp. A multiplier scales review priority onto the same
53
+ * open scale the boosted new cards live on, so a heavy backlog can genuinely
54
+ * lift reviews into competition. Tunable — verify review vs new ordering in the
55
+ * dbg overlay's "review backpressure" panel.
46
56
  */
47
- const MAX_BACKLOG_PRESSURE = 0.5;
57
+ const MAX_BACKLOG_MULTIPLIER = 2.0;
48
58
 
49
59
  /**
50
60
  * Configuration for the SRS strategy.
@@ -158,14 +168,31 @@ export default class SRSNavigator extends ContentNavigator implements CardGenera
158
168
  }
159
169
  }
160
170
 
161
- // Compute backlog pressure - applies globally to all reviews
162
- const backlogPressure = this.computeBacklogPressure(dueReviews.length);
171
+ // Compute backlog pressure (multiplicative) - applies globally to all reviews
172
+ const backlogMultiplier = this.computeBacklogMultiplier(dueReviews.length);
173
+
174
+ // Time until the next not-yet-due review (for the debug overlay): shows
175
+ // reviews are *coming* even when none are due right now.
176
+ const notDue = reviews.filter((r) => !now.isAfter(moment.utc(r.reviewTime)));
177
+ let nextDueIn: string | null = null;
178
+ if (notDue.length > 0) {
179
+ const next = notDue.reduce((a, b) =>
180
+ moment.utc(a.reviewTime).isBefore(moment.utc(b.reviewTime)) ? a : b
181
+ );
182
+ const until = moment.duration(moment.utc(next.reviewTime).diff(now));
183
+ nextDueIn =
184
+ until.asHours() < 1
185
+ ? `${Math.round(until.asMinutes())}m`
186
+ : until.asHours() < 24
187
+ ? `${Math.round(until.asHours())}h`
188
+ : `${Math.round(until.asDays())}d`;
189
+ }
163
190
 
164
191
  // Log review status for transparency
165
192
  if (dueReviews.length > 0) {
166
193
  const pressureNote =
167
- backlogPressure > 0
168
- ? ` [backlog pressure: +${backlogPressure.toFixed(2)}]`
194
+ backlogMultiplier > 1
195
+ ? ` [backlog pressure: ×${backlogMultiplier.toFixed(2)}]`
169
196
  : ` [healthy backlog]`;
170
197
  logger.info(
171
198
  `[SRS] Course ${courseId}: ${dueReviews.length} reviews due now (of ${reviews.length} scheduled)${pressureNote}`
@@ -192,7 +219,7 @@ export default class SRSNavigator extends ContentNavigator implements CardGenera
192
219
  }
193
220
 
194
221
  const scored = dueReviews.map((review) => {
195
- const { score, reason } = this.computeUrgencyScore(review, now, backlogPressure);
222
+ const { score, reason } = this.computeUrgencyScore(review, now, backlogMultiplier);
196
223
 
197
224
  return {
198
225
  cardId: review.cardId,
@@ -213,41 +240,56 @@ export default class SRSNavigator extends ContentNavigator implements CardGenera
213
240
  });
214
241
 
215
242
  // Sort by score descending and limit
243
+ const sorted = scored.sort((a, b) => b.score - a.score);
244
+
245
+ // Capture backlog state for the live session overlay (see SrsDebugger).
246
+ captureSrsBacklog({
247
+ courseId,
248
+ scheduledTotal: reviews.length,
249
+ dueNow: dueReviews.length,
250
+ healthyBacklog: this.healthyBacklog,
251
+ backlogMultiplier,
252
+ maxBacklogMultiplier: MAX_BACKLOG_MULTIPLIER,
253
+ topReviewScore: sorted.length > 0 ? sorted[0].score : null,
254
+ nextDueIn,
255
+ timestamp: Date.now(),
256
+ });
257
+
216
258
  // [perf] parked: SRSgen / getPendingReviews timing
217
- // const srsResult = { cards: scored.sort((a, b) => b.score - a.score).slice(0, limit) };
259
+ // const srsResult = { cards: sorted.slice(0, limit) };
218
260
  // logger.info(
219
261
  // `[perf][SRSgen] total=${(performance.now() - tSrs0).toFixed(0)}ms ` +
220
262
  // `(pendingReviews=${(tReviews - tSrs0).toFixed(0)}) ` +
221
263
  // `[scheduled=${reviews.length} due=${dueReviews.length}]`
222
264
  // );
223
- return { cards: scored.sort((a, b) => b.score - a.score).slice(0, limit) };
265
+ return { cards: sorted.slice(0, limit) };
224
266
  }
225
267
 
226
268
  /**
227
- * Compute backlog pressure based on number of due reviews.
269
+ * Compute the multiplicative backlog pressure based on number of due reviews.
228
270
  *
229
- * Backlog pressure is 0 when at or below healthy threshold,
230
- * and increases linearly above it, maxing out at MAX_BACKLOG_PRESSURE.
271
+ * ×1.0 at or below the healthy threshold (no boost), increasing linearly above
272
+ * it and maxing out at MAX_BACKLOG_MULTIPLIER at the healthy backlog.
231
273
  *
232
- * Examples (with default healthyBacklog=20):
233
- * - 10 due reviews → 0.00 (healthy)
234
- * - 20 due reviews → 0.00 (at threshold)
235
- * - 40 due reviews → 0.25 (2x threshold)
236
- * - 60 due reviews → 0.50 (3x threshold, maxed)
274
+ * Examples (with default healthyBacklog=20, MAX_BACKLOG_MULTIPLIER=2.0):
275
+ * - 10 due reviews → ×1.00 (healthy)
276
+ * - 20 due reviews → ×1.00 (at threshold)
277
+ * - 40 due reviews → ×1.50 (2x threshold)
278
+ * - 60 due reviews → ×2.00 (3x threshold, maxed)
237
279
  *
238
280
  * @param dueCount - Number of reviews currently due
239
- * @returns Backlog pressure score to add to urgency (0 to MAX_BACKLOG_PRESSURE)
281
+ * @returns Multiplier applied to review urgency (1.0 to MAX_BACKLOG_MULTIPLIER)
240
282
  */
241
- private computeBacklogPressure(dueCount: number): number {
283
+ private computeBacklogMultiplier(dueCount: number): number {
242
284
  if (dueCount <= this.healthyBacklog) {
243
- return 0;
285
+ return 1.0;
244
286
  }
245
287
 
246
- // Linear increase: at 2x healthy, pressure = 0.25; at 3x, pressure = 0.50
288
+ // Linear in excess: ×1 at healthy, reaching MAX at healthy (excess = 2×healthy).
247
289
  const excess = dueCount - this.healthyBacklog;
248
- const pressure = (excess / this.healthyBacklog) * (MAX_BACKLOG_PRESSURE / 2);
290
+ const multiplier = 1 + (excess / this.healthyBacklog) * ((MAX_BACKLOG_MULTIPLIER - 1) / 2);
249
291
 
250
- return Math.min(MAX_BACKLOG_PRESSURE, pressure);
292
+ return Math.min(MAX_BACKLOG_MULTIPLIER, multiplier);
251
293
  }
252
294
 
253
295
  /**
@@ -263,22 +305,23 @@ export default class SRSNavigator extends ContentNavigator implements CardGenera
263
305
  * - 30 days (720h) → ~0.56
264
306
  * - 180 days → ~0.30
265
307
  *
266
- * 3. Backlog pressure = global boost when review backlog exceeds healthy threshold
267
- * - At healthy backlog: 0
268
- * - At 2x healthy: +0.25
269
- * - At 3x+ healthy: +0.50 (max)
308
+ * 3. Backlog pressure = global *multiplier* when review backlog exceeds the
309
+ * healthy threshold (×1.0 healthy up to MAX_BACKLOG_MULTIPLIER at 3×).
270
310
  *
271
- * Combined: base 0.5 + (urgency factors * 0.45) + backlog pressure
272
- * Result range: 0.5 to 1.0 (uncapped to allow high-urgency reviews to compete with new cards)
311
+ * Combined: (base 0.5 + urgency factors * 0.45) × backlog multiplier.
312
+ * Per-card range before pressure: ~0.57–0.95. NOT clamped to 1.0 under a
313
+ * heavy backlog reviews scale onto the open scale to compete with (and exceed)
314
+ * new cards; what keeps them from running away is the bounded multiplier, not
315
+ * a hard ceiling.
273
316
  *
274
317
  * @param review - The scheduled card to score
275
318
  * @param now - Current time
276
- * @param backlogPressure - Pre-computed backlog pressure (0 to 0.5)
319
+ * @param backlogMultiplier - Pre-computed backlog multiplier (1.0 to MAX_BACKLOG_MULTIPLIER)
277
320
  */
278
321
  private computeUrgencyScore(
279
322
  review: ScheduledCard,
280
323
  now: moment.Moment,
281
- backlogPressure: number
324
+ backlogMultiplier: number
282
325
  ): { score: number; reason: string } {
283
326
  const scheduledAt = moment.utc(review.scheduledAt);
284
327
  const due = moment.utc(review.reviewTime);
@@ -299,10 +342,11 @@ export default class SRSNavigator extends ContentNavigator implements CardGenera
299
342
  const overdueContribution = Math.min(1.0, Math.max(0, relativeOverdue));
300
343
  const urgency = overdueContribution * 0.5 + recencyFactor * 0.5;
301
344
 
302
- // Final score: base 0.5 + urgency contribution + backlog pressure
303
- // Uncapped at 1.0 (no 0.95 ceiling) - allows high-urgency reviews to compete with new cards
345
+ // Final score: per-card urgency (base 0.5 + contribution) scaled by the
346
+ // global backlog multiplier. No 1.0 clamp reviews compete on the open
347
+ // scale; the bounded multiplier (not a ceiling) caps the lift.
304
348
  const baseScore = 0.5 + urgency * 0.45;
305
- const score = Math.min(1.0, baseScore + backlogPressure);
349
+ const score = baseScore * backlogMultiplier;
306
350
 
307
351
  // Build reason string with all contributing factors
308
352
  const reasonParts = [
@@ -312,8 +356,8 @@ export default class SRSNavigator extends ContentNavigator implements CardGenera
312
356
  `recency: ${recencyFactor.toFixed(2)}`,
313
357
  ];
314
358
 
315
- if (backlogPressure > 0) {
316
- reasonParts.push(`backlog: +${backlogPressure.toFixed(2)}`);
359
+ if (backlogMultiplier > 1) {
360
+ reasonParts.push(`backlog: ×${backlogMultiplier.toFixed(2)}`);
317
361
  }
318
362
 
319
363
  reasonParts.push('review');
@@ -19,11 +19,20 @@ import type { GeneratorResult, ReplanHints } from './generators/types';
19
19
  export {
20
20
  pipelineDebugAPI,
21
21
  mountPipelineDebugger,
22
+ getActivePipeline,
22
23
  type PipelineRunReport,
23
24
  type GeneratorSummary,
24
25
  type FilterImpact,
25
26
  } from './PipelineDebugger';
26
27
 
28
+ // Re-export the commit-free forecast capability surface.
29
+ export type { PipelineForecaster } from './Pipeline';
30
+ export {
31
+ getSrsBacklogDebug,
32
+ clearSrsBacklogDebug,
33
+ type SrsBacklogDebug,
34
+ } from './SrsDebugger';
35
+
27
36
  import { LearnableWeight } from '../types/contentNavigationStrategy';
28
37
  export type { ContentNavigationStrategyData, LearnableWeight } from '../types/contentNavigationStrategy';
29
38
  import type { ContentNavigationStrategyData } from '../types/contentNavigationStrategy';