Files
autocad-mcp-pro/benchmarks/render_matrix_chart.py
T
Umut CANandClaude Opus 5.5 ba41428c88 bench: matrix v7 adds takeoff_roundtrip and understand_foreign; five understanding checks; the live smoke and the field-test script
The evidence for track H, in the shape tracks A, E, B + G and F used.

- benchmarks/tasks_v7.py: 21 tasks, DEFAULT_MATRIX = "v7"; v6 to v2 stay
  addressable. Both new tasks read the synthetic plant pair and nothing
  else. takeoff_roundtrip: the P&ID called schematic, every routable run's
  layout length equal to the rectilinear MST of its tags, sizes and services
  as drawn (a return drawn on the supply layer included), the cable metres
  rounded up after the allowance, an unstated power left empty.
  understand_foreign: inches over millimetres flagged, the stray entity found
  by handle, two plan copies separated, five layers classified. A test breaks
  each input and watches its gate fail by name.
- Five correctness checks, every number worked out by hand in its docstring;
  the published A/B report is re-run over 48 checks: 26 / 48 -> 48 / 48,
  zero regressed.
- scripts/smoke_understand_com.py reads the pair through the live engine -
  snapshots, the DWG route, both takeoffs and a diff - and closes every
  document it opens; the CHANGELOG records what it returned.
  scripts/field_test_takeoff.py runs the takeoffs on a user's own files,
  refuses to write inside the repository and prints no quantity by default.
- The README token table is remeasured; check_doc_numbers derives the task
  count from the v7 matrix; CLAUDE.md, both READMEs, the CHANGELOG and the
  spec status line say what shipped.

One reader defect found on the way: describe.tag_occurrences read a wiring
callout ("Wiring to CP-2") as a place its panel is written, so the scale
check called CP-2 ambiguous and read "Wiring to CP1" as a phantom tag CP1 -
10 tags and 45 pairs (13.3 % within 10 %) where the generator placed 11 and
55 (21.8 %). A callout is no longer a tag occurrence, the rule
network.tag_occurrences already kept; two tests failed before the fix.

The smoke works only in documents it opened from its own temporary folder,
known by full path; it attaches with GetActiveObject only (the backend's
Dispatch fallback would launch AutoCAD), checks in the same COM-thread call
that the active document is its own before each snapshot, the DWG save and
the move, closes its documents with Close(False) and re-activates the
document that was active when it started. No new ActiveX member: Documents,
Item, FullName, ActiveDocument, Activate and Close are the ones the backend
already calls. Measured on AutoCAD 2026 (25.1s): exit 0, model-space types
equal file / live / DWG, export 0.104 s, no document of the run left open.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FKFStjEFzpGyLq5bMpWxTd
2026-09-24 15:10:37 +03:00

181 lines
5.9 KiB
Python

