@vue-skuilder/db 0.1.32-e → 0.1.32-f
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 +20 -3
- package/dist/core/index.d.ts +20 -3
- package/dist/core/index.js +461 -30
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.mjs +461 -30
- package/dist/core/index.mjs.map +1 -1
- package/dist/impl/couch/index.js +461 -30
- package/dist/impl/couch/index.js.map +1 -1
- package/dist/impl/couch/index.mjs +461 -30
- package/dist/impl/couch/index.mjs.map +1 -1
- package/dist/impl/static/index.js +457 -28
- package/dist/impl/static/index.js.map +1 -1
- package/dist/impl/static/index.mjs +457 -28
- package/dist/impl/static/index.mjs.map +1 -1
- package/dist/index.d.cts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +467 -32
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +467 -32
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -4
- package/src/core/navigators/Pipeline.ts +104 -13
- package/src/core/navigators/PipelineDebugger.ts +296 -3
- package/src/core/navigators/generators/CompositeGenerator.ts +4 -1
- package/src/core/navigators/generators/prescribed.ts +246 -22
- package/src/impl/couch/courseDB.ts +3 -2
- package/src/study/SessionController.ts +1 -0
- package/src/study/services/CardHydrationService.ts +6 -1
- package/tests/core/navigators/CompositeGenerator.test.ts +14 -50
- package/tests/core/navigators/Pipeline.test.ts +13 -12
|
@@ -514,8 +514,9 @@ function getOrigin(card) {
|
|
|
514
514
|
const firstEntry = card.provenance[0];
|
|
515
515
|
if (!firstEntry) return "unknown";
|
|
516
516
|
const reason = firstEntry.reason?.toLowerCase() || "";
|
|
517
|
-
|
|
518
|
-
if (reason.includes("
|
|
517
|
+
const strategy = firstEntry.strategy?.toLowerCase() || "";
|
|
518
|
+
if (reason.includes("new card") || strategy.includes("elo")) return "new";
|
|
519
|
+
if (reason.includes("review") || strategy.includes("srs")) return "review";
|
|
519
520
|
return "unknown";
|
|
520
521
|
}
|
|
521
522
|
function captureRun(report) {
|
|
@@ -535,12 +536,13 @@ function parseCardElo(provenance) {
|
|
|
535
536
|
const match = eloEntry.reason.match(/card:\s*(\d+)/);
|
|
536
537
|
return match ? parseInt(match[1], 10) : void 0;
|
|
537
538
|
}
|
|
538
|
-
function buildRunReport(courseId, courseName, generatorName, generators, generatedCount, filters, allCards, selectedCards, userElo) {
|
|
539
|
+
function buildRunReport(courseId, courseName, generatorName, generators, generatedCount, filters, allCards, selectedCards, userElo, hints) {
|
|
539
540
|
const selectedIds = new Set(selectedCards.map((c) => c.cardId));
|
|
540
541
|
const cards = allCards.map((card) => ({
|
|
541
542
|
cardId: card.cardId,
|
|
542
543
|
courseId: card.courseId,
|
|
543
544
|
origin: getOrigin(card),
|
|
545
|
+
generator: card.provenance[0]?.strategyName || card.provenance[0]?.strategy,
|
|
544
546
|
finalScore: card.score,
|
|
545
547
|
cardElo: parseCardElo(card.provenance),
|
|
546
548
|
provenance: card.provenance,
|
|
@@ -557,6 +559,7 @@ function buildRunReport(courseId, courseName, generatorName, generators, generat
|
|
|
557
559
|
generators,
|
|
558
560
|
generatedCount,
|
|
559
561
|
filters,
|
|
562
|
+
hints,
|
|
560
563
|
finalCount: selectedCards.length,
|
|
561
564
|
reviewsSelected,
|
|
562
565
|
newSelected,
|
|
@@ -596,13 +599,169 @@ function printRunSummary(run) {
|
|
|
596
599
|
);
|
|
597
600
|
console.groupEnd();
|
|
598
601
|
}
|
|
602
|
+
function renderUI() {
|
|
603
|
+
if (!_uiContainer) return;
|
|
604
|
+
const runs = runHistory;
|
|
605
|
+
const selectedRun = _selectedRunIndex !== null ? runs[_selectedRunIndex] : null;
|
|
606
|
+
const styles = `
|
|
607
|
+
#sk-pipeline-debugger {
|
|
608
|
+
position: fixed;
|
|
609
|
+
top: 0;
|
|
610
|
+
left: 0;
|
|
611
|
+
width: 100vw;
|
|
612
|
+
height: 100vh;
|
|
613
|
+
background: #f8f9fa;
|
|
614
|
+
color: #212529;
|
|
615
|
+
z-index: 999999;
|
|
616
|
+
display: flex;
|
|
617
|
+
flex-direction: column;
|
|
618
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
|
619
|
+
font-size: 14px;
|
|
620
|
+
}
|
|
621
|
+
#sk-pipeline-debugger header {
|
|
622
|
+
padding: 1rem;
|
|
623
|
+
background: #343a40;
|
|
624
|
+
color: white;
|
|
625
|
+
display: flex;
|
|
626
|
+
justify-content: space-between;
|
|
627
|
+
align-items: center;
|
|
628
|
+
}
|
|
629
|
+
#sk-pipeline-debugger .container {
|
|
630
|
+
display: flex;
|
|
631
|
+
flex: 1;
|
|
632
|
+
overflow: hidden;
|
|
633
|
+
}
|
|
634
|
+
#sk-pipeline-debugger .sidebar {
|
|
635
|
+
width: 300px;
|
|
636
|
+
border-right: 1px solid #dee2e6;
|
|
637
|
+
overflow-y: auto;
|
|
638
|
+
background: white;
|
|
639
|
+
}
|
|
640
|
+
#sk-pipeline-debugger .main-content {
|
|
641
|
+
flex: 1;
|
|
642
|
+
overflow-y: auto;
|
|
643
|
+
padding: 1.5rem;
|
|
644
|
+
}
|
|
645
|
+
#sk-pipeline-debugger .run-item {
|
|
646
|
+
padding: 0.75rem 1rem;
|
|
647
|
+
border-bottom: 1px solid #eee;
|
|
648
|
+
cursor: pointer;
|
|
649
|
+
}
|
|
650
|
+
#sk-pipeline-debugger .run-item:hover { background: #f1f3f5; }
|
|
651
|
+
#sk-pipeline-debugger .run-item.active { background: #e9ecef; border-left: 4px solid #007bff; }
|
|
652
|
+
#sk-pipeline-debugger h2, #sk-pipeline-debugger h3 { margin-top: 0; }
|
|
653
|
+
#sk-pipeline-debugger table { width: 100%; border-collapse: collapse; margin-bottom: 1rem; background: white; }
|
|
654
|
+
#sk-pipeline-debugger th, #sk-pipeline-debugger td { border: 1px solid #dee2e6; padding: 0.5rem; text-align: left; }
|
|
655
|
+
#sk-pipeline-debugger th { background: #f1f3f5; }
|
|
656
|
+
#sk-pipeline-debugger code { background: #f1f3f5; padding: 0.1rem 0.3rem; border-radius: 3px; font-family: monospace; }
|
|
657
|
+
#sk-pipeline-debugger .close-btn { background: #dc3545; color: white; border: none; padding: 0.5rem 1rem; border-radius: 4px; cursor: pointer; }
|
|
658
|
+
#sk-pipeline-debugger .search-box { margin-bottom: 1rem; width: 100%; padding: 0.5rem; border: 1px solid #ced4da; border-radius: 4px; }
|
|
659
|
+
#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; }
|
|
660
|
+
`;
|
|
661
|
+
const runListHtml = runs.length === 0 ? '<div style="padding: 1rem;">No runs captured yet.</div>' : runs.map(
|
|
662
|
+
(r, i) => `
|
|
663
|
+
<div class="run-item ${i === _selectedRunIndex ? "active" : ""}" onclick="window.skuilder.pipeline._selectRun(${i})">
|
|
664
|
+
<strong>${r.timestamp.toLocaleTimeString()}</strong><br/>
|
|
665
|
+
<small>${r.courseName || r.courseId.slice(0, 8)}</small><br/>
|
|
666
|
+
<small>${r.finalCount} cards selected</small>
|
|
667
|
+
</div>
|
|
668
|
+
`
|
|
669
|
+
).join("");
|
|
670
|
+
let detailsHtml = '<div style="color: #6c757d; text-align: center; margin-top: 5rem;">Select a run to see details</div>';
|
|
671
|
+
if (selectedRun) {
|
|
672
|
+
const filteredCards = selectedRun.cards.filter(
|
|
673
|
+
(c) => !_cardSearchQuery || c.cardId.toLowerCase().includes(_cardSearchQuery.toLowerCase())
|
|
674
|
+
);
|
|
675
|
+
detailsHtml = `
|
|
676
|
+
<h2>Run: ${selectedRun.runId}</h2>
|
|
677
|
+
<p>
|
|
678
|
+
<strong>Time:</strong> ${selectedRun.timestamp.toLocaleString()} |
|
|
679
|
+
<strong>Course:</strong> ${selectedRun.courseName || selectedRun.courseId} |
|
|
680
|
+
<strong>User ELO:</strong> ${selectedRun.userElo ?? "unknown"}
|
|
681
|
+
</p>
|
|
682
|
+
|
|
683
|
+
<h3>Pipeline Config</h3>
|
|
684
|
+
<table>
|
|
685
|
+
<tr><th>Generator</th><td>${selectedRun.generatorName} (${selectedRun.generatedCount} candidates)</td></tr>
|
|
686
|
+
${(selectedRun.generators || []).map(
|
|
687
|
+
(g) => `
|
|
688
|
+
<tr><td style="padding-left: 2rem;">\u21B3 ${g.name}</td><td>${g.cardCount} cards (${g.newCount} new, ${g.reviewCount} review, top: ${g.topScore.toFixed(2)})</td></tr>
|
|
689
|
+
`
|
|
690
|
+
).join("")}
|
|
691
|
+
</table>
|
|
692
|
+
|
|
693
|
+
${selectedRun.hints ? `
|
|
694
|
+
<h3>Ephemeral Hints</h3>
|
|
695
|
+
<table>
|
|
696
|
+
${selectedRun.hints._label ? `<tr><th>Label</th><td>${selectedRun.hints._label}</td></tr>` : ""}
|
|
697
|
+
${selectedRun.hints.boostTags ? `<tr><th>Boost Tags</th><td><pre style="margin:0">${JSON.stringify(selectedRun.hints.boostTags, null, 2)}</pre></td></tr>` : ""}
|
|
698
|
+
${selectedRun.hints.boostCards ? `<tr><th>Boost Cards</th><td><pre style="margin:0">${JSON.stringify(selectedRun.hints.boostCards, null, 2)}</pre></td></tr>` : ""}
|
|
699
|
+
${selectedRun.hints.requireTags ? `<tr><th>Require Tags</th><td>${selectedRun.hints.requireTags.join(", ")}</td></tr>` : ""}
|
|
700
|
+
${selectedRun.hints.requireCards ? `<tr><th>Require Cards</th><td>${selectedRun.hints.requireCards.join(", ")}</td></tr>` : ""}
|
|
701
|
+
${selectedRun.hints.excludeTags ? `<tr><th>Exclude Tags</th><td>${selectedRun.hints.excludeTags.join(", ")}</td></tr>` : ""}
|
|
702
|
+
${selectedRun.hints.excludeCards ? `<tr><th>Exclude Cards</th><td>${selectedRun.hints.excludeCards.join(", ")}</td></tr>` : ""}
|
|
703
|
+
</table>
|
|
704
|
+
` : ""}
|
|
705
|
+
|
|
706
|
+
<h3>Filter Impact</h3>
|
|
707
|
+
<table>
|
|
708
|
+
<thead><tr><th>Filter</th><th>Boosted</th><th>Penalized</th><th>Passed</th><th>Removed</th></tr></thead>
|
|
709
|
+
<tbody>
|
|
710
|
+
${selectedRun.filters.map(
|
|
711
|
+
(f) => `
|
|
712
|
+
<tr><td>${f.name}</td><td>\u2191${f.boosted}</td><td>\u2193${f.penalized}</td><td>=${f.passed}</td><td>\u2715${f.removed}</td></tr>
|
|
713
|
+
`
|
|
714
|
+
).join("")}
|
|
715
|
+
</tbody>
|
|
716
|
+
</table>
|
|
717
|
+
|
|
718
|
+
<h3>Cards (${selectedRun.finalCount} selected / ${selectedRun.cards.length} total)</h3>
|
|
719
|
+
<input type="text" class="search-box" placeholder="Search Card ID..." value="${_cardSearchQuery}" oninput="window.skuilder.pipeline._setSearch(this.value)">
|
|
720
|
+
|
|
721
|
+
<table>
|
|
722
|
+
<thead><tr><th>ID</th><th>Generator</th><th>Origin</th><th>Score</th><th>Selected</th></tr></thead>
|
|
723
|
+
<tbody>
|
|
724
|
+
${filteredCards.map(
|
|
725
|
+
(c) => `
|
|
726
|
+
<tr>
|
|
727
|
+
<td><code>${c.cardId}</code></td>
|
|
728
|
+
<td>${c.generator || "unknown"}</td>
|
|
729
|
+
<td>${c.origin}</td>
|
|
730
|
+
<td>${c.finalScore.toFixed(3)}</td>
|
|
731
|
+
<td>${c.selected ? "\u2705" : "\u274C"}</td>
|
|
732
|
+
</tr>
|
|
733
|
+
${c.selected || _cardSearchQuery ? `
|
|
734
|
+
<tr>
|
|
735
|
+
<td colspan="5">
|
|
736
|
+
<div class="provenance">${formatProvenance(c.provenance)}</div>
|
|
737
|
+
</td>
|
|
738
|
+
</tr>
|
|
739
|
+
` : ""}
|
|
740
|
+
`
|
|
741
|
+
).join("")}
|
|
742
|
+
</tbody>
|
|
743
|
+
</table>
|
|
744
|
+
`;
|
|
745
|
+
}
|
|
746
|
+
_uiContainer.innerHTML = `
|
|
747
|
+
<style>${styles}</style>
|
|
748
|
+
<header>
|
|
749
|
+
<strong>Pipeline Debugger</strong>
|
|
750
|
+
<button class="close-btn" onclick="window.skuilder.pipeline.ui()">Close</button>
|
|
751
|
+
</header>
|
|
752
|
+
<div class="container">
|
|
753
|
+
<div class="sidebar">${runListHtml}</div>
|
|
754
|
+
<div class="main-content">${detailsHtml}</div>
|
|
755
|
+
</div>
|
|
756
|
+
`;
|
|
757
|
+
}
|
|
599
758
|
function mountPipelineDebugger() {
|
|
600
759
|
if (typeof window === "undefined") return;
|
|
601
760
|
const win = window;
|
|
602
761
|
win.skuilder = win.skuilder || {};
|
|
603
762
|
win.skuilder.pipeline = pipelineDebugAPI;
|
|
604
763
|
}
|
|
605
|
-
var _activePipeline, MAX_RUNS, runHistory, pipelineDebugAPI;
|
|
764
|
+
var _activePipeline, MAX_RUNS, runHistory, _uiContainer, _selectedRunIndex, _cardSearchQuery, pipelineDebugAPI;
|
|
606
765
|
var init_PipelineDebugger = __esm({
|
|
607
766
|
"src/core/navigators/PipelineDebugger.ts"() {
|
|
608
767
|
"use strict";
|
|
@@ -611,6 +770,9 @@ var init_PipelineDebugger = __esm({
|
|
|
611
770
|
_activePipeline = null;
|
|
612
771
|
MAX_RUNS = 10;
|
|
613
772
|
runHistory = [];
|
|
773
|
+
_uiContainer = null;
|
|
774
|
+
_selectedRunIndex = null;
|
|
775
|
+
_cardSearchQuery = "";
|
|
614
776
|
pipelineDebugAPI = {
|
|
615
777
|
/**
|
|
616
778
|
* Get raw run history for programmatic access.
|
|
@@ -750,16 +912,20 @@ var init_PipelineDebugger = __esm({
|
|
|
750
912
|
const mode = reason.match(/mode=([^;]+)/)?.[1] ?? "unknown";
|
|
751
913
|
const blocked = reason.match(/blocked=([^;]+)/)?.[1] ?? "unknown";
|
|
752
914
|
const blockedTargets = reason.match(/blockedTargets=([^;]+)/)?.[1] ?? "none";
|
|
915
|
+
const supportCard = reason.match(/supportCard=([^;]+)/)?.[1] ?? "none";
|
|
753
916
|
const supportTags = reason.match(/supportTags=([^;]+)/)?.[1] ?? "none";
|
|
754
917
|
const multiplier = reason.match(/multiplier=([^;]+)/)?.[1] ?? "unknown";
|
|
918
|
+
const supportSource = mode === "discovered-support" ? "discovered" : mode === "support" ? "authored" : "n/a";
|
|
755
919
|
return {
|
|
756
920
|
group: parsedGroup,
|
|
757
921
|
mode,
|
|
922
|
+
supportSource,
|
|
758
923
|
cardId: card.cardId,
|
|
759
924
|
selected: card.selected ? "yes" : "no",
|
|
760
925
|
finalScore: card.finalScore.toFixed(3),
|
|
761
926
|
blocked,
|
|
762
927
|
blockedTargets,
|
|
928
|
+
supportCard,
|
|
763
929
|
supportTags,
|
|
764
930
|
multiplier
|
|
765
931
|
};
|
|
@@ -775,6 +941,8 @@ var init_PipelineDebugger = __esm({
|
|
|
775
941
|
const selectedRows = rows.filter((r) => r.selected === "yes");
|
|
776
942
|
const blockedTargetSet = /* @__PURE__ */ new Set();
|
|
777
943
|
const supportTagSet = /* @__PURE__ */ new Set();
|
|
944
|
+
const authoredSupportSet = /* @__PURE__ */ new Set();
|
|
945
|
+
const discoveredSupportSet = /* @__PURE__ */ new Set();
|
|
778
946
|
for (const row of rows) {
|
|
779
947
|
if (row.blockedTargets && row.blockedTargets !== "none") {
|
|
780
948
|
row.blockedTargets.split("|").filter(Boolean).forEach((t) => blockedTargetSet.add(t));
|
|
@@ -782,6 +950,13 @@ var init_PipelineDebugger = __esm({
|
|
|
782
950
|
if (row.supportTags && row.supportTags !== "none") {
|
|
783
951
|
row.supportTags.split("|").filter(Boolean).forEach((t) => supportTagSet.add(t));
|
|
784
952
|
}
|
|
953
|
+
if (row.supportCard && row.supportCard !== "none") {
|
|
954
|
+
if (row.supportSource === "discovered") {
|
|
955
|
+
discoveredSupportSet.add(row.supportCard);
|
|
956
|
+
} else if (row.supportSource === "authored") {
|
|
957
|
+
authoredSupportSet.add(row.supportCard);
|
|
958
|
+
}
|
|
959
|
+
}
|
|
785
960
|
}
|
|
786
961
|
logger.info(`Prescribed cards in run: ${rows.length}`);
|
|
787
962
|
logger.info(`Selected prescribed cards: ${selectedRows.length}`);
|
|
@@ -791,6 +966,12 @@ var init_PipelineDebugger = __esm({
|
|
|
791
966
|
logger.info(
|
|
792
967
|
`Resolved support tags referenced: ${supportTagSet.size > 0 ? [...supportTagSet].join(", ") : "none"}`
|
|
793
968
|
);
|
|
969
|
+
logger.info(
|
|
970
|
+
`Authored support cards emitted: ${authoredSupportSet.size > 0 ? [...authoredSupportSet].join(", ") : "none"}`
|
|
971
|
+
);
|
|
972
|
+
logger.info(
|
|
973
|
+
`Discovered support cards emitted: ${discoveredSupportSet.size > 0 ? [...discoveredSupportSet].join(", ") : "none"}`
|
|
974
|
+
);
|
|
794
975
|
console.groupEnd();
|
|
795
976
|
},
|
|
796
977
|
/**
|
|
@@ -925,6 +1106,39 @@ var init_PipelineDebugger = __esm({
|
|
|
925
1106
|
Object.fromEntries(entries.map(([tag, data]) => [tag, { score: Math.round(data.score), count: data.count }]))
|
|
926
1107
|
);
|
|
927
1108
|
},
|
|
1109
|
+
/**
|
|
1110
|
+
* Toggle the full-screen UI debugger.
|
|
1111
|
+
*/
|
|
1112
|
+
ui() {
|
|
1113
|
+
if (_uiContainer) {
|
|
1114
|
+
document.body.removeChild(_uiContainer);
|
|
1115
|
+
_uiContainer = null;
|
|
1116
|
+
return;
|
|
1117
|
+
}
|
|
1118
|
+
_uiContainer = document.createElement("div");
|
|
1119
|
+
_uiContainer.id = "sk-pipeline-debugger";
|
|
1120
|
+
document.body.appendChild(_uiContainer);
|
|
1121
|
+
if (_selectedRunIndex === null && runHistory.length > 0) {
|
|
1122
|
+
_selectedRunIndex = 0;
|
|
1123
|
+
}
|
|
1124
|
+
renderUI();
|
|
1125
|
+
},
|
|
1126
|
+
/**
|
|
1127
|
+
* Internal UI helpers
|
|
1128
|
+
* @internal
|
|
1129
|
+
*/
|
|
1130
|
+
_selectRun(index) {
|
|
1131
|
+
_selectedRunIndex = index;
|
|
1132
|
+
renderUI();
|
|
1133
|
+
},
|
|
1134
|
+
/**
|
|
1135
|
+
* Internal UI helpers
|
|
1136
|
+
* @internal
|
|
1137
|
+
*/
|
|
1138
|
+
_setSearch(query) {
|
|
1139
|
+
_cardSearchQuery = query;
|
|
1140
|
+
renderUI();
|
|
1141
|
+
},
|
|
928
1142
|
/**
|
|
929
1143
|
* Show help.
|
|
930
1144
|
*/
|
|
@@ -933,6 +1147,7 @@ var init_PipelineDebugger = __esm({
|
|
|
933
1147
|
\u{1F527} Pipeline Debug API
|
|
934
1148
|
|
|
935
1149
|
Commands:
|
|
1150
|
+
.ui() Toggle full-screen UI debugger
|
|
936
1151
|
.showLastRun() Show summary of most recent pipeline run
|
|
937
1152
|
.showRun(id|index) Show summary of a specific run (by index or ID suffix)
|
|
938
1153
|
.showCard(cardId) Show provenance trail for a specific card
|
|
@@ -949,6 +1164,7 @@ Commands:
|
|
|
949
1164
|
.help() Show this help message
|
|
950
1165
|
|
|
951
1166
|
Example:
|
|
1167
|
+
window.skuilder.pipeline.ui()
|
|
952
1168
|
window.skuilder.pipeline.showLastRun()
|
|
953
1169
|
window.skuilder.pipeline.showRun(1)
|
|
954
1170
|
await window.skuilder.pipeline.diagnoseCardSpace()
|
|
@@ -1111,7 +1327,7 @@ var init_CompositeGenerator = __esm({
|
|
|
1111
1327
|
for (const [, items] of byCardId) {
|
|
1112
1328
|
const cards2 = items.map((i) => i.card);
|
|
1113
1329
|
const aggregatedScore = this.aggregateScores(items);
|
|
1114
|
-
const finalScore = Math.
|
|
1330
|
+
const finalScore = Math.max(0, aggregatedScore);
|
|
1115
1331
|
const mergedProvenance = cards2.flatMap((c) => c.provenance);
|
|
1116
1332
|
const initialScore = cards2[0].score;
|
|
1117
1333
|
const action = finalScore > initialScore ? "boosted" : finalScore < initialScore ? "penalized" : "passed";
|
|
@@ -1308,10 +1524,26 @@ function matchesTagPattern(tag, pattern) {
|
|
|
1308
1524
|
const re = new RegExp(`^${escaped}$`);
|
|
1309
1525
|
return re.test(tag);
|
|
1310
1526
|
}
|
|
1527
|
+
function extractWordStem(cardId) {
|
|
1528
|
+
for (const prefix of ["c-ml-", "c-ws-", "c-spelling-"]) {
|
|
1529
|
+
if (cardId.startsWith(prefix)) {
|
|
1530
|
+
const rest = cardId.slice(prefix.length);
|
|
1531
|
+
const lastDash = rest.lastIndexOf("-");
|
|
1532
|
+
return lastDash > 0 ? rest.slice(0, lastDash) : rest;
|
|
1533
|
+
}
|
|
1534
|
+
}
|
|
1535
|
+
return cardId;
|
|
1536
|
+
}
|
|
1537
|
+
function shuffleInPlace(arr) {
|
|
1538
|
+
for (let i = arr.length - 1; i > 0; i--) {
|
|
1539
|
+
const j = Math.floor(Math.random() * (i + 1));
|
|
1540
|
+
[arr[i], arr[j]] = [arr[j], arr[i]];
|
|
1541
|
+
}
|
|
1542
|
+
}
|
|
1311
1543
|
function pickTopByScore(cards, limit) {
|
|
1312
1544
|
return [...cards].sort((a, b) => b.score - a.score || a.cardId.localeCompare(b.cardId)).slice(0, limit);
|
|
1313
1545
|
}
|
|
1314
|
-
var DEFAULT_FRESHNESS_WINDOW, DEFAULT_MAX_DIRECT_PER_RUN, DEFAULT_MAX_SUPPORT_PER_RUN, DEFAULT_HIERARCHY_DEPTH, DEFAULT_MIN_COUNT, BASE_TARGET_SCORE, BASE_SUPPORT_SCORE, MAX_TARGET_MULTIPLIER, MAX_SUPPORT_MULTIPLIER,
|
|
1546
|
+
var DEFAULT_FRESHNESS_WINDOW, DEFAULT_MAX_DIRECT_PER_RUN, DEFAULT_MAX_SUPPORT_PER_RUN, DEFAULT_HIERARCHY_DEPTH, DEFAULT_MIN_COUNT, BASE_TARGET_SCORE, BASE_SUPPORT_SCORE, DISCOVERED_SUPPORT_SCORE, MAX_TARGET_MULTIPLIER, MAX_SUPPORT_MULTIPLIER, PRESCRIBED_DEBUG_VERSION, PrescribedCardsGenerator;
|
|
1315
1547
|
var init_prescribed = __esm({
|
|
1316
1548
|
"src/core/navigators/generators/prescribed.ts"() {
|
|
1317
1549
|
"use strict";
|
|
@@ -1324,11 +1556,10 @@ var init_prescribed = __esm({
|
|
|
1324
1556
|
DEFAULT_MIN_COUNT = 3;
|
|
1325
1557
|
BASE_TARGET_SCORE = 1;
|
|
1326
1558
|
BASE_SUPPORT_SCORE = 0.8;
|
|
1559
|
+
DISCOVERED_SUPPORT_SCORE = 12;
|
|
1327
1560
|
MAX_TARGET_MULTIPLIER = 8;
|
|
1328
1561
|
MAX_SUPPORT_MULTIPLIER = 4;
|
|
1329
|
-
|
|
1330
|
-
LESSON_GATE_PENALTY_TAG_HINT = "concept:";
|
|
1331
|
-
PRESCRIBED_DEBUG_VERSION = "testversion-prescribed-v2";
|
|
1562
|
+
PRESCRIBED_DEBUG_VERSION = "testversion-prescribed-v3";
|
|
1332
1563
|
PrescribedCardsGenerator = class extends ContentNavigator {
|
|
1333
1564
|
name;
|
|
1334
1565
|
config;
|
|
@@ -1364,6 +1595,20 @@ var init_prescribed = __esm({
|
|
|
1364
1595
|
const allSupportIds = dedupe(this.config.groups.flatMap((g) => g.supportCardIds ?? []));
|
|
1365
1596
|
const allRelevantIds = dedupe([...allTargetIds, ...allSupportIds]);
|
|
1366
1597
|
const tagsByCard = allRelevantIds.length > 0 ? await this.course.getAppliedTagsBatch(allRelevantIds) : /* @__PURE__ */ new Map();
|
|
1598
|
+
const courseTagDocs = await this.course.getCourseTagStubs().catch(
|
|
1599
|
+
() => ({
|
|
1600
|
+
rows: [],
|
|
1601
|
+
offset: 0,
|
|
1602
|
+
total_rows: 0
|
|
1603
|
+
})
|
|
1604
|
+
);
|
|
1605
|
+
const cardsByTag = /* @__PURE__ */ new Map();
|
|
1606
|
+
for (const row of courseTagDocs.rows ?? []) {
|
|
1607
|
+
const tagDoc = row.doc;
|
|
1608
|
+
if (tagDoc?.name && Array.isArray(tagDoc.taggedCards)) {
|
|
1609
|
+
cardsByTag.set(tagDoc.name, [...tagDoc.taggedCards]);
|
|
1610
|
+
}
|
|
1611
|
+
}
|
|
1367
1612
|
const nextState = {
|
|
1368
1613
|
updatedAt: isoNow(),
|
|
1369
1614
|
groups: {}
|
|
@@ -1378,11 +1623,31 @@ var init_prescribed = __esm({
|
|
|
1378
1623
|
activeIds,
|
|
1379
1624
|
seenIds,
|
|
1380
1625
|
tagsByCard,
|
|
1626
|
+
cardsByTag,
|
|
1381
1627
|
hierarchyConfigs,
|
|
1382
1628
|
userTagElo,
|
|
1383
1629
|
userGlobalElo
|
|
1384
1630
|
});
|
|
1385
1631
|
groupRuntimes.push(runtime);
|
|
1632
|
+
logger.info(
|
|
1633
|
+
`[Prescribed] Group '${group.id}': ${group.targetCardIds.length} targets total, ${runtime.encounteredTargets.size} encountered, ${runtime.pendingTargets.length} pending (${runtime.surfaceableTargets.length} surfaceable, ${runtime.blockedTargets.length} blocked), ${runtime.supportCandidates.length} authored support candidates, ${runtime.discoveredSupportCandidates.length} discovered support candidates, pressure=${runtime.pressureMultiplier.toFixed(2)}`
|
|
1634
|
+
);
|
|
1635
|
+
if (runtime.blockedTargets.length > 0) {
|
|
1636
|
+
logger.info(
|
|
1637
|
+
`[Prescribed] Group '${group.id}' blocked targets: ${runtime.blockedTargets.join(", ")}`
|
|
1638
|
+
);
|
|
1639
|
+
logger.info(
|
|
1640
|
+
`[Prescribed] Group '${group.id}' support tags needed: ${runtime.supportTags.join(", ") || "(none)"}`
|
|
1641
|
+
);
|
|
1642
|
+
logger.info(
|
|
1643
|
+
`[Prescribed] Group '${group.id}' escalation mode: ` + (runtime.supportCandidates.length > 0 ? "direct-support" : runtime.discoveredSupportCandidates.length > 0 ? "inserted-support-candidates" : "boost-only")
|
|
1644
|
+
);
|
|
1645
|
+
if (runtime.discoveredSupportCandidates.length > 0) {
|
|
1646
|
+
logger.info(
|
|
1647
|
+
`[Prescribed] Group '${group.id}' discovered support candidates: ${runtime.discoveredSupportCandidates.join(", ")}`
|
|
1648
|
+
);
|
|
1649
|
+
}
|
|
1650
|
+
}
|
|
1386
1651
|
nextState.groups[group.id] = this.buildNextGroupState(runtime, progress.groups[group.id]);
|
|
1387
1652
|
const directCards = this.buildDirectTargetCards(
|
|
1388
1653
|
runtime,
|
|
@@ -1394,15 +1659,30 @@ var init_prescribed = __esm({
|
|
|
1394
1659
|
courseId,
|
|
1395
1660
|
emittedIds
|
|
1396
1661
|
);
|
|
1397
|
-
|
|
1662
|
+
const discoveredSupportCards = this.buildDiscoveredSupportCards(
|
|
1663
|
+
runtime,
|
|
1664
|
+
courseId,
|
|
1665
|
+
emittedIds
|
|
1666
|
+
);
|
|
1667
|
+
emitted.push(...directCards, ...supportCards, ...discoveredSupportCards);
|
|
1398
1668
|
}
|
|
1399
1669
|
const hintSummary = this.buildSupportHintSummary(groupRuntimes);
|
|
1400
1670
|
const hints = Object.keys(hintSummary.boostTags).length > 0 ? {
|
|
1401
1671
|
boostTags: hintSummary.boostTags,
|
|
1402
1672
|
_label: `prescribed-support (${hintSummary.supportTags.length} tags; blocked=${hintSummary.blockedTargetIds.length}; testversion=${PRESCRIBED_DEBUG_VERSION})`
|
|
1403
1673
|
} : void 0;
|
|
1674
|
+
if (hints) {
|
|
1675
|
+
const tagEntries = Object.entries(hints.boostTags ?? {});
|
|
1676
|
+
logger.info(
|
|
1677
|
+
`[Prescribed] Emitting ${tagEntries.length} boost hint(s): ` + tagEntries.map(([tag, mult]) => `${tag}\xD7${mult.toFixed(1)}`).join(", ")
|
|
1678
|
+
);
|
|
1679
|
+
} else {
|
|
1680
|
+
logger.info("[Prescribed] No hints to emit (no blocked targets or no support tags)");
|
|
1681
|
+
}
|
|
1404
1682
|
if (emitted.length === 0) {
|
|
1405
|
-
logger.
|
|
1683
|
+
logger.info(
|
|
1684
|
+
"[Prescribed] 0 cards emitted (all targets blocked, authored/discovered support candidates exhausted)" + (hints ? " \u2014 boost hints emitted but may not survive filters" : "")
|
|
1685
|
+
);
|
|
1406
1686
|
await this.putStrategyState(nextState).catch((e) => {
|
|
1407
1687
|
logger.debug(`[Prescribed] Failed to persist empty-state update: ${e}`);
|
|
1408
1688
|
});
|
|
@@ -1435,7 +1715,7 @@ var init_prescribed = __esm({
|
|
|
1435
1715
|
logger.debug(`[Prescribed] Failed to persist prescribed progress: ${e}`);
|
|
1436
1716
|
});
|
|
1437
1717
|
logger.info(
|
|
1438
|
-
`[Prescribed] Emitting ${finalCards.length} cards (${finalCards.filter((c) => c.provenance[0]?.reason.includes("mode=target")).length} target, ${finalCards.filter((c) => c.provenance[0]?.reason.includes("mode=support")).length} support)`
|
|
1718
|
+
`[Prescribed] Emitting ${finalCards.length} cards (${finalCards.filter((c) => c.provenance[0]?.reason.includes("mode=target")).length} target, ${finalCards.filter((c) => c.provenance[0]?.reason.includes("mode=support")).length} support, ${finalCards.filter((c) => c.provenance[0]?.reason.includes("mode=discovered-support")).length} discovered support)`
|
|
1439
1719
|
);
|
|
1440
1720
|
return hints ? { cards: finalCards, hints } : { cards: finalCards };
|
|
1441
1721
|
}
|
|
@@ -1465,9 +1745,15 @@ var init_prescribed = __esm({
|
|
|
1465
1745
|
const groupsRaw = Array.isArray(parsed.groups) ? parsed.groups : [];
|
|
1466
1746
|
const groups = groupsRaw.map((raw, i) => ({
|
|
1467
1747
|
id: typeof raw.id === "string" && raw.id.trim().length > 0 ? raw.id : `group-${i + 1}`,
|
|
1468
|
-
targetCardIds: dedupe(
|
|
1469
|
-
|
|
1470
|
-
|
|
1748
|
+
targetCardIds: dedupe(
|
|
1749
|
+
Array.isArray(raw.targetCardIds) ? raw.targetCardIds.filter((v) => typeof v === "string") : []
|
|
1750
|
+
),
|
|
1751
|
+
supportCardIds: dedupe(
|
|
1752
|
+
Array.isArray(raw.supportCardIds) ? raw.supportCardIds.filter((v) => typeof v === "string") : []
|
|
1753
|
+
),
|
|
1754
|
+
supportTagPatterns: dedupe(
|
|
1755
|
+
Array.isArray(raw.supportTagPatterns) ? raw.supportTagPatterns.filter((v) => typeof v === "string") : []
|
|
1756
|
+
),
|
|
1471
1757
|
freshnessWindowSessions: typeof raw.freshnessWindowSessions === "number" ? raw.freshnessWindowSessions : DEFAULT_FRESHNESS_WINDOW,
|
|
1472
1758
|
maxDirectTargetsPerRun: typeof raw.maxDirectTargetsPerRun === "number" ? raw.maxDirectTargetsPerRun : DEFAULT_MAX_DIRECT_PER_RUN,
|
|
1473
1759
|
maxSupportCardsPerRun: typeof raw.maxSupportCardsPerRun === "number" ? raw.maxSupportCardsPerRun : DEFAULT_MAX_SUPPORT_PER_RUN,
|
|
@@ -1484,7 +1770,7 @@ var init_prescribed = __esm({
|
|
|
1484
1770
|
}
|
|
1485
1771
|
async loadHierarchyConfigs() {
|
|
1486
1772
|
try {
|
|
1487
|
-
const strategies = await this.course.
|
|
1773
|
+
const strategies = await this.course.getAllNavigationStrategies();
|
|
1488
1774
|
return strategies.filter((s) => s.implementingClass === "hierarchyDefinition").map((s) => {
|
|
1489
1775
|
try {
|
|
1490
1776
|
const parsed = JSON.parse(s.serializedData);
|
|
@@ -1507,6 +1793,7 @@ var init_prescribed = __esm({
|
|
|
1507
1793
|
activeIds,
|
|
1508
1794
|
seenIds,
|
|
1509
1795
|
tagsByCard,
|
|
1796
|
+
cardsByTag,
|
|
1510
1797
|
hierarchyConfigs,
|
|
1511
1798
|
userTagElo,
|
|
1512
1799
|
userGlobalElo
|
|
@@ -1570,6 +1857,22 @@ var init_prescribed = __esm({
|
|
|
1570
1857
|
[...supportTags]
|
|
1571
1858
|
)
|
|
1572
1859
|
]).filter((id) => !activeIds.has(id) && !seenIds.has(id));
|
|
1860
|
+
const discoveredSupportCandidates = blockedTargets.length > 0 && supportTags.size > 0 && supportCandidates.length === 0 ? this.findDiscoveredSupportCards({
|
|
1861
|
+
supportTags: [...supportTags],
|
|
1862
|
+
cardsByTag,
|
|
1863
|
+
activeIds,
|
|
1864
|
+
seenIds,
|
|
1865
|
+
excludedIds: /* @__PURE__ */ new Set([
|
|
1866
|
+
...group.targetCardIds,
|
|
1867
|
+
...group.supportCardIds ?? []
|
|
1868
|
+
]),
|
|
1869
|
+
limit: group.maxSupportCardsPerRun ?? DEFAULT_MAX_SUPPORT_PER_RUN
|
|
1870
|
+
}) : [];
|
|
1871
|
+
if (blockedTargets.length > 0 && supportTags.size > 0 && discoveredSupportCandidates.length === 0) {
|
|
1872
|
+
logger.info(
|
|
1873
|
+
`[Prescribed] Group '${group.id}' discovered 0 broader support candidates (blocked=${blockedTargets.length}; authoredSupport=${supportCandidates.length})`
|
|
1874
|
+
);
|
|
1875
|
+
}
|
|
1573
1876
|
const sessionsSinceSurfaced = priorState?.sessionsSinceSurfaced ?? 0;
|
|
1574
1877
|
const freshnessWindow = group.freshnessWindowSessions ?? DEFAULT_FRESHNESS_WINDOW;
|
|
1575
1878
|
const staleSessions = Math.max(0, sessionsSinceSurfaced - freshnessWindow);
|
|
@@ -1583,6 +1886,7 @@ var init_prescribed = __esm({
|
|
|
1583
1886
|
surfaceableTargets,
|
|
1584
1887
|
targetTags,
|
|
1585
1888
|
supportCandidates,
|
|
1889
|
+
discoveredSupportCandidates,
|
|
1586
1890
|
supportTags: [...supportTags],
|
|
1587
1891
|
pressureMultiplier,
|
|
1588
1892
|
supportMultiplier,
|
|
@@ -1653,6 +1957,33 @@ var init_prescribed = __esm({
|
|
|
1653
1957
|
}
|
|
1654
1958
|
return cards;
|
|
1655
1959
|
}
|
|
1960
|
+
buildDiscoveredSupportCards(runtime, courseId, emittedIds) {
|
|
1961
|
+
if (runtime.blockedTargets.length === 0 || runtime.discoveredSupportCandidates.length === 0) {
|
|
1962
|
+
return [];
|
|
1963
|
+
}
|
|
1964
|
+
const maxSupport = runtime.group.maxSupportCardsPerRun ?? DEFAULT_MAX_SUPPORT_PER_RUN;
|
|
1965
|
+
const supportIds = runtime.discoveredSupportCandidates.filter((id) => !emittedIds.has(id)).slice(0, maxSupport);
|
|
1966
|
+
const cards = [];
|
|
1967
|
+
for (const cardId of supportIds) {
|
|
1968
|
+
emittedIds.add(cardId);
|
|
1969
|
+
cards.push({
|
|
1970
|
+
cardId,
|
|
1971
|
+
courseId,
|
|
1972
|
+
score: DISCOVERED_SUPPORT_SCORE * runtime.supportMultiplier,
|
|
1973
|
+
provenance: [
|
|
1974
|
+
{
|
|
1975
|
+
strategy: "prescribed",
|
|
1976
|
+
strategyName: this.strategyName || this.name,
|
|
1977
|
+
strategyId: this.strategyId || "NAVIGATION_STRATEGY-prescribed",
|
|
1978
|
+
action: "generated",
|
|
1979
|
+
score: DISCOVERED_SUPPORT_SCORE * runtime.supportMultiplier,
|
|
1980
|
+
reason: `mode=discovered-support;group=${runtime.group.id};pending=${runtime.pendingTargets.length};blocked=${runtime.blockedTargets.length};blockedTargets=${runtime.blockedTargets.join("|") || "none"};supportCard=${cardId};supportTags=${runtime.supportTags.join("|") || "none"};multiplier=${runtime.supportMultiplier.toFixed(2)};testversion=${runtime.debugVersion}`
|
|
1981
|
+
}
|
|
1982
|
+
]
|
|
1983
|
+
});
|
|
1984
|
+
}
|
|
1985
|
+
return cards;
|
|
1986
|
+
}
|
|
1656
1987
|
findSupportCardsByTags(group, tagsByCard, supportTags) {
|
|
1657
1988
|
if (supportTags.length === 0) {
|
|
1658
1989
|
return [];
|
|
@@ -1675,6 +2006,40 @@ var init_prescribed = __esm({
|
|
|
1675
2006
|
}
|
|
1676
2007
|
return [...candidates];
|
|
1677
2008
|
}
|
|
2009
|
+
findDiscoveredSupportCards(args) {
|
|
2010
|
+
const { supportTags, cardsByTag, activeIds, seenIds, excludedIds, limit } = args;
|
|
2011
|
+
const byCardId = /* @__PURE__ */ new Map();
|
|
2012
|
+
for (const supportTag of supportTags) {
|
|
2013
|
+
const taggedCards = cardsByTag.get(supportTag) ?? [];
|
|
2014
|
+
for (const cardId of taggedCards) {
|
|
2015
|
+
if (activeIds.has(cardId) || seenIds.has(cardId) || excludedIds.has(cardId)) {
|
|
2016
|
+
continue;
|
|
2017
|
+
}
|
|
2018
|
+
const existing = byCardId.get(cardId);
|
|
2019
|
+
if (existing) {
|
|
2020
|
+
existing.matches += 1;
|
|
2021
|
+
} else {
|
|
2022
|
+
byCardId.set(cardId, { cardId, matches: 1 });
|
|
2023
|
+
}
|
|
2024
|
+
}
|
|
2025
|
+
}
|
|
2026
|
+
const candidates = [...byCardId.values()].sort((a, b) => b.matches - a.matches || a.cardId.localeCompare(b.cardId));
|
|
2027
|
+
const usedStems = /* @__PURE__ */ new Set();
|
|
2028
|
+
const diverse = [];
|
|
2029
|
+
const deferred = [];
|
|
2030
|
+
for (const entry of candidates) {
|
|
2031
|
+
const stem = extractWordStem(entry.cardId);
|
|
2032
|
+
if (!usedStems.has(stem)) {
|
|
2033
|
+
usedStems.add(stem);
|
|
2034
|
+
diverse.push(entry);
|
|
2035
|
+
} else {
|
|
2036
|
+
deferred.push(entry);
|
|
2037
|
+
}
|
|
2038
|
+
}
|
|
2039
|
+
shuffleInPlace(diverse);
|
|
2040
|
+
shuffleInPlace(deferred);
|
|
2041
|
+
return [...diverse, ...deferred].slice(0, limit).map((entry) => entry.cardId);
|
|
2042
|
+
}
|
|
1678
2043
|
resolveBlockedSupportTags(targetTags, hierarchyConfigs, userTagElo, userGlobalElo, hierarchyWalkEnabled, maxDepth) {
|
|
1679
2044
|
const supportTags = /* @__PURE__ */ new Set();
|
|
1680
2045
|
let blocked = false;
|
|
@@ -1720,7 +2085,6 @@ var init_prescribed = __esm({
|
|
|
1720
2085
|
}
|
|
1721
2086
|
collectSupportTagsRecursive(tag, hierarchyConfigs, userTagElo, userGlobalElo, depth, visited, out) {
|
|
1722
2087
|
if (depth < 0 || visited.has(tag)) return;
|
|
1723
|
-
if (this.isHardGatedTag(tag)) return;
|
|
1724
2088
|
visited.add(tag);
|
|
1725
2089
|
let walkedFurther = false;
|
|
1726
2090
|
for (const hierarchy of hierarchyConfigs) {
|
|
@@ -1748,9 +2112,6 @@ var init_prescribed = __esm({
|
|
|
1748
2112
|
out.add(tag);
|
|
1749
2113
|
}
|
|
1750
2114
|
}
|
|
1751
|
-
isHardGatedTag(tag) {
|
|
1752
|
-
return LOCKED_TAG_PREFIXES.some((prefix) => tag.startsWith(prefix)) && tag.startsWith(LESSON_GATE_PENALTY_TAG_HINT);
|
|
1753
|
-
}
|
|
1754
2115
|
isPrerequisiteMet(prereq, userTagElo, userGlobalElo) {
|
|
1755
2116
|
if (!userTagElo) return false;
|
|
1756
2117
|
const minCount = prereq.masteryThreshold?.minCount ?? DEFAULT_MIN_COUNT;
|
|
@@ -3296,6 +3657,32 @@ var init_Pipeline = __esm({
|
|
|
3296
3657
|
cards = await this.hydrateTags(cards);
|
|
3297
3658
|
const tHydrate = performance.now();
|
|
3298
3659
|
const allCardsBeforeFiltering = [...cards];
|
|
3660
|
+
const pendingHints = this._ephemeralHints;
|
|
3661
|
+
if (pendingHints?.requireCards?.length) {
|
|
3662
|
+
const poolIds = new Set(allCardsBeforeFiltering.map((c) => c.cardId));
|
|
3663
|
+
const missingIds = pendingHints.requireCards.filter(
|
|
3664
|
+
(p) => !p.includes("*") && !poolIds.has(p)
|
|
3665
|
+
);
|
|
3666
|
+
if (missingIds.length > 0) {
|
|
3667
|
+
const fetchedTags = await this.course.getAppliedTagsBatch(missingIds);
|
|
3668
|
+
const courseId = this.course.getCourseID();
|
|
3669
|
+
for (const cardId of missingIds) {
|
|
3670
|
+
allCardsBeforeFiltering.push({
|
|
3671
|
+
cardId,
|
|
3672
|
+
courseId,
|
|
3673
|
+
score: 1,
|
|
3674
|
+
tags: fetchedTags.get(cardId) ?? [],
|
|
3675
|
+
provenance: []
|
|
3676
|
+
});
|
|
3677
|
+
}
|
|
3678
|
+
logger.info(
|
|
3679
|
+
`[Pipeline] Pre-fetched ${missingIds.length} required card(s) into pool: ${missingIds.join(", ")}`
|
|
3680
|
+
);
|
|
3681
|
+
}
|
|
3682
|
+
}
|
|
3683
|
+
const prescribedIds = new Set(
|
|
3684
|
+
cards.filter((c) => c.provenance.some((p) => p.strategy === "prescribed")).map((c) => c.cardId)
|
|
3685
|
+
);
|
|
3299
3686
|
const filterImpacts = [];
|
|
3300
3687
|
for (const filter of this.filters) {
|
|
3301
3688
|
const beforeCount = cards.length;
|
|
@@ -3310,6 +3697,17 @@ var init_Pipeline = __esm({
|
|
|
3310
3697
|
else passed++;
|
|
3311
3698
|
}
|
|
3312
3699
|
filterImpacts.push({ name: filter.name, boosted, penalized, passed, removed });
|
|
3700
|
+
if (prescribedIds.size > 0) {
|
|
3701
|
+
const survivingIds = new Set(cards.map((c) => c.cardId));
|
|
3702
|
+
const killedPrescribed = [...prescribedIds].filter((id) => !survivingIds.has(id));
|
|
3703
|
+
const zeroedPrescribed = cards.filter((c) => prescribedIds.has(c.cardId) && c.score === 0).map((c) => c.cardId);
|
|
3704
|
+
if (killedPrescribed.length > 0 || zeroedPrescribed.length > 0) {
|
|
3705
|
+
logger.info(
|
|
3706
|
+
`[Pipeline] Filter '${filter.name}' impact on prescribed cards: ` + (killedPrescribed.length > 0 ? `removed=[${killedPrescribed.join(", ")}] ` : "") + (zeroedPrescribed.length > 0 ? `zeroed=[${zeroedPrescribed.join(", ")}]` : "")
|
|
3707
|
+
);
|
|
3708
|
+
killedPrescribed.forEach((id) => prescribedIds.delete(id));
|
|
3709
|
+
}
|
|
3710
|
+
}
|
|
3313
3711
|
logger.debug(`[Pipeline] Filter '${filter.name}': ${beforeScores.size} \u2192 ${cards.length} cards (\u2191${boosted} \u2193${penalized} =${passed})`);
|
|
3314
3712
|
}
|
|
3315
3713
|
cards = cards.filter((c) => c.score > 0);
|
|
@@ -3346,7 +3744,8 @@ var init_Pipeline = __esm({
|
|
|
3346
3744
|
filterImpacts,
|
|
3347
3745
|
cards,
|
|
3348
3746
|
result,
|
|
3349
|
-
context.userElo
|
|
3747
|
+
context.userElo,
|
|
3748
|
+
hints ?? void 0
|
|
3350
3749
|
);
|
|
3351
3750
|
captureRun(report);
|
|
3352
3751
|
} catch (e) {
|
|
@@ -3456,13 +3855,27 @@ var init_Pipeline = __esm({
|
|
|
3456
3855
|
}
|
|
3457
3856
|
}
|
|
3458
3857
|
const cardIds = new Set(cards.map((c) => c.cardId));
|
|
3858
|
+
const cardMap = new Map(cards.map((c) => [c.cardId, c]));
|
|
3459
3859
|
const hintLabel = hints._label ? `Replan Hint (${hints._label})` : "Replan Hint";
|
|
3460
|
-
const
|
|
3461
|
-
|
|
3462
|
-
|
|
3860
|
+
const applyRequirement = (card, reason) => {
|
|
3861
|
+
const mandatoryScore = Number.POSITIVE_INFINITY;
|
|
3862
|
+
const existing = cardMap.get(card.cardId);
|
|
3863
|
+
if (existing) {
|
|
3864
|
+
if (existing.score < mandatoryScore) {
|
|
3865
|
+
existing.score = mandatoryScore;
|
|
3866
|
+
existing.provenance.push({
|
|
3867
|
+
strategy: "ephemeralHint",
|
|
3868
|
+
strategyId: "ephemeral-hint",
|
|
3869
|
+
strategyName: hintLabel,
|
|
3870
|
+
action: "boosted",
|
|
3871
|
+
score: mandatoryScore,
|
|
3872
|
+
reason: `${reason} (upgrade to mandatory score)`
|
|
3873
|
+
});
|
|
3874
|
+
}
|
|
3875
|
+
} else {
|
|
3463
3876
|
cards.push({
|
|
3464
3877
|
...card,
|
|
3465
|
-
score:
|
|
3878
|
+
score: mandatoryScore,
|
|
3466
3879
|
provenance: [
|
|
3467
3880
|
...card.provenance,
|
|
3468
3881
|
{
|
|
@@ -3470,25 +3883,41 @@ var init_Pipeline = __esm({
|
|
|
3470
3883
|
strategyId: "ephemeral-hint",
|
|
3471
3884
|
strategyName: hintLabel,
|
|
3472
3885
|
action: "boosted",
|
|
3473
|
-
score:
|
|
3886
|
+
score: mandatoryScore,
|
|
3474
3887
|
reason
|
|
3475
3888
|
}
|
|
3476
3889
|
]
|
|
3477
3890
|
});
|
|
3478
3891
|
cardIds.add(card.cardId);
|
|
3892
|
+
cardMap.set(card.cardId, cards[cards.length - 1]);
|
|
3479
3893
|
}
|
|
3480
3894
|
};
|
|
3481
3895
|
if (hints.requireCards?.length) {
|
|
3482
3896
|
for (const pattern of hints.requireCards) {
|
|
3897
|
+
for (const cardId of cardIds) {
|
|
3898
|
+
if (globMatch(cardId, pattern)) {
|
|
3899
|
+
applyRequirement(cardMap.get(cardId), `requireCard ${pattern}`);
|
|
3900
|
+
}
|
|
3901
|
+
}
|
|
3483
3902
|
for (const card of allCards) {
|
|
3484
|
-
if (globMatch(card.cardId, pattern))
|
|
3903
|
+
if (globMatch(card.cardId, pattern)) {
|
|
3904
|
+
applyRequirement(card, `requireCard ${pattern}`);
|
|
3905
|
+
}
|
|
3485
3906
|
}
|
|
3486
3907
|
}
|
|
3487
3908
|
}
|
|
3488
3909
|
if (hints.requireTags?.length) {
|
|
3489
3910
|
for (const pattern of hints.requireTags) {
|
|
3911
|
+
for (const cardId of cardIds) {
|
|
3912
|
+
const card = cardMap.get(cardId);
|
|
3913
|
+
if (cardMatchesTagPattern(card, pattern)) {
|
|
3914
|
+
applyRequirement(card, `requireTag ${pattern}`);
|
|
3915
|
+
}
|
|
3916
|
+
}
|
|
3490
3917
|
for (const card of allCards) {
|
|
3491
|
-
if (cardMatchesTagPattern(card, pattern))
|
|
3918
|
+
if (cardMatchesTagPattern(card, pattern)) {
|
|
3919
|
+
applyRequirement(card, `requireTag ${pattern}`);
|
|
3920
|
+
}
|
|
3492
3921
|
}
|
|
3493
3922
|
}
|
|
3494
3923
|
}
|