93 lines
3 KiB
Python
93 lines
3 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Generate a deterministic summary of spec/coverage/*.json (sections + rule ids).
|
|
Outputs JSON and a short Markdown summary for status docs.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import json
|
|
from datetime import datetime, timezone
|
|
from pathlib import Path
|
|
|
|
|
|
def load_coverage_files(coverage_dir: Path) -> list[dict]:
|
|
files = sorted(coverage_dir.glob("*.json"))
|
|
payloads = []
|
|
for path in files:
|
|
payload = json.loads(path.read_text())
|
|
payloads.append({"path": path, "data": payload})
|
|
return payloads
|
|
|
|
|
|
def summarize(payloads: list[dict]) -> dict:
|
|
books = {}
|
|
all_rule_ids: set[str] = set()
|
|
|
|
for item in payloads:
|
|
data = item["data"]
|
|
book = data.get("book", "UNKNOWN")
|
|
sections = data.get("sections", [])
|
|
|
|
status_counts: dict[str, int] = {}
|
|
rule_ids: set[str] = set()
|
|
|
|
for section in sections:
|
|
status = section.get("status", "unknown")
|
|
status_counts[status] = status_counts.get(status, 0) + 1
|
|
for rid in section.get("rule_ids", []):
|
|
rule_ids.add(rid)
|
|
|
|
books[book] = {
|
|
"sections": len(sections),
|
|
"rules": len(rule_ids),
|
|
"status_counts": dict(sorted(status_counts.items())),
|
|
}
|
|
all_rule_ids.update(rule_ids)
|
|
|
|
return {
|
|
"generated_utc": datetime.now(timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z"),
|
|
"books": dict(sorted(books.items())),
|
|
"total_rules": len(all_rule_ids),
|
|
}
|
|
|
|
|
|
def write_json(out_path: Path, summary: dict) -> None:
|
|
out_path.write_text(json.dumps(summary, indent=2) + "\n")
|
|
|
|
|
|
def write_md(out_path: Path, summary: dict) -> None:
|
|
lines: list[str] = []
|
|
lines.append("# Coverage Summary")
|
|
lines.append("")
|
|
lines.append(f"Generated (UTC): {summary['generated_utc']}")
|
|
lines.append("")
|
|
lines.append("## By Book")
|
|
lines.append("")
|
|
for book, stats in summary["books"].items():
|
|
lines.append(f"- {book}: sections={stats['sections']}, rules={stats['rules']}, status_counts={stats['status_counts']}")
|
|
lines.append("")
|
|
lines.append(f"Total unique rule_ids: {summary['total_rules']}")
|
|
lines.append("")
|
|
out_path.write_text("\n".join(lines))
|
|
|
|
|
|
def main() -> int:
|
|
parser = argparse.ArgumentParser(description="Summarize spec coverage files.")
|
|
parser.add_argument("--coverage-dir", default="spec/coverage", help="Coverage directory (default: spec/coverage)")
|
|
parser.add_argument("--out-json", default="out/coverage-summary.json", help="Output JSON path")
|
|
parser.add_argument("--out-md", default="out/coverage-summary.md", help="Output Markdown path")
|
|
args = parser.parse_args()
|
|
|
|
coverage_dir = Path(args.coverage_dir)
|
|
payloads = load_coverage_files(coverage_dir)
|
|
summary = summarize(payloads)
|
|
|
|
write_json(Path(args.out_json), summary)
|
|
write_md(Path(args.out_md), summary)
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|