sigrank 0.16.0 → 0.16.1
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/README.md +117 -77
- package/adapters.mjs +493 -238
- package/badges.mjs +157 -56
- package/cascade.mjs +132 -70
- package/cli.mjs +1311 -893
- package/connect.mjs +2 -2
- package/index.mjs +78 -59
- package/keystore.mjs +64 -44
- package/narrate.mjs +34 -22
- package/package.json +1 -1
- package/preflight.mjs +101 -29
- package/sign.mjs +46 -27
- package/submit.mjs +137 -71
- package/tokenpull.mjs +325 -188
- package/tools.mjs +1702 -951
- package/tui.mjs +1860 -1081
package/badges.mjs
CHANGED
|
@@ -25,92 +25,190 @@
|
|
|
25
25
|
* @param {number|null} params.rank — current leaderboard rank (null if not ranked)
|
|
26
26
|
* @returns {object} { earned_this_week, in_progress, collection }
|
|
27
27
|
*/
|
|
28
|
-
export function computeBadges({
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
28
|
+
export function computeBadges({
|
|
29
|
+
pillars,
|
|
30
|
+
cascade: cas,
|
|
31
|
+
history = [],
|
|
32
|
+
isVerified = false,
|
|
33
|
+
rank = null,
|
|
34
|
+
}) {
|
|
35
|
+
const cr = Number(pillars?.cacheRead || 0);
|
|
36
|
+
const i = Number(pillars?.input || 0);
|
|
37
|
+
const leverage = cas?.leverage ?? (i > 0 ? cr / i : 0);
|
|
32
38
|
|
|
33
|
-
const earned = []
|
|
34
|
-
const inProgress = []
|
|
35
|
-
const collection = []
|
|
39
|
+
const earned = [];
|
|
40
|
+
const inProgress = [];
|
|
41
|
+
const collection = [];
|
|
36
42
|
|
|
37
43
|
// 1. First Spark — cacheRead > input (1× leverage)
|
|
38
44
|
if (leverage > 1) {
|
|
39
|
-
earned.push({
|
|
40
|
-
|
|
45
|
+
earned.push({
|
|
46
|
+
id: "first_spark",
|
|
47
|
+
label: "First Spark",
|
|
48
|
+
icon: "◈",
|
|
49
|
+
condition: "1× cache leverage",
|
|
50
|
+
date_earned: _latestDate(history),
|
|
51
|
+
});
|
|
52
|
+
collection.push("first_spark");
|
|
41
53
|
} else if (cr > 0) {
|
|
42
|
-
inProgress.push({
|
|
54
|
+
inProgress.push({
|
|
55
|
+
id: "first_spark",
|
|
56
|
+
label: "First Spark",
|
|
57
|
+
icon: "◈",
|
|
58
|
+
progress: Math.min(leverage, 1),
|
|
59
|
+
target: 1,
|
|
60
|
+
display: `${leverage.toFixed(1)}× / 1×`,
|
|
61
|
+
});
|
|
43
62
|
}
|
|
44
63
|
|
|
45
64
|
// 2. Cascade Engine — cacheRead > 10× input
|
|
46
65
|
if (leverage > 10) {
|
|
47
|
-
earned.push({
|
|
48
|
-
|
|
66
|
+
earned.push({
|
|
67
|
+
id: "cascade_engine",
|
|
68
|
+
label: "Cascade Engine",
|
|
69
|
+
icon: "⚡",
|
|
70
|
+
condition: "10× cache leverage",
|
|
71
|
+
date_earned: _latestDate(history),
|
|
72
|
+
});
|
|
73
|
+
collection.push("cascade_engine");
|
|
49
74
|
} else if (leverage > 1) {
|
|
50
|
-
inProgress.push({
|
|
75
|
+
inProgress.push({
|
|
76
|
+
id: "cascade_engine",
|
|
77
|
+
label: "Cascade Engine",
|
|
78
|
+
icon: "⚡",
|
|
79
|
+
progress: Math.min(leverage, 10),
|
|
80
|
+
target: 10,
|
|
81
|
+
display: `${leverage.toFixed(1)}× / 10×`,
|
|
82
|
+
});
|
|
51
83
|
}
|
|
52
84
|
|
|
53
85
|
// 3. Chain Reaction — cacheRead > 100× input
|
|
54
86
|
if (leverage > 100) {
|
|
55
|
-
earned.push({
|
|
56
|
-
|
|
87
|
+
earned.push({
|
|
88
|
+
id: "chain_reaction",
|
|
89
|
+
label: "Chain Reaction",
|
|
90
|
+
icon: "⚡",
|
|
91
|
+
condition: "100× cache leverage",
|
|
92
|
+
date_earned: _latestDate(history),
|
|
93
|
+
});
|
|
94
|
+
collection.push("chain_reaction");
|
|
57
95
|
} else if (leverage > 10) {
|
|
58
|
-
inProgress.push({
|
|
96
|
+
inProgress.push({
|
|
97
|
+
id: "chain_reaction",
|
|
98
|
+
label: "Chain Reaction",
|
|
99
|
+
icon: "⚡",
|
|
100
|
+
progress: Math.min(leverage, 100),
|
|
101
|
+
target: 100,
|
|
102
|
+
display: `${leverage.toFixed(1)}× / 100×`,
|
|
103
|
+
});
|
|
59
104
|
}
|
|
60
105
|
|
|
61
106
|
// 4. Foundation — 3 completed BUILD→MAINTAIN arcs
|
|
62
|
-
const arcs = _countBuildMaintainArcs(history)
|
|
107
|
+
const arcs = _countBuildMaintainArcs(history);
|
|
63
108
|
if (arcs >= 3) {
|
|
64
|
-
earned.push({
|
|
65
|
-
|
|
109
|
+
earned.push({
|
|
110
|
+
id: "foundation",
|
|
111
|
+
label: "Foundation",
|
|
112
|
+
icon: "🏗️",
|
|
113
|
+
condition: "3 BUILD→MAINTAIN arcs",
|
|
114
|
+
date_earned: _latestDate(history),
|
|
115
|
+
});
|
|
116
|
+
collection.push("foundation");
|
|
66
117
|
} else {
|
|
67
|
-
inProgress.push({
|
|
118
|
+
inProgress.push({
|
|
119
|
+
id: "foundation",
|
|
120
|
+
label: "Foundation",
|
|
121
|
+
icon: "🏗️",
|
|
122
|
+
progress: arcs,
|
|
123
|
+
target: 3,
|
|
124
|
+
display: `${arcs}/3 arcs`,
|
|
125
|
+
});
|
|
68
126
|
}
|
|
69
127
|
|
|
70
128
|
// 5. Phoenix — returned to MAINTAIN within 1 day of disruption, 5×
|
|
71
|
-
const phoenixCount = _countPhoenixReturns(history)
|
|
129
|
+
const phoenixCount = _countPhoenixReturns(history);
|
|
72
130
|
if (phoenixCount >= 5) {
|
|
73
|
-
earned.push({
|
|
74
|
-
|
|
131
|
+
earned.push({
|
|
132
|
+
id: "phoenix",
|
|
133
|
+
label: "Phoenix",
|
|
134
|
+
icon: "🔥",
|
|
135
|
+
condition: "5× MAINTAIN recovery within 1 day",
|
|
136
|
+
date_earned: _latestDate(history),
|
|
137
|
+
});
|
|
138
|
+
collection.push("phoenix");
|
|
75
139
|
} else {
|
|
76
|
-
inProgress.push({
|
|
140
|
+
inProgress.push({
|
|
141
|
+
id: "phoenix",
|
|
142
|
+
label: "Phoenix",
|
|
143
|
+
icon: "🔥",
|
|
144
|
+
progress: phoenixCount,
|
|
145
|
+
target: 5,
|
|
146
|
+
display: `${phoenixCount}/5 recoveries`,
|
|
147
|
+
});
|
|
77
148
|
}
|
|
78
149
|
|
|
79
150
|
// 6. Verified — submitted via signed agent
|
|
80
151
|
if (isVerified) {
|
|
81
|
-
earned.push({
|
|
82
|
-
|
|
152
|
+
earned.push({
|
|
153
|
+
id: "verified",
|
|
154
|
+
label: "Verified",
|
|
155
|
+
icon: "✓",
|
|
156
|
+
condition: "Submitted via signed agent",
|
|
157
|
+
date_earned: _latestDate(history),
|
|
158
|
+
});
|
|
159
|
+
collection.push("verified");
|
|
83
160
|
}
|
|
84
161
|
// No in-progress for verified — it's binary
|
|
85
162
|
|
|
86
163
|
// 7. Cascade Streak 🔥 — 7 consecutive days MAINTAIN (current badge)
|
|
87
|
-
const streak = _countMaintainStreak(history)
|
|
164
|
+
const streak = _countMaintainStreak(history);
|
|
88
165
|
if (streak >= 7) {
|
|
89
|
-
earned.push({
|
|
90
|
-
|
|
166
|
+
earned.push({
|
|
167
|
+
id: "cascade_streak",
|
|
168
|
+
label: "Cascade Streak",
|
|
169
|
+
icon: "🔥",
|
|
170
|
+
condition: "7 consecutive days MAINTAIN",
|
|
171
|
+
date_earned: _latestDate(history),
|
|
172
|
+
current: true,
|
|
173
|
+
});
|
|
174
|
+
collection.push("cascade_streak");
|
|
91
175
|
} else {
|
|
92
|
-
inProgress.push({
|
|
176
|
+
inProgress.push({
|
|
177
|
+
id: "cascade_streak",
|
|
178
|
+
label: "Cascade Streak",
|
|
179
|
+
icon: "🔥",
|
|
180
|
+
progress: streak,
|
|
181
|
+
target: 7,
|
|
182
|
+
display: `${streak}/7 days`,
|
|
183
|
+
});
|
|
93
184
|
}
|
|
94
185
|
|
|
95
186
|
// 8. Top 10 🏆 — currently in top 10 (server-side, but we include it if rank is provided)
|
|
96
187
|
if (rank !== null && rank <= 10) {
|
|
97
|
-
earned.push({
|
|
98
|
-
|
|
188
|
+
earned.push({
|
|
189
|
+
id: "top_10",
|
|
190
|
+
label: "Top 10",
|
|
191
|
+
icon: "🏆",
|
|
192
|
+
condition: `Currently #${rank}`,
|
|
193
|
+
date_earned: _latestDate(history),
|
|
194
|
+
current: true,
|
|
195
|
+
});
|
|
196
|
+
collection.push("top_10");
|
|
99
197
|
}
|
|
100
198
|
// No in-progress for top 10 — it's a live position, not a progress badge
|
|
101
199
|
|
|
102
200
|
return {
|
|
103
|
-
earned_this_week: earned.map(b => b.id),
|
|
201
|
+
earned_this_week: earned.map((b) => b.id),
|
|
104
202
|
in_progress: inProgress,
|
|
105
203
|
collection,
|
|
106
|
-
}
|
|
204
|
+
};
|
|
107
205
|
}
|
|
108
206
|
|
|
109
207
|
// ── Helpers ─────────────────────────────────────────────────────────────────
|
|
110
208
|
|
|
111
209
|
function _latestDate(history) {
|
|
112
|
-
if (!history || history.length === 0) return null
|
|
113
|
-
return history[history.length - 1]?.date || null
|
|
210
|
+
if (!history || history.length === 0) return null;
|
|
211
|
+
return history[history.length - 1]?.date || null;
|
|
114
212
|
}
|
|
115
213
|
|
|
116
214
|
/**
|
|
@@ -118,46 +216,49 @@ function _latestDate(history) {
|
|
|
118
216
|
* An arc = a BUILD phase followed by a MAINTAIN phase.
|
|
119
217
|
*/
|
|
120
218
|
function _countBuildMaintainArcs(history) {
|
|
121
|
-
if (!history || history.length < 2) return 0
|
|
122
|
-
let arcs = 0
|
|
123
|
-
let wasBuild = false
|
|
219
|
+
if (!history || history.length < 2) return 0;
|
|
220
|
+
let arcs = 0;
|
|
221
|
+
let wasBuild = false;
|
|
124
222
|
for (const h of history) {
|
|
125
|
-
if (h.mode ===
|
|
126
|
-
wasBuild = true
|
|
127
|
-
} else if (h.mode ===
|
|
128
|
-
arcs
|
|
129
|
-
wasBuild = false
|
|
223
|
+
if (h.mode === "BUILD") {
|
|
224
|
+
wasBuild = true;
|
|
225
|
+
} else if (h.mode === "MAINTAIN" && wasBuild) {
|
|
226
|
+
arcs++;
|
|
227
|
+
wasBuild = false;
|
|
130
228
|
}
|
|
131
229
|
}
|
|
132
|
-
return arcs
|
|
230
|
+
return arcs;
|
|
133
231
|
}
|
|
134
232
|
|
|
135
233
|
/**
|
|
136
234
|
* Count Phoenix returns — MAINTAIN within 1 day of a non-MAINTAIN day.
|
|
137
235
|
*/
|
|
138
236
|
function _countPhoenixReturns(history) {
|
|
139
|
-
if (!history || history.length < 2) return 0
|
|
140
|
-
let count = 0
|
|
237
|
+
if (!history || history.length < 2) return 0;
|
|
238
|
+
let count = 0;
|
|
141
239
|
for (let idx = 1; idx < history.length; idx++) {
|
|
142
|
-
if (
|
|
143
|
-
|
|
240
|
+
if (
|
|
241
|
+
history[idx].mode === "MAINTAIN" &&
|
|
242
|
+
history[idx - 1].mode !== "MAINTAIN"
|
|
243
|
+
) {
|
|
244
|
+
count++;
|
|
144
245
|
}
|
|
145
246
|
}
|
|
146
|
-
return count
|
|
247
|
+
return count;
|
|
147
248
|
}
|
|
148
249
|
|
|
149
250
|
/**
|
|
150
251
|
* Count current consecutive MAINTAIN days (ending at the last entry).
|
|
151
252
|
*/
|
|
152
253
|
function _countMaintainStreak(history) {
|
|
153
|
-
if (!history || history.length === 0) return 0
|
|
154
|
-
let streak = 0
|
|
254
|
+
if (!history || history.length === 0) return 0;
|
|
255
|
+
let streak = 0;
|
|
155
256
|
for (let idx = history.length - 1; idx >= 0; idx--) {
|
|
156
|
-
if (history[idx].mode ===
|
|
157
|
-
streak
|
|
257
|
+
if (history[idx].mode === "MAINTAIN") {
|
|
258
|
+
streak++;
|
|
158
259
|
} else {
|
|
159
|
-
break
|
|
260
|
+
break;
|
|
160
261
|
}
|
|
161
262
|
}
|
|
162
|
-
return streak
|
|
263
|
+
return streak;
|
|
163
264
|
}
|
package/cascade.mjs
CHANGED
|
@@ -13,40 +13,45 @@
|
|
|
13
13
|
* Callers that require a fully-formed result should check `warnings.length === 0`.
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
-
export const round = (n, d) =>
|
|
16
|
+
export const round = (n, d) =>
|
|
17
|
+
Number.isFinite(n) ? Number(n.toFixed(d)) : null;
|
|
17
18
|
|
|
18
19
|
/** The four raw token pillars → the cascade. */
|
|
19
20
|
export function cascade({ input, output, cacheCreate, cacheRead }) {
|
|
20
|
-
const i = Number(input),
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
const i = Number(input),
|
|
22
|
+
o = Number(output),
|
|
23
|
+
cw = Number(cacheCreate),
|
|
24
|
+
cr = Number(cacheRead);
|
|
25
|
+
const total = i + o + cw + cr;
|
|
26
|
+
const warnings = [];
|
|
23
27
|
|
|
24
28
|
// SNR: undefined when both i and o are 0 (empty session)
|
|
25
|
-
const snrDenom = i + o
|
|
26
|
-
const snr = snrDenom > 0 ? o / snrDenom : null
|
|
27
|
-
if (snr === null) warnings.push(
|
|
29
|
+
const snrDenom = i + o;
|
|
30
|
+
const snr = snrDenom > 0 ? o / snrDenom : null;
|
|
31
|
+
if (snr === null) warnings.push("snr_undefined: input+output=0");
|
|
28
32
|
|
|
29
33
|
// velocity: undefined when i=0 (no fresh input — pure cache-only session)
|
|
30
|
-
const velocity = i > 0 ? o / i : null
|
|
31
|
-
if (velocity === null) warnings.push(
|
|
34
|
+
const velocity = i > 0 ? o / i : null;
|
|
35
|
+
if (velocity === null) warnings.push("velocity_undefined: input=0");
|
|
32
36
|
|
|
33
37
|
// leverage: undefined when i=0
|
|
34
|
-
const leverage = i > 0 ? cr / i : null
|
|
35
|
-
if (leverage === null) warnings.push(
|
|
38
|
+
const leverage = i > 0 ? cr / i : null;
|
|
39
|
+
if (leverage === null) warnings.push("leverage_undefined: input=0");
|
|
36
40
|
|
|
37
41
|
// Υ = leverage × velocity = (Cr·O)/I² — null when either component is null
|
|
38
|
-
const yield_ =
|
|
39
|
-
|
|
40
|
-
|
|
42
|
+
const yield_ =
|
|
43
|
+
leverage !== null && velocity !== null ? leverage * velocity : null;
|
|
44
|
+
if (yield_ === null && !warnings.some((w) => w.startsWith("yield")))
|
|
45
|
+
warnings.push("yield_undefined: requires input>0");
|
|
41
46
|
|
|
42
47
|
// dev10x = log10(Cr/I) — collapses when cw=0 (no cache commits) or i=0 or cr=0.
|
|
43
48
|
// narrate.mjs already has a nonCompounding branch for this case; we return null
|
|
44
49
|
// explicitly so the branch triggers correctly (vs -Infinity on log10(0)).
|
|
45
|
-
let dev10x = null
|
|
50
|
+
let dev10x = null;
|
|
46
51
|
if (i > 0 && o > 0 && cw > 0 && cr > 0) {
|
|
47
|
-
dev10x = Math.log10((o / i) * (cw / o) * (cr / cw)) // = log10(Cr/I)
|
|
52
|
+
dev10x = Math.log10((o / i) * (cw / o) * (cr / cw)); // = log10(Cr/I)
|
|
48
53
|
} else {
|
|
49
|
-
warnings.push(
|
|
54
|
+
warnings.push("dev10x_undefined: requires all four pillars > 0");
|
|
50
55
|
}
|
|
51
56
|
|
|
52
57
|
const result = {
|
|
@@ -58,22 +63,22 @@ export function cascade({ input, output, cacheCreate, cacheRead }) {
|
|
|
58
63
|
dev10x: round(dev10x, 2),
|
|
59
64
|
class: classify(yield_, dev10x),
|
|
60
65
|
mode: detectMode({ input: i, output: o, cacheCreate: cw, cacheRead: cr }),
|
|
61
|
-
}
|
|
62
|
-
if (warnings.length > 0) result.warnings = warnings
|
|
63
|
-
return result
|
|
66
|
+
};
|
|
67
|
+
if (warnings.length > 0) result.warnings = warnings;
|
|
68
|
+
return result;
|
|
64
69
|
}
|
|
65
70
|
|
|
66
71
|
/** MVP class tiering from Υ + 10xDEV (canon assigns from cascade SNR + 10xDEV; this
|
|
67
72
|
* is the open-MVP approximation — proprietary threshold cuts stay server-side). */
|
|
68
73
|
export function classify(yieldVal, dev10x) {
|
|
69
|
-
if (yieldVal >= 1000 || dev10x >= 3) return
|
|
70
|
-
if (dev10x >= 1.45) return
|
|
71
|
-
if (dev10x >= 1.35) return
|
|
72
|
-
if (dev10x >= 1.2) return
|
|
73
|
-
if (dev10x >= 1.0) return
|
|
74
|
-
if (dev10x >= 0) return
|
|
75
|
-
if (dev10x >= -0.3) return
|
|
76
|
-
return
|
|
74
|
+
if (yieldVal >= 1000 || dev10x >= 3) return "TRANSMITTER";
|
|
75
|
+
if (dev10x >= 1.45) return "ARCH+";
|
|
76
|
+
if (dev10x >= 1.35) return "ARCH";
|
|
77
|
+
if (dev10x >= 1.2) return "POWER";
|
|
78
|
+
if (dev10x >= 1.0) return "BASE";
|
|
79
|
+
if (dev10x >= 0) return "SEEKER";
|
|
80
|
+
if (dev10x >= -0.3) return "REFINER";
|
|
81
|
+
return "IGNITER";
|
|
77
82
|
}
|
|
78
83
|
|
|
79
84
|
/**
|
|
@@ -94,31 +99,39 @@ export function classify(yieldVal, dev10x) {
|
|
|
94
99
|
* Returns { mode, confidence }.
|
|
95
100
|
*/
|
|
96
101
|
export function detectMode({ input, output, cacheCreate, cacheRead }) {
|
|
97
|
-
const i = Number(input),
|
|
98
|
-
|
|
102
|
+
const i = Number(input),
|
|
103
|
+
o = Number(output),
|
|
104
|
+
cw = Number(cacheCreate),
|
|
105
|
+
cr = Number(cacheRead);
|
|
106
|
+
const total = i + o + cw + cr;
|
|
99
107
|
|
|
100
108
|
// IDLE: near-zero tokens
|
|
101
|
-
if (total < 1000) return { mode:
|
|
109
|
+
if (total < 1000) return { mode: "IDLE", confidence: 1.0 };
|
|
102
110
|
|
|
103
|
-
const leverage
|
|
104
|
-
const velocity
|
|
105
|
-
const input_share = total > 0 ? i / total : 0
|
|
111
|
+
const leverage = i > 0 ? cr / i : 0;
|
|
112
|
+
const velocity = i > 0 ? o / i : 0;
|
|
113
|
+
const input_share = total > 0 ? i / total : 0;
|
|
106
114
|
|
|
107
115
|
// MAINTAIN: high leverage + high velocity (the cascade is compounding)
|
|
108
|
-
if (leverage > 10 && velocity > 1)
|
|
109
|
-
|
|
116
|
+
if (leverage > 10 && velocity > 1)
|
|
117
|
+
return { mode: "MAINTAIN", confidence: 0.9 };
|
|
118
|
+
if (leverage > 3 && velocity > 0.5)
|
|
119
|
+
return { mode: "MAINTAIN", confidence: 0.7 };
|
|
110
120
|
|
|
111
121
|
// DEBUG: low velocity + high input share (reading, not producing)
|
|
112
|
-
if (velocity < 0.3 && input_share > 0.5)
|
|
122
|
+
if (velocity < 0.3 && input_share > 0.5)
|
|
123
|
+
return { mode: "DEBUG", confidence: 0.8 };
|
|
113
124
|
|
|
114
125
|
// EDIT: high input share + high velocity (fresh input but producing)
|
|
115
|
-
if (input_share > 0.4 && velocity > 0.5)
|
|
126
|
+
if (input_share > 0.4 && velocity > 0.5)
|
|
127
|
+
return { mode: "EDIT", confidence: 0.7 };
|
|
116
128
|
|
|
117
129
|
// DEBUG (secondary): high input share + low velocity
|
|
118
|
-
if (input_share > 0.4 && velocity < 0.5)
|
|
130
|
+
if (input_share > 0.4 && velocity < 0.5)
|
|
131
|
+
return { mode: "DEBUG", confidence: 0.6 };
|
|
119
132
|
|
|
120
133
|
// BUILD: fallback (high input, no cache reuse yet)
|
|
121
|
-
return { mode:
|
|
134
|
+
return { mode: "BUILD", confidence: 0.6 };
|
|
122
135
|
}
|
|
123
136
|
|
|
124
137
|
/**
|
|
@@ -131,7 +144,7 @@ export const MODE_EXPECTED_YIELD = {
|
|
|
131
144
|
DEBUG: 10,
|
|
132
145
|
MAINTAIN: 5000,
|
|
133
146
|
IDLE: 0,
|
|
134
|
-
}
|
|
147
|
+
};
|
|
135
148
|
|
|
136
149
|
/**
|
|
137
150
|
* qualityScore — actual yield relative to mode expectation.
|
|
@@ -139,9 +152,9 @@ export const MODE_EXPECTED_YIELD = {
|
|
|
139
152
|
* Returns a number 0+ (can exceed 1.0 if outperforming the expected yield).
|
|
140
153
|
*/
|
|
141
154
|
export function qualityScore(actualYield, mode) {
|
|
142
|
-
const expected = MODE_EXPECTED_YIELD[mode] ?? 1
|
|
143
|
-
if (expected === 0) return actualYield === 0 ? 1.0 : 0
|
|
144
|
-
return actualYield / expected
|
|
155
|
+
const expected = MODE_EXPECTED_YIELD[mode] ?? 1;
|
|
156
|
+
if (expected === 0) return actualYield === 0 ? 1.0 : 0;
|
|
157
|
+
return actualYield / expected;
|
|
145
158
|
}
|
|
146
159
|
|
|
147
160
|
/**
|
|
@@ -159,54 +172,103 @@ export function qualityScore(actualYield, mode) {
|
|
|
159
172
|
* something looks suspicious we surface it in `_parseWarnings` on the returned object.
|
|
160
173
|
*/
|
|
161
174
|
export function parsePillars(text) {
|
|
162
|
-
const t = String(text ||
|
|
163
|
-
const pw = [] // parse warnings to attach
|
|
175
|
+
const t = String(text || "").trim();
|
|
176
|
+
const pw = []; // parse warnings to attach
|
|
164
177
|
|
|
165
178
|
// ── JSON path ──────────────────────────────────────────────────────────────
|
|
166
179
|
try {
|
|
167
|
-
const j = JSON.parse(t)
|
|
168
|
-
if (j && typeof j ===
|
|
169
|
-
const g = (...keys) => {
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
const
|
|
180
|
+
const j = JSON.parse(t);
|
|
181
|
+
if (j && typeof j === "object" && !Array.isArray(j)) {
|
|
182
|
+
const g = (...keys) => {
|
|
183
|
+
for (const k of keys) if (j[k] != null) return j[k];
|
|
184
|
+
return null;
|
|
185
|
+
};
|
|
186
|
+
const input = g(
|
|
187
|
+
"input",
|
|
188
|
+
"tokens_input_fresh",
|
|
189
|
+
"inputTokens",
|
|
190
|
+
"input_tokens",
|
|
191
|
+
);
|
|
192
|
+
const output = g(
|
|
193
|
+
"output",
|
|
194
|
+
"tokens_output",
|
|
195
|
+
"outputTokens",
|
|
196
|
+
"output_tokens",
|
|
197
|
+
);
|
|
198
|
+
const cacheCreate = g(
|
|
199
|
+
"cacheCreate",
|
|
200
|
+
"tokens_cache_creation",
|
|
201
|
+
"cache_creation_tokens",
|
|
202
|
+
);
|
|
203
|
+
const cacheRead = g(
|
|
204
|
+
"cacheRead",
|
|
205
|
+
"tokens_cache_read",
|
|
206
|
+
"cache_read_tokens",
|
|
207
|
+
);
|
|
174
208
|
if ([input, output, cacheCreate, cacheRead].every((v) => v != null)) {
|
|
175
209
|
const pillars = {
|
|
176
210
|
input: Number(input),
|
|
177
211
|
output: Number(output),
|
|
178
212
|
cacheCreate: Number(cacheCreate),
|
|
179
213
|
cacheRead: Number(cacheRead),
|
|
180
|
-
}
|
|
181
|
-
if (
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
214
|
+
};
|
|
215
|
+
if (
|
|
216
|
+
[
|
|
217
|
+
pillars.input,
|
|
218
|
+
pillars.output,
|
|
219
|
+
pillars.cacheCreate,
|
|
220
|
+
pillars.cacheRead,
|
|
221
|
+
].some((v) => !Number.isFinite(v))
|
|
222
|
+
)
|
|
223
|
+
throw new Error(
|
|
224
|
+
"Non-numeric pillar value in JSON (got string or non-finite number).",
|
|
225
|
+
);
|
|
226
|
+
if (
|
|
227
|
+
[
|
|
228
|
+
pillars.input,
|
|
229
|
+
pillars.output,
|
|
230
|
+
pillars.cacheCreate,
|
|
231
|
+
pillars.cacheRead,
|
|
232
|
+
].some((v) => v < 0)
|
|
233
|
+
)
|
|
234
|
+
pw.push(
|
|
235
|
+
"negative_pillar: one or more pillars is negative — may be a data error",
|
|
236
|
+
);
|
|
237
|
+
if (pw.length > 0) pillars._parseWarnings = pw;
|
|
238
|
+
return pillars;
|
|
187
239
|
}
|
|
188
240
|
}
|
|
189
241
|
} catch (e) {
|
|
190
242
|
// JSON.parse syntax error — fall through to positional. Re-throw parse errors we raised ourselves.
|
|
191
|
-
if (e.message.startsWith(
|
|
243
|
+
if (e.message.startsWith("Non-numeric")) throw e;
|
|
192
244
|
}
|
|
193
245
|
|
|
194
246
|
// ── Positional path ────────────────────────────────────────────────────────
|
|
195
247
|
// Guard: if the text contains alphabetic words, the numeric extraction is unreliable.
|
|
196
248
|
// We still attempt it (don't throw away the data) but flag it for review.
|
|
197
249
|
if (/[a-zA-Z]/.test(t))
|
|
198
|
-
pw.push(
|
|
250
|
+
pw.push(
|
|
251
|
+
"positional_from_mixed_text: extracted numbers from text that contains alphabetic characters — verify these are the correct 4 pillars",
|
|
252
|
+
);
|
|
199
253
|
|
|
200
|
-
const nums = (t.match(/-?\d[\d,]*\.?\d*/g) || []).map((s) =>
|
|
254
|
+
const nums = (t.match(/-?\d[\d,]*\.?\d*/g) || []).map((s) =>
|
|
255
|
+
Number(s.replace(/,/g, "")),
|
|
256
|
+
);
|
|
201
257
|
if (nums.length >= 4) {
|
|
202
|
-
const [input, output, cacheCreate, cacheRead] = nums
|
|
258
|
+
const [input, output, cacheCreate, cacheRead] = nums;
|
|
203
259
|
if (nums.length > 4)
|
|
204
|
-
pw.push(
|
|
205
|
-
|
|
260
|
+
pw.push(
|
|
261
|
+
`positional_extra_numbers: found ${nums.length} numbers, using first 4 — inspect for positional order error`,
|
|
262
|
+
);
|
|
263
|
+
const pillars = { input, output, cacheCreate, cacheRead };
|
|
206
264
|
if ([input, output, cacheCreate, cacheRead].some((v) => v < 0))
|
|
207
|
-
pw.push(
|
|
208
|
-
|
|
209
|
-
|
|
265
|
+
pw.push(
|
|
266
|
+
"negative_pillar: one or more pillars is negative — may be a data error",
|
|
267
|
+
);
|
|
268
|
+
if (pw.length > 0) pillars._parseWarnings = pw;
|
|
269
|
+
return pillars;
|
|
210
270
|
}
|
|
211
|
-
throw new Error(
|
|
271
|
+
throw new Error(
|
|
272
|
+
"Could not parse 4 token pillars (input, output, cacheCreate, cacheRead) from the input.",
|
|
273
|
+
);
|
|
212
274
|
}
|