Files
linuxcncWebWASM/scripts/generate_axis_case_parity_report.py

93 lines
5.8 KiB
Python

#!/usr/bin/env python3
"""Aggregate case-level AXIS parity evidence without hiding blocked families."""
from __future__ import annotations
import hashlib
import json
from pathlib import Path
from typing import Any
ROOT = Path(__file__).resolve().parents[1]
REPORT = ROOT / "coverage/axis-case-parity.report.json"
MATRIX = ROOT / "coverage/sim-case-matrix-s8-06.report.json"
def canonical(value: Any) -> str:
return json.dumps(value, ensure_ascii=False, sort_keys=True, separators=(",", ":"))
def file_hash(path: Path) -> str | None:
return hashlib.sha256(path.read_bytes()).hexdigest() if path.is_file() else None
def report(path: str) -> dict[str, Any]:
value = json.loads((ROOT / path).read_text(encoding="utf-8"))
return {"path": path, "schema": value.get("schema"), "task_id": value.get("task_id"), "status": value.get("status"), "sha256": file_hash(ROOT / path)}
def case_row(path: str, family: str, status: str, evidence: list[str], reason: str | None = None) -> dict[str, Any]:
result: dict[str, Any] = {"path": path, "family": family, "status": status, "source_sha256": file_hash(ROOT / path), "evidence": evidence}
if reason: result["reason"] = reason
return result
def generate() -> dict[str, Any]:
matrix = json.loads(MATRIX.read_text(encoding="utf-8"))
sample = matrix.get("execution", {}).get("sample", {}).get("cases", [])
by_path = {row.get("path"): row for row in sample if isinstance(row, dict)}
selected = [
case_row("linuxcnc/configs/sim/axis/minimal_xyz.ini", "cartesian/trivkins", "verified", ["coverage/minimal-xyz-vertical-s8-04.report.json", "AXIS-CTRL-01..08", "AXIS-VIEW-01..04"]),
case_row("linuxcnc/configs/sim/axis/corexy/corexy_by_kins.ini", "corexy/kins", "verified", ["coverage/corexy-dual-path-s8-05.report.json", "AXIS-VIEW-01..04"]),
case_row("linuxcnc/configs/sim/axis/corexy/corexy_by_hal.ini", "corexy/hal", "verified", ["coverage/corexy-dual-path-s8-05.report.json", "AXIS-VIEW-01..04"]),
case_row("linuxcnc/configs/sim/axis/ldelta.ini", "lineardelta", "verified", ["coverage/machine-families-s8-08.report.json", "AXIS-VIEW-01..04"]),
case_row("linuxcnc/configs/sim/axis/rdelta.ini", "rotarydelta", "verified", ["coverage/machine-families-s8-08.report.json", "AXIS-VIEW-01..04"]),
]
blocked_paths = [
"linuxcnc/configs/sim/axis/lathe.ini",
"linuxcnc/configs/sim/axis/lathe-fanucy/lathe-fanucy.ini",
"linuxcnc/configs/sim/axis/gantry/gantry.ini",
"linuxcnc/configs/sim/axis/gantry/gantry_mm.ini",
"linuxcnc/configs/sim/axis/vismach/5axis/bridgemill/5axis.ini",
"linuxcnc/configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzac-trt.ini",
]
blocked = []
for path in blocked_paths:
row = by_path.get(path, {})
blocked.append(case_row(path, "lathe/gantry/vismach-5axis", "blocked", ["coverage/sim-case-matrix-s8-06.report.json"], ", ".join(row.get("missing", [])) or "resource/startup matrix blocker"))
frontend = ["coverage/axis-control.report.json", "coverage/axis-view-edit.report.json", "coverage/gui-browser-visual-fe-m4.report.json"]
report_data: dict[str, Any] = {
"schema": "axis-case-parity-report.v1",
"task_id": "AXIS-CASE-01..04",
"context_id": "linuxcnc-2.10.0-pre0-wasm-m1-py-c1",
"status": "partial",
"implementation_status": "representative_cases_verified_remaining_families_blocked",
"cases": selected + blocked,
"family_summary": {
"cartesian_trivkins": {"status": "verified", "cases": 1},
"corexy_dual_path": {"status": "verified", "cases": 2},
"delta_representatives": {"status": "verified", "cases": 2},
"lathe_gantry_vismach_5axis": {"status": "blocked", "cases": len(blocked), "reason": "exact resource/startup blockers remain in S8-06; official kinematics oracle separately blocked"},
},
"evidence_reports": [report(path) for path in ["coverage/minimal-xyz-vertical-s8-04.report.json", "coverage/corexy-dual-path-s8-05.report.json", "coverage/machine-families-s8-08.report.json", "coverage/sim-case-matrix-s8-06.report.json", *frontend]],
"checks": {
"case_01_minimal_xyz": {"status": "verified", "endpoint_recovery": True, "controls": True, "frontend_authority": True},
"case_02_corexy_dual_path": {"status": "verified", "kins": True, "hal": True, "mode_parity": True, "recovery_parity": True},
"case_03_lathe_gantry": {"status": "blocked", "resource_matrix": "BLOCKED_RESOURCE", "oracle": "separate"},
"case_04_vismach_5axis": {"status": "blocked", "resource_matrix": "BLOCKED_RESOURCE", "oracle": "official_kinematics_oracle_blocked"},
"exact_paths": {"status": "verified", "basename_substitution": False, "matrix_case_count": matrix.get("counts", {})},
"frontend_mapping": {"status": "verified", "reports": frontend, "worker_snapshot_owner": True},
"blocked_visibility": {"status": "verified", "resource_and_oracle_blockers_retained": True},
},
"boundaries": ["verified cases are representative, not full 235-case acceptance", "lathe/gantry/VisMach/5-axis remain resource/oracle blocked", "no native oracle is inferred from Worker parity", "physical hardware and host GUI remain excluded"],
}
report_data["report_sha256"] = hashlib.sha256(canonical(report_data).encode()).hexdigest()
REPORT.parent.mkdir(parents=True, exist_ok=True)
REPORT.write_text(json.dumps(report_data, ensure_ascii=False, sort_keys=True, indent=2) + "\n", encoding="utf-8")
return report_data
if __name__ == "__main__":
result = generate()
print(f"axis_case_parity={result['status']} verified={sum(row['status']=='verified' for row in result['cases'])} blocked={sum(row['status']=='blocked' for row in result['cases'])} report_sha256={result['report_sha256']}")