Files
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

53 KiB
Raw Permalink Blame History

WillTools -- catalogue -- rationale

Why the values in knowledge.yaml are what they are. Moved out of that file so the keys the tool actually reads stay legible. NOTHING HERE IS PARSED by anything -- it is for the reader, human or model.

Each section is named for the key it explains. The key itself keeps a # sources: stub and a pointer back here, so no number sits in the layer with its provenance out of sight.

Generated once by tools/layersplit.py; edit freely from here on.


face_designations.frames

The closed set of frames of reference, and THE SIDES EACH ONE CAN NAME.

A vocabulary declaring a frame that is not one of these cannot be resolved against anything, and neither can a token whose side does not belong to its vocabulary's frame -- side: near under frame: form is half an edit, and config_problems reports both.

TWO TOKENS NAME THE SAME FACE WHEN THEY HAVE THE SAME (frame, side). That pair IS the identity of a face here, which is how a plant whose near/far is anchored to the FORM can say that its N.F. and T.I.F. are one face -- both frame: form, both side: top -- without either vocabulary having to know about the other.

both belongs to every frame: a token like E.L. names all the faces the vocabulary can name, so no frame is needed to resolve it.

face_designations.vocabularies.near_far


LAYER POSITION, SEEN FROM THE DRAWING.

WHICH ANCHOR A PRODUCER USES IS NOT PUBLISHED AND IS NOT ASSUMED. This vocabulary is the view-anchored reading: near and far measured from the direction the elevation looks. It is a real convention and it is NOT universal -- a producer may anchor the same two words to the CASTING BED instead, so that "near face" simply means the face that ends up on top in the form. That producer declares its own form-anchored vocabulary in its own plant file (a map, so this one is untouched) and selects that; it does not half-edit this one.

Which is exactly why frame is a field rather than a comment: the same two tokens mean different things at different plants, and only the frame says which.

E.L. is the odd one and it is why side exists separately from means: "each layer" names BOTH faces at once, so it is symmetric -- flipping the view does not make it wrong. A check that treated it as a single face and flipped it would invent a finding.

orientation

========================================================================== WHICH END OF A PIECE IS UP ONCE IT IS ERECTED.

Owner, 2026-08-30, asked whether to wire or delete the two plant keys that had been asking a producer to declare this:

"哪头朝上取决于 assembly origin。也就是 TOT。写入公共知识。
 assembly origin 里面有一个朝上的头。"

-- which end is up depends on the ASSEMBLY ORIGIN, also called TOT; write it into the public knowledge; the assembly origin has an up end in it.

WHY THIS IS PUBLIC AND NOT PLANT KNOWLEDGE. It is how a precast assembly is built, not a house habit. The origin is placed as part of making the assembly, and placing it IS the statement of which end goes up -- so there is nothing for a producer to declare, and a key asking one to declare it is asking the wrong question. The test this file applies to everything in it holds: this identifies no client, no job and no producer.

WHAT IT REPLACED, AND WHY THAT MATTERS MORE THAN THE VALUE. The plant layer used to carry orientation.top_is_higher_global_z: true -- "the top is the end at the higher elevation in the model". That is a different MECHANISM, not a different answer, and the difference is not cosmetic: global elevation answers this for a wall panel stood on end and is meaningless for a piece erected long-ways, where both ends are at the same height and the winner is whatever the model's rounding happens to be. An origin has a direction whichever way the piece is erected.

A HIGHER LAYER MAY STILL OVERRIDE. up_end is a MAP, so it deep-merges: a producer whose assemblies genuinely do not carry a usable origin corrects one field and inherits the rest. What it may not do is state a different mechanism -- that is code, not data, and up_end_rule() reads exactly the fields below.

orientation.up_end.origin_up

---- the model-reading half ---------------------------------------

origin_up NULL WOULD NOT MEAN "the origin says nothing". It would mean NOBODY HAS READ ONE YET -- the same distinction, for the same reason, as drawings.reinforcing_rule: guessing which property of an origin denotes its up end puts every erection anchor on the wrong edge at once, silently, which is the exact failure this rule exists to end. It shipped null and unverified for exactly as long as that was true.

IT IS NOW ANSWERED, and by measurement rather than by reasoning. AssemblyInstance.GetTransform() returns a Transform whose BasisY is the erected up direction. Read against independently measured geometry -- extents unioned from each assembly's GetMemberIds() member boxes and projected onto that assembly's own basis, so the correlation is against the pieces and not against the transform itself:

BasisY  exactly global (0,0,1) on 281 of 282 assemblies   <- up
BasisZ  the SMALLEST extent on 25 of 26 sampled           <- face
        (0.71 / 1.34 / 2.00 / 2.71 ft -- panel thickness)      normal
BasisX  the longest extent on 23 of 26; horizontal on all 282

BasisX x BasisY = BasisZ: the frame is right-handed and consistent.

also_seen_as below records the owner's colours (green up, purple top-in-form, red long direction), which map cleanly onto Y / Z / X under Revit's standard axis colouring. THAT MAPPING IS INFERRED, NOT READ: the API exposes no colour at all, so it is carried as a separate, unconfirmed field rather than folded into the sentences above. It is with the owner.

AssemblyInstance.get_BoundingBox(None) returns NONE, which is why the extents above had to be unioned from member boxes. Worth knowing before anybody writes the obvious one-liner.

orientation.up_end.applies_to

---- and the three ways it is wrong on real data ------------------

None of these is hypothetical; each is a piece in front of us. The rule DECLINES on all three rather than clamping or picking the nearest axis, because a wrong answer here is silent: the moments still balance, they are just balanced about the wrong end.

  1. ONE ASSEMBLY IN 282 HAS BasisY POINTING SIDEWAYS -- (0,1,0), with BasisZ up and a BasisZ extent of 9.46 ft, nowhere near a thickness. On that piece every reading above is simply wrong, so the check for it is an assertion and not a preference: BasisY must be world up within up_tolerance, or the origin cannot answer and says so.

  2. THE RULE IS FOR HORIZONTAL PANELS ONLY. BasisX is the long direction on 23 of 26 sampled; the other three run longest along BasisY. The owner has said vertical panels work differently and will explain, so until he does the rule stands down on anything it cannot confirm is horizontal rather than assuming the same frame holds.

  3. NEAR-SQUARE PIECES CANNOT BE CLASSIFIED AT ALL. The long-vs-up test separates cleanly at aspect ratios 1.7 to 5.7, and two pieces sit at 1.00 (11.58 x 11.63 ft) and 0.92 (10.71 x 11.63). near_square_ratio is set at 1.25 -- comfortably above the near-square pair and comfortably below the tightest real horizontal piece, so the band it declines on is genuinely undecidable rather than merely awkward.

