@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
package/dist/impl/couch/index.js
CHANGED
|
@@ -648,6 +648,7 @@ var PipelineDebugger_exports = {};
|
|
|
648
648
|
__export(PipelineDebugger_exports, {
|
|
649
649
|
buildRunReport: () => buildRunReport,
|
|
650
650
|
captureRun: () => captureRun,
|
|
651
|
+
clearRunHistory: () => clearRunHistory,
|
|
651
652
|
mountPipelineDebugger: () => mountPipelineDebugger,
|
|
652
653
|
pipelineDebugAPI: () => pipelineDebugAPI,
|
|
653
654
|
registerPipelineForDebug: () => registerPipelineForDebug
|
|
@@ -655,6 +656,9 @@ __export(PipelineDebugger_exports, {
|
|
|
655
656
|
function registerPipelineForDebug(pipeline) {
|
|
656
657
|
_activePipeline = pipeline;
|
|
657
658
|
}
|
|
659
|
+
function clearRunHistory() {
|
|
660
|
+
runHistory.length = 0;
|
|
661
|
+
}
|
|
658
662
|
function getOrigin(card) {
|
|
659
663
|
const firstEntry = card.provenance[0];
|
|
660
664
|
if (!firstEntry) return "unknown";
|
|
@@ -683,7 +687,7 @@ function parseCardElo(provenance) {
|
|
|
683
687
|
}
|
|
684
688
|
function buildRunReport(courseId, courseName, generatorName, generators, generatedCount, filters, allCards, selectedCards, userElo, hints) {
|
|
685
689
|
const selectedIds = new Set(selectedCards.map((c) => c.cardId));
|
|
686
|
-
const
|
|
690
|
+
const toReport = (card) => ({
|
|
687
691
|
cardId: card.cardId,
|
|
688
692
|
courseId: card.courseId,
|
|
689
693
|
origin: getOrigin(card),
|
|
@@ -693,7 +697,47 @@ function buildRunReport(courseId, courseName, generatorName, generators, generat
|
|
|
693
697
|
provenance: card.provenance,
|
|
694
698
|
tags: card.tags,
|
|
695
699
|
selected: selectedIds.has(card.cardId)
|
|
696
|
-
})
|
|
700
|
+
});
|
|
701
|
+
const selectedReported = [];
|
|
702
|
+
const nearMissReported = [];
|
|
703
|
+
const discardedTailCards = [];
|
|
704
|
+
let nonSelectedSeen = 0;
|
|
705
|
+
for (const card of allCards) {
|
|
706
|
+
if (selectedIds.has(card.cardId)) {
|
|
707
|
+
selectedReported.push(toReport(card));
|
|
708
|
+
} else if (nonSelectedSeen < DISCARDED_KEEP_TOP) {
|
|
709
|
+
nearMissReported.push(toReport(card));
|
|
710
|
+
nonSelectedSeen++;
|
|
711
|
+
} else {
|
|
712
|
+
discardedTailCards.push(card);
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
const cards = [...selectedReported, ...nearMissReported];
|
|
716
|
+
let discardedTail;
|
|
717
|
+
if (discardedTailCards.length > 0) {
|
|
718
|
+
let scoreMin = Infinity;
|
|
719
|
+
let scoreMax = -Infinity;
|
|
720
|
+
let eloMin = Infinity;
|
|
721
|
+
let eloMax = -Infinity;
|
|
722
|
+
let eloSeen = false;
|
|
723
|
+
for (const c of discardedTailCards) {
|
|
724
|
+
if (c.score < scoreMin) scoreMin = c.score;
|
|
725
|
+
if (c.score > scoreMax) scoreMax = c.score;
|
|
726
|
+
const elo = parseCardElo(c.provenance);
|
|
727
|
+
if (elo !== void 0) {
|
|
728
|
+
eloSeen = true;
|
|
729
|
+
if (elo < eloMin) eloMin = elo;
|
|
730
|
+
if (elo > eloMax) eloMax = elo;
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
const eloFragment = eloSeen ? `, ELO ${eloMin}\u2013${eloMax}` : "";
|
|
734
|
+
discardedTail = {
|
|
735
|
+
count: discardedTailCards.length,
|
|
736
|
+
scoreRange: [scoreMin, scoreMax],
|
|
737
|
+
eloRange: eloSeen ? [eloMin, eloMax] : void 0,
|
|
738
|
+
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.`
|
|
739
|
+
};
|
|
740
|
+
}
|
|
697
741
|
const reviewsSelected = selectedCards.filter((c) => getOrigin(c) === "review").length;
|
|
698
742
|
const newSelected = selectedCards.filter((c) => getOrigin(c) === "new").length;
|
|
699
743
|
return {
|
|
@@ -708,7 +752,8 @@ function buildRunReport(courseId, courseName, generatorName, generators, generat
|
|
|
708
752
|
finalCount: selectedCards.length,
|
|
709
753
|
reviewsSelected,
|
|
710
754
|
newSelected,
|
|
711
|
-
cards
|
|
755
|
+
cards,
|
|
756
|
+
discardedTail
|
|
712
757
|
};
|
|
713
758
|
}
|
|
714
759
|
function formatProvenance(provenance) {
|
|
@@ -744,6 +789,44 @@ function printRunSummary(run) {
|
|
|
744
789
|
);
|
|
745
790
|
console.groupEnd();
|
|
746
791
|
}
|
|
792
|
+
function escapeHtml(s) {
|
|
793
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
794
|
+
}
|
|
795
|
+
function escapeAttr(s) {
|
|
796
|
+
return escapeHtml(s).replace(/"/g, """);
|
|
797
|
+
}
|
|
798
|
+
function copyTextToClipboard(text, btn) {
|
|
799
|
+
const done = () => {
|
|
800
|
+
if (!btn) return;
|
|
801
|
+
const orig = btn.textContent ?? "Copy";
|
|
802
|
+
btn.textContent = "Copied!";
|
|
803
|
+
btn.classList.add("copied");
|
|
804
|
+
setTimeout(() => {
|
|
805
|
+
btn.textContent = orig;
|
|
806
|
+
btn.classList.remove("copied");
|
|
807
|
+
}, 1200);
|
|
808
|
+
};
|
|
809
|
+
const fallback = () => {
|
|
810
|
+
const ta = document.createElement("textarea");
|
|
811
|
+
ta.value = text;
|
|
812
|
+
ta.style.position = "fixed";
|
|
813
|
+
ta.style.opacity = "0";
|
|
814
|
+
document.body.appendChild(ta);
|
|
815
|
+
ta.select();
|
|
816
|
+
try {
|
|
817
|
+
document.execCommand("copy");
|
|
818
|
+
} catch (e) {
|
|
819
|
+
logger.warn(`[Pipeline Debug] Copy failed: ${e}`);
|
|
820
|
+
}
|
|
821
|
+
document.body.removeChild(ta);
|
|
822
|
+
done();
|
|
823
|
+
};
|
|
824
|
+
if (navigator.clipboard?.writeText) {
|
|
825
|
+
navigator.clipboard.writeText(text).then(done).catch(fallback);
|
|
826
|
+
} else {
|
|
827
|
+
fallback();
|
|
828
|
+
}
|
|
829
|
+
}
|
|
747
830
|
function renderUI() {
|
|
748
831
|
if (!_uiContainer) return;
|
|
749
832
|
const runs = runHistory;
|
|
@@ -802,6 +885,13 @@ function renderUI() {
|
|
|
802
885
|
#sk-pipeline-debugger .close-btn { background: #dc3545; color: white; border: none; padding: 0.5rem 1rem; border-radius: 4px; cursor: pointer; }
|
|
803
886
|
#sk-pipeline-debugger .search-box { margin-bottom: 1rem; width: 100%; padding: 0.5rem; border: 1px solid #ced4da; border-radius: 4px; }
|
|
804
887
|
#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; }
|
|
888
|
+
#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; }
|
|
889
|
+
#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; }
|
|
890
|
+
#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; }
|
|
891
|
+
#sk-pipeline-debugger .copy-btn:hover { background: #0b5ed7; }
|
|
892
|
+
#sk-pipeline-debugger .copy-btn.copied { background: #198754; }
|
|
893
|
+
#sk-pipeline-debugger .section-head { display: flex; align-items: center; justify-content: space-between; margin-top: 1rem; }
|
|
894
|
+
#sk-pipeline-debugger .section-head h3 { margin: 0; }
|
|
805
895
|
`;
|
|
806
896
|
const runListHtml = runs.length === 0 ? '<div style="padding: 1rem;">No runs captured yet.</div>' : runs.map(
|
|
807
897
|
(r, i) => `
|
|
@@ -809,6 +899,7 @@ function renderUI() {
|
|
|
809
899
|
<strong>${r.timestamp.toLocaleTimeString()}</strong><br/>
|
|
810
900
|
<small>${r.courseName || r.courseId.slice(0, 8)}</small><br/>
|
|
811
901
|
<small>${r.finalCount} cards selected</small>
|
|
902
|
+
${r.hints?._label ? `<br/><span class="run-label" title="${escapeAttr(r.hints._label)}">${escapeHtml(r.hints._label)}</span>` : ""}
|
|
812
903
|
</div>
|
|
813
904
|
`
|
|
814
905
|
).join("");
|
|
@@ -817,11 +908,13 @@ function renderUI() {
|
|
|
817
908
|
const filteredCards = selectedRun.cards.filter(
|
|
818
909
|
(c) => !_cardSearchQuery || c.cardId.toLowerCase().includes(_cardSearchQuery.toLowerCase())
|
|
819
910
|
);
|
|
911
|
+
const labelText = selectedRun.hints?._label ?? "(no label)";
|
|
820
912
|
detailsHtml = `
|
|
821
913
|
<h2>Run: ${selectedRun.runId}</h2>
|
|
914
|
+
<div class="label-banner" title="${escapeAttr(labelText)}">${escapeHtml(labelText)}</div>
|
|
822
915
|
<p>
|
|
823
|
-
<strong>Time:</strong> ${selectedRun.timestamp.toLocaleString()} |
|
|
824
|
-
<strong>Course:</strong> ${selectedRun.courseName || selectedRun.courseId} |
|
|
916
|
+
<strong>Time:</strong> ${selectedRun.timestamp.toLocaleString()} |
|
|
917
|
+
<strong>Course:</strong> ${selectedRun.courseName || selectedRun.courseId} |
|
|
825
918
|
<strong>User ELO:</strong> ${selectedRun.userElo ?? "unknown"}
|
|
826
919
|
</p>
|
|
827
920
|
|
|
@@ -836,7 +929,10 @@ function renderUI() {
|
|
|
836
929
|
</table>
|
|
837
930
|
|
|
838
931
|
${selectedRun.hints ? `
|
|
839
|
-
<
|
|
932
|
+
<div class="section-head">
|
|
933
|
+
<h3>Ephemeral Hints</h3>
|
|
934
|
+
<button class="copy-btn" onclick="window.skuilder.pipeline._copyConfig('${selectedRun.runId}', this)">Copy config</button>
|
|
935
|
+
</div>
|
|
840
936
|
<table>
|
|
841
937
|
${selectedRun.hints._label ? `<tr><th>Label</th><td>${selectedRun.hints._label}</td></tr>` : ""}
|
|
842
938
|
${selectedRun.hints.boostTags ? `<tr><th>Boost Tags</th><td><pre style="margin:0">${JSON.stringify(selectedRun.hints.boostTags, null, 2)}</pre></td></tr>` : ""}
|
|
@@ -860,7 +956,10 @@ function renderUI() {
|
|
|
860
956
|
</tbody>
|
|
861
957
|
</table>
|
|
862
958
|
|
|
863
|
-
<
|
|
959
|
+
<div class="section-head">
|
|
960
|
+
<h3>Cards (${selectedRun.finalCount} selected / ${selectedRun.cards.length} total)</h3>
|
|
961
|
+
<button class="copy-btn" onclick="window.skuilder.pipeline._copyResults('${selectedRun.runId}', this)">Copy results</button>
|
|
962
|
+
</div>
|
|
864
963
|
<input type="text" class="search-box" placeholder="Search Card ID..." value="${_cardSearchQuery}" oninput="window.skuilder.pipeline._setSearch(this.value)">
|
|
865
964
|
|
|
866
965
|
<table>
|
|
@@ -906,7 +1005,7 @@ function mountPipelineDebugger() {
|
|
|
906
1005
|
win.skuilder = win.skuilder || {};
|
|
907
1006
|
win.skuilder.pipeline = pipelineDebugAPI;
|
|
908
1007
|
}
|
|
909
|
-
var _activePipeline, MAX_RUNS, runHistory, _uiContainer, _selectedRunIndex, _cardSearchQuery, pipelineDebugAPI;
|
|
1008
|
+
var _activePipeline, MAX_RUNS, runHistory, DISCARDED_KEEP_TOP, _uiContainer, _selectedRunIndex, _cardSearchQuery, pipelineDebugAPI;
|
|
910
1009
|
var init_PipelineDebugger = __esm({
|
|
911
1010
|
"src/core/navigators/PipelineDebugger.ts"() {
|
|
912
1011
|
"use strict";
|
|
@@ -915,6 +1014,7 @@ var init_PipelineDebugger = __esm({
|
|
|
915
1014
|
_activePipeline = null;
|
|
916
1015
|
MAX_RUNS = 10;
|
|
917
1016
|
runHistory = [];
|
|
1017
|
+
DISCARDED_KEEP_TOP = 25;
|
|
918
1018
|
_uiContainer = null;
|
|
919
1019
|
_selectedRunIndex = null;
|
|
920
1020
|
_cardSearchQuery = "";
|
|
@@ -979,7 +1079,14 @@ var init_PipelineDebugger = __esm({
|
|
|
979
1079
|
return;
|
|
980
1080
|
}
|
|
981
1081
|
}
|
|
982
|
-
|
|
1082
|
+
const runsWithTails = runHistory.filter((r) => r.discardedTail && r.discardedTail.count > 0);
|
|
1083
|
+
if (runsWithTails.length > 0) {
|
|
1084
|
+
logger.info(
|
|
1085
|
+
`[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.`
|
|
1086
|
+
);
|
|
1087
|
+
} else {
|
|
1088
|
+
logger.info(`[Pipeline Debug] Card '${cardId}' not found in recent runs.`);
|
|
1089
|
+
}
|
|
983
1090
|
},
|
|
984
1091
|
/**
|
|
985
1092
|
* Explain why reviews may or may not have been selected.
|
|
@@ -1284,6 +1391,50 @@ var init_PipelineDebugger = __esm({
|
|
|
1284
1391
|
_cardSearchQuery = query;
|
|
1285
1392
|
renderUI();
|
|
1286
1393
|
},
|
|
1394
|
+
/**
|
|
1395
|
+
* Internal UI helpers
|
|
1396
|
+
* @internal
|
|
1397
|
+
*/
|
|
1398
|
+
_copyConfig(runId, btn) {
|
|
1399
|
+
const run = runHistory.find((r) => r.runId === runId);
|
|
1400
|
+
if (!run) return;
|
|
1401
|
+
const payload = {
|
|
1402
|
+
runId: run.runId,
|
|
1403
|
+
timestamp: run.timestamp.toISOString(),
|
|
1404
|
+
courseId: run.courseId,
|
|
1405
|
+
courseName: run.courseName,
|
|
1406
|
+
hints: run.hints ?? null
|
|
1407
|
+
};
|
|
1408
|
+
copyTextToClipboard(JSON.stringify(payload, null, 2), btn);
|
|
1409
|
+
},
|
|
1410
|
+
/**
|
|
1411
|
+
* Internal UI helpers
|
|
1412
|
+
* @internal
|
|
1413
|
+
*
|
|
1414
|
+
* Copies an "abridged" view of results: just the selected cards with their
|
|
1415
|
+
* generator, origin, final score, and the top provenance reason. Designed
|
|
1416
|
+
* for pasting into bug reports without flooding with full provenance.
|
|
1417
|
+
*/
|
|
1418
|
+
_copyResults(runId, btn) {
|
|
1419
|
+
const run = runHistory.find((r) => r.runId === runId);
|
|
1420
|
+
if (!run) return;
|
|
1421
|
+
const selected = run.cards.filter((c) => c.selected).sort((a, b) => b.finalScore - a.finalScore).map((c) => ({
|
|
1422
|
+
cardId: c.cardId,
|
|
1423
|
+
generator: c.generator,
|
|
1424
|
+
origin: c.origin,
|
|
1425
|
+
score: Number(c.finalScore.toFixed(3)),
|
|
1426
|
+
topReason: c.provenance[0]?.reason ?? ""
|
|
1427
|
+
}));
|
|
1428
|
+
const payload = {
|
|
1429
|
+
runId: run.runId,
|
|
1430
|
+
label: run.hints?._label ?? null,
|
|
1431
|
+
finalCount: run.finalCount,
|
|
1432
|
+
newSelected: run.newSelected,
|
|
1433
|
+
reviewsSelected: run.reviewsSelected,
|
|
1434
|
+
selected
|
|
1435
|
+
};
|
|
1436
|
+
copyTextToClipboard(JSON.stringify(payload, null, 2), btn);
|
|
1437
|
+
},
|
|
1287
1438
|
/**
|
|
1288
1439
|
* Show help.
|
|
1289
1440
|
*/
|
|
@@ -5389,12 +5540,12 @@ ${e.stack}` : JSON.stringify(e);
|
|
|
5389
5540
|
async getWeightedCards(limit) {
|
|
5390
5541
|
const u = await this._getCurrentUser();
|
|
5391
5542
|
try {
|
|
5392
|
-
const
|
|
5543
|
+
const navigator2 = await this.createNavigator(u);
|
|
5393
5544
|
if (this._pendingHints) {
|
|
5394
|
-
|
|
5545
|
+
navigator2.setEphemeralHints(this._pendingHints);
|
|
5395
5546
|
this._pendingHints = null;
|
|
5396
5547
|
}
|
|
5397
|
-
return
|
|
5548
|
+
return navigator2.getWeightedCards(limit);
|
|
5398
5549
|
} catch (e) {
|
|
5399
5550
|
logger.error(`[courseDB] Error getting weighted cards: ${e}`);
|
|
5400
5551
|
throw e;
|