@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/core/index.js
CHANGED
|
@@ -735,8 +735,9 @@ function getOrigin(card) {
|
|
|
735
735
|
const firstEntry = card.provenance[0];
|
|
736
736
|
if (!firstEntry) return "unknown";
|
|
737
737
|
const reason = firstEntry.reason?.toLowerCase() || "";
|
|
738
|
-
|
|
739
|
-
if (reason.includes("
|
|
738
|
+
const strategy = firstEntry.strategy?.toLowerCase() || "";
|
|
739
|
+
if (reason.includes("new card") || strategy.includes("elo")) return "new";
|
|
740
|
+
if (reason.includes("review") || strategy.includes("srs")) return "review";
|
|
740
741
|
return "unknown";
|
|
741
742
|
}
|
|
742
743
|
function captureRun(report) {
|
|
@@ -756,12 +757,13 @@ function parseCardElo(provenance) {
|
|
|
756
757
|
const match = eloEntry.reason.match(/card:\s*(\d+)/);
|
|
757
758
|
return match ? parseInt(match[1], 10) : void 0;
|
|
758
759
|
}
|
|
759
|
-
function buildRunReport(courseId, courseName, generatorName, generators, generatedCount, filters, allCards, selectedCards, userElo) {
|
|
760
|
+
function buildRunReport(courseId, courseName, generatorName, generators, generatedCount, filters, allCards, selectedCards, userElo, hints) {
|
|
760
761
|
const selectedIds = new Set(selectedCards.map((c) => c.cardId));
|
|
761
762
|
const cards = allCards.map((card) => ({
|
|
762
763
|
cardId: card.cardId,
|
|
763
764
|
courseId: card.courseId,
|
|
764
765
|
origin: getOrigin(card),
|
|
766
|
+
generator: card.provenance[0]?.strategyName || card.provenance[0]?.strategy,
|
|
765
767
|
finalScore: card.score,
|
|
766
768
|
cardElo: parseCardElo(card.provenance),
|
|
767
769
|
provenance: card.provenance,
|
|
@@ -778,6 +780,7 @@ function buildRunReport(courseId, courseName, generatorName, generators, generat
|
|
|
778
780
|
generators,
|
|
779
781
|
generatedCount,
|
|
780
782
|
filters,
|
|
783
|
+
hints,
|
|
781
784
|
finalCount: selectedCards.length,
|
|
782
785
|
reviewsSelected,
|
|
783
786
|
newSelected,
|
|
@@ -817,13 +820,169 @@ function printRunSummary(run) {
|
|
|
817
820
|
);
|
|
818
821
|
console.groupEnd();
|
|
819
822
|
}
|
|
823
|
+
function renderUI() {
|
|
824
|
+
if (!_uiContainer) return;
|
|
825
|
+
const runs = runHistory;
|
|
826
|
+
const selectedRun = _selectedRunIndex !== null ? runs[_selectedRunIndex] : null;
|
|
827
|
+
const styles = `
|
|
828
|
+
#sk-pipeline-debugger {
|
|
829
|
+
position: fixed;
|
|
830
|
+
top: 0;
|
|
831
|
+
left: 0;
|
|
832
|
+
width: 100vw;
|
|
833
|
+
height: 100vh;
|
|
834
|
+
background: #f8f9fa;
|
|
835
|
+
color: #212529;
|
|
836
|
+
z-index: 999999;
|
|
837
|
+
display: flex;
|
|
838
|
+
flex-direction: column;
|
|
839
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
|
840
|
+
font-size: 14px;
|
|
841
|
+
}
|
|
842
|
+
#sk-pipeline-debugger header {
|
|
843
|
+
padding: 1rem;
|
|
844
|
+
background: #343a40;
|
|
845
|
+
color: white;
|
|
846
|
+
display: flex;
|
|
847
|
+
justify-content: space-between;
|
|
848
|
+
align-items: center;
|
|
849
|
+
}
|
|
850
|
+
#sk-pipeline-debugger .container {
|
|
851
|
+
display: flex;
|
|
852
|
+
flex: 1;
|
|
853
|
+
overflow: hidden;
|
|
854
|
+
}
|
|
855
|
+
#sk-pipeline-debugger .sidebar {
|
|
856
|
+
width: 300px;
|
|
857
|
+
border-right: 1px solid #dee2e6;
|
|
858
|
+
overflow-y: auto;
|
|
859
|
+
background: white;
|
|
860
|
+
}
|
|
861
|
+
#sk-pipeline-debugger .main-content {
|
|
862
|
+
flex: 1;
|
|
863
|
+
overflow-y: auto;
|
|
864
|
+
padding: 1.5rem;
|
|
865
|
+
}
|
|
866
|
+
#sk-pipeline-debugger .run-item {
|
|
867
|
+
padding: 0.75rem 1rem;
|
|
868
|
+
border-bottom: 1px solid #eee;
|
|
869
|
+
cursor: pointer;
|
|
870
|
+
}
|
|
871
|
+
#sk-pipeline-debugger .run-item:hover { background: #f1f3f5; }
|
|
872
|
+
#sk-pipeline-debugger .run-item.active { background: #e9ecef; border-left: 4px solid #007bff; }
|
|
873
|
+
#sk-pipeline-debugger h2, #sk-pipeline-debugger h3 { margin-top: 0; }
|
|
874
|
+
#sk-pipeline-debugger table { width: 100%; border-collapse: collapse; margin-bottom: 1rem; background: white; }
|
|
875
|
+
#sk-pipeline-debugger th, #sk-pipeline-debugger td { border: 1px solid #dee2e6; padding: 0.5rem; text-align: left; }
|
|
876
|
+
#sk-pipeline-debugger th { background: #f1f3f5; }
|
|
877
|
+
#sk-pipeline-debugger code { background: #f1f3f5; padding: 0.1rem 0.3rem; border-radius: 3px; font-family: monospace; }
|
|
878
|
+
#sk-pipeline-debugger .close-btn { background: #dc3545; color: white; border: none; padding: 0.5rem 1rem; border-radius: 4px; cursor: pointer; }
|
|
879
|
+
#sk-pipeline-debugger .search-box { margin-bottom: 1rem; width: 100%; padding: 0.5rem; border: 1px solid #ced4da; border-radius: 4px; }
|
|
880
|
+
#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; }
|
|
881
|
+
`;
|
|
882
|
+
const runListHtml = runs.length === 0 ? '<div style="padding: 1rem;">No runs captured yet.</div>' : runs.map(
|
|
883
|
+
(r, i) => `
|
|
884
|
+
<div class="run-item ${i === _selectedRunIndex ? "active" : ""}" onclick="window.skuilder.pipeline._selectRun(${i})">
|
|
885
|
+
<strong>${r.timestamp.toLocaleTimeString()}</strong><br/>
|
|
886
|
+
<small>${r.courseName || r.courseId.slice(0, 8)}</small><br/>
|
|
887
|
+
<small>${r.finalCount} cards selected</small>
|
|
888
|
+
</div>
|
|
889
|
+
`
|
|
890
|
+
).join("");
|
|
891
|
+
let detailsHtml = '<div style="color: #6c757d; text-align: center; margin-top: 5rem;">Select a run to see details</div>';
|
|
892
|
+
if (selectedRun) {
|
|
893
|
+
const filteredCards = selectedRun.cards.filter(
|
|
894
|
+
(c) => !_cardSearchQuery || c.cardId.toLowerCase().includes(_cardSearchQuery.toLowerCase())
|
|
895
|
+
);
|
|
896
|
+
detailsHtml = `
|
|
897
|
+
<h2>Run: ${selectedRun.runId}</h2>
|
|
898
|
+
<p>
|
|
899
|
+
<strong>Time:</strong> ${selectedRun.timestamp.toLocaleString()} |
|
|
900
|
+
<strong>Course:</strong> ${selectedRun.courseName || selectedRun.courseId} |
|
|
901
|
+
<strong>User ELO:</strong> ${selectedRun.userElo ?? "unknown"}
|
|
902
|
+
</p>
|
|
903
|
+
|
|
904
|
+
<h3>Pipeline Config</h3>
|
|
905
|
+
<table>
|
|
906
|
+
<tr><th>Generator</th><td>${selectedRun.generatorName} (${selectedRun.generatedCount} candidates)</td></tr>
|
|
907
|
+
${(selectedRun.generators || []).map(
|
|
908
|
+
(g) => `
|
|
909
|
+
<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>
|
|
910
|
+
`
|
|
911
|
+
).join("")}
|
|
912
|
+
</table>
|
|
913
|
+
|
|
914
|
+
${selectedRun.hints ? `
|
|
915
|
+
<h3>Ephemeral Hints</h3>
|
|
916
|
+
<table>
|
|
917
|
+
${selectedRun.hints._label ? `<tr><th>Label</th><td>${selectedRun.hints._label}</td></tr>` : ""}
|
|
918
|
+
${selectedRun.hints.boostTags ? `<tr><th>Boost Tags</th><td><pre style="margin:0">${JSON.stringify(selectedRun.hints.boostTags, null, 2)}</pre></td></tr>` : ""}
|
|
919
|
+
${selectedRun.hints.boostCards ? `<tr><th>Boost Cards</th><td><pre style="margin:0">${JSON.stringify(selectedRun.hints.boostCards, null, 2)}</pre></td></tr>` : ""}
|
|
920
|
+
${selectedRun.hints.requireTags ? `<tr><th>Require Tags</th><td>${selectedRun.hints.requireTags.join(", ")}</td></tr>` : ""}
|
|
921
|
+
${selectedRun.hints.requireCards ? `<tr><th>Require Cards</th><td>${selectedRun.hints.requireCards.join(", ")}</td></tr>` : ""}
|
|
922
|
+
${selectedRun.hints.excludeTags ? `<tr><th>Exclude Tags</th><td>${selectedRun.hints.excludeTags.join(", ")}</td></tr>` : ""}
|
|
923
|
+
${selectedRun.hints.excludeCards ? `<tr><th>Exclude Cards</th><td>${selectedRun.hints.excludeCards.join(", ")}</td></tr>` : ""}
|
|
924
|
+
</table>
|
|
925
|
+
` : ""}
|
|
926
|
+
|
|
927
|
+
<h3>Filter Impact</h3>
|
|
928
|
+
<table>
|
|
929
|
+
<thead><tr><th>Filter</th><th>Boosted</th><th>Penalized</th><th>Passed</th><th>Removed</th></tr></thead>
|
|
930
|
+
<tbody>
|
|
931
|
+
${selectedRun.filters.map(
|
|
932
|
+
(f) => `
|
|
933
|
+
<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>
|
|
934
|
+
`
|
|
935
|
+
).join("")}
|
|
936
|
+
</tbody>
|
|
937
|
+
</table>
|
|
938
|
+
|
|
939
|
+
<h3>Cards (${selectedRun.finalCount} selected / ${selectedRun.cards.length} total)</h3>
|
|
940
|
+
<input type="text" class="search-box" placeholder="Search Card ID..." value="${_cardSearchQuery}" oninput="window.skuilder.pipeline._setSearch(this.value)">
|
|
941
|
+
|
|
942
|
+
<table>
|
|
943
|
+
<thead><tr><th>ID</th><th>Generator</th><th>Origin</th><th>Score</th><th>Selected</th></tr></thead>
|
|
944
|
+
<tbody>
|
|
945
|
+
${filteredCards.map(
|
|
946
|
+
(c) => `
|
|
947
|
+
<tr>
|
|
948
|
+
<td><code>${c.cardId}</code></td>
|
|
949
|
+
<td>${c.generator || "unknown"}</td>
|
|
950
|
+
<td>${c.origin}</td>
|
|
951
|
+
<td>${c.finalScore.toFixed(3)}</td>
|
|
952
|
+
<td>${c.selected ? "\u2705" : "\u274C"}</td>
|
|
953
|
+
</tr>
|
|
954
|
+
${c.selected || _cardSearchQuery ? `
|
|
955
|
+
<tr>
|
|
956
|
+
<td colspan="5">
|
|
957
|
+
<div class="provenance">${formatProvenance(c.provenance)}</div>
|
|
958
|
+
</td>
|
|
959
|
+
</tr>
|
|
960
|
+
` : ""}
|
|
961
|
+
`
|
|
962
|
+
).join("")}
|
|
963
|
+
</tbody>
|
|
964
|
+
</table>
|
|
965
|
+
`;
|
|
966
|
+
}
|
|
967
|
+
_uiContainer.innerHTML = `
|
|
968
|
+
<style>${styles}</style>
|
|
969
|
+
<header>
|
|
970
|
+
<strong>Pipeline Debugger</strong>
|
|
971
|
+
<button class="close-btn" onclick="window.skuilder.pipeline.ui()">Close</button>
|
|
972
|
+
</header>
|
|
973
|
+
<div class="container">
|
|
974
|
+
<div class="sidebar">${runListHtml}</div>
|
|
975
|
+
<div class="main-content">${detailsHtml}</div>
|
|
976
|
+
</div>
|
|
977
|
+
`;
|
|
978
|
+
}
|
|
820
979
|
function mountPipelineDebugger() {
|
|
821
980
|
if (typeof window === "undefined") return;
|
|
822
981
|
const win = window;
|
|
823
982
|
win.skuilder = win.skuilder || {};
|
|
824
983
|
win.skuilder.pipeline = pipelineDebugAPI;
|
|
825
984
|
}
|
|
826
|
-
var _activePipeline, MAX_RUNS, runHistory, pipelineDebugAPI;
|
|
985
|
+
var _activePipeline, MAX_RUNS, runHistory, _uiContainer, _selectedRunIndex, _cardSearchQuery, pipelineDebugAPI;
|
|
827
986
|
var init_PipelineDebugger = __esm({
|
|
828
987
|
"src/core/navigators/PipelineDebugger.ts"() {
|
|
829
988
|
"use strict";
|
|
@@ -832,6 +991,9 @@ var init_PipelineDebugger = __esm({
|
|
|
832
991
|
_activePipeline = null;
|
|
833
992
|
MAX_RUNS = 10;
|
|
834
993
|
runHistory = [];
|
|
994
|
+
_uiContainer = null;
|
|
995
|
+
_selectedRunIndex = null;
|
|
996
|
+
_cardSearchQuery = "";
|
|
835
997
|
pipelineDebugAPI = {
|
|
836
998
|
/**
|
|
837
999
|
* Get raw run history for programmatic access.
|
|
@@ -971,16 +1133,20 @@ var init_PipelineDebugger = __esm({
|
|
|
971
1133
|
const mode = reason.match(/mode=([^;]+)/)?.[1] ?? "unknown";
|
|
972
1134
|
const blocked = reason.match(/blocked=([^;]+)/)?.[1] ?? "unknown";
|
|
973
1135
|
const blockedTargets = reason.match(/blockedTargets=([^;]+)/)?.[1] ?? "none";
|
|
1136
|
+
const supportCard = reason.match(/supportCard=([^;]+)/)?.[1] ?? "none";
|
|
974
1137
|
const supportTags = reason.match(/supportTags=([^;]+)/)?.[1] ?? "none";
|
|
975
1138
|
const multiplier = reason.match(/multiplier=([^;]+)/)?.[1] ?? "unknown";
|
|
1139
|
+
const supportSource = mode === "discovered-support" ? "discovered" : mode === "support" ? "authored" : "n/a";
|
|
976
1140
|
return {
|
|
977
1141
|
group: parsedGroup,
|
|
978
1142
|
mode,
|
|
1143
|
+
supportSource,
|
|
979
1144
|
cardId: card.cardId,
|
|
980
1145
|
selected: card.selected ? "yes" : "no",
|
|
981
1146
|
finalScore: card.finalScore.toFixed(3),
|
|
982
1147
|
blocked,
|
|
983
1148
|
blockedTargets,
|
|
1149
|
+
supportCard,
|
|
984
1150
|
supportTags,
|
|
985
1151
|
multiplier
|
|
986
1152
|
};
|
|
@@ -996,6 +1162,8 @@ var init_PipelineDebugger = __esm({
|
|
|
996
1162
|
const selectedRows = rows.filter((r) => r.selected === "yes");
|
|
997
1163
|
const blockedTargetSet = /* @__PURE__ */ new Set();
|
|
998
1164
|
const supportTagSet = /* @__PURE__ */ new Set();
|
|
1165
|
+
const authoredSupportSet = /* @__PURE__ */ new Set();
|
|
1166
|
+
const discoveredSupportSet = /* @__PURE__ */ new Set();
|
|
999
1167
|
for (const row of rows) {
|
|
1000
1168
|
if (row.blockedTargets && row.blockedTargets !== "none") {
|
|
1001
1169
|
row.blockedTargets.split("|").filter(Boolean).forEach((t) => blockedTargetSet.add(t));
|
|
@@ -1003,6 +1171,13 @@ var init_PipelineDebugger = __esm({
|
|
|
1003
1171
|
if (row.supportTags && row.supportTags !== "none") {
|
|
1004
1172
|
row.supportTags.split("|").filter(Boolean).forEach((t) => supportTagSet.add(t));
|
|
1005
1173
|
}
|
|
1174
|
+
if (row.supportCard && row.supportCard !== "none") {
|
|
1175
|
+
if (row.supportSource === "discovered") {
|
|
1176
|
+
discoveredSupportSet.add(row.supportCard);
|
|
1177
|
+
} else if (row.supportSource === "authored") {
|
|
1178
|
+
authoredSupportSet.add(row.supportCard);
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1006
1181
|
}
|
|
1007
1182
|
logger.info(`Prescribed cards in run: ${rows.length}`);
|
|
1008
1183
|
logger.info(`Selected prescribed cards: ${selectedRows.length}`);
|
|
@@ -1012,6 +1187,12 @@ var init_PipelineDebugger = __esm({
|
|
|
1012
1187
|
logger.info(
|
|
1013
1188
|
`Resolved support tags referenced: ${supportTagSet.size > 0 ? [...supportTagSet].join(", ") : "none"}`
|
|
1014
1189
|
);
|
|
1190
|
+
logger.info(
|
|
1191
|
+
`Authored support cards emitted: ${authoredSupportSet.size > 0 ? [...authoredSupportSet].join(", ") : "none"}`
|
|
1192
|
+
);
|
|
1193
|
+
logger.info(
|
|
1194
|
+
`Discovered support cards emitted: ${discoveredSupportSet.size > 0 ? [...discoveredSupportSet].join(", ") : "none"}`
|
|
1195
|
+
);
|
|
1015
1196
|
console.groupEnd();
|
|
1016
1197
|
},
|
|
1017
1198
|
/**
|
|
@@ -1146,6 +1327,39 @@ var init_PipelineDebugger = __esm({
|
|
|
1146
1327
|
Object.fromEntries(entries.map(([tag, data]) => [tag, { score: Math.round(data.score), count: data.count }]))
|
|
1147
1328
|
);
|
|
1148
1329
|
},
|
|
1330
|
+
/**
|
|
1331
|
+
* Toggle the full-screen UI debugger.
|
|
1332
|
+
*/
|
|
1333
|
+
ui() {
|
|
1334
|
+
if (_uiContainer) {
|
|
1335
|
+
document.body.removeChild(_uiContainer);
|
|
1336
|
+
_uiContainer = null;
|
|
1337
|
+
return;
|
|
1338
|
+
}
|
|
1339
|
+
_uiContainer = document.createElement("div");
|
|
1340
|
+
_uiContainer.id = "sk-pipeline-debugger";
|
|
1341
|
+
document.body.appendChild(_uiContainer);
|
|
1342
|
+
if (_selectedRunIndex === null && runHistory.length > 0) {
|
|
1343
|
+
_selectedRunIndex = 0;
|
|
1344
|
+
}
|
|
1345
|
+
renderUI();
|
|
1346
|
+
},
|
|
1347
|
+
/**
|
|
1348
|
+
* Internal UI helpers
|
|
1349
|
+
* @internal
|
|
1350
|
+
*/
|
|
1351
|
+
_selectRun(index) {
|
|
1352
|
+
_selectedRunIndex = index;
|
|
1353
|
+
renderUI();
|
|
1354
|
+
},
|
|
1355
|
+
/**
|
|
1356
|
+
* Internal UI helpers
|
|
1357
|
+
* @internal
|
|
1358
|
+
*/
|
|
1359
|
+
_setSearch(query) {
|
|
1360
|
+
_cardSearchQuery = query;
|
|
1361
|
+
renderUI();
|
|
1362
|
+
},
|
|
1149
1363
|
/**
|
|
1150
1364
|
* Show help.
|
|
1151
1365
|
*/
|
|
@@ -1154,6 +1368,7 @@ var init_PipelineDebugger = __esm({
|
|
|
1154
1368
|
\u{1F527} Pipeline Debug API
|
|
1155
1369
|
|
|
1156
1370
|
Commands:
|
|
1371
|
+
.ui() Toggle full-screen UI debugger
|
|
1157
1372
|
.showLastRun() Show summary of most recent pipeline run
|
|
1158
1373
|
.showRun(id|index) Show summary of a specific run (by index or ID suffix)
|
|
1159
1374
|
.showCard(cardId) Show provenance trail for a specific card
|
|
@@ -1170,6 +1385,7 @@ Commands:
|
|
|
1170
1385
|
.help() Show this help message
|
|
1171
1386
|
|
|
1172
1387
|
Example:
|
|
1388
|
+
window.skuilder.pipeline.ui()
|
|
1173
1389
|
window.skuilder.pipeline.showLastRun()
|
|
1174
1390
|
window.skuilder.pipeline.showRun(1)
|
|
1175
1391
|
await window.skuilder.pipeline.diagnoseCardSpace()
|
|
@@ -1332,7 +1548,7 @@ var init_CompositeGenerator = __esm({
|
|
|
1332
1548
|
for (const [, items] of byCardId) {
|
|
1333
1549
|
const cards2 = items.map((i) => i.card);
|
|
1334
1550
|
const aggregatedScore = this.aggregateScores(items);
|
|
1335
|
-
const finalScore = Math.
|
|
1551
|
+
const finalScore = Math.max(0, aggregatedScore);
|
|
1336
1552
|
const mergedProvenance = cards2.flatMap((c) => c.provenance);
|
|
1337
1553
|
const initialScore = cards2[0].score;
|
|
1338
1554
|
const action = finalScore > initialScore ? "boosted" : finalScore < initialScore ? "penalized" : "passed";
|
|
@@ -1529,10 +1745,26 @@ function matchesTagPattern(tag, pattern) {
|
|
|
1529
1745
|
const re = new RegExp(`^${escaped}$`);
|
|
1530
1746
|
return re.test(tag);
|
|
1531
1747
|
}
|
|
1748
|
+
function extractWordStem(cardId) {
|
|
1749
|
+
for (const prefix of ["c-ml-", "c-ws-", "c-spelling-"]) {
|
|
1750
|
+
if (cardId.startsWith(prefix)) {
|
|
1751
|
+
const rest = cardId.slice(prefix.length);
|
|
1752
|
+
const lastDash = rest.lastIndexOf("-");
|
|
1753
|
+
return lastDash > 0 ? rest.slice(0, lastDash) : rest;
|
|
1754
|
+
}
|
|
1755
|
+
}
|
|
1756
|
+
return cardId;
|
|
1757
|
+
}
|
|
1758
|
+
function shuffleInPlace(arr) {
|
|
1759
|
+
for (let i = arr.length - 1; i > 0; i--) {
|
|
1760
|
+
const j = Math.floor(Math.random() * (i + 1));
|
|
1761
|
+
[arr[i], arr[j]] = [arr[j], arr[i]];
|
|
1762
|
+
}
|
|
1763
|
+
}
|
|
1532
1764
|
function pickTopByScore(cards, limit) {
|
|
1533
1765
|
return [...cards].sort((a, b) => b.score - a.score || a.cardId.localeCompare(b.cardId)).slice(0, limit);
|
|
1534
1766
|
}
|
|
1535
|
-
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,
|
|
1767
|
+
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;
|
|
1536
1768
|
var init_prescribed = __esm({
|
|
1537
1769
|
"src/core/navigators/generators/prescribed.ts"() {
|
|
1538
1770
|
"use strict";
|
|
@@ -1545,11 +1777,10 @@ var init_prescribed = __esm({
|
|
|
1545
1777
|
DEFAULT_MIN_COUNT = 3;
|
|
1546
1778
|
BASE_TARGET_SCORE = 1;
|
|
1547
1779
|
BASE_SUPPORT_SCORE = 0.8;
|
|
1780
|
+
DISCOVERED_SUPPORT_SCORE = 12;
|
|
1548
1781
|
MAX_TARGET_MULTIPLIER = 8;
|
|
1549
1782
|
MAX_SUPPORT_MULTIPLIER = 4;
|
|
1550
|
-
|
|
1551
|
-
LESSON_GATE_PENALTY_TAG_HINT = "concept:";
|
|
1552
|
-
PRESCRIBED_DEBUG_VERSION = "testversion-prescribed-v2";
|
|
1783
|
+
PRESCRIBED_DEBUG_VERSION = "testversion-prescribed-v3";
|
|
1553
1784
|
PrescribedCardsGenerator = class extends ContentNavigator {
|
|
1554
1785
|
name;
|
|
1555
1786
|
config;
|
|
@@ -1585,6 +1816,20 @@ var init_prescribed = __esm({
|
|
|
1585
1816
|
const allSupportIds = dedupe(this.config.groups.flatMap((g) => g.supportCardIds ?? []));
|
|
1586
1817
|
const allRelevantIds = dedupe([...allTargetIds, ...allSupportIds]);
|
|
1587
1818
|
const tagsByCard = allRelevantIds.length > 0 ? await this.course.getAppliedTagsBatch(allRelevantIds) : /* @__PURE__ */ new Map();
|
|
1819
|
+
const courseTagDocs = await this.course.getCourseTagStubs().catch(
|
|
1820
|
+
() => ({
|
|
1821
|
+
rows: [],
|
|
1822
|
+
offset: 0,
|
|
1823
|
+
total_rows: 0
|
|
1824
|
+
})
|
|
1825
|
+
);
|
|
1826
|
+
const cardsByTag = /* @__PURE__ */ new Map();
|
|
1827
|
+
for (const row of courseTagDocs.rows ?? []) {
|
|
1828
|
+
const tagDoc = row.doc;
|
|
1829
|
+
if (tagDoc?.name && Array.isArray(tagDoc.taggedCards)) {
|
|
1830
|
+
cardsByTag.set(tagDoc.name, [...tagDoc.taggedCards]);
|
|
1831
|
+
}
|
|
1832
|
+
}
|
|
1588
1833
|
const nextState = {
|
|
1589
1834
|
updatedAt: isoNow(),
|
|
1590
1835
|
groups: {}
|
|
@@ -1599,11 +1844,31 @@ var init_prescribed = __esm({
|
|
|
1599
1844
|
activeIds,
|
|
1600
1845
|
seenIds,
|
|
1601
1846
|
tagsByCard,
|
|
1847
|
+
cardsByTag,
|
|
1602
1848
|
hierarchyConfigs,
|
|
1603
1849
|
userTagElo,
|
|
1604
1850
|
userGlobalElo
|
|
1605
1851
|
});
|
|
1606
1852
|
groupRuntimes.push(runtime);
|
|
1853
|
+
logger.info(
|
|
1854
|
+
`[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)}`
|
|
1855
|
+
);
|
|
1856
|
+
if (runtime.blockedTargets.length > 0) {
|
|
1857
|
+
logger.info(
|
|
1858
|
+
`[Prescribed] Group '${group.id}' blocked targets: ${runtime.blockedTargets.join(", ")}`
|
|
1859
|
+
);
|
|
1860
|
+
logger.info(
|
|
1861
|
+
`[Prescribed] Group '${group.id}' support tags needed: ${runtime.supportTags.join(", ") || "(none)"}`
|
|
1862
|
+
);
|
|
1863
|
+
logger.info(
|
|
1864
|
+
`[Prescribed] Group '${group.id}' escalation mode: ` + (runtime.supportCandidates.length > 0 ? "direct-support" : runtime.discoveredSupportCandidates.length > 0 ? "inserted-support-candidates" : "boost-only")
|
|
1865
|
+
);
|
|
1866
|
+
if (runtime.discoveredSupportCandidates.length > 0) {
|
|
1867
|
+
logger.info(
|
|
1868
|
+
`[Prescribed] Group '${group.id}' discovered support candidates: ${runtime.discoveredSupportCandidates.join(", ")}`
|
|
1869
|
+
);
|
|
1870
|
+
}
|
|
1871
|
+
}
|
|
1607
1872
|
nextState.groups[group.id] = this.buildNextGroupState(runtime, progress.groups[group.id]);
|
|
1608
1873
|
const directCards = this.buildDirectTargetCards(
|
|
1609
1874
|
runtime,
|
|
@@ -1615,15 +1880,30 @@ var init_prescribed = __esm({
|
|
|
1615
1880
|
courseId,
|
|
1616
1881
|
emittedIds
|
|
1617
1882
|
);
|
|
1618
|
-
|
|
1883
|
+
const discoveredSupportCards = this.buildDiscoveredSupportCards(
|
|
1884
|
+
runtime,
|
|
1885
|
+
courseId,
|
|
1886
|
+
emittedIds
|
|
1887
|
+
);
|
|
1888
|
+
emitted.push(...directCards, ...supportCards, ...discoveredSupportCards);
|
|
1619
1889
|
}
|
|
1620
1890
|
const hintSummary = this.buildSupportHintSummary(groupRuntimes);
|
|
1621
1891
|
const hints = Object.keys(hintSummary.boostTags).length > 0 ? {
|
|
1622
1892
|
boostTags: hintSummary.boostTags,
|
|
1623
1893
|
_label: `prescribed-support (${hintSummary.supportTags.length} tags; blocked=${hintSummary.blockedTargetIds.length}; testversion=${PRESCRIBED_DEBUG_VERSION})`
|
|
1624
1894
|
} : void 0;
|
|
1895
|
+
if (hints) {
|
|
1896
|
+
const tagEntries = Object.entries(hints.boostTags ?? {});
|
|
1897
|
+
logger.info(
|
|
1898
|
+
`[Prescribed] Emitting ${tagEntries.length} boost hint(s): ` + tagEntries.map(([tag, mult]) => `${tag}\xD7${mult.toFixed(1)}`).join(", ")
|
|
1899
|
+
);
|
|
1900
|
+
} else {
|
|
1901
|
+
logger.info("[Prescribed] No hints to emit (no blocked targets or no support tags)");
|
|
1902
|
+
}
|
|
1625
1903
|
if (emitted.length === 0) {
|
|
1626
|
-
logger.
|
|
1904
|
+
logger.info(
|
|
1905
|
+
"[Prescribed] 0 cards emitted (all targets blocked, authored/discovered support candidates exhausted)" + (hints ? " \u2014 boost hints emitted but may not survive filters" : "")
|
|
1906
|
+
);
|
|
1627
1907
|
await this.putStrategyState(nextState).catch((e) => {
|
|
1628
1908
|
logger.debug(`[Prescribed] Failed to persist empty-state update: ${e}`);
|
|
1629
1909
|
});
|
|
@@ -1656,7 +1936,7 @@ var init_prescribed = __esm({
|
|
|
1656
1936
|
logger.debug(`[Prescribed] Failed to persist prescribed progress: ${e}`);
|
|
1657
1937
|
});
|
|
1658
1938
|
logger.info(
|
|
1659
|
-
`[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)`
|
|
1939
|
+
`[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)`
|
|
1660
1940
|
);
|
|
1661
1941
|
return hints ? { cards: finalCards, hints } : { cards: finalCards };
|
|
1662
1942
|
}
|
|
@@ -1686,9 +1966,15 @@ var init_prescribed = __esm({
|
|
|
1686
1966
|
const groupsRaw = Array.isArray(parsed.groups) ? parsed.groups : [];
|
|
1687
1967
|
const groups = groupsRaw.map((raw, i) => ({
|
|
1688
1968
|
id: typeof raw.id === "string" && raw.id.trim().length > 0 ? raw.id : `group-${i + 1}`,
|
|
1689
|
-
targetCardIds: dedupe(
|
|
1690
|
-
|
|
1691
|
-
|
|
1969
|
+
targetCardIds: dedupe(
|
|
1970
|
+
Array.isArray(raw.targetCardIds) ? raw.targetCardIds.filter((v) => typeof v === "string") : []
|
|
1971
|
+
),
|
|
1972
|
+
supportCardIds: dedupe(
|
|
1973
|
+
Array.isArray(raw.supportCardIds) ? raw.supportCardIds.filter((v) => typeof v === "string") : []
|
|
1974
|
+
),
|
|
1975
|
+
supportTagPatterns: dedupe(
|
|
1976
|
+
Array.isArray(raw.supportTagPatterns) ? raw.supportTagPatterns.filter((v) => typeof v === "string") : []
|
|
1977
|
+
),
|
|
1692
1978
|
freshnessWindowSessions: typeof raw.freshnessWindowSessions === "number" ? raw.freshnessWindowSessions : DEFAULT_FRESHNESS_WINDOW,
|
|
1693
1979
|
maxDirectTargetsPerRun: typeof raw.maxDirectTargetsPerRun === "number" ? raw.maxDirectTargetsPerRun : DEFAULT_MAX_DIRECT_PER_RUN,
|
|
1694
1980
|
maxSupportCardsPerRun: typeof raw.maxSupportCardsPerRun === "number" ? raw.maxSupportCardsPerRun : DEFAULT_MAX_SUPPORT_PER_RUN,
|
|
@@ -1705,7 +1991,7 @@ var init_prescribed = __esm({
|
|
|
1705
1991
|
}
|
|
1706
1992
|
async loadHierarchyConfigs() {
|
|
1707
1993
|
try {
|
|
1708
|
-
const strategies = await this.course.
|
|
1994
|
+
const strategies = await this.course.getAllNavigationStrategies();
|
|
1709
1995
|
return strategies.filter((s) => s.implementingClass === "hierarchyDefinition").map((s) => {
|
|
1710
1996
|
try {
|
|
1711
1997
|
const parsed = JSON.parse(s.serializedData);
|
|
@@ -1728,6 +2014,7 @@ var init_prescribed = __esm({
|
|
|
1728
2014
|
activeIds,
|
|
1729
2015
|
seenIds,
|
|
1730
2016
|
tagsByCard,
|
|
2017
|
+
cardsByTag,
|
|
1731
2018
|
hierarchyConfigs,
|
|
1732
2019
|
userTagElo,
|
|
1733
2020
|
userGlobalElo
|
|
@@ -1791,6 +2078,22 @@ var init_prescribed = __esm({
|
|
|
1791
2078
|
[...supportTags]
|
|
1792
2079
|
)
|
|
1793
2080
|
]).filter((id) => !activeIds.has(id) && !seenIds.has(id));
|
|
2081
|
+
const discoveredSupportCandidates = blockedTargets.length > 0 && supportTags.size > 0 && supportCandidates.length === 0 ? this.findDiscoveredSupportCards({
|
|
2082
|
+
supportTags: [...supportTags],
|
|
2083
|
+
cardsByTag,
|
|
2084
|
+
activeIds,
|
|
2085
|
+
seenIds,
|
|
2086
|
+
excludedIds: /* @__PURE__ */ new Set([
|
|
2087
|
+
...group.targetCardIds,
|
|
2088
|
+
...group.supportCardIds ?? []
|
|
2089
|
+
]),
|
|
2090
|
+
limit: group.maxSupportCardsPerRun ?? DEFAULT_MAX_SUPPORT_PER_RUN
|
|
2091
|
+
}) : [];
|
|
2092
|
+
if (blockedTargets.length > 0 && supportTags.size > 0 && discoveredSupportCandidates.length === 0) {
|
|
2093
|
+
logger.info(
|
|
2094
|
+
`[Prescribed] Group '${group.id}' discovered 0 broader support candidates (blocked=${blockedTargets.length}; authoredSupport=${supportCandidates.length})`
|
|
2095
|
+
);
|
|
2096
|
+
}
|
|
1794
2097
|
const sessionsSinceSurfaced = priorState?.sessionsSinceSurfaced ?? 0;
|
|
1795
2098
|
const freshnessWindow = group.freshnessWindowSessions ?? DEFAULT_FRESHNESS_WINDOW;
|
|
1796
2099
|
const staleSessions = Math.max(0, sessionsSinceSurfaced - freshnessWindow);
|
|
@@ -1804,6 +2107,7 @@ var init_prescribed = __esm({
|
|
|
1804
2107
|
surfaceableTargets,
|
|
1805
2108
|
targetTags,
|
|
1806
2109
|
supportCandidates,
|
|
2110
|
+
discoveredSupportCandidates,
|
|
1807
2111
|
supportTags: [...supportTags],
|
|
1808
2112
|
pressureMultiplier,
|
|
1809
2113
|
supportMultiplier,
|
|
@@ -1874,6 +2178,33 @@ var init_prescribed = __esm({
|
|
|
1874
2178
|
}
|
|
1875
2179
|
return cards;
|
|
1876
2180
|
}
|
|
2181
|
+
buildDiscoveredSupportCards(runtime, courseId, emittedIds) {
|
|
2182
|
+
if (runtime.blockedTargets.length === 0 || runtime.discoveredSupportCandidates.length === 0) {
|
|
2183
|
+
return [];
|
|
2184
|
+
}
|
|
2185
|
+
const maxSupport = runtime.group.maxSupportCardsPerRun ?? DEFAULT_MAX_SUPPORT_PER_RUN;
|
|
2186
|
+
const supportIds = runtime.discoveredSupportCandidates.filter((id) => !emittedIds.has(id)).slice(0, maxSupport);
|
|
2187
|
+
const cards = [];
|
|
2188
|
+
for (const cardId of supportIds) {
|
|
2189
|
+
emittedIds.add(cardId);
|
|
2190
|
+
cards.push({
|
|
2191
|
+
cardId,
|
|
2192
|
+
courseId,
|
|
2193
|
+
score: DISCOVERED_SUPPORT_SCORE * runtime.supportMultiplier,
|
|
2194
|
+
provenance: [
|
|
2195
|
+
{
|
|
2196
|
+
strategy: "prescribed",
|
|
2197
|
+
strategyName: this.strategyName || this.name,
|
|
2198
|
+
strategyId: this.strategyId || "NAVIGATION_STRATEGY-prescribed",
|
|
2199
|
+
action: "generated",
|
|
2200
|
+
score: DISCOVERED_SUPPORT_SCORE * runtime.supportMultiplier,
|
|
2201
|
+
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}`
|
|
2202
|
+
}
|
|
2203
|
+
]
|
|
2204
|
+
});
|
|
2205
|
+
}
|
|
2206
|
+
return cards;
|
|
2207
|
+
}
|
|
1877
2208
|
findSupportCardsByTags(group, tagsByCard, supportTags) {
|
|
1878
2209
|
if (supportTags.length === 0) {
|
|
1879
2210
|
return [];
|
|
@@ -1896,6 +2227,40 @@ var init_prescribed = __esm({
|
|
|
1896
2227
|
}
|
|
1897
2228
|
return [...candidates];
|
|
1898
2229
|
}
|
|
2230
|
+
findDiscoveredSupportCards(args) {
|
|
2231
|
+
const { supportTags, cardsByTag, activeIds, seenIds, excludedIds, limit } = args;
|
|
2232
|
+
const byCardId = /* @__PURE__ */ new Map();
|
|
2233
|
+
for (const supportTag of supportTags) {
|
|
2234
|
+
const taggedCards = cardsByTag.get(supportTag) ?? [];
|
|
2235
|
+
for (const cardId of taggedCards) {
|
|
2236
|
+
if (activeIds.has(cardId) || seenIds.has(cardId) || excludedIds.has(cardId)) {
|
|
2237
|
+
continue;
|
|
2238
|
+
}
|
|
2239
|
+
const existing = byCardId.get(cardId);
|
|
2240
|
+
if (existing) {
|
|
2241
|
+
existing.matches += 1;
|
|
2242
|
+
} else {
|
|
2243
|
+
byCardId.set(cardId, { cardId, matches: 1 });
|
|
2244
|
+
}
|
|
2245
|
+
}
|
|
2246
|
+
}
|
|
2247
|
+
const candidates = [...byCardId.values()].sort((a, b) => b.matches - a.matches || a.cardId.localeCompare(b.cardId));
|
|
2248
|
+
const usedStems = /* @__PURE__ */ new Set();
|
|
2249
|
+
const diverse = [];
|
|
2250
|
+
const deferred = [];
|
|
2251
|
+
for (const entry of candidates) {
|
|
2252
|
+
const stem = extractWordStem(entry.cardId);
|
|
2253
|
+
if (!usedStems.has(stem)) {
|
|
2254
|
+
usedStems.add(stem);
|
|
2255
|
+
diverse.push(entry);
|
|
2256
|
+
} else {
|
|
2257
|
+
deferred.push(entry);
|
|
2258
|
+
}
|
|
2259
|
+
}
|
|
2260
|
+
shuffleInPlace(diverse);
|
|
2261
|
+
shuffleInPlace(deferred);
|
|
2262
|
+
return [...diverse, ...deferred].slice(0, limit).map((entry) => entry.cardId);
|
|
2263
|
+
}
|
|
1899
2264
|
resolveBlockedSupportTags(targetTags, hierarchyConfigs, userTagElo, userGlobalElo, hierarchyWalkEnabled, maxDepth) {
|
|
1900
2265
|
const supportTags = /* @__PURE__ */ new Set();
|
|
1901
2266
|
let blocked = false;
|
|
@@ -1941,7 +2306,6 @@ var init_prescribed = __esm({
|
|
|
1941
2306
|
}
|
|
1942
2307
|
collectSupportTagsRecursive(tag, hierarchyConfigs, userTagElo, userGlobalElo, depth, visited, out) {
|
|
1943
2308
|
if (depth < 0 || visited.has(tag)) return;
|
|
1944
|
-
if (this.isHardGatedTag(tag)) return;
|
|
1945
2309
|
visited.add(tag);
|
|
1946
2310
|
let walkedFurther = false;
|
|
1947
2311
|
for (const hierarchy of hierarchyConfigs) {
|
|
@@ -1969,9 +2333,6 @@ var init_prescribed = __esm({
|
|
|
1969
2333
|
out.add(tag);
|
|
1970
2334
|
}
|
|
1971
2335
|
}
|
|
1972
|
-
isHardGatedTag(tag) {
|
|
1973
|
-
return LOCKED_TAG_PREFIXES.some((prefix) => tag.startsWith(prefix)) && tag.startsWith(LESSON_GATE_PENALTY_TAG_HINT);
|
|
1974
|
-
}
|
|
1975
2336
|
isPrerequisiteMet(prereq, userTagElo, userGlobalElo) {
|
|
1976
2337
|
if (!userTagElo) return false;
|
|
1977
2338
|
const minCount = prereq.masteryThreshold?.minCount ?? DEFAULT_MIN_COUNT;
|
|
@@ -3763,6 +4124,32 @@ var init_Pipeline = __esm({
|
|
|
3763
4124
|
cards = await this.hydrateTags(cards);
|
|
3764
4125
|
const tHydrate = performance.now();
|
|
3765
4126
|
const allCardsBeforeFiltering = [...cards];
|
|
4127
|
+
const pendingHints = this._ephemeralHints;
|
|
4128
|
+
if (pendingHints?.requireCards?.length) {
|
|
4129
|
+
const poolIds = new Set(allCardsBeforeFiltering.map((c) => c.cardId));
|
|
4130
|
+
const missingIds = pendingHints.requireCards.filter(
|
|
4131
|
+
(p) => !p.includes("*") && !poolIds.has(p)
|
|
4132
|
+
);
|
|
4133
|
+
if (missingIds.length > 0) {
|
|
4134
|
+
const fetchedTags = await this.course.getAppliedTagsBatch(missingIds);
|
|
4135
|
+
const courseId = this.course.getCourseID();
|
|
4136
|
+
for (const cardId of missingIds) {
|
|
4137
|
+
allCardsBeforeFiltering.push({
|
|
4138
|
+
cardId,
|
|
4139
|
+
courseId,
|
|
4140
|
+
score: 1,
|
|
4141
|
+
tags: fetchedTags.get(cardId) ?? [],
|
|
4142
|
+
provenance: []
|
|
4143
|
+
});
|
|
4144
|
+
}
|
|
4145
|
+
logger.info(
|
|
4146
|
+
`[Pipeline] Pre-fetched ${missingIds.length} required card(s) into pool: ${missingIds.join(", ")}`
|
|
4147
|
+
);
|
|
4148
|
+
}
|
|
4149
|
+
}
|
|
4150
|
+
const prescribedIds = new Set(
|
|
4151
|
+
cards.filter((c) => c.provenance.some((p) => p.strategy === "prescribed")).map((c) => c.cardId)
|
|
4152
|
+
);
|
|
3766
4153
|
const filterImpacts = [];
|
|
3767
4154
|
for (const filter of this.filters) {
|
|
3768
4155
|
const beforeCount = cards.length;
|
|
@@ -3777,6 +4164,17 @@ var init_Pipeline = __esm({
|
|
|
3777
4164
|
else passed++;
|
|
3778
4165
|
}
|
|
3779
4166
|
filterImpacts.push({ name: filter.name, boosted, penalized, passed, removed });
|
|
4167
|
+
if (prescribedIds.size > 0) {
|
|
4168
|
+
const survivingIds = new Set(cards.map((c) => c.cardId));
|
|
4169
|
+
const killedPrescribed = [...prescribedIds].filter((id) => !survivingIds.has(id));
|
|
4170
|
+
const zeroedPrescribed = cards.filter((c) => prescribedIds.has(c.cardId) && c.score === 0).map((c) => c.cardId);
|
|
4171
|
+
if (killedPrescribed.length > 0 || zeroedPrescribed.length > 0) {
|
|
4172
|
+
logger.info(
|
|
4173
|
+
`[Pipeline] Filter '${filter.name}' impact on prescribed cards: ` + (killedPrescribed.length > 0 ? `removed=[${killedPrescribed.join(", ")}] ` : "") + (zeroedPrescribed.length > 0 ? `zeroed=[${zeroedPrescribed.join(", ")}]` : "")
|
|
4174
|
+
);
|
|
4175
|
+
killedPrescribed.forEach((id) => prescribedIds.delete(id));
|
|
4176
|
+
}
|
|
4177
|
+
}
|
|
3780
4178
|
logger.debug(`[Pipeline] Filter '${filter.name}': ${beforeScores.size} \u2192 ${cards.length} cards (\u2191${boosted} \u2193${penalized} =${passed})`);
|
|
3781
4179
|
}
|
|
3782
4180
|
cards = cards.filter((c) => c.score > 0);
|
|
@@ -3813,7 +4211,8 @@ var init_Pipeline = __esm({
|
|
|
3813
4211
|
filterImpacts,
|
|
3814
4212
|
cards,
|
|
3815
4213
|
result,
|
|
3816
|
-
context.userElo
|
|
4214
|
+
context.userElo,
|
|
4215
|
+
hints ?? void 0
|
|
3817
4216
|
);
|
|
3818
4217
|
captureRun(report);
|
|
3819
4218
|
} catch (e) {
|
|
@@ -3923,13 +4322,27 @@ var init_Pipeline = __esm({
|
|
|
3923
4322
|
}
|
|
3924
4323
|
}
|
|
3925
4324
|
const cardIds = new Set(cards.map((c) => c.cardId));
|
|
4325
|
+
const cardMap = new Map(cards.map((c) => [c.cardId, c]));
|
|
3926
4326
|
const hintLabel = hints._label ? `Replan Hint (${hints._label})` : "Replan Hint";
|
|
3927
|
-
const
|
|
3928
|
-
|
|
3929
|
-
|
|
4327
|
+
const applyRequirement = (card, reason) => {
|
|
4328
|
+
const mandatoryScore = Number.POSITIVE_INFINITY;
|
|
4329
|
+
const existing = cardMap.get(card.cardId);
|
|
4330
|
+
if (existing) {
|
|
4331
|
+
if (existing.score < mandatoryScore) {
|
|
4332
|
+
existing.score = mandatoryScore;
|
|
4333
|
+
existing.provenance.push({
|
|
4334
|
+
strategy: "ephemeralHint",
|
|
4335
|
+
strategyId: "ephemeral-hint",
|
|
4336
|
+
strategyName: hintLabel,
|
|
4337
|
+
action: "boosted",
|
|
4338
|
+
score: mandatoryScore,
|
|
4339
|
+
reason: `${reason} (upgrade to mandatory score)`
|
|
4340
|
+
});
|
|
4341
|
+
}
|
|
4342
|
+
} else {
|
|
3930
4343
|
cards.push({
|
|
3931
4344
|
...card,
|
|
3932
|
-
score:
|
|
4345
|
+
score: mandatoryScore,
|
|
3933
4346
|
provenance: [
|
|
3934
4347
|
...card.provenance,
|
|
3935
4348
|
{
|
|
@@ -3937,25 +4350,41 @@ var init_Pipeline = __esm({
|
|
|
3937
4350
|
strategyId: "ephemeral-hint",
|
|
3938
4351
|
strategyName: hintLabel,
|
|
3939
4352
|
action: "boosted",
|
|
3940
|
-
score:
|
|
4353
|
+
score: mandatoryScore,
|
|
3941
4354
|
reason
|
|
3942
4355
|
}
|
|
3943
4356
|
]
|
|
3944
4357
|
});
|
|
3945
4358
|
cardIds.add(card.cardId);
|
|
4359
|
+
cardMap.set(card.cardId, cards[cards.length - 1]);
|
|
3946
4360
|
}
|
|
3947
4361
|
};
|
|
3948
4362
|
if (hints.requireCards?.length) {
|
|
3949
4363
|
for (const pattern of hints.requireCards) {
|
|
4364
|
+
for (const cardId of cardIds) {
|
|
4365
|
+
if (globMatch(cardId, pattern)) {
|
|
4366
|
+
applyRequirement(cardMap.get(cardId), `requireCard ${pattern}`);
|
|
4367
|
+
}
|
|
4368
|
+
}
|
|
3950
4369
|
for (const card of allCards) {
|
|
3951
|
-
if (globMatch(card.cardId, pattern))
|
|
4370
|
+
if (globMatch(card.cardId, pattern)) {
|
|
4371
|
+
applyRequirement(card, `requireCard ${pattern}`);
|
|
4372
|
+
}
|
|
3952
4373
|
}
|
|
3953
4374
|
}
|
|
3954
4375
|
}
|
|
3955
4376
|
if (hints.requireTags?.length) {
|
|
3956
4377
|
for (const pattern of hints.requireTags) {
|
|
4378
|
+
for (const cardId of cardIds) {
|
|
4379
|
+
const card = cardMap.get(cardId);
|
|
4380
|
+
if (cardMatchesTagPattern(card, pattern)) {
|
|
4381
|
+
applyRequirement(card, `requireTag ${pattern}`);
|
|
4382
|
+
}
|
|
4383
|
+
}
|
|
3957
4384
|
for (const card of allCards) {
|
|
3958
|
-
if (cardMatchesTagPattern(card, pattern))
|
|
4385
|
+
if (cardMatchesTagPattern(card, pattern)) {
|
|
4386
|
+
applyRequirement(card, `requireTag ${pattern}`);
|
|
4387
|
+
}
|
|
3959
4388
|
}
|
|
3960
4389
|
}
|
|
3961
4390
|
}
|