orientation.up_end.origin_point_meaning

---- one thing this does NOT yet answer, said out loud -----------

BasisY is asserted to be world up before it is used, so on every piece where the rule RUNS it gives the same answer the global-elevation fallback gives. That is not a bug and it is not nothing -- the whole value of the rule on those pieces is the three refusals, which catch the pieces where the old reading was silently wrong -- but it does mean the ruled mechanism is not yet contributing an INDEPENDENT signal.

The owner's sentence was "assembly origin 里面有一个朝上的头" -- the origin has an up END in it. BasisY is a DIRECTION. Whether he also means that Transform.Origin SITS AT the up end -- which would be an independent signal, and would answer a near-square or vertical piece that the direction cannot -- has not been asked or measured. Recorded here rather than assumed either way.

catalogue.safety_factors


Safety factors on a lifter's rated capacity.

These are properties of the LIFTER, not of a job, which is why they live here beside the ratings they divide and not in a per-job rigging block. Will, 2026-08-30: "这俩参数是不是 lifter 的 tension 或者 shear capacity? 如果是的话应该从公共知识里面获取参数。" They are.

AND BECAUSE THEY ARE THE LIFTER'S, THEY ARE NOW WRITTEN ON THE LIFTER. The pair below used to hold tension_fos: 4.0 / shear_fos: 2.67 and applied to every insert in the file, from every manufacturer. Both halves of that were wrong:

  • 2.67 HAD NO SOURCE. Nothing published it, nobody typed it, and no transcript contains it -- searched 2026-08-31 for 2.67, 2.67:1, shear_fos, 安全系数, shear and SWL, and the only hits are the code that carries the number and the documents quoting that code. A comment justifying it as "the manufacturer's 4:1 tension / 2.67:1 shear" (panellift/PLAN.md) was written AFTER the constant and cites no page. That is a pedigree invented for a number, which is worse on a safety factor than no comment at all, and it has been removed rather than reworded.
  • THE MANUFACTURER'S PAGE CONTRADICTS IT. ALP catalog p53 (02/2024) heads the whole capacity table "Capacity in Concrete 4:1 SWL" -- ONE heading spanning BOTH the shear and the tension columns -- and publishes shear EQUAL to tension on all seven QLPD rows. For these parts the manufacturer's shear factor is 4:1, not 2.67:1.

SO THERE IS NO GLOBAL VALUE ANY MORE, and these two are null on purpose rather than corrected to 4.0/4.0. A global 4.0 would be the same defect in a better disguise: the next manufacturer's insert would silently inherit a factor read off ALP's page. The evidence that this is not hypothetical is in this very file -- QL589SPG below publishes 4,010 lb shear against 16,000 lb tension (7,690 without its tension bar), nothing like the 1:1 the QLPD rows show, so whatever governs it is not the disk anchors' rule.

The factors now live inside each insert entry as its own safety_factors: block, cited to the page that publishes them. knowledge.lifter_safety_factor() resolves them PER PART and refuses to borrow from another; panellift.checks.check_insert_capacity reports an insert it cannot resolve one for as unjudged instead of falling back on a number.

THEY APPLY ONLY WHERE THE CATALOGUE QUOTES AN ULTIMATE LOAD. Every working_load_lb below is a published SWL -- the factor is already inside the number -- so under the default working-load reading they are not applied at all and dividing by them again would be a double count. They come into force only where the rating is an ultimate, which the caller declares with capacity_is_ultimate (default False, and nothing in this repository sets it true).

catalogue.inserts.QLPD2T3WG


ALP QuikLift Disk Anchor. Datasheet 02/2024, QLPD4T5WG.pdf.

working_load_lb is the published 4:1 SWL, so it ALREADY contains the safety factor and nothing downstream may apply another.

