WillTools: precast shop-ticket QC and panel handling tools
ci / test (push) Has been cancelled

Public demo snapshot of an in-house pyRevit extension for precast concrete
shop-ticket QC (37 checks / 46 checklist rows) and a standalone panel
handling solver (PCI Chapter 8).

- No client, job, plant or product data: knowledge layers ship as
  structural templates only.
- Ticket QC's model-read escalation targets a LAN model server endpoint
  (configurable), not a cloud provider.
- Built from the private development repo via tools/make_dist.py.
This commit is contained in:
2026-09-03 16:33:47 -05:00
commit 52d94303bc
281 changed files with 155656 additions and 0 deletions
+156
View File
@@ -0,0 +1,156 @@
# What this audit learned, sorted by where it belongs
Three scales. The test for which scale a lesson belongs to is: **who would have
to change for this to stop being true?**
- **Plant** — a different plant would answer differently. Goes in the knowledge
file, never in code. Nothing here should be defaulted, because a wrong default
reads as a missing value and the check then quietly does not run.
- **Tool** — the code has the wrong model of the world. Goes in the repo.
- **Method** — how to go about looking at all. Goes in the skill.
---
## Plant — entries for the knowledge file
Each is stated with the evidence, because a plant setting nobody can trace back
is one nobody dares change.
### `legend.printed_strand_count_is_per_piece` — already false, and correctly so
The design summary for WP1 says `(14) 1/2" 270K @ 28,920`. The pieces print
their own counts: WP210's section A-A prints `(10)`, WP200's `(6)`, WP199's
`(14)`. The 14 is the design family's figure.
I first wrote this up as a setting to change. **It is already `false`, set at the
job layer** — checked against the merged config. So the five `STR-QTY` entries
are not a misconfiguration: with the flag false the check reports them as WARN
rather than FAIL, which is the intended soft signal that a piece carries fewer
strands than its design family's maximum. Reading a WARN as a defect was my
error, not the tool's.
### `legend.pattern_prefixes` — the concept does not apply here
No sheet in this model carries a view named `STRAND PATTERN…` or
`REINFORCING PATTERN…`. 0 of 204. The reinforcing schedule is the **bill of
materials table on sheet 1**, and the per-piece strand count is inside section
A-A. Until the check is repointed, `LGD-MISSING` fires on every WP ticket in the
job and takes `FC`, `FCI`, `PCF-NOTE` and `STR-NOTE` down with it.
### The BOM's strand row is linear feet
`321 | 0010001 | M392 - 1/2" 270ksi Low Lax Strand` on a 22'-11" piece is a
length. Verified on two pieces:
| piece | BOM | panel | BOM / length | section prints |
| --- | --- | --- | --- | --- |
| WP199 | 321 | 22.9167 ft | 14.007 | (14) |
| WP210 | 249 | 24.9167 ft | 9.993 | (10) |
A checker that reads that row as a quantity reports a 23x error on a correct
ticket. This needs to be stated somewhere the tool can read it, because nothing
in the row's text says "feet".
### `interference` — P136 is exempt from everything, and a plant group went missing
**Decided and applied.** `exempt_families: [P136]` is now in the plant knowledge
file. The reason, in Will's words: the disk anchor is placed to a nominal
position and the crew rotates it on the form to suit, so the modelled
orientation is a starting point rather than the built one. Anything it overlaps
in the model is a consequence of that freedom, not a defect. This is separate
from `insert_bars`, which exempts the bars *belonging* to a P136 — that entry
already existed and is correct, and it works by symmetry rather than distance.
**But the edit does not take effect yet, and the reason matters more than the
edit.** The generated project config is the last layer and it writes out *every*
key at its merged-at-generation-time value, including `exempt_families: []`. So
it freezes the past and overrides any later change made upstream. A plant edit
is invisible until the project config is regenerated. Regenerating is Will's to
do, from Revit.
The same mechanism has already lost something: the plant file defines
component_groups:
- name: EMBED P76
families: [SAMPLE_CLIENT_EMBED_P76, SR5602, SR6999]
interferes: within_group
and the merged config carries only the job layer's `CONNX TOP OF PANEL TO COLUMN`
group. Lists replace rather than merge, so the job layer's single entry deleted
the plant's. The generated config even prints the substitution in its own header
comments — the tool knows, and says so where nobody reads it.
**Worth deciding separately:** whether `component_groups` should merge by `name`
instead of replacing wholesale. As it stands, any layer that wants to add one
group silently discards every group defined above it.
### `interference.tolerance_in` / `min_thickness_in`
Currently 0.125" with `min_thickness_in` unset, so thickness defaults to the
same. The recurring overlap is 0.21–0.23". Whether that is acceptance or defect
is a plant answer, and it decides nine findings.
---
## Tool — the code has the wrong model
### A ticket is more than one sheet
97 of the tickets in this job are `1 OF 2` + `2 OF 2` under the same sheet name;
9 are `1 OF 1`. `collect_context` resolves a mark to a single sheet and returns
whichever wins. Every ticket read this way is half a ticket, and everything on
the other sheet is reported as missing. On twelve pieces this produced sixty
findings from one cause; across the job it would produce roughly a thousand.
**Fix:** collect every sheet whose name equals the mark and treat the union as
the ticket.
### `ValueString` is not what prints
The handling dimensions carry `ValueOverride = U+200E` and still print their
value. Any check that decides what the ticket says by reading `ValueString`, or
by reading `ValueOverride` and finding it blank, will be wrong in one direction
or the other. What the override is for is not established.
### `read_ticket` field problems
- `thickness` is returned in **feet** (0.667) under a name that reads as inches.
- ~~`lifters` come back with `x` and `y` as `None`~~ — **retracted, this was my
error.** Each lifter carries `local`, an `(X, Y, Z)` tuple in the panel's own
frame; there are no `x`/`y` keys, and my probe asked for the ones that do not
exist. `local[0]` is measured from the panel **centre**, so
`station = length / 2 + local[0]`. Verified: WP200's four P136 sit at
`local[0] = ±7.208` on a 22.917 ft piece, giving 4.25 ft from the end, and
the ticket's PICKING/STRIPPING diagram prints `4'-3"`. They agree exactly.
The comparison that finds real defects was available the whole time.
- What is genuinely missing is a **named accessor**: `local[0]` plus an
undocumented centre-origin convention is how this went wrong. A
`station_ft(lifter, panel)` helper would have made the mistake impossible.
### `tools/rvt.py` crashes on hidden characters
Printing a result containing `U+200E` raises `UnicodeEncodeError` on a cp1252
console. Force UTF-8 on stdout in the tool rather than making every caller set
`PYTHONIOENCODING`.
### The scrub cannot read a PDF
Exported tickets carry the job name and the client's name in the title block,
and `scrub.py` scans text. `sample data/` and `*.pdf` are now gitignored, which
is the containment, but the gate itself is blind to them.
---
## Method — how to look
These are in `docs/ticket-check.skill.md` in full. In short:
1. **Render the page and look at it.** Extracting a drawing's text stream drops
dimensions without saying so, and produces confident wrong findings.
2. **Both values and both sources, or it is not a finding.**
3. **Separate "they disagree" from "I could not find it"** — and do not pad the
second list. Three items reported as uncheckable were printed on sheet 1.
4. **One root cause is one finding**, however many codes it fans into.
5. **A repeated family pair is one pattern**, not one defect per piece.
6. **Arithmetic before alarm.** 321 against 14 looks like a 23x error until you
divide by the panel length.