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
This commit is contained in:
Umut CAN
2026-09-24 15:10:37 +03:00
co-authored by Claude Opus 5.5
parent 835bc4b65e
commit ba41428c88
21 changed files with 1418 additions and 39 deletions
+149
View File
@@ -39,6 +39,16 @@ new `arch` pack, three `arch_*` critique focuses, and English or Turkish
labels. No new contract member. Spec:
`docs/superpowers/specs/2026-09-23-v1.6-arch-design.md`.
Track H of 1.6: understanding drawings somebody else made. Six tools in two
new sections — understanding and QA (26: a one-call report, a scale check
between a P&ID and its layout, a revision diff, topology checks) and plant
takeoffs (27: pipe and cable quantities whose topology comes from the P&ID
and whose lengths come from the layout) — one DXF snapshot per drawing (a
live document through `Document.Export`, never renamed), a vocabulary in five
languages, three `topo_*` critique focuses, a new `plant` pack and the
`office` extra. Read-only by default; nothing invented. No new contract
member. Spec: `docs/superpowers/specs/2026-09-24-v1.6-understand-design.md`.
### Added
- **Styles — 10 tools, pack `core`.** `dimstyle_list/create/modify/set_current`,
@@ -139,6 +149,33 @@ labels. No new contract member. Spec:
2026 (`25.1s (LMS Tech)`): the example plan drawn in one transaction, both
rooms read back within 0.1 % of their labels and of the hand-computed net
floor, and `drawing_critique(focus=None)` returned 0 issues (record below).
- **Understanding foreign drawings — 4 tools, pack `core`; plant takeoffs — 2
tools, pack `plant`.** `drawing_understand`, `drawing_scale_check`,
`drawing_diff`, `drawing_topology_check`, `pipe_takeoff`, `cable_takeoff`.
Every one reads a snapshot of frozen WCS records
(`engineering/understand/snapshot.py`); labels are read by one parser
(`%%c`, `\U+2205`, the U+2300 sign, `ø` and MTEXT format runs decoded,
Cyrillic look-alike tags folded to Latin) and layer names and texts by one
EN / TR / RU / NL / DE vocabulary. The takeoff workbook is written as CSV
always and as XLSX with the new `office` extra (`openpyxl`, also in `full`).
Lean gains `drawing_understand` and reaches 65.
- **Benchmark evidence for track H.** `benchmarks/tasks_v7.py` (21 tasks, the
default matrix; v6 to v2 stay addressable) adds `takeoff_roundtrip` and
`understand_foreign`, both on the synthetic plant pair of
`tests/fixtures/plant_pair.py`, whose truth is computed from the positions
it placed; each gate is shown to fail on a broken input. The correctness
suite grows to 48 with `scale_check_detects_schematic`,
`pipe_takeoff_rmst_exact`, `cable_takeoff_roundup`,
`diff_detects_known_edits` and `topology_known_defects`. A/B against
`v1.5.1`: **26 / 48 → 48 / 48, twenty-two `miss → pass`, zero regressed**.
- **`scripts/field_test_takeoff.py`** runs both takeoffs on a user's own P&ID
and layout and writes the report next to them; it refuses an output folder
inside the repository and prints no quantity unless `--show` is given.
- **Live COM smoke.** `scripts/smoke_understand_com.py` executed once on
AutoCAD 2026 (`25.1s (LMS Tech)`): the live snapshots matched
their files type for type in model space, the DWG route read the same
P&ID, both takeoffs matched the truth and the diff reported exactly the
one entity moved (record below).
- **P&ID — 9 tools, pack `pid`.**
- `pid_symbol_list` / `pid_symbol_insert`: a 43-symbol ISO 10628-2 /
@@ -318,8 +355,40 @@ labels. No new contract member. Spec:
`KeyError: 'start'` recorded above as a busy-refused point read (not
re-measured).
- **Track H, found after the groups landed.** Six defects in the readers,
each with a test that failed before its fix:
- A truncated DXF (an interrupted save, a partial copy) made ezdxf raise
`StopIteration`, which `asyncio.to_thread` cannot hand back to an
awaiting caller, so every track H tool reading one would have hung. The
snapshot now refuses it as "not a readable DXF file".
- `take_snapshot` of a DWG the operator already had open opened it again
read-only and closed "it" unsaved - possibly their copy, with their
unsaved work. An open document is now looked up first, exported as it
stands and left open; only a document the snapshot opened is closed.
- `drawing_diff`: a LINE drawn back to front and moved was reported as
reshaped; an INSERT carrying one attribute tag twice could lose an edit
of the first value (a repeated tag is now compared as the list of its
values); the cluster list is capped like the other lists
(`truncated["clusters"]`, the markup still clouds every cluster); and a
whole-drawing move keeps only the 32 nearest candidates per entity.
- A line number was read as an equipment tag: `100-P-001` yielded the pump
`P-001`. A token that is the tail of a word beginning with a digit is no
longer a tag.
- The pipe network merged ends and dropped stubs within `tol = 1.0`
drawing units - a metre on a metre P&ID. The tolerance now defaults to
1 mm in the P&ID's inferred unit, and a dropped stub is counted and said.
- A wiring callout was counted as a place its panel is written:
`Wiring to CP-2` made `CP-2` ambiguous in `drawing_scale_check` and
`drawing_understand`, and `Wiring to CP1` a phantom tag `CP1`. On the
synthetic pair the check matched 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 the line-network reader already kept.
### Changed
- **`MAX_DXF_BYTES` defaults to 512 MB** (was 50 MB): a real plant layout read
for track H was a 188 MB DXF. The snapshot's refusal names the file's size,
the limit and the variable; `0` still disables the check.
- Three tool groups appear in `system_about` and `docs/tool-inventory.json`:
`styles` (10), `page_setup` (6), `environment` (22) — 204 tools in 23 groups;
`LEAN_TOOL_NAMES` 55; capability keys `dwgprops`, `dwt_write`,
@@ -949,6 +1018,86 @@ The first run exited 1: the first room label was refused because the room
reader found 0 wall segments on `A-WALL-E-N` — the `entity_list` narrowing
listed under **Fixed**. The run above is the one after the fix.
### Live COM smoke — understanding (track H)
`scripts/smoke_understand_com.py`, AutoCAD 2026, Windows 11, on the synthetic
plant pair only — `take_snapshot` of `pid.dxf` opened live, the same P&ID
saved as DWG and read back through the read-only DWG route, `take_snapshot`
of `layout.dxf`, `pipe_rows` / `cable_rows` on the live snapshots against the
pair's truth, then one tag moved 10 mm and `diff_snapshots` of the layout
before and after, all through the COM engine, exit 0, every document closed:
```json
{
"autocad": "25.1s (LMS Tech)",
"export_s": 0.104,
"live_pid_source": "live:pid.dxf",
"dwg_pid_source": "live:pid.dwg",
"model_space": {
"pid_file": {
"ARC": 1,
"INSERT": 15,
"LINE": 8,
"LWPOLYLINE": 8,
"MTEXT": 8,
"TEXT": 31
},
"pid_live": {
"ARC": 1,
"INSERT": 15,
"LINE": 8,
"LWPOLYLINE": 8,
"MTEXT": 8,
"TEXT": 31
},
"pid_dwg": {
"ARC": 1,
"INSERT": 15,
"LINE": 8,
"LWPOLYLINE": 8,
"MTEXT": 8,
"TEXT": 31
},
"layout_file": {
"CIRCLE": 18,
"LINE": 25,
"LWPOLYLINE": 4,
"MULTILEADER": 8,
"TEXT": 26
},
"layout_live": {
"CIRCLE": 18,
"LINE": 25,
"LWPOLYLINE": 4,
"MULTILEADER": 8,
"TEXT": 26
}
},
"paper_space_layouts": [],
"takeoffs": {
"scale_verdict": "schematic",
"within_10pct": 0.21818181818181817,
"runs_checked": 9,
"pipe_mismatch": [],
"loads_checked": 4,
"cable_mismatch": []
},
"diff": {
"moved": "44",
"added": 0,
"removed": 0,
"changed": 1,
"moved_is_the_change": true
}
}
```
The smoke works only in documents it brought in from its own temporary
folder, known by full path; each live snapshot, the DWG save and the move
first check, in the same COM-thread call, that the active document is the
smoke's own, and on the way out it re-activates the document that was active
when it started.
## [1.5.1] — 2026-08-06
A patch for three defects found *after* 1.5.0 went to PyPI, two of them by the