@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
package/dist/impl/couch/index.js
CHANGED
|
@@ -659,8 +659,9 @@ function getOrigin(card) {
|
|
|
659
659
|
const firstEntry = card.provenance[0];
|
|
660
660
|
if (!firstEntry) return "unknown";
|
|
661
661
|
const reason = firstEntry.reason?.toLowerCase() || "";
|
|
662
|
-
|
|
663
|
-
if (reason.includes("
|
|
662
|
+
const strategy = firstEntry.strategy?.toLowerCase() || "";
|
|
663
|
+
if (reason.includes("new card") || strategy.includes("elo")) return "new";
|
|
664
|
+
if (reason.includes("review") || strategy.includes("srs")) return "review";
|
|
664
665
|
return "unknown";
|
|
665
666
|
}
|
|
666
667
|
function captureRun(report) {
|
|
@@ -680,12 +681,13 @@ function parseCardElo(provenance) {
|
|
|
680
681
|
const match = eloEntry.reason.match(/card:\s*(\d+)/);
|
|
681
682
|
return match ? parseInt(match[1], 10) : void 0;
|
|
682
683
|
}
|
|
683
|
-
function buildRunReport(courseId, courseName, generatorName, generators, generatedCount, filters, allCards, selectedCards, userElo) {
|
|
684
|
+
function buildRunReport(courseId, courseName, generatorName, generators, generatedCount, filters, allCards, selectedCards, userElo, hints) {
|
|
684
685
|
const selectedIds = new Set(selectedCards.map((c) => c.cardId));
|
|
685
686
|
const cards = allCards.map((card) => ({
|
|
686
687
|
cardId: card.cardId,
|
|
687
688
|
courseId: card.courseId,
|
|
688
689
|
origin: getOrigin(card),
|
|
690
|
+
generator: card.provenance[0]?.strategyName || card.provenance[0]?.strategy,
|
|
689
691
|
finalScore: card.score,
|
|
690
692
|
cardElo: parseCardElo(card.provenance),
|
|
691
693
|
provenance: card.provenance,
|
|
@@ -702,6 +704,7 @@ function buildRunReport(courseId, courseName, generatorName, generators, generat
|
|
|
702
704
|
generators,
|
|
703
705
|
generatedCount,
|
|
704
706
|
filters,
|
|
707
|
+
hints,
|
|
705
708
|
finalCount: selectedCards.length,
|
|
706
709
|
reviewsSelected,
|
|
707
710
|
newSelected,
|
|
@@ -741,13 +744,169 @@ function printRunSummary(run) {
|
|
|
741
744
|
);
|
|
742
745
|
console.groupEnd();
|
|
743
746
|
}
|
|
747
|
+
function renderUI() {
|
|
748
|
+
if (!_uiContainer) return;
|
|
749
|
+
const runs = runHistory;
|
|
750
|
+
const selectedRun = _selectedRunIndex !== null ? runs[_selectedRunIndex] : null;
|
|
751
|
+
const styles = `
|
|
752
|
+
#sk-pipeline-debugger {
|
|
753
|
+
position: fixed;
|
|
754
|
+
top: 0;
|
|
755
|
+
left: 0;
|
|
756
|
+
width: 100vw;
|
|
757
|
+
height: 100vh;
|
|
758
|
+
background: #f8f9fa;
|
|
759
|
+
color: #212529;
|
|
760
|
+
z-index: 999999;
|
|
761
|
+
display: flex;
|
|
762
|
+
flex-direction: column;
|
|
763
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
|
764
|
+
font-size: 14px;
|
|
765
|
+
}
|
|
766
|
+
#sk-pipeline-debugger header {
|
|
767
|
+
padding: 1rem;
|
|
768
|
+
background: #343a40;
|
|
769
|
+
color: white;
|
|
770
|
+
display: flex;
|
|
771
|
+
justify-content: space-between;
|
|
772
|
+
align-items: center;
|
|
773
|
+
}
|
|
774
|
+
#sk-pipeline-debugger .container {
|
|
775
|
+
display: flex;
|
|
776
|
+
flex: 1;
|
|
777
|
+
overflow: hidden;
|
|
778
|
+
}
|
|
779
|
+
#sk-pipeline-debugger .sidebar {
|
|
780
|
+
width: 300px;
|
|
781
|
+
border-right: 1px solid #dee2e6;
|
|
782
|
+
overflow-y: auto;
|
|
783
|
+
background: white;
|
|
784
|
+
}
|
|
785
|
+
#sk-pipeline-debugger .main-content {
|
|
786
|
+
flex: 1;
|
|
787
|
+
overflow-y: auto;
|
|
788
|
+
padding: 1.5rem;
|
|
789
|
+
}
|
|
790
|
+
#sk-pipeline-debugger .run-item {
|
|
791
|
+
padding: 0.75rem 1rem;
|
|
792
|
+
border-bottom: 1px solid #eee;
|
|
793
|
+
cursor: pointer;
|
|
794
|
+
}
|
|
795
|
+
#sk-pipeline-debugger .run-item:hover { background: #f1f3f5; }
|
|
796
|
+
#sk-pipeline-debugger .run-item.active { background: #e9ecef; border-left: 4px solid #007bff; }
|
|
797
|
+
#sk-pipeline-debugger h2, #sk-pipeline-debugger h3 { margin-top: 0; }
|
|
798
|
+
#sk-pipeline-debugger table { width: 100%; border-collapse: collapse; margin-bottom: 1rem; background: white; }
|
|
799
|
+
#sk-pipeline-debugger th, #sk-pipeline-debugger td { border: 1px solid #dee2e6; padding: 0.5rem; text-align: left; }
|
|
800
|
+
#sk-pipeline-debugger th { background: #f1f3f5; }
|
|
801
|
+
#sk-pipeline-debugger code { background: #f1f3f5; padding: 0.1rem 0.3rem; border-radius: 3px; font-family: monospace; }
|
|
802
|
+
#sk-pipeline-debugger .close-btn { background: #dc3545; color: white; border: none; padding: 0.5rem 1rem; border-radius: 4px; cursor: pointer; }
|
|
803
|
+
#sk-pipeline-debugger .search-box { margin-bottom: 1rem; width: 100%; padding: 0.5rem; border: 1px solid #ced4da; border-radius: 4px; }
|
|
804
|
+
#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; }
|
|
805
|
+
`;
|
|
806
|
+
const runListHtml = runs.length === 0 ? '<div style="padding: 1rem;">No runs captured yet.</div>' : runs.map(
|
|
807
|
+
(r, i) => `
|
|
808
|
+
<div class="run-item ${i === _selectedRunIndex ? "active" : ""}" onclick="window.skuilder.pipeline._selectRun(${i})">
|
|
809
|
+
<strong>${r.timestamp.toLocaleTimeString()}</strong><br/>
|
|
810
|
+
<small>${r.courseName || r.courseId.slice(0, 8)}</small><br/>
|
|
811
|
+
<small>${r.finalCount} cards selected</small>
|
|
812
|
+
</div>
|
|
813
|
+
`
|
|
814
|
+
).join("");
|
|
815
|
+
let detailsHtml = '<div style="color: #6c757d; text-align: center; margin-top: 5rem;">Select a run to see details</div>';
|
|
816
|
+
if (selectedRun) {
|
|
817
|
+
const filteredCards = selectedRun.cards.filter(
|
|
818
|
+
(c) => !_cardSearchQuery || c.cardId.toLowerCase().includes(_cardSearchQuery.toLowerCase())
|
|
819
|
+
);
|
|
820
|
+
detailsHtml = `
|
|
821
|
+
<h2>Run: ${selectedRun.runId}</h2>
|
|
822
|
+
<p>
|
|
823
|
+
<strong>Time:</strong> ${selectedRun.timestamp.toLocaleString()} |
|
|
824
|
+
<strong>Course:</strong> ${selectedRun.courseName || selectedRun.courseId} |
|
|
825
|
+
<strong>User ELO:</strong> ${selectedRun.userElo ?? "unknown"}
|
|
826
|
+
</p>
|
|
827
|
+
|
|
828
|
+
<h3>Pipeline Config</h3>
|
|
829
|
+
<table>
|
|
830
|
+
<tr><th>Generator</th><td>${selectedRun.generatorName} (${selectedRun.generatedCount} candidates)</td></tr>
|
|
831
|
+
${(selectedRun.generators || []).map(
|
|
832
|
+
(g) => `
|
|
833
|
+
<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>
|
|
834
|
+
`
|
|
835
|
+
).join("")}
|
|
836
|
+
</table>
|
|
837
|
+
|
|
838
|
+
${selectedRun.hints ? `
|
|
839
|
+
<h3>Ephemeral Hints</h3>
|
|
840
|
+
<table>
|
|
841
|
+
${selectedRun.hints._label ? `<tr><th>Label</th><td>${selectedRun.hints._label}</td></tr>` : ""}
|
|
842
|
+
${selectedRun.hints.boostTags ? `<tr><th>Boost Tags</th><td><pre style="margin:0">${JSON.stringify(selectedRun.hints.boostTags, null, 2)}</pre></td></tr>` : ""}
|
|
843
|
+
${selectedRun.hints.boostCards ? `<tr><th>Boost Cards</th><td><pre style="margin:0">${JSON.stringify(selectedRun.hints.boostCards, null, 2)}</pre></td></tr>` : ""}
|
|
844
|
+
${selectedRun.hints.requireTags ? `<tr><th>Require Tags</th><td>${selectedRun.hints.requireTags.join(", ")}</td></tr>` : ""}
|
|
845
|
+
${selectedRun.hints.requireCards ? `<tr><th>Require Cards</th><td>${selectedRun.hints.requireCards.join(", ")}</td></tr>` : ""}
|
|
846
|
+
${selectedRun.hints.excludeTags ? `<tr><th>Exclude Tags</th><td>${selectedRun.hints.excludeTags.join(", ")}</td></tr>` : ""}
|
|
847
|
+
${selectedRun.hints.excludeCards ? `<tr><th>Exclude Cards</th><td>${selectedRun.hints.excludeCards.join(", ")}</td></tr>` : ""}
|
|
848
|
+
</table>
|
|
849
|
+
` : ""}
|
|
850
|
+
|
|
851
|
+
<h3>Filter Impact</h3>
|
|
852
|
+
<table>
|
|
853
|
+
<thead><tr><th>Filter</th><th>Boosted</th><th>Penalized</th><th>Passed</th><th>Removed</th></tr></thead>
|
|
854
|
+
<tbody>
|
|
855
|
+
${selectedRun.filters.map(
|
|
856
|
+
(f) => `
|
|
857
|
+
<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>
|
|
858
|
+
`
|
|
859
|
+
).join("")}
|
|
860
|
+
</tbody>
|
|
861
|
+
</table>
|
|
862
|
+
|
|
863
|
+
<h3>Cards (${selectedRun.finalCount} selected / ${selectedRun.cards.length} total)</h3>
|
|
864
|
+
<input type="text" class="search-box" placeholder="Search Card ID..." value="${_cardSearchQuery}" oninput="window.skuilder.pipeline._setSearch(this.value)">
|
|
865
|
+
|
|
866
|
+
<table>
|
|
867
|
+
<thead><tr><th>ID</th><th>Generator</th><th>Origin</th><th>Score</th><th>Selected</th></tr></thead>
|
|
868
|
+
<tbody>
|
|
869
|
+
${filteredCards.map(
|
|
870
|
+
(c) => `
|
|
871
|
+
<tr>
|
|
872
|
+
<td><code>${c.cardId}</code></td>
|
|
873
|
+
<td>${c.generator || "unknown"}</td>
|
|
874
|
+
<td>${c.origin}</td>
|
|
875
|
+
<td>${c.finalScore.toFixed(3)}</td>
|
|
876
|
+
<td>${c.selected ? "\u2705" : "\u274C"}</td>
|
|
877
|
+
</tr>
|
|
878
|
+
${c.selected || _cardSearchQuery ? `
|
|
879
|
+
<tr>
|
|
880
|
+
<td colspan="5">
|
|
881
|
+
<div class="provenance">${formatProvenance(c.provenance)}</div>
|
|
882
|
+
</td>
|
|
883
|
+
</tr>
|
|
884
|
+
` : ""}
|
|
885
|
+
`
|
|
886
|
+
).join("")}
|
|
887
|
+
</tbody>
|
|
888
|
+
</table>
|
|
889
|
+
`;
|
|
890
|
+
}
|
|
891
|
+
_uiContainer.innerHTML = `
|
|
892
|
+
<style>${styles}</style>
|
|
893
|
+
<header>
|
|
894
|
+
<strong>Pipeline Debugger</strong>
|
|
895
|
+
<button class="close-btn" onclick="window.skuilder.pipeline.ui()">Close</button>
|
|
896
|
+
</header>
|
|
897
|
+
<div class="container">
|
|
898
|
+
<div class="sidebar">${runListHtml}</div>
|
|
899
|
+
<div class="main-content">${detailsHtml}</div>
|
|
900
|
+
</div>
|
|
901
|
+
`;
|
|
902
|
+
}
|
|
744
903
|
function mountPipelineDebugger() {
|
|
745
904
|
if (typeof window === "undefined") return;
|
|
746
905
|
const win = window;
|
|
747
906
|
win.skuilder = win.skuilder || {};
|
|
748
907
|
win.skuilder.pipeline = pipelineDebugAPI;
|
|
749
908
|
}
|
|
750
|
-
var _activePipeline, MAX_RUNS, runHistory, pipelineDebugAPI;
|
|
909
|
+
var _activePipeline, MAX_RUNS, runHistory, _uiContainer, _selectedRunIndex, _cardSearchQuery, pipelineDebugAPI;
|
|
751
910
|
var init_PipelineDebugger = __esm({
|
|
752
911
|
"src/core/navigators/PipelineDebugger.ts"() {
|
|
753
912
|
"use strict";
|
|
@@ -756,6 +915,9 @@ var init_PipelineDebugger = __esm({
|
|
|
756
915
|
_activePipeline = null;
|
|
757
916
|
MAX_RUNS = 10;
|
|
758
917
|
runHistory = [];
|
|
918
|
+
_uiContainer = null;
|
|
919
|
+
_selectedRunIndex = null;
|
|
920
|
+
_cardSearchQuery = "";
|
|
759
921
|
pipelineDebugAPI = {
|
|
760
922
|
/**
|
|
761
923
|
* Get raw run history for programmatic access.
|
|
@@ -895,16 +1057,20 @@ var init_PipelineDebugger = __esm({
|
|
|
895
1057
|
const mode = reason.match(/mode=([^;]+)/)?.[1] ?? "unknown";
|
|
896
1058
|
const blocked = reason.match(/blocked=([^;]+)/)?.[1] ?? "unknown";
|
|
897
1059
|
const blockedTargets = reason.match(/blockedTargets=([^;]+)/)?.[1] ?? "none";
|
|
1060
|
+
const supportCard = reason.match(/supportCard=([^;]+)/)?.[1] ?? "none";
|
|
898
1061
|
const supportTags = reason.match(/supportTags=([^;]+)/)?.[1] ?? "none";
|
|
899
1062
|
const multiplier = reason.match(/multiplier=([^;]+)/)?.[1] ?? "unknown";
|
|
1063
|
+
const supportSource = mode === "discovered-support" ? "discovered" : mode === "support" ? "authored" : "n/a";
|
|
900
1064
|
return {
|
|
901
1065
|
group: parsedGroup,
|
|
902
1066
|
mode,
|
|
1067
|
+
supportSource,
|
|
903
1068
|
cardId: card.cardId,
|
|
904
1069
|
selected: card.selected ? "yes" : "no",
|
|
905
1070
|
finalScore: card.finalScore.toFixed(3),
|
|
906
1071
|
blocked,
|
|
907
1072
|
blockedTargets,
|
|
1073
|
+
supportCard,
|
|
908
1074
|
supportTags,
|
|
909
1075
|
multiplier
|
|
910
1076
|
};
|
|
@@ -920,6 +1086,8 @@ var init_PipelineDebugger = __esm({
|
|
|
920
1086
|
const selectedRows = rows.filter((r) => r.selected === "yes");
|
|
921
1087
|
const blockedTargetSet = /* @__PURE__ */ new Set();
|
|
922
1088
|
const supportTagSet = /* @__PURE__ */ new Set();
|
|
1089
|
+
const authoredSupportSet = /* @__PURE__ */ new Set();
|
|
1090
|
+
const discoveredSupportSet = /* @__PURE__ */ new Set();
|
|
923
1091
|
for (const row of rows) {
|
|
924
1092
|
if (row.blockedTargets && row.blockedTargets !== "none") {
|
|
925
1093
|
row.blockedTargets.split("|").filter(Boolean).forEach((t) => blockedTargetSet.add(t));
|
|
@@ -927,6 +1095,13 @@ var init_PipelineDebugger = __esm({
|
|
|
927
1095
|
if (row.supportTags && row.supportTags !== "none") {
|
|
928
1096
|
row.supportTags.split("|").filter(Boolean).forEach((t) => supportTagSet.add(t));
|
|
929
1097
|
}
|
|
1098
|
+
if (row.supportCard && row.supportCard !== "none") {
|
|
1099
|
+
if (row.supportSource === "discovered") {
|
|
1100
|
+
discoveredSupportSet.add(row.supportCard);
|
|
1101
|
+
} else if (row.supportSource === "authored") {
|
|
1102
|
+
authoredSupportSet.add(row.supportCard);
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
930
1105
|
}
|
|
931
1106
|
logger.info(`Prescribed cards in run: ${rows.length}`);
|
|
932
1107
|
logger.info(`Selected prescribed cards: ${selectedRows.length}`);
|
|
@@ -936,6 +1111,12 @@ var init_PipelineDebugger = __esm({
|
|
|
936
1111
|
logger.info(
|
|
937
1112
|
`Resolved support tags referenced: ${supportTagSet.size > 0 ? [...supportTagSet].join(", ") : "none"}`
|
|
938
1113
|
);
|
|
1114
|
+
logger.info(
|
|
1115
|
+
`Authored support cards emitted: ${authoredSupportSet.size > 0 ? [...authoredSupportSet].join(", ") : "none"}`
|
|
1116
|
+
);
|
|
1117
|
+
logger.info(
|
|
1118
|
+
`Discovered support cards emitted: ${discoveredSupportSet.size > 0 ? [...discoveredSupportSet].join(", ") : "none"}`
|
|
1119
|
+
);
|
|
939
1120
|
console.groupEnd();
|
|
940
1121
|
},
|
|
941
1122
|
/**
|
|
@@ -1070,6 +1251,39 @@ var init_PipelineDebugger = __esm({
|
|
|
1070
1251
|
Object.fromEntries(entries.map(([tag, data]) => [tag, { score: Math.round(data.score), count: data.count }]))
|
|
1071
1252
|
);
|
|
1072
1253
|
},
|
|
1254
|
+
/**
|
|
1255
|
+
* Toggle the full-screen UI debugger.
|
|
1256
|
+
*/
|
|
1257
|
+
ui() {
|
|
1258
|
+
if (_uiContainer) {
|
|
1259
|
+
document.body.removeChild(_uiContainer);
|
|
1260
|
+
_uiContainer = null;
|
|
1261
|
+
return;
|
|
1262
|
+
}
|
|
1263
|
+
_uiContainer = document.createElement("div");
|
|
1264
|
+
_uiContainer.id = "sk-pipeline-debugger";
|
|
1265
|
+
document.body.appendChild(_uiContainer);
|
|
1266
|
+
if (_selectedRunIndex === null && runHistory.length > 0) {
|
|
1267
|
+
_selectedRunIndex = 0;
|
|
1268
|
+
}
|
|
1269
|
+
renderUI();
|
|
1270
|
+
},
|
|
1271
|
+
/**
|
|
1272
|
+
* Internal UI helpers
|
|
1273
|
+
* @internal
|
|
1274
|
+
*/
|
|
1275
|
+
_selectRun(index) {
|
|
1276
|
+
_selectedRunIndex = index;
|
|
1277
|
+
renderUI();
|
|
1278
|
+
},
|
|
1279
|
+
/**
|
|
1280
|
+
* Internal UI helpers
|
|
1281
|
+
* @internal
|
|
1282
|
+
*/
|
|
1283
|
+
_setSearch(query) {
|
|
1284
|
+
_cardSearchQuery = query;
|
|
1285
|
+
renderUI();
|
|
1286
|
+
},
|
|
1073
1287
|
/**
|
|
1074
1288
|
* Show help.
|
|
1075
1289
|
*/
|
|
@@ -1078,6 +1292,7 @@ var init_PipelineDebugger = __esm({
|
|
|
1078
1292
|
\u{1F527} Pipeline Debug API
|
|
1079
1293
|
|
|
1080
1294
|
Commands:
|
|
1295
|
+
.ui() Toggle full-screen UI debugger
|
|
1081
1296
|
.showLastRun() Show summary of most recent pipeline run
|
|
1082
1297
|
.showRun(id|index) Show summary of a specific run (by index or ID suffix)
|
|
1083
1298
|
.showCard(cardId) Show provenance trail for a specific card
|
|
@@ -1094,6 +1309,7 @@ Commands:
|
|
|
1094
1309
|
.help() Show this help message
|
|
1095
1310
|
|
|
1096
1311
|
Example:
|
|
1312
|
+
window.skuilder.pipeline.ui()
|
|
1097
1313
|
window.skuilder.pipeline.showLastRun()
|
|
1098
1314
|
window.skuilder.pipeline.showRun(1)
|
|
1099
1315
|
await window.skuilder.pipeline.diagnoseCardSpace()
|
|
@@ -1256,7 +1472,7 @@ var init_CompositeGenerator = __esm({
|
|
|
1256
1472
|
for (const [, items] of byCardId) {
|
|
1257
1473
|
const cards2 = items.map((i) => i.card);
|
|
1258
1474
|
const aggregatedScore = this.aggregateScores(items);
|
|
1259
|
-
const finalScore = Math.
|
|
1475
|
+
const finalScore = Math.max(0, aggregatedScore);
|
|
1260
1476
|
const mergedProvenance = cards2.flatMap((c) => c.provenance);
|
|
1261
1477
|
const initialScore = cards2[0].score;
|
|
1262
1478
|
const action = finalScore > initialScore ? "boosted" : finalScore < initialScore ? "penalized" : "passed";
|
|
@@ -1453,10 +1669,26 @@ function matchesTagPattern(tag, pattern) {
|
|
|
1453
1669
|
const re = new RegExp(`^${escaped}$`);
|
|
1454
1670
|
return re.test(tag);
|
|
1455
1671
|
}
|
|
1672
|
+
function extractWordStem(cardId) {
|
|
1673
|
+
for (const prefix of ["c-ml-", "c-ws-", "c-spelling-"]) {
|
|
1674
|
+
if (cardId.startsWith(prefix)) {
|
|
1675
|
+
const rest = cardId.slice(prefix.length);
|
|
1676
|
+
const lastDash = rest.lastIndexOf("-");
|
|
1677
|
+
return lastDash > 0 ? rest.slice(0, lastDash) : rest;
|
|
1678
|
+
}
|
|
1679
|
+
}
|
|
1680
|
+
return cardId;
|
|
1681
|
+
}
|
|
1682
|
+
function shuffleInPlace(arr) {
|
|
1683
|
+
for (let i = arr.length - 1; i > 0; i--) {
|
|
1684
|
+
const j = Math.floor(Math.random() * (i + 1));
|
|
1685
|
+
[arr[i], arr[j]] = [arr[j], arr[i]];
|
|
1686
|
+
}
|
|
1687
|
+
}
|
|
1456
1688
|
function pickTopByScore(cards, limit) {
|
|
1457
1689
|
return [...cards].sort((a, b) => b.score - a.score || a.cardId.localeCompare(b.cardId)).slice(0, limit);
|
|
1458
1690
|
}
|
|
1459
|
-
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,
|
|
1691
|
+
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;
|
|
1460
1692
|
var init_prescribed = __esm({
|
|
1461
1693
|
"src/core/navigators/generators/prescribed.ts"() {
|
|
1462
1694
|
"use strict";
|
|
@@ -1469,11 +1701,10 @@ var init_prescribed = __esm({
|
|
|
1469
1701
|
DEFAULT_MIN_COUNT = 3;
|
|
1470
1702
|
BASE_TARGET_SCORE = 1;
|
|
1471
1703
|
BASE_SUPPORT_SCORE = 0.8;
|
|
1704
|
+
DISCOVERED_SUPPORT_SCORE = 12;
|
|
1472
1705
|
MAX_TARGET_MULTIPLIER = 8;
|
|
1473
1706
|
MAX_SUPPORT_MULTIPLIER = 4;
|
|
1474
|
-
|
|
1475
|
-
LESSON_GATE_PENALTY_TAG_HINT = "concept:";
|
|
1476
|
-
PRESCRIBED_DEBUG_VERSION = "testversion-prescribed-v2";
|
|
1707
|
+
PRESCRIBED_DEBUG_VERSION = "testversion-prescribed-v3";
|
|
1477
1708
|
PrescribedCardsGenerator = class extends ContentNavigator {
|
|
1478
1709
|
name;
|
|
1479
1710
|
config;
|
|
@@ -1509,6 +1740,20 @@ var init_prescribed = __esm({
|
|
|
1509
1740
|
const allSupportIds = dedupe(this.config.groups.flatMap((g) => g.supportCardIds ?? []));
|
|
1510
1741
|
const allRelevantIds = dedupe([...allTargetIds, ...allSupportIds]);
|
|
1511
1742
|
const tagsByCard = allRelevantIds.length > 0 ? await this.course.getAppliedTagsBatch(allRelevantIds) : /* @__PURE__ */ new Map();
|
|
1743
|
+
const courseTagDocs = await this.course.getCourseTagStubs().catch(
|
|
1744
|
+
() => ({
|
|
1745
|
+
rows: [],
|
|
1746
|
+
offset: 0,
|
|
1747
|
+
total_rows: 0
|
|
1748
|
+
})
|
|
1749
|
+
);
|
|
1750
|
+
const cardsByTag = /* @__PURE__ */ new Map();
|
|
1751
|
+
for (const row of courseTagDocs.rows ?? []) {
|
|
1752
|
+
const tagDoc = row.doc;
|
|
1753
|
+
if (tagDoc?.name && Array.isArray(tagDoc.taggedCards)) {
|
|
1754
|
+
cardsByTag.set(tagDoc.name, [...tagDoc.taggedCards]);
|
|
1755
|
+
}
|
|
1756
|
+
}
|
|
1512
1757
|
const nextState = {
|
|
1513
1758
|
updatedAt: isoNow(),
|
|
1514
1759
|
groups: {}
|
|
@@ -1523,11 +1768,31 @@ var init_prescribed = __esm({
|
|
|
1523
1768
|
activeIds,
|
|
1524
1769
|
seenIds,
|
|
1525
1770
|
tagsByCard,
|
|
1771
|
+
cardsByTag,
|
|
1526
1772
|
hierarchyConfigs,
|
|
1527
1773
|
userTagElo,
|
|
1528
1774
|
userGlobalElo
|
|
1529
1775
|
});
|
|
1530
1776
|
groupRuntimes.push(runtime);
|
|
1777
|
+
logger.info(
|
|
1778
|
+
`[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)}`
|
|
1779
|
+
);
|
|
1780
|
+
if (runtime.blockedTargets.length > 0) {
|
|
1781
|
+
logger.info(
|
|
1782
|
+
`[Prescribed] Group '${group.id}' blocked targets: ${runtime.blockedTargets.join(", ")}`
|
|
1783
|
+
);
|
|
1784
|
+
logger.info(
|
|
1785
|
+
`[Prescribed] Group '${group.id}' support tags needed: ${runtime.supportTags.join(", ") || "(none)"}`
|
|
1786
|
+
);
|
|
1787
|
+
logger.info(
|
|
1788
|
+
`[Prescribed] Group '${group.id}' escalation mode: ` + (runtime.supportCandidates.length > 0 ? "direct-support" : runtime.discoveredSupportCandidates.length > 0 ? "inserted-support-candidates" : "boost-only")
|
|
1789
|
+
);
|
|
1790
|
+
if (runtime.discoveredSupportCandidates.length > 0) {
|
|
1791
|
+
logger.info(
|
|
1792
|
+
`[Prescribed] Group '${group.id}' discovered support candidates: ${runtime.discoveredSupportCandidates.join(", ")}`
|
|
1793
|
+
);
|
|
1794
|
+
}
|
|
1795
|
+
}
|
|
1531
1796
|
nextState.groups[group.id] = this.buildNextGroupState(runtime, progress.groups[group.id]);
|
|
1532
1797
|
const directCards = this.buildDirectTargetCards(
|
|
1533
1798
|
runtime,
|
|
@@ -1539,15 +1804,30 @@ var init_prescribed = __esm({
|
|
|
1539
1804
|
courseId,
|
|
1540
1805
|
emittedIds
|
|
1541
1806
|
);
|
|
1542
|
-
|
|
1807
|
+
const discoveredSupportCards = this.buildDiscoveredSupportCards(
|
|
1808
|
+
runtime,
|
|
1809
|
+
courseId,
|
|
1810
|
+
emittedIds
|
|
1811
|
+
);
|
|
1812
|
+
emitted.push(...directCards, ...supportCards, ...discoveredSupportCards);
|
|
1543
1813
|
}
|
|
1544
1814
|
const hintSummary = this.buildSupportHintSummary(groupRuntimes);
|
|
1545
1815
|
const hints = Object.keys(hintSummary.boostTags).length > 0 ? {
|
|
1546
1816
|
boostTags: hintSummary.boostTags,
|
|
1547
1817
|
_label: `prescribed-support (${hintSummary.supportTags.length} tags; blocked=${hintSummary.blockedTargetIds.length}; testversion=${PRESCRIBED_DEBUG_VERSION})`
|
|
1548
1818
|
} : void 0;
|
|
1819
|
+
if (hints) {
|
|
1820
|
+
const tagEntries = Object.entries(hints.boostTags ?? {});
|
|
1821
|
+
logger.info(
|
|
1822
|
+
`[Prescribed] Emitting ${tagEntries.length} boost hint(s): ` + tagEntries.map(([tag, mult]) => `${tag}\xD7${mult.toFixed(1)}`).join(", ")
|
|
1823
|
+
);
|
|
1824
|
+
} else {
|
|
1825
|
+
logger.info("[Prescribed] No hints to emit (no blocked targets or no support tags)");
|
|
1826
|
+
}
|
|
1549
1827
|
if (emitted.length === 0) {
|
|
1550
|
-
logger.
|
|
1828
|
+
logger.info(
|
|
1829
|
+
"[Prescribed] 0 cards emitted (all targets blocked, authored/discovered support candidates exhausted)" + (hints ? " \u2014 boost hints emitted but may not survive filters" : "")
|
|
1830
|
+
);
|
|
1551
1831
|
await this.putStrategyState(nextState).catch((e) => {
|
|
1552
1832
|
logger.debug(`[Prescribed] Failed to persist empty-state update: ${e}`);
|
|
1553
1833
|
});
|
|
@@ -1580,7 +1860,7 @@ var init_prescribed = __esm({
|
|
|
1580
1860
|
logger.debug(`[Prescribed] Failed to persist prescribed progress: ${e}`);
|
|
1581
1861
|
});
|
|
1582
1862
|
logger.info(
|
|
1583
|
-
`[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)`
|
|
1863
|
+
`[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)`
|
|
1584
1864
|
);
|
|
1585
1865
|
return hints ? { cards: finalCards, hints } : { cards: finalCards };
|
|
1586
1866
|
}
|
|
@@ -1610,9 +1890,15 @@ var init_prescribed = __esm({
|
|
|
1610
1890
|
const groupsRaw = Array.isArray(parsed.groups) ? parsed.groups : [];
|
|
1611
1891
|
const groups = groupsRaw.map((raw, i) => ({
|
|
1612
1892
|
id: typeof raw.id === "string" && raw.id.trim().length > 0 ? raw.id : `group-${i + 1}`,
|
|
1613
|
-
targetCardIds: dedupe(
|
|
1614
|
-
|
|
1615
|
-
|
|
1893
|
+
targetCardIds: dedupe(
|
|
1894
|
+
Array.isArray(raw.targetCardIds) ? raw.targetCardIds.filter((v) => typeof v === "string") : []
|
|
1895
|
+
),
|
|
1896
|
+
supportCardIds: dedupe(
|
|
1897
|
+
Array.isArray(raw.supportCardIds) ? raw.supportCardIds.filter((v) => typeof v === "string") : []
|
|
1898
|
+
),
|
|
1899
|
+
supportTagPatterns: dedupe(
|
|
1900
|
+
Array.isArray(raw.supportTagPatterns) ? raw.supportTagPatterns.filter((v) => typeof v === "string") : []
|
|
1901
|
+
),
|
|
1616
1902
|
freshnessWindowSessions: typeof raw.freshnessWindowSessions === "number" ? raw.freshnessWindowSessions : DEFAULT_FRESHNESS_WINDOW,
|
|
1617
1903
|
maxDirectTargetsPerRun: typeof raw.maxDirectTargetsPerRun === "number" ? raw.maxDirectTargetsPerRun : DEFAULT_MAX_DIRECT_PER_RUN,
|
|
1618
1904
|
maxSupportCardsPerRun: typeof raw.maxSupportCardsPerRun === "number" ? raw.maxSupportCardsPerRun : DEFAULT_MAX_SUPPORT_PER_RUN,
|
|
@@ -1629,7 +1915,7 @@ var init_prescribed = __esm({
|
|
|
1629
1915
|
}
|
|
1630
1916
|
async loadHierarchyConfigs() {
|
|
1631
1917
|
try {
|
|
1632
|
-
const strategies = await this.course.
|
|
1918
|
+
const strategies = await this.course.getAllNavigationStrategies();
|
|
1633
1919
|
return strategies.filter((s) => s.implementingClass === "hierarchyDefinition").map((s) => {
|
|
1634
1920
|
try {
|
|
1635
1921
|
const parsed = JSON.parse(s.serializedData);
|
|
@@ -1652,6 +1938,7 @@ var init_prescribed = __esm({
|
|
|
1652
1938
|
activeIds,
|
|
1653
1939
|
seenIds,
|
|
1654
1940
|
tagsByCard,
|
|
1941
|
+
cardsByTag,
|
|
1655
1942
|
hierarchyConfigs,
|
|
1656
1943
|
userTagElo,
|
|
1657
1944
|
userGlobalElo
|
|
@@ -1715,6 +2002,22 @@ var init_prescribed = __esm({
|
|
|
1715
2002
|
[...supportTags]
|
|
1716
2003
|
)
|
|
1717
2004
|
]).filter((id) => !activeIds.has(id) && !seenIds.has(id));
|
|
2005
|
+
const discoveredSupportCandidates = blockedTargets.length > 0 && supportTags.size > 0 && supportCandidates.length === 0 ? this.findDiscoveredSupportCards({
|
|
2006
|
+
supportTags: [...supportTags],
|
|
2007
|
+
cardsByTag,
|
|
2008
|
+
activeIds,
|
|
2009
|
+
seenIds,
|
|
2010
|
+
excludedIds: /* @__PURE__ */ new Set([
|
|
2011
|
+
...group.targetCardIds,
|
|
2012
|
+
...group.supportCardIds ?? []
|
|
2013
|
+
]),
|
|
2014
|
+
limit: group.maxSupportCardsPerRun ?? DEFAULT_MAX_SUPPORT_PER_RUN
|
|
2015
|
+
}) : [];
|
|
2016
|
+
if (blockedTargets.length > 0 && supportTags.size > 0 && discoveredSupportCandidates.length === 0) {
|
|
2017
|
+
logger.info(
|
|
2018
|
+
`[Prescribed] Group '${group.id}' discovered 0 broader support candidates (blocked=${blockedTargets.length}; authoredSupport=${supportCandidates.length})`
|
|
2019
|
+
);
|
|
2020
|
+
}
|
|
1718
2021
|
const sessionsSinceSurfaced = priorState?.sessionsSinceSurfaced ?? 0;
|
|
1719
2022
|
const freshnessWindow = group.freshnessWindowSessions ?? DEFAULT_FRESHNESS_WINDOW;
|
|
1720
2023
|
const staleSessions = Math.max(0, sessionsSinceSurfaced - freshnessWindow);
|
|
@@ -1728,6 +2031,7 @@ var init_prescribed = __esm({
|
|
|
1728
2031
|
surfaceableTargets,
|
|
1729
2032
|
targetTags,
|
|
1730
2033
|
supportCandidates,
|
|
2034
|
+
discoveredSupportCandidates,
|
|
1731
2035
|
supportTags: [...supportTags],
|
|
1732
2036
|
pressureMultiplier,
|
|
1733
2037
|
supportMultiplier,
|
|
@@ -1798,6 +2102,33 @@ var init_prescribed = __esm({
|
|
|
1798
2102
|
}
|
|
1799
2103
|
return cards;
|
|
1800
2104
|
}
|
|
2105
|
+
buildDiscoveredSupportCards(runtime, courseId, emittedIds) {
|
|
2106
|
+
if (runtime.blockedTargets.length === 0 || runtime.discoveredSupportCandidates.length === 0) {
|
|
2107
|
+
return [];
|
|
2108
|
+
}
|
|
2109
|
+
const maxSupport = runtime.group.maxSupportCardsPerRun ?? DEFAULT_MAX_SUPPORT_PER_RUN;
|
|
2110
|
+
const supportIds = runtime.discoveredSupportCandidates.filter((id) => !emittedIds.has(id)).slice(0, maxSupport);
|
|
2111
|
+
const cards = [];
|
|
2112
|
+
for (const cardId of supportIds) {
|
|
2113
|
+
emittedIds.add(cardId);
|
|
2114
|
+
cards.push({
|
|
2115
|
+
cardId,
|
|
2116
|
+
courseId,
|
|
2117
|
+
score: DISCOVERED_SUPPORT_SCORE * runtime.supportMultiplier,
|
|
2118
|
+
provenance: [
|
|
2119
|
+
{
|
|
2120
|
+
strategy: "prescribed",
|
|
2121
|
+
strategyName: this.strategyName || this.name,
|
|
2122
|
+
strategyId: this.strategyId || "NAVIGATION_STRATEGY-prescribed",
|
|
2123
|
+
action: "generated",
|
|
2124
|
+
score: DISCOVERED_SUPPORT_SCORE * runtime.supportMultiplier,
|
|
2125
|
+
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}`
|
|
2126
|
+
}
|
|
2127
|
+
]
|
|
2128
|
+
});
|
|
2129
|
+
}
|
|
2130
|
+
return cards;
|
|
2131
|
+
}
|
|
1801
2132
|
findSupportCardsByTags(group, tagsByCard, supportTags) {
|
|
1802
2133
|
if (supportTags.length === 0) {
|
|
1803
2134
|
return [];
|
|
@@ -1820,6 +2151,40 @@ var init_prescribed = __esm({
|
|
|
1820
2151
|
}
|
|
1821
2152
|
return [...candidates];
|
|
1822
2153
|
}
|
|
2154
|
+
findDiscoveredSupportCards(args) {
|
|
2155
|
+
const { supportTags, cardsByTag, activeIds, seenIds, excludedIds, limit } = args;
|
|
2156
|
+
const byCardId = /* @__PURE__ */ new Map();
|
|
2157
|
+
for (const supportTag of supportTags) {
|
|
2158
|
+
const taggedCards = cardsByTag.get(supportTag) ?? [];
|
|
2159
|
+
for (const cardId of taggedCards) {
|
|
2160
|
+
if (activeIds.has(cardId) || seenIds.has(cardId) || excludedIds.has(cardId)) {
|
|
2161
|
+
continue;
|
|
2162
|
+
}
|
|
2163
|
+
const existing = byCardId.get(cardId);
|
|
2164
|
+
if (existing) {
|
|
2165
|
+
existing.matches += 1;
|
|
2166
|
+
} else {
|
|
2167
|
+
byCardId.set(cardId, { cardId, matches: 1 });
|
|
2168
|
+
}
|
|
2169
|
+
}
|
|
2170
|
+
}
|
|
2171
|
+
const candidates = [...byCardId.values()].sort((a, b) => b.matches - a.matches || a.cardId.localeCompare(b.cardId));
|
|
2172
|
+
const usedStems = /* @__PURE__ */ new Set();
|
|
2173
|
+
const diverse = [];
|
|
2174
|
+
const deferred = [];
|
|
2175
|
+
for (const entry of candidates) {
|
|
2176
|
+
const stem = extractWordStem(entry.cardId);
|
|
2177
|
+
if (!usedStems.has(stem)) {
|
|
2178
|
+
usedStems.add(stem);
|
|
2179
|
+
diverse.push(entry);
|
|
2180
|
+
} else {
|
|
2181
|
+
deferred.push(entry);
|
|
2182
|
+
}
|
|
2183
|
+
}
|
|
2184
|
+
shuffleInPlace(diverse);
|
|
2185
|
+
shuffleInPlace(deferred);
|
|
2186
|
+
return [...diverse, ...deferred].slice(0, limit).map((entry) => entry.cardId);
|
|
2187
|
+
}
|
|
1823
2188
|
resolveBlockedSupportTags(targetTags, hierarchyConfigs, userTagElo, userGlobalElo, hierarchyWalkEnabled, maxDepth) {
|
|
1824
2189
|
const supportTags = /* @__PURE__ */ new Set();
|
|
1825
2190
|
let blocked = false;
|
|
@@ -1865,7 +2230,6 @@ var init_prescribed = __esm({
|
|
|
1865
2230
|
}
|
|
1866
2231
|
collectSupportTagsRecursive(tag, hierarchyConfigs, userTagElo, userGlobalElo, depth, visited, out) {
|
|
1867
2232
|
if (depth < 0 || visited.has(tag)) return;
|
|
1868
|
-
if (this.isHardGatedTag(tag)) return;
|
|
1869
2233
|
visited.add(tag);
|
|
1870
2234
|
let walkedFurther = false;
|
|
1871
2235
|
for (const hierarchy of hierarchyConfigs) {
|
|
@@ -1893,9 +2257,6 @@ var init_prescribed = __esm({
|
|
|
1893
2257
|
out.add(tag);
|
|
1894
2258
|
}
|
|
1895
2259
|
}
|
|
1896
|
-
isHardGatedTag(tag) {
|
|
1897
|
-
return LOCKED_TAG_PREFIXES.some((prefix) => tag.startsWith(prefix)) && tag.startsWith(LESSON_GATE_PENALTY_TAG_HINT);
|
|
1898
|
-
}
|
|
1899
2260
|
isPrerequisiteMet(prereq, userTagElo, userGlobalElo) {
|
|
1900
2261
|
if (!userTagElo) return false;
|
|
1901
2262
|
const minCount = prereq.masteryThreshold?.minCount ?? DEFAULT_MIN_COUNT;
|
|
@@ -3441,6 +3802,32 @@ var init_Pipeline = __esm({
|
|
|
3441
3802
|
cards = await this.hydrateTags(cards);
|
|
3442
3803
|
const tHydrate = performance.now();
|
|
3443
3804
|
const allCardsBeforeFiltering = [...cards];
|
|
3805
|
+
const pendingHints = this._ephemeralHints;
|
|
3806
|
+
if (pendingHints?.requireCards?.length) {
|
|
3807
|
+
const poolIds = new Set(allCardsBeforeFiltering.map((c) => c.cardId));
|
|
3808
|
+
const missingIds = pendingHints.requireCards.filter(
|
|
3809
|
+
(p) => !p.includes("*") && !poolIds.has(p)
|
|
3810
|
+
);
|
|
3811
|
+
if (missingIds.length > 0) {
|
|
3812
|
+
const fetchedTags = await this.course.getAppliedTagsBatch(missingIds);
|
|
3813
|
+
const courseId = this.course.getCourseID();
|
|
3814
|
+
for (const cardId of missingIds) {
|
|
3815
|
+
allCardsBeforeFiltering.push({
|
|
3816
|
+
cardId,
|
|
3817
|
+
courseId,
|
|
3818
|
+
score: 1,
|
|
3819
|
+
tags: fetchedTags.get(cardId) ?? [],
|
|
3820
|
+
provenance: []
|
|
3821
|
+
});
|
|
3822
|
+
}
|
|
3823
|
+
logger.info(
|
|
3824
|
+
`[Pipeline] Pre-fetched ${missingIds.length} required card(s) into pool: ${missingIds.join(", ")}`
|
|
3825
|
+
);
|
|
3826
|
+
}
|
|
3827
|
+
}
|
|
3828
|
+
const prescribedIds = new Set(
|
|
3829
|
+
cards.filter((c) => c.provenance.some((p) => p.strategy === "prescribed")).map((c) => c.cardId)
|
|
3830
|
+
);
|
|
3444
3831
|
const filterImpacts = [];
|
|
3445
3832
|
for (const filter of this.filters) {
|
|
3446
3833
|
const beforeCount = cards.length;
|
|
@@ -3455,6 +3842,17 @@ var init_Pipeline = __esm({
|
|
|
3455
3842
|
else passed++;
|
|
3456
3843
|
}
|
|
3457
3844
|
filterImpacts.push({ name: filter.name, boosted, penalized, passed, removed });
|
|
3845
|
+
if (prescribedIds.size > 0) {
|
|
3846
|
+
const survivingIds = new Set(cards.map((c) => c.cardId));
|
|
3847
|
+
const killedPrescribed = [...prescribedIds].filter((id) => !survivingIds.has(id));
|
|
3848
|
+
const zeroedPrescribed = cards.filter((c) => prescribedIds.has(c.cardId) && c.score === 0).map((c) => c.cardId);
|
|
3849
|
+
if (killedPrescribed.length > 0 || zeroedPrescribed.length > 0) {
|
|
3850
|
+
logger.info(
|
|
3851
|
+
`[Pipeline] Filter '${filter.name}' impact on prescribed cards: ` + (killedPrescribed.length > 0 ? `removed=[${killedPrescribed.join(", ")}] ` : "") + (zeroedPrescribed.length > 0 ? `zeroed=[${zeroedPrescribed.join(", ")}]` : "")
|
|
3852
|
+
);
|
|
3853
|
+
killedPrescribed.forEach((id) => prescribedIds.delete(id));
|
|
3854
|
+
}
|
|
3855
|
+
}
|
|
3458
3856
|
logger.debug(`[Pipeline] Filter '${filter.name}': ${beforeScores.size} \u2192 ${cards.length} cards (\u2191${boosted} \u2193${penalized} =${passed})`);
|
|
3459
3857
|
}
|
|
3460
3858
|
cards = cards.filter((c) => c.score > 0);
|
|
@@ -3491,7 +3889,8 @@ var init_Pipeline = __esm({
|
|
|
3491
3889
|
filterImpacts,
|
|
3492
3890
|
cards,
|
|
3493
3891
|
result,
|
|
3494
|
-
context.userElo
|
|
3892
|
+
context.userElo,
|
|
3893
|
+
hints ?? void 0
|
|
3495
3894
|
);
|
|
3496
3895
|
captureRun(report);
|
|
3497
3896
|
} catch (e) {
|
|
@@ -3601,13 +4000,27 @@ var init_Pipeline = __esm({
|
|
|
3601
4000
|
}
|
|
3602
4001
|
}
|
|
3603
4002
|
const cardIds = new Set(cards.map((c) => c.cardId));
|
|
4003
|
+
const cardMap = new Map(cards.map((c) => [c.cardId, c]));
|
|
3604
4004
|
const hintLabel = hints._label ? `Replan Hint (${hints._label})` : "Replan Hint";
|
|
3605
|
-
const
|
|
3606
|
-
|
|
3607
|
-
|
|
4005
|
+
const applyRequirement = (card, reason) => {
|
|
4006
|
+
const mandatoryScore = Number.POSITIVE_INFINITY;
|
|
4007
|
+
const existing = cardMap.get(card.cardId);
|
|
4008
|
+
if (existing) {
|
|
4009
|
+
if (existing.score < mandatoryScore) {
|
|
4010
|
+
existing.score = mandatoryScore;
|
|
4011
|
+
existing.provenance.push({
|
|
4012
|
+
strategy: "ephemeralHint",
|
|
4013
|
+
strategyId: "ephemeral-hint",
|
|
4014
|
+
strategyName: hintLabel,
|
|
4015
|
+
action: "boosted",
|
|
4016
|
+
score: mandatoryScore,
|
|
4017
|
+
reason: `${reason} (upgrade to mandatory score)`
|
|
4018
|
+
});
|
|
4019
|
+
}
|
|
4020
|
+
} else {
|
|
3608
4021
|
cards.push({
|
|
3609
4022
|
...card,
|
|
3610
|
-
score:
|
|
4023
|
+
score: mandatoryScore,
|
|
3611
4024
|
provenance: [
|
|
3612
4025
|
...card.provenance,
|
|
3613
4026
|
{
|
|
@@ -3615,25 +4028,41 @@ var init_Pipeline = __esm({
|
|
|
3615
4028
|
strategyId: "ephemeral-hint",
|
|
3616
4029
|
strategyName: hintLabel,
|
|
3617
4030
|
action: "boosted",
|
|
3618
|
-
score:
|
|
4031
|
+
score: mandatoryScore,
|
|
3619
4032
|
reason
|
|
3620
4033
|
}
|
|
3621
4034
|
]
|
|
3622
4035
|
});
|
|
3623
4036
|
cardIds.add(card.cardId);
|
|
4037
|
+
cardMap.set(card.cardId, cards[cards.length - 1]);
|
|
3624
4038
|
}
|
|
3625
4039
|
};
|
|
3626
4040
|
if (hints.requireCards?.length) {
|
|
3627
4041
|
for (const pattern of hints.requireCards) {
|
|
4042
|
+
for (const cardId of cardIds) {
|
|
4043
|
+
if (globMatch(cardId, pattern)) {
|
|
4044
|
+
applyRequirement(cardMap.get(cardId), `requireCard ${pattern}`);
|
|
4045
|
+
}
|
|
4046
|
+
}
|
|
3628
4047
|
for (const card of allCards) {
|
|
3629
|
-
if (globMatch(card.cardId, pattern))
|
|
4048
|
+
if (globMatch(card.cardId, pattern)) {
|
|
4049
|
+
applyRequirement(card, `requireCard ${pattern}`);
|
|
4050
|
+
}
|
|
3630
4051
|
}
|
|
3631
4052
|
}
|
|
3632
4053
|
}
|
|
3633
4054
|
if (hints.requireTags?.length) {
|
|
3634
4055
|
for (const pattern of hints.requireTags) {
|
|
4056
|
+
for (const cardId of cardIds) {
|
|
4057
|
+
const card = cardMap.get(cardId);
|
|
4058
|
+
if (cardMatchesTagPattern(card, pattern)) {
|
|
4059
|
+
applyRequirement(card, `requireTag ${pattern}`);
|
|
4060
|
+
}
|
|
4061
|
+
}
|
|
3635
4062
|
for (const card of allCards) {
|
|
3636
|
-
if (cardMatchesTagPattern(card, pattern))
|
|
4063
|
+
if (cardMatchesTagPattern(card, pattern)) {
|
|
4064
|
+
applyRequirement(card, `requireTag ${pattern}`);
|
|
4065
|
+
}
|
|
3637
4066
|
}
|
|
3638
4067
|
}
|
|
3639
4068
|
}
|