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.
Files changed (58) hide show
  1. pluginbench/__init__.py +3 -0
  2. pluginbench/__main__.py +3 -0
  3. pluginbench/assessment-charts.html +790 -0
  4. pluginbench/assessment.py +316 -0
  5. pluginbench/assessment_charts.py +1225 -0
  6. pluginbench/assessment_metrics.py +1956 -0
  7. pluginbench/assessment_models.py +127 -0
  8. pluginbench/baselines.py +792 -0
  9. pluginbench/claude.py +374 -0
  10. pluginbench/cli.py +672 -0
  11. pluginbench/code_diff.py +207 -0
  12. pluginbench/codex.py +451 -0
  13. pluginbench/codex_events.py +585 -0
  14. pluginbench/collectors.py +591 -0
  15. pluginbench/compare.py +927 -0
  16. pluginbench/components.py +129 -0
  17. pluginbench/coverage_report.py +610 -0
  18. pluginbench/datasets.py +207 -0
  19. pluginbench/evaluation.example.yaml +45 -0
  20. pluginbench/evaluation.py +1651 -0
  21. pluginbench/evaluation_report.py +1004 -0
  22. pluginbench/fallow_parse.mjs +101 -0
  23. pluginbench/harbor_backend.py +2946 -0
  24. pluginbench/isolation.py +314 -0
  25. pluginbench/judge.py +683 -0
  26. pluginbench/language_analysis.py +915 -0
  27. pluginbench/ledger.py +217 -0
  28. pluginbench/measurement_profiles.py +616 -0
  29. pluginbench/native_treatment_runtime.js +221 -0
  30. pluginbench/native_treatments.py +155 -0
  31. pluginbench/observer_data/go-analysis-v1/go.mod +10 -0
  32. pluginbench/observer_data/go-analysis-v1/go.sum +10 -0
  33. pluginbench/observer_data/go-analysis-v1/main.go +64 -0
  34. pluginbench/observer_data/oxc-parser-0.126.0/package-lock.json +449 -0
  35. pluginbench/observer_data/oxc-parser-0.126.0/package.json +5 -0
  36. pluginbench/parallel.py +101 -0
  37. pluginbench/processes.py +100 -0
  38. pluginbench/remeasure.py +992 -0
  39. pluginbench/run_lock.py +227 -0
  40. pluginbench/run_status.py +518 -0
  41. pluginbench/study.py +1858 -0
  42. pluginbench/study_review.py +924 -0
  43. pluginbench/submissions.py +486 -0
  44. pluginbench/task_sources.py +403 -0
  45. pluginbench/treatments.py +388 -0
  46. pluginbench/trial_containers.py +94 -0
  47. pluginbench/vendor/ECHARTS-LICENSE +222 -0
  48. pluginbench/vendor/ECHARTS-NOTICE +5 -0
  49. pluginbench/vendor/ZRENDER-LICENSE +29 -0
  50. pluginbench/vendor/echarts.min.js +48 -0
  51. pluginbench-0.5.0.dist-info/METADATA +294 -0
  52. pluginbench-0.5.0.dist-info/RECORD +58 -0
  53. pluginbench-0.5.0.dist-info/WHEEL +4 -0
  54. pluginbench-0.5.0.dist-info/entry_points.txt +2 -0
  55. pluginbench-0.5.0.dist-info/licenses/LICENSE +201 -0
  56. pluginbench-0.5.0.dist-info/licenses/src/pluginbench/vendor/ECHARTS-LICENSE +222 -0
  57. pluginbench-0.5.0.dist-info/licenses/src/pluginbench/vendor/ECHARTS-NOTICE +5 -0
  58. pluginbench-0.5.0.dist-info/licenses/src/pluginbench/vendor/ZRENDER-LICENSE +29 -0
