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.
13 KiB
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.
- The button becomes selection-driven, like Panel Lift Check: preselected assemblies run straight through; nothing preselected gives a list to tick.
- 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 2and2 OF 2, andread_ticketreports 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:
panelliftandWillTools.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 FORCEview.
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
Dimensionelements in theTICKET NOTE - PANEL - DUNNAGE/- ERECTION/- PICKING/STRIPPINGviews. Use theValue, notValueString— these dimensions carry aValueOverrideofU+200EandValueStringis 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 aP76(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 SR5602overlaps 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
Transactionanywhere in this work. - Both suites stay green, every phase:
panelliftandWillTools.extension. WillTools.extension/is IronPython 2.7: no f-strings, no pathlib, no dataclasses.pythonis not on PATH. The interpreter is the Python 3.12 install under the current user'sAppData\Local\Programs;tools/rvt.pyand the test commands inCLAUDE.mdboth spell it out.- Run
python tools/scrub.pybefore 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.