@stage5/lumine 0.3.0 → 0.3.1

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/lib/admin.js CHANGED
@@ -2417,7 +2417,7 @@ export function parseAdminOperation(options) {
2417
2417
  // long as two GPT-6 Sol calls, so it gets its own timeout. The daily
2418
2418
  // website management run calls it in a bounded loop (Mikey, 2026-09-28).
2419
2419
  const usage =
2420
- "Usage: lumine admin reward-bank coverage <buildId> [--max-step 3] | seed <buildId> [--max-step 3].";
2420
+ "Usage: lumine admin reward-bank coverage <buildId> [--max-step 3] | seed <buildId> [--max-step 3] | report <buildId> [--days 1].";
2421
2421
  const buildId = parseRequiredInteger(
2422
2422
  target || options.adminBuild,
2423
2423
  "Build ID",
@@ -2427,6 +2427,20 @@ export function parseAdminOperation(options) {
2427
2427
  ? parseRequiredInteger(options.adminMaxStep, "--max-step", 1)
2428
2428
  : 3;
2429
2429
  if (maxStep > 10) throw cliValidationError("--max-step must be at most 10.");
2430
+ if (action === "report") {
2431
+ const days = options.adminDays
2432
+ ? parseRequiredInteger(options.adminDays, "--days", 1)
2433
+ : 1;
2434
+ if (days > 31) throw cliValidationError("--days must be at most 31.");
2435
+ return readOperation(
2436
+ "reward-bank.report",
2437
+ withQuery("/cli/admin/reward-bank/report", {
2438
+ buildId: String(buildId),
2439
+ days: String(days),
2440
+ }),
2441
+ { requiresRun: false },
2442
+ );
2443
+ }
2430
2444
  if (action === "coverage" || action === "status") {
2431
2445
  return readOperation(
2432
2446
  "reward-bank.coverage",
@@ -5390,6 +5404,70 @@ function printReviewRequestResult({ operation, data, result }) {
5390
5404
  printReviewEvents(data.events);
5391
5405
  }
5392
5406
 
5407
+ // Human view of `lumine admin reward-bank report`; --json has everything.
5408
+ function printRewardBankReport(data) {
5409
+ const pct = (value) =>
5410
+ value === null || value === undefined ? "–" : `${Math.round(value * 100)}%`;
5411
+ console.log(`Question bank report · build #${data.buildId} · last ${data.days} day(s)`);
5412
+ if (data.coverage) {
5413
+ console.log(
5414
+ `Seeding coverage (steps 1-3): ${data.coverage.filled} / ${data.coverage.total}`,
5415
+ );
5416
+ }
5417
+ if (!data.generation) {
5418
+ console.log("Generation: no telemetry table yet.");
5419
+ } else {
5420
+ for (const row of data.generation.bySource) {
5421
+ if (!row.rounds) continue;
5422
+ console.log(
5423
+ `Generation (${row.source}): ${row.rounds} rounds · ${row.accepted} accepted (${pct(row.acceptRate)}) · off-brief ${row.offBrief} · answer mismatch ${row.answerMismatch} · malformed ${row.malformed} · errors ${row.errors} · $${row.costUsd} · avg ${row.avgSeconds ?? "–"}s (max ${row.maxSeconds ?? "–"}s)`,
5424
+ );
5425
+ }
5426
+ for (const row of data.generation.recentRejections.slice(0, 5)) {
5427
+ console.log(
5428
+ ` rejected ${row.outcome} ${row.ruleId}/${row.topicKey} step ${row.step}: ${row.problems || "(no reason given)"}`,
5429
+ );
5430
+ }
5431
+ }
5432
+ console.log(`Learners: ${data.learners.total}`);
5433
+ for (const row of data.learners.byRuleStep) {
5434
+ console.log(
5435
+ ` ${row.ruleId} step ${row.step}: served ${row.served} · answered ${row.answered} · first-try ${pct(row.firstTryRate)} · solved ${row.solved} · skipped ${row.skipped}`,
5436
+ );
5437
+ }
5438
+ const paid = data.payouts.reduce(
5439
+ (sum, row) => ({ xp: sum.xp + row.xp, coins: sum.coins + row.coins, receipts: sum.receipts + row.receipts }),
5440
+ { xp: 0, coins: 0, receipts: 0 },
5441
+ );
5442
+ console.log(
5443
+ `Payouts: ${paid.receipts} receipts · ${paid.xp.toLocaleString()} XP · ${paid.coins.toLocaleString()} Coins`,
5444
+ );
5445
+ for (const row of data.payouts) {
5446
+ console.log(
5447
+ ` ${row.ruleId} step ${row.step}: ${row.receipts} (${row.firstTry} first try) · ${row.earners} earners · ${row.xp.toLocaleString()} XP · ${row.coins.toLocaleString()} Coins`,
5448
+ );
5449
+ }
5450
+ if (data.ladder.length) {
5451
+ console.log(
5452
+ `Ladder now: ${data.ladder.map((row) => `${row.ruleId} s${row.step}:${row.topicLadders}`).join(", ")}`,
5453
+ );
5454
+ }
5455
+ const hidden = data.bank.filter((row) => row.status === "hidden");
5456
+ console.log(
5457
+ `Bank: ${data.bank.filter((row) => row.status === "active").reduce((n, row) => n + row.questions, 0)} active · ${hidden.reduce((n, row) => n + row.questions, 0)} hidden${hidden.length ? ` (${hidden.map((row) => `${row.hiddenReason}: ${row.questions}`).join(", ")})` : ""} · ${data.bank.reduce((n, row) => n + row.createdInWindow, 0)} new in window`,
5458
+ );
5459
+ for (const row of data.questionsToReview.reported) {
5460
+ console.log(
5461
+ ` reported #${row.id} (${row.reports}, ${row.status}) ${row.ruleId}/${row.topicKey} s${row.step}: ${row.reasons}`,
5462
+ );
5463
+ }
5464
+ for (const row of data.questionsToReview.hardest) {
5465
+ console.log(
5466
+ ` hardest #${row.id} ${row.ruleId}/${row.topicKey} s${row.step}: solved ${pct(row.solveRate)} of ${row.learners}`,
5467
+ );
5468
+ }
5469
+ }
5470
+
5393
5471
  function printAdminResult({ operation, result }) {
5394
5472
  const data = result?.data || {};
5395
5473
  if (printChatSafetyResult({ operation, data })) return;
@@ -5397,6 +5475,10 @@ function printAdminResult({ operation, result }) {
5397
5475
  printRewardActivity(data);
5398
5476
  return;
5399
5477
  }
5478
+ if (operation.name === "reward-bank.report") {
5479
+ printRewardBankReport(data);
5480
+ return;
5481
+ }
5400
5482
  if (operation.name.startsWith("reward-bank.")) {
5401
5483
  if (operation.name === "reward-bank.seed") {
5402
5484
  console.log(
package/lib/commands.js CHANGED
@@ -3060,6 +3060,7 @@ export function printHelp() {
3060
3060
  lumine admin post reward <target> [--type subject|comment|aiStory|dailyReflection] --twinkles 3 [--json]
3061
3061
  lumine admin reward-bank coverage <buildId> [--max-step 3] [--json]
3062
3062
  lumine admin reward-bank seed <buildId> [--max-step 3] [--json] (daily run: writes ONE missing seed question, lowest step first, billed to Zero/Ciel's AI Energy)
3063
+ lumine admin reward-bank report <buildId> [--days 1] [--json] (question-bank telemetry: generation outcomes, learners, payouts, ladder, questions to review)
3063
3064
  lumine admin comment draft <target> [--type subject|comment|build|aiStory|dailyReflection] [--file <comment.md>] [--review-receipt <review.json>|--reviewed-version <id> --reviewed-via runtime|code] [--review-context <context.json>] [--identity zero|ciel|auto] [--json]
3064
3065
  lumine admin comment reply comment:<id> [--file <reply.md>] [--reviewed-version <id> --reviewed-via runtime|code] [--review-context <context.json>] [--identity zero|ciel|auto] [--json]
3065
3066
  lumine admin comment post --draft-id <id> [--json]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stage5/lumine",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "description": "Command line tools for launching Lumine builds on Twinkle.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1304,8 +1304,18 @@ them so the first learner on a unit never waits or pays.
1304
1304
  ```bash
1305
1305
  lumine admin reward-bank coverage <buildId> [--max-step 3] [--json] # read-only, no run
1306
1306
  lumine admin reward-bank seed <buildId> [--max-step 3] [--json] # delegated full run
1307
+ lumine admin reward-bank report <buildId> [--days 1] [--json] # read-only telemetry
1307
1308
  ```
1308
1309
 
1310
+ `report` covers the last `--days` (1-31): generation rounds by source
1311
+ (learner or seed) with outcomes (`accepted`, `off_brief`: the checker judged
1312
+ the question outside the brief's language, level or LaTeX rule;
1313
+ `answer_mismatch`; `malformed`; `error`), cost and time, and recent rejection
1314
+ reasons; learners served/answered/first-try/solved/skipped per rule and step;
1315
+ payouts; the current ladder distribution; active/hidden questions; the
1316
+ hardest questions (3+ learners) and every reported question with reasons;
1317
+ and seeding coverage for steps 1-3 once the app is approved.
1318
+
1309
1319
  - `seed` writes ONE question per call into the first missing slot, lowest
1310
1320
  step first across every topic of every generated-quiz rule, up to
1311
1321
  `--max-step` (default 3, at most 10). A slot is a (rule, topic, step) with