| |
| """Run and independently validate the SG08/SP08 diagnostic evaluations.""" |
|
|
| from __future__ import annotations |
|
|
| import argparse |
| import hashlib |
| import json |
| import shlex |
| import subprocess |
| import sys |
| from pathlib import Path |
| from typing import Any |
|
|
|
|
| SPECS = { |
| "SG08": { |
| "config": "configs/evaluation/semantic_segmentation/SG08_camvid_cross_dataset_diagnostic.json", |
| "evaluator": "scripts/stages/evaluate_sg08_camvid_diagnostic.py", |
| }, |
| "SP08": { |
| "config": "configs/evaluation/speech_kws/SP08_speech_commands_yes_no_diagnostic.json", |
| "evaluator": "scripts/stages/evaluate_sp08_speech_commands_diagnostic.py", |
| }, |
| } |
|
|
|
|
| def parse_args() -> argparse.Namespace: |
| parser = argparse.ArgumentParser(description=__doc__) |
| parser.add_argument("--repo-root", type=Path, default=Path.cwd()) |
| parser.add_argument("--model", choices=["SG08", "SP08", "all"], default="all") |
| return parser.parse_args() |
|
|
|
|
| def sha256_file(path: Path) -> str: |
| digest = hashlib.sha256() |
| with path.open("rb") as handle: |
| for chunk in iter(lambda: handle.read(1024 * 1024), b""): |
| digest.update(chunk) |
| return digest.hexdigest() |
|
|
|
|
| def atomic_text(path: Path, value: str) -> None: |
| path.parent.mkdir(parents=True, exist_ok=True) |
| temporary = path.with_suffix(path.suffix + ".tmp") |
| temporary.write_text(value, encoding="utf-8") |
| temporary.replace(path) |
|
|
|
|
| def atomic_json(path: Path, value: Any) -> None: |
| atomic_text(path, json.dumps(value, indent=2, sort_keys=True) + "\n") |
|
|
|
|
| def run_logged(command: list[str], root: Path, log_dir: Path, stem: str) -> int: |
| log_dir.mkdir(parents=True, exist_ok=True) |
| atomic_text(log_dir / f"{stem}.command.txt", shlex.join(command) + "\n") |
| completed = subprocess.run(command, cwd=root, capture_output=True, text=True, check=False) |
| atomic_text(log_dir / f"{stem}.stdout.log", completed.stdout) |
| atomic_text(log_dir / f"{stem}.stderr.log", completed.stderr) |
| atomic_text(log_dir / f"{stem}.exit_code.txt", f"{completed.returncode}\n") |
| return completed.returncode |
|
|
|
|
| def run_model(root: Path, model_id: str) -> dict[str, Any]: |
| spec = SPECS[model_id] |
| config_path = root / spec["config"] |
| config = json.loads(config_path.read_text(encoding="utf-8")) |
| result_dir = root / config["output_dir"] |
| log_dir = result_dir.parent / "logs" |
| python_path = root / config["runtime"]["python"] |
| evaluator_command = [ |
| str(python_path), |
| str(root / spec["evaluator"]), |
| "--project-root", str(root), |
| "--config", str(config_path), |
| "--output-dir", str(result_dir), |
| ] |
| evaluate_exit = run_logged(evaluator_command, root, log_dir, "evaluate") |
| if evaluate_exit != 0: |
| return {"model_id": model_id, "status": "FAIL", "stage": "evaluate", "exit_code": evaluate_exit} |
|
|
| validation_path = result_dir / "validation_report.json" |
| validator_command = [ |
| str(root / ".venv/bin/python"), |
| str(root / "scripts/stages/validate_sg08_sp08_diagnostic.py"), |
| "--project-root", str(root), |
| "--config", str(config_path), |
| "--result-dir", str(result_dir), |
| "--execution-log-dir", str(log_dir), |
| "--output", str(validation_path), |
| ] |
| validate_exit = run_logged(validator_command, root, log_dir, "validate") |
| manifest_path = result_dir / "diagnostic_artifact_manifest.json" |
| package_paths = [ |
| config_path, |
| root / spec["evaluator"], |
| root / "scripts/stages/validate_sg08_sp08_diagnostic.py", |
| *[path for path in sorted(result_dir.glob("*")) if path.is_file() and path != manifest_path], |
| *sorted(log_dir.glob("*")), |
| ] |
| atomic_json( |
| manifest_path, |
| { |
| "schema_version": "1.0", |
| "model_id": model_id, |
| "evaluation_kind": config["evaluation_kind"], |
| "canonical_q1_affected": False, |
| "status": "PASS" if validate_exit == 0 else "FAIL", |
| "artifacts": [ |
| { |
| "path": str(path.relative_to(root)), |
| "bytes": path.stat().st_size, |
| "sha256": sha256_file(path), |
| } |
| for path in package_paths |
| ], |
| }, |
| ) |
| return { |
| "model_id": model_id, |
| "status": "PASS" if validate_exit == 0 else "FAIL", |
| "stage": "complete" if validate_exit == 0 else "validate", |
| "exit_code": validate_exit, |
| "result_dir": str(result_dir.relative_to(root)), |
| "manifest": str(manifest_path.relative_to(root)), |
| } |
|
|
|
|
| def main() -> int: |
| args = parse_args() |
| root = args.repo_root.resolve() |
| model_ids = list(SPECS) if args.model == "all" else [args.model] |
| results = [run_model(root, model_id) for model_id in model_ids] |
| print(json.dumps({"results": results}, indent=2, sort_keys=True)) |
| return 0 if all(result["status"] == "PASS" for result in results) else 1 |
|
|
|
|
| if __name__ == "__main__": |
| raise SystemExit(main()) |
|
|