@prismatic-io/lux 0.0.2-preview.30 → 0.0.2-preview.33
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/lib/assertions/core/command-exits-zero.d.ts +1 -0
- package/lib/assertions/core/command-exits-zero.d.ts.map +1 -1
- package/lib/assertions/core/file-checks.d.ts +6 -0
- package/lib/assertions/core/file-checks.d.ts.map +1 -1
- package/lib/assertions/core/json-pointer-equals.d.ts +1 -0
- package/lib/assertions/core/json-pointer-equals.d.ts.map +1 -1
- package/lib/assertions/core/output-checks.d.ts +3 -0
- package/lib/assertions/core/output-checks.d.ts.map +1 -1
- package/lib/assertions/core/predicate.d.ts +1 -0
- package/lib/assertions/core/predicate.d.ts.map +1 -1
- package/lib/assertions/core/resource-checks.d.ts +6 -0
- package/lib/assertions/core/resource-checks.d.ts.map +1 -1
- package/lib/assertions/rubric/index.d.ts +2 -0
- package/lib/assertions/rubric/index.d.ts.map +1 -1
- package/lib/cli/output-schemas.d.ts +11 -5
- package/lib/cli/output-schemas.d.ts.map +1 -1
- package/lib/cli/output-schemas.js +5 -2
- package/lib/cli/output-schemas.js.map +1 -1
- package/lib/cli/program.d.ts.map +1 -1
- package/lib/cli/program.js +2 -1
- package/lib/cli/program.js.map +1 -1
- package/lib/cli/render/format.d.ts.map +1 -1
- package/lib/cli/render/format.js +1 -1
- package/lib/cli/render/format.js.map +1 -1
- package/lib/cli/render/reporter.d.ts.map +1 -1
- package/lib/cli/render/reporter.js +11 -4
- package/lib/cli/render/reporter.js.map +1 -1
- package/lib/core/assertion.d.ts +5 -2
- package/lib/core/assertion.d.ts.map +1 -1
- package/lib/core/assertion.js +3 -1
- package/lib/core/assertion.js.map +1 -1
- package/lib/core/case.d.ts +5 -1
- package/lib/core/case.d.ts.map +1 -1
- package/lib/core/case.js +1 -0
- package/lib/core/case.js.map +1 -1
- package/lib/core/experiment.d.ts +1 -0
- package/lib/core/experiment.d.ts.map +1 -1
- package/lib/core/experiment.js +5 -1
- package/lib/core/experiment.js.map +1 -1
- package/lib/core/rubric.d.ts +1 -0
- package/lib/core/rubric.d.ts.map +1 -1
- package/lib/core/run.d.ts +1 -0
- package/lib/core/run.d.ts.map +1 -1
- package/lib/orchestrator/compare.d.ts +6 -5
- package/lib/orchestrator/compare.d.ts.map +1 -1
- package/lib/orchestrator/compare.js +20 -11
- package/lib/orchestrator/compare.js.map +1 -1
- package/lib/orchestrator/config.d.ts +2 -0
- package/lib/orchestrator/config.d.ts.map +1 -1
- package/lib/orchestrator/experiment-contracts.d.ts +1 -0
- package/lib/orchestrator/experiment-contracts.d.ts.map +1 -1
- package/lib/orchestrator/experiment-effects.d.ts.map +1 -1
- package/lib/orchestrator/experiment-effects.js +4 -2
- package/lib/orchestrator/experiment-effects.js.map +1 -1
- package/lib/orchestrator/experiment-evaluation.d.ts.map +1 -1
- package/lib/orchestrator/experiment-evaluation.js +15 -2
- package/lib/orchestrator/experiment-evaluation.js.map +1 -1
- package/lib/orchestrator/experiment-report.d.ts +1 -0
- package/lib/orchestrator/experiment-report.d.ts.map +1 -1
- package/lib/orchestrator/experiment-runs.d.ts +2 -0
- package/lib/orchestrator/experiment-runs.d.ts.map +1 -1
- package/lib/orchestrator/experiment-selection.d.ts.map +1 -1
- package/lib/orchestrator/experiment-selection.js +99 -43
- package/lib/orchestrator/experiment-selection.js.map +1 -1
- package/lib/orchestrator/grader-experiment.d.ts +4 -2
- package/lib/orchestrator/grader-experiment.d.ts.map +1 -1
- package/lib/orchestrator/grading.d.ts +1 -0
- package/lib/orchestrator/grading.d.ts.map +1 -1
- package/lib/orchestrator/grading.js +34 -31
- package/lib/orchestrator/grading.js.map +1 -1
- package/lib/orchestrator/list-runs.d.ts.map +1 -1
- package/lib/orchestrator/list-runs.js +7 -7
- package/lib/orchestrator/list-runs.js.map +1 -1
- package/lib/orchestrator/paired-effects.d.ts.map +1 -1
- package/lib/orchestrator/paired-effects.js +7 -1
- package/lib/orchestrator/paired-effects.js.map +1 -1
- package/lib/orchestrator/regrade.d.ts +2 -1
- package/lib/orchestrator/regrade.d.ts.map +1 -1
- package/lib/orchestrator/suite-compare.d.ts.map +1 -1
- package/lib/orchestrator/suite-compare.js +18 -6
- package/lib/orchestrator/suite-compare.js.map +1 -1
- package/lib/orchestrator/suite-summary.d.ts +2 -0
- package/lib/orchestrator/suite-summary.d.ts.map +1 -1
- package/lib/orchestrator/suite-summary.js +6 -2
- package/lib/orchestrator/suite-summary.js.map +1 -1
- package/lib/previewer/server.d.ts.map +1 -1
- package/lib/previewer/server.js +2 -1
- package/lib/previewer/server.js.map +1 -1
- package/package.json +1 -1
- package/skills/lux-answerer/SKILL.md +1 -1
- package/src/cli/output-schemas.ts +5 -2
- package/src/cli/program.ts +3 -1
- package/src/cli/render/format.ts +1 -1
- package/src/cli/render/reporter.ts +10 -4
- package/src/core/assertion.ts +3 -1
- package/src/core/case.ts +5 -1
- package/src/core/experiment.ts +7 -1
- package/src/orchestrator/compare.ts +28 -12
- package/src/orchestrator/experiment-effects.ts +4 -2
- package/src/orchestrator/experiment-evaluation.ts +18 -2
- package/src/orchestrator/experiment-selection.ts +101 -18
- package/src/orchestrator/grading.ts +50 -28
- package/src/orchestrator/list-runs.ts +8 -8
- package/src/orchestrator/paired-effects.ts +6 -1
- package/src/orchestrator/suite-compare.ts +19 -5
- package/src/orchestrator/suite-summary.ts +7 -2
- package/src/previewer/server.ts +2 -0
- package/viewer/app.css +36 -0
- package/viewer/charts.js +16 -4
- package/viewer/detail.js +40 -16
- package/viewer/format.js +2 -2
|
@@ -93,7 +93,7 @@ const summarizeRun = async (
|
|
|
93
93
|
casePassed: grading?.casePassed ?? null,
|
|
94
94
|
caseScore: grading?.caseScore ?? null,
|
|
95
95
|
assertionPassed: passing,
|
|
96
|
-
assertionTotal: grading?.assertions.length ?? 0,
|
|
96
|
+
assertionTotal: grading?.assertions.filter((a) => a.passed !== null).length ?? 0,
|
|
97
97
|
...(readErrors.length > 0 ? { readErrors } : {}),
|
|
98
98
|
};
|
|
99
99
|
};
|
|
@@ -162,9 +162,7 @@ export const formatRunsTable = (runs: ListedRun[]): string => {
|
|
|
162
162
|
else if (r.casePassed === true) status = "PASS";
|
|
163
163
|
else if (r.casePassed === false) status = "FAIL";
|
|
164
164
|
const score =
|
|
165
|
-
r.caseScore !== null
|
|
166
|
-
? `${(r.caseScore * 100).toFixed(0)}% (${r.assertionPassed}/${r.assertionTotal})`
|
|
167
|
-
: "—";
|
|
165
|
+
r.caseScore !== null ? `${r.caseScore} (${r.assertionPassed}/${r.assertionTotal})` : "—";
|
|
168
166
|
const duration = r.durationMs !== null ? `${(r.durationMs / 1000).toFixed(1)}s` : "—";
|
|
169
167
|
const agents = r.subagentCount > 0 ? `1+${r.subagentCount}` : "1";
|
|
170
168
|
return [status, r.caseSlug, r.driverName, agents, r.ts, r.exitReason ?? "?", score, duration];
|
|
@@ -362,17 +360,19 @@ const appendGradingDetail = (lines: string[], detail: RunDetail): void => {
|
|
|
362
360
|
let status = "ungraded";
|
|
363
361
|
if (detail.grading.casePassed === true) status = "PASS";
|
|
364
362
|
else if (detail.grading.casePassed === false) status = "FAIL";
|
|
365
|
-
const score =
|
|
366
|
-
detail.grading.caseScore !== null ? `${(detail.grading.caseScore * 100).toFixed(0)}%` : "—";
|
|
363
|
+
const score = detail.grading.caseScore !== null ? String(detail.grading.caseScore) : "—";
|
|
367
364
|
lines.push(
|
|
368
|
-
`grading: ${status} (${score}, ${detail.grading.assertions.filter((a) => a.passed).length}/${detail.grading.assertions.length})`,
|
|
365
|
+
`grading: ${status} (${score}, ${detail.grading.assertions.filter((a) => a.passed).length}/${detail.grading.assertions.filter((a) => a.passed !== null).length})`,
|
|
369
366
|
);
|
|
370
367
|
for (const a of detail.grading.assertions) {
|
|
371
368
|
let mark = a.passed ? "✓" : "✗";
|
|
369
|
+
if (a.passed === null) mark = "ℹ informational";
|
|
372
370
|
if (a.abstention) mark = "?";
|
|
373
371
|
const took = ` (${(a.durationMs / 1000).toFixed(1)}s)`;
|
|
374
372
|
const abstention = a.abstention ? " — insufficient evidence" : "";
|
|
375
|
-
lines.push(
|
|
373
|
+
lines.push(
|
|
374
|
+
` ${mark} ${a.name} [score ${a.errorMessage !== undefined || a.abstention ? "unavailable" : a.score}]${took}${abstention}${a.comment ? ` — ${a.comment}` : ""}`,
|
|
375
|
+
);
|
|
376
376
|
}
|
|
377
377
|
}
|
|
378
378
|
};
|
|
@@ -24,6 +24,7 @@ export const clusterCaseSamples = (
|
|
|
24
24
|
}
|
|
25
25
|
if (current.identityHash !== sample.identityHash) current.identityHash = null;
|
|
26
26
|
if (current.clusterId !== sample.clusterId) current.identityHash = null;
|
|
27
|
+
if (current.scoreBasis !== sample.scoreBasis) current.scoreBasis = null;
|
|
27
28
|
const metrics = new Map(Object.entries(current.metrics));
|
|
28
29
|
for (const [metric, value] of Object.entries(sample.metrics)) {
|
|
29
30
|
const before = metrics.get(metric);
|
|
@@ -65,7 +66,9 @@ export const groupedCaseMetricSamples = (
|
|
|
65
66
|
if (
|
|
66
67
|
Object.values(samples).some(
|
|
67
68
|
(sample) =>
|
|
68
|
-
!Object.hasOwn(sample.metrics, metric) ||
|
|
69
|
+
!Object.hasOwn(sample.metrics, metric) ||
|
|
70
|
+
sample.metrics[metric]?.n !== repetitions ||
|
|
71
|
+
(metric === "score" && !sample.scoreBasis),
|
|
69
72
|
)
|
|
70
73
|
)
|
|
71
74
|
return [];
|
|
@@ -132,6 +135,8 @@ const pairedDeltas = (
|
|
|
132
135
|
const b = after.caseSamples?.[id];
|
|
133
136
|
if (!a?.identityHash || a.identityHash !== b?.identityHash)
|
|
134
137
|
throw new Error(`paired case '${id}' has incompatible evaluation identity`);
|
|
138
|
+
if (metric === "score" && (!a.scoreBasis || a.scoreBasis !== b.scoreBasis))
|
|
139
|
+
throw new Error(`paired score for case '${id}' has different or unavailable scoring basis`);
|
|
135
140
|
if (a.clusterId !== b.clusterId)
|
|
136
141
|
throw new Error(`paired case '${id}' has incompatible cluster membership`);
|
|
137
142
|
const av = Object.hasOwn(a.metrics, metric) ? a.metrics[metric] : undefined;
|
|
@@ -74,10 +74,19 @@ const pairedMetricDeltas = (
|
|
|
74
74
|
const a = before[index];
|
|
75
75
|
const b = after[index];
|
|
76
76
|
if (!a || !b) continue;
|
|
77
|
-
const
|
|
77
|
+
const am: Record<string, number> = {
|
|
78
|
+
...a.metrics,
|
|
79
|
+
...(a.score !== undefined ? { score: a.score } : {}),
|
|
80
|
+
};
|
|
81
|
+
const bm: Record<string, number> = {
|
|
82
|
+
...b.metrics,
|
|
83
|
+
...(b.score !== undefined ? { score: b.score } : {}),
|
|
84
|
+
};
|
|
85
|
+
const names = new Set([...Object.keys(am), ...Object.keys(bm)]);
|
|
78
86
|
for (const name of names) {
|
|
79
|
-
|
|
80
|
-
const
|
|
87
|
+
if (name === "score" && (!a.scoreBasis || a.scoreBasis !== b.scoreBasis)) continue;
|
|
88
|
+
const av = am[name];
|
|
89
|
+
const bv = bm[name];
|
|
81
90
|
if (av === undefined || bv === undefined) continue;
|
|
82
91
|
const values = deltas.get(name) ?? [];
|
|
83
92
|
values.push(bv - av);
|
|
@@ -196,9 +205,14 @@ export const formatSuiteComparisonText = (comparison: SuiteComparison): string =
|
|
|
196
205
|
}
|
|
197
206
|
}
|
|
198
207
|
for (const entry of comparison.cases) {
|
|
199
|
-
if (
|
|
208
|
+
if (entry.regressed) {
|
|
209
|
+
lines.push(
|
|
210
|
+
` ↓ ${suiteGroupKey(entry)}: ${entry.before ? percent(entry.before.passRate) : "—"} → ${entry.after ? percent(entry.after.passRate) : "missing"}`,
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
const score = entry.pairedMetricDeltas.score;
|
|
200
214
|
lines.push(
|
|
201
|
-
`
|
|
215
|
+
` ${suiteGroupKey(entry)} score change: ${score ? `${score.mean >= 0 ? "+" : ""}${score.mean} (${score.n} paired)` : "unavailable"}`,
|
|
202
216
|
);
|
|
203
217
|
}
|
|
204
218
|
return lines.join("\n");
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
import { hasAgentUsage } from "../core/usage.js";
|
|
12
12
|
import { runComparisonIdentity } from "./comparison-identity.js";
|
|
13
13
|
import { safeReadJson } from "./fs-read.js";
|
|
14
|
+
import { gradingScoreBasis } from "./grading.js";
|
|
14
15
|
import type { SuiteCaseResult } from "./orchestrator.js";
|
|
15
16
|
import type { SuiteRun } from "./suite-runs.js";
|
|
16
17
|
|
|
@@ -65,6 +66,7 @@ export const SuiteSampleSchema = z.object({
|
|
|
65
66
|
passed: z.boolean().nullable(),
|
|
66
67
|
identityHash: z.string().nullable(),
|
|
67
68
|
score: z.exactOptional(z.number()),
|
|
69
|
+
scoreBasis: z.exactOptional(z.string().nullable()),
|
|
68
70
|
metrics: z.record(z.string(), z.number()),
|
|
69
71
|
});
|
|
70
72
|
export type SuiteSample = z.infer<typeof SuiteSampleSchema>;
|
|
@@ -354,7 +356,10 @@ export const buildSuiteSummary = (
|
|
|
354
356
|
samples: entries.map((entry, index) => {
|
|
355
357
|
const samples: MetricSamples = new Map();
|
|
356
358
|
const result = entry.result?.result;
|
|
357
|
-
if (result)
|
|
359
|
+
if (result) {
|
|
360
|
+
add(samples, "score", result.grading.caseScore ?? undefined);
|
|
361
|
+
collectMetadata(samples, result.metadata);
|
|
362
|
+
}
|
|
358
363
|
return {
|
|
359
364
|
index,
|
|
360
365
|
caseId: entry.item.caseId,
|
|
@@ -364,7 +369,7 @@ export const buildSuiteSummary = (
|
|
|
364
369
|
passed: entry.result?.casePassed ?? null,
|
|
365
370
|
identityHash: result ? runComparisonIdentity(result.metadata) : null,
|
|
366
371
|
...(result?.grading.caseScore !== null && result?.grading.caseScore !== undefined
|
|
367
|
-
? { score: result.grading.caseScore }
|
|
372
|
+
? { score: result.grading.caseScore, scoreBasis: gradingScoreBasis(result.grading) }
|
|
368
373
|
: {}),
|
|
369
374
|
metrics: Object.fromEntries(
|
|
370
375
|
[...samples.entries()].map(([name, sample]) => [name, sample.values[0] ?? 0]),
|
package/src/previewer/server.ts
CHANGED
|
@@ -7,6 +7,7 @@ import { type DriverMetricDescriptor, type DriverMetrics, exec } from "../core/i
|
|
|
7
7
|
import {
|
|
8
8
|
compareRuns,
|
|
9
9
|
compareSuiteRuns,
|
|
10
|
+
hasRegression,
|
|
10
11
|
type ListedRun,
|
|
11
12
|
listExperimentRuns,
|
|
12
13
|
listRuns,
|
|
@@ -424,6 +425,7 @@ const handleRunComparison = async ({
|
|
|
424
425
|
]);
|
|
425
426
|
sendJson(request, response, 200, {
|
|
426
427
|
kind: "run",
|
|
428
|
+
regressed: hasRegression(comparison),
|
|
427
429
|
comparison: {
|
|
428
430
|
...comparison,
|
|
429
431
|
pathA: displayPath(runsRoot, before.runDir),
|
package/viewer/app.css
CHANGED
|
@@ -993,6 +993,10 @@ h1 {
|
|
|
993
993
|
monospace;
|
|
994
994
|
}
|
|
995
995
|
|
|
996
|
+
.assertion.informational .assertion-mark {
|
|
997
|
+
color: var(--muted);
|
|
998
|
+
}
|
|
999
|
+
|
|
996
1000
|
.assertion.passed .assertion-mark {
|
|
997
1001
|
color: var(--lime);
|
|
998
1002
|
}
|
|
@@ -1252,6 +1256,37 @@ h1 {
|
|
|
1252
1256
|
color: var(--muted);
|
|
1253
1257
|
}
|
|
1254
1258
|
|
|
1259
|
+
.score-bar {
|
|
1260
|
+
position: relative;
|
|
1261
|
+
display: block;
|
|
1262
|
+
width: 100%;
|
|
1263
|
+
min-width: 92px;
|
|
1264
|
+
height: 9px;
|
|
1265
|
+
background: #282d30;
|
|
1266
|
+
}
|
|
1267
|
+
|
|
1268
|
+
.score-fill {
|
|
1269
|
+
display: block;
|
|
1270
|
+
height: 100%;
|
|
1271
|
+
background: var(--muted);
|
|
1272
|
+
}
|
|
1273
|
+
|
|
1274
|
+
.score-fill.tone-positive,
|
|
1275
|
+
.score-fill.tone-after {
|
|
1276
|
+
background: var(--lime);
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1279
|
+
.score-fill.tone-negative {
|
|
1280
|
+
background: var(--coral);
|
|
1281
|
+
}
|
|
1282
|
+
|
|
1283
|
+
.score-bar > .has-threshold {
|
|
1284
|
+
position: absolute;
|
|
1285
|
+
top: -2px;
|
|
1286
|
+
height: 13px;
|
|
1287
|
+
border-left: 1px solid currentColor;
|
|
1288
|
+
}
|
|
1289
|
+
|
|
1255
1290
|
.quantized-bar {
|
|
1256
1291
|
display: grid;
|
|
1257
1292
|
grid-template-columns: repeat(20, minmax(2px, 1fr));
|
|
@@ -1401,6 +1436,7 @@ h1 {
|
|
|
1401
1436
|
text-transform: uppercase;
|
|
1402
1437
|
}
|
|
1403
1438
|
|
|
1439
|
+
.assertion-content > .score-bar,
|
|
1404
1440
|
.assertion-content > .quantized-bar {
|
|
1405
1441
|
max-width: 220px;
|
|
1406
1442
|
margin-top: 8px;
|
package/viewer/charts.js
CHANGED
|
@@ -51,28 +51,40 @@ const distributionBar = (segments, label) => {
|
|
|
51
51
|
</div>`;
|
|
52
52
|
};
|
|
53
53
|
|
|
54
|
+
export const scoreBar = (value, tone, label, threshold = null) => {
|
|
55
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
56
|
+
return `<span class="score-bar unavailable" role="img" aria-label="${escapeHtml(label)}"></span>`;
|
|
57
|
+
}
|
|
58
|
+
return `<span class="score-bar" role="img" aria-label="${escapeHtml(label)}">
|
|
59
|
+
<i class="score-fill ${escapeHtml(tone)}" style="width: ${value * 100}%"></i>
|
|
60
|
+
${typeof threshold === "number" ? `<i class="has-threshold" style="left: ${threshold * 100}%"></i>` : ""}
|
|
61
|
+
</span>`;
|
|
62
|
+
};
|
|
63
|
+
|
|
54
64
|
export const comparisonBars = ({
|
|
55
65
|
title,
|
|
56
66
|
before,
|
|
57
67
|
after,
|
|
58
68
|
maximum,
|
|
69
|
+
continuous = false,
|
|
59
70
|
beforeText,
|
|
60
71
|
afterText,
|
|
61
72
|
deltaText,
|
|
62
73
|
deltaTone,
|
|
63
74
|
}) => {
|
|
75
|
+
const bar = continuous ? scoreBar : quantizedBar;
|
|
64
76
|
const scale = Math.max(maximum ?? 0, before ?? 0, after ?? 0, 1);
|
|
65
77
|
return `<figure class="comparison-bars">
|
|
66
78
|
<figcaption><span>${escapeHtml(title)}</span><b class="${escapeHtml(
|
|
67
79
|
deltaTone,
|
|
68
80
|
)}">${escapeHtml(deltaText)}</b></figcaption>
|
|
69
|
-
<div class="bar-lane"><strong>A</strong>${
|
|
70
|
-
|
|
81
|
+
<div class="bar-lane"><strong>A</strong>${bar(
|
|
82
|
+
before === null || before === undefined ? null : before / scale,
|
|
71
83
|
"tone-before",
|
|
72
84
|
`${title} baseline ${beforeText}`,
|
|
73
85
|
)}<span>${escapeHtml(beforeText)}</span></div>
|
|
74
|
-
<div class="bar-lane"><strong>B</strong>${
|
|
75
|
-
|
|
86
|
+
<div class="bar-lane"><strong>B</strong>${bar(
|
|
87
|
+
after === null || after === undefined ? null : after / scale,
|
|
76
88
|
"tone-after",
|
|
77
89
|
`${title} candidate ${afterText}`,
|
|
78
90
|
)}<span>${escapeHtml(afterText)}</span></div>
|
package/viewer/detail.js
CHANGED
|
@@ -1,4 +1,16 @@
|
|
|
1
|
-
|
|
1
|
+
const assertionScore = (assertion) =>
|
|
2
|
+
assertion.errorMessage !== undefined || assertion.abstention !== undefined
|
|
3
|
+
? null
|
|
4
|
+
: assertion.score;
|
|
5
|
+
|
|
6
|
+
const assertionPresentation = (assertion) => {
|
|
7
|
+
if (assertionScore(assertion) === null) return { className: " unavailable", mark: "?", tone: "" };
|
|
8
|
+
if (assertion.passed === null) return { className: " informational", mark: "ℹ", tone: "" };
|
|
9
|
+
if (assertion.passed) return { className: " passed", mark: "✓", tone: "tone-positive" };
|
|
10
|
+
return { className: "", mark: "×", tone: "tone-negative" };
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
import { comparisonBars, compositionFigure, quantizedBar, scoreBar } from "./charts.js";
|
|
2
14
|
import {
|
|
3
15
|
badge,
|
|
4
16
|
compactNumber,
|
|
@@ -98,6 +110,7 @@ const assertionMeta = (assertion) =>
|
|
|
98
110
|
const assertionDiagnostics = (assertion) => {
|
|
99
111
|
const rows = [
|
|
100
112
|
assertion.comment ? `<p class="assertion-comment">${escapeHtml(assertion.comment)}</p>` : "",
|
|
113
|
+
assertion.abstention ? `<p class="assertion-comment">Insufficient evidence</p>` : "",
|
|
101
114
|
assertion.errorMessage
|
|
102
115
|
? `<p class="diagnostic-error">${escapeHtml(assertion.errorMessage)}</p>`
|
|
103
116
|
: "",
|
|
@@ -112,6 +125,7 @@ const assertionDiagnostics = (assertion) => {
|
|
|
112
125
|
|
|
113
126
|
const inferredMetricUnit = (name, unit) => {
|
|
114
127
|
if (unit) return unit;
|
|
128
|
+
if (name === "score") return "score";
|
|
115
129
|
if (name.endsWith("Ms")) return "milliseconds";
|
|
116
130
|
if (name.endsWith(".bytes")) return "bytes";
|
|
117
131
|
if (name.endsWith(".usd")) return "usd";
|
|
@@ -261,11 +275,11 @@ export const renderRunDetail = (item, detail) => {
|
|
|
261
275
|
{
|
|
262
276
|
key: "regressed",
|
|
263
277
|
label: "Failed",
|
|
264
|
-
value: assertions.filter((assertion) =>
|
|
278
|
+
value: assertions.filter((assertion) => assertion.passed === false).length,
|
|
265
279
|
},
|
|
266
280
|
],
|
|
267
281
|
`${assertions.filter((assertion) => assertion.passed).length} of ${
|
|
268
|
-
assertions.length
|
|
282
|
+
assertions.filter((assertion) => assertion.passed !== null).length
|
|
269
283
|
} assertions passed`,
|
|
270
284
|
)}
|
|
271
285
|
${
|
|
@@ -305,30 +319,30 @@ export const renderRunDetail = (item, detail) => {
|
|
|
305
319
|
: ""
|
|
306
320
|
}
|
|
307
321
|
<section class="detail-section">
|
|
308
|
-
<h3>Judgment <span>${assertions.filter((entry) => entry.passed).length}/${assertions.length} passed</span></h3>
|
|
322
|
+
<h3>Judgment <span>${assertions.filter((entry) => entry.passed).length}/${assertions.filter((entry) => entry.passed !== null).length} passed</span></h3>
|
|
309
323
|
<div class="assertion-list">
|
|
310
324
|
${
|
|
311
325
|
assertions.length
|
|
312
326
|
? assertions
|
|
313
327
|
.map(
|
|
314
328
|
(assertion) => `
|
|
315
|
-
<article class="assertion${assertion.
|
|
316
|
-
<span class="assertion-mark">${assertion.
|
|
329
|
+
<article class="assertion${assertionPresentation(assertion).className}">
|
|
330
|
+
<span class="assertion-mark">${assertionPresentation(assertion).mark}</span>
|
|
317
331
|
<span class="assertion-content">
|
|
318
332
|
<strong>${escapeHtml(assertion.name)}</strong>
|
|
319
333
|
<small>${escapeHtml(assertionMeta(assertion))}</small>
|
|
320
|
-
${
|
|
321
|
-
assertion
|
|
322
|
-
assertion.
|
|
334
|
+
${scoreBar(
|
|
335
|
+
assertionScore(assertion),
|
|
336
|
+
assertionPresentation(assertion).tone,
|
|
323
337
|
`${assertion.name} scored ${percentage(
|
|
324
|
-
assertion
|
|
325
|
-
)}
|
|
326
|
-
assertion.threshold,
|
|
338
|
+
assertionScore(assertion),
|
|
339
|
+
)}${assertion.passed === null ? " (informational)" : `; threshold ${percentage(assertion.threshold)}`}`,
|
|
340
|
+
assertion.passed === null ? null : assertion.threshold,
|
|
327
341
|
)}
|
|
328
342
|
${assertionDiagnostics(assertion)}
|
|
329
343
|
</span>
|
|
330
344
|
<span class="assertion-score">${escapeHtml(
|
|
331
|
-
percentage(assertion
|
|
345
|
+
percentage(assertionScore(assertion)),
|
|
332
346
|
)}</span>
|
|
333
347
|
</article>`,
|
|
334
348
|
)
|
|
@@ -893,18 +907,20 @@ export const renderRunComparison = (payload, labels) => {
|
|
|
893
907
|
runDelta?.agentUsageAvailable?.b === false ? null : totalTokens(runDelta?.usage?.b?.agent);
|
|
894
908
|
const tokenDelta = tokenA === null || tokenB === null ? null : tokenB - tokenA;
|
|
895
909
|
const scoreDelta =
|
|
910
|
+
comparison.grading.caseScoreComparable !== false &&
|
|
896
911
|
typeof comparison.grading.caseScoreA === "number" &&
|
|
897
912
|
typeof comparison.grading.caseScoreB === "number"
|
|
898
913
|
? comparison.grading.caseScoreB - comparison.grading.caseScoreA
|
|
899
914
|
: null;
|
|
915
|
+
let comparisonStatus = "ungraded";
|
|
916
|
+
if (payload.regressed === true) comparisonStatus = "failed";
|
|
917
|
+
if (payload.regressed === false) comparisonStatus = "passed";
|
|
900
918
|
const driverMetrics = driverMetricComparisons(runDelta);
|
|
901
919
|
const subagentsA = subagentTotals(runDelta?.agentActivity?.a);
|
|
902
920
|
const subagentsB = subagentTotals(runDelta?.agentActivity?.b);
|
|
903
921
|
return `
|
|
904
922
|
<header class="detail-header comparison-header">
|
|
905
|
-
<div class="detail-kicker">${badge(
|
|
906
|
-
comparison.grading.regressions ? "failed" : "passed",
|
|
907
|
-
)}<span>run comparison</span></div>
|
|
923
|
+
<div class="detail-kicker">${badge(comparisonStatus)}<span>run comparison</span></div>
|
|
908
924
|
<div class="detail-title-row">
|
|
909
925
|
<div><h2>${escapeHtml(labels.before)} → ${escapeHtml(
|
|
910
926
|
labels.after,
|
|
@@ -920,6 +936,7 @@ export const renderRunComparison = (payload, labels) => {
|
|
|
920
936
|
)}. Treat deltas as potentially incomparable.</div>`
|
|
921
937
|
: '<div class="integrity-banner">✓ Case, evaluator, runtime, and driver identities are comparable</div>'
|
|
922
938
|
}
|
|
939
|
+
${comparison.grading.caseScoreComparable === false ? '<div class="attention-banner">Score change unavailable: scored assertions or weights differ, or evidence is missing.</div>' : ""}
|
|
923
940
|
<dl class="comparison-stats">
|
|
924
941
|
<div><dt>Case score</dt><dd>${escapeHtml(
|
|
925
942
|
`${percentage(comparison.grading.caseScoreA)} → ${percentage(
|
|
@@ -950,6 +967,7 @@ export const renderRunComparison = (payload, labels) => {
|
|
|
950
967
|
<div class="comparison-plot-grid">
|
|
951
968
|
${comparisonBars({
|
|
952
969
|
title: "Case score",
|
|
970
|
+
continuous: true,
|
|
953
971
|
before: comparison.grading.caseScoreA,
|
|
954
972
|
after: comparison.grading.caseScoreB,
|
|
955
973
|
maximum: 1,
|
|
@@ -1045,6 +1063,12 @@ export const renderRunComparison = (payload, labels) => {
|
|
|
1045
1063
|
value: comparison.grading.deltas.filter((delta) => delta.status === "removed")
|
|
1046
1064
|
.length,
|
|
1047
1065
|
},
|
|
1066
|
+
{
|
|
1067
|
+
key: "unavailable",
|
|
1068
|
+
label: "Unavailable",
|
|
1069
|
+
value: comparison.grading.deltas.filter((delta) => delta.status === "unavailable")
|
|
1070
|
+
.length,
|
|
1071
|
+
},
|
|
1048
1072
|
{
|
|
1049
1073
|
key: "unchanged",
|
|
1050
1074
|
label: "Unchanged",
|
package/viewer/format.js
CHANGED
|
@@ -6,11 +6,11 @@ export const escapeHtml = (value) =>
|
|
|
6
6
|
);
|
|
7
7
|
|
|
8
8
|
export const percentage = (value) =>
|
|
9
|
-
typeof value === "number" ? `${
|
|
9
|
+
typeof value === "number" ? `${Number((value * 100).toPrecision(15))}%` : "—";
|
|
10
10
|
|
|
11
11
|
export const signedPercentage = (value) => {
|
|
12
12
|
if (typeof value !== "number") return "—";
|
|
13
|
-
const rounded =
|
|
13
|
+
const rounded = Number((value * 100).toPrecision(15));
|
|
14
14
|
return `${rounded > 0 ? "+" : ""}${rounded}%`;
|
|
15
15
|
};
|
|
16
16
|
|