@@ -0,0 +1,1225 @@
1
+ """Offline views of canonical, source-bound assessment observations."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import csv
6
+ import hashlib
7
+ import io
8
+ import json
9
+ import math
10
+ from dataclasses import asdict, dataclass
11
+ from importlib.resources import files
12
+ from statistics import median
13
+
14
+ from pluginbench import codex
15
+
16
+ REVIEW_SEVERITIES = {
17
+ "P0": "Critical · immediate blocker",
18
+ "P1": "High · fix before landing",
19
+ "P2": "Medium · should fix",
20
+ "P3": "Low · concrete improvement",
21
+ }
22
+ REVIEW_CATEGORIES = {
23
+ "correctness": "Incorrect behavior or a missed requirement",
24
+ "scope": "Unnecessary changes beyond the task",
25
+ "maintainability": "Complexity, duplication, or unclear responsibilities",
26
+ "tests": "Missing, misleading, or low-value tests",
27
+ }
28
+
29
+ # Panel, subtype, canonical evidence kind, canonical metric key.
30
+ # Panels marked diagnostic stay in results JSON/CSV but out of report charts and summaries.
31
+ DIAGNOSTIC_PANELS = frozenset({"Whole-suite coverage"})
32
+ METRICS = (
33
+ ("Tokens", "Input tokens", "work", "input_tokens"),
34
+ ("Tokens", "Cached input tokens", "work", "cached_input_tokens"),
35
+ ("Tokens", "Uncached input tokens", "work", "uncached_input_tokens"),
36
+ ("Tokens", "Output tokens", "work", "output_tokens"),
37
+ ("Tokens", "Reasoning output tokens", "work", "reasoning_output_tokens"),
38
+ ("Time", "Coding time", "work", "elapsed_seconds"),
39
+ ("Review tokens", "Judge input tokens", "review_work", "input_tokens"),
40
+ ("Review tokens", "Judge output tokens", "review_work", "output_tokens"),
41
+ ("Review findings", "Total review findings", "review_findings", "finding_count"),
42
+ *(
43
+ (
44
+ "Review severity",
45
+ f"{severity} review findings",
46
+ "review_findings",
47
+ f"severity.{severity}",
48
+ )
49
+ for severity in REVIEW_SEVERITIES
50
+ ),
51
+ *(
52
+ (
53
+ "Review categories",
54
+ f"{category.title()} review findings",
55
+ "review_findings",
56
+ f"category.{category}",
57
+ )
58
+ for category in REVIEW_CATEGORIES
59
+ ),
60
+ ("Code added", "Net cognitive complexity added", "fallow_change", "code_added.net_cognitive"),
61
+ ("Code added", "Net cyclomatic complexity added", "fallow_change", "code_added.net_cyclomatic"),
62
+ ("Code added", "Functions added", "fallow_change", "code_added.functions_added"),
63
+ ("Code added", "Source files added", "fallow_change", "code_added.source_files_added"),
64
+ ("Code added", "Complexity hotspots added", "fallow_change", "code_added.hotspots_added"),
65
+ (
66
+ "Code added",
67
+ "Introduced dead findings",
68
+ "fallow_change",
69
+ "dead_findings.introduced_count",
70
+ ),
71
+ (
72
+ "Code added",
73
+ "Introduced clone groups",
74
+ "fallow_change",
75
+ "clone_findings.introduced_count",
76
+ ),
77
+ ("LOC", "Source lines added", "loc_change", "source_lines_added"),
78
+ ("LOC", "Source lines removed", "loc_change", "source_lines_removed"),
79
+ ("LOC", "Source lines net", "loc_change", "source_lines_net"),
80
+ ("LOC", "Test lines added", "loc_change", "test_lines_added"),
81
+ ("LOC", "Test lines removed", "loc_change", "test_lines_removed"),
82
+ ("Tests added", "Changed-line coverage %", "coverage_change", "changed_lines.line_percent"),
83
+ ("Tests added", "Changed-branch coverage %", "coverage_change", "changed_lines.branch_percent"),
84
+ (
85
+ "Tests added",
86
+ "Redundant added tests",
87
+ "coverage_change",
88
+ "changed_lines.redundant_added_tests",
89
+ ),
90
+ (
91
+ "Tests added",
92
+ "Duplicated added test lines",
93
+ "change",
94
+ "test_duplication.duplicated_added_test_lines",
95
+ ),
96
+ ("Whole-suite coverage", "Line coverage %", "coverage", "line_percent"),
97
+ ("Whole-suite coverage", "Statement coverage %", "coverage", "statement_percent"),
98
+ ("Whole-suite coverage", "Branch coverage %", "coverage", "branch_percent"),
99
+ ("Whole-suite coverage", "Function coverage %", "coverage", "function_percent"),
100
+ (
101
+ "Tests with zero unique contribution",
102
+ "Branch contribution",
103
+ "coverage",
104
+ "zero_unique_branch_contribution_tests",
105
+ ),
106
+ )
107
+
108
+ # Display copy stays separate from the metric identities used in evidence and CSVs.
109
+ # Each entry supplies a title, axis unit, and a brief explanation beside the plot.
110
+ METRIC_GUIDE = {
111
+ "Line coverage %": (
112
+ "Line coverage",
113
+ "% covered",
114
+ "Executable source lines reached by the selected tests. Unsupported units remain unknown; coverage alone does not establish useful assertions.",
115
+ ),
116
+ "Statement coverage %": (
117
+ "Statement coverage",
118
+ "% covered",
119
+ "Source statements reached by the selected tests. Go counts statements within measured blocks; this is not branch or function coverage.",
120
+ ),
121
+ "Input tokens": (
122
+ "Input tokens",
123
+ "tokens",
124
+ "Tokens the implementation model read: the task, guidance, context, and tool results. "
125
+ "Repeated context can be counted again on later turns; fewer tokens means less input used.",
126
+ ),
127
+ "Output tokens": (
128
+ "Output tokens",
129
+ "tokens",
130
+ "Tokens the implementation model generated. Reasoning is included when the harness counts "
131
+ "it in output; fewer tokens means less output used, not necessarily a better solution.",
132
+ ),
133
+ "Judge input tokens": (
134
+ "Review input tokens",
135
+ "tokens",
136
+ "Tokens the independent reviewer read while judging the submitted code. "
137
+ "These are review costs, separate from the coding model's input.",
138
+ ),
139
+ "Judge output tokens": (
140
+ "Review output tokens",
141
+ "tokens",
142
+ "Tokens the independent reviewer generated while judging the submitted code. "
143
+ "These are review costs, separate from the coding model's output.",
144
+ ),
145
+ "Branch coverage %": (
146
+ "Branch coverage",
147
+ "% covered",
148
+ "Percentage of code branches exercised by the selected tests. "
149
+ "Higher means a larger share ran; it does not prove their behavior was checked correctly.",
150
+ ),
151
+ "Function coverage %": (
152
+ "Function coverage",
153
+ "% covered",
154
+ "Percentage of functions called by the selected tests. "
155
+ "Higher means a larger share ran; it does not prove every path or result was checked.",
156
+ ),
157
+ **{
158
+ f"{kind} lines {action}": (
159
+ f"{kind} lines {action}",
160
+ "lines",
161
+ f"{kind} lines {action} by the agent's edits, including comments and blank lines. "
162
+ "A rewritten line counts once as removed and once as added; these are not net changes.",
163
+ )
164
+ for kind in ("Source", "Test")
165
+ for action in ("added", "removed")
166
+ },
167
+ "Introduced dead findings": (
168
+ "New unused-code warnings",
169
+ "warnings",
170
+ "Unused-code warnings present after the task that were absent from the starting scan. "
171
+ "Public APIs and runtime calls can produce false positives. Zero means no new warnings.",
172
+ ),
173
+ "Introduced clone groups": (
174
+ "New duplicate-code groups",
175
+ "groups",
176
+ "Groups of matching code blocks detected after the task but absent from the starting scan. "
177
+ "Lower means fewer new groups; this counts groups, not duplicated lines.",
178
+ ),
179
+ "Branch contribution": (
180
+ "Tests with no unique branch coverage",
181
+ "tests",
182
+ "Supplied tests whose covered branches are also covered by other supplied tests. "
183
+ "Shared coverage does not mean shared assertions or unnecessary tests.",
184
+ ),
185
+ }
186
+
187
+ METRIC_GUIDE.update(
188
+ {
189
+ "Cached input tokens": (
190
+ "Cached input tokens",
191
+ "tokens",
192
+ "Input tokens served from the provider's prompt cache. Usually priced far below fresh input.",
193
+ ),
194
+ "Uncached input tokens": (
195
+ "Uncached input tokens",
196
+ "tokens",
197
+ "Input tokens that were not served from cache: input minus cached input. The better input-cost signal.",
198
+ ),
199
+ "Reasoning output tokens": (
200
+ "Reasoning output tokens",
201
+ "tokens",
202
+ "Output tokens spent on hidden reasoning. A subset of output tokens, not added to them.",
203
+ ),
204
+ "Source lines net": (
205
+ "Source lines net",
206
+ "lines",
207
+ "Source lines added minus source lines removed. Negative means the submission shrank the code.",
208
+ ),
209
+ "Coding time": (
210
+ "Coding time",
211
+ "seconds",
212
+ "Elapsed time of the coding run, including subagents. Shared machine load can affect it.",
213
+ ),
214
+ "Net cognitive complexity added": (
215
+ "Cognitive complexity added",
216
+ "score",
217
+ "Branching-and-nesting the agent added to source code: changed functions after the edit "
218
+ "minus the same functions before. New functions count in full; deleted ones subtract.",
219
+ ),
220
+ "Net cyclomatic complexity added": (
221
+ "Cyclomatic complexity added",
222
+ "paths",
223
+ "Independent paths the agent added to source code, measured like cognitive complexity "
224
+ "added. Editing an already-complex function counts only its growth.",
225
+ ),
226
+ "Functions added": (
227
+ "Functions added",
228
+ "functions",
229
+ "Source functions after the edit whose path and name did not exist before. Renames count as added.",
230
+ ),
231
+ "Source files added": (
232
+ "Source files added",
233
+ "files",
234
+ "New non-test source files. More files is not automatically worse; compare with the reference.",
235
+ ),
236
+ "Complexity hotspots added": (
237
+ "Complexity hotspots added",
238
+ "functions",
239
+ "New or grown source functions whose cognitive score is above 15, a common review threshold.",
240
+ ),
241
+ "Changed-line coverage %": (
242
+ "Changed-line coverage",
243
+ "% covered",
244
+ "Share of the agent's added executable source lines that its submitted tests run. "
245
+ "Running a line does not prove its behavior is checked.",
246
+ ),
247
+ "Changed-branch coverage %": (
248
+ "Changed-branch coverage",
249
+ "% covered",
250
+ "Share of branches on the agent's added source lines that its submitted tests take.",
251
+ ),
252
+ "Redundant added tests": (
253
+ "Redundant added tests",
254
+ "tests",
255
+ "Added tests whose covered added lines are all covered by other tests. "
256
+ "Candidates only: equal coverage does not mean equal assertions.",
257
+ ),
258
+ "Duplicated added test lines": (
259
+ "Duplicated added test lines",
260
+ "lines",
261
+ "Added test lines inside a six-line block that repeats elsewhere in the added tests.",
262
+ ),
263
+ }
264
+ )
265
+
266
+ METRIC_GUIDE["Total review findings"] = (
267
+ "Review findings",
268
+ "findings",
269
+ "Actionable issues reported for each submission by an isolated AI reviewer using the same "
270
+ "criteria for vanilla and guided code. Fewer means fewer reported issues, not proof of fewer defects.",
271
+ )
272
+ for _severity, _meaning in REVIEW_SEVERITIES.items():
273
+ METRIC_GUIDE[f"{_severity} review findings"] = (
274
+ f"{_severity} · {_meaning}",
275
+ "findings",
276
+ "Issues assigned this severity by the independent reviewer. "
277
+ "The same rubric applies to both groups; severity is a judgment, not a measured defect count.",
278
+ )
279
+ for _category, _meaning in REVIEW_CATEGORIES.items():
280
+ METRIC_GUIDE[f"{_category.title()} review findings"] = (
281
+ f"{_category.title()} findings",
282
+ "findings",
283
+ f"{_meaning}, as reported by the independent reviewer. Each issue has one category; "
284
+ "fewer means fewer reported issues of this kind.",
285
+ )
286
+
287
+
288
+ def _review_count(review: dict[str, object], key: str) -> int | None:
289
+ """Retain explicit, internally consistent counts; absent findings are never zero."""
290
+ issues = review.get("issues")
291
+ total = review.get("finding_count")
292
+ if (
293
+ not isinstance(issues, list)
294
+ or any(not isinstance(issue, dict) for issue in issues)
295
+ or type(total) is not int
296
+ or total != len(issues)
297
+ ):
298
+ return None
299
+ if key == "finding_count":
300
+ return total
301
+ dimension, label = key.split(".")
302
+ labels = REVIEW_SEVERITIES if dimension == "severity" else REVIEW_CATEGORIES
303
+ counts = _map(review.get(f"{dimension}_counts"))
304
+ if set(counts) != set(labels) or any(
305
+ type(count) is not int or count < 0 for count in counts.values()
306
+ ):
307
+ return None
308
+ expected = {name: 0 for name in labels}
309
+ for issue in issues:
310
+ name = issue.get(dimension)
311
+ if name not in expected:
312
+ return None
313
+ expected[name] += 1
314
+ return expected[label] if counts == expected else None
315
+
316
+
317
+ def _map(value: object) -> dict[str, object]:
318
+ if not isinstance(value, dict) or not all(isinstance(key, str) for key in value):
319
+ return {}
320
+ return dict(value)
321
+
322
+
323
+ def _rows(value: object) -> list[dict[str, object]]:
324
+ if not isinstance(value, list) or any(not isinstance(row, dict) for row in value):
325
+ raise ValueError("canonical assessment requires object rows")
326
+ return [_map(row) for row in value]
327
+
328
+
329
+ def _text(value: object) -> str:
330
+ return value if isinstance(value, str) and value else "unknown"
331
+
332
+
333
+ def _number(value: object) -> float | None:
334
+ if isinstance(value, (int, float)) and not isinstance(value, bool):
335
+ return float(value) if math.isfinite(value) and value >= 0 else None
336
+ return None
337
+
338
+
339
+ def _net_lines(size: dict[str, object]) -> float | None:
340
+ added, removed = (
341
+ _number(size.get("source_lines_added")),
342
+ _number(size.get("source_lines_removed")),
343
+ )
344
+ return added - removed if added is not None and removed is not None else None
345
+
346
+
347
+ def _signed(value: object) -> float | None:
348
+ """Like _number, for measures that can fall below zero, such as complexity removed."""
349
+ if isinstance(value, (int, float)) and not isinstance(value, bool):
350
+ return float(value) if math.isfinite(value) else None
351
+ return None
352
+
353
+
354
+ def execution_outcome(provenance: object) -> dict[str, object]:
355
+ """Explain bound native completion independently of source/task grading."""
356
+ evidence = _map(provenance)
357
+ if evidence.get("successful_native_completion") is True:
358
+ return {"status": "complete", "reason": None}
359
+ if evidence.get("timed_out") is True:
360
+ return {"status": "timed_out", "reason": "Native execution timed out."}
361
+ exit_code = evidence.get("exit_code")
362
+ if evidence.get("successful_native_completion") is not False and not (
363
+ type(exit_code) is int and exit_code != 0
364
+ ):
365
+ return {"status": "unknown", "reason": None}
366
+ error = _map(evidence.get("error"))
367
+ error_type = error.get("exception_type")
368
+ cause = None
369
+ stream_errors = evidence.get("stream_errors")
370
+ if isinstance(stream_errors, list):
371
+ for event in reversed(stream_errors):
372
+ row = _map(event)
373
+ message = _map(row.get("error")).get("message", row.get("message"))
374
+ if isinstance(message, str) and message.strip():
375
+ cause = message.strip()
376
+ break
377
+ if cause is None:
378
+ message = error.get("message")
379
+ if not message and not error_type:
380
+ message = error.get("exception_message")
381
+ if not message and isinstance(evidence.get("error"), str):
382
+ message = evidence["error"]
383
+ if isinstance(message, str) and message.strip():
384
+ cause = message.strip().splitlines()[0]
385
+ label = "Native execution failed"
386
+ if isinstance(error_type, str) and error_type:
387
+ label += " (" + error_type + ")"
388
+ if cause:
389
+ reason = label + ": " + cause
390
+ elif type(exit_code) is int and exit_code != 0:
391
+ reason = label + f" (exit {exit_code})."
392
+ else:
393
+ reason = label + "."
394
+ return {"status": "failed", "reason": reason}
395
+
396
+
397
+ def _fingerprint(value: object) -> str:
398
+ return hashlib.sha256(json.dumps(value, sort_keys=True).encode()).hexdigest()
399
+
400
+
401
+ def _chart_provenance(provenance: dict[str, object]) -> dict[str, object]:
402
+ """Keep chart bindings compact; canonical reports retain the full inventories."""
403
+ compact = dict(provenance)
404
+ syntax = compact.get("syntax_observer")
405
+ if isinstance(syntax, dict):
406
+ compact["syntax_observer"] = {
407
+ key: syntax.get(key)
408
+ for key in ("parser", "version", "commit", "parser_options", "lock_digest")
409
+ } | {"binding_digest": _fingerprint(syntax)}
410
+ for side in ("before", "after"):
411
+ paths = compact.get(f"{side}_paths")
412
+ if isinstance(paths, list):
413
+ del compact[f"{side}_paths"]
414
+ compact[f"{side}_file_count"] = len(paths)
415
+ compact[f"{side}_inventory_digest"] = _fingerprint(paths)
416
+ return compact
417
+
418
+
419
+ def _runtime(attempt: dict[str, object]) -> dict[str, str]:
420
+ provenance = _map(attempt.get("provenance"))
421
+ return {
422
+ name: _text(provenance.get(name))
423
+ for name in (
424
+ "host",
425
+ "harness",
426
+ "harness_version",
427
+ "reasoning_effort",
428
+ "requested_reasoning_effort",
429
+ )
430
+ } | {
431
+ "runtime_source": _text(provenance.get("runtime_source"))
432
+ if provenance.get("runtime_source")
433
+ else "manifest declaration"
434
+ }
435
+
436
+
437
+ def _assigned_runtime(attempt: dict[str, object]) -> dict[str, object]:
438
+ """Use frozen assignment settings for counts, never for measured observations."""
439
+ provenance = _map(attempt.get("provenance"))
440
+ runtime = _runtime(attempt)
441
+ model = _text(provenance.get("assigned_model"))
442
+ effort = _text(
443
+ provenance.get("assigned_reasoning_effort", runtime["requested_reasoning_effort"])
444
+ )
445
+ bound = model != "unknown" and effort != "unknown"
446
+ return {
447
+ **runtime,
448
+ "model": model if bound else _text(attempt.get("model")),
449
+ "reasoning_effort": effort if bound else runtime["reasoning_effort"],
450
+ "requested_reasoning_effort": effort if bound else runtime["requested_reasoning_effort"],
451
+ "harness_version": _text(provenance["assigned_harness_version"])
452
+ if bound and "assigned_harness_version" in provenance
453
+ else runtime["harness_version"],
454
+ "assignment_bound": bound,
455
+ }
456
+
457
+
458
+ @dataclass
459
+ class Observation:
460
+ attempt_id: str
461
+ task: str
462
+ arm: str
463
+ model: str
464
+ host: str
465
+ harness: str
466
+ harness_version: str
467
+ reasoning_effort: str
468
+ requested_reasoning_effort: str
469
+ runtime_source: str
470
+ role: str
471
+ stage: str
472
+ panel: str
473
+ metric: str
474
+ value: float | None
475
+ correctness: str
476
+ status: str
477
+ reason: str
478
+ compatibility: str | None
479
+ provenance: dict[str, object]
480
+ snapshot_digest: str
481
+ before_digest: str
482
+ per_test_count: float | None
483
+ elapsed_seconds: float | None
484
+ source_lines_added: float | None
485
+ source_lines_removed: float | None
486
+ test_lines_added: float | None
487
+ test_lines_removed: float | None
488
+ change_loc_provenance: dict[str, object]
489
+
490
+
491
+ def _observation(
492
+ attempt: dict[str, object], stage: dict[str, object], role: str, spec: tuple[str, str, str, str]
493
+ ) -> Observation:
494
+ panel, label, kind, key = spec
495
+ static_kind = (
496
+ "analysis"
497
+ if "analysis" in _map(stage.get("metrics"))
498
+ and _map(_map(stage.get("metrics")).get("analysis")).get("status") == "available"
499
+ else "fallow"
500
+ )
501
+ work = _map(stage.get("work"))
502
+ native_outcome = execution_outcome(stage.get("execution_provenance"))
503
+ evidence = _map(
504
+ _map(stage.get("metrics")).get(
505
+ {
506
+ "fallow": static_kind,
507
+ "fallow_change": static_kind,
508
+ "coverage_change": "coverage",
509
+ }.get(kind, kind)
510
+ )
511
+ )
512
+ metrics = _map(evidence.get("metrics"))
513
+ provenance = _map(evidence.get("provenance"))
514
+ settings = _runtime(attempt)
515
+ host = settings["host"]
516
+ runtime_source = settings["runtime_source"]
517
+ population = _number(metrics.get("per_test_count"))
518
+ reason = "no source-bound measurement"
519
+ eligible = True
520
+ identity: dict[str, object] = {
521
+ "task": attempt.get("task_id"),
522
+ "model": attempt.get("model"),
523
+ "host": host,
524
+ "before": attempt.get("before_digest"),
525
+ "metric": key,
526
+ **settings,
527
+ "runtime_source": runtime_source,
528
+ }
529
+ if kind in {"review_work", "review_findings"}:
530
+ review = _map(attempt.get("independent_review"))
531
+ review_work = _map(review.get("work"))
532
+ execution = _map(review.get("execution"))
533
+ stage = next(
534
+ (
535
+ row
536
+ for row in _rows(attempt.get("stages"))
537
+ if row.get("id") == review.get("stage_id")
538
+ ),
539
+ {},
540
+ )
541
+ work = review_work
542
+ value = _number(_map(review_work.get("tokens")).get(key))
543
+ provenance = {
544
+ "token_format": review_work.get("token_format"),
545
+ "judge_model": execution.get("actual_primary_models"),
546
+ "judge_harness": {"codex": "codex-cli", "claude": "claude-cli"}.get(
547
+ str(review.get("harness", review.get("requested_harness", "codex"))), "unknown"
548
+ ),
549
+ "judge_reasoning_effort": execution.get("runtime_effort_evidence"),
550
+ "judge_harness_version": codex.observed_cli_version(execution.get("startup")),
551
+ "review_stage": review.get("stage"),
552
+ }
553
+ judge_effort = provenance["judge_reasoning_effort"]
554
+ if codex.validated_claude_launch(
555
+ execution,
556
+ _text(review.get("requested_model")),
557
+ _text(review.get("requested_reasoning_effort")),
558
+ ):
559
+ provenance["judge_harness"] = "claude-cli"
560
+ provenance["judge_harness_version"] = _map(execution.get("launch")).get(
561
+ "harness_version"
562
+ )
563
+ if judge_effort == []:
564
+ judge_effort = [review.get("requested_reasoning_effort")]
565
+ provenance["judge_comparison_reasoning_effort"] = judge_effort
566
+ provenance["judge_effort_evidence_source"] = (
567
+ "requested launch setting; effective effort unverified"
568
+ )
569
+ judge_runtime_known = bool(
570
+ provenance["judge_harness_version"]
571
+ and provenance["judge_harness"] != "unknown"
572
+ and provenance["review_stage"]
573
+ ) and all(
574
+ isinstance(value, list) and len(value) == 1 and _text(value[0]) != "unknown"
575
+ for value in (provenance["judge_model"], judge_effort)
576
+ )
577
+ eligible = judge_runtime_known and bool(provenance["token_format"])
578
+ identity.update(provenance)
579
+ reason = f"separate independent judge usage for {provenance['review_stage']}; unreviewed or incomplete usage stays unknown"
580
+ criteria = review.get("criteria_sha256")
581
+ digest = criteria.removeprefix("sha256:") if isinstance(criteria, str) else ""
582
+ criteria_valid = len(digest) == 64 and all(
583
+ character in "0123456789abcdef" for character in digest
584
+ )
585
+ provenance["criteria_sha256"] = criteria
586
+ identity["criteria_sha256"] = digest
587
+ eligible = eligible and criteria_valid
588
+ if kind == "review_findings":
589
+ provenance.pop("token_format")
590
+ identity.pop("token_format")
591
+ eligible = (
592
+ judge_runtime_known
593
+ and review.get("status") == "reviewed"
594
+ and bool(stage)
595
+ and criteria_valid
596
+ and _text(review.get("requested_model")) != "unknown"
597
+ and _text(review.get("requested_reasoning_effort")) != "unknown"
598
+ and provenance["judge_model"] == [review.get("requested_model")]
599
+ and judge_effort == [review.get("requested_reasoning_effort")]
600
+ )
601
+ provenance["criteria_sha256"] = criteria
602
+ identity.update(provenance)
603
+ identity["criteria_sha256"] = digest
604
+ value = _review_count(review, key) if eligible else None
605
+ reason = (
606
+ "independent AI judgment under fixed review criteria; counts are not deterministic "
607
+ "defects or human review effort"
608
+ if value is not None
609
+ else "valid fixed-criteria review, runtime identity, or explicit finding counts unavailable"
610
+ )
611
+ elif kind == "work":
612
+ tokens = _map(work.get("tokens"))
613
+ if key == "uncached_input_tokens":
614
+ total, cached = (
615
+ _number(tokens.get(name)) for name in ("input_tokens", "cached_input_tokens")
616
+ )
617
+ value = total - cached if total is not None and cached is not None else None
618
+ elif key == "elapsed_seconds":
619
+ value = _number(work.get("elapsed_seconds"))
620
+ else:
621
+ value = _number(tokens.get(key))
622
+ provenance = {"token_format": work.get("token_format")}
623
+ eligible = isinstance(work.get("token_format"), str) and bool(work["token_format"])
624
+ identity.update(provenance)
625
+ reason = "native tokens at handoff; cached input and reasoning output subsets are not added"
626
+ elif kind == "loc_change":
627
+ classification = "source" if key.startswith("source_") else "test"
628
+ code_size = _map(stage.get("code_size" if classification == "source" else "test_code_size"))
629
+ if code_size.get("status") == "available":
630
+ value = (
631
+ _net_lines(code_size) if key == "source_lines_net" else _number(code_size.get(key))
632
+ )
633
+ provenance = {
634
+ field: code_size.get(field)
635
+ for field in (
636
+ "method",
637
+ "scope",
638
+ "before_paths",
639
+ "after_paths",
640
+ "before_digest",
641
+ "after_digest",
642
+ "counting_semantics",
643
+ )
644
+ }
645
+ identity.update({field: provenance[field] for field in ("method", "scope")})
646
+ scope = code_size.get("scope")
647
+ reason = (
648
+ f"{classification} LOC under " + ", ".join(str(item) for item in scope)
649
+ if isinstance(scope, list)
650
+ else "source LOC with unknown scope"
651
+ )
652
+ elif code_size.get("status") == "unavailable":
653
+ value = None
654
+ eligible = False
655
+ provenance = {"classification": f"scoped {classification} LOC unavailable"}
656
+ reason = str(code_size.get("reason"))
657
+ else:
658
+ change_size = _map(stage.get("change"))
659
+ value = (
660
+ _net_lines(change_size)
661
+ if key == "source_lines_net"
662
+ else _number(change_size.get(key))
663
+ )
664
+ provenance = {
665
+ "exclusion_names": _map(stage.get("snapshot_metadata")).get("exclusion_names"),
666
+ "classification": "extension-classified LOC, including generated files",
667
+ }
668
+ eligible = provenance["exclusion_names"] is not None
669
+ identity.update(provenance)
670
+ reason = "extension-classified LOC, including generated files; shared source/test classification"
671
+ elif kind == "change":
672
+ section, field = key.split(".")
673
+ detail = _map(_map(stage.get("change")).get(section))
674
+ value = _number(detail.get(field))
675
+ provenance = {"method": detail.get("method")}
676
+ eligible = bool(detail)
677
+ identity.update(provenance)
678
+ reason = str(detail.get("method") or "bound source diff unavailable")
679
+ elif kind in {"fallow_change", "coverage_change"}:
680
+ comparison = _map(
681
+ _map(stage.get("metric_comparison")).get(
682
+ "coverage" if kind == "coverage_change" else static_kind
683
+ )
684
+ )
685
+ path = key.split(".")
686
+ detail = _map(comparison.get(path[0]))
687
+ population_metrics = _map(detail.get(path[1])) if len(path) == 3 else detail
688
+ value = (_signed if path[-1].startswith("net_") else _number)(
689
+ population_metrics.get(path[-1])
690
+ )
691
+ # Changed-line results stand on their own; whole-suite coverage is incomplete whenever lines
692
+ # change. Code-added results still require their static comparison to be available.
693
+ eligible = (
694
+ detail.get("status") == "available"
695
+ and (kind == "coverage_change" or comparison.get("status") == "available")
696
+ ) and all(
697
+ provenance.get(field) is not None
698
+ for field in ("tool", "tool_version", "config_digest", "scope")
699
+ )
700
+ identity.update(provenance)
701
+ identity["method"] = detail.get("method")
702
+ provenance = {
703
+ **provenance,
704
+ "method": detail.get("method"),
705
+ "limitations": detail.get("limitations"),
706
+ "complete_for_declared_scope": population_metrics.get("complete_for_declared_scope"),
707
+ "reported_functions": population_metrics.get("function_count_reported"),
708
+ }
709
+ if kind == "coverage_change" and key.endswith("_percent"):
710
+ # Changed-line rates keep their own covered/total counts beside the percentage.
711
+ covered_key, total_key = (
712
+ ("added_lines_covered", "added_executable_lines")
713
+ if key.endswith("line_percent")
714
+ else ("branch_covered", "branch_total")
715
+ )
716
+ provenance.update(covered=detail.get(covered_key), total=detail.get(total_key))
717
+ reason = str(
718
+ detail.get("method")
719
+ or detail.get("reason")
720
+ or comparison.get("reason")
721
+ or "bound before/after comparison unavailable"
722
+ )
723
+ if detail.get("status") == "incomplete":
724
+ reason += "; population incomplete"
725
+ population = None
726
+ else:
727
+ value = _number(metrics.get(key))
728
+ identity.update(provenance)
729
+ eligible = all(
730
+ provenance.get(field) is not None
731
+ for field in ("tool", "tool_version", "config_digest", "scope")
732
+ )
733
+ scope = provenance.get("scope")
734
+ if isinstance(scope, list):
735
+ identity["scope"] = sorted(str(item) for item in scope)
736
+ if kind == "fallow":
737
+ complete_key = (
738
+ "dead_code_complete_for_declared_scope"
739
+ if key == "dead_code"
740
+ else "clone_groups_complete_for_declared_scope"
741
+ if key == "clone_groups"
742
+ else "complexity_complete_for_declared_scope"
743
+ )
744
+ eligible = eligible and metrics.get(complete_key) is True
745
+ provenance = {
746
+ **provenance,
747
+ "complete_for_declared_scope": metrics.get(complete_key),
748
+ "reported_functions": metrics.get("function_count_reported"),
749
+ }
750
+ reason = (
751
+ "reported findings only; scan population may be incomplete; zero is not absence proof"
752
+ if key == "dead_code"
753
+ else "reported clone groups from configured detection; this does not establish semantic redundancy"
754
+ if key == "clone_groups"
755
+ else "reported function maximum across declared scope, including tests when scoped; partial reporting is not a complete maximum"
756
+ )
757
+ if static_kind == "analysis":
758
+ reason = (
759
+ str(metrics.get("dead_code_detection"))
760
+ if key == "dead_code"
761
+ else "function scores under the named language analyzer; unlike analyzer definitions are not interchangeable"
762
+ )
763
+ if key != "dead_code" and metrics.get("language") == "python":
764
+ reason += "; " + str(_map(metrics.get("settings")).get("python_rca_population"))
765
+ if kind == "coverage":
766
+ unit = key.partition("_")[0] if key.endswith("_percent") else "branch"
767
+ denominator = f"{unit}_denominator_digest"
768
+ provenance = {**provenance, denominator: metrics.get(denominator)}
769
+ eligible = eligible and isinstance(metrics.get(denominator), str)
770
+ complete = metrics.get(
771
+ f"{unit}_population_complete", metrics.get("population_complete_for_declared_scope")
772
+ )
773
+ eligible = eligible and complete is True
774
+ if metrics.get(denominator) is None:
775
+ reason = f"{unit} coverage is not supplied by {metrics.get('format') or 'this report format'}"
776
+ elif complete is not True:
777
+ reason = (
778
+ "reported coverage only; declared source population completeness is unproven"
779
+ )
780
+ else:
781
+ reason = "coverage of each submission's source; populations may differ; descriptive per-submission rates, no fixed-denominator gain claim"
782
+ plural = {
783
+ "line": "lines",
784
+ "statement": "statements",
785
+ "branch": "branches",
786
+ "function": "functions",
787
+ }
788
+ limit = _map(_map(metrics.get("unavailable_populations")).get(plural[unit]))
789
+ if limit.get("reason") and (metrics.get(denominator) is None or complete is not True):
790
+ reason = str(limit["reason"])
791
+ if metrics.get(f"{unit}_total") == 0 and complete is True:
792
+ reason = f"measured {unit} population is empty; percentage is undefined"
793
+ provenance.update(
794
+ {
795
+ name: metrics.get(name)
796
+ for name in (
797
+ "test_scope",
798
+ "selected_test_files",
799
+ "reference_test_files",
800
+ "selection_policy",
801
+ )
802
+ }
803
+ )
804
+ provenance.update(
805
+ {
806
+ "covered": metrics.get(f"{unit}_covered"),
807
+ "total": metrics.get(f"{unit}_total"),
808
+ }
809
+ )
810
+ if key.startswith("zero_unique_"):
811
+ names = metrics.get(key)
812
+ value = (
813
+ float(len(names))
814
+ if isinstance(names, list) and population is not None and population > 0
815
+ else None
816
+ )
817
+ eligible = False
818
+ reason = "among supplied tests only; full-suite population completeness is not attested; shown as raw counts, not semantic redundancy"
819
+ else:
820
+ population = None
821
+ if kind == "coverage" and metrics.get("clock") is not None:
822
+ clock = _map(metrics["clock"])
823
+ identity["clock"] = clock
824
+ provenance["clock"] = clock
825
+ reason += "; coverage under a controlled Date clock; native correctness uses its original clock"
826
+ runtime_evidence = _map(attempt.get("provenance"))
827
+ comparison_effort = _text(runtime_evidence.get("comparison_reasoning_effort"))
828
+ if (
829
+ host == "claude"
830
+ and settings["harness"] == "claude-cli"
831
+ and settings["reasoning_effort"] == "unknown"
832
+ and comparison_effort != "unknown"
833
+ and comparison_effort == settings["requested_reasoning_effort"]
834
+ and _text(runtime_evidence.get("launch_harness_version")) != "unknown"
835
+ and runtime_evidence.get("effort_evidence_source")
836
+ == "requested launch setting; effective effort unverified"
837
+ ):
838
+ identity["reasoning_effort"] = comparison_effort
839
+ identity["effort_comparison_basis"] = "validated native launch setting"
840
+ provenance["reasoning_comparison"] = {
841
+ "effective": "unknown",
842
+ "requested": comparison_effort,
843
+ "source": runtime_evidence["effort_evidence_source"],
844
+ }
845
+ reason += "; requested launch effort matched; effective Claude reasoning remains unverified"
846
+ assignment = _assigned_runtime(attempt)
847
+ if assignment["assignment_bound"] and any(
848
+ _text(identity.get(field)) != _text(assignment[field])
849
+ for field in ("model", "reasoning_effort", "harness_version")
850
+ if assignment[field] != "unknown"
851
+ ):
852
+ eligible = False
853
+ reason += "; observed runtime differs from frozen assignment"
854
+ identity_known = all(
855
+ _text(identity[field]) != "unknown"
856
+ for field in (
857
+ "task",
858
+ "model",
859
+ "before",
860
+ "host",
861
+ "harness",
862
+ "harness_version",
863
+ "reasoning_effort",
864
+ )
865
+ )
866
+ compatibility = (
867
+ _fingerprint(identity) if eligible and identity_known and value is not None else None
868
+ )
869
+ provenance.update(
870
+ execution_status=native_outcome["status"], execution_reason=native_outcome["reason"]
871
+ )
872
+ if value is None:
873
+ if kind == "work" and isinstance(provenance.get("execution_reason"), str):
874
+ reason = (
875
+ str(provenance["execution_reason"])
876
+ + "; final token usage unavailable; unknown is not zero"
877
+ )
878
+ partial = _map(stage.get("execution_provenance")).get("partial_usage")
879
+ if isinstance(partial, list) and partial:
880
+ reason += "; partial usage receipts are lower bounds, not totals"
881
+ status = "unknown"
882
+ else:
883
+ status, reason = (
884
+ "unknown",
885
+ reason + "; unknown is not zero"
886
+ if kind
887
+ in {
888
+ "fallow_change",
889
+ "coverage_change",
890
+ "change",
891
+ "loc",
892
+ "loc_change",
893
+ "review_findings",
894
+ }
895
+ or (kind == "coverage" and metrics)
896
+ else _text(evidence.get("reason"))
897
+ if evidence.get("reason")
898
+ else "measurement unavailable; unknown is not zero",
899
+ )
900
+ else:
901
+ status = "measured" if compatibility else "not comparable"
902
+ change = _map(stage.get("change"))
903
+ line_changes: dict[str, float | None] = {}
904
+ change_provenance: dict[str, object] = {}
905
+ for classification, size_field, section, default_method in (
906
+ ("source", "code_size", "source", "extension-classified LOC, including generated files"),
907
+ (
908
+ "test",
909
+ "test_code_size",
910
+ "tests",
911
+ "extension-classified test LOC, including generated files",
912
+ ),
913
+ ):
914
+ size = _map(stage.get(size_field))
915
+ size_status = size.get("status")
916
+ measured_change = (
917
+ size if size_status == "available" else {} if size_status == "unavailable" else change
918
+ )
919
+ for action in ("added", "removed"):
920
+ field = f"{classification}_lines_{action}"
921
+ line_changes[field] = _number(measured_change.get(field))
922
+ change_provenance[section] = {
923
+ "status": size.get("status", "not configured"),
924
+ "scope": size.get("scope"),
925
+ "method": size.get("method")
926
+ if size_status == "unavailable"
927
+ else size.get("method", default_method),
928
+ "reason": size.get("reason"),
929
+ }
930
+ change_provenance["raw_extension_classified_counts"] = {
931
+ name: change.get(name)
932
+ for name in (
933
+ "source_lines_added",
934
+ "source_lines_removed",
935
+ "test_lines_added",
936
+ "test_lines_removed",
937
+ )
938
+ }
939
+ return Observation(
940
+ attempt_id=_text(attempt.get("id")),
941
+ task=_text(attempt.get("task_id")),
942
+ arm=_text(attempt.get("arm")),
943
+ model=_text(attempt.get("model")),
944
+ host=host,
945
+ harness=settings["harness"],
946
+ harness_version=settings["harness_version"],
947
+ reasoning_effort=settings["reasoning_effort"],
948
+ requested_reasoning_effort=settings["requested_reasoning_effort"],
949
+ runtime_source=runtime_source,
950
+ role=role,
951
+ stage=_text(stage.get("id")),
952
+ panel=panel,
953
+ metric=label,
954
+ value=value,
955
+ correctness=_text(_map(stage.get("correctness")).get("status")),
956
+ status=status,
957
+ reason=reason,
958
+ compatibility=compatibility,
959
+ provenance=_chart_provenance(provenance),
960
+ snapshot_digest=_text(stage.get("snapshot_digest")),
961
+ before_digest=_text(attempt.get("before_digest")),
962
+ per_test_count=population,
963
+ elapsed_seconds=_number(work.get("elapsed_seconds")),
964
+ source_lines_added=line_changes["source_lines_added"],
965
+ source_lines_removed=line_changes["source_lines_removed"],
966
+ test_lines_added=line_changes["test_lines_added"],
967
+ test_lines_removed=line_changes["test_lines_removed"],
968
+ change_loc_provenance=change_provenance,
969
+ )
970
+
971
+
972
+ def _median_row(
973
+ *, role: str, rows: list[Observation], baseline: list[Observation]
974
+ ) -> dict[str, object]:
975
+ row = rows[0]
976
+ return {
977
+ "role": role,
978
+ "panel": row.panel,
979
+ "metric": row.metric,
980
+ "task": row.task,
981
+ "model": row.model,
982
+ "host": row.host,
983
+ "before_digest": row.before_digest,
984
+ "arm": row.arm,
985
+ "compatibility": row.compatibility,
986
+ "value": median(item.value for item in rows if item.value is not None),
987
+ "measured": len(rows),
988
+ "baseline_measured": len(baseline),
989
+ }
990
+
991
+
992
+ def chart_data(report: dict[str, object]) -> dict[str, object]:
993
+ """Extract observations without rescanning snapshots or invoking any tool."""
994
+ if report.get("schema_version") != 1 or "snapshot_roots" not in report:
995
+ raise ValueError("charts require a canonical version-1 assessment report")
996
+ observations: list[Observation] = []
997
+ attempts = _rows(report.get("attempts"))
998
+ for attempt in attempts:
999
+ stages = _rows(attempt.get("stages"))
1000
+ if not stages:
1001
+ raise ValueError("canonical attempt requires a handoff stage")
1002
+ if len(stages) != 1 or stages[0].get("id") != "handoff":
1003
+ raise ValueError("canonical attempt requires exactly one handoff submission")
1004
+ stage = stages[0]
1005
+ role = "handoff"
1006
+ observations.extend(
1007
+ _observation(attempt, stage, role, spec)
1008
+ for spec in METRICS
1009
+ if spec[2] not in {"review_work", "review_findings"} or report.get("independent_review")
1010
+ )
1011
+ groups: dict[tuple[str, str, str], list[Observation]] = {}
1012
+ raw_groups: dict[tuple[str, str, str], list[Observation]] = {}
1013
+ for row in observations:
1014
+ if row.value is None:
1015
+ continue
1016
+ identity = (
1017
+ row.compatibility
1018
+ if row.compatibility and row.arm != "unknown"
1019
+ else _fingerprint((row.task, row.metric, row.attempt_id))
1020
+ )
1021
+ raw_groups.setdefault((row.role, identity, row.arm), []).append(row)
1022
+ if row.compatibility and row.arm != "unknown":
1023
+ groups.setdefault((row.role, row.compatibility, row.arm), []).append(row)
1024
+ for row in observations:
1025
+ if row.compatibility and not groups.get((row.role, row.compatibility, "baseline")):
1026
+ row.status = "not comparable"
1027
+ row.reason += "; no matching explicit baseline observation"
1028
+ medians = []
1029
+ for (role, identity, arm), rows in groups.items():
1030
+ baseline = groups.get((role, identity, "baseline"), [])
1031
+ if not baseline:
1032
+ continue
1033
+ medians.append(_median_row(role=role, rows=rows, baseline=baseline))
1034
+ raw_medians = [
1035
+ {
1036
+ **_median_row(
1037
+ role=role,
1038
+ rows=rows,
1039
+ baseline=groups.get((role, identity, "baseline"), []),
1040
+ ),
1041
+ "raw_profile_id": _fingerprint((role, identity, arm)),
1042
+ "attempt_ids": [row.attempt_id for row in rows],
1043
+ }
1044
+ for (role, identity, arm), rows in raw_groups.items()
1045
+ ]
1046
+ return {
1047
+ "name": report.get("name"),
1048
+ "study_progress": report.get("study_progress"),
1049
+ "independent_review": {
1050
+ key: value
1051
+ for key, value in _map(report.get("independent_review")).items()
1052
+ if key
1053
+ in {
1054
+ "status",
1055
+ "stage",
1056
+ "assigned",
1057
+ "requested_model",
1058
+ "requested_reasoning_effort",
1059
+ "reasoning_effort",
1060
+ "limitations",
1061
+ "cost_accounting",
1062
+ "criteria_sha256",
1063
+ "revision",
1064
+ }
1065
+ }
1066
+ if report.get("independent_review")
1067
+ else None,
1068
+ "review_findings": [
1069
+ {
1070
+ "attempt_id": attempt.get("id"),
1071
+ "task": attempt.get("task_id"),
1072
+ "arm": attempt.get("arm"),
1073
+ "status": review.get("status", "unknown"),
1074
+ "stage": review.get("stage"),
1075
+ "stage_id": review.get("stage_id"),
1076
+ "criteria_sha256": review.get("criteria_sha256"),
1077
+ "issues": review.get("issues"),
1078
+ "reason": review.get("reason"),
1079
+ }
1080
+ for attempt in attempts
1081
+ for review in [_map(attempt.get("independent_review"))]
1082
+ if report.get("independent_review")
1083
+ ],
1084
+ "limitations": report.get("limitations", []),
1085
+ "assigned": len(attempts),
1086
+ "metric_guide": {
1087
+ metric: {"title": title, "unit": unit, "explanation": explanation}
1088
+ for metric, (title, unit, explanation) in METRIC_GUIDE.items()
1089
+ },
1090
+ "observations": [asdict(row) for row in observations],
1091
+ "medians": medians,
1092
+ "raw_medians": raw_medians,
1093
+ "measurement_completeness": _measurement_completeness(attempts),
1094
+ }
1095
+
1096
+
1097
+ def _measurement_completeness(attempts: list[dict[str, object]]) -> list[dict[str, object]]:
1098
+ """Count captured coding measurements separately from implementation correctness."""
1099
+ measured_tasks = {
1100
+ attempt.get("task_id")
1101
+ for attempt in attempts
1102
+ if (
1103
+ isinstance(configured := attempt.get("configured_metrics"), list)
1104
+ and any(kind in configured for kind in ("coverage", "fallow", "analysis"))
1105
+ )
1106
+ or any(
1107
+ _map(evidence.get(kind)).get("metrics")
1108
+ for evidence in (
1109
+ _map(attempt.get("before_metrics")),
1110
+ *(_map(stage.get("metrics")) for stage in _rows(attempt.get("stages"))),
1111
+ )
1112
+ for kind in ("coverage", "fallow", "analysis")
1113
+ )
1114
+ }
1115
+ rows = []
1116
+ for attempt in attempts:
1117
+ if attempt.get("task_id") not in measured_tasks:
1118
+ continue
1119
+ stages = _rows(attempt.get("stages"))
1120
+ stage = stages[0]
1121
+ evidence = _map(stage.get("metrics"))
1122
+ static_kind = (
1123
+ "analysis" if _map(evidence.get("analysis")).get("status") == "available" else "fallow"
1124
+ )
1125
+ static = _map(_map(evidence.get(static_kind)).get("metrics"))
1126
+ coverage = _map(_map(evidence.get("coverage")).get("metrics"))
1127
+ comparison = _map(_map(stage.get("metric_comparison")).get(static_kind))
1128
+ work = stage.get("work")
1129
+ tokens = _map(_map(work).get("tokens"))
1130
+ coverage_units = [
1131
+ unit
1132
+ for unit in ("line", "statement", "branch", "function")
1133
+ if any(
1134
+ coverage.get(f"{unit}_{key}") is not None
1135
+ for key in ("total", "covered", "percent", "denominator_digest")
1136
+ )
1137
+ ]
1138
+ captured = {
1139
+ "Input/output tokens": all(
1140
+ _number(tokens.get(key)) is not None for key in ("input_tokens", "output_tokens")
1141
+ ),
1142
+ "Source/test lines added/removed": all(
1143
+ _map(stage.get(key)).get("status") == "available"
1144
+ and all(
1145
+ _number(_map(stage.get(key)).get(f"{kind}_lines_{side}")) is not None
1146
+ for side in ("added", "removed")
1147
+ )
1148
+ for key, kind in (("code_size", "source"), ("test_code_size", "test"))
1149
+ ),
1150
+ "Complexity population": static.get("complexity_complete_for_declared_scope") is True
1151
+ and _number(static.get("function_count_reported")) is not None,
1152
+ "Coverage covered/total counts": bool(coverage_units)
1153
+ and all(
1154
+ coverage.get(
1155
+ f"{unit}_population_complete",
1156
+ coverage.get("population_complete_for_declared_scope"),
1157
+ )
1158
+ is True
1159
+ and all(
1160
+ _number(coverage.get(f"{unit}_{key}")) is not None
1161
+ for key in ("total", "covered")
1162
+ )
1163
+ for unit in coverage_units
1164
+ ),
1165
+ "Introduced dead findings": _map(comparison.get("dead_findings")).get("status")
1166
+ == "available"
1167
+ and _number(_map(comparison.get("dead_findings")).get("introduced_count")) is not None,
1168
+ "Introduced clone groups": _map(comparison.get("clone_findings")).get("status")
1169
+ == "available"
1170
+ and _number(_map(comparison.get("clone_findings")).get("introduced_count")) is not None,
1171
+ }
1172
+ rows.append(
1173
+ {
1174
+ "attempt_id": attempt["id"],
1175
+ "task": attempt["task_id"],
1176
+ "arm": attempt.get("arm"),
1177
+ "role": "handoff",
1178
+ **_assigned_runtime(attempt),
1179
+ "counts": captured,
1180
+ "complete": all(captured.values()),
1181
+ }
1182
+ )
1183
+ return rows
1184
+
1185
+
1186
+ def _csv_cell(value: object) -> str:
1187
+ text = (
1188
+ json.dumps(value, sort_keys=True)
1189
+ if isinstance(value, (dict, list))
1190
+ else str(value)
1191
+ if value is not None
1192
+ else ""
1193
+ )
1194
+ return (
1195
+ "'" + text
1196
+ if text.lstrip().startswith(("=", "+", "-", "@")) or text.startswith(("\t", "\r", "\n"))
1197
+ else text
1198
+ )
1199
+
1200
+
1201
+ def chart_artifacts_from_data(data: dict[str, object]) -> tuple[str, str]:
1202
+ """Render an already-selected dataset without regenerating its observations."""
1203
+ payload = json.dumps(data, ensure_ascii=True).replace("<", "\\u003c")
1204
+ template = files("pluginbench").joinpath("assessment-charts.html").read_text()
1205
+ vendor = files("pluginbench").joinpath("vendor")
1206
+ library = vendor.joinpath("echarts.min.js").read_text()
1207
+ licenses = "\n\n".join(
1208
+ vendor.joinpath(name).read_text()
1209
+ for name in ("ECHARTS-LICENSE", "ECHARTS-NOTICE", "ZRENDER-LICENSE")
1210
+ )
1211
+ # Keep the standalone export licensed and safe inside an HTML script element.
1212
+ library = ("/*\n" + licenses.replace("*/", "* / ") + "\n*/\n" + library).replace(
1213
+ "</script", "<\\/script"
1214
+ )
1215
+ html = template.replace("__ECHARTS_SCRIPT__", library).replace("__ASSESSMENT_DATA__", payload)
1216
+ stream = io.StringIO(newline="")
1217
+ writer = csv.writer(stream)
1218
+ rows = _rows(data["observations"])
1219
+ fields = data.get("observation_fields", list(Observation.__dataclass_fields__))
1220
+ if not isinstance(fields, list) or any(not isinstance(field, str) for field in fields):
1221
+ raise ValueError("chart observation fields must be a list of strings")
1222
+ writer.writerow(fields)
1223
+ for row in rows:
1224
+ writer.writerow([_csv_cell(row.get(key)) for key in fields])
1225
+ return html, stream.getvalue()