51 lines
3.2 KiB
Python
51 lines
3.2 KiB
Python
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
|