# 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: `- ` 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.