@vue-skuilder/db 0.1.35 → 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 +229 -25
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +229 -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 +74 -11
- package/src/study/SessionDebugger.ts +8 -0
|
@@ -527,6 +527,7 @@ var PipelineDebugger_exports = {};
|
|
|
527
527
|
__export(PipelineDebugger_exports, {
|
|
528
528
|
buildRunReport: () => buildRunReport,
|
|
529
529
|
captureRun: () => captureRun,
|
|
530
|
+
clearRunHistory: () => clearRunHistory,
|
|
530
531
|
mountPipelineDebugger: () => mountPipelineDebugger,
|
|
531
532
|
pipelineDebugAPI: () => pipelineDebugAPI,
|
|
532
533
|
registerPipelineForDebug: () => registerPipelineForDebug
|
|
@@ -534,6 +535,9 @@ __export(PipelineDebugger_exports, {
|
|
|
534
535
|
function registerPipelineForDebug(pipeline) {
|
|
535
536
|
_activePipeline = pipeline;
|
|
536
537
|
}
|
|
538
|
+
function clearRunHistory() {
|
|
539
|
+
runHistory.length = 0;
|
|
540
|
+
}
|
|
537
541
|
function getOrigin(card) {
|
|
538
542
|
const firstEntry = card.provenance[0];
|
|
539
543
|
if (!firstEntry) return "unknown";
|
|
@@ -562,7 +566,7 @@ function parseCardElo(provenance) {
|
|
|
562
566
|
}
|
|
563
567
|
function buildRunReport(courseId, courseName, generatorName, generators, generatedCount, filters, allCards, selectedCards, userElo, hints) {
|
|
564
568
|
const selectedIds = new Set(selectedCards.map((c) => c.cardId));
|
|
565
|
-
const
|
|
569
|
+
const toReport = (card) => ({
|
|
566
570
|
cardId: card.cardId,
|
|
567
571
|
courseId: card.courseId,
|
|
568
572
|
origin: getOrigin(card),
|
|
@@ -572,7 +576,47 @@ function buildRunReport(courseId, courseName, generatorName, generators, generat
|
|
|
572
576
|
provenance: card.provenance,
|
|
573
577
|
tags: card.tags,
|
|
574
578
|
selected: selectedIds.has(card.cardId)
|
|
575
|
-
})
|
|
579
|
+
});
|
|
580
|
+
const selectedReported = [];
|
|
581
|
+
const nearMissReported = [];
|
|
582
|
+
const discardedTailCards = [];
|
|
583
|
+
let nonSelectedSeen = 0;
|
|
584
|
+
for (const card of allCards) {
|
|
585
|
+
if (selectedIds.has(card.cardId)) {
|
|
586
|
+
selectedReported.push(toReport(card));
|
|
587
|
+
} else if (nonSelectedSeen < DISCARDED_KEEP_TOP) {
|
|
588
|
+
nearMissReported.push(toReport(card));
|
|
589
|
+
nonSelectedSeen++;
|
|
590
|
+
} else {
|
|
591
|
+
discardedTailCards.push(card);
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
const cards = [...selectedReported, ...nearMissReported];
|
|
595
|
+
let discardedTail;
|
|
596
|
+
if (discardedTailCards.length > 0) {
|
|
597
|
+
let scoreMin = Infinity;
|
|
598
|
+
let scoreMax = -Infinity;
|
|
599
|
+
let eloMin = Infinity;
|
|
600
|
+
let eloMax = -Infinity;
|
|
601
|
+
let eloSeen = false;
|
|
602
|
+
for (const c of discardedTailCards) {
|
|
603
|
+
if (c.score < scoreMin) scoreMin = c.score;
|
|
604
|
+
if (c.score > scoreMax) scoreMax = c.score;
|
|
605
|
+
const elo = parseCardElo(c.provenance);
|
|
606
|
+
if (elo !== void 0) {
|
|
607
|
+
eloSeen = true;
|
|
608
|
+
if (elo < eloMin) eloMin = elo;
|
|
609
|
+
if (elo > eloMax) eloMax = elo;
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
const eloFragment = eloSeen ? `, ELO ${eloMin}\u2013${eloMax}` : "";
|
|
613
|
+
discardedTail = {
|
|
614
|
+
count: discardedTailCards.length,
|
|
615
|
+
scoreRange: [scoreMin, scoreMax],
|
|
616
|
+
eloRange: eloSeen ? [eloMin, eloMax] : void 0,
|
|
617
|
+
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.`
|
|
618
|
+
};
|
|
619
|
+
}
|
|
576
620
|
const reviewsSelected = selectedCards.filter((c) => getOrigin(c) === "review").length;
|
|
577
621
|
const newSelected = selectedCards.filter((c) => getOrigin(c) === "new").length;
|
|
578
622
|
return {
|
|
@@ -587,7 +631,8 @@ function buildRunReport(courseId, courseName, generatorName, generators, generat
|
|
|
587
631
|
finalCount: selectedCards.length,
|
|
588
632
|
reviewsSelected,
|
|
589
633
|
newSelected,
|
|
590
|
-
cards
|
|
634
|
+
cards,
|
|
635
|
+
discardedTail
|
|
591
636
|
};
|
|
592
637
|
}
|
|
593
638
|
function formatProvenance(provenance) {
|
|
@@ -623,6 +668,44 @@ function printRunSummary(run) {
|
|
|
623
668
|
);
|
|
624
669
|
console.groupEnd();
|
|
625
670
|
}
|
|
671
|
+
function escapeHtml(s) {
|
|
672
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
673
|
+
}
|
|
674
|
+
function escapeAttr(s) {
|
|
675
|
+
return escapeHtml(s).replace(/"/g, """);
|
|
676
|
+
}
|
|
677
|
+
function copyTextToClipboard(text, btn) {
|
|
678
|
+
const done = () => {
|
|
679
|
+
if (!btn) return;
|
|
680
|
+
const orig = btn.textContent ?? "Copy";
|
|
681
|
+
btn.textContent = "Copied!";
|
|
682
|
+
btn.classList.add("copied");
|
|
683
|
+
setTimeout(() => {
|
|
684
|
+
btn.textContent = orig;
|
|
685
|
+
btn.classList.remove("copied");
|
|
686
|
+
}, 1200);
|
|
687
|
+
};
|
|
688
|
+
const fallback = () => {
|
|
689
|
+
const ta = document.createElement("textarea");
|
|
690
|
+
ta.value = text;
|
|
691
|
+
ta.style.position = "fixed";
|
|
692
|
+
ta.style.opacity = "0";
|
|
693
|
+
document.body.appendChild(ta);
|
|
694
|
+
ta.select();
|
|
695
|
+
try {
|
|
696
|
+
document.execCommand("copy");
|
|
697
|
+
} catch (e) {
|
|
698
|
+
logger.warn(`[Pipeline Debug] Copy failed: ${e}`);
|
|
699
|
+
}
|
|
700
|
+
document.body.removeChild(ta);
|
|
701
|
+
done();
|
|
702
|
+
};
|
|
703
|
+
if (navigator.clipboard?.writeText) {
|
|
704
|
+
navigator.clipboard.writeText(text).then(done).catch(fallback);
|
|
705
|
+
} else {
|
|
706
|
+
fallback();
|
|
707
|
+
}
|
|
708
|
+
}
|
|
626
709
|
function renderUI() {
|
|
627
710
|
if (!_uiContainer) return;
|
|
628
711
|
const runs = runHistory;
|
|
@@ -681,6 +764,13 @@ function renderUI() {
|
|
|
681
764
|
#sk-pipeline-debugger .close-btn { background: #dc3545; color: white; border: none; padding: 0.5rem 1rem; border-radius: 4px; cursor: pointer; }
|
|
682
765
|
#sk-pipeline-debugger .search-box { margin-bottom: 1rem; width: 100%; padding: 0.5rem; border: 1px solid #ced4da; border-radius: 4px; }
|
|
683
766
|
#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; }
|
|
767
|
+
#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; }
|
|
768
|
+
#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; }
|
|
769
|
+
#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; }
|
|
770
|
+
#sk-pipeline-debugger .copy-btn:hover { background: #0b5ed7; }
|
|
771
|
+
#sk-pipeline-debugger .copy-btn.copied { background: #198754; }
|
|
772
|
+
#sk-pipeline-debugger .section-head { display: flex; align-items: center; justify-content: space-between; margin-top: 1rem; }
|
|
773
|
+
#sk-pipeline-debugger .section-head h3 { margin: 0; }
|
|
684
774
|
`;
|
|
685
775
|
const runListHtml = runs.length === 0 ? '<div style="padding: 1rem;">No runs captured yet.</div>' : runs.map(
|
|
686
776
|
(r, i) => `
|
|
@@ -688,6 +778,7 @@ function renderUI() {
|
|
|
688
778
|
<strong>${r.timestamp.toLocaleTimeString()}</strong><br/>
|
|
689
779
|
<small>${r.courseName || r.courseId.slice(0, 8)}</small><br/>
|
|
690
780
|
<small>${r.finalCount} cards selected</small>
|
|
781
|
+
${r.hints?._label ? `<br/><span class="run-label" title="${escapeAttr(r.hints._label)}">${escapeHtml(r.hints._label)}</span>` : ""}
|
|
691
782
|
</div>
|
|
692
783
|
`
|
|
693
784
|
).join("");
|
|
@@ -696,11 +787,13 @@ function renderUI() {
|
|
|
696
787
|
const filteredCards = selectedRun.cards.filter(
|
|
697
788
|
(c) => !_cardSearchQuery || c.cardId.toLowerCase().includes(_cardSearchQuery.toLowerCase())
|
|
698
789
|
);
|
|
790
|
+
const labelText = selectedRun.hints?._label ?? "(no label)";
|
|
699
791
|
detailsHtml = `
|
|
700
792
|
<h2>Run: ${selectedRun.runId}</h2>
|
|
793
|
+
<div class="label-banner" title="${escapeAttr(labelText)}">${escapeHtml(labelText)}</div>
|
|
701
794
|
<p>
|
|
702
|
-
<strong>Time:</strong> ${selectedRun.timestamp.toLocaleString()} |
|
|
703
|
-
<strong>Course:</strong> ${selectedRun.courseName || selectedRun.courseId} |
|
|
795
|
+
<strong>Time:</strong> ${selectedRun.timestamp.toLocaleString()} |
|
|
796
|
+
<strong>Course:</strong> ${selectedRun.courseName || selectedRun.courseId} |
|
|
704
797
|
<strong>User ELO:</strong> ${selectedRun.userElo ?? "unknown"}
|
|
705
798
|
</p>
|
|
706
799
|
|
|
@@ -715,7 +808,10 @@ function renderUI() {
|
|
|
715
808
|
</table>
|
|
716
809
|
|
|
717
810
|
${selectedRun.hints ? `
|
|
718
|
-
<
|
|
811
|
+
<div class="section-head">
|
|
812
|
+
<h3>Ephemeral Hints</h3>
|
|
813
|
+
<button class="copy-btn" onclick="window.skuilder.pipeline._copyConfig('${selectedRun.runId}', this)">Copy config</button>
|
|
814
|
+
</div>
|
|
719
815
|
<table>
|
|
720
816
|
${selectedRun.hints._label ? `<tr><th>Label</th><td>${selectedRun.hints._label}</td></tr>` : ""}
|
|
721
817
|
${selectedRun.hints.boostTags ? `<tr><th>Boost Tags</th><td><pre style="margin:0">${JSON.stringify(selectedRun.hints.boostTags, null, 2)}</pre></td></tr>` : ""}
|
|
@@ -739,7 +835,10 @@ function renderUI() {
|
|
|
739
835
|
</tbody>
|
|
740
836
|
</table>
|
|
741
837
|
|
|
742
|
-
<
|
|
838
|
+
<div class="section-head">
|
|
839
|
+
<h3>Cards (${selectedRun.finalCount} selected / ${selectedRun.cards.length} total)</h3>
|
|
840
|
+
<button class="copy-btn" onclick="window.skuilder.pipeline._copyResults('${selectedRun.runId}', this)">Copy results</button>
|
|
841
|
+
</div>
|
|
743
842
|
<input type="text" class="search-box" placeholder="Search Card ID..." value="${_cardSearchQuery}" oninput="window.skuilder.pipeline._setSearch(this.value)">
|
|
744
843
|
|
|
745
844
|
<table>
|
|
@@ -785,7 +884,7 @@ function mountPipelineDebugger() {
|
|
|
785
884
|
win.skuilder = win.skuilder || {};
|
|
786
885
|
win.skuilder.pipeline = pipelineDebugAPI;
|
|
787
886
|
}
|
|
788
|
-
var _activePipeline, MAX_RUNS, runHistory, _uiContainer, _selectedRunIndex, _cardSearchQuery, pipelineDebugAPI;
|
|
887
|
+
var _activePipeline, MAX_RUNS, runHistory, DISCARDED_KEEP_TOP, _uiContainer, _selectedRunIndex, _cardSearchQuery, pipelineDebugAPI;
|
|
789
888
|
var init_PipelineDebugger = __esm({
|
|
790
889
|
"src/core/navigators/PipelineDebugger.ts"() {
|
|
791
890
|
"use strict";
|
|
@@ -794,6 +893,7 @@ var init_PipelineDebugger = __esm({
|
|
|
794
893
|
_activePipeline = null;
|
|
795
894
|
MAX_RUNS = 10;
|
|
796
895
|
runHistory = [];
|
|
896
|
+
DISCARDED_KEEP_TOP = 25;
|
|
797
897
|
_uiContainer = null;
|
|
798
898
|
_selectedRunIndex = null;
|
|
799
899
|
_cardSearchQuery = "";
|
|
@@ -858,7 +958,14 @@ var init_PipelineDebugger = __esm({
|
|
|
858
958
|
return;
|
|
859
959
|
}
|
|
860
960
|
}
|
|
861
|
-
|
|
961
|
+
const runsWithTails = runHistory.filter((r) => r.discardedTail && r.discardedTail.count > 0);
|
|
962
|
+
if (runsWithTails.length > 0) {
|
|
963
|
+
logger.info(
|
|
964
|
+
`[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.`
|
|
965
|
+
);
|
|
966
|
+
} else {
|
|
967
|
+
logger.info(`[Pipeline Debug] Card '${cardId}' not found in recent runs.`);
|
|
968
|
+
}
|
|
862
969
|
},
|
|
863
970
|
/**
|
|
864
971
|
* Explain why reviews may or may not have been selected.
|
|
@@ -1163,6 +1270,50 @@ var init_PipelineDebugger = __esm({
|
|
|
1163
1270
|
_cardSearchQuery = query;
|
|
1164
1271
|
renderUI();
|
|
1165
1272
|
},
|
|
1273
|
+
/**
|
|
1274
|
+
* Internal UI helpers
|
|
1275
|
+
* @internal
|
|
1276
|
+
*/
|
|
1277
|
+
_copyConfig(runId, btn) {
|
|
1278
|
+
const run = runHistory.find((r) => r.runId === runId);
|
|
1279
|
+
if (!run) return;
|
|
1280
|
+
const payload = {
|
|
1281
|
+
runId: run.runId,
|
|
1282
|
+
timestamp: run.timestamp.toISOString(),
|
|
1283
|
+
courseId: run.courseId,
|
|
1284
|
+
courseName: run.courseName,
|
|
1285
|
+
hints: run.hints ?? null
|
|
1286
|
+
};
|
|
1287
|
+
copyTextToClipboard(JSON.stringify(payload, null, 2), btn);
|
|
1288
|
+
},
|
|
1289
|
+
/**
|
|
1290
|
+
* Internal UI helpers
|
|
1291
|
+
* @internal
|
|
1292
|
+
*
|
|
1293
|
+
* Copies an "abridged" view of results: just the selected cards with their
|
|
1294
|
+
* generator, origin, final score, and the top provenance reason. Designed
|
|
1295
|
+
* for pasting into bug reports without flooding with full provenance.
|
|
1296
|
+
*/
|
|
1297
|
+
_copyResults(runId, btn) {
|
|
1298
|
+
const run = runHistory.find((r) => r.runId === runId);
|
|
1299
|
+
if (!run) return;
|
|
1300
|
+
const selected = run.cards.filter((c) => c.selected).sort((a, b) => b.finalScore - a.finalScore).map((c) => ({
|
|
1301
|
+
cardId: c.cardId,
|
|
1302
|
+
generator: c.generator,
|
|
1303
|
+
origin: c.origin,
|
|
1304
|
+
score: Number(c.finalScore.toFixed(3)),
|
|
1305
|
+
topReason: c.provenance[0]?.reason ?? ""
|
|
1306
|
+
}));
|
|
1307
|
+
const payload = {
|
|
1308
|
+
runId: run.runId,
|
|
1309
|
+
label: run.hints?._label ?? null,
|
|
1310
|
+
finalCount: run.finalCount,
|
|
1311
|
+
newSelected: run.newSelected,
|
|
1312
|
+
reviewsSelected: run.reviewsSelected,
|
|
1313
|
+
selected
|
|
1314
|
+
};
|
|
1315
|
+
copyTextToClipboard(JSON.stringify(payload, null, 2), btn);
|
|
1316
|
+
},
|
|
1166
1317
|
/**
|
|
1167
1318
|
* Show help.
|
|
1168
1319
|
*/
|
|
@@ -7080,12 +7231,12 @@ var init_courseDB3 = __esm({
|
|
|
7080
7231
|
}
|
|
7081
7232
|
async getWeightedCards(limit) {
|
|
7082
7233
|
try {
|
|
7083
|
-
const
|
|
7234
|
+
const navigator2 = await this.createNavigator(this.userDB);
|
|
7084
7235
|
if (this._pendingHints) {
|
|
7085
|
-
|
|
7236
|
+
navigator2.setEphemeralHints(this._pendingHints);
|
|
7086
7237
|
this._pendingHints = null;
|
|
7087
7238
|
}
|
|
7088
|
-
return
|
|
7239
|
+
return navigator2.getWeightedCards(limit);
|
|
7089
7240
|
} catch (e) {
|
|
7090
7241
|
logger.error(`[static/courseDB] Error getting weighted cards: ${e}`);
|
|
7091
7242
|
throw e;
|