from __future__ import annotations import csv, json from collections import Counter, defaultdict from pathlib import Path from typing import Any def write_json(path: Path, value: Any) -> None: path.parent.mkdir(parents=True, exist_ok=True) temporary = path.with_suffix(path.suffix + ".tmp") temporary.write_text(json.dumps(value, ensure_ascii=True, indent=2, sort_keys=True) + "\n", encoding="utf-8") temporary.replace(path) def read_json(path: Path) -> Any: return json.loads(path.read_text(encoding="utf-8")) def write_manifest(path: Path, records: list[dict[str, Any]]) -> None: path.parent.mkdir(parents=True, exist_ok=True) path.write_text("".join(json.dumps(item, ensure_ascii=True, sort_keys=True) + "\n" for item in records), encoding="utf-8") def generate_reports(output: Path, records: list[dict[str, Any]]) -> dict[str, Any]: statuses = Counter(str(item.get("status") or "unknown") for item in records) operations: Counter[str] = Counter(); reasons: Counter[str] = Counter(); gaps: dict[str, list[str]] = defaultdict(list) rows = [] for item in records: sample_dir = output / "samples" / str(item["sample_id"]) diagnostics_path = sample_dir / "diagnostics.json" if diagnostics_path.exists(): for diagnostic in read_json(diagnostics_path): reasons[str(diagnostic.get("code") or "unknown")] += 1 if diagnostic.get("operation"): gaps[str(diagnostic["operation"])].append(str(item["sample_id"])) history_path = sample_dir / "history.json" if history_path.exists(): for step in read_json(history_path): operations[str(step.get("operation") or "unknown")] += 1 comparison_path = sample_dir / "comparison.json" if comparison_path.exists(): comparison = read_json(comparison_path); metrics = comparison["raw"]["metrics"] rows.append({"sample_id": item["sample_id"], "decision": comparison["decision"], "bbox_max_delta_mm": metrics["bbox_max_delta_mm"], "volume_relative_error": metrics["volume_relative_error"], "surface_area_relative_error": metrics["surface_area_relative_error"]}) summary = {"schema": "cadfs_to_cdsl.summary.v1", "total_models": len(records), "statuses": dict(statuses), "operation_counts": dict(operations), "failure_reasons": dict(reasons), "capability_gap_counts": {key: len(set(value)) for key, value in gaps.items()}} write_json(output / "summary.json", summary) gap_payload = {key: {"sample_count": len(set(ids)), "sample_ids": sorted(set(ids))} for key, ids in sorted(gaps.items())}; write_json(output / "capability_gaps.json", gap_payload) lines = ["# Unsupported CADFS capabilities", ""] for name, value in gap_payload.items(): lines.extend([f"## {name}", "", f"Affected models: {value['sample_count']}", "", "Sample IDs: " + ", ".join(value["sample_ids"]), ""]) (output / "unsupported_capabilities.md").write_text("\n".join(lines), encoding="utf-8") with (output / "comparison_summary.csv").open("w", newline="", encoding="utf-8") as handle: writer = csv.DictWriter(handle, fieldnames=["sample_id", "decision", "bbox_max_delta_mm", "volume_relative_error", "surface_area_relative_error"]); writer.writeheader(); writer.writerows(rows) return summary