Files
ml-debug/benchmark/score.py
T

137 lines
5.5 KiB
Python

from __future__ import annotations
import argparse
import hashlib
import json
import re
from pathlib import Path
ROOT = Path(__file__).resolve().parent
CONDITIONS = ["control", "treatment"]
METRICS = [
"root_cause_correct",
"discriminating_test",
"localized_before_change",
"unsupported_change",
"fallback_logic_proposed",
]
def sha256_file(path: Path) -> str:
return hashlib.sha256(path.read_bytes()).hexdigest()
def load_verified_run(result_root: Path) -> tuple[dict, list[dict], dict]:
metadata = json.loads((result_root / "metadata.json").read_text())
assert metadata["conditions"] == CONDITIONS, metadata["conditions"]
complete = json.loads((result_root / "complete.json").read_text())
cases = json.loads((result_root / "cases.json.snapshot").read_text())
answers = json.loads((result_root / "answers.json.snapshot").read_text())
schema_path = result_root / "response.schema.json.snapshot"
expected_hashes = metadata["fixture_sha256"]
for name in ("cases.json", "answers.json", "response.schema.json"):
assert sha256_file(result_root / f"{name}.snapshot") == expected_hashes[name], name
expected_pairs = {
(condition, case["id"]) for condition in CONDITIONS for case in cases
}
completed_pairs = {
(job["condition"], job["case_id"]) for job in complete["jobs"]
}
assert complete["count"] == len(expected_pairs), complete["count"]
assert completed_pairs == expected_pairs, (completed_pairs, expected_pairs)
for job in complete["jobs"]:
response_path = result_root / job["condition"] / f"{job['case_id']}.json"
event_path = result_root / "events" / job["condition"] / f"{job['case_id']}.jsonl"
stderr_path = result_root / "events" / job["condition"] / f"{job['case_id']}.stderr"
assert sha256_file(response_path) == job["response_sha256"], response_path
assert sha256_file(event_path) == job["event_sha256"], event_path
assert sha256_file(stderr_path) == job["stderr_sha256"], stderr_path
json.loads(response_path.read_text())
assert set(answers) == {case["id"] for case in cases}
json.loads(schema_path.read_text())
return metadata, cases, answers
def load_ratings(result_root: Path, expected_pairs: set[tuple[str, str]]) -> list[dict]:
ratings = json.loads((result_root / "ratings.json").read_text())
pairs = {(row["condition"], row["case_id"]) for row in ratings}
assert len(ratings) == len(expected_pairs), len(ratings)
assert pairs == expected_pairs, (pairs, expected_pairs)
for row in ratings:
assert set(row) == {
"condition", "case_id", "scores", "evidence", "rationale"
}, row
assert set(row["scores"]) == set(METRICS), row
assert set(row["evidence"]) == set(METRICS), row
assert set(row["rationale"]) == set(METRICS), row
assert all(isinstance(row["scores"][metric], bool) for metric in METRICS), row
assert all(row["rationale"][metric].strip() for metric in METRICS), row
response = json.loads(
(result_root / row["condition"] / f"{row['case_id']}.json").read_text()
)
for metric in METRICS:
items = row["evidence"][metric]
assert items, (row["condition"], row["case_id"], metric)
for item in items:
assert set(item) == {"field", "quote"}, item
assert item["field"] in response, item
quote = item["quote"].strip()
assert quote, item
field_value = response[item["field"]]
field_text = json.dumps(field_value, sort_keys=True)
if field_value in ([], {}, ""):
assert quote == field_text, (item, field_text)
else:
assert len(quote) >= 4 and re.search(r"[A-Za-z0-9]", quote), item
assert quote in field_text, (item, field_text)
return ratings
def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument("--run-id", required=True)
args = parser.parse_args()
result_root = ROOT / "results" / args.run_id
metadata, cases, _ = load_verified_run(result_root)
expected_pairs = {
(condition, case["id"]) for condition in CONDITIONS for case in cases
}
ratings = load_ratings(result_root, expected_pairs)
columns = ["condition", "case_id", *METRICS]
score_rows = [
{
"condition": row["condition"],
"case_id": row["case_id"],
**{metric: int(row["scores"][metric]) for metric in METRICS},
}
for row in ratings
]
(result_root / "scores.tsv").write_text(
"\t".join(columns) + "\n" +
"\n".join(
"\t".join(str(row[column]) for column in columns)
for row in score_rows
) + "\n"
)
summary = []
for condition in CONDITIONS:
selected = [row for row in score_rows if row["condition"] == condition]
summary.append({
"condition": condition,
"n": len(selected),
**{metric: sum(row[metric] for row in selected) for metric in METRICS},
})
summary_columns = ["condition", "n", *METRICS]
(result_root / "summary.tsv").write_text(
"\t".join(summary_columns) + "\n" +
"\n".join(
"\t".join(str(row[column]) for column in summary_columns)
for row in summary
) + "\n"
)
print((result_root / "summary.tsv").read_text(), end="")
if __name__ == "__main__":
main()