@vue-skuilder/db 0.1.32-e → 0.1.33-vite8-4
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 +457 -28
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.mjs +457 -28
- package/dist/core/index.mjs.map +1 -1
- package/dist/impl/couch/index.js +457 -28
- package/dist/impl/couch/index.js.map +1 -1
- package/dist/impl/couch/index.mjs +457 -28
- 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.js +457 -28
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +457 -28
- 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/tests/core/navigators/CompositeGenerator.test.ts +14 -50
- package/tests/core/navigators/Pipeline.test.ts +13 -12
|
@@ -637,8 +637,9 @@ function getOrigin(card) {
|
|
|
637
637
|
const firstEntry = card.provenance[0];
|
|
638
638
|
if (!firstEntry) return "unknown";
|
|
639
639
|
const reason = firstEntry.reason?.toLowerCase() || "";
|
|
640
|
-
|
|
641
|
-
if (reason.includes("
|
|
640
|
+
const strategy = firstEntry.strategy?.toLowerCase() || "";
|
|
641
|
+
if (reason.includes("new card") || strategy.includes("elo")) return "new";
|
|
642
|
+
if (reason.includes("review") || strategy.includes("srs")) return "review";
|
|
642
643
|
return "unknown";
|
|
643
644
|
}
|
|
644
645
|
function captureRun(report) {
|
|
@@ -658,12 +659,13 @@ function parseCardElo(provenance) {
|
|
|
658
659
|
const match = eloEntry.reason.match(/card:\s*(\d+)/);
|
|
659
660
|
return match ? parseInt(match[1], 10) : void 0;
|
|
660
661
|
}
|
|
661
|
-
function buildRunReport(courseId, courseName, generatorName, generators, generatedCount, filters, allCards, selectedCards, userElo) {
|
|
662
|
+
function buildRunReport(courseId, courseName, generatorName, generators, generatedCount, filters, allCards, selectedCards, userElo, hints) {
|
|
662
663
|
const selectedIds = new Set(selectedCards.map((c) => c.cardId));
|
|
663
664
|
const cards = allCards.map((card) => ({
|
|
664
665
|
cardId: card.cardId,
|
|
665
666
|
courseId: card.courseId,
|
|
666
667
|
origin: getOrigin(card),
|
|
668
|
+
generator: card.provenance[0]?.strategyName || card.provenance[0]?.strategy,
|
|
667
669
|
finalScore: card.score,
|
|
668
670
|
cardElo: parseCardElo(card.provenance),
|
|
669
671
|
provenance: card.provenance,
|
|
@@ -680,6 +682,7 @@ function buildRunReport(courseId, courseName, generatorName, generators, generat
|
|
|
680
682
|
generators,
|
|
681
683
|
generatedCount,
|
|
682
684
|
filters,
|
|
685
|
+
hints,
|
|
683
686
|
finalCount: selectedCards.length,
|
|
684
687
|
reviewsSelected,
|
|
685
688
|
newSelected,
|
|
@@ -719,13 +722,169 @@ function printRunSummary(run) {
|
|
|
719
722
|
);
|
|
720
723
|
console.groupEnd();
|
|
721
724
|
}
|
|
725
|
+
function renderUI() {
|
|
726
|
+
if (!_uiContainer) return;
|
|
727
|
+
const runs = runHistory;
|
|
728
|
+
const selectedRun = _selectedRunIndex !== null ? runs[_selectedRunIndex] : null;
|
|
729
|
+
const styles = `
|
|
730
|
+
#sk-pipeline-debugger {
|
|
731
|
+
position: fixed;
|
|
732
|
+
top: 0;
|
|
733
|
+
left: 0;
|
|
734
|
+
width: 100vw;
|
|
735
|
+
height: 100vh;
|
|
736
|
+
background: #f8f9fa;
|
|
737
|
+
color: #212529;
|
|
738
|
+
z-index: 999999;
|
|
739
|
+
display: flex;
|
|
740
|
+
flex-direction: column;
|
|
741
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
|
742
|
+
font-size: 14px;
|
|
743
|
+
}
|
|
744
|
+
#sk-pipeline-debugger header {
|
|
745
|
+
padding: 1rem;
|
|
746
|
+
background: #343a40;
|
|
747
|
+
color: white;
|
|
748
|
+
display: flex;
|
|
749
|
+
justify-content: space-between;
|
|
750
|
+
align-items: center;
|
|
751
|
+
}
|
|
752
|
+
#sk-pipeline-debugger .container {
|
|
753
|
+
display: flex;
|
|
754
|
+
flex: 1;
|
|
755
|
+
overflow: hidden;
|
|
756
|
+
}
|
|
757
|
+
#sk-pipeline-debugger .sidebar {
|
|
758
|
+
width: 300px;
|
|
759
|
+
border-right: 1px solid #dee2e6;
|
|
760
|
+
overflow-y: auto;
|
|
761
|
+
background: white;
|
|
762
|
+
}
|
|
763
|
+
#sk-pipeline-debugger .main-content {
|
|
764
|
+
flex: 1;
|
|
765
|
+
overflow-y: auto;
|
|
766
|
+
padding: 1.5rem;
|
|
767
|
+
}
|
|
768
|
+
#sk-pipeline-debugger .run-item {
|
|
769
|
+
padding: 0.75rem 1rem;
|
|
770
|
+
border-bottom: 1px solid #eee;
|
|
771
|
+
cursor: pointer;
|
|
772
|
+
}
|
|
773
|
+
#sk-pipeline-debugger .run-item:hover { background: #f1f3f5; }
|
|
774
|
+
#sk-pipeline-debugger .run-item.active { background: #e9ecef; border-left: 4px solid #007bff; }
|
|
775
|
+
#sk-pipeline-debugger h2, #sk-pipeline-debugger h3 { margin-top: 0; }
|
|
776
|
+
#sk-pipeline-debugger table { width: 100%; border-collapse: collapse; margin-bottom: 1rem; background: white; }
|
|
777
|
+
#sk-pipeline-debugger th, #sk-pipeline-debugger td { border: 1px solid #dee2e6; padding: 0.5rem; text-align: left; }
|
|
778
|
+
#sk-pipeline-debugger th { background: #f1f3f5; }
|
|
779
|
+
#sk-pipeline-debugger code { background: #f1f3f5; padding: 0.1rem 0.3rem; border-radius: 3px; font-family: monospace; }
|
|
780
|
+
#sk-pipeline-debugger .close-btn { background: #dc3545; color: white; border: none; padding: 0.5rem 1rem; border-radius: 4px; cursor: pointer; }
|
|
781
|
+
#sk-pipeline-debugger .search-box { margin-bottom: 1rem; width: 100%; padding: 0.5rem; border: 1px solid #ced4da; border-radius: 4px; }
|
|
782
|
+
#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; }
|
|
783
|
+
`;
|
|
784
|
+
const runListHtml = runs.length === 0 ? '<div style="padding: 1rem;">No runs captured yet.</div>' : runs.map(
|
|
785
|
+
(r, i) => `
|
|
786
|
+
<div class="run-item ${i === _selectedRunIndex ? "active" : ""}" onclick="window.skuilder.pipeline._selectRun(${i})">
|
|
787
|
+
<strong>${r.timestamp.toLocaleTimeString()}</strong><br/>
|
|
788
|
+
<small>${r.courseName || r.courseId.slice(0, 8)}</small><br/>
|
|
789
|
+
<small>${r.finalCount} cards selected</small>
|
|
790
|
+
</div>
|
|
791
|
+
`
|
|
792
|
+
).join("");
|
|
793
|
+
let detailsHtml = '<div style="color: #6c757d; text-align: center; margin-top: 5rem;">Select a run to see details</div>';
|
|
794
|
+
if (selectedRun) {
|
|
795
|
+
const filteredCards = selectedRun.cards.filter(
|
|
796
|
+
(c) => !_cardSearchQuery || c.cardId.toLowerCase().includes(_cardSearchQuery.toLowerCase())
|
|
797
|
+
);
|
|
798
|
+
detailsHtml = `
|
|
799
|
+
<h2>Run: ${selectedRun.runId}</h2>
|
|
800
|
+
<p>
|
|
801
|
+
<strong>Time:</strong> ${selectedRun.timestamp.toLocaleString()} |
|
|
802
|
+
<strong>Course:</strong> ${selectedRun.courseName || selectedRun.courseId} |
|
|
803
|
+
<strong>User ELO:</strong> ${selectedRun.userElo ?? "unknown"}
|
|
804
|
+
</p>
|
|
805
|
+
|
|
806
|
+
<h3>Pipeline Config</h3>
|
|
807
|
+
<table>
|
|
808
|
+
<tr><th>Generator</th><td>${selectedRun.generatorName} (${selectedRun.generatedCount} candidates)</td></tr>
|
|
809
|
+
${(selectedRun.generators || []).map(
|
|
810
|
+
(g) => `
|
|
811
|
+
<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>
|
|
812
|
+
`
|
|
813
|
+
).join("")}
|
|
814
|
+
</table>
|
|
815
|
+
|
|
816
|
+
${selectedRun.hints ? `
|
|
817
|
+
<h3>Ephemeral Hints</h3>
|
|
818
|
+
<table>
|
|
819
|
+
${selectedRun.hints._label ? `<tr><th>Label</th><td>${selectedRun.hints._label}</td></tr>` : ""}
|
|
820
|
+
${selectedRun.hints.boostTags ? `<tr><th>Boost Tags</th><td><pre style="margin:0">${JSON.stringify(selectedRun.hints.boostTags, null, 2)}</pre></td></tr>` : ""}
|
|
821
|
+
${selectedRun.hints.boostCards ? `<tr><th>Boost Cards</th><td><pre style="margin:0">${JSON.stringify(selectedRun.hints.boostCards, null, 2)}</pre></td></tr>` : ""}
|
|
822
|
+
${selectedRun.hints.requireTags ? `<tr><th>Require Tags</th><td>${selectedRun.hints.requireTags.join(", ")}</td></tr>` : ""}
|
|
823
|
+
${selectedRun.hints.requireCards ? `<tr><th>Require Cards</th><td>${selectedRun.hints.requireCards.join(", ")}</td></tr>` : ""}
|
|
824
|
+
${selectedRun.hints.excludeTags ? `<tr><th>Exclude Tags</th><td>${selectedRun.hints.excludeTags.join(", ")}</td></tr>` : ""}
|
|
825
|
+
${selectedRun.hints.excludeCards ? `<tr><th>Exclude Cards</th><td>${selectedRun.hints.excludeCards.join(", ")}</td></tr>` : ""}
|
|
826
|
+
</table>
|
|
827
|
+
` : ""}
|
|
828
|
+
|
|
829
|
+
<h3>Filter Impact</h3>
|
|
830
|
+
<table>
|
|
831
|
+
<thead><tr><th>Filter</th><th>Boosted</th><th>Penalized</th><th>Passed</th><th>Removed</th></tr></thead>
|
|
832
|
+
<tbody>
|
|
833
|
+
${selectedRun.filters.map(
|
|
834
|
+
(f) => `
|
|
835
|
+
<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>
|
|
836
|
+
`
|
|
837
|
+
).join("")}
|
|
838
|
+
</tbody>
|
|
839
|
+
</table>
|
|
840
|
+
|
|
841
|
+
<h3>Cards (${selectedRun.finalCount} selected / ${selectedRun.cards.length} total)</h3>
|
|
842
|
+
<input type="text" class="search-box" placeholder="Search Card ID..." value="${_cardSearchQuery}" oninput="window.skuilder.pipeline._setSearch(this.value)">
|
|
843
|
+
|
|
844
|
+
<table>
|
|
845
|
+
<thead><tr><th>ID</th><th>Generator</th><th>Origin</th><th>Score</th><th>Selected</th></tr></thead>
|
|
846
|
+
<tbody>
|
|
847
|
+
${filteredCards.map(
|
|
848
|
+
(c) => `
|
|
849
|
+
<tr>
|
|
850
|
+
<td><code>${c.cardId}</code></td>
|
|
851
|
+
<td>${c.generator || "unknown"}</td>
|
|
852
|
+
<td>${c.origin}</td>
|
|
853
|
+
<td>${c.finalScore.toFixed(3)}</td>
|
|
854
|
+
<td>${c.selected ? "\u2705" : "\u274C"}</td>
|
|
855
|
+
</tr>
|
|
856
|
+
${c.selected || _cardSearchQuery ? `
|
|
857
|
+
<tr>
|
|
858
|
+
<td colspan="5">
|
|
859
|
+
<div class="provenance">${formatProvenance(c.provenance)}</div>
|
|
860
|
+
</td>
|
|
861
|
+
</tr>
|
|
862
|
+
` : ""}
|
|
863
|
+
`
|
|
864
|
+
).join("")}
|
|
865
|
+
</tbody>
|
|
866
|
+
</table>
|
|
867
|
+
`;
|
|
868
|
+
}
|
|
869
|
+
_uiContainer.innerHTML = `
|
|
870
|
+
<style>${styles}</style>
|
|
871
|
+
<header>
|
|
872
|
+
<strong>Pipeline Debugger</strong>
|
|
873
|
+
<button class="close-btn" onclick="window.skuilder.pipeline.ui()">Close</button>
|
|
874
|
+
</header>
|
|
875
|
+
<div class="container">
|
|
876
|
+
<div class="sidebar">${runListHtml}</div>
|
|
877
|
+
<div class="main-content">${detailsHtml}</div>
|
|
878
|
+
</div>
|
|
879
|
+
`;
|
|
880
|
+
}
|
|
722
881
|
function mountPipelineDebugger() {
|
|
723
882
|
if (typeof window === "undefined") return;
|
|
724
883
|
const win = window;
|
|
725
884
|
win.skuilder = win.skuilder || {};
|
|
726
885
|
win.skuilder.pipeline = pipelineDebugAPI;
|
|
727
886
|
}
|
|
728
|
-
var _activePipeline, MAX_RUNS, runHistory, pipelineDebugAPI;
|
|
887
|
+
var _activePipeline, MAX_RUNS, runHistory, _uiContainer, _selectedRunIndex, _cardSearchQuery, pipelineDebugAPI;
|
|
729
888
|
var init_PipelineDebugger = __esm({
|
|
730
889
|
"src/core/navigators/PipelineDebugger.ts"() {
|
|
731
890
|
"use strict";
|
|
@@ -734,6 +893,9 @@ var init_PipelineDebugger = __esm({
|
|
|
734
893
|
_activePipeline = null;
|
|
735
894
|
MAX_RUNS = 10;
|
|
736
895
|
runHistory = [];
|
|
896
|
+
_uiContainer = null;
|
|
897
|
+
_selectedRunIndex = null;
|
|
898
|
+
_cardSearchQuery = "";
|
|
737
899
|
pipelineDebugAPI = {
|
|
738
900
|
/**
|
|
739
901
|
* Get raw run history for programmatic access.
|
|
@@ -873,16 +1035,20 @@ var init_PipelineDebugger = __esm({
|
|
|
873
1035
|
const mode = reason.match(/mode=([^;]+)/)?.[1] ?? "unknown";
|
|
874
1036
|
const blocked = reason.match(/blocked=([^;]+)/)?.[1] ?? "unknown";
|
|
875
1037
|
const blockedTargets = reason.match(/blockedTargets=([^;]+)/)?.[1] ?? "none";
|
|
1038
|
+
const supportCard = reason.match(/supportCard=([^;]+)/)?.[1] ?? "none";
|
|
876
1039
|
const supportTags = reason.match(/supportTags=([^;]+)/)?.[1] ?? "none";
|
|
877
1040
|
const multiplier = reason.match(/multiplier=([^;]+)/)?.[1] ?? "unknown";
|
|
1041
|
+
const supportSource = mode === "discovered-support" ? "discovered" : mode === "support" ? "authored" : "n/a";
|
|
878
1042
|
return {
|
|
879
1043
|
group: parsedGroup,
|
|
880
1044
|
mode,
|
|
1045
|
+
supportSource,
|
|
881
1046
|
cardId: card.cardId,
|
|
882
1047
|
selected: card.selected ? "yes" : "no",
|
|
883
1048
|
finalScore: card.finalScore.toFixed(3),
|
|
884
1049
|
blocked,
|
|
885
1050
|
blockedTargets,
|
|
1051
|
+
supportCard,
|
|
886
1052
|
supportTags,
|
|
887
1053
|
multiplier
|
|
888
1054
|
};
|
|
@@ -898,6 +1064,8 @@ var init_PipelineDebugger = __esm({
|
|
|
898
1064
|
const selectedRows = rows.filter((r) => r.selected === "yes");
|
|
899
1065
|
const blockedTargetSet = /* @__PURE__ */ new Set();
|
|
900
1066
|
const supportTagSet = /* @__PURE__ */ new Set();
|
|
1067
|
+
const authoredSupportSet = /* @__PURE__ */ new Set();
|
|
1068
|
+
const discoveredSupportSet = /* @__PURE__ */ new Set();
|
|
901
1069
|
for (const row of rows) {
|
|
902
1070
|
if (row.blockedTargets && row.blockedTargets !== "none") {
|
|
903
1071
|
row.blockedTargets.split("|").filter(Boolean).forEach((t) => blockedTargetSet.add(t));
|
|
@@ -905,6 +1073,13 @@ var init_PipelineDebugger = __esm({
|
|
|
905
1073
|
if (row.supportTags && row.supportTags !== "none") {
|
|
906
1074
|
row.supportTags.split("|").filter(Boolean).forEach((t) => supportTagSet.add(t));
|
|
907
1075
|
}
|
|
1076
|
+
if (row.supportCard && row.supportCard !== "none") {
|
|
1077
|
+
if (row.supportSource === "discovered") {
|
|
1078
|
+
discoveredSupportSet.add(row.supportCard);
|
|
1079
|
+
} else if (row.supportSource === "authored") {
|
|
1080
|
+
authoredSupportSet.add(row.supportCard);
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
908
1083
|
}
|
|
909
1084
|
logger.info(`Prescribed cards in run: ${rows.length}`);
|
|
910
1085
|
logger.info(`Selected prescribed cards: ${selectedRows.length}`);
|
|
@@ -914,6 +1089,12 @@ var init_PipelineDebugger = __esm({
|
|
|
914
1089
|
logger.info(
|
|
915
1090
|
`Resolved support tags referenced: ${supportTagSet.size > 0 ? [...supportTagSet].join(", ") : "none"}`
|
|
916
1091
|
);
|
|
1092
|
+
logger.info(
|
|
1093
|
+
`Authored support cards emitted: ${authoredSupportSet.size > 0 ? [...authoredSupportSet].join(", ") : "none"}`
|
|
1094
|
+
);
|
|
1095
|
+
logger.info(
|
|
1096
|
+
`Discovered support cards emitted: ${discoveredSupportSet.size > 0 ? [...discoveredSupportSet].join(", ") : "none"}`
|
|
1097
|
+
);
|
|
917
1098
|
console.groupEnd();
|
|
918
1099
|
},
|
|
919
1100
|
/**
|
|
@@ -1048,6 +1229,39 @@ var init_PipelineDebugger = __esm({
|
|
|
1048
1229
|
Object.fromEntries(entries.map(([tag, data]) => [tag, { score: Math.round(data.score), count: data.count }]))
|
|
1049
1230
|
);
|
|
1050
1231
|
},
|
|
1232
|
+
/**
|
|
1233
|
+
* Toggle the full-screen UI debugger.
|
|
1234
|
+
*/
|
|
1235
|
+
ui() {
|
|
1236
|
+
if (_uiContainer) {
|
|
1237
|
+
document.body.removeChild(_uiContainer);
|
|
1238
|
+
_uiContainer = null;
|
|
1239
|
+
return;
|
|
1240
|
+
}
|
|
1241
|
+
_uiContainer = document.createElement("div");
|
|
1242
|
+
_uiContainer.id = "sk-pipeline-debugger";
|
|
1243
|
+
document.body.appendChild(_uiContainer);
|
|
1244
|
+
if (_selectedRunIndex === null && runHistory.length > 0) {
|
|
1245
|
+
_selectedRunIndex = 0;
|
|
1246
|
+
}
|
|
1247
|
+
renderUI();
|
|
1248
|
+
},
|
|
1249
|
+
/**
|
|
1250
|
+
* Internal UI helpers
|
|
1251
|
+
* @internal
|
|
1252
|
+
*/
|
|
1253
|
+
_selectRun(index) {
|
|
1254
|
+
_selectedRunIndex = index;
|
|
1255
|
+
renderUI();
|
|
1256
|
+
},
|
|
1257
|
+
/**
|
|
1258
|
+
* Internal UI helpers
|
|
1259
|
+
* @internal
|
|
1260
|
+
*/
|
|
1261
|
+
_setSearch(query) {
|
|
1262
|
+
_cardSearchQuery = query;
|
|
1263
|
+
renderUI();
|
|
1264
|
+
},
|
|
1051
1265
|
/**
|
|
1052
1266
|
* Show help.
|
|
1053
1267
|
*/
|
|
@@ -1056,6 +1270,7 @@ var init_PipelineDebugger = __esm({
|
|
|
1056
1270
|
\u{1F527} Pipeline Debug API
|
|
1057
1271
|
|
|
1058
1272
|
Commands:
|
|
1273
|
+
.ui() Toggle full-screen UI debugger
|
|
1059
1274
|
.showLastRun() Show summary of most recent pipeline run
|
|
1060
1275
|
.showRun(id|index) Show summary of a specific run (by index or ID suffix)
|
|
1061
1276
|
.showCard(cardId) Show provenance trail for a specific card
|
|
@@ -1072,6 +1287,7 @@ Commands:
|
|
|
1072
1287
|
.help() Show this help message
|
|
1073
1288
|
|
|
1074
1289
|
Example:
|
|
1290
|
+
window.skuilder.pipeline.ui()
|
|
1075
1291
|
window.skuilder.pipeline.showLastRun()
|
|
1076
1292
|
window.skuilder.pipeline.showRun(1)
|
|
1077
1293
|
await window.skuilder.pipeline.diagnoseCardSpace()
|
|
@@ -1234,7 +1450,7 @@ var init_CompositeGenerator = __esm({
|
|
|
1234
1450
|
for (const [, items] of byCardId) {
|
|
1235
1451
|
const cards2 = items.map((i) => i.card);
|
|
1236
1452
|
const aggregatedScore = this.aggregateScores(items);
|
|
1237
|
-
const finalScore = Math.
|
|
1453
|
+
const finalScore = Math.max(0, aggregatedScore);
|
|
1238
1454
|
const mergedProvenance = cards2.flatMap((c) => c.provenance);
|
|
1239
1455
|
const initialScore = cards2[0].score;
|
|
1240
1456
|
const action = finalScore > initialScore ? "boosted" : finalScore < initialScore ? "penalized" : "passed";
|
|
@@ -1431,10 +1647,26 @@ function matchesTagPattern(tag, pattern) {
|
|
|
1431
1647
|
const re = new RegExp(`^${escaped}$`);
|
|
1432
1648
|
return re.test(tag);
|
|
1433
1649
|
}
|
|
1650
|
+
function extractWordStem(cardId) {
|
|
1651
|
+
for (const prefix of ["c-ml-", "c-ws-", "c-spelling-"]) {
|
|
1652
|
+
if (cardId.startsWith(prefix)) {
|
|
1653
|
+
const rest = cardId.slice(prefix.length);
|
|
1654
|
+
const lastDash = rest.lastIndexOf("-");
|
|
1655
|
+
return lastDash > 0 ? rest.slice(0, lastDash) : rest;
|
|
1656
|
+
}
|
|
1657
|
+
}
|
|
1658
|
+
return cardId;
|
|
1659
|
+
}
|
|
1660
|
+
function shuffleInPlace(arr) {
|
|
1661
|
+
for (let i = arr.length - 1; i > 0; i--) {
|
|
1662
|
+
const j = Math.floor(Math.random() * (i + 1));
|
|
1663
|
+
[arr[i], arr[j]] = [arr[j], arr[i]];
|
|
1664
|
+
}
|
|
1665
|
+
}
|
|
1434
1666
|
function pickTopByScore(cards, limit) {
|
|
1435
1667
|
return [...cards].sort((a, b) => b.score - a.score || a.cardId.localeCompare(b.cardId)).slice(0, limit);
|
|
1436
1668
|
}
|
|
1437
|
-
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,
|
|
1669
|
+
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;
|
|
1438
1670
|
var init_prescribed = __esm({
|
|
1439
1671
|
"src/core/navigators/generators/prescribed.ts"() {
|
|
1440
1672
|
"use strict";
|
|
@@ -1447,11 +1679,10 @@ var init_prescribed = __esm({
|
|
|
1447
1679
|
DEFAULT_MIN_COUNT = 3;
|
|
1448
1680
|
BASE_TARGET_SCORE = 1;
|
|
1449
1681
|
BASE_SUPPORT_SCORE = 0.8;
|
|
1682
|
+
DISCOVERED_SUPPORT_SCORE = 12;
|
|
1450
1683
|
MAX_TARGET_MULTIPLIER = 8;
|
|
1451
1684
|
MAX_SUPPORT_MULTIPLIER = 4;
|
|
1452
|
-
|
|
1453
|
-
LESSON_GATE_PENALTY_TAG_HINT = "concept:";
|
|
1454
|
-
PRESCRIBED_DEBUG_VERSION = "testversion-prescribed-v2";
|
|
1685
|
+
PRESCRIBED_DEBUG_VERSION = "testversion-prescribed-v3";
|
|
1455
1686
|
PrescribedCardsGenerator = class extends ContentNavigator {
|
|
1456
1687
|
name;
|
|
1457
1688
|
config;
|
|
@@ -1487,6 +1718,20 @@ var init_prescribed = __esm({
|
|
|
1487
1718
|
const allSupportIds = dedupe(this.config.groups.flatMap((g) => g.supportCardIds ?? []));
|
|
1488
1719
|
const allRelevantIds = dedupe([...allTargetIds, ...allSupportIds]);
|
|
1489
1720
|
const tagsByCard = allRelevantIds.length > 0 ? await this.course.getAppliedTagsBatch(allRelevantIds) : /* @__PURE__ */ new Map();
|
|
1721
|
+
const courseTagDocs = await this.course.getCourseTagStubs().catch(
|
|
1722
|
+
() => ({
|
|
1723
|
+
rows: [],
|
|
1724
|
+
offset: 0,
|
|
1725
|
+
total_rows: 0
|
|
1726
|
+
})
|
|
1727
|
+
);
|
|
1728
|
+
const cardsByTag = /* @__PURE__ */ new Map();
|
|
1729
|
+
for (const row of courseTagDocs.rows ?? []) {
|
|
1730
|
+
const tagDoc = row.doc;
|
|
1731
|
+
if (tagDoc?.name && Array.isArray(tagDoc.taggedCards)) {
|
|
1732
|
+
cardsByTag.set(tagDoc.name, [...tagDoc.taggedCards]);
|
|
1733
|
+
}
|
|
1734
|
+
}
|
|
1490
1735
|
const nextState = {
|
|
1491
1736
|
updatedAt: isoNow(),
|
|
1492
1737
|
groups: {}
|
|
@@ -1501,11 +1746,31 @@ var init_prescribed = __esm({
|
|
|
1501
1746
|
activeIds,
|
|
1502
1747
|
seenIds,
|
|
1503
1748
|
tagsByCard,
|
|
1749
|
+
cardsByTag,
|
|
1504
1750
|
hierarchyConfigs,
|
|
1505
1751
|
userTagElo,
|
|
1506
1752
|
userGlobalElo
|
|
1507
1753
|
});
|
|
1508
1754
|
groupRuntimes.push(runtime);
|
|
1755
|
+
logger.info(
|
|
1756
|
+
`[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)}`
|
|
1757
|
+
);
|
|
1758
|
+
if (runtime.blockedTargets.length > 0) {
|
|
1759
|
+
logger.info(
|
|
1760
|
+
`[Prescribed] Group '${group.id}' blocked targets: ${runtime.blockedTargets.join(", ")}`
|
|
1761
|
+
);
|
|
1762
|
+
logger.info(
|
|
1763
|
+
`[Prescribed] Group '${group.id}' support tags needed: ${runtime.supportTags.join(", ") || "(none)"}`
|
|
1764
|
+
);
|
|
1765
|
+
logger.info(
|
|
1766
|
+
`[Prescribed] Group '${group.id}' escalation mode: ` + (runtime.supportCandidates.length > 0 ? "direct-support" : runtime.discoveredSupportCandidates.length > 0 ? "inserted-support-candidates" : "boost-only")
|
|
1767
|
+
);
|
|
1768
|
+
if (runtime.discoveredSupportCandidates.length > 0) {
|
|
1769
|
+
logger.info(
|
|
1770
|
+
`[Prescribed] Group '${group.id}' discovered support candidates: ${runtime.discoveredSupportCandidates.join(", ")}`
|
|
1771
|
+
);
|
|
1772
|
+
}
|
|
1773
|
+
}
|
|
1509
1774
|
nextState.groups[group.id] = this.buildNextGroupState(runtime, progress.groups[group.id]);
|
|
1510
1775
|
const directCards = this.buildDirectTargetCards(
|
|
1511
1776
|
runtime,
|
|
@@ -1517,15 +1782,30 @@ var init_prescribed = __esm({
|
|
|
1517
1782
|
courseId,
|
|
1518
1783
|
emittedIds
|
|
1519
1784
|
);
|
|
1520
|
-
|
|
1785
|
+
const discoveredSupportCards = this.buildDiscoveredSupportCards(
|
|
1786
|
+
runtime,
|
|
1787
|
+
courseId,
|
|
1788
|
+
emittedIds
|
|
1789
|
+
);
|
|
1790
|
+
emitted.push(...directCards, ...supportCards, ...discoveredSupportCards);
|
|
1521
1791
|
}
|
|
1522
1792
|
const hintSummary = this.buildSupportHintSummary(groupRuntimes);
|
|
1523
1793
|
const hints = Object.keys(hintSummary.boostTags).length > 0 ? {
|
|
1524
1794
|
boostTags: hintSummary.boostTags,
|
|
1525
1795
|
_label: `prescribed-support (${hintSummary.supportTags.length} tags; blocked=${hintSummary.blockedTargetIds.length}; testversion=${PRESCRIBED_DEBUG_VERSION})`
|
|
1526
1796
|
} : void 0;
|
|
1797
|
+
if (hints) {
|
|
1798
|
+
const tagEntries = Object.entries(hints.boostTags ?? {});
|
|
1799
|
+
logger.info(
|
|
1800
|
+
`[Prescribed] Emitting ${tagEntries.length} boost hint(s): ` + tagEntries.map(([tag, mult]) => `${tag}\xD7${mult.toFixed(1)}`).join(", ")
|
|
1801
|
+
);
|
|
1802
|
+
} else {
|
|
1803
|
+
logger.info("[Prescribed] No hints to emit (no blocked targets or no support tags)");
|
|
1804
|
+
}
|
|
1527
1805
|
if (emitted.length === 0) {
|
|
1528
|
-
logger.
|
|
1806
|
+
logger.info(
|
|
1807
|
+
"[Prescribed] 0 cards emitted (all targets blocked, authored/discovered support candidates exhausted)" + (hints ? " \u2014 boost hints emitted but may not survive filters" : "")
|
|
1808
|
+
);
|
|
1529
1809
|
await this.putStrategyState(nextState).catch((e) => {
|
|
1530
1810
|
logger.debug(`[Prescribed] Failed to persist empty-state update: ${e}`);
|
|
1531
1811
|
});
|
|
@@ -1558,7 +1838,7 @@ var init_prescribed = __esm({
|
|
|
1558
1838
|
logger.debug(`[Prescribed] Failed to persist prescribed progress: ${e}`);
|
|
1559
1839
|
});
|
|
1560
1840
|
logger.info(
|
|
1561
|
-
`[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)`
|
|
1841
|
+
`[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)`
|
|
1562
1842
|
);
|
|
1563
1843
|
return hints ? { cards: finalCards, hints } : { cards: finalCards };
|
|
1564
1844
|
}
|
|
@@ -1588,9 +1868,15 @@ var init_prescribed = __esm({
|
|
|
1588
1868
|
const groupsRaw = Array.isArray(parsed.groups) ? parsed.groups : [];
|
|
1589
1869
|
const groups = groupsRaw.map((raw, i) => ({
|
|
1590
1870
|
id: typeof raw.id === "string" && raw.id.trim().length > 0 ? raw.id : `group-${i + 1}`,
|
|
1591
|
-
targetCardIds: dedupe(
|
|
1592
|
-
|
|
1593
|
-
|
|
1871
|
+
targetCardIds: dedupe(
|
|
1872
|
+
Array.isArray(raw.targetCardIds) ? raw.targetCardIds.filter((v) => typeof v === "string") : []
|
|
1873
|
+
),
|
|
1874
|
+
supportCardIds: dedupe(
|
|
1875
|
+
Array.isArray(raw.supportCardIds) ? raw.supportCardIds.filter((v) => typeof v === "string") : []
|
|
1876
|
+
),
|
|
1877
|
+
supportTagPatterns: dedupe(
|
|
1878
|
+
Array.isArray(raw.supportTagPatterns) ? raw.supportTagPatterns.filter((v) => typeof v === "string") : []
|
|
1879
|
+
),
|
|
1594
1880
|
freshnessWindowSessions: typeof raw.freshnessWindowSessions === "number" ? raw.freshnessWindowSessions : DEFAULT_FRESHNESS_WINDOW,
|
|
1595
1881
|
maxDirectTargetsPerRun: typeof raw.maxDirectTargetsPerRun === "number" ? raw.maxDirectTargetsPerRun : DEFAULT_MAX_DIRECT_PER_RUN,
|
|
1596
1882
|
maxSupportCardsPerRun: typeof raw.maxSupportCardsPerRun === "number" ? raw.maxSupportCardsPerRun : DEFAULT_MAX_SUPPORT_PER_RUN,
|
|
@@ -1607,7 +1893,7 @@ var init_prescribed = __esm({
|
|
|
1607
1893
|
}
|
|
1608
1894
|
async loadHierarchyConfigs() {
|
|
1609
1895
|
try {
|
|
1610
|
-
const strategies = await this.course.
|
|
1896
|
+
const strategies = await this.course.getAllNavigationStrategies();
|
|
1611
1897
|
return strategies.filter((s) => s.implementingClass === "hierarchyDefinition").map((s) => {
|
|
1612
1898
|
try {
|
|
1613
1899
|
const parsed = JSON.parse(s.serializedData);
|
|
@@ -1630,6 +1916,7 @@ var init_prescribed = __esm({
|
|
|
1630
1916
|
activeIds,
|
|
1631
1917
|
seenIds,
|
|
1632
1918
|
tagsByCard,
|
|
1919
|
+
cardsByTag,
|
|
1633
1920
|
hierarchyConfigs,
|
|
1634
1921
|
userTagElo,
|
|
1635
1922
|
userGlobalElo
|
|
@@ -1693,6 +1980,22 @@ var init_prescribed = __esm({
|
|
|
1693
1980
|
[...supportTags]
|
|
1694
1981
|
)
|
|
1695
1982
|
]).filter((id) => !activeIds.has(id) && !seenIds.has(id));
|
|
1983
|
+
const discoveredSupportCandidates = blockedTargets.length > 0 && supportTags.size > 0 && supportCandidates.length === 0 ? this.findDiscoveredSupportCards({
|
|
1984
|
+
supportTags: [...supportTags],
|
|
1985
|
+
cardsByTag,
|
|
1986
|
+
activeIds,
|
|
1987
|
+
seenIds,
|
|
1988
|
+
excludedIds: /* @__PURE__ */ new Set([
|
|
1989
|
+
...group.targetCardIds,
|
|
1990
|
+
...group.supportCardIds ?? []
|
|
1991
|
+
]),
|
|
1992
|
+
limit: group.maxSupportCardsPerRun ?? DEFAULT_MAX_SUPPORT_PER_RUN
|
|
1993
|
+
}) : [];
|
|
1994
|
+
if (blockedTargets.length > 0 && supportTags.size > 0 && discoveredSupportCandidates.length === 0) {
|
|
1995
|
+
logger.info(
|
|
1996
|
+
`[Prescribed] Group '${group.id}' discovered 0 broader support candidates (blocked=${blockedTargets.length}; authoredSupport=${supportCandidates.length})`
|
|
1997
|
+
);
|
|
1998
|
+
}
|
|
1696
1999
|
const sessionsSinceSurfaced = priorState?.sessionsSinceSurfaced ?? 0;
|
|
1697
2000
|
const freshnessWindow = group.freshnessWindowSessions ?? DEFAULT_FRESHNESS_WINDOW;
|
|
1698
2001
|
const staleSessions = Math.max(0, sessionsSinceSurfaced - freshnessWindow);
|
|
@@ -1706,6 +2009,7 @@ var init_prescribed = __esm({
|
|
|
1706
2009
|
surfaceableTargets,
|
|
1707
2010
|
targetTags,
|
|
1708
2011
|
supportCandidates,
|
|
2012
|
+
discoveredSupportCandidates,
|
|
1709
2013
|
supportTags: [...supportTags],
|
|
1710
2014
|
pressureMultiplier,
|
|
1711
2015
|
supportMultiplier,
|
|
@@ -1776,6 +2080,33 @@ var init_prescribed = __esm({
|
|
|
1776
2080
|
}
|
|
1777
2081
|
return cards;
|
|
1778
2082
|
}
|
|
2083
|
+
buildDiscoveredSupportCards(runtime, courseId, emittedIds) {
|
|
2084
|
+
if (runtime.blockedTargets.length === 0 || runtime.discoveredSupportCandidates.length === 0) {
|
|
2085
|
+
return [];
|
|
2086
|
+
}
|
|
2087
|
+
const maxSupport = runtime.group.maxSupportCardsPerRun ?? DEFAULT_MAX_SUPPORT_PER_RUN;
|
|
2088
|
+
const supportIds = runtime.discoveredSupportCandidates.filter((id) => !emittedIds.has(id)).slice(0, maxSupport);
|
|
2089
|
+
const cards = [];
|
|
2090
|
+
for (const cardId of supportIds) {
|
|
2091
|
+
emittedIds.add(cardId);
|
|
2092
|
+
cards.push({
|
|
2093
|
+
cardId,
|
|
2094
|
+
courseId,
|
|
2095
|
+
score: DISCOVERED_SUPPORT_SCORE * runtime.supportMultiplier,
|
|
2096
|
+
provenance: [
|
|
2097
|
+
{
|
|
2098
|
+
strategy: "prescribed",
|
|
2099
|
+
strategyName: this.strategyName || this.name,
|
|
2100
|
+
strategyId: this.strategyId || "NAVIGATION_STRATEGY-prescribed",
|
|
2101
|
+
action: "generated",
|
|
2102
|
+
score: DISCOVERED_SUPPORT_SCORE * runtime.supportMultiplier,
|
|
2103
|
+
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}`
|
|
2104
|
+
}
|
|
2105
|
+
]
|
|
2106
|
+
});
|
|
2107
|
+
}
|
|
2108
|
+
return cards;
|
|
2109
|
+
}
|
|
1779
2110
|
findSupportCardsByTags(group, tagsByCard, supportTags) {
|
|
1780
2111
|
if (supportTags.length === 0) {
|
|
1781
2112
|
return [];
|
|
@@ -1798,6 +2129,40 @@ var init_prescribed = __esm({
|
|
|
1798
2129
|
}
|
|
1799
2130
|
return [...candidates];
|
|
1800
2131
|
}
|
|
2132
|
+
findDiscoveredSupportCards(args) {
|
|
2133
|
+
const { supportTags, cardsByTag, activeIds, seenIds, excludedIds, limit } = args;
|
|
2134
|
+
const byCardId = /* @__PURE__ */ new Map();
|
|
2135
|
+
for (const supportTag of supportTags) {
|
|
2136
|
+
const taggedCards = cardsByTag.get(supportTag) ?? [];
|
|
2137
|
+
for (const cardId of taggedCards) {
|
|
2138
|
+
if (activeIds.has(cardId) || seenIds.has(cardId) || excludedIds.has(cardId)) {
|
|
2139
|
+
continue;
|
|
2140
|
+
}
|
|
2141
|
+
const existing = byCardId.get(cardId);
|
|
2142
|
+
if (existing) {
|
|
2143
|
+
existing.matches += 1;
|
|
2144
|
+
} else {
|
|
2145
|
+
byCardId.set(cardId, { cardId, matches: 1 });
|
|
2146
|
+
}
|
|
2147
|
+
}
|
|
2148
|
+
}
|
|
2149
|
+
const candidates = [...byCardId.values()].sort((a, b) => b.matches - a.matches || a.cardId.localeCompare(b.cardId));
|
|
2150
|
+
const usedStems = /* @__PURE__ */ new Set();
|
|
2151
|
+
const diverse = [];
|
|
2152
|
+
const deferred = [];
|
|
2153
|
+
for (const entry of candidates) {
|
|
2154
|
+
const stem = extractWordStem(entry.cardId);
|
|
2155
|
+
if (!usedStems.has(stem)) {
|
|
2156
|
+
usedStems.add(stem);
|
|
2157
|
+
diverse.push(entry);
|
|
2158
|
+
} else {
|
|
2159
|
+
deferred.push(entry);
|
|
2160
|
+
}
|
|
2161
|
+
}
|
|
2162
|
+
shuffleInPlace(diverse);
|
|
2163
|
+
shuffleInPlace(deferred);
|
|
2164
|
+
return [...diverse, ...deferred].slice(0, limit).map((entry) => entry.cardId);
|
|
2165
|
+
}
|
|
1801
2166
|
resolveBlockedSupportTags(targetTags, hierarchyConfigs, userTagElo, userGlobalElo, hierarchyWalkEnabled, maxDepth) {
|
|
1802
2167
|
const supportTags = /* @__PURE__ */ new Set();
|
|
1803
2168
|
let blocked = false;
|
|
@@ -1843,7 +2208,6 @@ var init_prescribed = __esm({
|
|
|
1843
2208
|
}
|
|
1844
2209
|
collectSupportTagsRecursive(tag, hierarchyConfigs, userTagElo, userGlobalElo, depth, visited, out) {
|
|
1845
2210
|
if (depth < 0 || visited.has(tag)) return;
|
|
1846
|
-
if (this.isHardGatedTag(tag)) return;
|
|
1847
2211
|
visited.add(tag);
|
|
1848
2212
|
let walkedFurther = false;
|
|
1849
2213
|
for (const hierarchy of hierarchyConfigs) {
|
|
@@ -1871,9 +2235,6 @@ var init_prescribed = __esm({
|
|
|
1871
2235
|
out.add(tag);
|
|
1872
2236
|
}
|
|
1873
2237
|
}
|
|
1874
|
-
isHardGatedTag(tag) {
|
|
1875
|
-
return LOCKED_TAG_PREFIXES.some((prefix) => tag.startsWith(prefix)) && tag.startsWith(LESSON_GATE_PENALTY_TAG_HINT);
|
|
1876
|
-
}
|
|
1877
2238
|
isPrerequisiteMet(prereq, userTagElo, userGlobalElo) {
|
|
1878
2239
|
if (!userTagElo) return false;
|
|
1879
2240
|
const minCount = prereq.masteryThreshold?.minCount ?? DEFAULT_MIN_COUNT;
|
|
@@ -3419,6 +3780,32 @@ var init_Pipeline = __esm({
|
|
|
3419
3780
|
cards = await this.hydrateTags(cards);
|
|
3420
3781
|
const tHydrate = performance.now();
|
|
3421
3782
|
const allCardsBeforeFiltering = [...cards];
|
|
3783
|
+
const pendingHints = this._ephemeralHints;
|
|
3784
|
+
if (pendingHints?.requireCards?.length) {
|
|
3785
|
+
const poolIds = new Set(allCardsBeforeFiltering.map((c) => c.cardId));
|
|
3786
|
+
const missingIds = pendingHints.requireCards.filter(
|
|
3787
|
+
(p) => !p.includes("*") && !poolIds.has(p)
|
|
3788
|
+
);
|
|
3789
|
+
if (missingIds.length > 0) {
|
|
3790
|
+
const fetchedTags = await this.course.getAppliedTagsBatch(missingIds);
|
|
3791
|
+
const courseId = this.course.getCourseID();
|
|
3792
|
+
for (const cardId of missingIds) {
|
|
3793
|
+
allCardsBeforeFiltering.push({
|
|
3794
|
+
cardId,
|
|
3795
|
+
courseId,
|
|
3796
|
+
score: 1,
|
|
3797
|
+
tags: fetchedTags.get(cardId) ?? [],
|
|
3798
|
+
provenance: []
|
|
3799
|
+
});
|
|
3800
|
+
}
|
|
3801
|
+
logger.info(
|
|
3802
|
+
`[Pipeline] Pre-fetched ${missingIds.length} required card(s) into pool: ${missingIds.join(", ")}`
|
|
3803
|
+
);
|
|
3804
|
+
}
|
|
3805
|
+
}
|
|
3806
|
+
const prescribedIds = new Set(
|
|
3807
|
+
cards.filter((c) => c.provenance.some((p) => p.strategy === "prescribed")).map((c) => c.cardId)
|
|
3808
|
+
);
|
|
3422
3809
|
const filterImpacts = [];
|
|
3423
3810
|
for (const filter of this.filters) {
|
|
3424
3811
|
const beforeCount = cards.length;
|
|
@@ -3433,6 +3820,17 @@ var init_Pipeline = __esm({
|
|
|
3433
3820
|
else passed++;
|
|
3434
3821
|
}
|
|
3435
3822
|
filterImpacts.push({ name: filter.name, boosted, penalized, passed, removed });
|
|
3823
|
+
if (prescribedIds.size > 0) {
|
|
3824
|
+
const survivingIds = new Set(cards.map((c) => c.cardId));
|
|
3825
|
+
const killedPrescribed = [...prescribedIds].filter((id) => !survivingIds.has(id));
|
|
3826
|
+
const zeroedPrescribed = cards.filter((c) => prescribedIds.has(c.cardId) && c.score === 0).map((c) => c.cardId);
|
|
3827
|
+
if (killedPrescribed.length > 0 || zeroedPrescribed.length > 0) {
|
|
3828
|
+
logger.info(
|
|
3829
|
+
`[Pipeline] Filter '${filter.name}' impact on prescribed cards: ` + (killedPrescribed.length > 0 ? `removed=[${killedPrescribed.join(", ")}] ` : "") + (zeroedPrescribed.length > 0 ? `zeroed=[${zeroedPrescribed.join(", ")}]` : "")
|
|
3830
|
+
);
|
|
3831
|
+
killedPrescribed.forEach((id) => prescribedIds.delete(id));
|
|
3832
|
+
}
|
|
3833
|
+
}
|
|
3436
3834
|
logger.debug(`[Pipeline] Filter '${filter.name}': ${beforeScores.size} \u2192 ${cards.length} cards (\u2191${boosted} \u2193${penalized} =${passed})`);
|
|
3437
3835
|
}
|
|
3438
3836
|
cards = cards.filter((c) => c.score > 0);
|
|
@@ -3469,7 +3867,8 @@ var init_Pipeline = __esm({
|
|
|
3469
3867
|
filterImpacts,
|
|
3470
3868
|
cards,
|
|
3471
3869
|
result,
|
|
3472
|
-
context.userElo
|
|
3870
|
+
context.userElo,
|
|
3871
|
+
hints ?? void 0
|
|
3473
3872
|
);
|
|
3474
3873
|
captureRun(report);
|
|
3475
3874
|
} catch (e) {
|
|
@@ -3579,13 +3978,27 @@ var init_Pipeline = __esm({
|
|
|
3579
3978
|
}
|
|
3580
3979
|
}
|
|
3581
3980
|
const cardIds = new Set(cards.map((c) => c.cardId));
|
|
3981
|
+
const cardMap = new Map(cards.map((c) => [c.cardId, c]));
|
|
3582
3982
|
const hintLabel = hints._label ? `Replan Hint (${hints._label})` : "Replan Hint";
|
|
3583
|
-
const
|
|
3584
|
-
|
|
3585
|
-
|
|
3983
|
+
const applyRequirement = (card, reason) => {
|
|
3984
|
+
const mandatoryScore = Number.POSITIVE_INFINITY;
|
|
3985
|
+
const existing = cardMap.get(card.cardId);
|
|
3986
|
+
if (existing) {
|
|
3987
|
+
if (existing.score < mandatoryScore) {
|
|
3988
|
+
existing.score = mandatoryScore;
|
|
3989
|
+
existing.provenance.push({
|
|
3990
|
+
strategy: "ephemeralHint",
|
|
3991
|
+
strategyId: "ephemeral-hint",
|
|
3992
|
+
strategyName: hintLabel,
|
|
3993
|
+
action: "boosted",
|
|
3994
|
+
score: mandatoryScore,
|
|
3995
|
+
reason: `${reason} (upgrade to mandatory score)`
|
|
3996
|
+
});
|
|
3997
|
+
}
|
|
3998
|
+
} else {
|
|
3586
3999
|
cards.push({
|
|
3587
4000
|
...card,
|
|
3588
|
-
score:
|
|
4001
|
+
score: mandatoryScore,
|
|
3589
4002
|
provenance: [
|
|
3590
4003
|
...card.provenance,
|
|
3591
4004
|
{
|
|
@@ -3593,25 +4006,41 @@ var init_Pipeline = __esm({
|
|
|
3593
4006
|
strategyId: "ephemeral-hint",
|
|
3594
4007
|
strategyName: hintLabel,
|
|
3595
4008
|
action: "boosted",
|
|
3596
|
-
score:
|
|
4009
|
+
score: mandatoryScore,
|
|
3597
4010
|
reason
|
|
3598
4011
|
}
|
|
3599
4012
|
]
|
|
3600
4013
|
});
|
|
3601
4014
|
cardIds.add(card.cardId);
|
|
4015
|
+
cardMap.set(card.cardId, cards[cards.length - 1]);
|
|
3602
4016
|
}
|
|
3603
4017
|
};
|
|
3604
4018
|
if (hints.requireCards?.length) {
|
|
3605
4019
|
for (const pattern of hints.requireCards) {
|
|
4020
|
+
for (const cardId of cardIds) {
|
|
4021
|
+
if (globMatch(cardId, pattern)) {
|
|
4022
|
+
applyRequirement(cardMap.get(cardId), `requireCard ${pattern}`);
|
|
4023
|
+
}
|
|
4024
|
+
}
|
|
3606
4025
|
for (const card of allCards) {
|
|
3607
|
-
if (globMatch(card.cardId, pattern))
|
|
4026
|
+
if (globMatch(card.cardId, pattern)) {
|
|
4027
|
+
applyRequirement(card, `requireCard ${pattern}`);
|
|
4028
|
+
}
|
|
3608
4029
|
}
|
|
3609
4030
|
}
|
|
3610
4031
|
}
|
|
3611
4032
|
if (hints.requireTags?.length) {
|
|
3612
4033
|
for (const pattern of hints.requireTags) {
|
|
4034
|
+
for (const cardId of cardIds) {
|
|
4035
|
+
const card = cardMap.get(cardId);
|
|
4036
|
+
if (cardMatchesTagPattern(card, pattern)) {
|
|
4037
|
+
applyRequirement(card, `requireTag ${pattern}`);
|
|
4038
|
+
}
|
|
4039
|
+
}
|
|
3613
4040
|
for (const card of allCards) {
|
|
3614
|
-
if (cardMatchesTagPattern(card, pattern))
|
|
4041
|
+
if (cardMatchesTagPattern(card, pattern)) {
|
|
4042
|
+
applyRequirement(card, `requireTag ${pattern}`);
|
|
4043
|
+
}
|
|
3615
4044
|
}
|
|
3616
4045
|
}
|
|
3617
4046
|
}
|