@vue-skuilder/db 0.1.34 → 0.1.36
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/core/index.d.cts +46 -0
- package/dist/core/index.d.ts +46 -0
- package/dist/core/index.js +163 -12
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.mjs +163 -12
- package/dist/core/index.mjs.map +1 -1
- package/dist/impl/couch/index.js +163 -12
- package/dist/impl/couch/index.js.map +1 -1
- package/dist/impl/couch/index.mjs +163 -12
- package/dist/impl/couch/index.mjs.map +1 -1
- package/dist/impl/static/index.js +163 -12
- package/dist/impl/static/index.js.map +1 -1
- package/dist/impl/static/index.mjs +163 -12
- package/dist/impl/static/index.mjs.map +1 -1
- package/dist/index.d.cts +32 -2
- package/dist/index.d.ts +32 -2
- package/dist/index.js +232 -25
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +232 -25
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
- package/src/core/navigators/Pipeline.ts +5 -2
- package/src/core/navigators/PipelineDebugger.ts +238 -8
- package/src/study/SessionController.ts +88 -11
- package/src/study/SessionDebugger.ts +8 -0
|
@@ -503,6 +503,7 @@ var PipelineDebugger_exports = {};
|
|
|
503
503
|
__export(PipelineDebugger_exports, {
|
|
504
504
|
buildRunReport: () => buildRunReport,
|
|
505
505
|
captureRun: () => captureRun,
|
|
506
|
+
clearRunHistory: () => clearRunHistory,
|
|
506
507
|
mountPipelineDebugger: () => mountPipelineDebugger,
|
|
507
508
|
pipelineDebugAPI: () => pipelineDebugAPI,
|
|
508
509
|
registerPipelineForDebug: () => registerPipelineForDebug
|
|
@@ -510,6 +511,9 @@ __export(PipelineDebugger_exports, {
|
|
|
510
511
|
function registerPipelineForDebug(pipeline) {
|
|
511
512
|
_activePipeline = pipeline;
|
|
512
513
|
}
|
|
514
|
+
function clearRunHistory() {
|
|
515
|
+
runHistory.length = 0;
|
|
516
|
+
}
|
|
513
517
|
function getOrigin(card) {
|
|
514
518
|
const firstEntry = card.provenance[0];
|
|
515
519
|
if (!firstEntry) return "unknown";
|
|
@@ -538,7 +542,7 @@ function parseCardElo(provenance) {
|
|
|
538
542
|
}
|
|
539
543
|
function buildRunReport(courseId, courseName, generatorName, generators, generatedCount, filters, allCards, selectedCards, userElo, hints) {
|
|
540
544
|
const selectedIds = new Set(selectedCards.map((c) => c.cardId));
|
|
541
|
-
const
|
|
545
|
+
const toReport = (card) => ({
|
|
542
546
|
cardId: card.cardId,
|
|
543
547
|
courseId: card.courseId,
|
|
544
548
|
origin: getOrigin(card),
|
|
@@ -548,7 +552,47 @@ function buildRunReport(courseId, courseName, generatorName, generators, generat
|
|
|
548
552
|
provenance: card.provenance,
|
|
549
553
|
tags: card.tags,
|
|
550
554
|
selected: selectedIds.has(card.cardId)
|
|
551
|
-
})
|
|
555
|
+
});
|
|
556
|
+
const selectedReported = [];
|
|
557
|
+
const nearMissReported = [];
|
|
558
|
+
const discardedTailCards = [];
|
|
559
|
+
let nonSelectedSeen = 0;
|
|
560
|
+
for (const card of allCards) {
|
|
561
|
+
if (selectedIds.has(card.cardId)) {
|
|
562
|
+
selectedReported.push(toReport(card));
|
|
563
|
+
} else if (nonSelectedSeen < DISCARDED_KEEP_TOP) {
|
|
564
|
+
nearMissReported.push(toReport(card));
|
|
565
|
+
nonSelectedSeen++;
|
|
566
|
+
} else {
|
|
567
|
+
discardedTailCards.push(card);
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
const cards = [...selectedReported, ...nearMissReported];
|
|
571
|
+
let discardedTail;
|
|
572
|
+
if (discardedTailCards.length > 0) {
|
|
573
|
+
let scoreMin = Infinity;
|
|
574
|
+
let scoreMax = -Infinity;
|
|
575
|
+
let eloMin = Infinity;
|
|
576
|
+
let eloMax = -Infinity;
|
|
577
|
+
let eloSeen = false;
|
|
578
|
+
for (const c of discardedTailCards) {
|
|
579
|
+
if (c.score < scoreMin) scoreMin = c.score;
|
|
580
|
+
if (c.score > scoreMax) scoreMax = c.score;
|
|
581
|
+
const elo = parseCardElo(c.provenance);
|
|
582
|
+
if (elo !== void 0) {
|
|
583
|
+
eloSeen = true;
|
|
584
|
+
if (elo < eloMin) eloMin = elo;
|
|
585
|
+
if (elo > eloMax) eloMax = elo;
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
const eloFragment = eloSeen ? `, ELO ${eloMin}\u2013${eloMax}` : "";
|
|
589
|
+
discardedTail = {
|
|
590
|
+
count: discardedTailCards.length,
|
|
591
|
+
scoreRange: [scoreMin, scoreMax],
|
|
592
|
+
eloRange: eloSeen ? [eloMin, eloMax] : void 0,
|
|
593
|
+
note: `${discardedTailCards.length} additional candidate(s) scored below the top ${DISCARDED_KEEP_TOP} near-misses and were not retained (score ${scoreMin.toExponential(2)}\u2013${scoreMax.toExponential(2)}${eloFragment}). Likely ELO-window pull remnants filtered out by hierarchy/lesson/priority gates.`
|
|
594
|
+
};
|
|
595
|
+
}
|
|
552
596
|
const reviewsSelected = selectedCards.filter((c) => getOrigin(c) === "review").length;
|
|
553
597
|
const newSelected = selectedCards.filter((c) => getOrigin(c) === "new").length;
|
|
554
598
|
return {
|
|
@@ -563,7 +607,8 @@ function buildRunReport(courseId, courseName, generatorName, generators, generat
|
|
|
563
607
|
finalCount: selectedCards.length,
|
|
564
608
|
reviewsSelected,
|
|
565
609
|
newSelected,
|
|
566
|
-
cards
|
|
610
|
+
cards,
|
|
611
|
+
discardedTail
|
|
567
612
|
};
|
|
568
613
|
}
|
|
569
614
|
function formatProvenance(provenance) {
|
|
@@ -599,6 +644,44 @@ function printRunSummary(run) {
|
|
|
599
644
|
);
|
|
600
645
|
console.groupEnd();
|
|
601
646
|
}
|
|
647
|
+
function escapeHtml(s) {
|
|
648
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
649
|
+
}
|
|
650
|
+
function escapeAttr(s) {
|
|
651
|
+
return escapeHtml(s).replace(/"/g, """);
|
|
652
|
+
}
|
|
653
|
+
function copyTextToClipboard(text, btn) {
|
|
654
|
+
const done = () => {
|
|
655
|
+
if (!btn) return;
|
|
656
|
+
const orig = btn.textContent ?? "Copy";
|
|
657
|
+
btn.textContent = "Copied!";
|
|
658
|
+
btn.classList.add("copied");
|
|
659
|
+
setTimeout(() => {
|
|
660
|
+
btn.textContent = orig;
|
|
661
|
+
btn.classList.remove("copied");
|
|
662
|
+
}, 1200);
|
|
663
|
+
};
|
|
664
|
+
const fallback = () => {
|
|
665
|
+
const ta = document.createElement("textarea");
|
|
666
|
+
ta.value = text;
|
|
667
|
+
ta.style.position = "fixed";
|
|
668
|
+
ta.style.opacity = "0";
|
|
669
|
+
document.body.appendChild(ta);
|
|
670
|
+
ta.select();
|
|
671
|
+
try {
|
|
672
|
+
document.execCommand("copy");
|
|
673
|
+
} catch (e) {
|
|
674
|
+
logger.warn(`[Pipeline Debug] Copy failed: ${e}`);
|
|
675
|
+
}
|
|
676
|
+
document.body.removeChild(ta);
|
|
677
|
+
done();
|
|
678
|
+
};
|
|
679
|
+
if (navigator.clipboard?.writeText) {
|
|
680
|
+
navigator.clipboard.writeText(text).then(done).catch(fallback);
|
|
681
|
+
} else {
|
|
682
|
+
fallback();
|
|
683
|
+
}
|
|
684
|
+
}
|
|
602
685
|
function renderUI() {
|
|
603
686
|
if (!_uiContainer) return;
|
|
604
687
|
const runs = runHistory;
|
|
@@ -657,6 +740,13 @@ function renderUI() {
|
|
|
657
740
|
#sk-pipeline-debugger .close-btn { background: #dc3545; color: white; border: none; padding: 0.5rem 1rem; border-radius: 4px; cursor: pointer; }
|
|
658
741
|
#sk-pipeline-debugger .search-box { margin-bottom: 1rem; width: 100%; padding: 0.5rem; border: 1px solid #ced4da; border-radius: 4px; }
|
|
659
742
|
#sk-pipeline-debugger .provenance { font-size: 12px; color: #666; margin-top: 0.25rem; white-space: pre-wrap; font-family: monospace; background: #f8f9fa; padding: 0.5rem; border-radius: 4px; }
|
|
743
|
+
#sk-pipeline-debugger .run-label { display: inline-block; margin-top: 0.25rem; padding: 0.1rem 0.4rem; background: #fff3cd; color: #664d03; border-radius: 3px; font-family: monospace; font-size: 11px; max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; vertical-align: bottom; }
|
|
744
|
+
#sk-pipeline-debugger .label-banner { display: inline-block; padding: 0.25rem 0.6rem; background: #fff3cd; color: #664d03; border-radius: 4px; font-family: monospace; font-size: 13px; margin: 0 0 0.75rem 0; }
|
|
745
|
+
#sk-pipeline-debugger .copy-btn { background: #0d6efd; color: white; border: none; padding: 0.25rem 0.6rem; border-radius: 3px; cursor: pointer; font-size: 12px; margin-left: 0.5rem; }
|
|
746
|
+
#sk-pipeline-debugger .copy-btn:hover { background: #0b5ed7; }
|
|
747
|
+
#sk-pipeline-debugger .copy-btn.copied { background: #198754; }
|
|
748
|
+
#sk-pipeline-debugger .section-head { display: flex; align-items: center; justify-content: space-between; margin-top: 1rem; }
|
|
749
|
+
#sk-pipeline-debugger .section-head h3 { margin: 0; }
|
|
660
750
|
`;
|
|
661
751
|
const runListHtml = runs.length === 0 ? '<div style="padding: 1rem;">No runs captured yet.</div>' : runs.map(
|
|
662
752
|
(r, i) => `
|
|
@@ -664,6 +754,7 @@ function renderUI() {
|
|
|
664
754
|
<strong>${r.timestamp.toLocaleTimeString()}</strong><br/>
|
|
665
755
|
<small>${r.courseName || r.courseId.slice(0, 8)}</small><br/>
|
|
666
756
|
<small>${r.finalCount} cards selected</small>
|
|
757
|
+
${r.hints?._label ? `<br/><span class="run-label" title="${escapeAttr(r.hints._label)}">${escapeHtml(r.hints._label)}</span>` : ""}
|
|
667
758
|
</div>
|
|
668
759
|
`
|
|
669
760
|
).join("");
|
|
@@ -672,11 +763,13 @@ function renderUI() {
|
|
|
672
763
|
const filteredCards = selectedRun.cards.filter(
|
|
673
764
|
(c) => !_cardSearchQuery || c.cardId.toLowerCase().includes(_cardSearchQuery.toLowerCase())
|
|
674
765
|
);
|
|
766
|
+
const labelText = selectedRun.hints?._label ?? "(no label)";
|
|
675
767
|
detailsHtml = `
|
|
676
768
|
<h2>Run: ${selectedRun.runId}</h2>
|
|
769
|
+
<div class="label-banner" title="${escapeAttr(labelText)}">${escapeHtml(labelText)}</div>
|
|
677
770
|
<p>
|
|
678
|
-
<strong>Time:</strong> ${selectedRun.timestamp.toLocaleString()} |
|
|
679
|
-
<strong>Course:</strong> ${selectedRun.courseName || selectedRun.courseId} |
|
|
771
|
+
<strong>Time:</strong> ${selectedRun.timestamp.toLocaleString()} |
|
|
772
|
+
<strong>Course:</strong> ${selectedRun.courseName || selectedRun.courseId} |
|
|
680
773
|
<strong>User ELO:</strong> ${selectedRun.userElo ?? "unknown"}
|
|
681
774
|
</p>
|
|
682
775
|
|
|
@@ -691,7 +784,10 @@ function renderUI() {
|
|
|
691
784
|
</table>
|
|
692
785
|
|
|
693
786
|
${selectedRun.hints ? `
|
|
694
|
-
<
|
|
787
|
+
<div class="section-head">
|
|
788
|
+
<h3>Ephemeral Hints</h3>
|
|
789
|
+
<button class="copy-btn" onclick="window.skuilder.pipeline._copyConfig('${selectedRun.runId}', this)">Copy config</button>
|
|
790
|
+
</div>
|
|
695
791
|
<table>
|
|
696
792
|
${selectedRun.hints._label ? `<tr><th>Label</th><td>${selectedRun.hints._label}</td></tr>` : ""}
|
|
697
793
|
${selectedRun.hints.boostTags ? `<tr><th>Boost Tags</th><td><pre style="margin:0">${JSON.stringify(selectedRun.hints.boostTags, null, 2)}</pre></td></tr>` : ""}
|
|
@@ -715,7 +811,10 @@ function renderUI() {
|
|
|
715
811
|
</tbody>
|
|
716
812
|
</table>
|
|
717
813
|
|
|
718
|
-
<
|
|
814
|
+
<div class="section-head">
|
|
815
|
+
<h3>Cards (${selectedRun.finalCount} selected / ${selectedRun.cards.length} total)</h3>
|
|
816
|
+
<button class="copy-btn" onclick="window.skuilder.pipeline._copyResults('${selectedRun.runId}', this)">Copy results</button>
|
|
817
|
+
</div>
|
|
719
818
|
<input type="text" class="search-box" placeholder="Search Card ID..." value="${_cardSearchQuery}" oninput="window.skuilder.pipeline._setSearch(this.value)">
|
|
720
819
|
|
|
721
820
|
<table>
|
|
@@ -761,7 +860,7 @@ function mountPipelineDebugger() {
|
|
|
761
860
|
win.skuilder = win.skuilder || {};
|
|
762
861
|
win.skuilder.pipeline = pipelineDebugAPI;
|
|
763
862
|
}
|
|
764
|
-
var _activePipeline, MAX_RUNS, runHistory, _uiContainer, _selectedRunIndex, _cardSearchQuery, pipelineDebugAPI;
|
|
863
|
+
var _activePipeline, MAX_RUNS, runHistory, DISCARDED_KEEP_TOP, _uiContainer, _selectedRunIndex, _cardSearchQuery, pipelineDebugAPI;
|
|
765
864
|
var init_PipelineDebugger = __esm({
|
|
766
865
|
"src/core/navigators/PipelineDebugger.ts"() {
|
|
767
866
|
"use strict";
|
|
@@ -770,6 +869,7 @@ var init_PipelineDebugger = __esm({
|
|
|
770
869
|
_activePipeline = null;
|
|
771
870
|
MAX_RUNS = 10;
|
|
772
871
|
runHistory = [];
|
|
872
|
+
DISCARDED_KEEP_TOP = 25;
|
|
773
873
|
_uiContainer = null;
|
|
774
874
|
_selectedRunIndex = null;
|
|
775
875
|
_cardSearchQuery = "";
|
|
@@ -834,7 +934,14 @@ var init_PipelineDebugger = __esm({
|
|
|
834
934
|
return;
|
|
835
935
|
}
|
|
836
936
|
}
|
|
837
|
-
|
|
937
|
+
const runsWithTails = runHistory.filter((r) => r.discardedTail && r.discardedTail.count > 0);
|
|
938
|
+
if (runsWithTails.length > 0) {
|
|
939
|
+
logger.info(
|
|
940
|
+
`[Pipeline Debug] Card '${cardId}' not found in retained cards. ${runsWithTails.length} run(s) have discarded tails that were not retained \u2014 the card may have been a low-score candidate. See run.discardedTail for ranges.`
|
|
941
|
+
);
|
|
942
|
+
} else {
|
|
943
|
+
logger.info(`[Pipeline Debug] Card '${cardId}' not found in recent runs.`);
|
|
944
|
+
}
|
|
838
945
|
},
|
|
839
946
|
/**
|
|
840
947
|
* Explain why reviews may or may not have been selected.
|
|
@@ -1139,6 +1246,50 @@ var init_PipelineDebugger = __esm({
|
|
|
1139
1246
|
_cardSearchQuery = query;
|
|
1140
1247
|
renderUI();
|
|
1141
1248
|
},
|
|
1249
|
+
/**
|
|
1250
|
+
* Internal UI helpers
|
|
1251
|
+
* @internal
|
|
1252
|
+
*/
|
|
1253
|
+
_copyConfig(runId, btn) {
|
|
1254
|
+
const run = runHistory.find((r) => r.runId === runId);
|
|
1255
|
+
if (!run) return;
|
|
1256
|
+
const payload = {
|
|
1257
|
+
runId: run.runId,
|
|
1258
|
+
timestamp: run.timestamp.toISOString(),
|
|
1259
|
+
courseId: run.courseId,
|
|
1260
|
+
courseName: run.courseName,
|
|
1261
|
+
hints: run.hints ?? null
|
|
1262
|
+
};
|
|
1263
|
+
copyTextToClipboard(JSON.stringify(payload, null, 2), btn);
|
|
1264
|
+
},
|
|
1265
|
+
/**
|
|
1266
|
+
* Internal UI helpers
|
|
1267
|
+
* @internal
|
|
1268
|
+
*
|
|
1269
|
+
* Copies an "abridged" view of results: just the selected cards with their
|
|
1270
|
+
* generator, origin, final score, and the top provenance reason. Designed
|
|
1271
|
+
* for pasting into bug reports without flooding with full provenance.
|
|
1272
|
+
*/
|
|
1273
|
+
_copyResults(runId, btn) {
|
|
1274
|
+
const run = runHistory.find((r) => r.runId === runId);
|
|
1275
|
+
if (!run) return;
|
|
1276
|
+
const selected = run.cards.filter((c) => c.selected).sort((a, b) => b.finalScore - a.finalScore).map((c) => ({
|
|
1277
|
+
cardId: c.cardId,
|
|
1278
|
+
generator: c.generator,
|
|
1279
|
+
origin: c.origin,
|
|
1280
|
+
score: Number(c.finalScore.toFixed(3)),
|
|
1281
|
+
topReason: c.provenance[0]?.reason ?? ""
|
|
1282
|
+
}));
|
|
1283
|
+
const payload = {
|
|
1284
|
+
runId: run.runId,
|
|
1285
|
+
label: run.hints?._label ?? null,
|
|
1286
|
+
finalCount: run.finalCount,
|
|
1287
|
+
newSelected: run.newSelected,
|
|
1288
|
+
reviewsSelected: run.reviewsSelected,
|
|
1289
|
+
selected
|
|
1290
|
+
};
|
|
1291
|
+
copyTextToClipboard(JSON.stringify(payload, null, 2), btn);
|
|
1292
|
+
},
|
|
1142
1293
|
/**
|
|
1143
1294
|
* Show help.
|
|
1144
1295
|
*/
|
|
@@ -7054,12 +7205,12 @@ var init_courseDB3 = __esm({
|
|
|
7054
7205
|
}
|
|
7055
7206
|
async getWeightedCards(limit) {
|
|
7056
7207
|
try {
|
|
7057
|
-
const
|
|
7208
|
+
const navigator2 = await this.createNavigator(this.userDB);
|
|
7058
7209
|
if (this._pendingHints) {
|
|
7059
|
-
|
|
7210
|
+
navigator2.setEphemeralHints(this._pendingHints);
|
|
7060
7211
|
this._pendingHints = null;
|
|
7061
7212
|
}
|
|
7062
|
-
return
|
|
7213
|
+
return navigator2.getWeightedCards(limit);
|
|
7063
7214
|
} catch (e) {
|
|
7064
7215
|
logger.error(`[static/courseDB] Error getting weighted cards: ${e}`);
|
|
7065
7216
|
throw e;
|