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.
40 KiB
Ticket checklist → check code mapping
Rebuilt 2026-08-30 from the code (ticketchecks.py, ticketpage.py), not
from the previous version of this file. The prior map (dated 08-28) had gone
stale: it marked 1.1, 1.3, 5.2, 5.4 and 8.2 as missing/partial when all five
now have working checks, it did not know 2.2's owner mapping had not
changed, it missed that CLASH-UNREADABLE and BOM-MEMBERS had since been
claimed as owners of 7.1 and 5.1 in ticketpage.CHECKLIST_ITEMS, and it
predates item 4.7 entirely. Status below comes from reading the check
functions and grepping for whether each finding code is actually emitted —
never from what the old file, or CHECKLIST_ITEMS's say-so alone, claimed.
Status vocabulary (see the task brief this file answers to):
implemented — runs and compares the thing the item describes.
partial — implemented inside another path, or only half the item.
missing — no code.
not needed — deliberately not built; who decided and why is stated.
0 — collect the whole ticket (grade A, prose-only, not numbered)
| item | code | status |
|---|---|---|
| Union of all sheets named the mark | read_ticket collects sheets by mark (ticketqc.py) |
implemented |
1 — identity
| # | item | grade | code | status |
|---|---|---|---|---|
| 1.1 | Sheet name equals the piece mark | A | SHEET-NAME |
implemented — check_sheet_name / ticketpage.sheet_name_matches. Built since the last map; confirmed the finding is actually emitted (ticketchecks.py:2091-2102). |
| 1.2 | Sheet design-number reduces to the piece's design | A | DSN-MATCH / DSN-SUFFIX / DSN-PREFIX |
implemented (ticketqc.py:1359-1377) |
| 1.3 | Exactly one ticket set per piece | A | TICKET-SET |
implemented — check_ticket_set / ticketpage.ticket_set_status, plus a second finding when the plant's TKT_SHEET_COUNT parameter disagrees with the pages actually found (ticketchecks.py:2105-2162). Built since the last map. |
| 1.4 | Every placed view name refers to this design | VIEW-DSN |
implemented 2026-08-30 — check_view_name_design / ticketpage.view_name_design_status, with a skip probe, and it now has a CHECKLIST_ITEMS row (it had none, so it could not even report NOT RUN). The contradiction this row used to flag is closed: the item was never assessed as "not needed" — the agent that met it had no vision capability and skipped it, which was a capability gap, not a ruling (William, 2026-08-30). Re-graded A→B: recognising a design label inside a view name needs the plant's design-token pattern (derived from nomenclature.design_number_re, no new plant fact), and the verdict is WARN-only by nature — a stale name on a correct note is a reader's trap, never a defect in the piece, so it is not worth failing a ticket over. Only tokens sharing this design's letter prefix are compared, or Elevation Top 2 reduces to TOP2 and every ticket grows a false finding. |
|
| 1.5 | Printed call-outs do not overlap each other | C | CALLOUT-OVERLAP (+ CALLOUT-OVERLAP-UNREAD) |
implemented 2026-08-30 — check_callout_overlap + ticketpage.callout_overlap_status / rect_overlap, with a skip probe. Text-bearing annotations (text notes, tags, keynotes) on the SAME placed view whose bounding boxes intersect with positive area → WARN naming both texts and the overlap size in printed inches. Boxes are converted from view-space feet through the view's Scale (ft * 12 / scale), because inches on paper is the only unit a reader can act on; a view with no readable scale is set aside, not guessed. Text-vs-linework is deliberately out of scope and the docstring says so — a call-out over a drawn edge is the normal condition on every elevation, so flagging it would report the whole sheet. Grade C, not B: a bounding box carries the text's leading and side bearings, so it is a little larger than the ink and an intersection is a shortlist for a person, never a defect claim. Rotated annotations are set aside, never flagged — an axis-aligned box circumscribes rotated glyphs and overstates them by up to the string's length — and so are annotations whose rotation cannot be read at all (unknown ≠ zero); both land under CALLOUT-OVERLAP-UNREAD at OK severity, which is unowned by any row so it can neither decide the verdict nor vanish. Measured before building (bridge, one WP ticket): 22/22 text-note boxes non-null, 0 overlapping pairs across 7 views — quiet on a good sheet. |
2 — dimensions & mass
| # | item | grade | code | status |
|---|---|---|---|---|
| 2.1 | Printed length/width/depth vs modelled | B | SIZE-LENGTH / SIZE-WIDTH / SIZE-MAX-* / SIZE-MIN-* |
implemented (ticketqc.py:1550-1553) |
| 2.2 | Weight three ways agree, pattern reported | A | WGT-* |
implemented 2026-08-30 — check_weight_three_way now gathers all three (printed = sheet.weight; reconstructed and measured = liftexport.weight_volumes, one geometry read per ticket, no erected orientation solved) and ticketpage.weight_agreement decides. With three, weight_three_way names the odd one out (WGT-PRINTED / WGT-MODEL / WGT-READ / WGT-MIXED) — it was dead code before this, which is why the item reported a disagreement without saying which of the three disagreed. With only two readable it emits WGT-TWO-WAY, never OK, naming the pair, the missing source and why it is missing; with fewer than two the skip probe reports NOT RUN with the same reason. The panel's stored weight parameter is not one of the three (it is written down, not derived) and now has its own code, WGT-PARAM, raised only when it contradicts a derived weight — it is what liftexport.weight_check reads as stated, so a stale one stops a healthy piece at the handling gate. The handling gate itself is unchanged: still an unclassified pass/fail. 2026-09-08: a wythe with a non-constant thickness (a step or taper liftexport._openings/_reconstructed_ft3 cannot represent in a plan-view projection — WP100) used to overstate the reconstruction and report WGT-READ, blaming a fine piece for a hole in the tool. _reconstructed_ft3 now cross-checks each wythe's gross-less-openings against that same solid's own Volume, and when they disagree beyond handling.weight_tolerance with no opening to explain it, the reconstruction leg alone stands down as NOT APPLICABLE (named, with the unaccounted ft3) — the measured leg is untouched, since it never depended on the reconstruction. check_weight_three_way then compares printed vs measured through the new ticketpage.weight_agreement_no_reconstruction, which — unlike the generic two-way path — CAN return WGT-OK, because with the tool's own limitation named, two-way agreement really is the whole answer; disagreement in that state is WGT-TWO-WAY-NA, not WGT-READ. |
| 2.3 | Unit weight on ticket vs design summary | A | PCF-NOTE / PCF-MODEL |
implemented (ticketqc.py:1315-1325) |
| 2.4 | Cast extras counted in weight | A | CAST-EXTRA |
implemented 2026-08-30 — check_cast_extras + ticketpage.cast_extra_agreement. Not a weight comparison: a SET comparison. The counted side is liftexport.extra_cast_members — split out of extra_cast_ft3 so 2.4 reads exactly the members whose Volume fed 2.2's reconstructed_lb, and cannot drift from it. The drawn side is the ticket's words: a BOM row or a placed view's text naming a families.cast_include token. Three things it catches that 2.2 cannot: an extra the ticket names that the model does not carry (missing from all three weights, which then agree), an extra in the weight that the ticket never names, and a cast member whose Volume could not be read (drawn, and counted as nothing). BOM states respected: OK/PARTIAL judged, ABSENT judged from view text and says so, UNREADABLE/UNKNOWN stand the comparison down through the skip probe. |
3 — concrete
| # | item | grade | code | status |
|---|---|---|---|---|
| 3.1 | f'c ticket vs summary | A | FC |
implemented |
| 3.2 | f'ci ticket vs summary | A | FCI |
implemented |
| 3.3 | f'ci/f'c release ratio in band | B | FCI-RATIO |
implemented, but distinguish from "not built." check_release_ratio + its skip probe (_probe_release_ratio) exist and run; on a model whose design spec carries no f'c/f'ci, the probe reports it as NOT RUN with the reason stated, not silence and not a finding. That is an input gap on this model, not missing code — matches the task brief's own framing. |
4 — strand
| # | item | grade | code | status |
|---|---|---|---|---|
| 4.1 | Printed strand count vs modelled | B | STR-QTY |
implemented |
| 4.2 | Diameter & grade vs design | A | STR-DIA / STR-DIA-EXTRA |
implemented |
| 4.3 | Jacking force on ticket vs design | A | JACK-FORCE |
implemented (check_jacking_force) |
| 4.4 | Strand positions vs design chain from stated rail | B | STRAND-POS |
implemented (check_strand_layout) — worth flagging: the code does not read a plant-stated "which rail is fixed" setting. _fit_chain tries the pattern read from both edges ("as modelled" / "mirrored") and keeps whichever fits the design chain better. That may be the right answer to a different, real ambiguity (a piece modelled in either orientation) but it is not what the checklist item describes (a fixed rail declared as plant knowledge) — worth the engineer's eyes, not re-graded here since it still produces a correct-looking comparison. |
| 4.5 | Debonded strands called out where design debonds | C | STRAND-CALLOUT |
implemented 2026-08-30 — check_debond_callout + ticketpage.debond_callout_status. Owner's ruling: where the debond condition is met, expect a call-out; none → WARN. It CONSUMES 4.7 rather than re-deriving geometry — both items now read one _debond_requirement(t, know), cached, so they can never disagree about whether a run is short. When 4.7 could not judge, 4.5 reports NOT RUN repeating 4.7's own reason. The ticket side is strand.debond_callout_tokens (plant nomenclature, empty default → NOT RUN with that reason) matched against BOM rows and placed-view text. Stays grade C: the call-out is a bare note with no station and no strand number, so the OK says in words that it was not verified to cover these runs. |
| 4.6 | Row count & spacing plausible | A | STRAND-SPACING / STRAND-ROWS / STRAND-GRID / BED-WIDTH / BED-OFFSET |
implemented |
| 4.7 | Strand debonded wherever concrete along it runs short | B | STRAND-DEBOND |
implemented — landed mid-run. It was missing on my first pass; a re-check while writing this file found check_strand_debond / ticketpage.strand_debond_runs fully wired: registered in CHECKS, has a skip probe (_probe_strand_debond) that reports NOT RUN — not silence — when the plant threshold, strand positions, length, or opening geometry aren't available, and CHECKLIST_ITEMS already carries a 4.7 row owning STRAND-DEBOND. This is the race the task brief warned about: another agent was implementing this concurrently in the same two files. Confirmed present as of this read; re-run the grep below before trusting it further out. |
5 — reinforcing & embeds
| # | item | grade | code | status |
|---|---|---|---|---|
| 5.1 | BED PLAN callout embed exists in model | A | BOM-MEMBERS |
partial, unchanged — CHECKLIST_ITEMS now names BOM-MEMBERS as this item's owner, but check_bom_members compares BOM schedule rows (quantity + mark) against assembly member counts, not the "Elevation Top" BED PLAN callout text the checklist item actually describes (that text is only read elsewhere, by check_bed_plan_reverse, for the reverse direction). So the mapping in CHECKLIST_ITEMS is a real code path, but not quite the comparison 5.1 asks for — see disagreement #2. |
| 5.2 | Embed in model appears on ticket | A | BED-REVERSE |
implemented — check_bed_plan_reverse reads the "Elevation Top" view's flat text and reports model families not called out on it, excluding families declared drawings.annotation_covered_by_parent. Built since the last map. Note it is deliberately WARN-only and reports the difference, "not a verdict" per its own docstring — a defect list, no severity judgement baked in. |
| 5.3 | Face designations consistent with model side | C | — | missing — no owner code, no narrowing logic. CHECKLIST_ITEMS gives it a reason string but check_name is None. |
| 5.4 | Mesh / WWF designation vs design | A | WWF |
implemented — check_wwf_designation + ticketpage.wwf_designation_status, with a skip probe that reports NOT RUN (not a silent pass) when the design's spec carries no WWF mesh token. Built since the last map. |
| 5.5 | Bar marks legible on the drawing | D | — | SPLIT 2026-08-30, on the owner's approval, and the row narrowed. It used to read "legible and unique" and carried both. Uniqueness is a comparison between the model's marks and the sheet's printed text, so it moved to 5.6 and is computed. Legibility is a property of the picture, which no query answers, so it stays here: grade D, CHECKLIST_FOR_ENGINEER = ('5.5',), FOR ENGINEER on every report so the item can never silently vanish. The vision path is unchanged — ticketvision.ESCALATIONS still carries AI-BARMARK against item 5.5, and its question text still names uniqueness alongside legibility. That overlap is deliberate: a model reading the rendered sheet is a second, independent look, never the computed answer, and section 8bis forbids it from claiming a verdict either way. |
| 5.6 | Bar marks unique, and model and print name the same bars | B | BARMARK-UNIQUE (+ BARMARK-UNREAD) |
implemented 2026-08-30 — check_bar_mark_uniqueness + ticketpage.bar_mark_uniqueness_status / mark_tokens, with a skip probe. The computable half of the old 5.5. Three WARNs for three different defects: (a) two bars sharing a mark but differing in family or length — one mark naming two parts, the expensive one, because the mark is what a fabricator cuts to; (b) a printed mark no bar carries; (c) a modelled bar whose mark is never printed. A bar with no mark at all is none of the three — it is could-not-judge under BARMARK-UNREAD, and is never counted as (c), because an absent mark cannot be looked for in the printed text. Two design decisions came from a live read and overrode the spec's assumptions. First, the spec named tags' TagText; the bridge found zero IndependentTag and zero keynote elements on the specimen ticket — every printed bar call-out is a plain TextNote on a Detail view. Both paths are gathered anyway (a tag-carrying plant must not fall through to silence) but nothing depends on the tag path, and the tag-absence conclusion is one specimen on one plant and is flagged as such. Second, the printed string contains the mark and never equals it (SR324 (F.F.), SR310 (E.L.), P76 W/ SR5602 (S.I.F.)), so the match is whole-token containment — string equality would have called every call-out on every ticket an orphan. Note-view pollution is real and separable by view TYPE: Legends carry headings and the jacking-force paragraph, Detail views carry the call-outs, so Legends are excluded — a type test, needing no plant nomenclature. Marks are read instance-first then TYPE (_bar_mark), because one bar family here carries its mark only on the type; ticketqc._member_mark is deliberately not widened, since that would change what four other checks call a mark. |
| 5.7 | No bar longer than the stock it is cut from | B | BAR-LENGTH (+ BAR-LENGTH-UNREAD) |
implemented 2026-08-30 — check_bar_stock_length + ticketpage.bar_stock_length_status, with a skip probe. Owner's ruling: bars come in 40 ft stock and a piece must be shorter unless other knowledge overrides. The number is published data, so it lives in knowledge/catalogue.yaml as catalogue.rebar.stock_length_ft; null → NOT RUN naming the key, never an assumed 40. Length comes from the member.length role — the plant layer names the parameter, this repository never does — and from nothing else — no geometry, no bounding box, ever: a bent bar consumes its developed length off the stick and a box only sees the folded envelope, so it understates every bent bar and would pass exactly the pieces this catches. Verified live: the parameter is on the instance and its internal unit is decimal feet. Three states, not two — over stock (WARN), under (pass), and 0.0, which 260 elements hold and which is neither a length nor an absence, so it is reported as could-not-judge alongside the bars with no parameter at all. Grouped by MARK, because the model carries 2,122 elements at or over 40 ft (max 60.00) and a per-instance report would bury every other row. The finding ships with its ruling open: a continuous or spliced run legitimately exceeds stock (the catalogue records x CONT. (SEE PLAN) LAP SPLICE) and whether these 60-footers are those is with the owner. Nothing is pre-exempted; when he rules, the fix is a list in catalogue.rebar.stock_exempt_marks — a config edit, not a code change — matched exactly, never as a substring (SR1 must not swallow SR1101), and exemptions are listed in the report rather than silently dropped. Exempt by mark, not family, deliberately unlike _is_reinforcing: one family here carries both 11 ft and 60 ft bars, so a family exemption would switch the check off. Dormant at this plant today, correctly — which members are bars is _is_reinforcing and nothing else, this plant models bars as Specialty Equipment so Revit's category says nothing, and drawings.reinforcing_families is empty, so 5.6 and 5.7 both report NOT RUN naming that key until it is filled in. |
6 — handling & lifting
| # | item | grade | code | status |
|---|---|---|---|---|
| 6.1 | Insert capacity vs load | A | LIFT-CAPACITY |
implemented |
| 6.2 | Insert edge distances | A | LIFT-EDGE |
implemented |
| 6.3 | Current handling result exists & passes | A | HANDLE-* |
implemented — bundles a large family of HANDLE- codes (reuse of a reviewed result, a sweep result, or an on-the-spot run; see the extensive module notes in ticketchecks.py above check_handling_result) |
| 6.4 | Dunnage not under an opening | A | DUN-OPENING |
implemented — every dunnage finding is emitted as WARN by deliberate ruling (William, 08-28), never FAIL, so opening_clearance_fail_in is inert as a severity knob (it only shapes when something is reported at all). Closed 2026-08-30 — it could not say why it said nothing. Six silent return [] paths and no skip probe, so run_all logged RAN-EMPTY and the row read NOT RUN, reason "no probe declared" — a sentence about the tool, not the piece. Every site now sets t['_dunnage_silence'] and _probe_dunnage_openings reads it (the _bed_reverse_skip pattern; the fallback is a reason and never None, since a None return would turn all six silences into a quiet OK). Exactly one path is a pass: the fall-through after the clearance loop, where the geometry was solved and every station cleared — it now emits an explicit OK ("every one of the N dunnage station(s) is at least 6" clear"), matching 4.7's flip. "No size notes found" and "notes found but no parseable feet" are NOT passes and keep reporting NOT RUN — they are failures to read the ticket, and an unread dunnage row is not a clear one. One open judgement call, named not decided: when opening_counts_only_if_through_thickness is set and every opening is a blind recess, the plant's own rule arguably answers the question and OK could be defended; it reports NOT RUN instead, because that under-claims (the safe direction) and because the sibling path — an empty opening list — genuinely cannot be judged, liftexport._openings returning [] both for a piece with no opening and for a solid it failed to read. 4.7 can tell those apart via _strand_debond_openings; this check cannot yet. Worth the owner's ruling. |
| 6.5 | DUNNAGE / ERECTION / PICKING notes say something | NOTE-CONTENT |
implemented 2026-08-30 — check_note_views / ticketpage.note_view_content_status, with a skip probe. Contradiction closed on the same ruling as 1.4: no census ever found nothing; the agent had no vision and skipped it. The grading question — can the API read these notes at all, or only a picture of the sheet? — was answered rather than assumed: a note view's contents are ordinary view-owned elements, so FilteredElementCollector(doc, view.Id) sees both its text notes and its graphics, and "holds nothing but its own heading" is an exact count. No vision path was needed or built. B not A because which views are the note views is plant nomenclature (notes.instruction_views, default empty → the row reads NOT RUN with that reason, never a pass). Graphics count as content, deliberately: a plant that draws its instruction would otherwise have every note view reported empty. Not built: whether a note that does say something says the right thing — that verdict would come from a model reading the rendered sheet, is not unit-testable to a fixed answer, and does not belong on the A/B/C/D ladder at all. Needs its own category first. |
|
| 6.6 | Odd part count carries the BED PLAN note it requires | B | PARITY-NOTE |
implemented 2026-08-31 — check_parity_note / ticketpage.parity_note_status. Section 6 because the trigger is a lifting insert's count and the consequence is an erection instruction. Grade B: the count rests on a plant-declared family name and the phrase match proves only that the words are on the plan, not that they are about this part. |
| 6.7 | Minimum spacing between lifting anchors | A | LIFT-SPACING |
implemented 2026-08-31 — check_lifter_anchor_spacing. ALP catalog p53, "minimum anchor spacing is 2x the published edge distance" — usually derived (2× the part's own published edge distance, reached through the plant's alias) rather than a printed number, so a part with no published edge distance to double is NOT judged and named rather than falling back to a plant-wide default. |
| 6.8 | Printed picking stations match the modelled picking inserts | B | PICK-STATIONS, PICK-UNREADABLE |
implemented 2026-08-31 — check_picking_match. Reads the PICKING text-note band on the ticket sheet, recovers stations from each end, and compares them to the modelled picking inserts (handling.lifter_groups.face.families) within handling.picking_tolerance_ft. Grade B and WARN-only by ruling (Will 08-27): a mismatch is for the engineer to judge, not the tool's verdict. |
| 6.9 | Lifting-point count vs panel length (plant rule) | A | LIFT-POINTS, LIFT-POINTS-UNREAD |
implemented 2026-09-02 — check_lifting_points. An MC plant rule, not published product data: owner, verbatim, "ticketqc在MC加一条check:lifting points: MC的规矩是,panel长度小于20ft则2p lifting,大于20则是4p lifting。实际上25以内都可以2p。你按照20-25给warn,25以上给fail写。" Nominal rule — under 20 ft, 2-point; 20 ft and over, 4-point — is stricter than the real ceiling, where 2-point actually holds to 25 ft, so the grey zone between the two grades WARN and only past 25 ft does a 2-point piece FAIL. 4 or more points is OK at any length; the check flags only too FEW points for the length, never too many. Both thresholds (lifters.lifting_points.two_point_max_length_ft / .two_point_absolute_max_ft) are MC plant knowledge, landed in MC's own plant layer; a layer that has not set both — any non-MC plant — gets no judgement from this rule; since 2026-09-08 the row is pulled out of the coloured table entirely, into the "Not applicable at this plant" folded section naming the missing keys (ticketchecks.PLANT_GATE_KEYS), rather than printing as an alarming NOT RUN or holding another plant's panels to MC's numbers. A "point" is counted by clustering recognised lifters by station along the panel's long axis (liftexport.rows_of, within handling.group_tolerance_ft) so that two anchors sharing one pick point are not double-counted — the same grouping Panel Lift Check's own stage logic uses, called rather than re-derived. Zero recognised lifters is 6.1's territory and this row stands down naming it rather than repeating 6.1's own sentence. |
| 6.10 | Lifting-point position vs PCI recommendation (plant rule) | A | LIFT-POS, LIFT-POS-UNREAD |
implemented 2026-09-02 — check_lift_positions. DISTINCT from 6.9: 6.9 grades the COUNT of lifting points against panel length, this grades WHERE each already-counted point sits against PCI's recommended station, and never re-flags a count problem — that stays 6.9's job. An MC plant rule, owner verbatim: "follow PCI的意思是检查一下lifter的位置,如果跟PCI相差大于6" 则报WARN。很多时候没办法完全follow PCI,但是这件事儿让用户判断可行不可行。" WARN-ONLY, never FAIL, by that ruling — a miss from PCI is for the engineer to judge feasible or not, same posture as 6.8's picking-station mismatch. Two-point pieces are compared against PCI's published 0.207L / 0.793L equal-moment two-point pick — a PCI PUBLIC CONSTANT (ticketchecks.PCI_TWO_POINT_FRACTION, mirrored by panellift.pci.OPTIMAL_TWO_LINE_FRACTION), never plant knowledge. Only the 6" tolerance (lifters.lifting_points.pci_position_tolerance_in) is MC's own number, landed beside the 6.9 thresholds; a plant that has not set it gets no judgement from this rule at all. Any point count other than two stands the row down (LIFT-POS-UNREAD, naming the count) rather than guessing a 4-point (or N-point) PCI location nobody has published to this tool yet — _pci_recommended_stations is keyed by point count so a future fill-in is one new branch, never a hardcoded guess. Boundary is strict: exactly 6" off passes, 6.01" warns. Reuses 6.9's own point clustering (liftexport.rows_of) so the two checks can never disagree about how many points there are or where one sits. |
| 6.11 | Dunnage-point count vs stripping-point count (plant rule) | A | DUN-STRIP, DUN-STRIP-UNREAD |
implemented 2026-09-02 — check_dunnage_stripping_match. A third MC plant rule, owner verbatim: "默认MC panels dunnage point=stripping points。4p stripping 则4p dunnage。" — by default a panel's dunnage-point count equals its stripping-point count. And: "stripping point就是face lifters位置" / "LPA开头的就是face lifter" — a stripping point is a face (back-stripping) lifter position, and the LPA-prefixed anchors are the face lifters. A DEFAULT, NOT AN ABSOLUTE RULE — WARN-ONLY, never FAIL, same posture as 6.9/6.10: a mismatch is the engineer's to judge feasible or not. Which lifters are "face" ones is read STRUCTURALLY, off the plant's own lifters.capacity_lb.<family>.usage: back_stripping tag, reached through the exact resolver lifter_capacity() uses (knowledge._longest_substring_match, longest key wins) so this row and 6.1 can never disagree about which entry a family resolves to; the bare LPA family-name prefix is a FALLBACK, used only when the resolved entry (or the absence of one) carries no usage tag at all — never overriding one that says otherwise. Stripping points (N_strip) are counted exactly like 6.9/6.10 — liftexport.rows_of clusters recognised lifters into stations within handling.group_tolerance_ft, and a station counts toward N_strip when ANY lifter sharing it is a face/stripping one. Dunnage points (N_dun) are read exactly like 6.4 reads them — via _dunnage_station_reader, factored out of check_dunnage_openings 2026-09-02 so the two checks can never quietly disagree about where the dunnage sits or why a ticket's row would not read — but unlike 6.4 this needs the count whether or not the piece has an opening, so it never gates on one. Gated on lifters.lifting_points.dunnage_matches_stripping, co-located with the 6.9/6.10 keys; a plant that has not set it gets no judgement from this row at all. Stand-downs: the MC key absent (since 2026-09-08, pulled into the "Not applicable at this plant" section naming it, not a coloured-table NOT RUN); zero recognised lifters (defers to 6.1 by name, 6.9's pattern); no lifter position readable (DUN-STRIP-UNREAD); zero face/stripping lifters identified though other lifters exist (DUN-STRIP-UNREAD, named — never a quiet 0-vs-0 pass); dunnage stations unreadable (DUN-STRIP-UNREAD, reusing 6.4's own silence reasons). |
7 — interference
| # | item | grade | code | status |
|---|---|---|---|---|
| 7.1 | Interference check | B | CLASH, CLASH-UNREADABLE |
implemented — check_member_interference. CLASH-UNREADABLE is now explicitly claimed as an owner in CHECKLIST_ITEMS (it was in the old map's "legacy, unmapped" list — that has changed, see disagreement #3). Findings are WARN-only by deliberate ruling (Will 08-28): measured over 57 archived tickets, 157 findings, almost all reinforcing/strand/mesh crossing an anchor or embed. The engineer looked at the most frequent pair (SR701 x P106, 13 tickets / 39 findings) and called it a real problem worth warning about — do not infer the rest are noise from that fact alone; that inference was made once before and overruled. |
8 — text hygiene
| # | item | grade | code | status |
|---|---|---|---|---|
| 8.1 | No unresolved design-summary lines | C | SPEC-UNRESOLVED |
implemented (check_spec_unresolved) |
| 8.2 | No invisible/non-printing characters | A | INVISIBLE |
implemented — check_invisible / ticketpage.invisible_chars, scanning every sheet parameter string read for the ticket. Built since the last map. |
Disagreements between ticketpage.CHECKLIST_ITEMS and ticket-checklist.md
- Two checklist items still have no row in
CHECKLIST_ITEMS: 2.4 and 4.5. (1.4 was on this list; it got its row on 2026-08-30 along with the check behind it.) (4.7 landed mid-run — see its row above and the race notice — and now has aCHECKLIST_ITEMSrow.) For items with no code this means the per-ticket report has noNOT RUNrow either — it cannot say "we didn't check this," it can only say nothing, which is exactly the failure modeticket-checklist.md's closing line warns against ("A system that cannot say what it did not do cannot be measured"). Nowhere is it explained why 2.4/4.5 are absent from the table. - 5.1's owner code doesn't implement 5.1 as written.
CHECKLIST_ITEMSnamesBOM-MEMBERSas 5.1's owner; the actual comparison it runs is BOM schedule rows vs. assembly member counts, not "the BED PLAN callouts exist in the model" (which reads a different view's text entirely, and is already coded — for the reverse direction — insidecheck_bed_plan_reverse). Not a new problem, but worth restating since the ownership claim invites reading it as more complete than it is. - The previous map's "legacy codes with no checklist row" list is
stale on two entries.
BOM-MEMBERSandCLASH-UNREADABLEwere both listed there as unmapped;CHECKLIST_ITEMSnow explicitly claims them as owners of 5.1 and 7.1 respectively. Removed from the unmapped list below. The 1.4/6.5 "not needed" framing does not check out.CLOSED 2026-08-30 by the owner. There was no "not needed" decision to find, which is why no one could find it: the agent that met 1.4 and 6.5 had no vision capability and skipped both on that basis. A capability gap, not a ruling. Both were wanted, both were re-graded (A→B, reasons on their rows above) and both are now built. Worth keeping as a record of the failure shape: a skip with no reason attached became, one map later, a decision nobody made — the same silence-reads-as-a-verdict problem the NOT RUN row exists to prevent, one level up in the documentation.
The three gap directions
1. Checklist items with no code (missing): 4.5, 5.3. (4.7 was in this list at the start of this read; it landed mid-run — see the race notice on its row above — and is now implemented. 1.4 and 6.5 were built 2026-08-30.)
2. Checklist items covered only inside another path (partial):
2.2CLOSED 2026-08-30 — the ticket report now runs the three-way itself and names the odd one out; see its row above.liftexport.weight_checkis still an unclassified pass/fail gate on the handling path, which is a different thing and deliberately left alone.- 2.4 — cast extras are folded into
weight_check's reconstructed number, no standalone comparison. - 5.1 — implemented as a BOM-table-vs-members check, not the BED-PLAN-text comparison the item describes.
3. Finding codes that answer no checklist item, verified against the
current CHECKLIST_ITEMS owners (this list has shrunk since the last map —
BOM-MEMBERS and CLASH-UNREADABLE are no longer in it, see disagreement
#3):
-
PICK-STATIONS—check_picking_matchcompares the ticket's PICKING diagram stations against modelled picking inserts. The checklist has no picking item (§6 covers dunnage only). Confirmed still unmapped. -
BOM-STRAND—check_bom_strand_count's BOM-linear-feet / section-print / model three-way agreement on strand count. Conceptually sits between 4.1 and 4.3 but is not named by either. Confirmed still unmapped. -
BAR-UNPAIRED— a side-output of the interference check: bars with no counterpart in another layer and no insert nearby, i.e. unexplained by the model's own symmetry logic. Confirmed still unmapped. -
LGD-MISSING(ticketqc._check_legend_identity) — re-read 2026-08-30. Two separate things were true of it and only one has been fixed.The original false positive is gone. It used to look for a placed view named
STRAND PATTERN/REINFORCING PATTERNand FAIL when there was none, which fired on all 204 WP sheets because this plant draws no such view. Phase 2 repointed it: withlegend.pattern_prefixesempty (the shipped default, and this plant's answer) it no longer asks about a legend view at all — it asks whether the ticket carries a BOM schedule, which is what this plant's tickets use instead. So it now reports the presence of the thing that actually exists.The residual false positive is a "could not read" reported as a defect. The
FAILbranch —'no pattern legend configured and no BOM table on the ticket'— readst['bom_schedules'], and_read_bom_schedulesswallows every per-schedule failure with a bareexcept Exception: continue(unnamed schedule,GetTableDatathrowing, zero-row body). A ticket whose BOM could not be read and a ticket with no BOM produce the identical empty dict, and the check calls both a FAIL. That is the same failure this toolchain keeps meeting, inverted: not a silent pass, but an unreadable input asserted as a finding. Fixing it means giving_read_bom_schedulesa way to say "I saw N schedules and could not read them" and splitting the FAIL intoLGD-MISSING(genuinely absent) and anLGD-UNREADABLEWARN. That is Revit-side gathering and not host-testable, so it is written down here rather than changed blind.Ownership is still open, and is an owner decision. No checklist item claims
LGD-*. §4 and §5 name the strand and reinforcing values; nothing in the checklist asks "does this ticket carry a schedule drawing at all." The two candidates: fold it into 4.1/BOM-STRAND(the BOM's existence is a precondition for the strand three-way, so its absence belongs there), or retire the code entirely and let the checks that need the BOM report their own NOT RUN when it is missing. The second is cleaner and loses nothing — a BOM-less ticket would then be reported by every check that wanted it, with reasons, instead of by one orphan code nobody's row displays. Until it is decided,LGD-MISSING's verdict — OK or FAIL — never reaches the checklist table at all. -
Handling-internals bundle (unchanged):
LIFT-WEIGHT,LIFT-UNKNOWN(lift-family variants of 6.1/6.2, not matched byCHECKLIST_ITEMS' exact-code owners);HANDLE-ROW-FLEX-*,HANDLE-INSERT,HANDLE-ASPECT,HANDLE-PLUMB,HANDLE-BUDGET,HANDLE-STALE,HANDLE-SWEPT-REFUSED,HANDLE-NOT-CHECKED,HANDLE-STAGE-CONFIG,HANDLE-NO-STRENGTH,HANDLE-NO-PYTHON,HANDLE-CALC-ERROR,HANDLE-UNAVAILABLE,HANDLE-WEIGHT-GATE,HANDLE-SWEEP-UNGATED(all bundle into 6.3 via theHANDLE-prefix match inchecklist_states, so they are not individually unmapped, just internal detail). -
DUN-UNREADABLE,PICK-UNREADABLE— unavailability variants, same shape asCLASH-UNREADABLEwas before it got claimed; these two are not claimed by any owner list and remain unmapped. -
WGT-*variants beyondWGT-OK/WGT-PRINTED(WGT-MODEL,WGT-MIXED) — not seen live yet, so "unmapped" is moot for them so far. Correction, 2026-09-08:WGT-READis not dead code — it fired live on WP100 (_scratch/wp100_weight.md), and that was the false positive the same day's fix addresses (see 2.2 above); the claim below was wrong the day it was written, not merely stale. -
CHECK-ERROR— wrapper for any check exception, not a checklist item.
Count by grade
Counting all 31 numbered items (1.1–8.2, including 4.7; excluding the prose-only item "0"):
| grade | items | implemented | partial | missing | not needed |
|---|---|---|---|---|---|
| A | 21 | 16 | 3 (2.2, 2.4, 5.1) | 2 (1.4, 6.5 — contested, see above) | 0 |
| B | 6 | 6 (incl. 4.7, landed mid-run — see race notice) | 0 | 0 | 0 |
| C | 3 | 1 (8.1) | 0 | 2 (4.5, 5.3) | 0 |
| D | 1 | 0 | 0 | 0 | 1 (5.5) |
| total | 31 | 23 | 3 | 4 | 1 |
Of the 23 "implemented," two are input-dependent rather than universally green on every model — worth keeping separate from "not built":
- 3.3 (f'ci/f'c release ratio) reports NOT RUN on a design spec that carries no f'c/f'ci — the code runs, the model just doesn't have the input.
- 5.4 (mesh/WWF) reports NOT RUN when the design's spec has no WWF mesh token — same shape: code exists, this model's spec doesn't feed it.
Both would need Extract Design re-run (or the design summary populated) to move from NOT RUN to an actual verdict on this model — I did not run that; the code alone tells you the check exists and why it currently has nothing to say.
What I could not determine from the code alone
- Whether items 1.4 and 6.5 really were the subject of a "not needed"
decision, and by whom. I found no such record outside the previous
version of this file, and the checklist doc's own text and tally argue
the opposite for 6.5. This needs the person who made that call (if it
was made) to confirm it, or
ticket-checklist.mdto be corrected to match reality — right now the two documents disagree with each other and I have no way to arbitrate that from source alone. - Whether 4.7's implementation (which landed mid-run while I was writing
this file — see the race notice on its row) is correct against real
strand and opening data. I read the code and it is coherent and matches
the checklist's description (measures continuous-concrete runs between
through-thickness openings per strand, using
strand.debond_below_continuous_ftfrom plant knowledge, never defaulted), but I have not run it against a model, per the task's constraints. - Whether
check_strand_layout's "try both edges, keep the better fit" behavior (4.4) is an intentional handling of a real modelling ambiguity or a gap against the checklist's "which rail is fixed is plant knowledge, never inferred" requirement. This needs the engineer's judgment on intent, not something a static read can settle. - Any of this against an actual open model or ticket set — I did not run Ticket QC, touch Revit, or open the bridge, per the task's constraints.