@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
|
@@ -538,8 +538,9 @@ function getOrigin(card) {
|
|
|
538
538
|
const firstEntry = card.provenance[0];
|
|
539
539
|
if (!firstEntry) return "unknown";
|
|
540
540
|
const reason = firstEntry.reason?.toLowerCase() || "";
|
|
541
|
-
|
|
542
|
-
if (reason.includes("
|
|
541
|
+
const strategy = firstEntry.strategy?.toLowerCase() || "";
|
|
542
|
+
if (reason.includes("new card") || strategy.includes("elo")) return "new";
|
|
543
|
+
if (reason.includes("review") || strategy.includes("srs")) return "review";
|
|
543
544
|
return "unknown";
|
|
544
545
|
}
|
|
545
546
|
function captureRun(report) {
|
|
@@ -559,12 +560,13 @@ function parseCardElo(provenance) {
|
|
|
559
560
|
const match = eloEntry.reason.match(/card:\s*(\d+)/);
|
|
560
561
|
return match ? parseInt(match[1], 10) : void 0;
|
|
561
562
|
}
|
|
562
|
-
function buildRunReport(courseId, courseName, generatorName, generators, generatedCount, filters, allCards, selectedCards, userElo) {
|
|
563
|
+
function buildRunReport(courseId, courseName, generatorName, generators, generatedCount, filters, allCards, selectedCards, userElo, hints) {
|
|
563
564
|
const selectedIds = new Set(selectedCards.map((c) => c.cardId));
|
|
564
565
|
const cards = allCards.map((card) => ({
|
|
565
566
|
cardId: card.cardId,
|
|
566
567
|
courseId: card.courseId,
|
|
567
568
|
origin: getOrigin(card),
|
|
569
|
+
generator: card.provenance[0]?.strategyName || card.provenance[0]?.strategy,
|
|
568
570
|
finalScore: card.score,
|
|
569
571
|
cardElo: parseCardElo(card.provenance),
|
|
570
572
|
provenance: card.provenance,
|
|
@@ -581,6 +583,7 @@ function buildRunReport(courseId, courseName, generatorName, generators, generat
|
|
|
581
583
|
generators,
|
|
582
584
|
generatedCount,
|
|
583
585
|
filters,
|
|
586
|
+
hints,
|
|
584
587
|
finalCount: selectedCards.length,
|
|
585
588
|
reviewsSelected,
|
|
586
589
|
newSelected,
|
|
@@ -620,13 +623,169 @@ function printRunSummary(run) {
|
|
|
620
623
|
);
|
|
621
624
|
console.groupEnd();
|
|
622
625
|
}
|
|
626
|
+
function renderUI() {
|
|
627
|
+
if (!_uiContainer) return;
|
|
628
|
+
const runs = runHistory;
|
|
629
|
+
const selectedRun = _selectedRunIndex !== null ? runs[_selectedRunIndex] : null;
|
|
630
|
+
const styles = `
|
|
631
|
+
#sk-pipeline-debugger {
|
|
632
|
+
position: fixed;
|
|
633
|
+
top: 0;
|
|
634
|
+
left: 0;
|
|
635
|
+
width: 100vw;
|
|
636
|
+
height: 100vh;
|
|
637
|
+
background: #f8f9fa;
|
|
638
|
+
color: #212529;
|
|
639
|
+
z-index: 999999;
|
|
640
|
+
display: flex;
|
|
641
|
+
flex-direction: column;
|
|
642
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
|
643
|
+
font-size: 14px;
|
|
644
|
+
}
|
|
645
|
+
#sk-pipeline-debugger header {
|
|
646
|
+
padding: 1rem;
|
|
647
|
+
background: #343a40;
|
|
648
|
+
color: white;
|
|
649
|
+
display: flex;
|
|
650
|
+
justify-content: space-between;
|
|
651
|
+
align-items: center;
|
|
652
|
+
}
|
|
653
|
+
#sk-pipeline-debugger .container {
|
|
654
|
+
display: flex;
|
|
655
|
+
flex: 1;
|
|
656
|
+
overflow: hidden;
|
|
657
|
+
}
|
|
658
|
+
#sk-pipeline-debugger .sidebar {
|
|
659
|
+
width: 300px;
|
|
660
|
+
border-right: 1px solid #dee2e6;
|
|
661
|
+
overflow-y: auto;
|
|
662
|
+
background: white;
|
|
663
|
+
}
|
|
664
|
+
#sk-pipeline-debugger .main-content {
|
|
665
|
+
flex: 1;
|
|
666
|
+
overflow-y: auto;
|
|
667
|
+
padding: 1.5rem;
|
|
668
|
+
}
|
|
669
|
+
#sk-pipeline-debugger .run-item {
|
|
670
|
+
padding: 0.75rem 1rem;
|
|
671
|
+
border-bottom: 1px solid #eee;
|
|
672
|
+
cursor: pointer;
|
|
673
|
+
}
|
|
674
|
+
#sk-pipeline-debugger .run-item:hover { background: #f1f3f5; }
|
|
675
|
+
#sk-pipeline-debugger .run-item.active { background: #e9ecef; border-left: 4px solid #007bff; }
|
|
676
|
+
#sk-pipeline-debugger h2, #sk-pipeline-debugger h3 { margin-top: 0; }
|
|
677
|
+
#sk-pipeline-debugger table { width: 100%; border-collapse: collapse; margin-bottom: 1rem; background: white; }
|
|
678
|
+
#sk-pipeline-debugger th, #sk-pipeline-debugger td { border: 1px solid #dee2e6; padding: 0.5rem; text-align: left; }
|
|
679
|
+
#sk-pipeline-debugger th { background: #f1f3f5; }
|
|
680
|
+
#sk-pipeline-debugger code { background: #f1f3f5; padding: 0.1rem 0.3rem; border-radius: 3px; font-family: monospace; }
|
|
681
|
+
#sk-pipeline-debugger .close-btn { background: #dc3545; color: white; border: none; padding: 0.5rem 1rem; border-radius: 4px; cursor: pointer; }
|
|
682
|
+
#sk-pipeline-debugger .search-box { margin-bottom: 1rem; width: 100%; padding: 0.5rem; border: 1px solid #ced4da; border-radius: 4px; }
|
|
683
|
+
#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; }
|
|
684
|
+
`;
|
|
685
|
+
const runListHtml = runs.length === 0 ? '<div style="padding: 1rem;">No runs captured yet.</div>' : runs.map(
|
|
686
|
+
(r, i) => `
|
|
687
|
+
<div class="run-item ${i === _selectedRunIndex ? "active" : ""}" onclick="window.skuilder.pipeline._selectRun(${i})">
|
|
688
|
+
<strong>${r.timestamp.toLocaleTimeString()}</strong><br/>
|
|
689
|
+
<small>${r.courseName || r.courseId.slice(0, 8)}</small><br/>
|
|
690
|
+
<small>${r.finalCount} cards selected</small>
|
|
691
|
+
</div>
|
|
692
|
+
`
|
|
693
|
+
).join("");
|
|
694
|
+
let detailsHtml = '<div style="color: #6c757d; text-align: center; margin-top: 5rem;">Select a run to see details</div>';
|
|
695
|
+
if (selectedRun) {
|
|
696
|
+
const filteredCards = selectedRun.cards.filter(
|
|
697
|
+
(c) => !_cardSearchQuery || c.cardId.toLowerCase().includes(_cardSearchQuery.toLowerCase())
|
|
698
|
+
);
|
|
699
|
+
detailsHtml = `
|
|
700
|
+
<h2>Run: ${selectedRun.runId}</h2>
|
|
701
|
+
<p>
|
|
702
|
+
<strong>Time:</strong> ${selectedRun.timestamp.toLocaleString()} |
|
|
703
|
+
<strong>Course:</strong> ${selectedRun.courseName || selectedRun.courseId} |
|
|
704
|
+
<strong>User ELO:</strong> ${selectedRun.userElo ?? "unknown"}
|
|
705
|
+
</p>
|
|
706
|
+
|
|
707
|
+
<h3>Pipeline Config</h3>
|
|
708
|
+
<table>
|
|
709
|
+
<tr><th>Generator</th><td>${selectedRun.generatorName} (${selectedRun.generatedCount} candidates)</td></tr>
|
|
710
|
+
${(selectedRun.generators || []).map(
|
|
711
|
+
(g) => `
|
|
712
|
+
<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>
|
|
713
|
+
`
|
|
714
|
+
).join("")}
|
|
715
|
+
</table>
|
|
716
|
+
|
|
717
|
+
${selectedRun.hints ? `
|
|
718
|
+
<h3>Ephemeral Hints</h3>
|
|
719
|
+
<table>
|
|
720
|
+
${selectedRun.hints._label ? `<tr><th>Label</th><td>${selectedRun.hints._label}</td></tr>` : ""}
|
|
721
|
+
${selectedRun.hints.boostTags ? `<tr><th>Boost Tags</th><td><pre style="margin:0">${JSON.stringify(selectedRun.hints.boostTags, null, 2)}</pre></td></tr>` : ""}
|
|
722
|
+
${selectedRun.hints.boostCards ? `<tr><th>Boost Cards</th><td><pre style="margin:0">${JSON.stringify(selectedRun.hints.boostCards, null, 2)}</pre></td></tr>` : ""}
|
|
723
|
+
${selectedRun.hints.requireTags ? `<tr><th>Require Tags</th><td>${selectedRun.hints.requireTags.join(", ")}</td></tr>` : ""}
|
|
724
|
+
${selectedRun.hints.requireCards ? `<tr><th>Require Cards</th><td>${selectedRun.hints.requireCards.join(", ")}</td></tr>` : ""}
|
|
725
|
+
${selectedRun.hints.excludeTags ? `<tr><th>Exclude Tags</th><td>${selectedRun.hints.excludeTags.join(", ")}</td></tr>` : ""}
|
|
726
|
+
${selectedRun.hints.excludeCards ? `<tr><th>Exclude Cards</th><td>${selectedRun.hints.excludeCards.join(", ")}</td></tr>` : ""}
|
|
727
|
+
</table>
|
|
728
|
+
` : ""}
|
|
729
|
+
|
|
730
|
+
<h3>Filter Impact</h3>
|
|
731
|
+
<table>
|
|
732
|
+
<thead><tr><th>Filter</th><th>Boosted</th><th>Penalized</th><th>Passed</th><th>Removed</th></tr></thead>
|
|
733
|
+
<tbody>
|
|
734
|
+
${selectedRun.filters.map(
|
|
735
|
+
(f) => `
|
|
736
|
+
<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>
|
|
737
|
+
`
|
|
738
|
+
).join("")}
|
|
739
|
+
</tbody>
|
|
740
|
+
</table>
|
|
741
|
+
|
|
742
|
+
<h3>Cards (${selectedRun.finalCount} selected / ${selectedRun.cards.length} total)</h3>
|
|
743
|
+
<input type="text" class="search-box" placeholder="Search Card ID..." value="${_cardSearchQuery}" oninput="window.skuilder.pipeline._setSearch(this.value)">
|
|
744
|
+
|
|
745
|
+
<table>
|
|
746
|
+
<thead><tr><th>ID</th><th>Generator</th><th>Origin</th><th>Score</th><th>Selected</th></tr></thead>
|
|
747
|
+
<tbody>
|
|
748
|
+
${filteredCards.map(
|
|
749
|
+
(c) => `
|
|
750
|
+
<tr>
|
|
751
|
+
<td><code>${c.cardId}</code></td>
|
|
752
|
+
<td>${c.generator || "unknown"}</td>
|
|
753
|
+
<td>${c.origin}</td>
|
|
754
|
+
<td>${c.finalScore.toFixed(3)}</td>
|
|
755
|
+
<td>${c.selected ? "\u2705" : "\u274C"}</td>
|
|
756
|
+
</tr>
|
|
757
|
+
${c.selected || _cardSearchQuery ? `
|
|
758
|
+
<tr>
|
|
759
|
+
<td colspan="5">
|
|
760
|
+
<div class="provenance">${formatProvenance(c.provenance)}</div>
|
|
761
|
+
</td>
|
|
762
|
+
</tr>
|
|
763
|
+
` : ""}
|
|
764
|
+
`
|
|
765
|
+
).join("")}
|
|
766
|
+
</tbody>
|
|
767
|
+
</table>
|
|
768
|
+
`;
|
|
769
|
+
}
|
|
770
|
+
_uiContainer.innerHTML = `
|
|
771
|
+
<style>${styles}</style>
|
|
772
|
+
<header>
|
|
773
|
+
<strong>Pipeline Debugger</strong>
|
|
774
|
+
<button class="close-btn" onclick="window.skuilder.pipeline.ui()">Close</button>
|
|
775
|
+
</header>
|
|
776
|
+
<div class="container">
|
|
777
|
+
<div class="sidebar">${runListHtml}</div>
|
|
778
|
+
<div class="main-content">${detailsHtml}</div>
|
|
779
|
+
</div>
|
|
780
|
+
`;
|
|
781
|
+
}
|
|
623
782
|
function mountPipelineDebugger() {
|
|
624
783
|
if (typeof window === "undefined") return;
|
|
625
784
|
const win = window;
|
|
626
785
|
win.skuilder = win.skuilder || {};
|
|
627
786
|
win.skuilder.pipeline = pipelineDebugAPI;
|
|
628
787
|
}
|
|
629
|
-
var _activePipeline, MAX_RUNS, runHistory, pipelineDebugAPI;
|
|
788
|
+
var _activePipeline, MAX_RUNS, runHistory, _uiContainer, _selectedRunIndex, _cardSearchQuery, pipelineDebugAPI;
|
|
630
789
|
var init_PipelineDebugger = __esm({
|
|
631
790
|
"src/core/navigators/PipelineDebugger.ts"() {
|
|
632
791
|
"use strict";
|
|
@@ -635,6 +794,9 @@ var init_PipelineDebugger = __esm({
|
|
|
635
794
|
_activePipeline = null;
|
|
636
795
|
MAX_RUNS = 10;
|
|
637
796
|
runHistory = [];
|
|
797
|
+
_uiContainer = null;
|
|
798
|
+
_selectedRunIndex = null;
|
|
799
|
+
_cardSearchQuery = "";
|
|
638
800
|
pipelineDebugAPI = {
|
|
639
801
|
/**
|
|
640
802
|
* Get raw run history for programmatic access.
|
|
@@ -774,16 +936,20 @@ var init_PipelineDebugger = __esm({
|
|
|
774
936
|
const mode = reason.match(/mode=([^;]+)/)?.[1] ?? "unknown";
|
|
775
937
|
const blocked = reason.match(/blocked=([^;]+)/)?.[1] ?? "unknown";
|
|
776
938
|
const blockedTargets = reason.match(/blockedTargets=([^;]+)/)?.[1] ?? "none";
|
|
939
|
+
const supportCard = reason.match(/supportCard=([^;]+)/)?.[1] ?? "none";
|
|
777
940
|
const supportTags = reason.match(/supportTags=([^;]+)/)?.[1] ?? "none";
|
|
778
941
|
const multiplier = reason.match(/multiplier=([^;]+)/)?.[1] ?? "unknown";
|
|
942
|
+
const supportSource = mode === "discovered-support" ? "discovered" : mode === "support" ? "authored" : "n/a";
|
|
779
943
|
return {
|
|
780
944
|
group: parsedGroup,
|
|
781
945
|
mode,
|
|
946
|
+
supportSource,
|
|
782
947
|
cardId: card.cardId,
|
|
783
948
|
selected: card.selected ? "yes" : "no",
|
|
784
949
|
finalScore: card.finalScore.toFixed(3),
|
|
785
950
|
blocked,
|
|
786
951
|
blockedTargets,
|
|
952
|
+
supportCard,
|
|
787
953
|
supportTags,
|
|
788
954
|
multiplier
|
|
789
955
|
};
|
|
@@ -799,6 +965,8 @@ var init_PipelineDebugger = __esm({
|
|
|
799
965
|
const selectedRows = rows.filter((r) => r.selected === "yes");
|
|
800
966
|
const blockedTargetSet = /* @__PURE__ */ new Set();
|
|
801
967
|
const supportTagSet = /* @__PURE__ */ new Set();
|
|
968
|
+
const authoredSupportSet = /* @__PURE__ */ new Set();
|
|
969
|
+
const discoveredSupportSet = /* @__PURE__ */ new Set();
|
|
802
970
|
for (const row of rows) {
|
|
803
971
|
if (row.blockedTargets && row.blockedTargets !== "none") {
|
|
804
972
|
row.blockedTargets.split("|").filter(Boolean).forEach((t) => blockedTargetSet.add(t));
|
|
@@ -806,6 +974,13 @@ var init_PipelineDebugger = __esm({
|
|
|
806
974
|
if (row.supportTags && row.supportTags !== "none") {
|
|
807
975
|
row.supportTags.split("|").filter(Boolean).forEach((t) => supportTagSet.add(t));
|
|
808
976
|
}
|
|
977
|
+
if (row.supportCard && row.supportCard !== "none") {
|
|
978
|
+
if (row.supportSource === "discovered") {
|
|
979
|
+
discoveredSupportSet.add(row.supportCard);
|
|
980
|
+
} else if (row.supportSource === "authored") {
|
|
981
|
+
authoredSupportSet.add(row.supportCard);
|
|
982
|
+
}
|
|
983
|
+
}
|
|
809
984
|
}
|
|
810
985
|
logger.info(`Prescribed cards in run: ${rows.length}`);
|
|
811
986
|
logger.info(`Selected prescribed cards: ${selectedRows.length}`);
|
|
@@ -815,6 +990,12 @@ var init_PipelineDebugger = __esm({
|
|
|
815
990
|
logger.info(
|
|
816
991
|
`Resolved support tags referenced: ${supportTagSet.size > 0 ? [...supportTagSet].join(", ") : "none"}`
|
|
817
992
|
);
|
|
993
|
+
logger.info(
|
|
994
|
+
`Authored support cards emitted: ${authoredSupportSet.size > 0 ? [...authoredSupportSet].join(", ") : "none"}`
|
|
995
|
+
);
|
|
996
|
+
logger.info(
|
|
997
|
+
`Discovered support cards emitted: ${discoveredSupportSet.size > 0 ? [...discoveredSupportSet].join(", ") : "none"}`
|
|
998
|
+
);
|
|
818
999
|
console.groupEnd();
|
|
819
1000
|
},
|
|
820
1001
|
/**
|
|
@@ -949,6 +1130,39 @@ var init_PipelineDebugger = __esm({
|
|
|
949
1130
|
Object.fromEntries(entries.map(([tag, data]) => [tag, { score: Math.round(data.score), count: data.count }]))
|
|
950
1131
|
);
|
|
951
1132
|
},
|
|
1133
|
+
/**
|
|
1134
|
+
* Toggle the full-screen UI debugger.
|
|
1135
|
+
*/
|
|
1136
|
+
ui() {
|
|
1137
|
+
if (_uiContainer) {
|
|
1138
|
+
document.body.removeChild(_uiContainer);
|
|
1139
|
+
_uiContainer = null;
|
|
1140
|
+
return;
|
|
1141
|
+
}
|
|
1142
|
+
_uiContainer = document.createElement("div");
|
|
1143
|
+
_uiContainer.id = "sk-pipeline-debugger";
|
|
1144
|
+
document.body.appendChild(_uiContainer);
|
|
1145
|
+
if (_selectedRunIndex === null && runHistory.length > 0) {
|
|
1146
|
+
_selectedRunIndex = 0;
|
|
1147
|
+
}
|
|
1148
|
+
renderUI();
|
|
1149
|
+
},
|
|
1150
|
+
/**
|
|
1151
|
+
* Internal UI helpers
|
|
1152
|
+
* @internal
|
|
1153
|
+
*/
|
|
1154
|
+
_selectRun(index) {
|
|
1155
|
+
_selectedRunIndex = index;
|
|
1156
|
+
renderUI();
|
|
1157
|
+
},
|
|
1158
|
+
/**
|
|
1159
|
+
* Internal UI helpers
|
|
1160
|
+
* @internal
|
|
1161
|
+
*/
|
|
1162
|
+
_setSearch(query) {
|
|
1163
|
+
_cardSearchQuery = query;
|
|
1164
|
+
renderUI();
|
|
1165
|
+
},
|
|
952
1166
|
/**
|
|
953
1167
|
* Show help.
|
|
954
1168
|
*/
|
|
@@ -957,6 +1171,7 @@ var init_PipelineDebugger = __esm({
|
|
|
957
1171
|
\u{1F527} Pipeline Debug API
|
|
958
1172
|
|
|
959
1173
|
Commands:
|
|
1174
|
+
.ui() Toggle full-screen UI debugger
|
|
960
1175
|
.showLastRun() Show summary of most recent pipeline run
|
|
961
1176
|
.showRun(id|index) Show summary of a specific run (by index or ID suffix)
|
|
962
1177
|
.showCard(cardId) Show provenance trail for a specific card
|
|
@@ -973,6 +1188,7 @@ Commands:
|
|
|
973
1188
|
.help() Show this help message
|
|
974
1189
|
|
|
975
1190
|
Example:
|
|
1191
|
+
window.skuilder.pipeline.ui()
|
|
976
1192
|
window.skuilder.pipeline.showLastRun()
|
|
977
1193
|
window.skuilder.pipeline.showRun(1)
|
|
978
1194
|
await window.skuilder.pipeline.diagnoseCardSpace()
|
|
@@ -1135,7 +1351,7 @@ var init_CompositeGenerator = __esm({
|
|
|
1135
1351
|
for (const [, items] of byCardId) {
|
|
1136
1352
|
const cards2 = items.map((i) => i.card);
|
|
1137
1353
|
const aggregatedScore = this.aggregateScores(items);
|
|
1138
|
-
const finalScore = Math.
|
|
1354
|
+
const finalScore = Math.max(0, aggregatedScore);
|
|
1139
1355
|
const mergedProvenance = cards2.flatMap((c) => c.provenance);
|
|
1140
1356
|
const initialScore = cards2[0].score;
|
|
1141
1357
|
const action = finalScore > initialScore ? "boosted" : finalScore < initialScore ? "penalized" : "passed";
|
|
@@ -1332,10 +1548,26 @@ function matchesTagPattern(tag, pattern) {
|
|
|
1332
1548
|
const re = new RegExp(`^${escaped}$`);
|
|
1333
1549
|
return re.test(tag);
|
|
1334
1550
|
}
|
|
1551
|
+
function extractWordStem(cardId) {
|
|
1552
|
+
for (const prefix of ["c-ml-", "c-ws-", "c-spelling-"]) {
|
|
1553
|
+
if (cardId.startsWith(prefix)) {
|
|
1554
|
+
const rest = cardId.slice(prefix.length);
|
|
1555
|
+
const lastDash = rest.lastIndexOf("-");
|
|
1556
|
+
return lastDash > 0 ? rest.slice(0, lastDash) : rest;
|
|
1557
|
+
}
|
|
1558
|
+
}
|
|
1559
|
+
return cardId;
|
|
1560
|
+
}
|
|
1561
|
+
function shuffleInPlace(arr) {
|
|
1562
|
+
for (let i = arr.length - 1; i > 0; i--) {
|
|
1563
|
+
const j = Math.floor(Math.random() * (i + 1));
|
|
1564
|
+
[arr[i], arr[j]] = [arr[j], arr[i]];
|
|
1565
|
+
}
|
|
1566
|
+
}
|
|
1335
1567
|
function pickTopByScore(cards, limit) {
|
|
1336
1568
|
return [...cards].sort((a, b) => b.score - a.score || a.cardId.localeCompare(b.cardId)).slice(0, limit);
|
|
1337
1569
|
}
|
|
1338
|
-
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,
|
|
1570
|
+
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;
|
|
1339
1571
|
var init_prescribed = __esm({
|
|
1340
1572
|
"src/core/navigators/generators/prescribed.ts"() {
|
|
1341
1573
|
"use strict";
|
|
@@ -1348,11 +1580,10 @@ var init_prescribed = __esm({
|
|
|
1348
1580
|
DEFAULT_MIN_COUNT = 3;
|
|
1349
1581
|
BASE_TARGET_SCORE = 1;
|
|
1350
1582
|
BASE_SUPPORT_SCORE = 0.8;
|
|
1583
|
+
DISCOVERED_SUPPORT_SCORE = 12;
|
|
1351
1584
|
MAX_TARGET_MULTIPLIER = 8;
|
|
1352
1585
|
MAX_SUPPORT_MULTIPLIER = 4;
|
|
1353
|
-
|
|
1354
|
-
LESSON_GATE_PENALTY_TAG_HINT = "concept:";
|
|
1355
|
-
PRESCRIBED_DEBUG_VERSION = "testversion-prescribed-v2";
|
|
1586
|
+
PRESCRIBED_DEBUG_VERSION = "testversion-prescribed-v3";
|
|
1356
1587
|
PrescribedCardsGenerator = class extends ContentNavigator {
|
|
1357
1588
|
name;
|
|
1358
1589
|
config;
|
|
@@ -1388,6 +1619,20 @@ var init_prescribed = __esm({
|
|
|
1388
1619
|
const allSupportIds = dedupe(this.config.groups.flatMap((g) => g.supportCardIds ?? []));
|
|
1389
1620
|
const allRelevantIds = dedupe([...allTargetIds, ...allSupportIds]);
|
|
1390
1621
|
const tagsByCard = allRelevantIds.length > 0 ? await this.course.getAppliedTagsBatch(allRelevantIds) : /* @__PURE__ */ new Map();
|
|
1622
|
+
const courseTagDocs = await this.course.getCourseTagStubs().catch(
|
|
1623
|
+
() => ({
|
|
1624
|
+
rows: [],
|
|
1625
|
+
offset: 0,
|
|
1626
|
+
total_rows: 0
|
|
1627
|
+
})
|
|
1628
|
+
);
|
|
1629
|
+
const cardsByTag = /* @__PURE__ */ new Map();
|
|
1630
|
+
for (const row of courseTagDocs.rows ?? []) {
|
|
1631
|
+
const tagDoc = row.doc;
|
|
1632
|
+
if (tagDoc?.name && Array.isArray(tagDoc.taggedCards)) {
|
|
1633
|
+
cardsByTag.set(tagDoc.name, [...tagDoc.taggedCards]);
|
|
1634
|
+
}
|
|
1635
|
+
}
|
|
1391
1636
|
const nextState = {
|
|
1392
1637
|
updatedAt: isoNow(),
|
|
1393
1638
|
groups: {}
|
|
@@ -1402,11 +1647,31 @@ var init_prescribed = __esm({
|
|
|
1402
1647
|
activeIds,
|
|
1403
1648
|
seenIds,
|
|
1404
1649
|
tagsByCard,
|
|
1650
|
+
cardsByTag,
|
|
1405
1651
|
hierarchyConfigs,
|
|
1406
1652
|
userTagElo,
|
|
1407
1653
|
userGlobalElo
|
|
1408
1654
|
});
|
|
1409
1655
|
groupRuntimes.push(runtime);
|
|
1656
|
+
logger.info(
|
|
1657
|
+
`[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)}`
|
|
1658
|
+
);
|
|
1659
|
+
if (runtime.blockedTargets.length > 0) {
|
|
1660
|
+
logger.info(
|
|
1661
|
+
`[Prescribed] Group '${group.id}' blocked targets: ${runtime.blockedTargets.join(", ")}`
|
|
1662
|
+
);
|
|
1663
|
+
logger.info(
|
|
1664
|
+
`[Prescribed] Group '${group.id}' support tags needed: ${runtime.supportTags.join(", ") || "(none)"}`
|
|
1665
|
+
);
|
|
1666
|
+
logger.info(
|
|
1667
|
+
`[Prescribed] Group '${group.id}' escalation mode: ` + (runtime.supportCandidates.length > 0 ? "direct-support" : runtime.discoveredSupportCandidates.length > 0 ? "inserted-support-candidates" : "boost-only")
|
|
1668
|
+
);
|
|
1669
|
+
if (runtime.discoveredSupportCandidates.length > 0) {
|
|
1670
|
+
logger.info(
|
|
1671
|
+
`[Prescribed] Group '${group.id}' discovered support candidates: ${runtime.discoveredSupportCandidates.join(", ")}`
|
|
1672
|
+
);
|
|
1673
|
+
}
|
|
1674
|
+
}
|
|
1410
1675
|
nextState.groups[group.id] = this.buildNextGroupState(runtime, progress.groups[group.id]);
|
|
1411
1676
|
const directCards = this.buildDirectTargetCards(
|
|
1412
1677
|
runtime,
|
|
@@ -1418,15 +1683,30 @@ var init_prescribed = __esm({
|
|
|
1418
1683
|
courseId,
|
|
1419
1684
|
emittedIds
|
|
1420
1685
|
);
|
|
1421
|
-
|
|
1686
|
+
const discoveredSupportCards = this.buildDiscoveredSupportCards(
|
|
1687
|
+
runtime,
|
|
1688
|
+
courseId,
|
|
1689
|
+
emittedIds
|
|
1690
|
+
);
|
|
1691
|
+
emitted.push(...directCards, ...supportCards, ...discoveredSupportCards);
|
|
1422
1692
|
}
|
|
1423
1693
|
const hintSummary = this.buildSupportHintSummary(groupRuntimes);
|
|
1424
1694
|
const hints = Object.keys(hintSummary.boostTags).length > 0 ? {
|
|
1425
1695
|
boostTags: hintSummary.boostTags,
|
|
1426
1696
|
_label: `prescribed-support (${hintSummary.supportTags.length} tags; blocked=${hintSummary.blockedTargetIds.length}; testversion=${PRESCRIBED_DEBUG_VERSION})`
|
|
1427
1697
|
} : void 0;
|
|
1698
|
+
if (hints) {
|
|
1699
|
+
const tagEntries = Object.entries(hints.boostTags ?? {});
|
|
1700
|
+
logger.info(
|
|
1701
|
+
`[Prescribed] Emitting ${tagEntries.length} boost hint(s): ` + tagEntries.map(([tag, mult]) => `${tag}\xD7${mult.toFixed(1)}`).join(", ")
|
|
1702
|
+
);
|
|
1703
|
+
} else {
|
|
1704
|
+
logger.info("[Prescribed] No hints to emit (no blocked targets or no support tags)");
|
|
1705
|
+
}
|
|
1428
1706
|
if (emitted.length === 0) {
|
|
1429
|
-
logger.
|
|
1707
|
+
logger.info(
|
|
1708
|
+
"[Prescribed] 0 cards emitted (all targets blocked, authored/discovered support candidates exhausted)" + (hints ? " \u2014 boost hints emitted but may not survive filters" : "")
|
|
1709
|
+
);
|
|
1430
1710
|
await this.putStrategyState(nextState).catch((e) => {
|
|
1431
1711
|
logger.debug(`[Prescribed] Failed to persist empty-state update: ${e}`);
|
|
1432
1712
|
});
|
|
@@ -1459,7 +1739,7 @@ var init_prescribed = __esm({
|
|
|
1459
1739
|
logger.debug(`[Prescribed] Failed to persist prescribed progress: ${e}`);
|
|
1460
1740
|
});
|
|
1461
1741
|
logger.info(
|
|
1462
|
-
`[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)`
|
|
1742
|
+
`[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)`
|
|
1463
1743
|
);
|
|
1464
1744
|
return hints ? { cards: finalCards, hints } : { cards: finalCards };
|
|
1465
1745
|
}
|
|
@@ -1489,9 +1769,15 @@ var init_prescribed = __esm({
|
|
|
1489
1769
|
const groupsRaw = Array.isArray(parsed.groups) ? parsed.groups : [];
|
|
1490
1770
|
const groups = groupsRaw.map((raw, i) => ({
|
|
1491
1771
|
id: typeof raw.id === "string" && raw.id.trim().length > 0 ? raw.id : `group-${i + 1}`,
|
|
1492
|
-
targetCardIds: dedupe(
|
|
1493
|
-
|
|
1494
|
-
|
|
1772
|
+
targetCardIds: dedupe(
|
|
1773
|
+
Array.isArray(raw.targetCardIds) ? raw.targetCardIds.filter((v) => typeof v === "string") : []
|
|
1774
|
+
),
|
|
1775
|
+
supportCardIds: dedupe(
|
|
1776
|
+
Array.isArray(raw.supportCardIds) ? raw.supportCardIds.filter((v) => typeof v === "string") : []
|
|
1777
|
+
),
|
|
1778
|
+
supportTagPatterns: dedupe(
|
|
1779
|
+
Array.isArray(raw.supportTagPatterns) ? raw.supportTagPatterns.filter((v) => typeof v === "string") : []
|
|
1780
|
+
),
|
|
1495
1781
|
freshnessWindowSessions: typeof raw.freshnessWindowSessions === "number" ? raw.freshnessWindowSessions : DEFAULT_FRESHNESS_WINDOW,
|
|
1496
1782
|
maxDirectTargetsPerRun: typeof raw.maxDirectTargetsPerRun === "number" ? raw.maxDirectTargetsPerRun : DEFAULT_MAX_DIRECT_PER_RUN,
|
|
1497
1783
|
maxSupportCardsPerRun: typeof raw.maxSupportCardsPerRun === "number" ? raw.maxSupportCardsPerRun : DEFAULT_MAX_SUPPORT_PER_RUN,
|
|
@@ -1508,7 +1794,7 @@ var init_prescribed = __esm({
|
|
|
1508
1794
|
}
|
|
1509
1795
|
async loadHierarchyConfigs() {
|
|
1510
1796
|
try {
|
|
1511
|
-
const strategies = await this.course.
|
|
1797
|
+
const strategies = await this.course.getAllNavigationStrategies();
|
|
1512
1798
|
return strategies.filter((s) => s.implementingClass === "hierarchyDefinition").map((s) => {
|
|
1513
1799
|
try {
|
|
1514
1800
|
const parsed = JSON.parse(s.serializedData);
|
|
@@ -1531,6 +1817,7 @@ var init_prescribed = __esm({
|
|
|
1531
1817
|
activeIds,
|
|
1532
1818
|
seenIds,
|
|
1533
1819
|
tagsByCard,
|
|
1820
|
+
cardsByTag,
|
|
1534
1821
|
hierarchyConfigs,
|
|
1535
1822
|
userTagElo,
|
|
1536
1823
|
userGlobalElo
|
|
@@ -1594,6 +1881,22 @@ var init_prescribed = __esm({
|
|
|
1594
1881
|
[...supportTags]
|
|
1595
1882
|
)
|
|
1596
1883
|
]).filter((id) => !activeIds.has(id) && !seenIds.has(id));
|
|
1884
|
+
const discoveredSupportCandidates = blockedTargets.length > 0 && supportTags.size > 0 && supportCandidates.length === 0 ? this.findDiscoveredSupportCards({
|
|
1885
|
+
supportTags: [...supportTags],
|
|
1886
|
+
cardsByTag,
|
|
1887
|
+
activeIds,
|
|
1888
|
+
seenIds,
|
|
1889
|
+
excludedIds: /* @__PURE__ */ new Set([
|
|
1890
|
+
...group.targetCardIds,
|
|
1891
|
+
...group.supportCardIds ?? []
|
|
1892
|
+
]),
|
|
1893
|
+
limit: group.maxSupportCardsPerRun ?? DEFAULT_MAX_SUPPORT_PER_RUN
|
|
1894
|
+
}) : [];
|
|
1895
|
+
if (blockedTargets.length > 0 && supportTags.size > 0 && discoveredSupportCandidates.length === 0) {
|
|
1896
|
+
logger.info(
|
|
1897
|
+
`[Prescribed] Group '${group.id}' discovered 0 broader support candidates (blocked=${blockedTargets.length}; authoredSupport=${supportCandidates.length})`
|
|
1898
|
+
);
|
|
1899
|
+
}
|
|
1597
1900
|
const sessionsSinceSurfaced = priorState?.sessionsSinceSurfaced ?? 0;
|
|
1598
1901
|
const freshnessWindow = group.freshnessWindowSessions ?? DEFAULT_FRESHNESS_WINDOW;
|
|
1599
1902
|
const staleSessions = Math.max(0, sessionsSinceSurfaced - freshnessWindow);
|
|
@@ -1607,6 +1910,7 @@ var init_prescribed = __esm({
|
|
|
1607
1910
|
surfaceableTargets,
|
|
1608
1911
|
targetTags,
|
|
1609
1912
|
supportCandidates,
|
|
1913
|
+
discoveredSupportCandidates,
|
|
1610
1914
|
supportTags: [...supportTags],
|
|
1611
1915
|
pressureMultiplier,
|
|
1612
1916
|
supportMultiplier,
|
|
@@ -1677,6 +1981,33 @@ var init_prescribed = __esm({
|
|
|
1677
1981
|
}
|
|
1678
1982
|
return cards;
|
|
1679
1983
|
}
|
|
1984
|
+
buildDiscoveredSupportCards(runtime, courseId, emittedIds) {
|
|
1985
|
+
if (runtime.blockedTargets.length === 0 || runtime.discoveredSupportCandidates.length === 0) {
|
|
1986
|
+
return [];
|
|
1987
|
+
}
|
|
1988
|
+
const maxSupport = runtime.group.maxSupportCardsPerRun ?? DEFAULT_MAX_SUPPORT_PER_RUN;
|
|
1989
|
+
const supportIds = runtime.discoveredSupportCandidates.filter((id) => !emittedIds.has(id)).slice(0, maxSupport);
|
|
1990
|
+
const cards = [];
|
|
1991
|
+
for (const cardId of supportIds) {
|
|
1992
|
+
emittedIds.add(cardId);
|
|
1993
|
+
cards.push({
|
|
1994
|
+
cardId,
|
|
1995
|
+
courseId,
|
|
1996
|
+
score: DISCOVERED_SUPPORT_SCORE * runtime.supportMultiplier,
|
|
1997
|
+
provenance: [
|
|
1998
|
+
{
|
|
1999
|
+
strategy: "prescribed",
|
|
2000
|
+
strategyName: this.strategyName || this.name,
|
|
2001
|
+
strategyId: this.strategyId || "NAVIGATION_STRATEGY-prescribed",
|
|
2002
|
+
action: "generated",
|
|
2003
|
+
score: DISCOVERED_SUPPORT_SCORE * runtime.supportMultiplier,
|
|
2004
|
+
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}`
|
|
2005
|
+
}
|
|
2006
|
+
]
|
|
2007
|
+
});
|
|
2008
|
+
}
|
|
2009
|
+
return cards;
|
|
2010
|
+
}
|
|
1680
2011
|
findSupportCardsByTags(group, tagsByCard, supportTags) {
|
|
1681
2012
|
if (supportTags.length === 0) {
|
|
1682
2013
|
return [];
|
|
@@ -1699,6 +2030,40 @@ var init_prescribed = __esm({
|
|
|
1699
2030
|
}
|
|
1700
2031
|
return [...candidates];
|
|
1701
2032
|
}
|
|
2033
|
+
findDiscoveredSupportCards(args) {
|
|
2034
|
+
const { supportTags, cardsByTag, activeIds, seenIds, excludedIds, limit } = args;
|
|
2035
|
+
const byCardId = /* @__PURE__ */ new Map();
|
|
2036
|
+
for (const supportTag of supportTags) {
|
|
2037
|
+
const taggedCards = cardsByTag.get(supportTag) ?? [];
|
|
2038
|
+
for (const cardId of taggedCards) {
|
|
2039
|
+
if (activeIds.has(cardId) || seenIds.has(cardId) || excludedIds.has(cardId)) {
|
|
2040
|
+
continue;
|
|
2041
|
+
}
|
|
2042
|
+
const existing = byCardId.get(cardId);
|
|
2043
|
+
if (existing) {
|
|
2044
|
+
existing.matches += 1;
|
|
2045
|
+
} else {
|
|
2046
|
+
byCardId.set(cardId, { cardId, matches: 1 });
|
|
2047
|
+
}
|
|
2048
|
+
}
|
|
2049
|
+
}
|
|
2050
|
+
const candidates = [...byCardId.values()].sort((a, b) => b.matches - a.matches || a.cardId.localeCompare(b.cardId));
|
|
2051
|
+
const usedStems = /* @__PURE__ */ new Set();
|
|
2052
|
+
const diverse = [];
|
|
2053
|
+
const deferred = [];
|
|
2054
|
+
for (const entry of candidates) {
|
|
2055
|
+
const stem = extractWordStem(entry.cardId);
|
|
2056
|
+
if (!usedStems.has(stem)) {
|
|
2057
|
+
usedStems.add(stem);
|
|
2058
|
+
diverse.push(entry);
|
|
2059
|
+
} else {
|
|
2060
|
+
deferred.push(entry);
|
|
2061
|
+
}
|
|
2062
|
+
}
|
|
2063
|
+
shuffleInPlace(diverse);
|
|
2064
|
+
shuffleInPlace(deferred);
|
|
2065
|
+
return [...diverse, ...deferred].slice(0, limit).map((entry) => entry.cardId);
|
|
2066
|
+
}
|
|
1702
2067
|
resolveBlockedSupportTags(targetTags, hierarchyConfigs, userTagElo, userGlobalElo, hierarchyWalkEnabled, maxDepth) {
|
|
1703
2068
|
const supportTags = /* @__PURE__ */ new Set();
|
|
1704
2069
|
let blocked = false;
|
|
@@ -1744,7 +2109,6 @@ var init_prescribed = __esm({
|
|
|
1744
2109
|
}
|
|
1745
2110
|
collectSupportTagsRecursive(tag, hierarchyConfigs, userTagElo, userGlobalElo, depth, visited, out) {
|
|
1746
2111
|
if (depth < 0 || visited.has(tag)) return;
|
|
1747
|
-
if (this.isHardGatedTag(tag)) return;
|
|
1748
2112
|
visited.add(tag);
|
|
1749
2113
|
let walkedFurther = false;
|
|
1750
2114
|
for (const hierarchy of hierarchyConfigs) {
|
|
@@ -1772,9 +2136,6 @@ var init_prescribed = __esm({
|
|
|
1772
2136
|
out.add(tag);
|
|
1773
2137
|
}
|
|
1774
2138
|
}
|
|
1775
|
-
isHardGatedTag(tag) {
|
|
1776
|
-
return LOCKED_TAG_PREFIXES.some((prefix) => tag.startsWith(prefix)) && tag.startsWith(LESSON_GATE_PENALTY_TAG_HINT);
|
|
1777
|
-
}
|
|
1778
2139
|
isPrerequisiteMet(prereq, userTagElo, userGlobalElo) {
|
|
1779
2140
|
if (!userTagElo) return false;
|
|
1780
2141
|
const minCount = prereq.masteryThreshold?.minCount ?? DEFAULT_MIN_COUNT;
|
|
@@ -3320,6 +3681,32 @@ var init_Pipeline = __esm({
|
|
|
3320
3681
|
cards = await this.hydrateTags(cards);
|
|
3321
3682
|
const tHydrate = performance.now();
|
|
3322
3683
|
const allCardsBeforeFiltering = [...cards];
|
|
3684
|
+
const pendingHints = this._ephemeralHints;
|
|
3685
|
+
if (pendingHints?.requireCards?.length) {
|
|
3686
|
+
const poolIds = new Set(allCardsBeforeFiltering.map((c) => c.cardId));
|
|
3687
|
+
const missingIds = pendingHints.requireCards.filter(
|
|
3688
|
+
(p) => !p.includes("*") && !poolIds.has(p)
|
|
3689
|
+
);
|
|
3690
|
+
if (missingIds.length > 0) {
|
|
3691
|
+
const fetchedTags = await this.course.getAppliedTagsBatch(missingIds);
|
|
3692
|
+
const courseId = this.course.getCourseID();
|
|
3693
|
+
for (const cardId of missingIds) {
|
|
3694
|
+
allCardsBeforeFiltering.push({
|
|
3695
|
+
cardId,
|
|
3696
|
+
courseId,
|
|
3697
|
+
score: 1,
|
|
3698
|
+
tags: fetchedTags.get(cardId) ?? [],
|
|
3699
|
+
provenance: []
|
|
3700
|
+
});
|
|
3701
|
+
}
|
|
3702
|
+
logger.info(
|
|
3703
|
+
`[Pipeline] Pre-fetched ${missingIds.length} required card(s) into pool: ${missingIds.join(", ")}`
|
|
3704
|
+
);
|
|
3705
|
+
}
|
|
3706
|
+
}
|
|
3707
|
+
const prescribedIds = new Set(
|
|
3708
|
+
cards.filter((c) => c.provenance.some((p) => p.strategy === "prescribed")).map((c) => c.cardId)
|
|
3709
|
+
);
|
|
3323
3710
|
const filterImpacts = [];
|
|
3324
3711
|
for (const filter of this.filters) {
|
|
3325
3712
|
const beforeCount = cards.length;
|
|
@@ -3334,6 +3721,17 @@ var init_Pipeline = __esm({
|
|
|
3334
3721
|
else passed++;
|
|
3335
3722
|
}
|
|
3336
3723
|
filterImpacts.push({ name: filter.name, boosted, penalized, passed, removed });
|
|
3724
|
+
if (prescribedIds.size > 0) {
|
|
3725
|
+
const survivingIds = new Set(cards.map((c) => c.cardId));
|
|
3726
|
+
const killedPrescribed = [...prescribedIds].filter((id) => !survivingIds.has(id));
|
|
3727
|
+
const zeroedPrescribed = cards.filter((c) => prescribedIds.has(c.cardId) && c.score === 0).map((c) => c.cardId);
|
|
3728
|
+
if (killedPrescribed.length > 0 || zeroedPrescribed.length > 0) {
|
|
3729
|
+
logger.info(
|
|
3730
|
+
`[Pipeline] Filter '${filter.name}' impact on prescribed cards: ` + (killedPrescribed.length > 0 ? `removed=[${killedPrescribed.join(", ")}] ` : "") + (zeroedPrescribed.length > 0 ? `zeroed=[${zeroedPrescribed.join(", ")}]` : "")
|
|
3731
|
+
);
|
|
3732
|
+
killedPrescribed.forEach((id) => prescribedIds.delete(id));
|
|
3733
|
+
}
|
|
3734
|
+
}
|
|
3337
3735
|
logger.debug(`[Pipeline] Filter '${filter.name}': ${beforeScores.size} \u2192 ${cards.length} cards (\u2191${boosted} \u2193${penalized} =${passed})`);
|
|
3338
3736
|
}
|
|
3339
3737
|
cards = cards.filter((c) => c.score > 0);
|
|
@@ -3370,7 +3768,8 @@ var init_Pipeline = __esm({
|
|
|
3370
3768
|
filterImpacts,
|
|
3371
3769
|
cards,
|
|
3372
3770
|
result,
|
|
3373
|
-
context.userElo
|
|
3771
|
+
context.userElo,
|
|
3772
|
+
hints ?? void 0
|
|
3374
3773
|
);
|
|
3375
3774
|
captureRun(report);
|
|
3376
3775
|
} catch (e) {
|
|
@@ -3480,13 +3879,27 @@ var init_Pipeline = __esm({
|
|
|
3480
3879
|
}
|
|
3481
3880
|
}
|
|
3482
3881
|
const cardIds = new Set(cards.map((c) => c.cardId));
|
|
3882
|
+
const cardMap = new Map(cards.map((c) => [c.cardId, c]));
|
|
3483
3883
|
const hintLabel = hints._label ? `Replan Hint (${hints._label})` : "Replan Hint";
|
|
3484
|
-
const
|
|
3485
|
-
|
|
3486
|
-
|
|
3884
|
+
const applyRequirement = (card, reason) => {
|
|
3885
|
+
const mandatoryScore = Number.POSITIVE_INFINITY;
|
|
3886
|
+
const existing = cardMap.get(card.cardId);
|
|
3887
|
+
if (existing) {
|
|
3888
|
+
if (existing.score < mandatoryScore) {
|
|
3889
|
+
existing.score = mandatoryScore;
|
|
3890
|
+
existing.provenance.push({
|
|
3891
|
+
strategy: "ephemeralHint",
|
|
3892
|
+
strategyId: "ephemeral-hint",
|
|
3893
|
+
strategyName: hintLabel,
|
|
3894
|
+
action: "boosted",
|
|
3895
|
+
score: mandatoryScore,
|
|
3896
|
+
reason: `${reason} (upgrade to mandatory score)`
|
|
3897
|
+
});
|
|
3898
|
+
}
|
|
3899
|
+
} else {
|
|
3487
3900
|
cards.push({
|
|
3488
3901
|
...card,
|
|
3489
|
-
score:
|
|
3902
|
+
score: mandatoryScore,
|
|
3490
3903
|
provenance: [
|
|
3491
3904
|
...card.provenance,
|
|
3492
3905
|
{
|
|
@@ -3494,25 +3907,41 @@ var init_Pipeline = __esm({
|
|
|
3494
3907
|
strategyId: "ephemeral-hint",
|
|
3495
3908
|
strategyName: hintLabel,
|
|
3496
3909
|
action: "boosted",
|
|
3497
|
-
score:
|
|
3910
|
+
score: mandatoryScore,
|
|
3498
3911
|
reason
|
|
3499
3912
|
}
|
|
3500
3913
|
]
|
|
3501
3914
|
});
|
|
3502
3915
|
cardIds.add(card.cardId);
|
|
3916
|
+
cardMap.set(card.cardId, cards[cards.length - 1]);
|
|
3503
3917
|
}
|
|
3504
3918
|
};
|
|
3505
3919
|
if (hints.requireCards?.length) {
|
|
3506
3920
|
for (const pattern of hints.requireCards) {
|
|
3921
|
+
for (const cardId of cardIds) {
|
|
3922
|
+
if (globMatch(cardId, pattern)) {
|
|
3923
|
+
applyRequirement(cardMap.get(cardId), `requireCard ${pattern}`);
|
|
3924
|
+
}
|
|
3925
|
+
}
|
|
3507
3926
|
for (const card of allCards) {
|
|
3508
|
-
if (globMatch(card.cardId, pattern))
|
|
3927
|
+
if (globMatch(card.cardId, pattern)) {
|
|
3928
|
+
applyRequirement(card, `requireCard ${pattern}`);
|
|
3929
|
+
}
|
|
3509
3930
|
}
|
|
3510
3931
|
}
|
|
3511
3932
|
}
|
|
3512
3933
|
if (hints.requireTags?.length) {
|
|
3513
3934
|
for (const pattern of hints.requireTags) {
|
|
3935
|
+
for (const cardId of cardIds) {
|
|
3936
|
+
const card = cardMap.get(cardId);
|
|
3937
|
+
if (cardMatchesTagPattern(card, pattern)) {
|
|
3938
|
+
applyRequirement(card, `requireTag ${pattern}`);
|
|
3939
|
+
}
|
|
3940
|
+
}
|
|
3514
3941
|
for (const card of allCards) {
|
|
3515
|
-
if (cardMatchesTagPattern(card, pattern))
|
|
3942
|
+
if (cardMatchesTagPattern(card, pattern)) {
|
|
3943
|
+
applyRequirement(card, `requireTag ${pattern}`);
|
|
3944
|
+
}
|
|
3516
3945
|
}
|
|
3517
3946
|
}
|
|
3518
3947
|
}
|