basis_fc_psi is the concrete the table was derived at. Capacity moves with sqrt(f'c) BELOW it and does NOT move above it:

factor = min(1, sqrt(f'c actual / basis_fc_psi))

Stronger concrete buys nothing because the anchor's own mechanical capacity takes over as the governing failure. Releasing at 2500 on a 3500 table costs sqrt(2500/3500) = 0.845.

LWT is the lifting wythe thickness the anchor height is cut for. Non-insulated panels need 1" cover below the foot of the anchor.

---- WHAT THE PAGE SAYS, VERBATIM (ALP catalog p53, 02/2024) ------

The capacity table's heading is "Capacity in Concrete 4:1 SWL" and it spans BOTH load columns, so 4:1 is the factor on shear as well as on tension; every QLPD row prints the same number twice. That is where each entry's safety_factors: {tension_fos: 4.0, shear_fos: 4.0} comes from, and it is why they are written per part rather than once for the file.

Two footnotes on the same page, both now landed: "Insulated Recess Width (IRW) = 2x Minimum Edge Distance" "Minimum anchor spacing is 2x the published edge distance" The second is min_spacing_rule on each entry below; checklist 6.7 measures it. Nothing in this tool had ever asked the question, so two 8T anchors 20" apart passed every check while the page asked for 28".

AND A WARNING ABOUT THE ULTIMATE ROUTE, because the factors above make it look safe and it is not. ultimate_tension_lb / 4.0 is NOT the published SWL on four of these seven rows -- the page shades those and says the shading marks where the anchor's own mechanical capacity governs, so the printed SWL is the LESSER of the 4:1 mechanical figure and what the concrete can hold:

QLPD4T3WG  32000/4 = 8000  vs  6257 published   (+28%)
QLPD4T4WG  32000/4 = 8000  vs  7200 published   (+11%)
QLPD8T6WG  64000/4 = 16000 vs 11019 published   (+45%)
QLPD8T7WG  64000/4 = 16000 vs 14640 published   (+9%)

The other three agree exactly. So for these parts the published working load is the answer and the ultimate is provenance, not an input: reading an ultimate through a factor here is unconservative even with the factor right. working_load_lb / shear_load_lb are what the checks read, and capacity_is_ultimate must stay false.

catalogue.inserts.LPA4T512G


ALP Lifting Pin Anchor, 4T -- a DIFFERENT clutch family from every QuikLift entry above (Lifting Pin, not ring clutch). ALP Catalog 2024 p16-18, read 2026-09-07.

RATED AT FOUR CONCRETE STRENGTHS, NOT A BASIS + sqrt(f'c). Every QuikLift entry above is one number at one basis psi, derated below it and capped above it (concrete_derate()). The LPA page instead prints four columns -- 1500 / 2500 / 3500 / 5000 psi -- and capacity keeps RISING past 3,500 (6,950 lb -> 8,000 lb at 5,000 psi, +15%). Running concrete_derate() on a single basis value here would silently cap the 5,000-psi case at the 3,500-psi number -- conservative, but not what the page says, and not traceable to a cite. So working_load_lb and basis_fc_psi are both null with a reason, and the four points land as by_fc_psi instead; nothing reads that list yet (RUNBOOK 3c: land it anyway).

TWO EDGE DISTANCES, NOT ONE, AND THEY ARE NOT INTERCHANGEABLE. p18 prints "Min. Edge Distances -- Tension / Shear" as two columns, 11in and 17in for this part. min_edge_distance_in (the field lifter_edge_distance() reads) is the TENSION figure; the SHEAR one is min_edge_distance_shear_in, a name no reader consumes yet, on purpose -- folding 17 into the one field a caller actually reads would overstate how close a lifter may sit when the load is really shear.

NO SHEAR LOAD IS PUBLISHED FOR THIS PART, ANYWHERE ON THE PAGE. The 17in figure above is an edge distance, not a rating. A plant's own tested chart may state that a back-cast LPA is rated in shear as its tension (footnote logic already in knowledge.py's _back_cast_shear_from_tension); that is plant knowledge and belongs in a plant layer, not here -- this entry answers only what ALP publishes, which is null with a reason.

ONE ROW LANDED, NOT THE FAMILY. The same two pages tabulate 30 LPA rows from 1T to 20T; only LPA4T512G is landed, because it is the only one anything in this repository currently models. The rest are real, orderable parts with no entry here yet -- adding one is copying a row, not re-deriving anything.

catalogue.inserts.QL589SPG


ALP QuikLift Tech Erection Anchor, 8T. ALP Catalog 2024 p35 (the QL527G / QL548G / QL589G capacity table), read 2026-09-01.

TENSION HAS TWO PUBLISHED COLUMNS AND THEY DIFFER BY 2.1x. The page rates this anchor with and without a TENSION BAR:

4:1 SWL tension WITHOUT tension bar   7,220 .. 11,680 by thickness
4:1 SWL tension WITH    tension bar   16,000 at every thickness

Until 2026-09-01 this entry carried a bare working_load_lb: 7690 with no source, no note that 7,690 was the 8 in row of a thickness table, and no note that the second column existed. Owner's question ("P76 的 tension capacity 在哪个文件里面?哪儿来的这个 7960 的数字?") is what surfaced it; 7,960 is in no row of the table, and the number actually recorded was 7,690.

WHICH COLUMN APPLIES IS A PLANT FACT, NOT A PRODUCT FACT, so it is NOT decided here. A plant states that it welds a tension bar to the part in its own layer (lifters.tension_bar_families / parts_shipped_with_tension_bar); the plant this was traced on does, via two bars welded to the embed. working_load_lb below is therefore the WITH-BAR rating and is only correct for a plant that has made that claim.

SHEAR IS THE THICKNESS-DEPENDENT ONE and shear_load_lb holds the THINNEST row (7-1/2 in / 8 in), which is the floor of the table -- conservative for every thicker panel and exact for the two thin ones. The full table is carried below so a reader that learns the panel thickness can stop being conservative; nothing reads it yet.

catalogue.inserts.QL589G


ALP QuikLift Tech Erection Anchor, 8T -- THE BASE ANCHOR, no shear plate. Same page family as QL589SPG above, same ring clutch (QL003, 8T-12T), a DIFFERENT physical part: no SPW/SPL/SPT dimensions, lighter (8.44 lb vs 9.64 lb), packed 250/crate against 200/crate. ALP Catalog 2024 p35 (pdf p35, footer "34 QUIKLIFT SYSTEM 02/2024"), read 2026-09-07.

A FLAG FOR THE OWNER, NOT A SILENT FIX. The QL589SPG entry immediately above this one is cited to p35 -- QL589G's own page, not QL589SPG's (p36, "QuikLift Tech Erection Anchor with Shear Plate", footer "35 QUIKLIFT SYSTEM 02/2025") -- and its by_panel_thickness_in table reproduces p35's QL589G row digit for digit (7,220 / 7,690 / 8,640 / 9,580 / 10,610 / 11,680 at 7.5-12in, matching this entry's own table below exactly). p36's own QL589SPG table is close but not identical (7,100 / 7,220 / 7,690 / 8,640 / 9,580 / 10,610 / 11,680 at 7-12in -- an extra 7in row, and 6,030 where QL589G's table reads 6,032 at 7.5-8in). So the entry named QL589SPG in this file currently carries QL589G's data, not its own. Nothing here was changed to fix that -- a landed entry another job may already be reading is not this pass's to silently rewrite (CLAUDE.md §3, "a contradiction hit mid-work is the owner's to settle"). What is added instead: this entry, landed correctly against p35 under the name the page actually prints, so a model family that says QL589G (as at least one real job's does -- LIFT_QL589G_8T, confirmed against a live model 2026-09-02) is no longer forced through the SPG-shaped entry to get a rating at all. Whether QL589SPG should be re-read against p36 and corrected is on the owner's list.

working_load_lb / shear_load_lb / by_panel_thickness_in below are QL589G's own numbers, read directly off p35, not copied from the QL589SPG entry -- the two entries agree because the two anchors' published capacities are themselves nearly identical, not because one was derived from the other.

catalogue.inserts.QL846G · QL847G · QL044G · QL054G


ALP QuikLift Plate Anchors -- back-stripping anchors with a plate welded to the foot, a different shape from every LPA / QuikLift ring-clutch entry above. ALP Catalog 2024 p52 (pdf p53, footer "52 QUIKLIFT SYSTEM 02/2025"), read 2026-09-07.

TENSION AND SHEAR ARE THE SAME PUBLISHED NUMBER, on all four rows -- the page's own "Capacity in Concrete 4:1 SWL" table prints one column of each, and they are equal on every row (3,570 / 4,700 / 4,730 / 6,350 lb both ways). That is a genuine published fact, not a transcription shortcut: it is the same SHAPE as the LPA-series back-cast footnote (shear equals tension when a part is cast in the panel's field rather than at a free edge), and here it is simply what ALP prints as the SWL for either direction -- no footnote needed, no plant fact required to reach it.

THE PUBLISHED EDGE DISTANCE IS TENSION ONLY. The page's own footnote: "Shear edge distances should be based on ACI calculations or testing in plain concrete." min_edge_distance_in on each entry is the tension figure the table actually prints; shear_edge_distance_published: false says outright that there is no second number to read for shear, so a caller does not assume min_edge_distance_in covers both directions the way it might for a part where only one distance is ever published because only one applies.

THE REBAR CAUTION IS PER-PART, NOT PER-PAGE. The dagger note ("rebar should not be placed between the recess member and the base plate") is marked on QL846G and QL847G (and QL128G, not landed here) but NOT on QL044G or QL054G -- landed as a note: only where the page's own dagger marks it, rather than copied onto all four.

WHY THIS MATTERS BEYOND THE FOUR NUMBERS. A plant's own tested chart can print these same four designations with an unqualified Zero in the shear column and no footnote marking them as the back-cast exception -- which under the three-state reading in knowledge.py (_field_rating_state) is correctly treated as UNRATED, not a real zero, and stands down rather than answering Zero. Before this landing, that stand-down had nowhere to fall through to: catalogue. inserts held none of these four designations. It does now -- with a real, equal-to-tension shear rating, from an independent, wholly public source. Reaching it from a plant layer still needs that layer's own lifters.catalogue_alias line (a plant's own part-numbering habit, not landed here -- RUNBOOK stage 3d).

catalogue.inserts.QL184G


ALP Insulated Panel Anchors -- QL184G / QL184SPG. ALP Catalog 2024 p.48-49 (pdf p49-50), judge-verified 2026-09-01 against the rendered page images (QNA/extraction-benchmark-2026-09-01.md, "QL184G verified values" section -- a three-way extraction benchmark's ground truth, not one agent's note). QL184SPG is QL184G with a factory shear plate added (SPW/SPL/SPT dimensions); both share the same ring clutch class.

A DIFFERENT QL SUB-FAMILY FROM QLPD ABOVE, on a different catalogue page, with its own capacity shape. QLPD's working_load_lb / shear_load_lb do NOT apply here -- these two entries carry only dimensions and the ultimate mechanical rating; the SWL capacities are a matrix keyed by panel geometry and part number, not a per-part scalar, so they are landed separately below as insulated_panel_anchor_capacities. Do not add working_load_lb / shear_load_lb to these two entries -- read the capacity table instead.

catalogue.insulated_panel_anchor_capacities

SOURCE: ALP Catalog 2024 p.48-49 (pdf p49-50), judge-verified 2026-09-01

QL184G / QL184SPG -- Capacities in Concrete, insulated panel anchors. ALP Catalog 2024 p.49 (pdf p50), judge-verified 2026-09-01 (same benchmark file as above, same "QL184G verified values" section -- every cell here was read individually off the rendered page image and passed the two identity checks below across all four rows).

WHY A SEPARATE TABLE AND NOT FOUR MORE inserts: ENTRIES. The page's capacity matrix is keyed by PANEL GEOMETRY, not by part number alone -- QL184G and QL184SPG each appear on more than one panel-geometry row, and the same geometry caption recurs for both parts (both anchors have a row under the "(3" x 2" x 3")" group). A dict keyed by part number cannot hold that; a list of rows, each carrying its own part number and caption, can. Only the four QL184G/QL184SPG rows are landed here -- the neighbouring QL217SPG / QL077G / QL242G / QL242SPG / QL243G / QL243SPG rows on the same page were read only as the judge's identity-check neighbourhood and are not requested for this catalogue.

---- SAFETY-FACTOR SEMANTICS, EXACTLY AS PRINTED ------------------

BOTH columns below are already SWL (working load) values -- the safety factor is baked into each published number, THE SAME AS QLPD ABOVE AND UNLIKE THE HK CONNECTOR ENTRIES (connectors.products.'HK Composites (ST*)'), whose ultimate_tensile_pullout_lb / shear_strength_lb are ULTIMATES with no stated factor. Do not divide anything below by another factor, and do not read ultimate_mechanical_tension_lb on the QL184G/QL184SPG entries above as if it were a fifth SWL column -- it is the mechanical ultimate the shaded reinforced-tension column is capped at, nothing more.

4:1 SWL -- the standard shear safety factor printed on the table heading for both shear columns. 2.66:1 SWL -- printed as a second, LOWER-factor column on the same rows, with this footnote verbatim: "A shear safety factor of 2.66:1 can be used at the discretion of the engineer for one-time lifts." It is not the default; it is a narrower allowance a designer opts into.

---- IDENTITY CHECKS (asserted at landing, hold on all 4 rows) ---

2.66:1 SWL Shear Parallel / 4:1 SWL Shear Parallel = 1.50 (= 4 / 2.66) 6900/4600 = 1.500 -- 5775/3850 = 1.500 -- 6600/4400 = 1.500 -- 5175/3450 = 1.500 4:1 SWL Reinforced Tension = ultimate_mechanical_tension_lb / 4, on every row below (all four are shaded / mechanically limited): 64000 / 4 = 16000, matches all four rows' reinforced_tension_4_1_lb.

---- NOT LANDED, deliberately (judge's flags, 2026-09-01) --------

  • Corner distance / minimum anchor spacing: the p50 insulated-panel matrix carries neither column, unlike the ordinary erection-anchor capacity tables on pdf p35-46. Do not borrow QLPD's min_spacing_rule here -- it is a different table on a different page for a different QL sub-family.
  • Rows at any concrete strength other than 4,500 psi -- QL184G and QL184SPG have no rows at any other basis_fc_psi on this page.
  • A PSI -> TL -> D bend-detail lookup subtable for Bend Detail A: the page prints one only for Bend Detail C (whose rows here are QL217SPG, not QL184*). Detail A's own illustration carries only "30 deg" and the label "TL", with no numbers printed on it. The bottom_bend_tl_in / top_bend_tl_in columns below are NOT that subtable -- they are the capacity matrix's own per-row columns and are landed as printed, including where the bend detail is A.

catalogue.rebar

---------------------------------------------------------------- rebar

ONE NUMBER FOR EVERY SIZE, AND IT IS A FALLBACK -- read only for a bar size no layer has listed. Will, 2026-08-30: "rebars come in 40ft stock. unless overwritten by other knowledge, all rebars pieces must be less than 40ft." That is a sound industry default and it is not the answer: REAL STOCK IS PER BAR SIZE, and the small sizes are commonly nothing like 40 ft. One producer's stock list, read 2026-08-30, gives #3 at 23'-4" and #4 at 20', with 40' from #5 up.

Which means a check comparing every bar against this number passes a 30 ft #4 that its plant cannot cut from 20 ft stock -- a false OK on a real defect. The per-size table is rebar.stock_by_size and it is PLANT knowledge: which sizes a producer stocks, and how long, is that producer's, not the trade's, so it is never written here. Resolution order is per-size first, this number only if the size is unlisted, and NOTHING if neither -- see knowledge.rebar_stock().

catalogue.rebar.astm_grades

---- what an ASTM designation says about yield strength -------------

PUBLIC, and it belongs here rather than in a plant file for the same reason a lifter's rating does: this is what the standard says, the same for everyone who buys the bar.

THE DESIGNATION DOES NOT DETERMINE fy, and reading it as if it did is the trap this block exists to close. A stock list saying "A615" has told you nothing about strength.

grades_ksi the grades the current standard defines default_fy_ksi what to assume when nothing states the grade, or NULL where no assumption is defensible

rebar_fy() always reports which of the two its answer came from, so a default can never be mistaken for a stated fact. Where default_fy_ksi is null the caller gets None and must stand down and say why. Nothing anywhere quietly falls back to 60.

A PLANT THAT STATES ITS OWN STOCK MATERIAL OUTRANKS THIS. "This plant stocks A706 Grade 60" is an assertion about a plant; "A706 usually means Grade 60" is what the designation tends to mean. They can be the same number and they are not the same claim, so the plant layer's rebar.stock_material is consulted first and rebar_fy() reports plant stock rather than astm default when it answers.

catalogue.slotted_inserts


SLOTTED INSERTS -- JVI PSA. Catalogue 2016-5, read 2026-08-31.

A slotted insert is cast into a panel and receives a strap anchor that ties the panel back to a steel or concrete frame. It is NOT a lifting insert and nothing in inserts: above applies to it: those are rated for a crane pulling a piece out of a form, these for a wind and gravity connection that has to tolerate erection tolerance, which is what the SLOT is for.

WHY PUBLIC. Manufacturer catalogue data -- one manufacturer, one product line, the same for everyone who buys it. It identifies no client, no job and no producer, which is the whole test for this file.

---- THE ONE THING A READER MUST NOT DO WITH THESE NUMBERS --------

ultimate_pull_out_lb and ultimate_shear_lb are ULTIMATES. The page prints them under the headings "ULTIMATE PULL OUT CAPACITY" and "ULTIMATE SHEAR CAPACITY", and no safety factor appears anywhere in the catalogue -- not in a heading, not in a footnote, not on the test-report list. That is the opposite of the inserts: block above, where every working_load_lb is a published 4:1 SWL with the factor already inside it.

So safety_factors here is NULL WITH THE REASON, and it is null in the way this file's own preamble demands: a part with no factor of its own is NOT judged on an ultimate rating, it is named as unjudged. Dividing these by the 4.0 the disk anchors publish would be borrowing one manufacturer's factor for another manufacturer's part, which is exactly the failure catalogue.safety_factors was emptied to prevent.

The values are also printed as INEQUALITIES -- ">13,400 lbs.", not "13,400 lbs." -- i.e. minimum guarantees, not test means. _stated_as carries that so a report can print the page's own claim.

catalogue.slotted_inserts.edge_distance

---- EDGE DISTANCE, AND IT IS DIRECTIONAL -----------------------

This is the fact a one-number edge check would get wrong. The page prints TWO minimums and they are not "standard" and "reinforced" -- both are the unreinforced minimum, measured in two different directions, and the drawing shows both cases:

  • an insert with its slot VERTICAL: 6" up to the top edge, 10" across to the side edge;
  • an insert with its slot HORIZONTAL: 10" up to the top edge, 6" across to the side edge.

The invariant is the same in both: 6 in. is measured ALONG the slot's long axis and 10 in. PERPENDICULAR to it. Verbatim: "The minimum edge distance without the use of additional reinforcing steel is 6" and 10" as shown in the illustration. The edge distance can be reduced if extra reinforcement is installed (PSA Test Report #2)."

A check that takes 6 as "the minimum" passes an insert 6 in. from the edge the page wants 10 in. from, and it does it on exactly half the orientations.

catalogue.slotted_inserts.unreinforced_top_edge_reduction

---- THE 25% REDUCTION, AND ITS ACTUAL CONDITION ----------------

Verbatim from the note under the strap-anchor connection figure: "NOTE: Reduce ultimate load by 25% if unreinforced insert is placed at top edge of panel. Added reinforcement allows insert to attain full pull-out capacity (PSA Test Report #2)."

THE CONDITION IS NARROWER THAN "AN UNREINFORCED EDGE". It is an unreinforced insert AT THE TOP EDGE of the panel. An unreinforced insert in the field of the panel is not covered by this sentence, and reading it as a general unreinforced-edge penalty would apply a 25% cut where the page does not ask for one -- conservative, but wrong, and wrong in a way nobody can trace back to a page.

catalogue.slotted_inserts.models

---- the models --------------------------------------------------

Two series, three models each. The series differ in LENGTH and in ADJUSTMENT RANGE; within a series the models differ in DEPTH, and pull-out rises with depth while shear does not move at all.

The 4500 and 6000 rows with the same last two digits publish IDENTICAL capacities (4525 and 6025 both >13,400 / >20,000). So the series is chosen for adjustment, not for strength, and a selector that ranked 6000 above 4500 on capacity would be ranking on a tie.

available_adjustment_in is the F dimension on the dimensional sketch; note it DROPS on the deepest model of each series (2-1/8 from 2-7/8, and 3-5/8 from 4-3/8) -- the deepest insert is the strongest and the least adjustable, which is the trade-off the table exists to show.

catalogue.slotted_inserts.strap_anchors

---- the strap anchor, which is the other half of the connection --

THE SHEAR CAPACITY OF THE CONNECTION IS THE STRAP'S, NOT THE INSERT'S, once the load is eccentric -- and every real connection is eccentric, because the strap stands off the panel face. The insert's >20,000 lb is the insert; these are what a 2 in. wide welded strap delivers at the eccentricity it is actually installed at, and at 3 in. that is 11.3 kips, a little over half the insert's rating.

Two things follow that a bare insert table would hide: the governing number is usually the strap's, and it depends on a dimension (eccentricity) nobody records on a ticket.

catalogue.shear_grid


SHEAR GRID -- C-GRID carbon-fibre grid for insulated wall panels.

A shear grid crosses the insulation of a sandwich panel and transfers HORIZONTAL SHEAR between the two wythes, which is what makes the panel act partly composite. It does the same structural job as the discrete connectors under connectors: below and it is a different object -- a continuous sheet rather than a set of pins -- so its capacity is a SHEAR FLOW, force per inch of grid, not a force per part.

WHY PUBLIC. This is published manufacturer test data: Altus's own double-shear programme for C-GRID, reproduced as Table 4.1 in a 2022 C-GRID insulated-wall-panel design deck. It names a manufacturer and a product and nothing else -- no client, no job, no producer.

---- WHAT recommended_nominal_pli IS, EXACTLY ------------------

The table publishes three columns and only the third is a design value. The page's own footnotes, verbatim:

** "Maximum is 1.5 times average based on VQ/I shear on the rectangular test specimens." *** "The recommended design value has been taken as the average of the 'average test value' and 'maximum minus 3 standard deviations.'" **** "Single test for each spacing. Standard deviation is across tests." (marked on POLY ISO)

So recommended_nominal_pli = round( (average + max_minus_3sd) / 2 ), rounded half up -- and that identity holds on all 37 rows, which is how this transcription was checked: every row reproduces its own third column from its first two.

THE SECOND COLUMN IS "MAXIMUM MINUS 3 STANDARD DEVIATIONS", NOT PLUS. It is smaller than the average on eight rows (XPS 2"/12" is 212 avg against 178), which is only possible for a minus. An earlier reading of this table as "Maximum + 3 Std. Dev." inverted the sign and, with it, what the column is for.

---- AND THESE ARE NOMINAL, NOT FACTORED -------------------------

No phi is applied here and none may be applied here. Which phi a designer puts on a grid design value depends on the CHECK (an ultimate in-place check and a stripping check do not use the same one) and on the INSULATION (smooth XPS carries an extra one). Those are design practice and they live in a plant layer, not in a catalogue.

catalogue.shear_grid.c_grid.shear_flow

---- the table ------------------------------------------------

Keyed insulation type -> thickness (in) -> grid spacing (in). Units: pounds per inch of grid (pli). Thickness and spacing keys are the page's own, so a lookup is an exact match and an unlisted pair has NO value rather than an interpolated one -- these are test results at discrete spacings and the trend is not monotonic (XPS sand-blasted 2 in. runs 308 / 599 / 528 across 12 / 24 / 48 in.), so interpolating between them would invent numbers the programme did not measure.

connectors

========================================================================== CONNECTORS -- what ties the two wythes of an insulated panel together.

Owner, 2026-08-31: "增加一个公共知识,connector 指的是穿越 insulation, 把两层 混凝土链接起来的东西 ... 公共知识应该有 connector 包含:HK Composites (ST50), 而厂知识应该有 R80=HK Composites (ST50)。"

WHAT A CONNECTOR IS. An insulated (sandwich) wall panel is two concrete wythes with a layer of insulation between them. A CONNECTOR is the part that CROSSES THE INSULATION and ties the two wythes together. Without enough of them the wythes are two slabs travelling in company, which is a structural condition and not a drafting one.

THIS IS THE PUBLIC HALF: the concept, and the PRODUCTS -- manufacturer and designation, the same wherever they are bought. The plant's own part numbers and the mapping onto these products are PLANT knowledge (connectors.catalogue_alias), exactly as lifters.catalogue_alias maps a plant's insert number onto catalogue.inserts. Same shape, same reason.

CAPACITIES AND EDGE DISTANCES SHIPPED NULL AND THE CAPACITIES ARE NOW IN. The owner: "我回头再给你找他们的 capacity 加入公共知识" -- and on 2026-08-30 he captured the manufacturer's own two product pages. Every capacity below is read off those pages, and every one of them is an ULTIMATE (see safety_factors below). What is STILL null is a WORKING LOAD and, for every designation but one, a minimum edge distance: a rating nobody has read is not a rating, and the checks that need one stand down naming the missing datum rather than borrowing a number from another product. Dormant and loud (PROJECT.md 3.2): the shape is right, the value is null, and the reason is written beside it.

TWO SYSTEMS, AND THEY ARE NOT A RANGE OF ONE PRODUCT. The manufacturer publishes a STANDARD (non-composite) line and a CA (composite-action) line, and the difference is a STRUCTURAL DESIGN BASIS, not a size: the Standard system is designed so the two wythes move independently and the ties flex, the CA system so the two wythes act together and a thinner panel can be built. design_basis carries it on every entry, because a tie chosen for one basis is not a substitute for a tie chosen for the other -- and nothing here decides which basis a given panel was designed on. That is a statement about the panel, not about the part.

ST50 AND ST100 ARE TWO DIFFERENT PRODUCTS AND ARE NOT INTERCHANGEABLE. ST50 is the owner's example pairing for the plant's R80; ST100 is evidenced twice on this job -- in the design summary (HK ST100'S @1'-4" O.C.) and on the model's own connector type. Nothing here folds them together, and a capacity that arrives for one may not be read onto the other.

connectors.design_bases

---- THE TWO DESIGN BASES, IN THE VENDOR'S OWN WORDS ------------------

This is a STRUCTURAL-BEHAVIOUR CLAIM MADE BY THE MANUFACTURER, and it is recorded as that and not as an engineering conclusion of this tool's. It is here rather than in a plant file because it is a property of the PRODUCT LINE, published on the vendor's own page, and it is the same wherever the part is bought.

WHY IT MATTERS MORE THAN A NUMBER. Whether a sandwich panel's two wythes act together decides which section a bending calculation is allowed to use, and it is the single assumption that makes a sandwich-panel handling or gravity check either right or nonsense. The tie a panel is built with is EVIDENCE about that assumption -- strong evidence, and still not the same thing as the designer's own statement. Nothing in this tool may conclude "this panel is non-composite" from a part number alone; it may say "the ties on this panel are from a line the manufacturer designs for non-composite behaviour", which is a different sentence and the only one the evidence supports.

connectors.product_line_no_published_capacity

---- THE MODEL NUMBERS THAT HAVE NO PUBLISHED TABLE -------------------

Both pages list their full product line and then tabulate only three designations each. The rest are real, orderable products with NO published capacity anywhere on the page -- so they are listed as EXISTING, with nothing else claimed about them. Listing them is not decoration: it is what stops a reader meeting a DP38 or a CA125 on a ticket and concluding the mark is a typo. Both pages add: "All ... Ties can be custom designed per project" and unlisted products are a call to their engineer.

NOTHING BELOW MAY BE INTERPOLATED. CA125 sits between CA100 and CA150 in length and its capacity is NOT between theirs -- the CA table's shear falls with length while its tension is flat, so a linear guess would be wrong in two directions at once and confident in both.

connectors.safety_factors

---- THE SAFETY FACTOR THAT IS NOT ON EITHER PAGE --------------------

Said once for every connector above, in the same shape as slotted_inserts.safety_factors, and for the same reason.

EVERY PUBLISHED CONNECTOR FIGURE IS AN ULTIMATE. The Standard page's columns read "Ultimate Tensile/Pullout", "Shear Strength" and "Ultimate Panel Shear Capacity/Tie"; the CA page's read "Ultimate Tension" and "Ultimate Shear". NO SAFETY FACTOR APPEARS ANYWHERE ON EITHER PAGE -- not in a heading, not in a footnote, not in the test citations. That is the opposite of inserts:, where every working_load_lb is a published 4:1 SWL with the factor already inside it.

So a working load cannot be derived here, and the 4.0 the disk anchors publish belongs to a different manufacturer's parts and may not be borrowed. A check that needs an allowable stands down naming this key.

THE 5% FRACTILE IS NOT A FACTOR EITHER. The CA page's fractile column is a statistical lower bound on the same ultimate test population at 95% confidence -- it narrows the uncertainty in the ultimate, it does not convert it into a service-level capacity.

connectors.edge_reference

---- WHICH EDGE THE DISTANCE IS MEASURED TO, added 2026-08-31 ---------

Owner, verbatim: "检测 edge 检测的不是 concrete edge,而是 insulation edge,所以如果 insulation 有 solid zone 被 cut through 而不只是 recess 的话,那么 edge 就是被 cut 之后的 edge。如果只是 recess,则按照原来的 edge 来算。"

HE STATED IT AS HOW THE MEASUREMENT WORKS, not as one plant's habit, which is why it is here in the public layer beside what a connector IS rather than in a plant file. A connector ties two wythes together THROUGH the insulation; where there is no insulation there is nothing for it to do, so the boundary that matters to it is the insulation's, and the concrete edge is a coincidence of the two agreeing on a normal piece.

checklist

========================================================================== THE MASTER CHECKLIST -- every question this tool can ask of a shop ticket.

Owner, 2026-08-31: "做一个总的 checklist 放 public knowledge 里面,然后 plant 里面做一层映射,job 里面用静音按键去控制。"

Three layers, and each owns exactly one thing:

PUBLIC (here) WHAT THE QUESTIONS ARE. The universe, trade-wide. Every id below is a STABLE IDENTITY and nothing renumbers it. PLANT WHICH questions that plant asks OF WHICH KIND OF MEMBER, in what order, under what numbers (checklists.profiles). A wall panel and a column do not get the same checklist, and that is the insight this architecture was built on. JOB the mute buttons. Presentation, per job, per piece kind -- checks.muted, edited from the Project Config screen.

WHY THIS IS DATA AND THE CHECKS ARE STILL CODE. What EXISTS, how it is worded, and what grade it carries is publishable knowledge about shop tickets -- the same at every plant, and correctable without a release. What ANSWERS a question is a check function, because a claim has to be able to be WRONG IN A REVIEWABLE WAY: it needs a skip probe that says why it stayed silent, a finding code, and a test. A YAML-defined row with no function behind it would have a number, a sentence, and no way to fail -- a checklist row that always looks fine, which is this project's cardinal failure with a friendly editor on top.

So checks: below NAMES A FUNCTION IN vibe.ticketchecks, and the pairing is enforced: a master entry naming a function that does not exist, or a registered check function no master entry names, FAILS THE SUITE. That test is load-bearing in a way nothing was when this list was a Python tuple -- a tuple could not drift from the code beside it, and this can.

---- THE FIELDS ----------------------------------------------------------

id STABLE. Frozen 2026-08-31 as the numbers this tool had always printed. See below. name what the row says on the report grade A pure code | B code plus a plant fact | C code narrows, a person decides | D a person reads the drawing owner_codes the finding codes that answer this row, or null not_run_reason set ONLY on a row a person owns by design -- the reason string is what makes it FOR ENGINEER rather than unbuilt (see ticketpage.CHECKLIST_FOR_ENGINEER) checks one function name, a list of them, or null

---- THE IDs ARE FROZEN, AND WHY THAT MATTERS MORE THAN IT LOOKS ---------

These read like numbers because they WERE the numbers. A plant may now renumber its own checklist in one YAML edit, and the number it shows is a DIFFERENT THING from the id here.

Everything that has ever named a check names it by the id: the owner's mutes, checks.not_applicable, the QNA records, qc-run.json and its consumers, and the suite. A renumbering that reached them would either break those references or -- far worse -- leave them working and pointing at a different question.

So a reference is now QUALIFIED: <plant>-<member kind> <number> names a position on one plant's board (sample_plant-wall_panel 3.3), and a BARE number means a master id. Every ruling written before 2026-08-31 cites bare numbers and they all still mean exactly what they meant: master ids.

REMOVING an id is a DELETION, not a renumbering, and it orphans every ruling that names it. A retired id is never reused.

catalogue.development_length

---------------------------------------------------------- development_length

ACI 318-19 Chapter 25, §25.4 (development of reinforcement) and §25.5 (splices). PUBLIC for the same reason catalogue.rebar.astm_grades is: this is the model code every producer and every job works under, not a plant's or a job's own fact, and it identifies nobody.

Read from the code's own text layer (knowledge/ACI318-19-ch25_decoded.txt, cross-checked against the cleaned extraction knowledge/ACI318-19-ch25.md), not from a producer's manual -- so the caution that governs the MC drop (a 42% page-error rate on cheap-model extraction notes) does not apply the same way here: there was no independently-audited extraction pass, but every equation and table landed below was re-read against the raw text layer directly by the agent that landed it, the same discipline the library RUNBOOK asks of a landing pass. Full extraction, confidence marks and the raw-text-fragment reasoning: knowledge/library/notes/ACI318-19-ch25-development.md.

---- WHAT THE VERIFICATION PASS CHANGED FROM THE EXTRACTION NOTE ----------

The note flagged four spots [UNREADABLE] or [B] because no rendered page image was available and the PDF's own equation font renders a stacked fraction as an unordered list of glyphs (numerator and denominator symbols interleaved, not left-to-right). Re-reading the raw text layer directly resolved three of the four, because this document's fragmentation is CONSISTENT: every equation's glyph-list contains exactly the symbols the known Code equation requires, no more and no fewer -- which is how the note's own Table 25.4.2.3 reconstruction was already confirmed (via the psi_s = 0.8 cross-check). Applying the same method elsewhere:

  • Table 25.4.2.3, "No. 6 and smaller / other cases" cell -- the note had this at confidence B (self-consistent reconstruction). The raw text literally contains the digits 3, 50 (and 3, 40 for the No. 7 and larger column) in the fraction's glyph list, the same way the favourable-row cells contain 25 and 20 outright. Landed at A.
  • wwr.deformed.psi_w.case_b (5*db/s) -- the note left both (a) and (b) of §25.4.6.3 as one unreadable pair. (b)'s glyph list contains only 5, db, s -- unambiguous. Landed at A.
  • wwr.plain.formula (§25.4.7.2(b)) -- the note could not confirm whether db and/or As appeared in the numerator. The glyph list contains fy, f'c (twice, matching the two-fraction-group bracket structure (fy/(lambda*sqrt(fc))) * (Ab/s)), Ab (an "A" with a "b" subscript, not a bare "As"), and s -- no db anywhere. Landed as 0.27*(fy*Ab)/(s*lambda*sqrt(fc)) at A, correcting the note's uncertain "As" to "Ab" (area of the individual wire being developed).
  • wwr.deformed.psi_w.case_a ((fy-35000)/fy) -- NOT fully resolved. The glyph list has exactly two occurrences of fy, one 35,000, and one subtraction, which fits (fy - 35000)/fy with nothing left over -- but unlike the three facts above, there is no independent cross-check (no worked example, no self-consistency test) confirming the numerator and denominator are assigned the way I read them. Landed at confidence B with the reasoning in the comment, exactly like the note's own Table 25.4.2.3 cells were landed at B before the psi_s cross-check existed. Re-verify against a rendered page before treating it as settled.

A genuine error, corrected: the note filed the "debonded strand requires double ld" rule under §25.4.8.3. The Code text puts it at §25.4.8.1(b) instead (conditioned on "bonding does not extend to the end of the member AND design includes tension at service loads in the precompressed tension zone" -- the AND is how the zero-tension exception is already built into the Code sentence, not a separate rule). §25.4.8.3 is a different, related provision: it permits embedment SHORTER than ld at a section, provided the design strand stress there does not exceed the bilinear Eq. 25.4.8.1 relationship. The "twice ld" language and the explicit zero-tension exception the note quoted are R25.4.8.3 COMMENTARY explaining the §25.4.8.1(b) rule -- not a second Code requirement. strand.correction in the YAML records this so nobody re-derives the same confusion from the note.

Everything else in the block -- Table 25.4.2.3's favourable-row cells, all four modification-factor tables (25.4.2.5, 25.4.3.2, 25.4.4.3, 25.4.9.3), the hooked-bar and headed-bar equations, the compression equation, the excess-reinforcement-reduction rule and its six exclusions (including the note's own correction of "D, E, or F" to the Code's actual "C, D, E, or F" for Seismic Design Categories -- confirmed directly off the raw text), the tension and compression lap-splice tables, and the WWR lap-splice provisions -- was re-read against the raw text layer verbatim and matches the note exactly. Confidence A throughout except wwr.deformed.psi_w.case_a, marked as above.

---- WHY NOTHING WAS LEFT OUT ---------------------------------------------

The note's own "must not land" list named three items; two are now confirmed (Table 25.4.2.3's sub-#6 cells, the WWR-plain formula) and the third (psi_w.case_a) landed anyway at confidence B, which the library RUNBOOK permits (A and B may land; only C and D must not). Nothing in this pass was read as ambiguous, contradictory, or a guess, so nothing went to the owner's question list.