Files
willtools-demo/docs/plan-ticketqc-refactor.md
will 25592fa6c2
ci / test (push) Successful in 6m14s
WillTools 2026-09-09 -- clean snapshot of the development repo for review
History-free snapshot of the private development repository at 77a82eb.
Code, docs and the public catalogue only: no plant or job knowledge
layers, no client, job or producer names (tools/scrub.py clean against
the widened term list; a second word-level grep clean). The intro deck is
left out because its slide images cannot be checked for job drawings.
2026-09-09 08:35:15 -05:00

13 KiB
Raw Permalink Blame History

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.