WillTools 2026-09-09 -- clean snapshot of the development repo for review
ci / test (push) Successful in 6m18s

History-free snapshot of the private development repository at cbe45c5
(2026-09-09). Code, docs and public catalogue only; no plant or job
knowledge layers, no client or job names (tools/scrub.py clean). The
intro deck is left out of this snapshot because its slide images could
not be checked for job drawings.
This commit is contained in:
2026-09-09 08:32:15 -05:00
commit 8e6ee31596
406 changed files with 223435 additions and 0 deletions
+288
View File
@@ -0,0 +1,288 @@
# Plan — rebuild Ticket QC around the piece, and fix what it gets wrong
Two things at once, because they touch the same code and the second is
meaningless without the first.
1. **The button becomes selection-driven**, like Panel Lift Check: preselected
assemblies run straight through; nothing preselected gives a list to tick.
2. **The checks stop producing false findings** — sixty of them on twelve
pieces, roughly a thousand across the job, **all from one cause**.
Order is not negotiable. **Phase 1 first** — not because it removes findings
(it does not; see its corrected acceptance) but because everything phase 4
compares is split across the two sheets, so nothing after it can be verified
until it lands.
---
## What is wrong today, measured
An audit of twelve WP1 pieces (WP194, 195, 196, 199, 200, 210, 213, 214, 215,
219, 220, 412) produced 135 findings. **60 of them are artefacts of ONE bug:**
| code | count | cause |
| --- | --- | --- |
| `LGD-MISSING` | 12 | looks for a placed view named `STRAND PATTERN…` / `REINFORCING PATTERN…`. **0 of the 204 WP sheets in this model has one.** The schedule is the BOM table on sheet 1; the per-piece strand count is inside section A-A. |
| `FC` | 12 | **the same cause.** `read_ticket` sets `legend_text` only when a legend view is found; with none, it stays `None`, and every comparison against it reports "in the summary, not found on the ticket". |
| `FCI` | 12 | " |
| `PCF-NOTE` | 12 | " |
| `STR-NOTE` | 12 | " |
An earlier version of this plan split these between the legend premise and a
separate two-sheet bug, and gave phase 1 a 48-finding drop to prove itself by.
Measured after the union landed: the four legend-dependent codes did not move.
**One cause, and phase 2 owns all of it.** The two-sheet fix is still required,
for what phase 4 needs — see phase 1's corrected acceptance.
The remaining findings include one real defect worth the whole exercise, and it
is the acceptance test for phase 4 — see below.
There is also a separate correction in `docs/ticket-audit-wp1-12.md` §4: the
97-tickets-are-two-sheets fact is true and still matters; the attribution of
those four codes to it was not.
---
## Phase 1 — a ticket is every sheet named after the mark
**Change:** `ticketqc.collect_context` currently maps a mark to one sheet.
Make it map a mark to a **list**, and `read_ticket` read the union: placed views
from all of them, sheet parameters from whichever carries them.
**Do not** special-case `1 OF 2`. Match on the sheet *name* equalling the mark
and take everything that matches, however many there are.
**Acceptance — CORRECTED 2026-08-27, after the change landed.**
The original text said `FC`, `FCI`, `PCF-NOTE` and `STR-NOTE` would fall from 12
to 0 here. **That was wrong, and it was my error, not the implementation's.**
Those four compare against `legend_text`, which `read_ticket` sets only when a
pattern-legend view is found — and this model has none. They belong to the
legend premise, which is phase 2. Measured after the union landed: WP199, WP210
and WP412 each resolve to both sheets, `read_ticket` reports two, and all four
still fire with `legends_found: 0`.
What phase 1 must actually show:
- Every one of the twelve resolves to **both** its sheets, `1 OF 2` and
`2 OF 2`, and `read_ticket` reports two.
- Sheet parameters — `tkt_length`, `tkt_width`, `tkt_depth`, design number,
plant — come back populated, from whichever sheet carries them.
- Placed views are the union of both sheets, deduped by element id.
- **No regression**: nothing that passed before now fails.
- Both suites green: `panellift` and `WillTools.extension`.
Phase 1 is correct and necessary regardless. A ticket **is** two sheets, and
everything phase 4 compares is split across them: it is a prerequisite, not a
finding-reducer.
---
## Phase 2 — the schedule is the BOM table, not a legend view
**Change:** repoint the legend checks. The information they want is:
- **The bill of materials on sheet 1** — a QTY / MARK / DESCRIPTION table.
- **The strand count for this piece** — inside section A-A on sheet 2, printed
as `(n) 0010001 (E.L.)`.
- **The jacking force** — a `TICKET NOTE - … STRAND JACKING FORCE` view.
`legend.pattern_prefixes` stays in the knowledge file for plants that do use a
legend view; this plant does not, and the check must not fail a ticket for the
absence of something the plant never draws.
**A trap that has already produced a "23x error" report:** the BOM's strand row
is a **length in feet**, not a count. Verified on three pieces:
| piece | BOM row | panel length | BOM ÷ length | section prints |
| --- | --- | --- | --- | --- |
| WP199 | 321 | 22.9167 ft | 14.007 | (14) |
| WP210 | 249 | 24.9167 ft | 9.993 | (10) |
| WP412 | 349 | 24.9167 ft | 14.006 | (14) |
**Acceptance:** this phase now carries **all sixty** of the false findings, not
twelve. `LGD-MISSING`, `FC`, `FCI`, `PCF-NOTE` and `STR-NOTE` all fall to 0 on
the twelve pieces — all five come from the same missing legend. A new check
compares the BOM strand row divided by the panel length against the section
count and against the model, and passes on all twelve.
---
## Phase 3 — the button is driven by the selection
Copy the shape already committed in `PanelLiftCheck.pushbutton/script.py`
(`20c6bda`), with one deliberate difference.
selection -> assemblies, or the assemblies that own the selected members
several preselected -> check exactly those, no dialog
one preselected -> check that one, no dialog
nothing preselected -> a checklist of every assembly, then check the ticked ones
**The difference from Panel Lift Check:** when nothing is preselected, do **not**
ask "batch or single". Go straight to the list. Ticket QC is always a batch of
one or more; the mode question does not exist here.
The list needs to be usable at 238 pieces: show mark, design number, and whether
a spec or only a regex summary backs it — the information the current
design-number dialog shows, moved onto the pieces.
**The engine has to change with it.** `ticketqc.run(doc, design_numbers=...)`
walks whole designs. Add a path that takes **assemblies** and resolves each
piece's design intent per piece. A worked read-only example of exactly this is
`_scratch/claude_tkt_run.py`. Keep the design-number entry point working —
`tools/sweep.py` and `ticketchecks.check_handling_result` both use it.
**Acceptance:** with three assemblies selected, the report covers three pieces
and names them. With nothing selected, the list appears, and ticking three gives
the same report. Selecting a *member* of an assembly works like selecting the
assembly.
---
## Phase 4 — the checks that were never there
All of these are arithmetic between two values that already exist. None needs a
drawing to be read; none needs judgement. Add them one at a time, each with its
own test.
### 4.1 Handling stations against the geometry
**This is the one that found a real defect, and it is the reason for the whole
refactor.** WP199's ticket prints dunnage at `4'-9"` from each end. The piece is
22'-11", so the blocks go at 4.75 and **18.17 ft**. `OPENING 1` runs **15.60 to
18.96 ft**. The ticket tells the yard to set a block where **74% of the width is
missing** — S of 403 in3 against 1,525 in3 for the full section.
To do this in code:
- Read the three handling diagrams' dimensions. They are `Dimension` elements in
the `TICKET NOTE - PANEL - DUNNAGE` / `- ERECTION` / `- PICKING/STRIPPING`
views. **Use the `Value`, not `ValueString`** — these dimensions carry a
`ValueOverride` of `U+200E` and `ValueString` is not reliable.
- **Close the chain.** Printed segments do not sum to the piece length; the
difference is a segment the ticket deliberately does not dimension. That is
correct and must not be reported. Recover it:
`missing = length - sum(printed)`. WP412: `2'-6" 7'-6" [blank] 7'-6" 2'-6"`
sums to 20'-0" on 24'-11"; the recovered 4'-11" gives stations 2.5, 10.0,
14.917, 22.417 — which are the four modelled P136 exactly.
- For each **dunnage** station, ask what section is left there. An opening under
a dunnage block is the finding. **Dunnage points are not inserts** — there is
nothing in the model to compare a dunnage station against, only geometry.
- For each **picking** station, compare against the modelled insert stations.
**Insert stations:** `read_ticket` gives each lifter a `local` tuple. There is
no `x` or `y` key — asking for one returns `None` and looks like missing data.
`local[0]` is measured from the panel **centre**:
`station = length / 2.0 + local[0]`.
**Acceptance:** WP199 raises the dunnage-in-opening finding with those numbers.
The other eleven do not raise a false one. WP412's four picking stations match
the model to the inch.
### 4.2 Weight, three ways
Not a comparison — an agreement between three numbers, and **the pattern of the
disagreement is the diagnosis**:
| | derived from |
| --- | --- |
| **printed** | the WEIGHT (lbs.) field in the title block |
| **reconstructed** | l x w x t less blockouts plus cast extras, x unit weight |
| **measured** | the modelled solid's volume x unit weight |
- b and c agree, a differs -> the ticket's weight field is stale.
- a and b agree, c differs -> something is modelled that the dimensions do not
describe.
- a and c agree, b differs -> **our** reading of the geometry is wrong. Suspect
the tool first.
`liftexport.weight_check` already computes all three as `stated`,
`reconstructed`, `solid`. What is missing is reporting *which* one disagrees.
**Acceptance:** WP219 printed 15,525 / model 15,525 and WP220 printed 15,371 /
model 15,371 both come out clean. WP199 printed 24,345 / model 24,348 is inside
tolerance. The five pieces the earlier sweep flagged (WP187, 192, 313, 409, 411)
each report which of the three disagrees.
### 4.3 The rest, in descending value
| check | what it compares |
| --- | --- |
| BOM row vs assembly members, family by family | needs `SuperComponent is not None` -> skip, or nested children double-count. `SR5602` sits inside `P76`, `RL_OVAL_PATCH_4T` inside `QLPD4T5WG`, `P106` inside `JVI PSA 6035` |
| Every placed view's name refers to this design | `TICKET NOTE - WP2- STRAND JACKING FORCE` is on 100 WP1 sheets. Its **content** is WP1's own number, so report the name, never infer the content is wrong |
| Invisible characters in ticket text | `U+200E` sits in the design summary and crashed a tool that printed it |
| Dimension chains close | every chain, against the piece's own dimension. Report the recovered segment, not a defect |
---
## Ground truth for the tests
Measured from the live model and the rendered tickets. Use these as fixtures.
| piece | length ft | width ft | thick | weight (ticket / model) | strands | BOM strand ft |
| --- | --- | --- | --- | --- | --- | --- |
| WP199 | 22.9167 | 11.9167 | 8" | 24,345 / 24,348 | 14 | 321 |
| WP200 | 22.9167 | 4.8333 | 8" | 9,389 / — | 6 | 138 |
| WP210 | 24.9167 | 8.4167 | 8" | 20,950 / — | 10 | 249 |
| WP213 | 21.9583 | — | 8" | — | 14 | 307 |
| WP215 | 18.9167 | 11.9167 | 8" | 22,192 / 22,192 | 14 | 265 |
| WP219 | 18.4583 | 8.4167 | 8" | 15,525 / 15,525 | 10 | 185 |
| WP220 | 18.4583 | 8.3333 | 8" | 15,371 / 15,371 | 10 | 185 |
| WP412 | 24.9167 | 11.9167 | 8" | 29,291 / 29,291 | 14 | 349 |
Handling dimensions as printed:
| piece | picking | erection | dunnage |
| --- | --- | --- | --- |
| WP199 | — | — | 4'-9" each end |
| WP200 | 4'-3" each end | 9" | 4'-9" each end |
| WP210 | 2'-6" 7'-6" [blank] 7'-6" 2'-6" | 1'-8" | 5'-2" each end |
| WP213 | 2'-0" 6'-0" [blank] 7'-4" 2'-0" | 2'-3" | 4'-7" each end |
| WP215 | 3'-10" each end | 2'-3" | 3'-11" each end |
| WP219 | 3'-9" each end | 1'-8" | 3'-10" each end |
| WP220 | 3'-9" each end | 1'-3" | 3'-10" each end |
| WP412 | 2'-6" 7'-6" [blank] 7'-6" 2'-6" | 2'-6" | 5'-2" each end |
Insert stations from the model, along the length:
| piece | P136 | P76 |
| --- | --- | --- |
| WP213 | — | 2.75, 8.0, 12.625, 19.958 |
| WP219 | 3.75, 14.708 | 2.75, 4.25, 14.208, 15.708 |
| WP220 | 3.75, 14.708 | 2.75, 4.25, 14.208, 15.708 |
| WP412 | 2.5, 10.0, 14.917, 22.417 | 3.5, 10.0, 14.917, 21.417 |
WP199 `OPENING 1` runs 15.60 to 18.96 ft.
---
## Not in scope, and why
These need an answer nobody has written down. **Do not decide them in code.**
- Whether the erection dimension landing on a `P136` (4 ton) rather than a
`P76` (8 ton, and the mark the BOM itself calls "Erection Anchor") is a
problem. It happens on WP412. On WP219 and WP220 the printed erection station
lands on **no insert at all**. Report as a question; do not fail a ticket on
it.
- The interference acceptance. The check flags shared material over 0.125";
`P136 x SR5602` overlaps 0.21–0.23" on nine of twelve pieces.
- Whether a family pair repeating across nine pieces is one problem or nine.
---
## Working rules
- **Read only.** Ticket QC touches nothing. No `Transaction` anywhere in this
work.
- Both suites stay green, every phase: `panellift` and `WillTools.extension`.
- `WillTools.extension/` is **IronPython 2.7**: no f-strings, no pathlib, no
dataclasses.
- `python` is not on PATH. The interpreter is the Python 3.12 install under the
current user's `AppData\Local\Programs`; `tools/rvt.py` and the test commands
in `CLAUDE.md` both spell it out.
- Run `python tools/scrub.py` before committing. No client or job name in the
repo, ever.
- **Commit each phase separately.** A phase that cannot be verified on its own
is a phase that gets debugged inside the next one.
- Push nothing. Will decides.