pluginbench 0.5.0__py3-none-any.whl
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.
- pluginbench/__init__.py +3 -0
- pluginbench/__main__.py +3 -0
- pluginbench/assessment-charts.html +790 -0
- pluginbench/assessment.py +316 -0
- pluginbench/assessment_charts.py +1225 -0
- pluginbench/assessment_metrics.py +1956 -0
- pluginbench/assessment_models.py +127 -0
- pluginbench/baselines.py +792 -0
- pluginbench/claude.py +374 -0
- pluginbench/cli.py +672 -0
- pluginbench/code_diff.py +207 -0
- pluginbench/codex.py +451 -0
- pluginbench/codex_events.py +585 -0
- pluginbench/collectors.py +591 -0
- pluginbench/compare.py +927 -0
- pluginbench/components.py +129 -0
- pluginbench/coverage_report.py +610 -0
- pluginbench/datasets.py +207 -0
- pluginbench/evaluation.example.yaml +45 -0
- pluginbench/evaluation.py +1651 -0
- pluginbench/evaluation_report.py +1004 -0
- pluginbench/fallow_parse.mjs +101 -0
- pluginbench/harbor_backend.py +2946 -0
- pluginbench/isolation.py +314 -0
- pluginbench/judge.py +683 -0
- pluginbench/language_analysis.py +915 -0
- pluginbench/ledger.py +217 -0
- pluginbench/measurement_profiles.py +616 -0
- pluginbench/native_treatment_runtime.js +221 -0
- pluginbench/native_treatments.py +155 -0
- pluginbench/observer_data/go-analysis-v1/go.mod +10 -0
- pluginbench/observer_data/go-analysis-v1/go.sum +10 -0
- pluginbench/observer_data/go-analysis-v1/main.go +64 -0
- pluginbench/observer_data/oxc-parser-0.126.0/package-lock.json +449 -0
- pluginbench/observer_data/oxc-parser-0.126.0/package.json +5 -0
- pluginbench/parallel.py +101 -0
- pluginbench/processes.py +100 -0
- pluginbench/remeasure.py +992 -0
- pluginbench/run_lock.py +227 -0
- pluginbench/run_status.py +518 -0
- pluginbench/study.py +1858 -0
- pluginbench/study_review.py +924 -0
- pluginbench/submissions.py +486 -0
- pluginbench/task_sources.py +403 -0
- pluginbench/treatments.py +388 -0
- pluginbench/trial_containers.py +94 -0
- pluginbench/vendor/ECHARTS-LICENSE +222 -0
- pluginbench/vendor/ECHARTS-NOTICE +5 -0
- pluginbench/vendor/ZRENDER-LICENSE +29 -0
- pluginbench/vendor/echarts.min.js +48 -0
- pluginbench-0.5.0.dist-info/METADATA +294 -0
- pluginbench-0.5.0.dist-info/RECORD +58 -0
- pluginbench-0.5.0.dist-info/WHEEL +4 -0
- pluginbench-0.5.0.dist-info/entry_points.txt +2 -0
- pluginbench-0.5.0.dist-info/licenses/LICENSE +201 -0
- pluginbench-0.5.0.dist-info/licenses/src/pluginbench/vendor/ECHARTS-LICENSE +222 -0
- pluginbench-0.5.0.dist-info/licenses/src/pluginbench/vendor/ECHARTS-NOTICE +5 -0
- pluginbench-0.5.0.dist-info/licenses/src/pluginbench/vendor/ZRENDER-LICENSE +29 -0
|
@@ -0,0 +1,1004 @@
|
|
|
1
|
+
"""Compact, selected views of canonical evidence for one evaluation."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import statistics
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
from pluginbench.measurement_profiles import REMOVED_TEST_OVERLAP, readiness_state
|
|
10
|
+
from pluginbench.assessment_charts import (
|
|
11
|
+
DIAGNOSTIC_PANELS,
|
|
12
|
+
METRICS,
|
|
13
|
+
_map,
|
|
14
|
+
_net_lines,
|
|
15
|
+
_number,
|
|
16
|
+
_rows,
|
|
17
|
+
_signed,
|
|
18
|
+
chart_artifacts_from_data,
|
|
19
|
+
chart_data,
|
|
20
|
+
execution_outcome,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
MEASUREMENT_METRICS = {
|
|
24
|
+
"tokens": (
|
|
25
|
+
"Input tokens",
|
|
26
|
+
"Cached input tokens",
|
|
27
|
+
"Uncached input tokens",
|
|
28
|
+
"Output tokens",
|
|
29
|
+
"Reasoning output tokens",
|
|
30
|
+
"Coding time",
|
|
31
|
+
),
|
|
32
|
+
"lines": (
|
|
33
|
+
"Source lines added",
|
|
34
|
+
"Source lines removed",
|
|
35
|
+
"Source lines net",
|
|
36
|
+
"Test lines added",
|
|
37
|
+
"Test lines removed",
|
|
38
|
+
"Duplicated added test lines",
|
|
39
|
+
),
|
|
40
|
+
"cyclomatic": (
|
|
41
|
+
"Net cyclomatic complexity added",
|
|
42
|
+
"Functions added",
|
|
43
|
+
"Source files added",
|
|
44
|
+
),
|
|
45
|
+
"cognitive": (
|
|
46
|
+
"Net cognitive complexity added",
|
|
47
|
+
"Complexity hotspots added",
|
|
48
|
+
),
|
|
49
|
+
"dead_code": ("Introduced dead findings", "Introduced clone groups"),
|
|
50
|
+
"coverage": (
|
|
51
|
+
"Changed-line coverage %",
|
|
52
|
+
"Changed-branch coverage %",
|
|
53
|
+
"Redundant added tests",
|
|
54
|
+
"Line coverage %",
|
|
55
|
+
"Statement coverage %",
|
|
56
|
+
"Branch coverage %",
|
|
57
|
+
"Function coverage %",
|
|
58
|
+
),
|
|
59
|
+
"review": (
|
|
60
|
+
"Total review findings",
|
|
61
|
+
"P0 review findings",
|
|
62
|
+
"P1 review findings",
|
|
63
|
+
"P2 review findings",
|
|
64
|
+
"P3 review findings",
|
|
65
|
+
"Correctness review findings",
|
|
66
|
+
"Scope review findings",
|
|
67
|
+
"Maintainability review findings",
|
|
68
|
+
"Tests review findings",
|
|
69
|
+
"Judge input tokens",
|
|
70
|
+
"Judge output tokens",
|
|
71
|
+
),
|
|
72
|
+
}
|
|
73
|
+
OBSERVATION_FIELDS = (
|
|
74
|
+
"attempt_id",
|
|
75
|
+
"task",
|
|
76
|
+
"arm",
|
|
77
|
+
"panel",
|
|
78
|
+
"metric",
|
|
79
|
+
"family",
|
|
80
|
+
"value",
|
|
81
|
+
"unit",
|
|
82
|
+
"correctness",
|
|
83
|
+
"status",
|
|
84
|
+
"reason",
|
|
85
|
+
"compatibility",
|
|
86
|
+
"model",
|
|
87
|
+
"host",
|
|
88
|
+
"harness",
|
|
89
|
+
"harness_version",
|
|
90
|
+
"reasoning_effort",
|
|
91
|
+
"requested_reasoning_effort",
|
|
92
|
+
"runtime_source",
|
|
93
|
+
"role",
|
|
94
|
+
"stage",
|
|
95
|
+
"snapshot_digest",
|
|
96
|
+
"before_digest",
|
|
97
|
+
"per_test_count",
|
|
98
|
+
"elapsed_seconds",
|
|
99
|
+
"provenance",
|
|
100
|
+
)
|
|
101
|
+
PROVENANCE_FIELDS = (
|
|
102
|
+
"language",
|
|
103
|
+
"analyzers",
|
|
104
|
+
"metric_settings",
|
|
105
|
+
"tool",
|
|
106
|
+
"tool_version",
|
|
107
|
+
"method",
|
|
108
|
+
"config_digest",
|
|
109
|
+
"scope",
|
|
110
|
+
"token_format",
|
|
111
|
+
"clock",
|
|
112
|
+
"complete_for_declared_scope",
|
|
113
|
+
"reported_functions",
|
|
114
|
+
"limitations",
|
|
115
|
+
"test_scope",
|
|
116
|
+
"covered",
|
|
117
|
+
"total",
|
|
118
|
+
"selection_policy",
|
|
119
|
+
"branch_denominator_digest",
|
|
120
|
+
"function_denominator_digest",
|
|
121
|
+
"line_denominator_digest",
|
|
122
|
+
"statement_denominator_digest",
|
|
123
|
+
"judge_model",
|
|
124
|
+
"judge_harness",
|
|
125
|
+
"judge_reasoning_effort",
|
|
126
|
+
"judge_comparison_reasoning_effort",
|
|
127
|
+
"judge_effort_evidence_source",
|
|
128
|
+
"judge_harness_version",
|
|
129
|
+
"review_stage",
|
|
130
|
+
"criteria_sha256",
|
|
131
|
+
"reasoning_comparison",
|
|
132
|
+
"execution_status",
|
|
133
|
+
"execution_reason",
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
TRUSTED_TEST_OVERLAP = "trusted verifier tests overwrite authored patch paths"
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def correctness_state(status: object, grading: object) -> str:
|
|
140
|
+
"""Correctness of one attempt, separating overlap passes from unknowns.
|
|
141
|
+
|
|
142
|
+
PluginBench withholds confirmation when trusted verifier tests overwrite paths the agent
|
|
143
|
+
changed. The verifier still grades the agent's production code against its own tests, so
|
|
144
|
+
reward 1 with every fail-to-pass and pass-to-pass test passing is an overlap pass, and
|
|
145
|
+
reward 0 is a fail. One exception stays unknown: reward 0 with no pass-to-pass test passing.
|
|
146
|
+
A replaced test file can remove code the agent's other test files use, so the existing suite
|
|
147
|
+
may fail to build before any test runs. Any other withheld result stays unknown.
|
|
148
|
+
"""
|
|
149
|
+
grading = _map(grading)
|
|
150
|
+
if status not in {"unknown", "fail"} or grading.get("reason") != TRUSTED_TEST_OVERLAP:
|
|
151
|
+
return str(status)
|
|
152
|
+
rewards = _map(_map(grading.get("native_verifier_result")).get("rewards"))
|
|
153
|
+
if status == "fail" and rewards.get("reward") != 0:
|
|
154
|
+
# A fail the receipt recorded itself, such as a run that did not complete.
|
|
155
|
+
return "fail"
|
|
156
|
+
if rewards.get("reward") == 0:
|
|
157
|
+
# Also a saved report's earlier verdict, which compare and judge read back.
|
|
158
|
+
p2p_total = rewards.get("p2p_total")
|
|
159
|
+
no_existing_test_ran = (
|
|
160
|
+
isinstance(p2p_total, int) and p2p_total > 0 and rewards.get("p2p_passed") == 0
|
|
161
|
+
)
|
|
162
|
+
return "unknown" if no_existing_test_ran else "fail"
|
|
163
|
+
every_test_passed = all(
|
|
164
|
+
isinstance(rewards.get(f"{kind}_total"), int)
|
|
165
|
+
and rewards.get(f"{kind}_passed") == rewards.get(f"{kind}_total")
|
|
166
|
+
for kind in ("f2p", "p2p")
|
|
167
|
+
)
|
|
168
|
+
return "overlap_pass" if rewards.get("reward") == 1 and every_test_passed else "unknown"
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
COMPLETENESS_COUNTS = {
|
|
172
|
+
"tokens": "Input/output tokens",
|
|
173
|
+
"lines": "Source/test lines added/removed",
|
|
174
|
+
"cognitive": "Complexity population",
|
|
175
|
+
"cyclomatic": "Complexity population",
|
|
176
|
+
"dead_code": "Introduced dead findings",
|
|
177
|
+
"coverage": "Coverage covered/total counts",
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def _selected_completeness(
|
|
182
|
+
*,
|
|
183
|
+
data: dict[str, object],
|
|
184
|
+
projection: dict[str, object],
|
|
185
|
+
selected: list[object],
|
|
186
|
+
skipped: dict[tuple[str, str], object],
|
|
187
|
+
) -> list[dict[str, object]]:
|
|
188
|
+
source = {
|
|
189
|
+
row["attempt_id"]: row
|
|
190
|
+
for row in _rows(projection["measurement_completeness"])
|
|
191
|
+
if row["role"] == "handoff"
|
|
192
|
+
}
|
|
193
|
+
observations = _rows(data["observations"])
|
|
194
|
+
required = {COMPLETENESS_COUNTS[str(family)] for family in selected if family != "review"}
|
|
195
|
+
results = []
|
|
196
|
+
for attempt in _rows(data["attempts"]):
|
|
197
|
+
record = source.get(attempt["id"], {})
|
|
198
|
+
measured = {
|
|
199
|
+
row["metric"]: row for row in observations if row["attempt_id"] == attempt["id"]
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
def captured(metrics: tuple[str, ...]) -> bool:
|
|
203
|
+
return all(
|
|
204
|
+
metric in measured and measured[metric]["value"] is not None for metric in metrics
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
counts = {name: _map(record.get("counts")).get(name) is True for name in sorted(required)}
|
|
208
|
+
# Same populations as the collector-backed projection count: totals and line changes only.
|
|
209
|
+
for family, metrics in (
|
|
210
|
+
("tokens", ("Input tokens", "Output tokens")),
|
|
211
|
+
("lines", MEASUREMENT_METRICS["lines"][:4]),
|
|
212
|
+
):
|
|
213
|
+
if family in selected and not record:
|
|
214
|
+
counts[COMPLETENESS_COUNTS[family]] = captured(metrics)
|
|
215
|
+
complexity_metrics = [
|
|
216
|
+
metric
|
|
217
|
+
for family in ("cognitive", "cyclomatic")
|
|
218
|
+
if family in selected
|
|
219
|
+
for metric in MEASUREMENT_METRICS[family]
|
|
220
|
+
]
|
|
221
|
+
if complexity_metrics:
|
|
222
|
+
# Code-added sums are defined (zero) for complete empty populations; unknown stays None.
|
|
223
|
+
counts["Complexity population"] = captured(tuple(complexity_metrics))
|
|
224
|
+
if "review" in selected:
|
|
225
|
+
counts["Review findings"] = captured(
|
|
226
|
+
tuple(
|
|
227
|
+
metric
|
|
228
|
+
for metric in MEASUREMENT_METRICS["review"]
|
|
229
|
+
if not metric.startswith("Judge ")
|
|
230
|
+
)
|
|
231
|
+
)
|
|
232
|
+
counts["Review input/output tokens"] = captured(
|
|
233
|
+
("Judge input tokens", "Judge output tokens")
|
|
234
|
+
)
|
|
235
|
+
# A pair the check marked as not measured is incomplete, whatever its collector counted.
|
|
236
|
+
for measure in map(str, selected):
|
|
237
|
+
if (str(attempt["task"]), measure) in skipped and measure in COMPLETENESS_COUNTS:
|
|
238
|
+
counts[COMPLETENESS_COUNTS[measure]] = False
|
|
239
|
+
results.append(
|
|
240
|
+
{
|
|
241
|
+
**record,
|
|
242
|
+
"attempt_id": attempt["id"],
|
|
243
|
+
"task": attempt["task"],
|
|
244
|
+
"arm": attempt["guidance"],
|
|
245
|
+
"role": "handoff",
|
|
246
|
+
"counts": counts,
|
|
247
|
+
"complete": bool(counts) and all(counts.values()),
|
|
248
|
+
}
|
|
249
|
+
)
|
|
250
|
+
return results
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
def _completeness_matrix(
|
|
254
|
+
observations: list[dict[str, object]], capabilities: dict[str, object]
|
|
255
|
+
) -> list[dict[str, object]]:
|
|
256
|
+
"""Task × measure × arm: measured, unknown with its reasons, or not configured for the task."""
|
|
257
|
+
groups: dict[tuple[str, str, str], dict[str, list[dict[str, object]]]] = {}
|
|
258
|
+
for row in observations:
|
|
259
|
+
key = (str(row["task"]), str(row["family"]), str(row["metric"]))
|
|
260
|
+
groups.setdefault(key, {}).setdefault(str(row["arm"]), []).append(row)
|
|
261
|
+
matrix: list[dict[str, object]] = []
|
|
262
|
+
for (task, family, metric), arms in sorted(groups.items()):
|
|
263
|
+
capability = _map(_map(capabilities.get(task)).get(family))
|
|
264
|
+
configured = not capability or readiness_state(capability) != "not_configured"
|
|
265
|
+
cells: dict[str, object] = {}
|
|
266
|
+
for arm, rows in sorted(arms.items()):
|
|
267
|
+
measured = sum(row["status"] == "measured" for row in rows)
|
|
268
|
+
reasons = sorted({str(row["reason"]) for row in rows if row["status"] != "measured"})
|
|
269
|
+
state = (
|
|
270
|
+
"not_configured"
|
|
271
|
+
if not configured
|
|
272
|
+
else "measured"
|
|
273
|
+
if measured == len(rows)
|
|
274
|
+
else "unknown"
|
|
275
|
+
)
|
|
276
|
+
text = (
|
|
277
|
+
state
|
|
278
|
+
if state != "unknown"
|
|
279
|
+
else f"unknown: {'; '.join(reasons)} ({measured} of {len(rows)} measured)"
|
|
280
|
+
)
|
|
281
|
+
cells[arm] = {
|
|
282
|
+
"state": state,
|
|
283
|
+
"measured": measured,
|
|
284
|
+
"assigned": len(rows),
|
|
285
|
+
"reasons": reasons,
|
|
286
|
+
"text": text,
|
|
287
|
+
}
|
|
288
|
+
matrix.append({"task": task, "family": family, "metric": metric, "cells": cells})
|
|
289
|
+
return matrix
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
def _assigned_counts(
|
|
293
|
+
observations: list[dict[str, object]],
|
|
294
|
+
) -> dict[tuple[object, object, object], int]:
|
|
295
|
+
attempts: dict[tuple[object, object, object], set[object]] = {}
|
|
296
|
+
for row in observations:
|
|
297
|
+
key = (row.get("task"), row.get("metric"), row.get("arm"))
|
|
298
|
+
attempts.setdefault(key, set()).add(row["attempt_id"])
|
|
299
|
+
return {key: len(ids) for key, ids in attempts.items()}
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
def _comparison_rows(data: dict[str, object]) -> list[dict[str, object]]:
|
|
303
|
+
observations = _rows(data["observations"])
|
|
304
|
+
assigned_counts = _assigned_counts(observations)
|
|
305
|
+
medians = _rows(data["medians"])
|
|
306
|
+
lookup = {
|
|
307
|
+
(row.get("task"), row.get("metric"), row.get("compatibility"), row.get("arm")): row
|
|
308
|
+
for row in medians
|
|
309
|
+
}
|
|
310
|
+
profiles = {
|
|
311
|
+
(row.get("task"), row.get("metric"), row.get("arm"), row.get("compatibility"))
|
|
312
|
+
for row in observations
|
|
313
|
+
if row.get("arm") not in {"baseline", "unknown"}
|
|
314
|
+
}
|
|
315
|
+
results = []
|
|
316
|
+
for task, metric, arm, compatibility in sorted(profiles, key=str):
|
|
317
|
+
baseline = lookup.get((task, metric, compatibility, "baseline"), {})
|
|
318
|
+
guided = lookup.get((task, metric, compatibility, arm), {})
|
|
319
|
+
baseline_value, guided_value = baseline.get("value"), guided.get("value")
|
|
320
|
+
absolute = (
|
|
321
|
+
float(guided_value) - float(baseline_value)
|
|
322
|
+
if compatibility is not None
|
|
323
|
+
and isinstance(baseline_value, (int, float))
|
|
324
|
+
and isinstance(guided_value, (int, float))
|
|
325
|
+
else None
|
|
326
|
+
)
|
|
327
|
+
known = absolute is not None
|
|
328
|
+
percentage = (
|
|
329
|
+
absolute / float(baseline_value) * 100
|
|
330
|
+
if absolute is not None
|
|
331
|
+
and isinstance(baseline_value, (int, float))
|
|
332
|
+
# A percentage of a zero or negative baseline has no meaning.
|
|
333
|
+
and baseline_value > 0
|
|
334
|
+
else None
|
|
335
|
+
)
|
|
336
|
+
assigned = {
|
|
337
|
+
group: assigned_counts.get((task, metric, group), 0) for group in ("baseline", arm)
|
|
338
|
+
}
|
|
339
|
+
baseline_measured, guided_measured = baseline.get("measured", 0), guided.get("measured", 0)
|
|
340
|
+
complete = baseline_measured == assigned["baseline"] and guided_measured == assigned[arm]
|
|
341
|
+
metric_guide = _map(_map(data.get("metric_guide")).get(str(metric)))
|
|
342
|
+
unit = metric_guide.get("unit")
|
|
343
|
+
results.append(
|
|
344
|
+
{
|
|
345
|
+
"task": task,
|
|
346
|
+
"absolute_delta_unit": "percentage points" if unit == "% covered" else unit,
|
|
347
|
+
"metric": metric,
|
|
348
|
+
"guidance": arm,
|
|
349
|
+
"compatibility": compatibility,
|
|
350
|
+
"baseline_median": baseline_value,
|
|
351
|
+
"guidance_median": guided_value,
|
|
352
|
+
"absolute_delta_vs_vanilla": absolute,
|
|
353
|
+
"percentage_delta_vs_vanilla": percentage,
|
|
354
|
+
"baseline_assigned": assigned["baseline"],
|
|
355
|
+
"baseline_measured": baseline_measured,
|
|
356
|
+
"guidance_assigned": assigned[arm],
|
|
357
|
+
"guidance_measured": guided_measured,
|
|
358
|
+
"status": "complete" if known and complete else "partial" if known else "unknown",
|
|
359
|
+
"reason": "Guidance median minus this evaluation's vanilla median; lower is not automatically better."
|
|
360
|
+
if known
|
|
361
|
+
else "No matching measured vanilla and guidance profile; unknown is not zero.",
|
|
362
|
+
"percentage_reason": "Undefined when vanilla is zero or either median is unknown."
|
|
363
|
+
if percentage is None
|
|
364
|
+
else None,
|
|
365
|
+
}
|
|
366
|
+
)
|
|
367
|
+
return results
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+
def _aggregate_groups(data: dict[str, object]) -> list[tuple[str, str | None, list[str]]]:
|
|
371
|
+
tasks = sorted({str(row["task"]) for row in _rows(data["attempts"])})
|
|
372
|
+
observations = _rows(data["observations"])
|
|
373
|
+
groups: dict[tuple[str, str | None], set[str]] = {}
|
|
374
|
+
task_analyzers: dict[tuple[str, str], set[str]] = {}
|
|
375
|
+
unbound_values: set[tuple[str, str]] = set()
|
|
376
|
+
for row in observations:
|
|
377
|
+
group = None
|
|
378
|
+
if row.get("family") in {"cognitive", "cyclomatic", "dead_code"}:
|
|
379
|
+
known = task_analyzers.setdefault((str(row["metric"]), str(row["task"])), set())
|
|
380
|
+
provenance = _map(row.get("provenance"))
|
|
381
|
+
analyzers = _map(provenance.get("analyzers"))
|
|
382
|
+
language = str(
|
|
383
|
+
provenance.get("language")
|
|
384
|
+
or ("JS/TS" if provenance.get("tool") == "fallow" else "unknown language")
|
|
385
|
+
)
|
|
386
|
+
family = str(row.get("family"))
|
|
387
|
+
tool_name = (
|
|
388
|
+
{"python": "vulture", "go": "staticcheck", "rust": "rustc"}.get(language, "fallow")
|
|
389
|
+
if family == "dead_code"
|
|
390
|
+
else "go_analysis"
|
|
391
|
+
if language == "go"
|
|
392
|
+
else "rca"
|
|
393
|
+
)
|
|
394
|
+
tool = _map(analyzers.get(tool_name))
|
|
395
|
+
version = tool.get("version", provenance.get("tool_version"))
|
|
396
|
+
if not version or version == "unknown" or (not tool and not provenance.get("tool")):
|
|
397
|
+
if row.get("value") is not None:
|
|
398
|
+
unbound_values.add((str(row["metric"]), str(row["task"])))
|
|
399
|
+
continue
|
|
400
|
+
version = str(version)
|
|
401
|
+
if tool_name == "go_analysis" and "gocognit=" in version:
|
|
402
|
+
part = "gocognit=" if family == "cognitive" else "gocyclo="
|
|
403
|
+
name = part[:-1] + " " + version.split(part)[-1].split()[0]
|
|
404
|
+
else:
|
|
405
|
+
name = (
|
|
406
|
+
version
|
|
407
|
+
if tool
|
|
408
|
+
else str(provenance.get("tool", "unknown analyzer")) + " " + version
|
|
409
|
+
)
|
|
410
|
+
group = f"{language.title()} · {name}"
|
|
411
|
+
known.add(group)
|
|
412
|
+
continue
|
|
413
|
+
groups.setdefault((str(row["metric"]), group), set()).add(str(row["task"]))
|
|
414
|
+
for (metric, task), known in task_analyzers.items():
|
|
415
|
+
group = (
|
|
416
|
+
"Conflicting analyzers"
|
|
417
|
+
if len(known) > 1
|
|
418
|
+
else next(iter(known))
|
|
419
|
+
if known and (metric, task) not in unbound_values
|
|
420
|
+
else "Unknown analyzer"
|
|
421
|
+
)
|
|
422
|
+
groups.setdefault((metric, group), set()).add(task)
|
|
423
|
+
return [
|
|
424
|
+
(metric, group, sorted(members) if group else tasks)
|
|
425
|
+
for (metric, group), members in sorted(groups.items(), key=str)
|
|
426
|
+
]
|
|
427
|
+
|
|
428
|
+
|
|
429
|
+
def _aggregate_rows(data: dict[str, object]) -> list[dict[str, object]]:
|
|
430
|
+
"""Give each assigned task one median, independent of its repeat count."""
|
|
431
|
+
observations = _rows(data["observations"])
|
|
432
|
+
assigned_counts = _assigned_counts(observations)
|
|
433
|
+
medians = _rows(data["medians"])
|
|
434
|
+
raw_medians = _rows(data["raw_medians"])
|
|
435
|
+
comparisons = _rows(data["comparisons"])
|
|
436
|
+
median_rows: dict[tuple[object, object, object], list[dict[str, object]]] = {}
|
|
437
|
+
raw_rows: dict[tuple[object, object, object], list[dict[str, object]]] = {}
|
|
438
|
+
comparison_rows: dict[tuple[object, object, object], list[dict[str, object]]] = {}
|
|
439
|
+
for row in medians:
|
|
440
|
+
median_rows.setdefault((row["task"], row["metric"], row["arm"]), []).append(row)
|
|
441
|
+
for row in raw_medians:
|
|
442
|
+
raw_rows.setdefault((row["task"], row["metric"], row["arm"]), []).append(row)
|
|
443
|
+
for row in comparisons:
|
|
444
|
+
if row["absolute_delta_vs_vanilla"] is not None:
|
|
445
|
+
comparison_rows.setdefault((row["task"], row["metric"], row["guidance"]), []).append(
|
|
446
|
+
row
|
|
447
|
+
)
|
|
448
|
+
arms = sorted(
|
|
449
|
+
{str(row["arm"]) for row in observations}, key=lambda arm: (arm != "baseline", arm)
|
|
450
|
+
)
|
|
451
|
+
results = []
|
|
452
|
+
for metric, analysis_group, tasks in _aggregate_groups(data):
|
|
453
|
+
unresolved_analyzer = analysis_group in {"Unknown analyzer", "Conflicting analyzers"}
|
|
454
|
+
baseline_values = {task: median_rows.get((task, metric, "baseline"), []) for task in tasks}
|
|
455
|
+
for arm in arms:
|
|
456
|
+
task_values = []
|
|
457
|
+
task_statuses = []
|
|
458
|
+
comparison_statuses = []
|
|
459
|
+
matching_baselines = []
|
|
460
|
+
for task in tasks:
|
|
461
|
+
rows = [] if unresolved_analyzer else raw_rows.get((task, metric, arm), [])
|
|
462
|
+
value = rows[0].get("value") if len(rows) == 1 else None
|
|
463
|
+
task_values.append({"task": task, "value": value})
|
|
464
|
+
matches = (
|
|
465
|
+
[] if unresolved_analyzer else comparison_rows.get((task, metric, arm), [])
|
|
466
|
+
)
|
|
467
|
+
baseline = baseline_values[task]
|
|
468
|
+
measured = rows[0].get("measured", 0) if len(rows) == 1 else 0
|
|
469
|
+
assigned = assigned_counts.get((task, metric, arm), 0)
|
|
470
|
+
raw_status = "complete" if value is not None and measured == assigned else "partial"
|
|
471
|
+
task_statuses.append(raw_status)
|
|
472
|
+
if arm == "baseline":
|
|
473
|
+
matching_baselines.append(value)
|
|
474
|
+
comparison_statuses.append(raw_status)
|
|
475
|
+
else:
|
|
476
|
+
match = matches[0] if len(matches) == 1 and len(baseline) == 1 else {}
|
|
477
|
+
matching_baselines.append(match.get("baseline_median"))
|
|
478
|
+
comparison_statuses.append(str(match.get("status", "unknown")))
|
|
479
|
+
known_values = [
|
|
480
|
+
float(row["value"]) for row in task_values if isinstance(row["value"], (int, float))
|
|
481
|
+
]
|
|
482
|
+
value = sum(known_values) / len(known_values) if known_values else None
|
|
483
|
+
known_baselines = [
|
|
484
|
+
float(value) for value in matching_baselines if isinstance(value, (int, float))
|
|
485
|
+
]
|
|
486
|
+
all_tasks_matched = len(known_values) == len(known_baselines) == len(tasks) and bool(
|
|
487
|
+
tasks
|
|
488
|
+
)
|
|
489
|
+
baseline_value = sum(known_baselines) / len(tasks) if all_tasks_matched else None
|
|
490
|
+
delta = (
|
|
491
|
+
value - baseline_value if value is not None and baseline_value is not None else None
|
|
492
|
+
)
|
|
493
|
+
guide = _map(_map(data["metric_guide"]).get(metric))
|
|
494
|
+
results.append(
|
|
495
|
+
{
|
|
496
|
+
"metric": metric,
|
|
497
|
+
"analysis_group": analysis_group,
|
|
498
|
+
"guidance": arm,
|
|
499
|
+
"value": value,
|
|
500
|
+
"unit": guide.get("unit"),
|
|
501
|
+
"method": "equal-weight mean of per-task medians",
|
|
502
|
+
"assigned_tasks": len(tasks),
|
|
503
|
+
"measured_tasks": len(known_values),
|
|
504
|
+
"task_medians": task_values,
|
|
505
|
+
"status": "complete"
|
|
506
|
+
if len(known_values) == len(tasks)
|
|
507
|
+
and all(status == "complete" for status in task_statuses)
|
|
508
|
+
else "partial"
|
|
509
|
+
if known_values
|
|
510
|
+
else "unknown",
|
|
511
|
+
"comparison_status": "complete"
|
|
512
|
+
if all_tasks_matched
|
|
513
|
+
and all(status == "complete" for status in comparison_statuses)
|
|
514
|
+
else "partial"
|
|
515
|
+
if all_tasks_matched
|
|
516
|
+
else "unknown",
|
|
517
|
+
"baseline_value": baseline_value,
|
|
518
|
+
"absolute_delta_vs_vanilla": delta,
|
|
519
|
+
"percentage_delta_vs_vanilla": delta / baseline_value * 100
|
|
520
|
+
if delta is not None and baseline_value
|
|
521
|
+
else None,
|
|
522
|
+
"absolute_delta_unit": "percentage points"
|
|
523
|
+
if guide.get("unit") == "% covered"
|
|
524
|
+
else guide.get("unit"),
|
|
525
|
+
"reason": "Analyzer provenance is missing or conflicting; overview values are unavailable. Raw observations and task medians remain separate."
|
|
526
|
+
if unresolved_analyzer
|
|
527
|
+
else "Each task has equal weight, regardless of repetitions. Deltas require matching vanilla and guidance medians for every assigned task. Partial raw averages describe the measured tasks only.",
|
|
528
|
+
}
|
|
529
|
+
)
|
|
530
|
+
return results
|
|
531
|
+
|
|
532
|
+
|
|
533
|
+
REFERENCE_METRICS = (
|
|
534
|
+
"Net cognitive complexity added",
|
|
535
|
+
"Net cyclomatic complexity added",
|
|
536
|
+
"Functions added",
|
|
537
|
+
"Source files added",
|
|
538
|
+
"Complexity hotspots added",
|
|
539
|
+
"Introduced dead findings",
|
|
540
|
+
"Introduced clone groups",
|
|
541
|
+
"Source lines added",
|
|
542
|
+
"Source lines removed",
|
|
543
|
+
"Source lines net",
|
|
544
|
+
)
|
|
545
|
+
|
|
546
|
+
|
|
547
|
+
def _reference_values(report: dict[str, object]) -> list[dict[str, object]]:
|
|
548
|
+
"""Code measures of each task's reference solution, measured like a submission."""
|
|
549
|
+
specs = {spec[1]: spec for spec in METRICS}
|
|
550
|
+
rows: list[dict[str, object]] = []
|
|
551
|
+
for task, value in sorted(_map(report.get("references")).items()):
|
|
552
|
+
reference = _map(value)
|
|
553
|
+
comparison = _map(reference.get("comparison"))
|
|
554
|
+
static = (
|
|
555
|
+
"analysis"
|
|
556
|
+
if _map(comparison.get("analysis")).get("status") == "available"
|
|
557
|
+
else "fallow"
|
|
558
|
+
)
|
|
559
|
+
for metric in REFERENCE_METRICS:
|
|
560
|
+
_, _, kind, key = specs[metric]
|
|
561
|
+
number = None
|
|
562
|
+
if reference.get("status") == "available":
|
|
563
|
+
if kind == "fallow_change":
|
|
564
|
+
section, field = key.split(".")
|
|
565
|
+
detail = _map(_map(comparison.get(static)).get(section))
|
|
566
|
+
number = (
|
|
567
|
+
_signed(detail.get(field)) if detail.get("status") == "available" else None
|
|
568
|
+
)
|
|
569
|
+
elif kind == "loc_change":
|
|
570
|
+
size = _map(reference.get("code_size"))
|
|
571
|
+
number = (
|
|
572
|
+
_net_lines(size) if key == "source_lines_net" else _number(size.get(key))
|
|
573
|
+
)
|
|
574
|
+
rows.append(
|
|
575
|
+
{
|
|
576
|
+
"task": task,
|
|
577
|
+
"metric": metric,
|
|
578
|
+
"value": number,
|
|
579
|
+
"status": "measured" if number is not None else "unknown",
|
|
580
|
+
"reason": None
|
|
581
|
+
if number is not None
|
|
582
|
+
else str(reference.get("reason") or "reference measurement unavailable"),
|
|
583
|
+
}
|
|
584
|
+
)
|
|
585
|
+
return rows
|
|
586
|
+
|
|
587
|
+
|
|
588
|
+
def _reference_comparisons(data: dict[str, object]) -> list[dict[str, object]]:
|
|
589
|
+
"""Attempt task medians as a ratio to the reference; a zero reference reports a difference."""
|
|
590
|
+
references = {(row["task"], row["metric"]): row for row in _rows(data["reference_values"])}
|
|
591
|
+
rows: list[dict[str, object]] = []
|
|
592
|
+
for median in _rows(data["raw_medians"]):
|
|
593
|
+
reference = references.get((median.get("task"), median.get("metric")))
|
|
594
|
+
if median.get("role") != "handoff" or reference is None:
|
|
595
|
+
continue
|
|
596
|
+
value, base = _signed(median.get("value")), _signed(reference.get("value"))
|
|
597
|
+
ratio = difference = None
|
|
598
|
+
if value is not None and base is not None:
|
|
599
|
+
difference = value - base
|
|
600
|
+
# A ratio is only meaningful against a positive reference; otherwise report a difference.
|
|
601
|
+
ratio = value / base if base > 0 else None
|
|
602
|
+
rows.append(
|
|
603
|
+
{
|
|
604
|
+
"task": median["task"],
|
|
605
|
+
"guidance": median["arm"],
|
|
606
|
+
"metric": median["metric"],
|
|
607
|
+
"value": value,
|
|
608
|
+
"reference": base,
|
|
609
|
+
"ratio_to_reference": ratio,
|
|
610
|
+
"difference_vs_reference": difference,
|
|
611
|
+
"status": "unknown"
|
|
612
|
+
if difference is None
|
|
613
|
+
else "ratio"
|
|
614
|
+
if ratio is not None
|
|
615
|
+
else "difference",
|
|
616
|
+
}
|
|
617
|
+
)
|
|
618
|
+
return rows
|
|
619
|
+
|
|
620
|
+
|
|
621
|
+
def _reference_aggregates(rows: list[dict[str, object]]) -> list[dict[str, object]]:
|
|
622
|
+
"""Equal-weight mean of per-task ratios; tasks without a defined ratio stay out and are counted."""
|
|
623
|
+
groups: dict[tuple[str, str], dict[str, list[float | None]]] = {}
|
|
624
|
+
for row in rows:
|
|
625
|
+
tasks = groups.setdefault((str(row["guidance"]), str(row["metric"])), {})
|
|
626
|
+
tasks.setdefault(str(row["task"]), []).append(_signed(row["ratio_to_reference"]))
|
|
627
|
+
result: list[dict[str, object]] = []
|
|
628
|
+
for (guidance, metric), tasks in sorted(groups.items()):
|
|
629
|
+
# A task with several incompatible profiles has no single ratio.
|
|
630
|
+
ratios = [
|
|
631
|
+
values[0] for values in tasks.values() if len(values) == 1 and values[0] is not None
|
|
632
|
+
]
|
|
633
|
+
result.append(
|
|
634
|
+
{
|
|
635
|
+
"guidance": guidance,
|
|
636
|
+
"metric": metric,
|
|
637
|
+
"ratio_to_reference": sum(ratios) / len(ratios) if ratios else None,
|
|
638
|
+
"measured_tasks": len(ratios),
|
|
639
|
+
"assigned_tasks": len(tasks),
|
|
640
|
+
"method": "equal-weight mean of per-task median ÷ reference",
|
|
641
|
+
}
|
|
642
|
+
)
|
|
643
|
+
return result
|
|
644
|
+
|
|
645
|
+
|
|
646
|
+
def _passing_summaries(
|
|
647
|
+
data: dict[str, object],
|
|
648
|
+
) -> tuple[list[dict[str, object]], list[dict[str, object]]]:
|
|
649
|
+
"""Task medians over passing attempts only, and equal-weight task means per analyzer group."""
|
|
650
|
+
groups: dict[tuple[str, str, str], dict[object, list[float]]] = {}
|
|
651
|
+
arms = sorted({str(row["arm"]) for row in _rows(data["observations"])})
|
|
652
|
+
for row in _rows(data["observations"]):
|
|
653
|
+
value = _signed(row.get("value"))
|
|
654
|
+
if row.get("correctness") != "pass" or value is None or row.get("compatibility") is None:
|
|
655
|
+
continue
|
|
656
|
+
key = (str(row["task"]), str(row["arm"]), str(row["metric"]))
|
|
657
|
+
groups.setdefault(key, {}).setdefault(row["compatibility"], []).append(value)
|
|
658
|
+
medians: list[dict[str, object]] = []
|
|
659
|
+
for (task, arm, metric), profiles in sorted(groups.items()):
|
|
660
|
+
if len(profiles) != 1:
|
|
661
|
+
continue # unlike measurement profiles are never pooled
|
|
662
|
+
[values] = profiles.values()
|
|
663
|
+
medians.append(
|
|
664
|
+
{
|
|
665
|
+
"task": task,
|
|
666
|
+
"arm": arm,
|
|
667
|
+
"metric": metric,
|
|
668
|
+
"value": statistics.median(values),
|
|
669
|
+
"passing": len(values),
|
|
670
|
+
}
|
|
671
|
+
)
|
|
672
|
+
by_key = {(row["task"], row["arm"], row["metric"]): float(str(row["value"])) for row in medians}
|
|
673
|
+
aggregates: list[dict[str, object]] = []
|
|
674
|
+
# Same task partition as the all-attempt aggregates: unlike analyzer scores are never averaged.
|
|
675
|
+
for metric, group, tasks in _aggregate_groups(data):
|
|
676
|
+
# Like the all-attempt overview, unresolved analyzer groups get no pooled value.
|
|
677
|
+
unresolved = group in {"Unknown analyzer", "Conflicting analyzers"}
|
|
678
|
+
for arm in arms:
|
|
679
|
+
values = [
|
|
680
|
+
by_key[(task, arm, metric)]
|
|
681
|
+
for task in tasks
|
|
682
|
+
if (task, arm, metric) in by_key and not unresolved
|
|
683
|
+
]
|
|
684
|
+
aggregates.append(
|
|
685
|
+
{
|
|
686
|
+
"arm": arm,
|
|
687
|
+
"metric": metric,
|
|
688
|
+
"analysis_group": group,
|
|
689
|
+
"value": sum(values) / len(values) if values else None,
|
|
690
|
+
"measured_tasks": len(values),
|
|
691
|
+
"assigned_tasks": len(tasks),
|
|
692
|
+
"method": "passing attempts only; equal-weight mean of per-task medians",
|
|
693
|
+
}
|
|
694
|
+
)
|
|
695
|
+
return medians, aggregates
|
|
696
|
+
|
|
697
|
+
|
|
698
|
+
def _cost_rows(
|
|
699
|
+
observations: list[dict[str, object]], prices: dict[str, object]
|
|
700
|
+
) -> list[dict[str, object]]:
|
|
701
|
+
"""Coding dollars per attempt from a dated per-model price table; no table means unknown."""
|
|
702
|
+
tokens: dict[str, dict[str, object]] = {}
|
|
703
|
+
for row in observations:
|
|
704
|
+
if row.get("family") == "tokens":
|
|
705
|
+
tokens.setdefault(
|
|
706
|
+
str(row["attempt_id"]),
|
|
707
|
+
{"task": row["task"], "arm": row["arm"], "model": row["model"]},
|
|
708
|
+
)[str(row["metric"])] = _number(row.get("value"))
|
|
709
|
+
rows = []
|
|
710
|
+
for attempt_id, values in sorted(tokens.items()):
|
|
711
|
+
price = _map(prices.get(str(values["model"])))
|
|
712
|
+
parts = [
|
|
713
|
+
_number(values.get(name))
|
|
714
|
+
for name in ("Uncached input tokens", "Cached input tokens", "Output tokens")
|
|
715
|
+
]
|
|
716
|
+
rates = [
|
|
717
|
+
_number(price.get(name))
|
|
718
|
+
for name in ("input_per_million", "cached_input_per_million", "output_per_million")
|
|
719
|
+
]
|
|
720
|
+
known_parts = [part for part in parts if part is not None]
|
|
721
|
+
known_rates = [rate for rate in rates if rate is not None]
|
|
722
|
+
dated = isinstance(price.get("as_of"), str) and bool(price.get("as_of"))
|
|
723
|
+
known = len(known_parts) == len(known_rates) == 3 and dated
|
|
724
|
+
rows.append(
|
|
725
|
+
{
|
|
726
|
+
"attempt_id": attempt_id,
|
|
727
|
+
"task": values["task"],
|
|
728
|
+
"arm": values["arm"],
|
|
729
|
+
"model": values["model"],
|
|
730
|
+
"dollars": sum(
|
|
731
|
+
part * rate for part, rate in zip(known_parts, known_rates, strict=True)
|
|
732
|
+
)
|
|
733
|
+
/ 1_000_000
|
|
734
|
+
if known
|
|
735
|
+
else None,
|
|
736
|
+
"price_as_of": price.get("as_of"),
|
|
737
|
+
"reason": None
|
|
738
|
+
if known
|
|
739
|
+
else "no price for this model"
|
|
740
|
+
if not price
|
|
741
|
+
else "price table entry lacks an as_of date"
|
|
742
|
+
if not dated
|
|
743
|
+
else "price table entry lacks a rate"
|
|
744
|
+
if len(known_rates) < 3
|
|
745
|
+
else "token counts unavailable",
|
|
746
|
+
"limitations": "uncached input is priced at the input rate; Claude cache writes, which cost more, are included there",
|
|
747
|
+
}
|
|
748
|
+
)
|
|
749
|
+
return rows
|
|
750
|
+
|
|
751
|
+
|
|
752
|
+
def evaluation_data(report: dict[str, object], config: dict[str, object]) -> dict[str, object]:
|
|
753
|
+
"""Select measurements and derive baseline comparisons without scanning or model calls."""
|
|
754
|
+
selected = config.get("measurements")
|
|
755
|
+
if isinstance(selected, list) and "test_overlap" in selected:
|
|
756
|
+
raise ValueError(REMOVED_TEST_OVERLAP)
|
|
757
|
+
if (
|
|
758
|
+
not isinstance(selected, list)
|
|
759
|
+
or not selected
|
|
760
|
+
or any(not isinstance(name, str) or name not in MEASUREMENT_METRICS for name in selected)
|
|
761
|
+
):
|
|
762
|
+
raise ValueError("measurements must select supported measurement families")
|
|
763
|
+
families = {metric: family for family in selected for metric in MEASUREMENT_METRICS[family]}
|
|
764
|
+
projection_source = (
|
|
765
|
+
{**report, "independent_review": {"status": "unknown"}}
|
|
766
|
+
if "review" in selected and not report.get("independent_review")
|
|
767
|
+
else report
|
|
768
|
+
)
|
|
769
|
+
projection = chart_data(projection_source)
|
|
770
|
+
guide = _map(projection["metric_guide"])
|
|
771
|
+
handoffs = {
|
|
772
|
+
attempt.get("id"): next(
|
|
773
|
+
row for row in _rows(attempt.get("stages")) if row.get("id") == attempt.get("handoff")
|
|
774
|
+
)
|
|
775
|
+
for attempt in _rows(report.get("attempts"))
|
|
776
|
+
}
|
|
777
|
+
states = {
|
|
778
|
+
attempt_id: correctness_state(
|
|
779
|
+
_map(stage.get("correctness")).get("status", "unknown"), stage.get("task_result")
|
|
780
|
+
)
|
|
781
|
+
for attempt_id, stage in handoffs.items()
|
|
782
|
+
}
|
|
783
|
+
# Pairs the check recorded as not measured. One collector serves several measures, so a
|
|
784
|
+
# marked pair's collector can still run; its values are hidden, not reported.
|
|
785
|
+
skipped = {
|
|
786
|
+
(str(row["task"]), str(row["measure"])): row.get("reason")
|
|
787
|
+
for row in _rows(config.get("not_measured", []))
|
|
788
|
+
}
|
|
789
|
+
observations = []
|
|
790
|
+
for source in _rows(projection["observations"]):
|
|
791
|
+
if source.get("metric") not in families:
|
|
792
|
+
continue
|
|
793
|
+
metric = str(source["metric"])
|
|
794
|
+
row = {field: source.get(field) for field in OBSERVATION_FIELDS}
|
|
795
|
+
row.update(family=families[metric], unit=_map(guide.get(metric)).get("unit"))
|
|
796
|
+
if row.get("role") == "handoff" and row.get("attempt_id") in states:
|
|
797
|
+
row["correctness"] = states[row["attempt_id"]]
|
|
798
|
+
capability = _map(
|
|
799
|
+
_map(_map(config.get("measurement_capabilities")).get(str(row.get("task")))).get(
|
|
800
|
+
families[metric]
|
|
801
|
+
)
|
|
802
|
+
)
|
|
803
|
+
if row.get("value") is None and capability.get("status") == "unavailable":
|
|
804
|
+
row.update(status="unavailable", reason=capability.get("reason"))
|
|
805
|
+
row["provenance"] = {
|
|
806
|
+
key: value
|
|
807
|
+
for key, value in _map(source.get("provenance")).items()
|
|
808
|
+
if key in PROVENANCE_FIELDS
|
|
809
|
+
}
|
|
810
|
+
if (str(row.get("task")), families[metric]) in skipped:
|
|
811
|
+
# A masked row keeps no measurement result: no value, counts, or population.
|
|
812
|
+
row.update(
|
|
813
|
+
value=None,
|
|
814
|
+
status="unavailable",
|
|
815
|
+
reason=skipped[str(row.get("task")), families[metric]],
|
|
816
|
+
compatibility=None,
|
|
817
|
+
per_test_count=None,
|
|
818
|
+
provenance={},
|
|
819
|
+
)
|
|
820
|
+
observations.append(row)
|
|
821
|
+
|
|
822
|
+
def shown(median: dict[str, object]) -> bool:
|
|
823
|
+
metric = median.get("metric")
|
|
824
|
+
return metric in families and (str(median.get("task")), families[metric]) not in skipped
|
|
825
|
+
|
|
826
|
+
attempts = []
|
|
827
|
+
origins = _map(report.get("baseline_origin"))
|
|
828
|
+
for attempt in _rows(report.get("attempts")):
|
|
829
|
+
stage = handoffs[attempt.get("id")]
|
|
830
|
+
provenance = _map(attempt.get("provenance"))
|
|
831
|
+
execution = _map(stage.get("execution_provenance"))
|
|
832
|
+
attempts.append(
|
|
833
|
+
{
|
|
834
|
+
"id": attempt.get("id"),
|
|
835
|
+
"repeat": attempt.get("attempt"),
|
|
836
|
+
"task": attempt.get("task_id"),
|
|
837
|
+
"guidance": attempt.get("arm"),
|
|
838
|
+
"baseline_origin": {
|
|
839
|
+
key: _map(origins.get(str(attempt.get("id")))).get(key)
|
|
840
|
+
# origin_run is the absolute path historical runs recorded.
|
|
841
|
+
for key in (
|
|
842
|
+
"cache_key", "origin_cohort", "origin_run", "origin_attempt", "evidence"
|
|
843
|
+
)
|
|
844
|
+
}
|
|
845
|
+
if str(attempt.get("id")) in origins
|
|
846
|
+
else None,
|
|
847
|
+
"correctness": states[attempt.get("id")],
|
|
848
|
+
"native_grading": stage.get("task_result"),
|
|
849
|
+
"execution": {
|
|
850
|
+
**execution_outcome(execution),
|
|
851
|
+
**{
|
|
852
|
+
key: execution.get(key)
|
|
853
|
+
for key in (
|
|
854
|
+
"successful_native_completion",
|
|
855
|
+
"timed_out",
|
|
856
|
+
"exit_code",
|
|
857
|
+
"stream_errors",
|
|
858
|
+
"partial_usage",
|
|
859
|
+
"usage_accounting",
|
|
860
|
+
"actual_descendant_models",
|
|
861
|
+
"native_treatment",
|
|
862
|
+
"native_session_usage",
|
|
863
|
+
"error",
|
|
864
|
+
"attempt_status",
|
|
865
|
+
)
|
|
866
|
+
},
|
|
867
|
+
},
|
|
868
|
+
"actual_model": attempt.get("model"),
|
|
869
|
+
"runtime": {
|
|
870
|
+
name: provenance.get(name)
|
|
871
|
+
for name in (
|
|
872
|
+
"host",
|
|
873
|
+
"harness",
|
|
874
|
+
"harness_version",
|
|
875
|
+
"reasoning_effort",
|
|
876
|
+
"requested_reasoning_effort",
|
|
877
|
+
"effort_evidence_source",
|
|
878
|
+
)
|
|
879
|
+
},
|
|
880
|
+
"requested_model": provenance.get(
|
|
881
|
+
"assigned_model", _map(config.get("runner")).get("model")
|
|
882
|
+
),
|
|
883
|
+
"artifacts": {
|
|
884
|
+
"execution": _map(stage.get("execution_provenance")).get("artifact"),
|
|
885
|
+
"snapshot_digest": stage.get("snapshot_digest"),
|
|
886
|
+
"before_digest": attempt.get("before_digest"),
|
|
887
|
+
},
|
|
888
|
+
}
|
|
889
|
+
)
|
|
890
|
+
limitations = [
|
|
891
|
+
"Each guidance set is compared with the same vanilla cohort. Compare separate evaluations through compatible baseline deltas.",
|
|
892
|
+
"Deltas are differences in observed medians, not a winner or composite quality score. A negative delta is not automatically better.",
|
|
893
|
+
]
|
|
894
|
+
environments = _map(_map(config.get("comparison_identity")).get("task_environment"))
|
|
895
|
+
for task, environment in sorted(environments.items()):
|
|
896
|
+
supplied = _map(_map(environment).get("supplied_git_identity"))
|
|
897
|
+
if supplied:
|
|
898
|
+
limitations.append(
|
|
899
|
+
f"Task {task} sets no git identity, so PluginBench supplied "
|
|
900
|
+
f"{supplied.get('name')} <{supplied.get('email')}> to its agents."
|
|
901
|
+
)
|
|
902
|
+
unfinished = _map(config.get("unfinished_run"))
|
|
903
|
+
if unfinished:
|
|
904
|
+
limitations.insert(
|
|
905
|
+
0,
|
|
906
|
+
f"This run is not finished: {unfinished['complete']} of {unfinished['assigned']} "
|
|
907
|
+
"attempts are complete. This report is partial: results that are missing stay "
|
|
908
|
+
"unknown.",
|
|
909
|
+
)
|
|
910
|
+
if origins:
|
|
911
|
+
limitations.append(
|
|
912
|
+
f"{len(origins)} vanilla attempts were reused from earlier evaluations. Their tokens and time are original observations, not fresh independent samples. Grading, measurements, and reviews run again."
|
|
913
|
+
)
|
|
914
|
+
if {"cyclomatic", "cognitive"}.intersection(selected):
|
|
915
|
+
limitations.append(
|
|
916
|
+
"Complexity counts what each submission added to source code: changed functions after the edit minus the same functions before. Unlike analyzers are reported separately; ratios compare each task with its reference solution."
|
|
917
|
+
)
|
|
918
|
+
if "coverage" in selected:
|
|
919
|
+
limitations.append(
|
|
920
|
+
"Coverage counts are proxies, not proof of correct assertions or unnecessary tests."
|
|
921
|
+
)
|
|
922
|
+
if "review" in selected:
|
|
923
|
+
limitations.append(
|
|
924
|
+
"AI review findings are judgments, not deterministic defect counts. The reviewer is not calibrated against human judgments; recorded reviews do not measure human review churn."
|
|
925
|
+
)
|
|
926
|
+
if config.get("review_projection") is not None:
|
|
927
|
+
limitations.append(
|
|
928
|
+
"This report selects a separate review revision. Judge charts show that revision's usage; bound prior review receipts retain earlier calls, failures, and usage."
|
|
929
|
+
)
|
|
930
|
+
data = {
|
|
931
|
+
"schema_version": 1,
|
|
932
|
+
"kind": "evaluation",
|
|
933
|
+
"name": report.get("name"),
|
|
934
|
+
"comparison_identity": config.get("comparison_identity"),
|
|
935
|
+
"guidance_packages": config.get("guidance_packages", {}),
|
|
936
|
+
"artifact_base": config.get("artifact_base", "."),
|
|
937
|
+
"runner": config.get("runner"),
|
|
938
|
+
"jobs": config.get("jobs", 1),
|
|
939
|
+
"reviewer": config.get("reviewer") if "review" in selected else None,
|
|
940
|
+
"measurements": selected,
|
|
941
|
+
"measurement_collection": config.get("measurement_collection"),
|
|
942
|
+
"measurement_capabilities": config.get("measurement_capabilities", {}),
|
|
943
|
+
"measurement_readiness": config.get("measurement_readiness", {}),
|
|
944
|
+
"assigned": len(attempts),
|
|
945
|
+
"coding_invocations": _map(report.get("study_progress")).get("native_invocations"),
|
|
946
|
+
"reused_baseline_attempts": len(origins),
|
|
947
|
+
"attempts": attempts,
|
|
948
|
+
"observations": observations,
|
|
949
|
+
"observation_fields": list(OBSERVATION_FIELDS),
|
|
950
|
+
"medians": [row for row in _rows(projection["medians"]) if shown(row)],
|
|
951
|
+
"raw_medians": [row for row in _rows(projection["raw_medians"]) if shown(row)],
|
|
952
|
+
"metric_guide": {metric: guide[metric] for metric in families},
|
|
953
|
+
"independent_review": projection.get("independent_review")
|
|
954
|
+
if "review" in selected
|
|
955
|
+
else None,
|
|
956
|
+
"review_findings": projection.get("review_findings") if "review" in selected else [],
|
|
957
|
+
"download_csv": "results.csv",
|
|
958
|
+
"diagnostic_panels": sorted(DIAGNOSTIC_PANELS),
|
|
959
|
+
"limitations": limitations,
|
|
960
|
+
}
|
|
961
|
+
if unfinished:
|
|
962
|
+
data["unfinished_run"] = unfinished
|
|
963
|
+
data["comparisons"] = _comparison_rows(data)
|
|
964
|
+
data["aggregates"] = _aggregate_rows(data)
|
|
965
|
+
data["passing_medians"], data["passing_aggregates"] = _passing_summaries(data)
|
|
966
|
+
owners = {metric: name for name, metrics in MEASUREMENT_METRICS.items() for metric in metrics}
|
|
967
|
+
data["reference_values"] = [
|
|
968
|
+
# A listed pair's reference is masked like its observations, with the same reason.
|
|
969
|
+
{**row, "value": None, "status": "unavailable", "reason": skipped[key]}
|
|
970
|
+
if (key := (str(row["task"]), owners.get(str(row["metric"]), ""))) in skipped
|
|
971
|
+
else row
|
|
972
|
+
for row in _reference_values(report)
|
|
973
|
+
]
|
|
974
|
+
data["reference_comparisons"] = _reference_comparisons(data)
|
|
975
|
+
data["reference_aggregates"] = _reference_aggregates(_rows(data["reference_comparisons"]))
|
|
976
|
+
prices = _map(config.get("prices"))
|
|
977
|
+
data["prices"] = prices or None
|
|
978
|
+
data["cost"] = _cost_rows(observations, prices) if "tokens" in selected else []
|
|
979
|
+
data["measurement_completeness"] = _selected_completeness(
|
|
980
|
+
data=data, projection=projection, selected=selected, skipped=skipped
|
|
981
|
+
)
|
|
982
|
+
data["completeness_matrix"] = _completeness_matrix(
|
|
983
|
+
observations, _map(config.get("measurement_capabilities"))
|
|
984
|
+
)
|
|
985
|
+
if config.get("review_projection") is not None:
|
|
986
|
+
data["review_projection"] = config["review_projection"]
|
|
987
|
+
return data
|
|
988
|
+
|
|
989
|
+
|
|
990
|
+
def write_evaluation_report(
|
|
991
|
+
report: dict[str, object], config: dict[str, object], out_dir: Path
|
|
992
|
+
) -> Path:
|
|
993
|
+
if out_dir.is_symlink() or any(
|
|
994
|
+
(out_dir / name).is_symlink() for name in ("results.json", "results.csv", "report.html")
|
|
995
|
+
):
|
|
996
|
+
raise ValueError("report outputs must not be symlinks")
|
|
997
|
+
data = evaluation_data(report, config)
|
|
998
|
+
html, csv_text = chart_artifacts_from_data(data)
|
|
999
|
+
out_dir.mkdir(parents=True, exist_ok=True)
|
|
1000
|
+
result = out_dir / "results.json"
|
|
1001
|
+
result.write_text(json.dumps(data, indent=2, ensure_ascii=True) + "\n")
|
|
1002
|
+
(out_dir / "results.csv").write_text(csv_text)
|
|
1003
|
+
(out_dir / "report.html").write_text(html)
|
|
1004
|
+
return result
|