8e6ee31596
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.
289 lines
13 KiB
Markdown
289 lines
13 KiB
Markdown
# 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.
|