"""Render the live-run task matrix (tasks x servers) as a deterministic SVG.
Input: the published per-server reports under ``benchmarks/results/published/``
(the same files the score chart uses). Output: a status heatmap — every task
against every server — so coverage gaps are visible per capability instead of
being folded into one number.
"""
from __future__ import annotations
import argparse
import json
from pathlib import Path
from typing import Any
from benchmarks.tasks_v7 import TASKS_V7
ROOT = Path(__file__).resolve().parents[1]
DEFAULT_INPUT_DIR = ROOT / "benchmarks" / "results" / "published"
DEFAULT_OUTPUT = ROOT / "docs" / "assets" / "autocad-mcp-taskmatrix.svg"
STATUS_COLORS = {
"pass": "#238636",
"partial": "#d29922",
"fail": "#da3633",
"timeout": "#a40e26",
"unsupported": "#30363d",
"not_run": "#161b22",
}
STATUS_GLYPHS = {
"pass": "P",
"partial": "~",
"fail": "F",
"timeout": "T",
"unsupported": "-",
"not_run": "?",
}
def load_reports(input_dir: Path) -> list[dict[str, Any]]:
reports: list[dict[str, Any]] = []
for path in sorted(input_dir.glob("*.json")):
data = json.loads(path.read_text(encoding="utf-8"))
if data.get("schema_version") != "2.0":
continue # perf reports and other lanes live in the same folder
reports.append(data)
if not reports:
raise SystemExit(f"no task-matrix reports found in {input_dir}")
# our server first, then alphabetical
reports.sort(key=lambda item: (item["adapter"] != "autocad-mcp-pro", item["adapter"]))
return reports
def render_chart(reports: list[dict[str, Any]], output: Path) -> None:
try:
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
from matplotlib.patches import Patch, Rectangle
except ImportError as exc:
raise SystemExit(
'Matplotlib is required. Install it with: pip install -e ".[pdf]"'
) from exc
matplotlib.rcParams["svg.hashsalt"] = "autocad-mcp-taskmatrix-v1"
task_ids = [task.task_id for task in TASKS_V7]
task_labels = [f"{task.task_id} ({task.category})" for task in TASKS_V7]
servers = [report["adapter"] for report in reports]
status_by_server = {
report["adapter"]: {item["task_id"]: item["status"] for item in report["results"]}
for report in reports
}
fig, ax = plt.subplots(
figsize=(3.4 + 2.4 * len(servers), 1.8 + 0.52 * len(task_ids)),
constrained_layout=True,
)
fig.patch.set_facecolor("#0d1117")
ax.set_facecolor("#0d1117")
for row, task_id in enumerate(task_ids):
for col, server in enumerate(servers):
status = status_by_server[server].get(task_id, "not_run")
color = STATUS_COLORS.get(status, "#f6f8fa")
ax.add_patch(
Rectangle(
(col + 0.06, row + 0.08),
0.88,
0.84,
facecolor=color,
edgecolor="#30363d",
linewidth=0.8,
)
)
glyph_color = "#ffffff" if status in ("pass", "fail", "timeout") else "#e6edf3"
ax.text(
col + 0.5,
row + 0.5,
STATUS_GLYPHS.get(status, "?"),
ha="center",
va="center",
fontsize=11,
fontweight="bold",
color=glyph_color,
)
ax.set_xlim(0, len(servers))
ax.set_ylim(len(task_ids), 0)
ax.set_xticks([index + 0.5 for index in range(len(servers))])
ax.set_xticklabels(servers, color="#e6edf3", fontsize=10, fontweight="bold")
ax.xaxis.set_ticks_position("top")
ax.set_yticks([index + 0.5 for index in range(len(task_ids))])
ax.set_yticklabels(task_labels, color="#e6edf3", fontsize=9)
ax.tick_params(length=0)
for spine in ax.spines.values():
spine.set_visible(False)
ax.set_title(
"Task matrix — live headless lane",
loc="left",
fontsize=17,
fontweight="bold",
color="#e6edf3",
pad=34,
)
legend_handles = [
Patch(facecolor=STATUS_COLORS["pass"], label="pass"),
Patch(facecolor=STATUS_COLORS["partial"], label="partial"),
Patch(facecolor=STATUS_COLORS["fail"], label="fail"),
Patch(facecolor=STATUS_COLORS["unsupported"], label="unsupported (no equivalent)"),
# Real rows now, not a theoretical state: the competitor reports were
# pinned and run against the v2 matrix, so the five v3 tasks were
# never put to them. An invented zero would look identical to a
# measured one, so they stay blank and say why.
Patch(facecolor=STATUS_COLORS["not_run"], edgecolor="#30363d", label="not run"),
]
ax.legend(
handles=legend_handles,
loc="upper left",
bbox_to_anchor=(0, -0.02),
ncol=5,
frameon=False,
fontsize=9,
labelcolor="#e6edf3",
)
output.parent.mkdir(parents=True, exist_ok=True)
fig.savefig(
output,
format="svg",
facecolor=fig.get_facecolor(),
metadata={"Creator": "AutoCAD MCP Pro task-matrix renderer", "Date": None},
)
plt.close(fig)
svg = output.read_text(encoding="utf-8")
normalized = "\n".join(line.rstrip() for line in svg.splitlines()) + "\n"
output.write_text(normalized, encoding="utf-8", newline="\n")
def build_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--input-dir", type=Path, default=DEFAULT_INPUT_DIR)
parser.add_argument("--output", type=Path, default=DEFAULT_OUTPUT)
return parser
def main() -> int:
args = build_parser().parse_args()
render_chart(load_reports(args.input_dir), args.output)
print(f"Rendered task-matrix chart: {args.output}")
return 0
if __name__ == "__main__":
raise SystemExit(main())