@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/index.mjs
CHANGED
|
@@ -865,8 +865,9 @@ function getOrigin(card) {
|
|
|
865
865
|
const firstEntry = card.provenance[0];
|
|
866
866
|
if (!firstEntry) return "unknown";
|
|
867
867
|
const reason = firstEntry.reason?.toLowerCase() || "";
|
|
868
|
-
|
|
869
|
-
if (reason.includes("
|
|
868
|
+
const strategy = firstEntry.strategy?.toLowerCase() || "";
|
|
869
|
+
if (reason.includes("new card") || strategy.includes("elo")) return "new";
|
|
870
|
+
if (reason.includes("review") || strategy.includes("srs")) return "review";
|
|
870
871
|
return "unknown";
|
|
871
872
|
}
|
|
872
873
|
function captureRun(report) {
|
|
@@ -886,12 +887,13 @@ function parseCardElo(provenance) {
|
|
|
886
887
|
const match = eloEntry.reason.match(/card:\s*(\d+)/);
|
|
887
888
|
return match ? parseInt(match[1], 10) : void 0;
|
|
888
889
|
}
|
|
889
|
-
function buildRunReport(courseId, courseName, generatorName, generators, generatedCount, filters, allCards, selectedCards, userElo) {
|
|
890
|
+
function buildRunReport(courseId, courseName, generatorName, generators, generatedCount, filters, allCards, selectedCards, userElo, hints) {
|
|
890
891
|
const selectedIds = new Set(selectedCards.map((c) => c.cardId));
|
|
891
892
|
const cards = allCards.map((card) => ({
|
|
892
893
|
cardId: card.cardId,
|
|
893
894
|
courseId: card.courseId,
|
|
894
895
|
origin: getOrigin(card),
|
|
896
|
+
generator: card.provenance[0]?.strategyName || card.provenance[0]?.strategy,
|
|
895
897
|
finalScore: card.score,
|
|
896
898
|
cardElo: parseCardElo(card.provenance),
|
|
897
899
|
provenance: card.provenance,
|
|
@@ -908,6 +910,7 @@ function buildRunReport(courseId, courseName, generatorName, generators, generat
|
|
|
908
910
|
generators,
|
|
909
911
|
generatedCount,
|
|
910
912
|
filters,
|
|
913
|
+
hints,
|
|
911
914
|
finalCount: selectedCards.length,
|
|
912
915
|
reviewsSelected,
|
|
913
916
|
newSelected,
|
|
@@ -947,13 +950,169 @@ function printRunSummary(run) {
|
|
|
947
950
|
);
|
|
948
951
|
console.groupEnd();
|
|
949
952
|
}
|
|
953
|
+
function renderUI() {
|
|
954
|
+
if (!_uiContainer) return;
|
|
955
|
+
const runs = runHistory;
|
|
956
|
+
const selectedRun = _selectedRunIndex !== null ? runs[_selectedRunIndex] : null;
|
|
957
|
+
const styles = `
|
|
958
|
+
#sk-pipeline-debugger {
|
|
959
|
+
position: fixed;
|
|
960
|
+
top: 0;
|
|
961
|
+
left: 0;
|
|
962
|
+
width: 100vw;
|
|
963
|
+
height: 100vh;
|
|
964
|
+
background: #f8f9fa;
|
|
965
|
+
color: #212529;
|
|
966
|
+
z-index: 999999;
|
|
967
|
+
display: flex;
|
|
968
|
+
flex-direction: column;
|
|
969
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
|
970
|
+
font-size: 14px;
|
|
971
|
+
}
|
|
972
|
+
#sk-pipeline-debugger header {
|
|
973
|
+
padding: 1rem;
|
|
974
|
+
background: #343a40;
|
|
975
|
+
color: white;
|
|
976
|
+
display: flex;
|
|
977
|
+
justify-content: space-between;
|
|
978
|
+
align-items: center;
|
|
979
|
+
}
|
|
980
|
+
#sk-pipeline-debugger .container {
|
|
981
|
+
display: flex;
|
|
982
|
+
flex: 1;
|
|
983
|
+
overflow: hidden;
|
|
984
|
+
}
|
|
985
|
+
#sk-pipeline-debugger .sidebar {
|
|
986
|
+
width: 300px;
|
|
987
|
+
border-right: 1px solid #dee2e6;
|
|
988
|
+
overflow-y: auto;
|
|
989
|
+
background: white;
|
|
990
|
+
}
|
|
991
|
+
#sk-pipeline-debugger .main-content {
|
|
992
|
+
flex: 1;
|
|
993
|
+
overflow-y: auto;
|
|
994
|
+
padding: 1.5rem;
|
|
995
|
+
}
|
|
996
|
+
#sk-pipeline-debugger .run-item {
|
|
997
|
+
padding: 0.75rem 1rem;
|
|
998
|
+
border-bottom: 1px solid #eee;
|
|
999
|
+
cursor: pointer;
|
|
1000
|
+
}
|
|
1001
|
+
#sk-pipeline-debugger .run-item:hover { background: #f1f3f5; }
|
|
1002
|
+
#sk-pipeline-debugger .run-item.active { background: #e9ecef; border-left: 4px solid #007bff; }
|
|
1003
|
+
#sk-pipeline-debugger h2, #sk-pipeline-debugger h3 { margin-top: 0; }
|
|
1004
|
+
#sk-pipeline-debugger table { width: 100%; border-collapse: collapse; margin-bottom: 1rem; background: white; }
|
|
1005
|
+
#sk-pipeline-debugger th, #sk-pipeline-debugger td { border: 1px solid #dee2e6; padding: 0.5rem; text-align: left; }
|
|
1006
|
+
#sk-pipeline-debugger th { background: #f1f3f5; }
|
|
1007
|
+
#sk-pipeline-debugger code { background: #f1f3f5; padding: 0.1rem 0.3rem; border-radius: 3px; font-family: monospace; }
|
|
1008
|
+
#sk-pipeline-debugger .close-btn { background: #dc3545; color: white; border: none; padding: 0.5rem 1rem; border-radius: 4px; cursor: pointer; }
|
|
1009
|
+
#sk-pipeline-debugger .search-box { margin-bottom: 1rem; width: 100%; padding: 0.5rem; border: 1px solid #ced4da; border-radius: 4px; }
|
|
1010
|
+
#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; }
|
|
1011
|
+
`;
|
|
1012
|
+
const runListHtml = runs.length === 0 ? '<div style="padding: 1rem;">No runs captured yet.</div>' : runs.map(
|
|
1013
|
+
(r, i) => `
|
|
1014
|
+
<div class="run-item ${i === _selectedRunIndex ? "active" : ""}" onclick="window.skuilder.pipeline._selectRun(${i})">
|
|
1015
|
+
<strong>${r.timestamp.toLocaleTimeString()}</strong><br/>
|
|
1016
|
+
<small>${r.courseName || r.courseId.slice(0, 8)}</small><br/>
|
|
1017
|
+
<small>${r.finalCount} cards selected</small>
|
|
1018
|
+
</div>
|
|
1019
|
+
`
|
|
1020
|
+
).join("");
|
|
1021
|
+
let detailsHtml = '<div style="color: #6c757d; text-align: center; margin-top: 5rem;">Select a run to see details</div>';
|
|
1022
|
+
if (selectedRun) {
|
|
1023
|
+
const filteredCards = selectedRun.cards.filter(
|
|
1024
|
+
(c) => !_cardSearchQuery || c.cardId.toLowerCase().includes(_cardSearchQuery.toLowerCase())
|
|
1025
|
+
);
|
|
1026
|
+
detailsHtml = `
|
|
1027
|
+
<h2>Run: ${selectedRun.runId}</h2>
|
|
1028
|
+
<p>
|
|
1029
|
+
<strong>Time:</strong> ${selectedRun.timestamp.toLocaleString()} |
|
|
1030
|
+
<strong>Course:</strong> ${selectedRun.courseName || selectedRun.courseId} |
|
|
1031
|
+
<strong>User ELO:</strong> ${selectedRun.userElo ?? "unknown"}
|
|
1032
|
+
</p>
|
|
1033
|
+
|
|
1034
|
+
<h3>Pipeline Config</h3>
|
|
1035
|
+
<table>
|
|
1036
|
+
<tr><th>Generator</th><td>${selectedRun.generatorName} (${selectedRun.generatedCount} candidates)</td></tr>
|
|
1037
|
+
${(selectedRun.generators || []).map(
|
|
1038
|
+
(g) => `
|
|
1039
|
+
<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>
|
|
1040
|
+
`
|
|
1041
|
+
).join("")}
|
|
1042
|
+
</table>
|
|
1043
|
+
|
|
1044
|
+
${selectedRun.hints ? `
|
|
1045
|
+
<h3>Ephemeral Hints</h3>
|
|
1046
|
+
<table>
|
|
1047
|
+
${selectedRun.hints._label ? `<tr><th>Label</th><td>${selectedRun.hints._label}</td></tr>` : ""}
|
|
1048
|
+
${selectedRun.hints.boostTags ? `<tr><th>Boost Tags</th><td><pre style="margin:0">${JSON.stringify(selectedRun.hints.boostTags, null, 2)}</pre></td></tr>` : ""}
|
|
1049
|
+
${selectedRun.hints.boostCards ? `<tr><th>Boost Cards</th><td><pre style="margin:0">${JSON.stringify(selectedRun.hints.boostCards, null, 2)}</pre></td></tr>` : ""}
|
|
1050
|
+
${selectedRun.hints.requireTags ? `<tr><th>Require Tags</th><td>${selectedRun.hints.requireTags.join(", ")}</td></tr>` : ""}
|
|
1051
|
+
${selectedRun.hints.requireCards ? `<tr><th>Require Cards</th><td>${selectedRun.hints.requireCards.join(", ")}</td></tr>` : ""}
|
|
1052
|
+
${selectedRun.hints.excludeTags ? `<tr><th>Exclude Tags</th><td>${selectedRun.hints.excludeTags.join(", ")}</td></tr>` : ""}
|
|
1053
|
+
${selectedRun.hints.excludeCards ? `<tr><th>Exclude Cards</th><td>${selectedRun.hints.excludeCards.join(", ")}</td></tr>` : ""}
|
|
1054
|
+
</table>
|
|
1055
|
+
` : ""}
|
|
1056
|
+
|
|
1057
|
+
<h3>Filter Impact</h3>
|
|
1058
|
+
<table>
|
|
1059
|
+
<thead><tr><th>Filter</th><th>Boosted</th><th>Penalized</th><th>Passed</th><th>Removed</th></tr></thead>
|
|
1060
|
+
<tbody>
|
|
1061
|
+
${selectedRun.filters.map(
|
|
1062
|
+
(f) => `
|
|
1063
|
+
<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>
|
|
1064
|
+
`
|
|
1065
|
+
).join("")}
|
|
1066
|
+
</tbody>
|
|
1067
|
+
</table>
|
|
1068
|
+
|
|
1069
|
+
<h3>Cards (${selectedRun.finalCount} selected / ${selectedRun.cards.length} total)</h3>
|
|
1070
|
+
<input type="text" class="search-box" placeholder="Search Card ID..." value="${_cardSearchQuery}" oninput="window.skuilder.pipeline._setSearch(this.value)">
|
|
1071
|
+
|
|
1072
|
+
<table>
|
|
1073
|
+
<thead><tr><th>ID</th><th>Generator</th><th>Origin</th><th>Score</th><th>Selected</th></tr></thead>
|
|
1074
|
+
<tbody>
|
|
1075
|
+
${filteredCards.map(
|
|
1076
|
+
(c) => `
|
|
1077
|
+
<tr>
|
|
1078
|
+
<td><code>${c.cardId}</code></td>
|
|
1079
|
+
<td>${c.generator || "unknown"}</td>
|
|
1080
|
+
<td>${c.origin}</td>
|
|
1081
|
+
<td>${c.finalScore.toFixed(3)}</td>
|
|
1082
|
+
<td>${c.selected ? "\u2705" : "\u274C"}</td>
|
|
1083
|
+
</tr>
|
|
1084
|
+
${c.selected || _cardSearchQuery ? `
|
|
1085
|
+
<tr>
|
|
1086
|
+
<td colspan="5">
|
|
1087
|
+
<div class="provenance">${formatProvenance(c.provenance)}</div>
|
|
1088
|
+
</td>
|
|
1089
|
+
</tr>
|
|
1090
|
+
` : ""}
|
|
1091
|
+
`
|
|
1092
|
+
).join("")}
|
|
1093
|
+
</tbody>
|
|
1094
|
+
</table>
|
|
1095
|
+
`;
|
|
1096
|
+
}
|
|
1097
|
+
_uiContainer.innerHTML = `
|
|
1098
|
+
<style>${styles}</style>
|
|
1099
|
+
<header>
|
|
1100
|
+
<strong>Pipeline Debugger</strong>
|
|
1101
|
+
<button class="close-btn" onclick="window.skuilder.pipeline.ui()">Close</button>
|
|
1102
|
+
</header>
|
|
1103
|
+
<div class="container">
|
|
1104
|
+
<div class="sidebar">${runListHtml}</div>
|
|
1105
|
+
<div class="main-content">${detailsHtml}</div>
|
|
1106
|
+
</div>
|
|
1107
|
+
`;
|
|
1108
|
+
}
|
|
950
1109
|
function mountPipelineDebugger() {
|
|
951
1110
|
if (typeof window === "undefined") return;
|
|
952
1111
|
const win = window;
|
|
953
1112
|
win.skuilder = win.skuilder || {};
|
|
954
1113
|
win.skuilder.pipeline = pipelineDebugAPI;
|
|
955
1114
|
}
|
|
956
|
-
var _activePipeline, MAX_RUNS, runHistory, pipelineDebugAPI;
|
|
1115
|
+
var _activePipeline, MAX_RUNS, runHistory, _uiContainer, _selectedRunIndex, _cardSearchQuery, pipelineDebugAPI;
|
|
957
1116
|
var init_PipelineDebugger = __esm({
|
|
958
1117
|
"src/core/navigators/PipelineDebugger.ts"() {
|
|
959
1118
|
"use strict";
|
|
@@ -962,6 +1121,9 @@ var init_PipelineDebugger = __esm({
|
|
|
962
1121
|
_activePipeline = null;
|
|
963
1122
|
MAX_RUNS = 10;
|
|
964
1123
|
runHistory = [];
|
|
1124
|
+
_uiContainer = null;
|
|
1125
|
+
_selectedRunIndex = null;
|
|
1126
|
+
_cardSearchQuery = "";
|
|
965
1127
|
pipelineDebugAPI = {
|
|
966
1128
|
/**
|
|
967
1129
|
* Get raw run history for programmatic access.
|
|
@@ -1101,16 +1263,20 @@ var init_PipelineDebugger = __esm({
|
|
|
1101
1263
|
const mode = reason.match(/mode=([^;]+)/)?.[1] ?? "unknown";
|
|
1102
1264
|
const blocked = reason.match(/blocked=([^;]+)/)?.[1] ?? "unknown";
|
|
1103
1265
|
const blockedTargets = reason.match(/blockedTargets=([^;]+)/)?.[1] ?? "none";
|
|
1266
|
+
const supportCard = reason.match(/supportCard=([^;]+)/)?.[1] ?? "none";
|
|
1104
1267
|
const supportTags = reason.match(/supportTags=([^;]+)/)?.[1] ?? "none";
|
|
1105
1268
|
const multiplier = reason.match(/multiplier=([^;]+)/)?.[1] ?? "unknown";
|
|
1269
|
+
const supportSource = mode === "discovered-support" ? "discovered" : mode === "support" ? "authored" : "n/a";
|
|
1106
1270
|
return {
|
|
1107
1271
|
group: parsedGroup,
|
|
1108
1272
|
mode,
|
|
1273
|
+
supportSource,
|
|
1109
1274
|
cardId: card.cardId,
|
|
1110
1275
|
selected: card.selected ? "yes" : "no",
|
|
1111
1276
|
finalScore: card.finalScore.toFixed(3),
|
|
1112
1277
|
blocked,
|
|
1113
1278
|
blockedTargets,
|
|
1279
|
+
supportCard,
|
|
1114
1280
|
supportTags,
|
|
1115
1281
|
multiplier
|
|
1116
1282
|
};
|
|
@@ -1126,6 +1292,8 @@ var init_PipelineDebugger = __esm({
|
|
|
1126
1292
|
const selectedRows = rows.filter((r) => r.selected === "yes");
|
|
1127
1293
|
const blockedTargetSet = /* @__PURE__ */ new Set();
|
|
1128
1294
|
const supportTagSet = /* @__PURE__ */ new Set();
|
|
1295
|
+
const authoredSupportSet = /* @__PURE__ */ new Set();
|
|
1296
|
+
const discoveredSupportSet = /* @__PURE__ */ new Set();
|
|
1129
1297
|
for (const row of rows) {
|
|
1130
1298
|
if (row.blockedTargets && row.blockedTargets !== "none") {
|
|
1131
1299
|
row.blockedTargets.split("|").filter(Boolean).forEach((t) => blockedTargetSet.add(t));
|
|
@@ -1133,6 +1301,13 @@ var init_PipelineDebugger = __esm({
|
|
|
1133
1301
|
if (row.supportTags && row.supportTags !== "none") {
|
|
1134
1302
|
row.supportTags.split("|").filter(Boolean).forEach((t) => supportTagSet.add(t));
|
|
1135
1303
|
}
|
|
1304
|
+
if (row.supportCard && row.supportCard !== "none") {
|
|
1305
|
+
if (row.supportSource === "discovered") {
|
|
1306
|
+
discoveredSupportSet.add(row.supportCard);
|
|
1307
|
+
} else if (row.supportSource === "authored") {
|
|
1308
|
+
authoredSupportSet.add(row.supportCard);
|
|
1309
|
+
}
|
|
1310
|
+
}
|
|
1136
1311
|
}
|
|
1137
1312
|
logger.info(`Prescribed cards in run: ${rows.length}`);
|
|
1138
1313
|
logger.info(`Selected prescribed cards: ${selectedRows.length}`);
|
|
@@ -1142,6 +1317,12 @@ var init_PipelineDebugger = __esm({
|
|
|
1142
1317
|
logger.info(
|
|
1143
1318
|
`Resolved support tags referenced: ${supportTagSet.size > 0 ? [...supportTagSet].join(", ") : "none"}`
|
|
1144
1319
|
);
|
|
1320
|
+
logger.info(
|
|
1321
|
+
`Authored support cards emitted: ${authoredSupportSet.size > 0 ? [...authoredSupportSet].join(", ") : "none"}`
|
|
1322
|
+
);
|
|
1323
|
+
logger.info(
|
|
1324
|
+
`Discovered support cards emitted: ${discoveredSupportSet.size > 0 ? [...discoveredSupportSet].join(", ") : "none"}`
|
|
1325
|
+
);
|
|
1145
1326
|
console.groupEnd();
|
|
1146
1327
|
},
|
|
1147
1328
|
/**
|
|
@@ -1276,6 +1457,39 @@ var init_PipelineDebugger = __esm({
|
|
|
1276
1457
|
Object.fromEntries(entries.map(([tag, data]) => [tag, { score: Math.round(data.score), count: data.count }]))
|
|
1277
1458
|
);
|
|
1278
1459
|
},
|
|
1460
|
+
/**
|
|
1461
|
+
* Toggle the full-screen UI debugger.
|
|
1462
|
+
*/
|
|
1463
|
+
ui() {
|
|
1464
|
+
if (_uiContainer) {
|
|
1465
|
+
document.body.removeChild(_uiContainer);
|
|
1466
|
+
_uiContainer = null;
|
|
1467
|
+
return;
|
|
1468
|
+
}
|
|
1469
|
+
_uiContainer = document.createElement("div");
|
|
1470
|
+
_uiContainer.id = "sk-pipeline-debugger";
|
|
1471
|
+
document.body.appendChild(_uiContainer);
|
|
1472
|
+
if (_selectedRunIndex === null && runHistory.length > 0) {
|
|
1473
|
+
_selectedRunIndex = 0;
|
|
1474
|
+
}
|
|
1475
|
+
renderUI();
|
|
1476
|
+
},
|
|
1477
|
+
/**
|
|
1478
|
+
* Internal UI helpers
|
|
1479
|
+
* @internal
|
|
1480
|
+
*/
|
|
1481
|
+
_selectRun(index) {
|
|
1482
|
+
_selectedRunIndex = index;
|
|
1483
|
+
renderUI();
|
|
1484
|
+
},
|
|
1485
|
+
/**
|
|
1486
|
+
* Internal UI helpers
|
|
1487
|
+
* @internal
|
|
1488
|
+
*/
|
|
1489
|
+
_setSearch(query) {
|
|
1490
|
+
_cardSearchQuery = query;
|
|
1491
|
+
renderUI();
|
|
1492
|
+
},
|
|
1279
1493
|
/**
|
|
1280
1494
|
* Show help.
|
|
1281
1495
|
*/
|
|
@@ -1284,6 +1498,7 @@ var init_PipelineDebugger = __esm({
|
|
|
1284
1498
|
\u{1F527} Pipeline Debug API
|
|
1285
1499
|
|
|
1286
1500
|
Commands:
|
|
1501
|
+
.ui() Toggle full-screen UI debugger
|
|
1287
1502
|
.showLastRun() Show summary of most recent pipeline run
|
|
1288
1503
|
.showRun(id|index) Show summary of a specific run (by index or ID suffix)
|
|
1289
1504
|
.showCard(cardId) Show provenance trail for a specific card
|
|
@@ -1300,6 +1515,7 @@ Commands:
|
|
|
1300
1515
|
.help() Show this help message
|
|
1301
1516
|
|
|
1302
1517
|
Example:
|
|
1518
|
+
window.skuilder.pipeline.ui()
|
|
1303
1519
|
window.skuilder.pipeline.showLastRun()
|
|
1304
1520
|
window.skuilder.pipeline.showRun(1)
|
|
1305
1521
|
await window.skuilder.pipeline.diagnoseCardSpace()
|
|
@@ -1462,7 +1678,7 @@ var init_CompositeGenerator = __esm({
|
|
|
1462
1678
|
for (const [, items] of byCardId) {
|
|
1463
1679
|
const cards2 = items.map((i) => i.card);
|
|
1464
1680
|
const aggregatedScore = this.aggregateScores(items);
|
|
1465
|
-
const finalScore = Math.
|
|
1681
|
+
const finalScore = Math.max(0, aggregatedScore);
|
|
1466
1682
|
const mergedProvenance = cards2.flatMap((c) => c.provenance);
|
|
1467
1683
|
const initialScore = cards2[0].score;
|
|
1468
1684
|
const action = finalScore > initialScore ? "boosted" : finalScore < initialScore ? "penalized" : "passed";
|
|
@@ -1659,10 +1875,26 @@ function matchesTagPattern(tag, pattern) {
|
|
|
1659
1875
|
const re = new RegExp(`^${escaped}$`);
|
|
1660
1876
|
return re.test(tag);
|
|
1661
1877
|
}
|
|
1878
|
+
function extractWordStem(cardId) {
|
|
1879
|
+
for (const prefix of ["c-ml-", "c-ws-", "c-spelling-"]) {
|
|
1880
|
+
if (cardId.startsWith(prefix)) {
|
|
1881
|
+
const rest = cardId.slice(prefix.length);
|
|
1882
|
+
const lastDash = rest.lastIndexOf("-");
|
|
1883
|
+
return lastDash > 0 ? rest.slice(0, lastDash) : rest;
|
|
1884
|
+
}
|
|
1885
|
+
}
|
|
1886
|
+
return cardId;
|
|
1887
|
+
}
|
|
1888
|
+
function shuffleInPlace(arr) {
|
|
1889
|
+
for (let i = arr.length - 1; i > 0; i--) {
|
|
1890
|
+
const j = Math.floor(Math.random() * (i + 1));
|
|
1891
|
+
[arr[i], arr[j]] = [arr[j], arr[i]];
|
|
1892
|
+
}
|
|
1893
|
+
}
|
|
1662
1894
|
function pickTopByScore(cards, limit) {
|
|
1663
1895
|
return [...cards].sort((a, b) => b.score - a.score || a.cardId.localeCompare(b.cardId)).slice(0, limit);
|
|
1664
1896
|
}
|
|
1665
|
-
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,
|
|
1897
|
+
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;
|
|
1666
1898
|
var init_prescribed = __esm({
|
|
1667
1899
|
"src/core/navigators/generators/prescribed.ts"() {
|
|
1668
1900
|
"use strict";
|
|
@@ -1675,11 +1907,10 @@ var init_prescribed = __esm({
|
|
|
1675
1907
|
DEFAULT_MIN_COUNT = 3;
|
|
1676
1908
|
BASE_TARGET_SCORE = 1;
|
|
1677
1909
|
BASE_SUPPORT_SCORE = 0.8;
|
|
1910
|
+
DISCOVERED_SUPPORT_SCORE = 12;
|
|
1678
1911
|
MAX_TARGET_MULTIPLIER = 8;
|
|
1679
1912
|
MAX_SUPPORT_MULTIPLIER = 4;
|
|
1680
|
-
|
|
1681
|
-
LESSON_GATE_PENALTY_TAG_HINT = "concept:";
|
|
1682
|
-
PRESCRIBED_DEBUG_VERSION = "testversion-prescribed-v2";
|
|
1913
|
+
PRESCRIBED_DEBUG_VERSION = "testversion-prescribed-v3";
|
|
1683
1914
|
PrescribedCardsGenerator = class extends ContentNavigator {
|
|
1684
1915
|
name;
|
|
1685
1916
|
config;
|
|
@@ -1715,6 +1946,20 @@ var init_prescribed = __esm({
|
|
|
1715
1946
|
const allSupportIds = dedupe(this.config.groups.flatMap((g) => g.supportCardIds ?? []));
|
|
1716
1947
|
const allRelevantIds = dedupe([...allTargetIds, ...allSupportIds]);
|
|
1717
1948
|
const tagsByCard = allRelevantIds.length > 0 ? await this.course.getAppliedTagsBatch(allRelevantIds) : /* @__PURE__ */ new Map();
|
|
1949
|
+
const courseTagDocs = await this.course.getCourseTagStubs().catch(
|
|
1950
|
+
() => ({
|
|
1951
|
+
rows: [],
|
|
1952
|
+
offset: 0,
|
|
1953
|
+
total_rows: 0
|
|
1954
|
+
})
|
|
1955
|
+
);
|
|
1956
|
+
const cardsByTag = /* @__PURE__ */ new Map();
|
|
1957
|
+
for (const row of courseTagDocs.rows ?? []) {
|
|
1958
|
+
const tagDoc = row.doc;
|
|
1959
|
+
if (tagDoc?.name && Array.isArray(tagDoc.taggedCards)) {
|
|
1960
|
+
cardsByTag.set(tagDoc.name, [...tagDoc.taggedCards]);
|
|
1961
|
+
}
|
|
1962
|
+
}
|
|
1718
1963
|
const nextState = {
|
|
1719
1964
|
updatedAt: isoNow(),
|
|
1720
1965
|
groups: {}
|
|
@@ -1729,11 +1974,31 @@ var init_prescribed = __esm({
|
|
|
1729
1974
|
activeIds,
|
|
1730
1975
|
seenIds,
|
|
1731
1976
|
tagsByCard,
|
|
1977
|
+
cardsByTag,
|
|
1732
1978
|
hierarchyConfigs,
|
|
1733
1979
|
userTagElo,
|
|
1734
1980
|
userGlobalElo
|
|
1735
1981
|
});
|
|
1736
1982
|
groupRuntimes.push(runtime);
|
|
1983
|
+
logger.info(
|
|
1984
|
+
`[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)}`
|
|
1985
|
+
);
|
|
1986
|
+
if (runtime.blockedTargets.length > 0) {
|
|
1987
|
+
logger.info(
|
|
1988
|
+
`[Prescribed] Group '${group.id}' blocked targets: ${runtime.blockedTargets.join(", ")}`
|
|
1989
|
+
);
|
|
1990
|
+
logger.info(
|
|
1991
|
+
`[Prescribed] Group '${group.id}' support tags needed: ${runtime.supportTags.join(", ") || "(none)"}`
|
|
1992
|
+
);
|
|
1993
|
+
logger.info(
|
|
1994
|
+
`[Prescribed] Group '${group.id}' escalation mode: ` + (runtime.supportCandidates.length > 0 ? "direct-support" : runtime.discoveredSupportCandidates.length > 0 ? "inserted-support-candidates" : "boost-only")
|
|
1995
|
+
);
|
|
1996
|
+
if (runtime.discoveredSupportCandidates.length > 0) {
|
|
1997
|
+
logger.info(
|
|
1998
|
+
`[Prescribed] Group '${group.id}' discovered support candidates: ${runtime.discoveredSupportCandidates.join(", ")}`
|
|
1999
|
+
);
|
|
2000
|
+
}
|
|
2001
|
+
}
|
|
1737
2002
|
nextState.groups[group.id] = this.buildNextGroupState(runtime, progress.groups[group.id]);
|
|
1738
2003
|
const directCards = this.buildDirectTargetCards(
|
|
1739
2004
|
runtime,
|
|
@@ -1745,15 +2010,30 @@ var init_prescribed = __esm({
|
|
|
1745
2010
|
courseId,
|
|
1746
2011
|
emittedIds
|
|
1747
2012
|
);
|
|
1748
|
-
|
|
2013
|
+
const discoveredSupportCards = this.buildDiscoveredSupportCards(
|
|
2014
|
+
runtime,
|
|
2015
|
+
courseId,
|
|
2016
|
+
emittedIds
|
|
2017
|
+
);
|
|
2018
|
+
emitted.push(...directCards, ...supportCards, ...discoveredSupportCards);
|
|
1749
2019
|
}
|
|
1750
2020
|
const hintSummary = this.buildSupportHintSummary(groupRuntimes);
|
|
1751
2021
|
const hints = Object.keys(hintSummary.boostTags).length > 0 ? {
|
|
1752
2022
|
boostTags: hintSummary.boostTags,
|
|
1753
2023
|
_label: `prescribed-support (${hintSummary.supportTags.length} tags; blocked=${hintSummary.blockedTargetIds.length}; testversion=${PRESCRIBED_DEBUG_VERSION})`
|
|
1754
2024
|
} : void 0;
|
|
2025
|
+
if (hints) {
|
|
2026
|
+
const tagEntries = Object.entries(hints.boostTags ?? {});
|
|
2027
|
+
logger.info(
|
|
2028
|
+
`[Prescribed] Emitting ${tagEntries.length} boost hint(s): ` + tagEntries.map(([tag, mult]) => `${tag}\xD7${mult.toFixed(1)}`).join(", ")
|
|
2029
|
+
);
|
|
2030
|
+
} else {
|
|
2031
|
+
logger.info("[Prescribed] No hints to emit (no blocked targets or no support tags)");
|
|
2032
|
+
}
|
|
1755
2033
|
if (emitted.length === 0) {
|
|
1756
|
-
logger.
|
|
2034
|
+
logger.info(
|
|
2035
|
+
"[Prescribed] 0 cards emitted (all targets blocked, authored/discovered support candidates exhausted)" + (hints ? " \u2014 boost hints emitted but may not survive filters" : "")
|
|
2036
|
+
);
|
|
1757
2037
|
await this.putStrategyState(nextState).catch((e) => {
|
|
1758
2038
|
logger.debug(`[Prescribed] Failed to persist empty-state update: ${e}`);
|
|
1759
2039
|
});
|
|
@@ -1786,7 +2066,7 @@ var init_prescribed = __esm({
|
|
|
1786
2066
|
logger.debug(`[Prescribed] Failed to persist prescribed progress: ${e}`);
|
|
1787
2067
|
});
|
|
1788
2068
|
logger.info(
|
|
1789
|
-
`[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)`
|
|
2069
|
+
`[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)`
|
|
1790
2070
|
);
|
|
1791
2071
|
return hints ? { cards: finalCards, hints } : { cards: finalCards };
|
|
1792
2072
|
}
|
|
@@ -1816,9 +2096,15 @@ var init_prescribed = __esm({
|
|
|
1816
2096
|
const groupsRaw = Array.isArray(parsed.groups) ? parsed.groups : [];
|
|
1817
2097
|
const groups = groupsRaw.map((raw, i) => ({
|
|
1818
2098
|
id: typeof raw.id === "string" && raw.id.trim().length > 0 ? raw.id : `group-${i + 1}`,
|
|
1819
|
-
targetCardIds: dedupe(
|
|
1820
|
-
|
|
1821
|
-
|
|
2099
|
+
targetCardIds: dedupe(
|
|
2100
|
+
Array.isArray(raw.targetCardIds) ? raw.targetCardIds.filter((v) => typeof v === "string") : []
|
|
2101
|
+
),
|
|
2102
|
+
supportCardIds: dedupe(
|
|
2103
|
+
Array.isArray(raw.supportCardIds) ? raw.supportCardIds.filter((v) => typeof v === "string") : []
|
|
2104
|
+
),
|
|
2105
|
+
supportTagPatterns: dedupe(
|
|
2106
|
+
Array.isArray(raw.supportTagPatterns) ? raw.supportTagPatterns.filter((v) => typeof v === "string") : []
|
|
2107
|
+
),
|
|
1822
2108
|
freshnessWindowSessions: typeof raw.freshnessWindowSessions === "number" ? raw.freshnessWindowSessions : DEFAULT_FRESHNESS_WINDOW,
|
|
1823
2109
|
maxDirectTargetsPerRun: typeof raw.maxDirectTargetsPerRun === "number" ? raw.maxDirectTargetsPerRun : DEFAULT_MAX_DIRECT_PER_RUN,
|
|
1824
2110
|
maxSupportCardsPerRun: typeof raw.maxSupportCardsPerRun === "number" ? raw.maxSupportCardsPerRun : DEFAULT_MAX_SUPPORT_PER_RUN,
|
|
@@ -1835,7 +2121,7 @@ var init_prescribed = __esm({
|
|
|
1835
2121
|
}
|
|
1836
2122
|
async loadHierarchyConfigs() {
|
|
1837
2123
|
try {
|
|
1838
|
-
const strategies = await this.course.
|
|
2124
|
+
const strategies = await this.course.getAllNavigationStrategies();
|
|
1839
2125
|
return strategies.filter((s) => s.implementingClass === "hierarchyDefinition").map((s) => {
|
|
1840
2126
|
try {
|
|
1841
2127
|
const parsed = JSON.parse(s.serializedData);
|
|
@@ -1858,6 +2144,7 @@ var init_prescribed = __esm({
|
|
|
1858
2144
|
activeIds,
|
|
1859
2145
|
seenIds,
|
|
1860
2146
|
tagsByCard,
|
|
2147
|
+
cardsByTag,
|
|
1861
2148
|
hierarchyConfigs,
|
|
1862
2149
|
userTagElo,
|
|
1863
2150
|
userGlobalElo
|
|
@@ -1921,6 +2208,22 @@ var init_prescribed = __esm({
|
|
|
1921
2208
|
[...supportTags]
|
|
1922
2209
|
)
|
|
1923
2210
|
]).filter((id) => !activeIds.has(id) && !seenIds.has(id));
|
|
2211
|
+
const discoveredSupportCandidates = blockedTargets.length > 0 && supportTags.size > 0 && supportCandidates.length === 0 ? this.findDiscoveredSupportCards({
|
|
2212
|
+
supportTags: [...supportTags],
|
|
2213
|
+
cardsByTag,
|
|
2214
|
+
activeIds,
|
|
2215
|
+
seenIds,
|
|
2216
|
+
excludedIds: /* @__PURE__ */ new Set([
|
|
2217
|
+
...group.targetCardIds,
|
|
2218
|
+
...group.supportCardIds ?? []
|
|
2219
|
+
]),
|
|
2220
|
+
limit: group.maxSupportCardsPerRun ?? DEFAULT_MAX_SUPPORT_PER_RUN
|
|
2221
|
+
}) : [];
|
|
2222
|
+
if (blockedTargets.length > 0 && supportTags.size > 0 && discoveredSupportCandidates.length === 0) {
|
|
2223
|
+
logger.info(
|
|
2224
|
+
`[Prescribed] Group '${group.id}' discovered 0 broader support candidates (blocked=${blockedTargets.length}; authoredSupport=${supportCandidates.length})`
|
|
2225
|
+
);
|
|
2226
|
+
}
|
|
1924
2227
|
const sessionsSinceSurfaced = priorState?.sessionsSinceSurfaced ?? 0;
|
|
1925
2228
|
const freshnessWindow = group.freshnessWindowSessions ?? DEFAULT_FRESHNESS_WINDOW;
|
|
1926
2229
|
const staleSessions = Math.max(0, sessionsSinceSurfaced - freshnessWindow);
|
|
@@ -1934,6 +2237,7 @@ var init_prescribed = __esm({
|
|
|
1934
2237
|
surfaceableTargets,
|
|
1935
2238
|
targetTags,
|
|
1936
2239
|
supportCandidates,
|
|
2240
|
+
discoveredSupportCandidates,
|
|
1937
2241
|
supportTags: [...supportTags],
|
|
1938
2242
|
pressureMultiplier,
|
|
1939
2243
|
supportMultiplier,
|
|
@@ -2004,6 +2308,33 @@ var init_prescribed = __esm({
|
|
|
2004
2308
|
}
|
|
2005
2309
|
return cards;
|
|
2006
2310
|
}
|
|
2311
|
+
buildDiscoveredSupportCards(runtime, courseId, emittedIds) {
|
|
2312
|
+
if (runtime.blockedTargets.length === 0 || runtime.discoveredSupportCandidates.length === 0) {
|
|
2313
|
+
return [];
|
|
2314
|
+
}
|
|
2315
|
+
const maxSupport = runtime.group.maxSupportCardsPerRun ?? DEFAULT_MAX_SUPPORT_PER_RUN;
|
|
2316
|
+
const supportIds = runtime.discoveredSupportCandidates.filter((id) => !emittedIds.has(id)).slice(0, maxSupport);
|
|
2317
|
+
const cards = [];
|
|
2318
|
+
for (const cardId of supportIds) {
|
|
2319
|
+
emittedIds.add(cardId);
|
|
2320
|
+
cards.push({
|
|
2321
|
+
cardId,
|
|
2322
|
+
courseId,
|
|
2323
|
+
score: DISCOVERED_SUPPORT_SCORE * runtime.supportMultiplier,
|
|
2324
|
+
provenance: [
|
|
2325
|
+
{
|
|
2326
|
+
strategy: "prescribed",
|
|
2327
|
+
strategyName: this.strategyName || this.name,
|
|
2328
|
+
strategyId: this.strategyId || "NAVIGATION_STRATEGY-prescribed",
|
|
2329
|
+
action: "generated",
|
|
2330
|
+
score: DISCOVERED_SUPPORT_SCORE * runtime.supportMultiplier,
|
|
2331
|
+
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}`
|
|
2332
|
+
}
|
|
2333
|
+
]
|
|
2334
|
+
});
|
|
2335
|
+
}
|
|
2336
|
+
return cards;
|
|
2337
|
+
}
|
|
2007
2338
|
findSupportCardsByTags(group, tagsByCard, supportTags) {
|
|
2008
2339
|
if (supportTags.length === 0) {
|
|
2009
2340
|
return [];
|
|
@@ -2026,6 +2357,40 @@ var init_prescribed = __esm({
|
|
|
2026
2357
|
}
|
|
2027
2358
|
return [...candidates];
|
|
2028
2359
|
}
|
|
2360
|
+
findDiscoveredSupportCards(args) {
|
|
2361
|
+
const { supportTags, cardsByTag, activeIds, seenIds, excludedIds, limit } = args;
|
|
2362
|
+
const byCardId = /* @__PURE__ */ new Map();
|
|
2363
|
+
for (const supportTag of supportTags) {
|
|
2364
|
+
const taggedCards = cardsByTag.get(supportTag) ?? [];
|
|
2365
|
+
for (const cardId of taggedCards) {
|
|
2366
|
+
if (activeIds.has(cardId) || seenIds.has(cardId) || excludedIds.has(cardId)) {
|
|
2367
|
+
continue;
|
|
2368
|
+
}
|
|
2369
|
+
const existing = byCardId.get(cardId);
|
|
2370
|
+
if (existing) {
|
|
2371
|
+
existing.matches += 1;
|
|
2372
|
+
} else {
|
|
2373
|
+
byCardId.set(cardId, { cardId, matches: 1 });
|
|
2374
|
+
}
|
|
2375
|
+
}
|
|
2376
|
+
}
|
|
2377
|
+
const candidates = [...byCardId.values()].sort((a, b) => b.matches - a.matches || a.cardId.localeCompare(b.cardId));
|
|
2378
|
+
const usedStems = /* @__PURE__ */ new Set();
|
|
2379
|
+
const diverse = [];
|
|
2380
|
+
const deferred = [];
|
|
2381
|
+
for (const entry of candidates) {
|
|
2382
|
+
const stem = extractWordStem(entry.cardId);
|
|
2383
|
+
if (!usedStems.has(stem)) {
|
|
2384
|
+
usedStems.add(stem);
|
|
2385
|
+
diverse.push(entry);
|
|
2386
|
+
} else {
|
|
2387
|
+
deferred.push(entry);
|
|
2388
|
+
}
|
|
2389
|
+
}
|
|
2390
|
+
shuffleInPlace(diverse);
|
|
2391
|
+
shuffleInPlace(deferred);
|
|
2392
|
+
return [...diverse, ...deferred].slice(0, limit).map((entry) => entry.cardId);
|
|
2393
|
+
}
|
|
2029
2394
|
resolveBlockedSupportTags(targetTags, hierarchyConfigs, userTagElo, userGlobalElo, hierarchyWalkEnabled, maxDepth) {
|
|
2030
2395
|
const supportTags = /* @__PURE__ */ new Set();
|
|
2031
2396
|
let blocked = false;
|
|
@@ -2071,7 +2436,6 @@ var init_prescribed = __esm({
|
|
|
2071
2436
|
}
|
|
2072
2437
|
collectSupportTagsRecursive(tag, hierarchyConfigs, userTagElo, userGlobalElo, depth, visited, out) {
|
|
2073
2438
|
if (depth < 0 || visited.has(tag)) return;
|
|
2074
|
-
if (this.isHardGatedTag(tag)) return;
|
|
2075
2439
|
visited.add(tag);
|
|
2076
2440
|
let walkedFurther = false;
|
|
2077
2441
|
for (const hierarchy of hierarchyConfigs) {
|
|
@@ -2099,9 +2463,6 @@ var init_prescribed = __esm({
|
|
|
2099
2463
|
out.add(tag);
|
|
2100
2464
|
}
|
|
2101
2465
|
}
|
|
2102
|
-
isHardGatedTag(tag) {
|
|
2103
|
-
return LOCKED_TAG_PREFIXES.some((prefix) => tag.startsWith(prefix)) && tag.startsWith(LESSON_GATE_PENALTY_TAG_HINT);
|
|
2104
|
-
}
|
|
2105
2466
|
isPrerequisiteMet(prereq, userTagElo, userGlobalElo) {
|
|
2106
2467
|
if (!userTagElo) return false;
|
|
2107
2468
|
const minCount = prereq.masteryThreshold?.minCount ?? DEFAULT_MIN_COUNT;
|
|
@@ -3893,6 +4254,32 @@ var init_Pipeline = __esm({
|
|
|
3893
4254
|
cards = await this.hydrateTags(cards);
|
|
3894
4255
|
const tHydrate = performance.now();
|
|
3895
4256
|
const allCardsBeforeFiltering = [...cards];
|
|
4257
|
+
const pendingHints = this._ephemeralHints;
|
|
4258
|
+
if (pendingHints?.requireCards?.length) {
|
|
4259
|
+
const poolIds = new Set(allCardsBeforeFiltering.map((c) => c.cardId));
|
|
4260
|
+
const missingIds = pendingHints.requireCards.filter(
|
|
4261
|
+
(p) => !p.includes("*") && !poolIds.has(p)
|
|
4262
|
+
);
|
|
4263
|
+
if (missingIds.length > 0) {
|
|
4264
|
+
const fetchedTags = await this.course.getAppliedTagsBatch(missingIds);
|
|
4265
|
+
const courseId = this.course.getCourseID();
|
|
4266
|
+
for (const cardId of missingIds) {
|
|
4267
|
+
allCardsBeforeFiltering.push({
|
|
4268
|
+
cardId,
|
|
4269
|
+
courseId,
|
|
4270
|
+
score: 1,
|
|
4271
|
+
tags: fetchedTags.get(cardId) ?? [],
|
|
4272
|
+
provenance: []
|
|
4273
|
+
});
|
|
4274
|
+
}
|
|
4275
|
+
logger.info(
|
|
4276
|
+
`[Pipeline] Pre-fetched ${missingIds.length} required card(s) into pool: ${missingIds.join(", ")}`
|
|
4277
|
+
);
|
|
4278
|
+
}
|
|
4279
|
+
}
|
|
4280
|
+
const prescribedIds = new Set(
|
|
4281
|
+
cards.filter((c) => c.provenance.some((p) => p.strategy === "prescribed")).map((c) => c.cardId)
|
|
4282
|
+
);
|
|
3896
4283
|
const filterImpacts = [];
|
|
3897
4284
|
for (const filter of this.filters) {
|
|
3898
4285
|
const beforeCount = cards.length;
|
|
@@ -3907,6 +4294,17 @@ var init_Pipeline = __esm({
|
|
|
3907
4294
|
else passed++;
|
|
3908
4295
|
}
|
|
3909
4296
|
filterImpacts.push({ name: filter.name, boosted, penalized, passed, removed });
|
|
4297
|
+
if (prescribedIds.size > 0) {
|
|
4298
|
+
const survivingIds = new Set(cards.map((c) => c.cardId));
|
|
4299
|
+
const killedPrescribed = [...prescribedIds].filter((id) => !survivingIds.has(id));
|
|
4300
|
+
const zeroedPrescribed = cards.filter((c) => prescribedIds.has(c.cardId) && c.score === 0).map((c) => c.cardId);
|
|
4301
|
+
if (killedPrescribed.length > 0 || zeroedPrescribed.length > 0) {
|
|
4302
|
+
logger.info(
|
|
4303
|
+
`[Pipeline] Filter '${filter.name}' impact on prescribed cards: ` + (killedPrescribed.length > 0 ? `removed=[${killedPrescribed.join(", ")}] ` : "") + (zeroedPrescribed.length > 0 ? `zeroed=[${zeroedPrescribed.join(", ")}]` : "")
|
|
4304
|
+
);
|
|
4305
|
+
killedPrescribed.forEach((id) => prescribedIds.delete(id));
|
|
4306
|
+
}
|
|
4307
|
+
}
|
|
3910
4308
|
logger.debug(`[Pipeline] Filter '${filter.name}': ${beforeScores.size} \u2192 ${cards.length} cards (\u2191${boosted} \u2193${penalized} =${passed})`);
|
|
3911
4309
|
}
|
|
3912
4310
|
cards = cards.filter((c) => c.score > 0);
|
|
@@ -3943,7 +4341,8 @@ var init_Pipeline = __esm({
|
|
|
3943
4341
|
filterImpacts,
|
|
3944
4342
|
cards,
|
|
3945
4343
|
result,
|
|
3946
|
-
context.userElo
|
|
4344
|
+
context.userElo,
|
|
4345
|
+
hints ?? void 0
|
|
3947
4346
|
);
|
|
3948
4347
|
captureRun(report);
|
|
3949
4348
|
} catch (e) {
|
|
@@ -4053,13 +4452,27 @@ var init_Pipeline = __esm({
|
|
|
4053
4452
|
}
|
|
4054
4453
|
}
|
|
4055
4454
|
const cardIds = new Set(cards.map((c) => c.cardId));
|
|
4455
|
+
const cardMap = new Map(cards.map((c) => [c.cardId, c]));
|
|
4056
4456
|
const hintLabel = hints._label ? `Replan Hint (${hints._label})` : "Replan Hint";
|
|
4057
|
-
const
|
|
4058
|
-
|
|
4059
|
-
|
|
4457
|
+
const applyRequirement = (card, reason) => {
|
|
4458
|
+
const mandatoryScore = Number.POSITIVE_INFINITY;
|
|
4459
|
+
const existing = cardMap.get(card.cardId);
|
|
4460
|
+
if (existing) {
|
|
4461
|
+
if (existing.score < mandatoryScore) {
|
|
4462
|
+
existing.score = mandatoryScore;
|
|
4463
|
+
existing.provenance.push({
|
|
4464
|
+
strategy: "ephemeralHint",
|
|
4465
|
+
strategyId: "ephemeral-hint",
|
|
4466
|
+
strategyName: hintLabel,
|
|
4467
|
+
action: "boosted",
|
|
4468
|
+
score: mandatoryScore,
|
|
4469
|
+
reason: `${reason} (upgrade to mandatory score)`
|
|
4470
|
+
});
|
|
4471
|
+
}
|
|
4472
|
+
} else {
|
|
4060
4473
|
cards.push({
|
|
4061
4474
|
...card,
|
|
4062
|
-
score:
|
|
4475
|
+
score: mandatoryScore,
|
|
4063
4476
|
provenance: [
|
|
4064
4477
|
...card.provenance,
|
|
4065
4478
|
{
|
|
@@ -4067,25 +4480,41 @@ var init_Pipeline = __esm({
|
|
|
4067
4480
|
strategyId: "ephemeral-hint",
|
|
4068
4481
|
strategyName: hintLabel,
|
|
4069
4482
|
action: "boosted",
|
|
4070
|
-
score:
|
|
4483
|
+
score: mandatoryScore,
|
|
4071
4484
|
reason
|
|
4072
4485
|
}
|
|
4073
4486
|
]
|
|
4074
4487
|
});
|
|
4075
4488
|
cardIds.add(card.cardId);
|
|
4489
|
+
cardMap.set(card.cardId, cards[cards.length - 1]);
|
|
4076
4490
|
}
|
|
4077
4491
|
};
|
|
4078
4492
|
if (hints.requireCards?.length) {
|
|
4079
4493
|
for (const pattern of hints.requireCards) {
|
|
4494
|
+
for (const cardId of cardIds) {
|
|
4495
|
+
if (globMatch(cardId, pattern)) {
|
|
4496
|
+
applyRequirement(cardMap.get(cardId), `requireCard ${pattern}`);
|
|
4497
|
+
}
|
|
4498
|
+
}
|
|
4080
4499
|
for (const card of allCards) {
|
|
4081
|
-
if (globMatch(card.cardId, pattern))
|
|
4500
|
+
if (globMatch(card.cardId, pattern)) {
|
|
4501
|
+
applyRequirement(card, `requireCard ${pattern}`);
|
|
4502
|
+
}
|
|
4082
4503
|
}
|
|
4083
4504
|
}
|
|
4084
4505
|
}
|
|
4085
4506
|
if (hints.requireTags?.length) {
|
|
4086
4507
|
for (const pattern of hints.requireTags) {
|
|
4508
|
+
for (const cardId of cardIds) {
|
|
4509
|
+
const card = cardMap.get(cardId);
|
|
4510
|
+
if (cardMatchesTagPattern(card, pattern)) {
|
|
4511
|
+
applyRequirement(card, `requireTag ${pattern}`);
|
|
4512
|
+
}
|
|
4513
|
+
}
|
|
4087
4514
|
for (const card of allCards) {
|
|
4088
|
-
if (cardMatchesTagPattern(card, pattern))
|
|
4515
|
+
if (cardMatchesTagPattern(card, pattern)) {
|
|
4516
|
+
applyRequirement(card, `requireTag ${pattern}`);
|
|
4517
|
+
}
|
|
4089
4518
|
}
|
|
4090
4519
|
}
|
|
4091
4520
|
}
|