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

3206 lines
147 KiB
YAML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Published hardware data. THE PUBLIC LAYER.
#
# This file IS part of the repository, and it is the only knowledge layer that
# is. Nothing here identifies a client, a job or a plant: it is manufacturers'
# catalogue data, the same for everyone who buys the part.
#
# It is still data rather than code. A rated load written into a .py file is a
# number nobody can correct without a release, and it will be wrong eventually.
#
# Read FIRST, before the plant file and the job file, so either can override an
# entry -- for a part tested differently, or a rating an engineer will not
# accept.
#
# The plant layer says which catalogue part its own part number means:
#
# family in the model -> lifters.catalogue_alias -> catalogue.inserts
# (plant knowledge) (here)
# ==========================================================================
# FACE DESIGNATIONS -- the parenthetical suffix on a bar or embed call-out.
# ==========================================================================
#
# A ticket calls a bar out as `SR324 (F.F.)`, `SR310 (E.L.)`,
# `P76 W/ SR5602 (S.I.F.)`. The suffix says WHICH FACE of the piece the part
# goes on. Checklist 5.3 asks whether it agrees with the side the model
# actually puts the part on.
#
# WHY THESE ARE PUBLIC. They are the trade's abbreviations, printed on
# tickets everywhere, and they identify no client, no job and no producer --
# which is the whole test for what belongs in this file. What is NOT public
# is WHICH SET a given producer uses: that is a house habit, so the plant
# layer SELECTS from here by id (`face_designations.use: [near_far, in_form]`)
# and never copies the tokens down.
#
# A producer using a token nobody published states its own vocabulary in its
# own plant file -- `vocabularies` is a MAP, so it deep-merges and a new
# vocabulary is an addition, not an edit to these.
#
#
# THE ONE THING THIS SHAPE EXISTS TO PRESERVE
# -------------------------------------------
# The two families below are measured FROM DIFFERENT THINGS and are not
# interchangeable:
#
# near / far measured from the VIEWING DIRECTION. It flips when
# the view flips. If an elevation looks at the panel
# from the opposite side than was assumed, every
# N.F./F.F. on that sheet is backwards -- and each one
# individually still reads perfectly normal.
#
# top / bottom in form
# measured from the CASTING BED. Absolute for the
# piece. A flipped view does not touch it.
#
# Flattening both into one token -> meaning map would throw away exactly the
# distinction that makes 5.3 decidable, so every vocabulary DECLARES ITS
# FRAME and a reader resolves a token against that frame's evidence.
#
#
# SHAPE
# -----
# frames: the closed set of frames of reference. A vocabulary
# naming a frame that is not here is a mistake, and a
# loud one -- see knowledge.config_problems.
# vocabularies: id -> { frame, measured_from, tokens }
# tokens: a LIST of { token, means, side }.
# `token` exactly as printed, dots and all.
# `means` what it is short for, in words, so a
# report can print it to a reader who has
# never seen the abbreviation.
# `side` the normalised symbol a check compares on,
# read WITHIN this vocabulary's frame. Never
# compare a `side` from one frame against a
# `side` from another.
#
# A LIST rather than a map, on purpose: a token is written `N.F.` and a
# dotted key cannot be reached by `knowledge.get(know, 'a.b.c')` at all.
# It also makes a published vocabulary ATOMIC -- lists replace, so nobody
# half-edits one of these; they declare their own instead.
#
# Adding a third vocabulary is a new entry under `vocabularies`. No schema
# change, no code change.
#
#
# THE MODEL-SIDE HALF (shape only -- every value below is plant knowledge)
# -----------------------------------------------------------------------
# A face designation is printed on a ticket AND recorded on the element, and
# 5.3 asks whether the two agree. The keys a plant states for the second one,
# all of them siblings of `use` and `vocabularies`:
#
# model_tokens what the element's field holds -> the printed
# designation. A MAP, so it deep-merges. SEPARATE from the
# vocabularies rather than an alias on a token, because
# `tokens` is a list and lists replace -- a plant aliasing
# a published token would replace the whole published
# vocabulary.
# read_from where that field is read. `[instance]`, and NOT
# `[instance, type]` the way the reinforcing rule is:
# measured over a real ticket corpus, the same part code
# carries different face tokens on different sheets, so
# the face is a property of the PLACEMENT. A type read
# collapses distinct facts onto one answer and looks
# clean doing it.
# modifiers tokens that STACK AFTER a face token in the same
# call-out -- `P106 (S.I.F.) (E.S.)` -- and say something
# other than which face. A modifier is orthogonal to the
# face axis, so it has no (frame, side) and cannot be a
# vocabulary entry; a reader strips it and resolves the
# face underneath. A reader expecting exactly one
# parenthesised group mis-parses the pair, which is the
# whole reason this key exists.
# not_face_tokens groups that pattern-match a face designation and are
# not one. The negative has to be written down or it
# returns as a false positive.
# attaches_to token -> the member kinds it may attach to ('embed',
# 'bar'). Real grammar, measured, and a check waiting to
# be written; DATA rather than a rule buried in one.
# recorded_on the member kinds whose elements record a face at all.
# Empty means NOBODY HAS SAID -- never "none". It is what
# tells a blank on a kind that does record faces
# (unknown) from a blank on a kind that never does (does
# not apply).
# top_in_form the LABEL under which a piece states which building
# face ends up on top in the form. That value is PER
# PIECE and it varies, so a form-anchored token cannot be
# resolved to a building face without reading it --
# `knowledge.building_face_of()` refuses instead of
# assuming a handedness, because assuming one is not
# slightly wrong, it is inverted on half a job.
#
# NOTHING HERE EVER READS ABSENCE AS A VALUE, and no key may be added that
# does. An unmarked call-out is annotation that was not repeated; a blank
# field is a kind that does not use it. `same_face()` answering None rather
# than False is the instrument for that.
#
# This section sits ABOVE `catalogue:` because `vibe.yamlkit` -- the host-side
# reader the test suite uses -- used to stop at the first folded scalar (`>-`),
# and there is one under `inserts`, so everything below it was invisible to
# every test that read this file. That is fixed (yamlkit now reads block
# scalars) and the section stays here anyway: face designations are not
# hardware, and the file reads better with them beside `catalogue:` rather
# than buried under it.
face_designations:
# -> rationale.md § face_designations.frames
frames:
view:
means: Relative to the viewing direction of the drawing. It flips when the view flips, so resolving one of these tokens requires knowing which way the elevation looks at the piece.
sides: [near, far, both]
form:
means: Relative to the casting bed -- how the piece lies while it is poured. Absolute for the piece; a flipped view does not change it.
sides: [top, bottom, side, both]
vocabularies:
# -> rationale.md § face_designations.vocabularies.near_far
near_far:
frame: view
measured_from: the direction the elevation looks at the piece
tokens:
- token: N.F.
means: near face
side: near
- token: F.F.
means: far face
side: far
- token: E.L.
means: each layer
side: both
# ------------------------------------------------------------------
# CASTING ORIENTATION, seen from the bed. T.I.F. / B.I.F. are as common
# at other producers as N.F. / F.F. are here, and S.I.F. turns up
# alongside the near/far set on the same ticket -- which is exactly why
# a plant may select two vocabularies at once.
# ------------------------------------------------------------------
in_form:
frame: form
measured_from: the casting bed the piece is poured on
tokens:
- token: T.I.F.
means: top in form
side: top
- token: B.I.F.
means: bottom in form
side: bottom
- token: S.I.F.
means: side in form
side: side
# sources: 2026-08-30
# -> rationale.md § orientation
orientation:
up_end:
# THE RULE. `assembly_origin` is the only value this reads today; another
# string reads as "somebody wrote a mechanism nobody implemented", and
# `up_end_rule()` stands down and quotes it back rather than guessing.
source: assembly_origin
also_called: TOT
stated_by: Will
stated_on: '2026-08-30'
means: >-
Which end of a precast piece is up once it is erected is a property of
the assembly's origin, which is placed with the assembly and carries an
up end. It is not derived from where the piece happens to sit in the
model, and it is not a producer's convention.
# -> rationale.md § orientation.up_end.origin_up
origin_up: BasisY
origin_up_as_stated: >-
"assembly origin 里面有一个朝上的头" -- the assembly origin itself
carries an up end.
origin_up_verified: true
origin_up_verified_by: the bridge holder, against 282 assemblies
origin_up_verified_on: '2026-08-30'
# -> rationale.md § orientation.up_end.also_seen_as
# TWO DISTINCT FRAMES, not one rule with wider coverage. Owner,
# 2026-09-01, on being shown this file: "分开,horizontal panel跟
# vertical panel,三个箭头的方向本来就是不一样的。这个应该进世界知识。"
# -- separate them; horizontal and vertical panels have different
# meanings for all three arrows to begin with; this belongs in world
# knowledge. So: this key used to be one string covering one (inferred)
# mapping. It is now a mapping of TWO frames, each with its own three
# arrows, each fact cited to who said it and when, and NEITHER frame may
# borrow a colour's meaning from the other -- that borrowing is exactly
# the mistake this restructure exists to prevent.
also_seen_as:
horizontal_panels:
stated_by: Will
stated_on: '2026-08-30'
status: confirmed against 282 assemblies -- see origin_up above
green: {axis: BasisY, means: the erected up direction}
purple: {axis: BasisZ, means: top-in-form}
red: {axis: BasisX, means: the long direction}
note: >-
The API exposes no colour at all; this three-arrow mapping is a
reading of the owner's words (green / purple / red), and it is
CONFIRMED because the measurement behind origin_up above -- BasisY
exactly global (0,0,1) on 281 of 282 assemblies, BasisZ the
smallest extent on 25 of 26 sampled, BasisX the longest on 23 of
26 -- is the same measurement that stands the colours up.
vertical_wall_panels:
stated_by: Will
stated_on: '2026-09-01'
status: >-
ONE of three arrows confirmed. Do not treat this frame as complete
and do not borrow purple or red from horizontal_panels above --
the owner's own words are that the arrows are different between
the two frames to begin with, so nothing here may be assumed from
there.
green:
axis: BasisY
means: world up (world Z+) when the panel is correctly configured
confirmed: true
stated_as: >-
"vertical wall panel里面,世界的上方应该是绿色箭头" -- in a
vertical wall panel, world up should be the green arrow.
purple:
axis: null
means: null
confirmed: false
open_question: >-
Not yet stated. A vertical wall panel's casting orientation
(top-in-form) is not necessarily on the same axis as a
horizontal panel's -- do not infer BasisZ here. Asked of the
owner 2026-09-01; record the answer here, cited and dated, the
day it arrives.
red:
axis: null
means: null
confirmed: false
open_question: >-
Not yet stated. Do not infer BasisX (the horizontal frame's
"long direction") here -- on a vertical wall panel BasisY
already carries a different meaning (world up) from the
horizontal frame's (erected up when stood on end), so nothing
about red follows from the horizontal row either. Asked of the
owner 2026-09-01; record the answer here, cited and dated, the
day it arrives.
# -> rationale.md § orientation.up_end.applies_to
# CODE READS THIS FIELD VERBATIM -- up_end_from_origin() in
# WillTools.extension/lib/vibe/knowledge.py (~line 3269) declines its
# THIRD check when applies_to == 'horizontal_panels' and the piece's
# up_axis is not 'width' (i.e. the piece is not lying/erected horizontal).
# LEFT UNCHANGED here on purpose. also_seen_as above is DATA about what
# the three arrows mean on each frame; it is not a claim that the ruled
# mechanism is now confirmed to run on a vertical panel, and widening
# this value would make up_end_from_origin() stop declining there -- a
# change to real verdicts on real pieces, and the owner's to make, not
# this landing's. See wp401_2026_09_01 below for the one real piece this
# was checked against and what widening would and would not change on it.
applies_to: horizontal_panels
up_tolerance: 0.99
near_square_ratio: 1.25
declines_because: >-
A piece whose origin does not agree with its geometry, a vertical panel
whose frame nobody has explained yet, and a near-square piece that could
be either are three different reasons to have no answer. All three are
answers; none of them is a default.
# -> rationale.md § orientation.up_end.wp401_2026_09_01
wp401_2026_09_01: >-
WP401 is NOT an instance of applies_to declining -- it never reaches
that third check. Its measured basis_up is (0, 1, 0), so
up_end_from_origin()'s FIRST refusal (the up basis must actually be
world up, within up_tolerance) fires before up_axis or applies_to are
ever read; the report's fallback-to-global-elevation notice on WP401
is that first refusal, not the third. Under the green-arrow rule
above (vertical_wall_panels), a correctly configured vertical wall
panel's BasisY IS world up -- so WP401's (0, 1, 0) is a real defect on
that assembly's origin (TOT), not merely an unconfirmed frame; the
report's red line follows from this fact, not from applies_to.
Consequence for the code, worked out and not acted on: widening
applies_to to admit vertical_wall_panels would NOT change WP401's
verdict -- refusal #1 still fires first on it regardless. It WOULD
change any piece whose origin genuinely has BasisY at world up AND
whose up_axis is not 'width': such a piece currently declines at
refusal #3 (fallback to global elevation, disclosed) and would start
being read off the origin instead (the ruled mechanism, undisclosed
because it is no longer a fallback). No such piece has been surveyed.
That survey, and the decision whether to widen applies_to on the
strength of it, is the owner's, not made here.
# -> rationale.md § orientation.up_end.origin_point_meaning
origin_point_meaning: null
origin_point_unverified_because: >-
Nobody has measured where Transform.Origin sits relative to the piece,
and nobody has asked whether the owner's "up end" means the point or the
direction. Until one of those happens the point is not read at all.
catalogue:
# * 2.67 HAD NO SOURCE. Nothing published it, nobody typed it, and no
# sources: p53; 2026-08-30 2026-08-31
# -> rationale.md § catalogue.safety_factors
safety_factors:
tension_fos: null
shear_fos: null
unknown_because: >-
There is no trade-wide safety factor on a lifting insert to publish
here. A factor belongs to the part that was tested, so it is written
inside each `inserts:` entry against the page that publishes it. The
2.67 that used to sit here had no source and the manufacturer's own
page gives 4:1 in shear for the parts it covers; see the block above.
A part with no entry of its own is NOT judged on an ultimate rating
-- it is named as unjudged, because inheriting one manufacturer's
factor is exactly the failure this null exists to prevent.
inserts:
# sources: p53
# -> rationale.md § catalogue.inserts.QLPD2T3WG
QLPD2T3WG:
working_load_lb: 4000
shear_load_lb: 4000
ultimate_tension_lb: 16000
min_edge_distance_in: 6
safety_factors:
tension_fos: 4.0
shear_fos: 4.0
source: 'ALP catalog p53, 02/2024 -- "Capacity in Concrete 4:1
SWL", the table heading, spanning both load columns'
min_spacing_rule:
multiple_of_edge_distance: 2.0
published_in: null
source: 'ALP catalog p53, 02/2024 -- footnote, "Minimum anchor
spacing is 2x the published edge distance"'
basis_fc_psi: 3500
basis_pcf: 145
lifting_wythe_in: 3
clutch: QL001 (2T-3T)
QLPD4T3WG:
working_load_lb: 6257
shear_load_lb: 6257
ultimate_tension_lb: 32000
min_edge_distance_in: 6
safety_factors:
tension_fos: 4.0
shear_fos: 4.0
source: 'ALP catalog p53, 02/2024 -- "Capacity in Concrete 4:1
SWL", the table heading, spanning both load columns'
min_spacing_rule:
multiple_of_edge_distance: 2.0
published_in: null
source: 'ALP catalog p53, 02/2024 -- footnote, "Minimum anchor
spacing is 2x the published edge distance"'
basis_fc_psi: 3500
basis_pcf: 145
lifting_wythe_in: 3
clutch: QL002 (4T-6T)
QLPD4T4WG:
working_load_lb: 7200
shear_load_lb: 7200
ultimate_tension_lb: 32000
min_edge_distance_in: 7.5
safety_factors:
tension_fos: 4.0
shear_fos: 4.0
source: 'ALP catalog p53, 02/2024 -- "Capacity in Concrete 4:1
SWL", the table heading, spanning both load columns'
min_spacing_rule:
multiple_of_edge_distance: 2.0
published_in: null
source: 'ALP catalog p53, 02/2024 -- footnote, "Minimum anchor
spacing is 2x the published edge distance"'
basis_fc_psi: 3500
basis_pcf: 145
lifting_wythe_in: 4
clutch: QL002 (4T-6T)
QLPD4T5WG:
working_load_lb: 8000
shear_load_lb: 8000
ultimate_tension_lb: 32000
min_edge_distance_in: 9
safety_factors:
tension_fos: 4.0
shear_fos: 4.0
source: 'ALP catalog p53, 02/2024 -- "Capacity in Concrete 4:1
SWL", the table heading, spanning both load columns'
min_spacing_rule:
multiple_of_edge_distance: 2.0
published_in: null
source: 'ALP catalog p53, 02/2024 -- footnote, "Minimum anchor
spacing is 2x the published edge distance"'
basis_fc_psi: 3500
basis_pcf: 145
lifting_wythe_in: 5
clutch: QL002 (4T-6T)
note: >-
shear must act perpendicular to the anchor face and the centreline of
the recess; pulling in shear parallel to the face can spall the
concrete and overload the anchor
QLPD8T6WG:
working_load_lb: 11019
shear_load_lb: 11019
ultimate_tension_lb: 64000
min_edge_distance_in: 11
safety_factors:
tension_fos: 4.0
shear_fos: 4.0
source: 'ALP catalog p53, 02/2024 -- "Capacity in Concrete 4:1
SWL", the table heading, spanning both load columns'
min_spacing_rule:
multiple_of_edge_distance: 2.0
published_in: null
source: 'ALP catalog p53, 02/2024 -- footnote, "Minimum anchor
spacing is 2x the published edge distance"'
basis_fc_psi: 3500
basis_pcf: 145
lifting_wythe_in: 6
clutch: QL003 (8T-12T)
QLPD8T7WG:
working_load_lb: 14640
shear_load_lb: 14640
ultimate_tension_lb: 64000
min_edge_distance_in: 12.5
safety_factors:
tension_fos: 4.0
shear_fos: 4.0
source: 'ALP catalog p53, 02/2024 -- "Capacity in Concrete 4:1
SWL", the table heading, spanning both load columns'
min_spacing_rule:
multiple_of_edge_distance: 2.0
published_in: null
source: 'ALP catalog p53, 02/2024 -- footnote, "Minimum anchor
spacing is 2x the published edge distance"'
basis_fc_psi: 3500
basis_pcf: 145
lifting_wythe_in: 7
clutch: QL003 (8T-12T)
QLPD8T8WG:
working_load_lb: 16000
shear_load_lb: 16000
ultimate_tension_lb: 64000
min_edge_distance_in: 14
safety_factors:
tension_fos: 4.0
shear_fos: 4.0
source: 'ALP catalog p53, 02/2024 -- "Capacity in Concrete 4:1
SWL", the table heading, spanning both load columns'
min_spacing_rule:
multiple_of_edge_distance: 2.0
published_in: null
source: 'ALP catalog p53, 02/2024 -- footnote, "Minimum anchor
spacing is 2x the published edge distance"'
basis_fc_psi: 3500
basis_pcf: 145
lifting_wythe_in: 8
clutch: QL003 (8T-12T)
# sources: p16-18 (LIFTING PIN SYSTEM, "ALP Lifting Pin Anchors" /
# "ALP Lifting Pin Anchors capacity in concrete"); 2026-09-07
# -> rationale.md § catalogue.inserts.LPA4T512G
LPA4T512G:
manufacturer: ALP
ton: 4T
clutch: QL002 (4T-6T)
dimensions_in: {L: 5.5, HD: 1.4375, RD: 0.75, FD: 2}
ultimate_mechanical_tension_lb: 32000
weight_lb: 1.05
qty_per_bag: 50
qty_per_crate: 2000
head_marking: ALP 4T 512
# NOT one basis + sqrt(f'c) -- published at FOUR discrete concrete
# strengths, and capacity keeps RISING past 3,500 psi (6,950 lb at
# 3,500 -> 8,000 lb at 5,000, +15%), which concrete_derate()'s "no
# gain above basis" rule would silently cap at 6,950. So no
# working_load_lb / basis_fc_psi is set on this entry -- that would
# invite the generic derate formula to run on a table it does not
# fit -- and the four points land as their own table instead.
working_load_lb: null
basis_fc_psi: null
unrated_because: >-
rated at four discrete concrete strengths (by_fc_psi below), not a
single basis + sqrt(f'c) -- see the comment above this entry
by_fc_psi:
- {fc_psi: 1500, tension_lb: 4550}
- {fc_psi: 2500, tension_lb: 5850}
- {fc_psi: 3500, tension_lb: 6950}
- {fc_psi: 5000, tension_lb: 8000}
# TENSION edge distance -- what lifter_edge_distance() reads.
min_edge_distance_in: 11
# SHEAR edge distance is larger and is its own field on purpose: the
# page prints two DIFFERENT minimums (11in tension / 17in shear) and
# folding them into one number would either overstate the tension
# case or understate the shear one. No reader consumes this scalar
# yet -- lifter_edge_distance() is single-valued and reads the
# tension figure above.
min_edge_distance_shear_in: 17
# No published shear LOAD for this part -- p18's SWL columns are
# TENSION only (headed "Capacity in Concrete, 4:1 SWL", four PSI
# columns); shear appears only as the larger minimum edge distance
# above, never as a rated load. Naming it unjudged rather than
# borrowing another product's shear or guessing a factor.
shear_load_lb: null
shear_unknown_because: >-
ALP Catalog 2024 p18 tabulates tension by concrete strength only
for the LPA series; it publishes a minimum shear EDGE DISTANCE
(17in) for this part but no shear LOAD anywhere on the page.
safety_factors:
tension_fos: 4.0
shear_fos: null
source: 'ALP Catalog 2024 p18, "Capacity in Concrete, 4:1 SWL" --
the table heading. No shear factor is published for this part;
see shear_unknown_because.'
min_spacing_rule:
multiple_of_edge_distance: 2.0
published_in: null
source: 'ALP Catalog 2024 p18 footnote, "Minimum anchor spacing is
2x the published edge distance"'
source: 'ALP Catalog 2024 p16-17 (pdf p17-18, "ALP Lifting Pin
Anchors" -- dimensions, ultimate mechanical tension, weight;
Revised 04/30/25), p18 (pdf p18, "ALP Lifting Pin Anchors capacity
in concrete" -- SWL by concrete strength, edge distances; Revised
03/28/2024). Read 2026-09-07, PDF text layer. This is the
LPA4T512G row only -- the same two pages publish the full LPA
family (1T through 20T, 30 rows); only the one row this job
actually models is landed.'
# sources: p35; 2026-09-01
# -> rationale.md § catalogue.inserts.QL589SPG
QL589SPG:
working_load_lb: 16000
shear_load_lb: 4010
basis_fc_psi: 3500
basis_pcf: 145
clutch: QL003 (8T-12T)
# p35 publishes a 'Min. Corner Distance', which is NOT an edge
# distance -- a corner is two free edges at once and the two numbers
# are not interchangeable. It is recorded under its own name; this
# entry still has no published edge distance, and
# `lifter_spacing_minimum` is right to say it has nothing to double.
min_corner_distance_in: 18
requires_tension_bar_for_rating: true
tension_without_tension_bar_lb: 7690 # the 8 in row, for contrast
source: 'ALP Catalog 2024 p35, QuikLift Tech Erection Anchor capacity
table -- "Capacity in Concrete", columns "4:1 SWL Tension w/o Tension
Bar" and "4:1 SWL Tension w/ Tension Bar". Table basis 3,500 psi and
145 PCF. Read 2026-09-01; which column applies is chosen by the
plant layer, not here'
# A list of records, not a mapping keyed by thickness -- 2026-09-01, a
# bare float key here (`7.5:`) broke `json.dumps(..., sort_keys=True)`
# on the whole document with `TypeError: '<' not supported between
# instances of 'float' and 'str'` the moment any other section had a
# string key, and a float is a poor key for a reader doing a lookup
# with measured-thickness noise anyway (a string key sidesteps the
# TypeError but is still matched against float noise by whoever reads
# it). Six rows, each thickness explicit as its own field.
by_panel_thickness_in:
- {thickness_in: 7.5, shear_266_lb: 6032, shear_41_lb: 4010, tension_no_bar_lb: 7220, tension_with_bar_lb: 16000}
- {thickness_in: 8.0, shear_266_lb: 6032, shear_41_lb: 4010, tension_no_bar_lb: 7690, tension_with_bar_lb: 16000}
- {thickness_in: 9.0, shear_266_lb: 6195, shear_41_lb: 4120, tension_no_bar_lb: 8640, tension_with_bar_lb: 16000}
- {thickness_in: 10.0, shear_266_lb: 6436, shear_41_lb: 4280, tension_no_bar_lb: 9580, tension_with_bar_lb: 16000}
- {thickness_in: 11.0, shear_266_lb: 6647, shear_41_lb: 4420, tension_no_bar_lb: 10610, tension_with_bar_lb: 16000}
- {thickness_in: 12.0, shear_266_lb: 6842, shear_41_lb: 4550, tension_no_bar_lb: 11680, tension_with_bar_lb: 16000}
note: >-
used on edge at the top of a wall panel to stand it up, so shear is
the rating that governs while the piece is tilting and tension is the
one that governs once it hangs plumb. The 2.66:1 shear column is
available "at the discretion of the engineer for one-time lifts"
(p35 footnote) and is deliberately NOT the one carried as
shear_load_lb -- a one-time-lift allowance is the engineer's call,
not the checker's
# sources: p35; 2026-09-07
# -> rationale.md § catalogue.inserts.QL589G
QL589G:
# THE BASE ANCHOR -- no shear plate, no SPW/SPL/SPT dimensions,
# lighter and packed 250/crate against QL589SPG's 200/crate. Same
# ring clutch (QL003, 8T-12T), same page family (QL527G / QL548G /
# QL589G), a DIFFERENT physical part from QL589SPG above -- see the
# rationale note for why the two must not be read as one product.
working_load_lb: 16000
shear_load_lb: 4010
basis_fc_psi: 3500
basis_pcf: 145
clutch: QL003 (8T-12T)
dimensions_in: {W: 3.75, L: 13.5, T: 0.75, HD: 1, A: 1, B: 3.625, N: 3.125}
ultimate_mechanical_tension_lb: 64000
weight_lb: 8.44
qty_per_crate: 250
min_corner_distance_in: 18
requires_tension_bar_for_rating: true
tension_without_tension_bar_lb: 7690 # the 8 in row, for contrast
source: 'ALP Catalog 2024 p35 (pdf p35, "QuikLift Tech Erection
Anchor" -- QL527G / QL548G / QL589G table, footer "34 QUIKLIFT
SYSTEM 02/2024"). Read 2026-09-07 off the PDF text layer,
cross-checked at 8x zoom against the rendered page image.'
by_panel_thickness_in:
- {thickness_in: 7.5, shear_266_lb: 6032, shear_41_lb: 4010, tension_no_bar_lb: 7220, tension_with_bar_lb: 16000}
- {thickness_in: 8.0, shear_266_lb: 6032, shear_41_lb: 4010, tension_no_bar_lb: 7690, tension_with_bar_lb: 16000}
- {thickness_in: 9.0, shear_266_lb: 6195, shear_41_lb: 4120, tension_no_bar_lb: 8640, tension_with_bar_lb: 16000}
- {thickness_in: 10.0, shear_266_lb: 6436, shear_41_lb: 4280, tension_no_bar_lb: 9580, tension_with_bar_lb: 16000}
- {thickness_in: 11.0, shear_266_lb: 6647, shear_41_lb: 4420, tension_no_bar_lb: 10610, tension_with_bar_lb: 16000}
- {thickness_in: 12.0, shear_266_lb: 6842, shear_41_lb: 4550, tension_no_bar_lb: 11680, tension_with_bar_lb: 16000}
note: >-
used on edge at the top of a wall panel to stand it up, so shear is
the rating that governs while the piece is tilting and tension is the
one that governs once it hangs plumb. The 2.66:1 shear column is
available "at the discretion of the engineer for one-time lifts"
(p35 footnote) and is deliberately NOT the one carried as
shear_load_lb -- a one-time-lift allowance is the engineer's call,
not the checker's
# sources: p48 p49; 2026-09-01
# -> rationale.md § catalogue.inserts.QL184G
QL184G:
manufacturer: ALP
ton: 8T
clutch: QL003 (8T-12T)
dimensions_in:
W: 6
L: 4.75
T: 0.75
A: 3.5
B: 4.375
# Ultimate, not SWL -- see the capacity matrix below for the SWL
# values this anchor is actually rigged to. Provenance for the
# `reinforced_tension_4_1_lb` identity check on the shaded rows, not
# an input to a working-load check by itself.
ultimate_mechanical_tension_lb: 64000
weight_lb: 5.62
qty_per_crate: 350
# SOURCE: ALP Catalog 2024 p.48-49 (pdf p49-50), judge-verified 2026-09-01
QL184SPG:
manufacturer: ALP
ton: 8T
clutch: QL003 (8T-12T)
dimensions_in:
W: 6
L: 4.75
T: 0.75
A: 3.5
B: 4.375
SPW: 3.5
SPL: 3
SPT: 0.375
ultimate_mechanical_tension_lb: 64000
weight_lb: 6.84
qty_per_crate: 350
# SOURCE: ALP Catalog 2024 p.48-49 (pdf p49-50), judge-verified 2026-09-01
# ---- QuikLift Plate Anchors -- ALP Catalog 2024 p52 (pdf p53), footer
# "52 QUIKLIFT SYSTEM 02/2025". Back-stripping anchors, a plate
# welded to the foot rather than the LPA/QuikLift ring-clutch
# shapes above. sources: p52; 2026-09-07
# -> rationale.md § catalogue.inserts.QL846G
QL846G:
manufacturer: ALP
ton: 4T
clutch: QL002 (4T-6T)
dimensions_in: {W: 1.5, H: 3, T: 0.625, FL: 3}
ultimate_mechanical_tension_lb: 32000
weight_lb: 1.23
qty_per_crate: 2000
working_load_lb: 3570
shear_load_lb: 3570
min_edge_distance_in: 6
# 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" -- do not read min_edge_distance_in as
# a shear edge distance too.
shear_edge_distance_published: false
note: >-
rebar should not be placed between the recess member and the base
plate; this may reduce the capacity of the system (page dagger
note, marked on this part)
safety_factors:
tension_fos: 4.0
shear_fos: 4.0
source: 'ALP Catalog 2024 p52, "Capacity in Concrete 4:1 SWL" --
the table heading, spanning both load columns'
min_spacing_rule:
multiple_of_edge_distance: 2.0
published_in: null
source: 'ALP Catalog 2024 p52 footnote, "Minimum anchor spacing is
2x the published edge distance"'
basis_fc_psi: 3500
basis_pcf: 145
source: 'ALP Catalog 2024 p52 (pdf p53, "QuikLift Plate Anchors" --
dimensions and "Capacity in Concrete 4:1 SWL" tables). Read
2026-09-07 off the PDF text layer, cross-checked at 8x zoom
against the rendered page image.'
QL847G:
manufacturer: ALP
ton: 4T
clutch: QL002 (4T-6T)
dimensions_in: {W: 1.5, H: 3.5, T: 0.625, FL: 3}
ultimate_mechanical_tension_lb: 32000
weight_lb: 1.42
qty_per_crate: 1700
working_load_lb: 4700
shear_load_lb: 4700
min_edge_distance_in: 7
shear_edge_distance_published: false
note: >-
rebar should not be placed between the recess member and the base
plate; this may reduce the capacity of the system (page dagger
note, marked on this part)
safety_factors:
tension_fos: 4.0
shear_fos: 4.0
source: 'ALP Catalog 2024 p52, "Capacity in Concrete 4:1 SWL" --
the table heading, spanning both load columns'
min_spacing_rule:
multiple_of_edge_distance: 2.0
published_in: null
source: 'ALP Catalog 2024 p52 footnote, "Minimum anchor spacing is
2x the published edge distance"'
basis_fc_psi: 3500
basis_pcf: 145
source: 'ALP Catalog 2024 p52 (pdf p53, "QuikLift Plate Anchors" --
dimensions and "Capacity in Concrete 4:1 SWL" tables). Read
2026-09-07 off the PDF text layer, cross-checked at 8x zoom
against the rendered page image.'
QL044G:
manufacturer: ALP
ton: 4T
clutch: QL002 (4T-6T)
dimensions_in: {W: 1.5, H: 4.375, T: 0.625, FL: 3.875}
ultimate_mechanical_tension_lb: 32000
weight_lb: 1.81
qty_per_crate: 1000
working_load_lb: 4730
shear_load_lb: 4730
min_edge_distance_in: 9
shear_edge_distance_published: false
# No dagger note on this row -- the rebar-placement caution above is
# marked on QL846G / QL847G (and QL128G) only, not on this part.
safety_factors:
tension_fos: 4.0
shear_fos: 4.0
source: 'ALP Catalog 2024 p52, "Capacity in Concrete 4:1 SWL" --
the table heading, spanning both load columns'
min_spacing_rule:
multiple_of_edge_distance: 2.0
published_in: null
source: 'ALP Catalog 2024 p52 footnote, "Minimum anchor spacing is
2x the published edge distance"'
basis_fc_psi: 3500
basis_pcf: 145
source: 'ALP Catalog 2024 p52 (pdf p53, "QuikLift Plate Anchors" --
dimensions and "Capacity in Concrete 4:1 SWL" tables). Read
2026-09-07 off the PDF text layer, cross-checked at 8x zoom
against the rendered page image.'
QL054G:
manufacturer: ALP
ton: 8T
clutch: QL003 (8T-12T)
dimensions_in: {W: 2.5, H: 6.25, T: 0.75, FL: 5}
ultimate_mechanical_tension_lb: 64000
weight_lb: 5.43
qty_per_crate: 400
working_load_lb: 6350
shear_load_lb: 6350
min_edge_distance_in: 12
shear_edge_distance_published: false
# No dagger note on this row -- see QL044G's comment above.
safety_factors:
tension_fos: 4.0
shear_fos: 4.0
source: 'ALP Catalog 2024 p52, "Capacity in Concrete 4:1 SWL" --
the table heading, spanning both load columns'
min_spacing_rule:
multiple_of_edge_distance: 2.0
published_in: null
source: 'ALP Catalog 2024 p52 footnote, "Minimum anchor spacing is
2x the published edge distance"'
basis_fc_psi: 3500
basis_pcf: 145
source: 'ALP Catalog 2024 p52 (pdf p53, "QuikLift Plate Anchors" --
dimensions and "Capacity in Concrete 4:1 SWL" tables). Read
2026-09-07 off the PDF text layer, cross-checked at 8x zoom
against the rendered page image.'
# sources: p48 p49 p50 p35; 2026-09-01
# -> rationale.md § catalogue.insulated_panel_anchor_capacities
insulated_panel_anchor_capacities:
_source: 'ALP Catalog 2024 p.48-49 (pdf p49-50), judge-verified 2026-09-01'
_basis_fc_psi: 4500
_rebar_grade: 60
panel_group_triple_semantics_uncertain: true
panel_group_triple_semantics_unknown_because: >-
The panel_group caption's three numbers (e.g. `(2" x 2" x 4")`) are
never defined on the page -- the catalogue does not say which
position is the exterior wythe, the insulation, or the interior
wythe, and does not say the order is even consistent between groups.
The left-side thumbnail sketches make this worse rather than better:
the "(2\" x 2\" x 4\")" group's thumbnail dimensions read 4/2/2
top-to-bottom, and the "(4\" x 2\" x 2\")" group's thumbnail reads
2/2/4 -- the two thumbnails do not even agree with each other on an
order, let alone with the caption string. Landed as the verbatim
caption string only. The order's meaning is an open question for
Will or ALP, never a parsed value.
rows:
- panel_group: 8" insulated panel (2" x 2" x 4")
part_number: QL184SPG
ton: 8T
shear_2_66_1_lb: 6900
shear_4_1_lb: 4600
shear_perpendicular_4_1_lb: 9870
reinforced_tension_4_1_lb: 16000
reinforced_tension_mechanically_limited: true
rebar_size: '#5'
bottom_bend_tl_in: 49
bottom_bend_detail: B
top_bend_tl_in: 39
top_bend_detail: A
- panel_group: 8" insulated panel (3" x 2" x 3")
part_number: QL184G
ton: 8T
shear_2_66_1_lb: 5775
shear_4_1_lb: 3850
shear_perpendicular_4_1_lb: 9900
reinforced_tension_4_1_lb: 16000
reinforced_tension_mechanically_limited: true
rebar_size: '#5'
bottom_bend_tl_in: 39
bottom_bend_detail: A
top_bend_tl_in: 39
top_bend_detail: A
- panel_group: 8" insulated panel (3" x 2" x 3")
part_number: QL184SPG
ton: 8T
shear_2_66_1_lb: 6600
shear_4_1_lb: 4400
shear_perpendicular_4_1_lb: 8800
reinforced_tension_4_1_lb: 16000
reinforced_tension_mechanically_limited: true
rebar_size: '#5'
bottom_bend_tl_in: 39
bottom_bend_detail: A
top_bend_tl_in: 39
top_bend_detail: A
- panel_group: 8" insulated panel (4" x 2" x 2")
part_number: QL184G
ton: 8T
shear_2_66_1_lb: 5175
shear_4_1_lb: 3450
shear_perpendicular_4_1_lb: 10500
reinforced_tension_4_1_lb: 16000
reinforced_tension_mechanically_limited: true
rebar_size: '#5'
bottom_bend_tl_in: 39
bottom_bend_detail: A
top_bend_tl_in: 49
top_bend_detail: B
# sources: 2026-08-30
# -> rationale.md § catalogue.rebar
rebar:
stock_length_ft: 40.0
# -> rationale.md § catalogue.rebar.astm_grades
astm_grades:
# Low-alloy, the weldable spec. Grade 60 only for most of its life, and
# the current A706/A706M also defines Grade 80 -- so 60 is a sound
# DEFAULT and not an identity. The default is Will's ruling,
# 2026-08-30, asked as a question ("A706 不就是 60ksi?") and recorded
# as the answer to one.
A706:
grades_ksi: [60, 80]
default_fy_ksi: 60
default_declared_by: Will
default_declared_on: '2026-08-30'
note: Grade 60 is the default, not the definition -- A706 Grade 80 exists.
# Carbon steel. The current standard covers Grades 60, 80 and 100, and
# historically 40 and 75 as well. NO DEFAULT IS DEFENSIBLE: a bar
# listed only as A615 could be any of them, and a check that assumed 60
# would be confidently wrong on a job that uses 80.
A615:
grades_ksi: [60, 80, 100]
default_fy_ksi: null
note: the designation alone does not give a grade -- ask, do not assume.
# Welded wire reinforcement (WWF / mesh): the DESIGNATION GRAMMAR, which
# is public (ASTM A1064/A1064M, size-number definition; also the trade
# explainers, e.g. mcswusa.com "how to read welded wire mesh sizes"),
# landed 2026-09-07 for issue #23 item 9g. Which STYLES a plant stocks is
# plant knowledge and lives in each plant layer (`mesh:`), never here.
#
# "6x6-W2.9xW2.9" -> longitudinal spacing 6 in, transverse spacing 6 in,
# longitudinal wire W2.9, transverse wire W2.9
# W = plain (smooth) wire, D = deformed wire; the number after the letter
# is the wire's nominal cross-sectional area in HUNDREDTHS of a square
# inch (W2.9 = 0.029 in2, D3 = 0.03 in2, D16 = 0.16 in2).
# area per foot of width, one layer = (100 / spacing_in) ... i.e.
# area_in2_per_ft = wire_area_in2 * 12 / spacing_in
# (6x6-W2.9xW2.9 -> 0.029 * 12 / 6 = 0.058 in2/ft each way).
# Older "gauge" designations (e.g. 6x6-10/10) are a different system and
# are NOT converted here; a gauge string is reported unparsed, never
# guessed into an area.
wwf:
designation_pattern: '<long_spacing>x<trans_spacing>-<long_wire>x<trans_wire>'
wire_letter_meaning: {W: plain, D: deformed}
wire_number_unit: hundredths of a square inch of nominal wire area
area_per_ft_formula: wire_area_in2 * 12 / spacing_in
gauge_designations_convertible: false
# SOURCE: ASTM A1064/A1064M size-number footnote ("the number of one
# hundredth of a square inch in the nominal area of the wire cross
# section"), read 2026-09-07 via the ASTM store abstract; the
# designation layout per Mid Continent Steel & Wire's reading guide.
# sources: 2026-08-31
# -> rationale.md § catalogue.slotted_inserts
slotted_inserts:
_source: 'JVI PSA catalogue 2016-5, p.3 (capacities), p.6 (dimensions), p.7 (edge distance, weights), p.5 (strap anchors, materials, finishes)'
_read_on: '2026-08-31'
# THE FACTOR THAT IS NOT HERE, said once for the whole block.
safety_factors:
tension_fos: null
shear_fos: null
unknown_because: >-
The catalogue publishes ULTIMATE pull-out and shear and states no
safety factor anywhere -- no heading, no footnote, no test-report
note. A working load cannot be derived from these without one, and
the 4:1 the disk anchors above publish belongs to a different
manufacturer's parts and may not be borrowed. A check that needs a
working load stands down naming this key.
capacity_is_ultimate: true
# The concrete these were tested in is not stated either, so there is no
# `basis_fc_psi` to scale from. Absent, not assumed.
basis_fc_psi: null
basis_fc_unknown_because: >-
The catalogue states no concrete strength for the published
capacities. Nothing here scales with sqrt(f'c) because there is no
basis to scale from.
# -> rationale.md § catalogue.slotted_inserts.edge_distance
edge_distance:
along_slot_axis_in: 6
across_slot_axis_in: 10
basis: without additional reinforcing steel
reducible_with_extra_reinforcement: true
reduction_source: 'PSA Test Report #2'
reduced_value_in: null
reduced_value_unknown_because: >-
The page says the edge distance CAN be reduced with extra
reinforcement and cites Test Report #2 for how far. The report is
not in hand, so there is no reduced number -- only the fact that a
reduction exists and where it is published.
note: >-
6 in. is measured along the slot's long axis and 10 in.
perpendicular to it. Both figures on the page show the same
invariant with the insert turned 90 degrees. NOT a standard/
reinforced pair -- both are the unreinforced minimum.
# -> rationale.md § catalogue.slotted_inserts.unreinforced_top_edge_reduction
unreinforced_top_edge_reduction:
fraction: 0.25
applies_to: ultimate_load
condition: unreinforced insert placed at the top edge of the panel
removed_by: added reinforcement -- restores full pull-out capacity
source: 'PSA Test Report #2'
not_a_general_unreinforced_penalty: true
# ---- the recommended edge reinforcement, as prose ----------------
#
# The page draws it rather than tabulating it: stepped hairpins near the
# edge, 3 1/2" from the edge, 3/4" minimum cover all round, 2 - #3
# stepped hairpins in the suggested corner detail, described as being IN
# ADDITION TO the panel's own reinforcement. Carried as text because it
# is a detail, not a rating.
edge_reinforcement: >-
Stepped hairpins near the edge -- the suggested corner detail draws
2 - #3 stepped hairpins at 3 1/2 in. from the edge with 3/4 in.
minimum cover, explicitly in addition to the panel's own
reinforcement. Drawn, not tabulated; no capacity is attached to it.
# ---- finishes ----------------------------------------------------
#
# Three, and they are three different material specifications rather
# than three coatings on one body -- a stainless insert has a stainless
# body and stainless nuts, not a coated carbon body. Material specs are
# carried per finish for that reason.
finishes:
j_finish:
what_it_is: >-
A patented three-step coating -- zinc plate to ASTM B633 Type II
at 0.0005 in. thickness, trivalent clear chromate (RoHS and ELV
compliant), then a seal coat. It reaches the threads of a strap
anchor, which is the area a dipped coating leaves most exposed.
plating_spec: 'ASTM B633, Type II'
plating_thickness_in: 0.0005
corrosion_test: 'ASTM B 117'
hours_to_red_rust: 500
hours_to_white_rust: 96
insert_body: 'Hot Rolled, P & O, HSLA SAE J2340 490XLF'
insert_square_nuts: '3/4 in. dia. NC thread, SAE J995 GR-5'
insert_studs: 'AISI 1016 or 1018 (round head)'
strap_body: 'ASTM A36/A529 GR 50, CSA G40.21 44W'
strap_jam_nuts: '3/4 in. dia. 10 UNC-2, SAE GR 5 hex, SAE J995'
strap_thread_stud: '3/4 in. dia. 10 UNC-2, AISI 1038 CRS'
hot_dipped_galvanized:
note: >-
Shares the J-finish material specification block on the page --
the same heading covers "J Finish & Hot Dipped Galvanized" for
both inserts and threaded strap anchors. No separate corrosion
figures are published for it.
corrosion_hours: null
corrosion_hours_unknown_because: >-
The 500 h / 96 h ASTM B 117 figures are printed under the J-finish
heading only. Nothing publishes an equivalent for the galvanized
option and it is not inherited.
stainless_steel:
insert_body: 'AISI 304 stainless steel'
insert_square_nuts: '3/4 in. dia. NC thread, AISI 304, ASTM A194 GR 8'
insert_studs: 'AISI 430 stainless steel'
strap_body: 'AISI 304 stainless steel'
strap_jam_nuts: 'AISI 304 stainless steel, ASTM A194 GR 8'
strap_thread_stud: '3/4 in. dia. 10 UNC-2, AISI 304 rolled formed'
plastic_closers: non-structural plastic, all finishes
welding: 'robot welding with minimum penetration of 0.8 mm (threaded strap anchors)'
# -> rationale.md § catalogue.slotted_inserts.models
models:
'4525':
series: '4500'
ultimate_pull_out_lb: 13400
ultimate_shear_lb: 20000
_stated_as: {pull_out: '>13,400 lbs.', shear: '>20,000 lbs.'}
available_adjustment_in: 2.875
depth_no_stud_in: 2.5
stud_length_in: null
stud_length_note: the shallowest model has no stud; the M dimension is blank on the page
weight_lb: 1.5
weight_kgf: 0.680
'4535':
series: '4500'
ultimate_pull_out_lb: 18800
ultimate_shear_lb: 20000
_stated_as: {pull_out: '>18,800 lbs.', shear: '>20,000 lbs.'}
available_adjustment_in: 2.875
depth_no_stud_in: 3.5
stud_length_in: 2.25
weight_lb: 1.9
weight_kgf: 0.862
'4545':
series: '4500'
ultimate_pull_out_lb: 21500
ultimate_shear_lb: 20000
_stated_as: {pull_out: '>21,500 lbs.', shear: '>20,000 lbs.'}
available_adjustment_in: 2.125
depth_no_stud_in: 4.5
stud_length_in: 3.25
weight_lb: 2.3
weight_kgf: 1.043
'6025':
series: '6000'
ultimate_pull_out_lb: 13400
ultimate_shear_lb: 20000
_stated_as: {pull_out: '>13,400 lbs.', shear: '>20,000 lbs.'}
available_adjustment_in: 4.375
depth_no_stud_in: 2.5
stud_length_in: null
stud_length_note: as 4525 -- no stud, M dimension blank
weight_lb: 2.0
weight_kgf: 0.907
'6035':
series: '6000'
ultimate_pull_out_lb: 18800
ultimate_shear_lb: 20000
_stated_as: {pull_out: '>18,800 lbs.', shear: '>20,000 lbs.'}
available_adjustment_in: 4.375
depth_no_stud_in: 3.5
stud_length_in: 2.25
weight_lb: 2.3
weight_kgf: 1.043
'6045':
series: '6000'
ultimate_pull_out_lb: 21500
ultimate_shear_lb: 20000
_stated_as: {pull_out: '>21,500 lbs.', shear: '>20,000 lbs.'}
available_adjustment_in: 3.625
depth_no_stud_in: 4.5
stud_length_in: 3.25
weight_lb: 2.5
weight_kgf: 1.134
# ---- the dimensions the two series share ------------------------
#
# Everything on the dimensional sketch that is NOT per model. Split out
# rather than repeated on six entries, because a value repeated six
# times is a value that goes out of step five times.
series_dimensions:
'4500':
length_overall_in: 5.75
length_excluding_plastic_tab_in: 4.5
width_overall_in: 4.125
width_at_base_in: 2.0
centre_to_centre_same_side_in: 1.5
centre_to_centre_across_insert_in: 2.75
top_of_insert_to_wing_in: 1.25
hole_diameter_in: 0.5625
stud_head_diameter_in: 0.875
'6000':
length_overall_in: 7.25
length_excluding_plastic_tab_in: 6.0
width_overall_in: 4.125
width_at_base_in: 2.0
centre_to_centre_same_side_in: 2.5
centre_to_centre_across_insert_in: 2.75
top_of_insert_to_wing_in: 1.25
hole_diameter_in: 0.5625
stud_head_diameter_in: 0.875
# -> rationale.md § catalogue.slotted_inserts.strap_anchors
strap_anchors:
tension_capacity_lb: 30000
tension_stated_as: 'Tension capacity exceeds 30 KIPS'
widths_in: [2]
widths_note: >-
The published shear table is for the 2 in. width, which the page
calls the highest-shear option ("Our standard 2" wide welded strap
anchor provides the Highest shear capacity"). Other widths exist --
the page says a range of strap anchors gives a range of shear
capacities -- and none of them is published here.
thickness_in: 0.375
lengths_in: [6, 8, 10, 12]
# The four catalogue numbers, and they read as the length: 675 is the
# 6 in. strap, 875 the 8 in., and so on. The 75 is the 3/4 in. thread.
models:
'675': {length_in: 6, weight_lb: 1.2, weight_kgf: 0.544}
'875': {length_in: 8, weight_lb: 1.6, weight_kgf: 0.726}
'1075': {length_in: 10, weight_lb: 2.0, weight_kgf: 0.907}
'1275': {length_in: 12, weight_lb: 2.4, weight_kgf: 1.089}
# Average tested shear against eccentricity, 2 in. width. The page
# heads the column "KIPS - Avg", so these are means and not the
# guaranteed minimums the insert capacities are.
# Keys are STRINGS ('1.0', not 1.0) for the same reason as
# catalogue.inserts.QL589SPG.by_panel_thickness_in -- a bare float key
# here mixes with the string keys `_units` / `_basis` in the same
# mapping and breaks `json.dumps(..., sort_keys=True)` on the whole
# document with `TypeError: '<' not supported between instances of
# 'float' and 'str'`. Found and fixed 2026-09-01 alongside that table.
shear_by_eccentricity_2in_width:
_units: kips
_basis: average
'1.0': 20.2
'1.5': 17.9
'2.0': 14.2
'2.5': 13.2
'3.0': 11.3
# Two erection facts with no numeric home except one clearance.
threaded_vs_notched: >-
A threaded strap rotates to any angle to take out tolerance without
losing pull-out capacity; a notched strap installs at 90 degrees to
the insert and concentrates the shear over its 3/8 in. notch width,
where the threaded strap spreads it across the insert lips.
notched_strap_rotation_clearance_in: 2
notched_strap_erection_caution: >-
A notched strap anchor must be placed after the panel is set and
needs at least 2 in. of clearance to rotate to 90 degrees. If the
insert sits too low, tipping the panel away from the frame to clear
the insert opening puts the installer's hands in the gap.
# ---- where the numbers come from, and what is not published -----
test_reports:
published_by: 'JVI (www.jvi-inc.com)'
list:
'1': pull-out capacity
'2': pull-out capacity near edge
'3': shear capacity
'4': pull-out capacity, sandwich panels
'5': pull-out capacity, end of insert
'6': corrosion resistance
'7': 30 KIP load capacity
note: >-
The catalogue cites these seven for its capacities and its edge
rules but reproduces none of them. Report #2 is the one this file
needs and does not have -- it is what would give a reduced edge
distance a number.
# -> rationale.md § catalogue.shear_grid
shear_grid:
c_grid:
manufacturer: Altus
product: C-GRID
what_it_is: >-
A carbon-fibre grid cast across the insulation of a sandwich wall
panel to transfer horizontal shear between the wythes. Capacity is
a shear flow -- pounds per inch of grid -- because the grid is
continuous, not a discrete part.
source: >-
Altus C-GRID Table 4.1, "Test Data and Nominal Strength Values",
reproduced in a 2022 C-GRID insulated-wall-panel design deck. Basis
stated on the same page: "To date Altus has performed over 200
double shear tests to arrive at values listed below."
test_basis: over 200 double shear tests
test_method: double shear on rectangular specimens, VQ/I
source_year: 2022
read_on: '2026-08-31'
recommended_value_rule: >-
recommended = average of (average test value) and (maximum minus 3
standard deviations), rounded half up. Verified to hold on all 37
published rows -- which is what checks this transcription.
maximum_rule: >-
The published maximum is 1.5 times the average, based on VQ/I shear
on the rectangular test specimens -- it is derived, not a
largest-observed.
values_are_nominal: true
values_are_nominal_note: >-
Nominal, unfactored. The phi a designer applies depends on the check
and on the insulation and is design practice, not product data --
it belongs in a plant layer, never here.
single_test_per_spacing_types: [POLY ISO]
single_test_caveat: >-
The page footnotes POLY ISO with "Single test for each spacing.
Standard deviation is across tests" -- i.e. that family's spread is
borrowed across spacings rather than measured within one. Its three
rows are weaker evidence than the rest of the table and the page
says so.
# A note printed above the table, for a condition the table does not
# cover: "Design value of C-GRID with debonded insulation is
# approximately 100 pli."
debonded_insulation_pli: 100
debonded_insulation_note: >-
Stated as "approximately", for debonded insulation, outside the
keyed table. It is not an entry in it and must not be read as one.
# -> rationale.md § catalogue.shear_grid.c_grid.shear_flow
shear_flow:
_units: pli
_columns: [average_test, max_minus_3sd, recommended_nominal]
EPS:
2: {12: [280, 381, 331], 24: [288, 414, 351], 48: [512, 711, 612]}
4: {12: [163, 223.5, 193], 24: [243, 334.5, 289], 48: [425, 496.5, 461]}
6: {12: [186, 258, 222], 24: [213, 286.5, 250], 48: [314, 360, 337]}
XPS:
2: {12: [212, 178, 195], 18: [180, 207, 194], 24: [160, 152, 156], 36: [142, 202, 172]}
4: {12: [176, 221, 199], 18: [198, 203, 201], 24: [161, 128, 145], 36: [163, 155, 159]}
XPS_sand_blasted:
2: {12: [290, 325, 308], 24: [540, 658, 599], 48: [586, 469, 528]}
4: {12: [314, 462, 388], 24: [444, 607, 526], 48: [593, 712, 653]}
XPS_machined:
3: {12: [243, 247, 245]}
POLY_ISO:
4: {12: [205, 289.5, 247], 24: [224, 276, 250], 48: [102, 105, 104]}
Kooltherm:
2: {12: [222, 234, 228], 24: [211, 277, 244], 48: [269, 144, 207]}
4: {12: [175, 223.5, 199], 24: [212, 270, 241], 48: [219, 259.5, 239]}
NeoPor_low_density:
2: {24: [438, 513, 476]}
6: {24: [323, 370.5, 347]}
NeoPor_high_density:
2: {24: [534, 735, 635]}
4: {24: [533, 750, 642]}
# ---- what this table does NOT publish --------------------------
#
# STIFFNESS. A partial-composite calculation needs a shear stiffness
# as well as a strength, and Table 4.1 is strengths only. Nothing on
# the page carries a k/in. A module that assumes a stiffness because
# it has a strength is inventing the half that governs deflection.
stiffness_k_per_in: null
stiffness_unknown_because: >-
Table 4.1 publishes shear flow STRENGTH only. No stiffness value
appears on the page in any form, so a partial-composite calculation
that needs one has to get it elsewhere and may not derive it from
these.
# FAILURE MODE. 200+ tests, and the page classifies none of them.
failure_modes: null
failure_modes_unknown_because: >-
The page states the test count and the derived values and never
classifies the failures. Whether a row is governed by the grid, the
bond or the insulation is not published.
# sources: 2026-09-07
# -> PCI Design Handbook 9th ed. (MNL-120-25), Section 5.8 "Loss of
# Prestress", pdf viewer p293-296, printed p5-42 to 5-45. Read via the
# Apryse WebViewer text layer, with Tables 5.8.1/5.8.2 and Eq. 5-105/
# 5-106 SCREEN-VERIFIED 2026-09-07 at 93% zoom (Eq. 5-105/5-106 also
# cross-checked numerically against Table 5.8.2 at three points, agree
# within rounding). Full extraction:
# knowledge/library/notes/PCI9-5.8-losses.md. Method: ACI-ASCE Committee
# 423 task group report (Zia et al. 1979), reprinted PCI Design Handbook
# 9th ed. Section 5.8.3 -- this is what will/REVIT #24 needs: the engine
# today assumes a lump-sum loss percentage, and the owner ruled "不要
# assume prestress loss 了,要算的,根据 PCI 算". Applies to pretensioned
# components; the handbook itself notes more detailed analysis may be
# warranted for unusual situations.
# -> rationale.md § catalogue.prestress_losses
prestress_losses:
method: 'PCI/Zia 1979 (ACI-ASCE Committee 423 task group report), PCI Design Handbook 9th ed. Section 5.8.3'
source: 'PCI Design Handbook 9th ed. (MNL-120-25) Section 5.8.3, p294-295 / printed 5-43 to 5-44'
applicability: pretensioned components
total_loss:
formula: 'TL = ES + CR + SH + RE'
source: 'Eq. 5-98, p294 / 5-43'
range_normalweight_psi: {min: 25000, max: 50000}
range_sand_lightweight_psi: {min: 30000, max: 55000}
load_table_lower_limit_psi: 30000
note: >-
"Total loss of prestress in typical components will range from
about 25,000 to 50,000 psi for normalweight concrete components,
and from about 30,000 to 55,000 psi for sand-lightweight
components... The load tables in Chapter 3 have a lower limit on
loss of 30,000 psi." Section 5.8.2, p294 / 5-43.
elastic_shortening:
formula: 'ES = Kes * Eps * fcir / Eci'
kes_pretensioned: 1.0
eps_psi: 28500000
eps_note: "about 28.5 x 10^6 psi -- Eps is a stated APPROXIMATION on this page, not itself a strand-specific modulus"
fcir_formula: 'fcir = Kcir*(Pi/Ag + Pi*e^2/Ig) - Mg*e/Ig'
kcir_pretensioned: 0.9
source: 'Eq. 5-99/5-100, p294-295 / 5-43 to 5-44'
creep:
formula: 'CR = Kcr * (Eps/Ec) * (fcir - fcds)'
kcr_normalweight: 2.0
kcr_sand_lightweight: 1.6
fcds_formula: 'fcds = Msd*e/Ig'
source: 'Eq. 5-101/5-102, p295 / 5-44. SCREEN-VERIFIED 2026-09-07.'
composite_note: >-
For composite components, if some dead load is applied to the
composite section, fcds is calculated in two operations -- one
for the noncomposite condition, one for the composite condition.
shrinkage:
formula: 'SH = 8.2e-6 * Ksh * Eps * (1 - 0.06*VS) * (100 - RH)'
ksh_pretensioned: 1.0
vs_definition: 'volume-to-surface ratio'
rh_definition: 'average ambient relative humidity, see PCI Design Aid 4-12'
source: 'Eq. 5-103, p295 / 5-44'
relaxation:
formula: 'RE = (Kre - J*(SH + CR + ES)) * C'
source: 'Eq. 5-104, p295 / 5-44'
kre_j_by_steel_type:
# Table 5.8.1, SCREEN-VERIFIED 2026-09-07 at 93% zoom against
# p295 / printed 5-44.
grade_270_low_relaxation_strand: {kre: 5000, j: 0.040}
grade_250_low_relaxation_wire: {kre: 4630, j: 0.037}
grade_240_or_235_low_relaxation_wire: {kre: 4400, j: 0.035}
grade_145_or_160_stress_relieved_bar: {kre: 6000, j: 0.050}
c_coefficient:
# Table 5.8.2 and its closed-form Eq. 5-105/5-106, both
# SCREEN-VERIFIED 2026-09-07 and cross-checked numerically
# against each other (three spot checks, agree within rounding --
# see knowledge/library/notes/PCI9-5.8-losses.md).
# STRING keys -- see catalogue.slotted_inserts.strap_anchors for
# why a bare float key here would break json.dumps(sort_keys=True).
table_by_fpi_over_fpu_ge_054:
# stress-relieved bar or low-relaxation strand/wire
'0.80': 1.28
'0.79': 1.22
'0.78': 1.16
'0.77': 1.11
'0.76': 1.05
'0.75': 1.00
'0.74': 0.95
'0.73': 0.90
'0.72': 0.85
'0.71': 0.80
'0.70': 0.75
'0.69': 0.70
'0.68': 0.66
'0.67': 0.61
'0.66': 0.57
'0.65': 0.53
'0.64': 0.49
'0.63': 0.45
'0.62': 0.41
'0.61': 0.37
'0.60': 0.33
formula_ge_054: 'C = (fpi_over_fpu / 0.21) * (fpi_over_fpu / 0.9 - 0.55)'
formula_ge_054_condition: 'low-relaxation strand, fpi/fpu >= 0.54, Eq. 5-105'
formula_le_054: 'C = fpi_over_fpu / 4.25'
formula_le_054_condition: 'low-relaxation strand, fpi/fpu <= 0.54, Eq. 5-106'
fpi_definition: 'fpi = Pi / Aps'
elastic_gain:
formula: 'fEG = (Eps/Ec) * fcgp'
source: 'Eq. 5-107, p296 / 5-45'
note: 'decrease in prestress loss from a transient superimposed load; in effect only while the transient load is active'
critical_locations:
note: >-
ES and CR losses are computed at the point of maximum flexural
tensile stress -- midspan for straight/parabolic/approximate-
parabolic strand, near the 0.4l point for strand deflected at
midspan only. Only the critical-point stresses need be
considered for bonded strand, unless a critical section falls
within the development length (Section 5.2.3).
source: 'Section 5.8.4, p295-296 / 5-44 to 5-45'
# sources: 2026-09-07
# -> PCI Design Handbook 9th ed., Section 15.2 "Material Properties --
# Prestressing Steel", Design Aids 15-9/15-10/15-11/15-12, pdf viewer
# p862-863, printed p15-31/15-32. SCREEN-VERIFIED 2026-09-07 at 93%
# zoom -- full-page render matches the text-layer transcription
# exactly. Full extraction:
# knowledge/library/notes/PCI9-ch15-reference.md.
# -> rationale.md § catalogue.strand
strand:
astm_a416_properties:
source: 'ASTM A416/A416M-18 via PCI Design Handbook 9th ed. Design Aid 15-9, p862 / 15-31'
grade_270:
fpu_ksi: 270
sizes:
'3/8': {diameter_in: 0.375, area_in2: 0.085, weight_lb_ft: 0.29}
'7/16': {diameter_in: 0.438, area_in2: 0.115, weight_lb_ft: 0.39}
'1/2': {diameter_in: 0.500, area_in2: 0.153, weight_lb_ft: 0.52}
'1/2 special': {diameter_in: 0.520, area_in2: 0.167, weight_lb_ft: 0.57}
'9/16': {diameter_in: 0.563, area_in2: 0.192, weight_lb_ft: 0.65}
'0.6': {diameter_in: 0.600, area_in2: 0.217, weight_lb_ft: 0.74}
'0.62': {diameter_in: 0.620, area_in2: 0.231, weight_lb_ft: 0.78}
'0.7': {diameter_in: 0.700, area_in2: 0.294, weight_lb_ft: 1.00}
grade_250:
fpu_ksi: 250
sizes:
'1/4': {diameter_in: 0.250, area_in2: 0.036, weight_lb_ft: 0.12}
'5/16': {diameter_in: 0.313, area_in2: 0.058, weight_lb_ft: 0.20}
'3/8': {diameter_in: 0.375, area_in2: 0.080, weight_lb_ft: 0.27}
'7/16': {diameter_in: 0.438, area_in2: 0.108, weight_lb_ft: 0.37}
'1/2': {diameter_in: 0.500, area_in2: 0.144, weight_lb_ft: 0.49}
'0.6': {diameter_in: 0.600, area_in2: 0.216, weight_lb_ft: 0.74}
astm_a421_wire_properties:
source: 'ASTM A421/A421M-21 via PCI Design Handbook 9th ed. Design Aid 15-10, p862 / 15-31'
entries:
- {diameter_in: 0.192, area_in2: 0.0289, weight_lb_ft: 0.1, fpu_ksi: 250}
- {diameter_in: 0.196, area_in2: 0.0302, weight_lb_ft: 0.104, fpu_ksi: 250}
- {diameter_in: 0.25, area_in2: 0.0491, weight_lb_ft: 0.169, fpu_ksi: 240}
- {diameter_in: 0.276, area_in2: 0.0598, weight_lb_ft: 0.206, fpu_ksi: 235}
astm_a722_bar_properties:
source: 'ASTM A722/A722M-18 via PCI Design Handbook 9th ed. Design Aid 15-11, p862 / 15-31'
type_1_plain:
fpu_ksi: 150
fpy_fraction_of_fpu: 0.85
sizes:
'3/4': {area_in2: 0.44, weight_lb_ft: 1.50}
'7/8': {area_in2: 0.60, weight_lb_ft: 2.04}
'1': {area_in2: 0.78, weight_lb_ft: 2.67}
'1-1/8': {area_in2: 0.99, weight_lb_ft: 3.38}
'1-1/4': {area_in2: 1.23, weight_lb_ft: 4.17}
'1-3/8': {area_in2: 1.48, weight_lb_ft: 5.05}
type_2_deformed:
fpu_ksi: 150
fpy_fraction_of_fpu: 0.80
sizes:
'5/8': {area_in2: 0.28, weight_lb_ft: 0.98}
'3/4': {area_in2: 0.42, weight_lb_ft: 1.49}
'1': {area_in2: 0.85, weight_lb_ft: 3.01}
'1-1/4': {area_in2: 1.25, weight_lb_ft: 4.39}
'1-3/8': {area_in2: 1.58, weight_lb_ft: 5.56}
'1-3/4': {area_in2: 2.58, weight_lb_ft: 9.10}
'2-1/2': {area_in2: 5.16, weight_lb_ft: 18.20}
'3': {area_in2: 6.85, weight_lb_ft: 24.09}
design_stress_strain:
source: 'PCI Design Handbook 9th ed. Design Aid 15-12, p863 / 15-32. SCREEN-VERIFIED 2026-09-07.'
astm_a416_min_yield_at_1pct_elongation_ksi: {grade_270: 243, grade_250: 225}
approx_strain_at_rupture_in_per_in: {min: 0.05, max: 0.07}
min_elongation_at_ultimate_pct: 3.5
formula_250ksi_below_0_0076: 'fps = 28500 * eps'
formula_250ksi_above_0_0076: 'fps = 250 - 0.04/(eps - 0.0064)'
formula_270ksi_below_0_0085: 'fps = 28500 * eps'
formula_270ksi_above_0_0085: 'fps = 270 - 0.04/(eps - 0.007)'
# sources: 2026-09-07
# -> PCI Design Handbook 9th ed., Section 5.9 "Camber and Deflection"
# and Design Aids 5-1/5-2, pdf viewer p293-300 (Section 5.9) and
# p448-449 (Design Aids), printed p5-45 to 5-49 and 5-197/5-198.
# Full extraction: knowledge/library/notes/PCI9-5.8-losses.md (§5.9 is
# in the same note as §5.8, both pulled from the same page range) and
# knowledge/library/notes/PCI9-5.17-design-aids.md.
# -> rationale.md § catalogue.camber_and_bowing
camber_and_bowing:
time_dependent_multipliers:
source: >-
Table 5.9.2, p297 / 5-46. SCREEN-VERIFIED 2026-09-07. Derivation:
Martin (1977). "Note that there is no corresponding multiplier
for prestressed concrete in ACI 318" -- these are PCI-original,
not an ACI 318 table.
at_erection:
deflection_downward_self_weight: {without_topping: 1.85, with_topping: 1.85}
camber_upward_prestress: {without_topping: 1.80, with_topping: 1.80}
final:
deflection_downward_self_weight: {without_topping: 2.70, with_topping: 2.40}
camber_upward_prestress: {without_topping: 2.45, with_topping: 2.20}
deflection_downward_superimposed_dead_load: {without_topping: 3.00, with_topping: 3.00}
deflection_downward_composite_topping: {without_topping: null, with_topping: 2.30}
nonprestressed_reinforcement_adjustment_formula: null
nonprestressed_reinforcement_adjustment_unknown_because: >-
Eq. 5-114 (Shaikh and Branson 1970, Martin 1977, p299 / 5-48) is
C2 as a function of C1 and Asd/Aps, but the text-layer extraction
garbled the fraction nesting and it was not screen-verified --
confidence C, not landed as a formula. Question for the owner /
next landing pass: re-render p299 and confirm before coding this.
camber_equations:
source: 'Design Aid 5-1, p448 / 5-197. SCREEN-VERIFIED 2026-09-07.'
straight_strands: 'Delta = Po*e*l^2 / (8*E*I)'
single_point_depressed: 'Delta = Po*ee*l^2/(8*E*I) + Po*e_end*l^2/(12*E*I)'
two_point_depressed: 'Delta = Po*ee*l^2/(8*E*I) + (Po*e_end/(E*I)) * (l^2/8 - a^2/6)'
variables: 'e/ee = strand eccentricity at midspan; e_end = eccentricity change from end to depression point; a = distance from each end to its depression point (two-point case)'
bowing:
formula: 'Delta = alpha * l^2 / (8*h)'
alpha_formula: 'alpha = C_thermal * (T1 - T2)'
source: 'Eq. 5-115/5-116, p299 / 5-48'
seasonal_temp_diff_open_structures_f: {min: 30, max: 40}
seasonal_note: >-
Seldom exceeds 30-40 F in open structures such as parking-deck
roofs; can be higher in an insulated wall panel, tempered by
concrete thermal lag.
restraint_forces_and_moments:
source: 'Design Aid 5-2, p449 / 5-198. SCREEN-VERIFIED 2026-09-07.'
effective_modulus_et:
short_term_fraction_of_ec: 0.75
note: >-
Consistent with the thermal-displacement reduction for
equivalent volume change, Section 4.4.3.1. Earlier editions
of this handbook used 0.5*Ec for long-term seasonal cases --
the 9th edition gives no current numeric value for that case.
intermediate_restraint:
case_1_single_midspan: {formula_p: 'P = 48*Et*I*Delta/l^3', formula_moment: 'M = P*l/4'}
case_2_two_points: {formula_p: 'P = 24*Et*I*Delta/(3*a*l^2 - 4*a^3)', formula_moment: 'M = P*a'}
case_3_three_or_more: {formula_sum_p: 'sum(P) = w*l = 77*Et*I*Delta/l^3', formula_moment: 'M = w*l^2/8 = sum(P)*(l/8)'}
end_restraint:
case_4_both_ends: {formula_moment: 'M = 8*Et*I*Delta/l^2'}
case_5_one_end: {formula_moment: 'M = 16*Et*I*Delta/l^2'}
# sources: 2026-09-07
# -> PCI Design Handbook 9th ed., Section 5.12 "Insulated Wall Panels",
# pdf viewer p304-309, printed p5-53 to 5-58. Full extraction:
# knowledge/library/notes/PCI9-5.12-insulated.md.
# -> rationale.md § catalogue.insulated_panel
insulated_panel:
insulation_properties:
source: 'Table 5.12.1, p305 / 5-54. SCREEN-VERIFIED 2026-09-07.'
note: >-
Only the maximum-use-temperature and the moisture caution are
landed here as the numbers a check might reasonably gate on; the
full per-density property matrix (density, water absorption,
compressive/tensile/shear/flexural strength, thermal
conductivity) is in the note file, not landed -- see the debt
list in the ledger.
max_use_temperature_f:
eps: 165
xps: 165
polyisocyanurate_unfaced: 250
polyisocyanurate_faced: 250
phenolic: 300
cellular_glass: 900
unfaced_felt_faced_polyiso_moisture_caution: >-
"Unfaced and felt-faced polyisocyanurates should not be used in
insulated wall panels because moisture absorption causes them to
expand." (footnote a, Table 5.12.1)
structural_design_assumptions:
source: 'Section 5.12.2, p305-306 / 5-54 to 5-55'
noncomposite: 'wythes act independently for the full life of the panel; flexible wythe connectors offer insignificant shrinkage/temperature restraint'
composite: 'wythes act as a fully composite unit for the full life; needs shear-resisting connectors, welded-wire trusses, or solid concrete zones -- thermal bowing tends to be GREATER than noncomposite; solid zones significantly compromise thermal efficiency'
partially_composite: 'between noncomposite and composite; may differ by check (deflection vs cracking vs ultimate strength)'
semicomposite: 'composite during handling (early insulation bond + flexible connectors), noncomposite in service (bond assumed unreliable long-term); wind loads split by wythe stiffness; slenderness uses the SUM of individual moments of inertia (Example 5-16)'
wythe_and_strand_limits:
source: 'Section 5.12.2 / 5.12.3 / 5.12.4, p306/p308, printed 5-55/5-57'
min_wythe_thickness_rule: 'no thinner than 3x the maximum aggregate size (some manufacturers use a 2-in. wythe with 3/4-in. aggregate)'
max_strand_diameter_by_wythe_thickness:
'3/8_in_ok_wythe_2_in': true
'1/2_in_needs_wythe_3_in_min': true
note: 'same underlying rule as catalogue.handling.stripping.max_strand_diameter_by_slab_thickness (Table 8.3.2), stated independently in two chapters -- both agree'
wythe_connector_spacing_in_oc: 24
wythe_connector_wire_gauge: '12 to 14 gauge, galvanized or stainless steel'
connection_center_third_of_span: true
camber_and_bowing_notes:
source: 'Section 5.12.5, p308-309 / 5-57 to 5-58'
thermal_bowing_greater_in_composite: true
bowing_usually_outward: true
# sources: 2026-09-07
# -> PCI Design Handbook 9th ed., Chapter 8 "Component Handling and
# Erection Bracing", pdf viewer p620-642, printed p8-3 to 8-23. Full
# extraction: knowledge/library/notes/PCI9-ch8-handling.md.
# -> rationale.md § catalogue.handling
handling:
stripping:
equivalent_static_load_multipliers:
source: 'Table 8.3.1, p629 / 8-10. SCREEN-VERIFIED 2026-09-07.'
note: 'For MEMBER FLEXURAL DESIGN ONLY -- NOT applied to lifting-device safety factors (which use the 4x-load factor below instead).'
exposed_aggregate_with_retarder:
flat_no_false_joints: 1.2
flat_false_joints_or_reveals: 1.3
fluted_proper_draft: 1.4
sculptured_other: 1.5
yard_handling_and_erection: 1.2
transportation: 1.5
smooth_form_release_agent_only:
flat_no_false_joints: 1.3
flat_false_joints_or_reveals: 1.4
fluted_proper_draft: 1.6
sculptured_other: 1.7
yard_handling_and_erection: 1.2
transportation: 1.5
allowable_tensile_stress:
formula: "ft_allowable = 7.5*sqrt(fc) / 1.5 = 5*sqrt(fc)"
factor_of_safety: 1.5
source: "Eq. 8-1, p629 / 8-10. Substitute f'ci for f'c during stripping."
prestressing_benefit_limits:
recommended_tensile_stress_limit: "5*sqrt(fci)"
recommended_avg_compression_after_losses_nonflexural_psi: {min: 125, max: 800}
source: 'Section 8.3.4.4, p630 / 8-11'
max_strand_diameter_by_slab_thickness:
source: 'Table 8.3.2, p630 / 8-11'
less_than_3_in: '3/8'
'3_in_or_more': '1/2 or larger'
post_tensioning:
transverse_reinforcement_exemption_max_width_ft: 12
monostrand_friction_coefficient: {min: 0.03, max: 0.05}
source: 'Section 8.3.4.4, p630 / 8-11 (ACI CODE-318-19 Section 24.4.3.5; Tanner 1977)'
moment_coefficients_edge_rotation:
source: 'Figure 8.3.1, p625 / 8-6'
two_point: {mx: 'w*a^2/8', my: '0.0107*w*a*b^2'}
four_point: {mx: 'w*a^2/8', my: '0.0027*w*a*b^2'}
moment_coefficients_flat_stripping:
source: 'Figure 8.3.2, p626 / 8-7'
two_point: {mx: '0.0107*w*a^2*b', my: '0.0214*w*a*b^2', mx_resisting_width: 'lesser of 15t or b/2'}
four_point: {mx: '0.0054*w*a^2*b', my: '0.0054*w*a*b^2', mx_resisting_width: 'lesser of 15t or b/4'}
rigging:
sling_angle_multiplier:
source: 'Figure 8.3.3, p627 / 8-8. Consistent with F = 1/sin(theta) to printed precision.'
'90_deg': 1.00
'75_deg': 1.04
'60_deg': 1.16
min_recommended_sling_angle_deg: 60
source_angle: 'Figures 8.3.4 / 8.3.11 / 8.6.2, p627 / p633 / p639'
lifting_devices:
aircraft_cable_wire_rope:
source: 'Table 8.3.3, p631 / 8-12. SCREEN-VERIFIED 2026-09-07. A safety factor of 4 (OSHA 1926.704) is already applied to these safe-load values.'
'3/8_in_aircraft_cable_safe_load_kip': 3.6
'7/16_in_wire_rope_safe_load_kip': 4.4
'1/2_in_wire_rope_safe_load_kip': 5.7
min_embedment_per_leg_in: 48
do_not_use_multiple_loops_single_location: true
strand_loops:
source: 'Section 8.3.5.2, p631 / 8-12'
safety_factor: 4
min_embedment_per_leg_in: 24
min_hook_diameter_x_strand_diameter: 4
min_bend_in: 2
default_safe_load_half_in_270ksi_single_loop_kip: 10
multi_strand_multiplier: {double: 1.7, triple: 2.2}
self_consolidating_concrete_caution: >-
Vertically inserted loops show lower pull-out strength
(air-bubble attraction around the strand); PCI TR-6-15
indicates longer embedment may be needed.
storage:
warpage_deflection:
formula: 'ymax = 22.5*w*sin(theta)*(l^4 + a^4) / (t^3 * Eci)'
time_dependent_formula: 'yt = ymax * (1 + lambda_delta)'
source: 'Eq. 8-2/8-3, p634-635 / 8-15 to 8-16'
support_at_0_2l_reduces_warpage_to_pct_of_end_support: 10
dimension_a_effect_pct_of_l_effect: 1
source: >-
p635 / 8-16 -- "most precast concrete producers will store panels
with support points at about 0.2l from each end, which will
reduce the warpage to approximately 10% of that of supports at
the ends" and "the effect of the dimension a is typically about
1% of that of l and can be ignored for usual storage conditions."
transportation:
common_payload_no_permit_tons: 24
standard_dimension_limits_ft: {vertical_horizontal: 8, length: 40}
lowboy_height_ft: {min: 10, max: 12}
total_height_no_permit: '13 ft 6 in (some states)'
max_width_with_permit_ft: {min: 10, max: 14}
length_permit_thresholds: 'varies by state -- some allow a simple permit over 70 ft, others require a special permit for any load over 55 ft'
weight_permit_thresholds: 'varies by state -- some allow up to 100 tons with a permit, others restrict severely above 25 tons'
source: 'Section 8.5, p635-636 / 8-16 to 8-17'
erection_stability:
safety_factors:
source: 'Table 8.7.1, p641 / 8-22. SCREEN-VERIFIED 2026-09-07.'
bracing_inserts_cast_in: 2
lifting_inserts: 4
lifting_hardware: 5
rotation_pick_coefficients:
source: 'Figure 8.6.1, p638 / 8-19. SCREEN-VERIFIED 2026-09-07.'
two_point_general:
reactions: ['w*l/(2a)', 'w*l*(1 - 1/(2a))']
neg_moment: '0.5*w*b^2*l^2'
pos_moment: '(w/2)*l^2*(1 - 1/(2a))^2'
constraint: 'a + b = 1.0'
two_point_equal_moments:
reactions_fraction: [0.70, 0.30]
positions_fraction_of_l: [0.29, 0.71]
neg_moment_coeff: 0.042
pos_moment_coeff: 0.045
two_point_four_point_stripping_locations:
reactions_fraction: [0.63, 0.37]
positions_fraction_of_l: [0.21, 0.79]
neg_moment_coeff: 0.022
pos_moment_coeff: 0.069
three_point_eight_point_stripping_locations:
reactions_fraction: [0.67, 0.33]
positions_fraction_of_l: [0.1, 0.3, 0.6]
positions_confidence: 'B -- read off the text layer, not screen-verified; the moment/reaction coefficients above ARE screen-verified'
neg_moment_coeff: -0.005
pos_moment_coeff: 0.055
three_point_alternate:
min_sling_length_x_l: 1.0
reactions_fraction: [0.77, 0.23]
neg_moment_coeff: -0.005
pos_moment_1_coeff: 0.036
pos_moment_2_coeff: 0.026
caution: 'see the source note on uncontrolled rolling, Section 8.6 p637 / 8-18'
asce_37_construction_loads: >-
Not adopted by IBC 2021 or ACI CODE-318-19; using ASCE 37 meets
the recognized standard of care absent contract-specific
construction-loading requirements. Source: Section 8.7.6,
p640 / 8-21.
# sources: 2026-09-07
# -> ACI 318-19, Chapter 25 "Reinforcement Details", §25.4 Development of
# reinforcement and §25.5 Splices. 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). PUBLIC: this is the model code,
# the same for every producer and every job -- it identifies nobody.
# Cites below are the ACI PRINTED page (the number in the page footer);
# the raw text layer's own convention is printed = PDF page - 2.
# Full extraction and the page-level confidence marks:
# knowledge/library/notes/ACI318-19-ch25-development.md
# -> rationale.md § catalogue.development_length
development_length:
sqrt_fc_cap_psi: 100.0 # §25.4.1.4, p.471 -- use sqrt(100) when fc > 10,000 psi
minimums_in:
# The floor under every §25.4 calculated length -- "greater of (a)/(b)/(c)".
tension_general_or_simplified: 12.0 # §25.4.2.1(b), p.471
hooked_bar_db: 8.0 # §25.4.3.1(b), p.474
hooked_bar_in: 6.0 # §25.4.3.1(c), p.474
headed_bar_db: 8.0 # §25.4.4.2(b), p.477
headed_bar_in: 6.0 # §25.4.4.2(c), p.477
compression_in: 8.0 # §25.4.9.1(b), p.487
wwr_deformed_in: 8.0 # §25.4.6.1(b), p.483
wwr_plain_in: 6.0 # §25.4.7.1(b), p.484
high_grade_transverse_requirement:
# fy >= 80,000 psi bars spaced closer than 6 in. o.c. need transverse
# reinforcement sized so Ktr is never smaller than 0.5*db. Same rule
# stated twice, once for development and once for lap splices.
fy_threshold_psi: 80000
spacing_threshold_in: 6.0
ktr_min_factor_db: 0.5
source_development: 'ACI 318-19 §25.4.2.2, p.471'
source_lap_splice: 'ACI 318-19 §25.5.1.5, p.488'
tension:
general:
formula: 'ld = (3*fy*psi_t*psi_e*psi_s*psi_g / (40*lambda*sqrt(fc))) * (db / ((cb+Ktr)/db))'
source: 'ACI 318-19 §25.4.2.4, Eq. 25.4.2.4a, p.472'
confinement_term_max: 2.5 # (cb+Ktr)/db shall not exceed 2.5
source_confinement_max: 'ACI 318-19 §25.4.2.4, p.472'
ktr_formula: 'Ktr = 40*Atr/(s*n)'
source_ktr: 'ACI 318-19 §25.4.2.4b, p.473'
ktr_zero_permitted: true # design simplification, even if transverse reinf. present
cb_definition: 'lesser of: distance from bar/wire centre to nearest concrete surface; one-half the c-c spacing of the bars/wires'
source_cb: 'ACI 318-19 §25.4.2.4, p.472'
modification_factors:
psi_t: {top_bar: 1.3, other: 1.0} # >12in fresh concrete below reinforcement
psi_e: {epoxy_low_cover_or_spacing: 1.5, epoxy_other: 1.2, uncoated_or_galvanized: 1.0}
psi_s: {no7_and_larger: 1.0, no6_and_smaller: 0.8}
psi_g: {grade_40_or_60: 1.0, grade_80: 1.15, grade_100: 1.3}
lambda: {lightweight: 0.75, normalweight: 1.0}
psi_t_times_psi_e_max: 1.7 # footnote, p.473
source: 'ACI 318-19 Table 25.4.2.5, p.473-474'
simplified:
# Table 25.4.2.3. ld is based on two preselected (cb+Ktr)/db values
# (1.5 for case1, 1.0 for case2) rather than the actual ratio -- see
# R25.4.2.3, p.472. case1 = clear spacing/cover/ties meet the
# favourable row; case2 = everything else.
no6_and_smaller:
case1: 'ld = (fy*psi_t*psi_e*psi_s*psi_g / (25*lambda*sqrt(fc))) * db'
case2: 'ld = (3*fy*psi_t*psi_e*psi_s*psi_g / (50*lambda*sqrt(fc))) * db'
no7_and_larger:
case1: 'ld = (fy*psi_t*psi_e*psi_g / (20*lambda*sqrt(fc))) * db'
case2: 'ld = (3*fy*psi_t*psi_e*psi_g / (40*lambda*sqrt(fc))) * db'
source: 'ACI 318-19 Table 25.4.2.3, p.472'
worked_example_check: >-
R25.4.2.3's own worked example (fy=60,000 psi, lambda=1, uncoated,
bottom bar, fc=4,000 psi) gives no7_and_larger.case1 = 47*db and
case2 = 71*db off these same coefficients -- p.472.
hooked_bar:
formula: 'ldh = (fy*psi_e*psi_r*psi_o*psi_c / (50*lambda*sqrt(fc))) * db'
source: 'ACI 318-19 §25.4.3.1(a), p.474'
measured_from: 'critical section to the outside end (edge) of the hook'
modification_factors:
psi_e: {coated: 1.2, uncoated_or_galvanized: 1.0}
psi_r: {no11_or_smaller_confined: 1.0, other: 1.6} # Ath >= 0.4*Ahs or s >= 6db
psi_o: {inside_column_core_or_side_cover_ge_6db: 1.0, other: 1.25}
psi_c: {fc_lt_6000: 'fc/15000 + 0.6', fc_ge_6000: 1.0}
lambda: {lightweight: 0.75, normalweight: 1.0}
source: 'ACI 318-19 Table 25.4.3.2, p.474-475'
discontinuous_end_tie_spacing_max_db: 3 # both side + top/bottom cover < 2.5in
source_discontinuous_end: 'ACI 318-19 §25.4.3.4, p.476'
headed_bar:
formula: 'ldt = (1.5*fy*psi_e*psi_p*psi_o*psi_c / (75*sqrt(fc))) * db'
source: 'ACI 318-19 §25.4.4.2(a), p.477'
no_lambda_normalweight_only: true # §25.4.4.1(d) requires normalweight concrete
conditions:
max_bar_size: 11
abrg_min: '4*Ab' # net bearing area of head
clear_cover_min_db: 2
ctc_spacing_min_db: 3
source_conditions: 'ACI 318-19 §25.4.4.1, p.477'
modification_factors:
psi_e: {coated: 1.2, uncoated_or_galvanized: 1.0}
psi_p: {no11_or_smaller_confined: 1.0, other: 1.6} # Att >= 0.3*Ahs or s >= 6db
psi_o: {inside_column_core_or_side_cover_ge_6db: 1.0, other: 1.25}
psi_c: {fc_lt_6000: 'fc/15000 + 0.6', fc_ge_6000: 1.0}
source: 'ACI 318-19 Table 25.4.4.3, p.480'
compression:
formula: 'ldc = max((fy*psi_r/(50*lambda*sqrt(fc)))*db, 0.0003*fy*psi_r*db)'
source: 'ACI 318-19 §25.4.9.2, p.487'
modification_factors:
psi_r: {enclosed_spiral_tie_or_hoop: 0.75, other: 1.0, permitted_default: 1.0}
lambda: {lightweight: 0.75, normalweight: 1.0}
source: 'ACI 318-19 Table 25.4.9.3, p.487'
excess_reinforcement_reduction:
ratio: '(As_required / As_provided)'
source: 'ACI 318-19 §25.4.10.1, p.487'
applies_to: ['tension.general (§25.4.2.1a)', 'wwr.deformed (§25.4.6.1a)', 'wwr.plain (§25.4.7.1a)', 'compression (§25.4.9.1a)']
floor: 'never below the matching entry in minimums_in'
not_permitted:
noncontinuous_supports: true
anchorage_or_development_for_fy_required: true
bars_required_continuous: true
hooked_headed_or_mechanically_anchored: true
seismic_design_category_c_d_e_or_f: true
pile_anchorage_seismic_design_category_c_d_e_or_f: true
source_exclusions: 'ACI 318-19 §25.4.10.2, p.487-488'
excluded_from_lap_splices: true # §25.5.1.4 -- never permitted when calculating a lap length
source_lap_exclusion: 'ACI 318-19 §25.5.1.4, p.488'
wwr:
deformed:
formula: 'ld = ld_per_tension_simplified_or_general * psi_w'
source: 'ACI 318-19 §25.4.6.2, p.483'
measured_from: 'critical section to the end of wire'
requires_all_wires_le_D31: true
psi_w:
case_a: 'psi_w = (fy - 35000) / fy'
case_b: 'psi_w = 5*db/s'
max: 1.0
applies_when: 'at least one cross wire within ld, at least 2in from the critical section'
no_qualifying_cross_wire: 1.0
source: 'ACI 318-19 §25.4.6.3 / §25.4.6.4, p.483-484'
confidence: >-
B -- case_a is reconstructed from a fragmented raw-text-layer
equation image (two occurrences of fy, one occurrence of
35,000, one subtraction, no other terms; no rendered page was
available to confirm placement). case_b (5*db/s) reads
unambiguously off the same page. Re-verify case_a against a
rendered page before treating it as A.
epoxy_psi_e_permitted: 1.0 # for epoxy-coated WWR meeting §25.4.6.3
source_epoxy: 'ACI 318-19 §25.4.6.2, p.483'
no_qualifying_wire_or_zinc_coated_use: 'plain WWR provisions (§25.4.7)'
source_fallback: 'ACI 318-19 §25.4.6.5 / §25.4.6.6, p.484'
plain:
formula: 'ld = max(spacing_of_cross_wires + 2, 0.27*(fy*Ab)/(s*lambda*sqrt(fc)))'
source: 'ACI 318-19 §25.4.7.2, p.484-485'
measured_from: 'critical section to the outermost cross wire'
requires_min_two_cross_wires: true
source_min_cross_wires: 'ACI 318-19 §25.4.7.1, p.484'
variables: 'Ab = area of the individual wire being developed; s = spacing between the wires being developed; lambda per Table 25.4.2.5'
strand:
# Pretensioned seven-wire strand, clean, 1/4in / 3/8in / 1/2in diameter,
# bonded applications only.
development:
formula: 'ld = (fse/3000)*db + ((fps - fse)/1000)*db'
source: 'ACI 318-19 §25.4.8.1, Eq. 25.4.8.1, p.485'
applies_to: 'seven-wire strand, clean, 1/4in/3/8in/1/2in diameter, bonded'
does_not_apply_to: ['plain wires', 'end-anchored tendons', 'unstressed strand']
source_scope: 'ACI 318-19 §25.4.8, p.485'
max_fps_tested_psi: 275000
transfer_length_formula: '(fse/3000)*db'
transfer_db: 50.0 # shorthand at fse = 150,000 psi: (150000/3000) = 50
transfer_db_basis_fse_psi: 150000
additional_bonded_length_formula: '((fps - fse)/1000)*db'
doubling_required_when_not_bonded_to_end_and_tension_at_service: true
source_doubling: 'ACI 318-19 §25.4.8.1(b), p.485'
doubling_exception_zero_tension_service_loads: true
source_doubling_exception: 'ACI 318-19 §24.5.2, discussed at R25.4.8.3, p.486'
reduced_embedment_permitted:
condition: 'design strand stress at the section does not exceed the bilinear Eq. 25.4.8.1 relationship'
source: 'ACI 318-19 §25.4.8.2 / §25.4.8.3, p.485-486'
correction: >-
The extraction note filed the doubling requirement under §25.4.8.3;
the code text puts it at §25.4.8.1(b) (conditioned on "bonding does
not extend to end of member AND design includes tension at service
loads"). §25.4.8.3 is the separate, related provision permitting
embedment SHORTER than ld at a section, and the "twice ld" / zero-
tension-exception language the note quoted from there is R25.4.8.3
COMMENTARY explaining the §25.4.8.1(b) rule, not a second code rule.
lap:
max_bar_size: 11 # bars larger than No. 11 may not be lap spliced
source_max_bar_size: 'ACI 318-19 §25.5.1.1, p.488'
tension:
class_a_factor: 1.0
class_b_factor: 1.3
min_in: 12.0
formula: 'lst = max(factor*ld, min_in)'
ld_basis: 'ld per §25.4.2.1(a)'
class_a_requires: 'As_provided/As_required >= 2.0 over the splice length, AND <= 50 percent of As spliced within the required lap length'
class_b_requires: 'anything not meeting Class A (includes every case where As_provided/As_required < 2.0)'
source: 'ACI 318-19 §25.5.2.1, Table 25.5.2.1, p.488-489'
different_size_bars: 'lst = greater of ld(larger bar) and lst(smaller bar)'
source_different_size: 'ACI 318-19 §25.5.2.2, p.489'
compression:
fy_le_60000: 'lsc = max(0.0005*fy*db, 12 in.)'
fy_60000_to_80000: 'lsc = max((0.0009*fy - 24)*db, 12 in.)'
fy_gt_80000: 'lsc = max((0.0009*fy - 24)*db, lst_per_25.5.2.1)'
source: 'ACI 318-19 §25.5.5.1(a-c), p.491'
fc_lt_3000_increase_factor: 1.3333 # "increased by one-third"
source_increase: 'ACI 318-19 §25.5.5.1, p.491'
no14_no18_to_no11_or_smaller_permitted: true # the only exception to max_bar_size above
source_exception: 'ACI 318-19 §25.5.5.3, p.491'
different_size_bars: 'lsc = greater of ldc(larger bar, per §25.4.9.1) and lsc(smaller bar, per §25.5.5.1)'
source_different_size: 'ACI 318-19 §25.5.5.4, p.491'
wwr_deformed:
formula: 'lst = max(1.3*ld, 8 in.)'
source: 'ACI 318-19 §25.5.3.1, p.489-490'
wwr_plain:
formula: 'lst = max(spacing_of_cross_wires + 2, 1.5*ld, 6 in.)'
formula_when_As_ratio_ge_2: 'lst = max(1.5*ld, 2 in.)'
source: 'ACI 318-19 §25.5.4.1 / §25.5.4.2, p.490-491'
# sources: 2026-08-30 2026-08-31
# -> rationale.md § connectors
connectors:
what_it_is: >-
The part that crosses the insulation of a sandwich (insulated) wall panel
and ties the two concrete wythes together. Declared by the owner
2026-08-31.
products:
# KEYED BY THE FULL PRODUCT NAME, manufacturer included, because that
# is how the owner wrote it and -- measured 2026-08-31 -- it is also
# verbatim what the model's connector TYPE carries in its description
# field. (Which field that is, is plant knowledge and is named in the
# plant layer through the `member.identity_description` role, never
# here.) One spelling, and the alias table has nothing to translate.
# ---- THE STANDARD (NON-COMPOSITE) LINE -----------------------------
#
# SOURCE FOR EVERY VALUE IN THE THREE ENTRIES BELOW: W1 (hkties.com
# standard / non-composite products page, captured 2026-08-30), re-read
# against the live page by the landing agent 2026-09-01 -- the whole
# ST50/ST75/ST100 comparison table and its three footnotes, transcribed
# a second time and compared cell by cell. Every cell agreed. The
# capture is a clue; the page is the source, and this line is the
# difference (CLAUDE.md, the three-stage law).
#
# A CAPTURE HASH IS NOT A DOCUMENT HASH. The vendor can edit the page
# tomorrow and nothing here would notice, so read these as "what that
# page said on those dates", and re-fetch before relying on one in an
# engineering decision.
#
# THE TABLE'S OWN IDENTITY, ASSERTED AT LANDING: insulation thickness
# (2/3/4 in) tracks the designation number (ST50/ST75/ST100 = 50/75/100
# mm), and tie length grows with it (5.31 / 6.25 / 7.25 in). Both hold
# across all three columns, which is what makes a row-shift detectable.
# ONE COLUMN DOES NOT RISE MONOTONICALLY AND IT IS NOT AN ERROR:
# `ultimate_panel_shear_per_tie_lb` reads 648 / 924 / 780 -- ST75 is the
# highest and ST100 is BELOW it. That was checked twice against the page
# for exactly the reason the identity rule exists, and the page really
# does print it that way. Do not "correct" it into a rising series.
'HK Composites (ST50)':
manufacturer: HK Composites
designation: ST50
system: standard_non_composite
design_basis: non_composite
tie_length_in: 5.31
embedment_head_in: 1.5
embedment_tip_in: 1.81
foam_plug_diameter_in: 0.5
insulation_thickness_in: 2.0
structural_wythe_min_in: 2.0
fascia_wythe_range_in: [2.0, 4.0]
ultimate_tensile_pullout_lb: 1260
shear_strength_lb: 1300
ultimate_panel_shear_per_tie_lb: 648
basis_fc_psi: 3000
test_criteria: 'ICC-ES AC 320'
thermal_conductivity_btu_in_hr_ft2_f: 2.1
fire_performance: >-
Resists a 300 lb tension load for over 90 minutes when fire tested
per ASTM E1512-01 section 7.4.3.
alkaline_resistance: >-
Excellent -- 99.1% retained strength after 3,000 hours in pH 12
solution.
service_temperature_f: [-40, 200]
# THE ONE NUMBER THAT DID NOT ARRIVE WITH THE REST. The page publishes
# no working load and no safety factor; see `safety_factors` below.
capacity_lb: null
capacity_unknown_because: >-
The three capacities above are ULTIMATES and are named as such. The
page publishes no working load and no safety factor, so a single
`capacity_lb` -- which every reader would take for an allowable --
cannot be derived from them. A check that needs a working load
stands down naming this key; a check that can consume an ultimate
reads the `ultimate_*` keys and declares that it is doing so.
min_edge_distance_in: null
min_edge_distance_unknown_because: >-
Still not published. The vendor's own product page carries embedment
depths and wythe thicknesses but NO edge distance for any
designation, so the 2026-09-01 landing did not fill this and did not
weaken the rule that filled it for ST100: it is a property of the
PRODUCT, the check that needs it stands down naming this key, and it
may never be filled from another product's datasheet.
# ST75 IS NEW HERE, added 2026-09-01 off the same table. It was not in
# this file before because nobody had read the page; it is neither
# evidenced on a job nor named by the owner, and it is landed because
# the table publishes it, which is what a public catalogue is for.
'HK Composites (ST75)':
manufacturer: HK Composites
designation: ST75
system: standard_non_composite
design_basis: non_composite
tie_length_in: 6.25
embedment_head_in: 1.5
embedment_tip_in: 1.73
foam_plug_diameter_in: 0.57
insulation_thickness_in: 3.0
structural_wythe_min_in: 2.0
fascia_wythe_range_in: [2.0, 4.0]
ultimate_tensile_pullout_lb: 1660
shear_strength_lb: 1700
ultimate_panel_shear_per_tie_lb: 924
basis_fc_psi: 3000
test_criteria: 'ICC-ES AC 320'
thermal_conductivity_btu_in_hr_ft2_f: 2.1
fire_performance: >-
Resists a 300 lb tension load for over 90 minutes when fire tested
per ASTM E1512-01 section 7.4.3.
alkaline_resistance: >-
Excellent -- 99.1% retained strength after 3,000 hours in pH 12
solution.
service_temperature_f: [-40, 200]
capacity_lb: null
capacity_unknown_because: >-
As ST50 -- the published figures are ultimates and the page states
no safety factor.
min_edge_distance_in: null
min_edge_distance_unknown_because: >-
Not published on the page, and NOT inherited from ST100's 3.0 in.
That number came from the owner about ST100 specifically.
'HK Composites (ST100)':
manufacturer: HK Composites
designation: ST100
system: standard_non_composite
design_basis: non_composite
tie_length_in: 7.25
embedment_head_in: 1.5
embedment_tip_in: 1.73
foam_plug_diameter_in: 0.57
insulation_thickness_in: 4.0
structural_wythe_min_in: 2.0
fascia_wythe_range_in: [2.0, 4.0]
ultimate_tensile_pullout_lb: 1660
shear_strength_lb: 1700
# 780, and it is LOWER than ST75's 924. See the identity note above.
ultimate_panel_shear_per_tie_lb: 780
basis_fc_psi: 3000
test_criteria: 'ICC-ES AC 320'
thermal_conductivity_btu_in_hr_ft2_f: 2.1
fire_performance: >-
Resists a 300 lb tension load for over 90 minutes when fire tested
per ASTM E1512-01 section 7.4.3.
alkaline_resistance: >-
Excellent -- 99.1% retained strength after 3,000 hours in pH 12
solution.
service_temperature_f: [-40, 200]
capacity_lb: null
capacity_unknown_because: >-
As ST50 -- the published figures are ultimates and the page states
no safety factor. The tensile and shear figures happen to equal
ST75's; the panel-shear figure does not, and none of the three is
inherited from anywhere. Each was read off this designation's own
column.
# STATED BY THE OWNER, 2026-08-31: "pin edge distance = 3". ST100 的
# 知识。写。" -- a number, for this designation, in his own words.
# ST50 and the rest stay null: he gave one product's number and
# borrowing it is the thing `min_edge_distance_unknown_because`
# exists to forbid.
min_edge_distance_in: 3.0
min_edge_distance_source: >-
Stated by the owner 2026-08-31 ("pin edge distance = 3"). Not read
off a datasheet -- when HK Composites' sheet arrives it is worth
checking against, and a disagreement is a question for him, not a
silent replacement.
# THE 6.0 IN CALIBRATION THAT USED TO SIT HERE HAS BEEN WITHDRAWN, and
# withdrawing it is the point rather than an omission. It was the
# smallest in-plane distance measured from a pin to the PANEL edge,
# and 2026-08-31 the owner defined the measurement differently: the
# edge that governs is the INSULATION edge (see `edge_reference`
# below). A pin-to-panel-edge number is not a calibration for a
# pin-to-insulation-edge check -- the two are different measurements
# of different things, and carrying the 6.0 across would have set an
# expectation the first live run then looked like a regression
# against. The insulation-edge distances on this job are UNMEASURED.
# Whatever the first run prints is new information.
# ---- THE CA (COMPOSITE ACTION) LINE, landed 2026-09-01 --------------
#
# SOURCE FOR EVERY VALUE IN THE THREE ENTRIES BELOW: W2 (hkties.com
# CA / composite-action products page, captured 2026-08-30), re-read
# against the live page by the landing agent 2026-09-01, cell by cell,
# with every footnote. Every cell agreed.
#
# A DIFFERENT DESIGN BASIS, NOT A BIGGER TIE. These are for panels
# designed to act COMPOSITELY -- the vendor's own sentence is in
# `design_bases` below -- and they are glass-fibre reinforced where the
# Standard line deliberately is not. The capacities are three to five
# times the Standard line's, which is exactly why nothing may read one
# line's number onto the other's designation.
#
# THE TABLE'S IDENTITY, ASSERTED AT LANDING: insulation thickness
# (2/3/4 in) tracks the designation (CA50/CA75/CA100), tie length rises
# (5.5 / 6.5 / 7.5 in), ultimate tension is CONSTANT across all three
# (5,900 lb -- it is a property of the tie's own section, not of the
# panel), and ultimate shear FALLS as the tie gets longer
# (5,315 / 4,902 / 4,489), as does the 5% fractile (5,183 / 4,720 /
# 4,257). Every fractile value sits below its own ultimate, on every
# row, which is the check that catches a shifted column.
'HK Composites (CA50)':
manufacturer: HK Composites
designation: CA50
system: composite_action
design_basis: composite
tie_length_in: 5.5
embedment_head_in: 1.75
embedment_tip_in: 1.75
foam_section_in: [0.47, 3.13]
insulation_thickness_in: 2.0
wythe_1_range_in: [2.0, 6.0]
wythe_2_range_in: [2.0, 6.0]
ultimate_tension_lb: 5900
ultimate_tension_basis_fc_psi: 5000
ultimate_shear_lb: 5315
shear_5pct_fractile_95pct_confidence_lb: 5183
shear_basis_fc_psi: 5000
test_criteria: 'ICC-ES AC 320'
thermal_conductivity_btu_in_hr_ft2_f: 1.75
fire_performance: >-
Resists a 300 lb tension load for over 90 minutes when fire tested
per ASTM E1512-01 section 7.4.3.
alkaline_resistance: >-
Excellent -- 96.2% retained strength after 3,000 hours in pH 12
solution.
service_temperature_f: [-40, 200]
capacity_lb: null
capacity_unknown_because: >-
Ultimates only, and the page states no safety factor. See
`safety_factors` below. The 5% fractile is NOT a working load
either -- it is a statistical lower bound on the same ultimate
test population, with no factor applied.
min_edge_distance_in: null
min_edge_distance_unknown_because: >-
The CA page publishes no edge distance for any designation. Not
inherited from the ST100 figure, which came from the owner about a
different product on a different design basis.
'HK Composites (CA75)':
manufacturer: HK Composites
designation: CA75
system: composite_action
design_basis: composite
tie_length_in: 6.5
embedment_head_in: 1.75
embedment_tip_in: 1.75
foam_section_in: [0.47, 3.13]
insulation_thickness_in: 3.0
wythe_1_range_in: [2.0, 6.0]
wythe_2_range_in: [2.0, 6.0]
ultimate_tension_lb: 5900
ultimate_tension_basis_fc_psi: 5000
ultimate_shear_lb: 4902
shear_5pct_fractile_95pct_confidence_lb: 4720
shear_basis_fc_psi: 5000
test_criteria: 'ICC-ES AC 320'
thermal_conductivity_btu_in_hr_ft2_f: 1.75
fire_performance: >-
Resists a 300 lb tension load for over 90 minutes when fire tested
per ASTM E1512-01 section 7.4.3.
alkaline_resistance: >-
Excellent -- 96.2% retained strength after 3,000 hours in pH 12
solution.
service_temperature_f: [-40, 200]
capacity_lb: null
capacity_unknown_because: >-
As CA50 -- ultimates, no published safety factor.
min_edge_distance_in: null
min_edge_distance_unknown_because: >-
Not published on the page.
'HK Composites (CA100)':
manufacturer: HK Composites
designation: CA100
system: composite_action
design_basis: composite
tie_length_in: 7.5
embedment_head_in: 1.75
embedment_tip_in: 1.75
foam_section_in: [0.47, 3.13]
insulation_thickness_in: 4.0
wythe_1_range_in: [2.0, 6.0]
wythe_2_range_in: [2.0, 6.0]
ultimate_tension_lb: 5900
ultimate_tension_basis_fc_psi: 5000
ultimate_shear_lb: 4489
shear_5pct_fractile_95pct_confidence_lb: 4257
shear_basis_fc_psi: 5000
test_criteria: 'ICC-ES AC 320'
thermal_conductivity_btu_in_hr_ft2_f: 1.75
fire_performance: >-
Resists a 300 lb tension load for over 90 minutes when fire tested
per ASTM E1512-01 section 7.4.3.
alkaline_resistance: >-
Excellent -- 96.2% retained strength after 3,000 hours in pH 12
solution.
service_temperature_f: [-40, 200]
capacity_lb: null
capacity_unknown_because: >-
As CA50 -- ultimates, no published safety factor.
min_edge_distance_in: null
min_edge_distance_unknown_because: >-
Not published on the page.
# -> rationale.md § connectors.design_bases
design_bases:
non_composite:
applies_to_system: standard_non_composite
designations: [DP25, DP30s, DP38, DP40, ST50, ST60, ST75, ST100]
# SOURCE: W1 (hkties.com standard page, captured 2026-08-30,
# re-read live 2026-09-01). The page's own sentence, verbatim.
source_sentence: >-
"This system is designed to create a non-composite insulated
concrete sandwich panel" -- the two wythes of concrete are tied
together but the design "still allow[s] the two layers to shrink or
expand independently", the tie flexing to take the differential
movement.
what_it_means: >-
The wythes are NOT designed to act as one section. The ties carry
the fascia wythe and flex under the differential thermal movement
between the two wythes; the structural wythe carries the panel.
source: 'W1 -- hkties.com standard / non-composite products page, captured 2026-08-30, re-read 2026-09-01'
composite:
applies_to_system: composite_action
designations: [CA25, CA40, CA50, CA75, CA100, CA125, CA150]
# SOURCE: W2 (hkties.com CA page, captured 2026-08-30, re-read live
# 2026-09-01). The page's own sentence, verbatim.
source_sentence: >-
"The two wythes of concrete are acting together structurally thus
allowing a thinner panel to be created."
what_it_means: >-
The wythes ARE designed to act together, so the section a
calculation may use is the composite one -- which is the whole
reason the panel can be thinner.
source: 'W2 -- hkties.com CA / composite-action products page, captured 2026-08-30, re-read 2026-09-01'
# A THIRD ANSWER EXISTS AND IT IS THE HONEST DEFAULT.
unknown_is_not_non_composite: >-
A panel whose tie designation is not in either list above, or which
carries no recognised tie at all, has an UNKNOWN design basis. It is
not non-composite by default and it is not composite by default. The
lookup returns None with the reason, in the same discipline as
knowledge.same_face().
# -> rationale.md § connectors.product_line_no_published_capacity
product_line_no_published_capacity:
standard_non_composite:
# SOURCE: W1, product-line table (captured 2026-08-30, re-read 2026-09-01)
DP25: {description: 'Double-Prong 1" Tie', capacity_lb: null}
DP30s: {description: 'Double-Prong 1" Tie', capacity_lb: null}
DP38: {description: 'Double-Prong 1.50" Tie', capacity_lb: null}
DP40: {description: 'Double-Prong 1.50" Tie', capacity_lb: null}
ST60: {description: 'Standard 2.5" Tie', capacity_lb: null}
composite_action:
# SOURCE: W2, product-line table (captured 2026-08-30, re-read 2026-09-01)
CA25: {description: 'Quad Tip 1" Tie', capacity_lb: null}
CA40: {description: 'Quad Tip 1.5" Tie', capacity_lb: null}
CA125: {description: 'Quad Tip 5" Tie', capacity_lb: null}
CA150: {description: 'Quad Tip 6" Tie', capacity_lb: null}
unknown_because: >-
The manufacturer's pages name these model numbers in the product-line
list and tabulate capacities only for ST50/ST75/ST100 and
CA50/CA75/CA100. No capacity is published for the models above, in
any unit, on either page. They exist; nothing else about them is
known here, and nothing may be interpolated from the tabulated
neighbours.
# -> rationale.md § connectors.safety_factors
safety_factors:
tension_fos: null
shear_fos: null
unknown_because: >-
Both manufacturer pages publish ULTIMATE capacities and state no
safety factor anywhere -- no heading, no footnote, no test citation.
A working load cannot be derived from an ultimate without one, and
the 4:1 published for the disk-anchor inserts belongs to another
manufacturer's product and may not be borrowed. Read 2026-09-01
against both live pages by the landing agent; the absence was looked
for, not assumed.
capacity_is_ultimate: true
# sources: 2026-08-31
# -> rationale.md § connectors.edge_reference
edge_reference:
measured_to: insulation_edge
stated_by: Will
stated_on: '2026-08-31'
what_it_means: >-
A connector's edge distance is measured to the edge of the INSULATION,
not to the edge of the concrete panel. On a piece whose insulation runs
to the panel edge the two are the same number; where they differ, the
insulation governs.
# A SOLID ZONE THAT GOES THROUGH REDEFINES THE EDGE; A RECESS DOES NOT.
# Full depth means concrete has REPLACED the insulation there, so the
# zone's border is a real insulation boundary and a pin beside it is at
# an edge. A recess is partial depth: insulation still runs under it,
# the pin still has insulation on every side, and the original boundary
# governs.
through_cut_solid_zone_moves_the_edge: true
recess_moves_the_edge: false
consequences: >-
The check's reference geometry is the insulation extent MINUS the
solid zones that cut through it. That is the same solid-zone
population rows 5.11-5.13 recognise -- ONE recognition, several
consumers, and a second reader for the same parts is how two rows
come to disagree about what is on a piece. Recesses are excluded from
it entirely, on his sentence: they never move the edge, so for this
measurement they do not exist.
# HOW THROUGH-CUT IS TOLD FROM PARTIAL IS AN INFERENCE, and it is
# labelled one everywhere it is used. Depth is not a field on these
# parts; it is in the type's description text (`3" SOLID ZONE`,
# `4" SOLID ZONE`) beside an insulation type named for its own
# thickness. Equal depths read as through-cut. A zone whose depth
# cannot be parsed is NOT guessed at either way -- the pins near it go
# unjudged and the finding says so.
depth_is_inferred_from_names: true
depth_inference_caveat: >-
Reading a depth out of a description string is a reading, not a
measurement. It is marked inferred in every finding built on it, and
a solid zone whose description states no depth stands the pins around
it down rather than defaulting to either answer.
# sources: 2026-08-31
# -> rationale.md § checklist
checklist:
master:
- id: '1.1'
name: 'Sheet name equals the piece mark'
grade: 'A'
owner_codes: ['SHEET-NAME']
not_run_reason: null
checks: 'check_sheet_name'
- id: '1.2'
name: 'Sheet design-number reduces to the piece design'
grade: 'A'
owner_codes: ['DSN-MATCH', 'DSN-SUFFIX', 'DSN-PREFIX']
not_run_reason: null
checks: null
- id: '1.3'
name: 'Exactly one ticket set per piece'
grade: 'A'
owner_codes: ['TICKET-SET']
not_run_reason: null
checks: 'check_ticket_set'
# Re-graded A -> B 2026-08-30. Mechanical, but it needs the plant's
# design-token pattern to recognise a design label inside a view name,
# and its verdict is never worth failing a ticket over -- a stale name
# on a correct note is a trap for a reader, not a defect in the piece.
- id: '1.4'
name: 'Every placed view name refers to this design'
grade: 'B'
owner_codes: ['VIEW-DSN']
not_run_reason: null
checks: 'check_view_name_design'
# Built 2026-08-30 (CALLOUT-OVERLAP, ticketpage.callout_overlap_status /
# ticketchecks.check_callout_overlap). Text-bearing annotations on one
# placed view whose printed boxes intersect with positive area. Grade C
# and not B: a bounding box carries the text's leading and side
# bearings, so it is a little larger than the ink -- an intersection is
# a place for a person to look, never a defect claim. Text-vs-linework
# is deliberately out of scope (a call-out over a drawn edge is the
# normal condition on every elevation). Rotated annotations report an
# axis-aligned box that overstates the ink, so a pair with a rotated
# member is set aside under CALLOUT-OVERLAP-UNREAD, never flagged.
- id: '1.5'
name: 'Printed call-outs do not overlap each other'
grade: 'C'
owner_codes: ['CALLOUT-OVERLAP']
not_run_reason: null
checks: 'check_callout_overlap'
- id: '2.1'
name: 'Printed length/width/depth vs modelled'
grade: 'B'
owner_codes: ['SIZE-LENGTH', 'SIZE-WIDTH', 'SIZE-MAX-', 'SIZE-MIN-']
not_run_reason: null
checks: null
# Printed / reconstructed / measured, and the finding names which of
# the three is the odd one out. When only two of them can be read the
# check says so (WGT-TWO-WAY, never OK) rather than passing a two-way
# off as a three-way; with fewer than two it does not run at all.
- id: '2.2'
name: 'Weight three ways agree, and which one is odd is named'
grade: 'A'
owner_codes: ['WGT-']
not_run_reason: null
checks: 'check_weight_three_way'
- id: '2.3'
name: 'Unit weight ticket vs design summary'
grade: 'A'
owner_codes: ['PCF-NOTE', 'PCF-MODEL']
not_run_reason: null
checks: null
# Built 2026-08-30 (CAST-EXTRA, ticketpage.cast_extra_agreement /
# ticketchecks.check_cast_extras). It is a SET comparison, not a weight
# one: the extras the ticket names against the extras whose volume fed
# 2.2's reconstructed weight (liftexport.extra_cast_members, the same
# members extra_cast_ft3 sums). That is the question 2.2 cannot ask --
# three weights can agree while all three are wrong the same way, and a
# ledge missing from both model and drawing agrees with itself.
# The ticket side is TEXT (a BOM row or a view's words naming a
# families.cast_include token), never the drawn geometry: no query
# answers "does this elevation show a ledge". An unreadable or
# un-gathered BOM stands the comparison down -- a row naming the extra
# may have been missed -- while an absent BOM is judged from view text
# and says so.
- id: '2.4'
name: 'Cast extras (a ledge is its own family) counted in the weight'
grade: 'A'
owner_codes: ['CAST-EXTRA']
not_run_reason: null
checks: 'check_cast_extras'
- id: '3.1'
name: 'f''c ticket vs summary'
grade: 'A'
owner_codes: ['FC']
not_run_reason: null
checks: null
- id: '3.2'
name: 'f''ci ticket vs summary'
grade: 'A'
owner_codes: ['FCI']
not_run_reason: null
checks: null
- id: '3.3'
name: 'f''ci/f''c release ratio in band'
grade: 'B'
owner_codes: ['FCI-RATIO']
not_run_reason: null
checks: 'check_release_ratio'
- id: '4.1'
name: 'Printed strand count vs modelled'
grade: 'B'
owner_codes: ['STR-QTY']
not_run_reason: null
checks: null
- id: '4.2'
name: 'Strand diameter & grade vs design'
grade: 'A'
owner_codes: ['STR-DIA', 'STR-DIA-EXTRA']
not_run_reason: null
checks: null
- id: '4.3'
name: 'Jacking force on ticket vs design'
grade: 'A'
owner_codes: ['JACK-FORCE']
not_run_reason: null
checks: 'check_jacking_force'
- id: '4.4'
name: 'Strand positions vs design chain from stated rail'
grade: 'B'
owner_codes: ['STRAND-POS']
not_run_reason: null
checks: 'check_strand_layout'
# Built 2026-08-30 (STRAND-CALLOUT, ticketpage.debond_callout_status /
# ticketchecks.check_debond_callout). Owner's ruling: where the debond
# condition is met, expect a call-out; where there is none, WARN.
# It CONSUMES 4.7 rather than re-deriving the geometry -- 4.7 owns where
# debonding is required, 4.5 owns whether the ticket says so. When 4.7
# could not judge, 4.5 reports NOT RUN with 4.7's own reason instead of
# warning about a requirement nobody established.
# Grade C stands: the call-out found on these tickets is a bare note
# with no station and no strand number, so a present call-out shortlists
# rather than proves, and the finding says exactly that.
- id: '4.5'
name: 'Debonded strands called out where the design debonds'
grade: 'C'
owner_codes: ['STRAND-CALLOUT']
not_run_reason: null
checks: 'check_debond_callout'
- id: '4.6'
name: 'Row count & spacing plausible'
grade: 'A'
owner_codes: ['STRAND-SPACING', 'STRAND-ROWS', 'STRAND-GRID', 'BED-WIDTH', 'BED-OFFSET']
not_run_reason: null
checks: 'check_strand_layout'
- id: '4.7'
name: 'Strand debonded wherever the concrete runs short'
grade: 'B'
owner_codes: ['STRAND-DEBOND']
not_run_reason: null
checks: 'check_strand_debond'
# ---- THE BOM'S OWN STRAND ROW, added 2026-08-31 ----------------------
#
# A CHECK THAT RAN AND COULD REACH NO ROW. `check_bom_strand_count` has
# been registered and running since before the master checklist moved into
# this file; nothing named it, so its BOM-STRAND finding -- which can be a
# FAIL -- landed in `qc-run.json` under a code no row owned and no row
# ever turned red. It was recorded as a known exemption
# (`ROWLESS_CHECKS`) rather than fixed, because giving a check a row is a
# decision about what the report claims and that is the owner's. He made
# it: "看看能跑的,能做的,现在都做了去".
#
# WHY ITS OWN ROW AND NOT A SECOND CHECK ON 4.1. 4.1 asks the printed
# section call-out against the model (STR-QTY, emitted by
# `ticketqc.check_ticket`). This asks the BOM's LINEAR-FEET row -- a
# length, divided by the piece length -- against the same model, and
# optionally against the section print as a third leg. Different input,
# different failure, different repair: a wrong BOM row is fixed in the
# schedule and a wrong section call-out is fixed on the drawing.
#
# AND THE MULTI-CHECK IDIOM WOULD HAVE COST NOT RUNS, WHICH IS THE
# OPPOSITE OF THE ORDER. A row is answered only when EVERY check it names
# has spoken, so hanging this off 4.1 would make 4.1 NOT RUN on every
# ticket whose BOM could not be read -- turning a row that had a verdict
# into a red one, over a silence about a different question. 5.1 pairs two
# checks because they are two halves of ONE question and half an answer is
# not an answer; these are two independent answers, and either alone is a
# real verdict.
#
# GRADE A: the comparison is arithmetic and its one constant is measured
# (`strand_count_from_linear_ft`'s 0.05 ratio tolerance, from 12 real
# tickets whose remainders span 0.0032-0.0226). The third leg needs
# `legend.section_strand_view` and is optional -- without it the row is
# still fully answered two ways.
- id: '4.8'
name: 'BOM strand row agrees with the section print and the model'
grade: 'A'
owner_codes: ['BOM-STRAND']
not_run_reason: null
checks: 'check_bom_strand_count'
# 5.1 IS TWO QUESTIONS AND ONLY ONE OF THEM WAS EVER ASKED.
# `check_bom_members` compares the BOM's named rows against the
# assembly -- a real comparison, and not the one this row is written
# as (docs/checklist-code-map.md, disagreement #2). The other half is
# the plan's own call-outs, and the part of THAT which needs the
# detail sheets is not built: `check_detail_callouts` reads
# nomenclature.detail_sheet_prefix, finds the bubbles the plan cites,
# and reports NOT RUN naming the sheets nobody reads. It passes only
# when the plan cites no detail at all, which is a real verdict.
# Two checks, so the row is answered only when BOTH spoke -- see
# checklist_states.
- id: '5.1'
name: 'BED PLAN callout embed exists in model'
grade: 'A'
owner_codes: ['BOM-MEMBERS', 'DETAIL-CALLOUT']
not_run_reason: null
checks: ['check_bom_members', 'check_detail_callouts']
- id: '5.2'
name: 'Embed in model appears on ticket'
grade: 'A'
owner_codes: ['BED-REVERSE']
not_run_reason: null
checks: 'check_bed_plan_reverse'
# 5.3 before 5.4. They were the wrong way round until 2026-08-30 (the
# owner read a real WP133 run and spotted it); the table is read as a
# checklist, and a reader who cannot find 5.3 where 5.3 belongs starts
# doubting whether the row is there at all. `test_checklist_is_in
# _numerical_order` now holds every row to it.
# The reason slot is what makes this a FOR ENGINEER row rather than an
# unbuilt one -- see CHECKLIST_FOR_ENGINEER -- so it says plainly that
# nothing computes it. It used to read "code narrows, person decides",
# which promised a narrowing no code performs.
- id: '5.3'
name: 'Face designations consistent with model side'
grade: 'C'
owner_codes: null
not_run_reason: >-
grade C: a person reads the face designations against the model side
-- by design no check computes this row, so it is a prompt to look
and never a verdict
checks: null
- id: '5.4'
name: 'Mesh / WWF designation vs design'
grade: 'A'
owner_codes: ['WWF']
not_run_reason: null
checks: 'check_wwf_designation'
# SPLIT 2026-08-30, on the owner's approval. 5.5 was one row asking two
# questions of different kinds. UNIQUENESS is a comparison between the
# model's marks and the sheet's printed text, and a comparison is code's
# job -- it moved to 5.6. LEGIBILITY is a property of the PICTURE, which
# no query answers, so it stays here at grade D and reads FOR ENGINEER.
# The vision path (`ticketvision.ESCALATIONS`, AI-BARMARK) is unchanged
# and still escalates this row; its question text now overlaps 5.6 on
# the uniqueness half, which is deliberate -- a model reading the
# rendered sheet is a second, independent look, never the computed
# answer, and section 8bis forbids it from claiming a verdict either way.
- id: '5.5'
name: 'Bar marks legible on the drawing'
grade: 'D'
owner_codes: null
not_run_reason: >-
grade D: a person reads the drawing; code only guarantees the
question was asked
checks: null
# The computed half of the old 5.5 (BARMARK-UNIQUE,
# ticketpage.bar_mark_uniqueness_status / ticketchecks.
# check_bar_mark_uniqueness). Three questions, three defects: one mark
# naming two parts, a printed mark no bar carries, a modelled bar never
# printed. Grade B -- mechanical, and the only local constant is the
# shape of a mark token, derived here rather than added as a plant fact.
- id: '5.6'
name: 'Bar marks unique, and model and print name the same bars'
grade: 'B'
owner_codes: ['BARMARK-UNIQUE']
not_run_reason: null
checks: 'check_bar_mark_uniqueness'
# Built 2026-08-30 (BAR-LENGTH, ticketpage.bar_stock_length_status /
# ticketchecks.check_bar_stock_length) on the owner's ruling that a bar
# is cut from 40 ft stock. The length is READ from the member.length
# role and never measured -- a bent bar consumes its developed length
# and a bounding box understates every one of them. Grade B: mechanical
# comparison against a published stock length, which is why the number
# lives in the catalogue and not in this file.
- id: '5.7'
name: 'No bar longer than the stock it is cut from'
grade: 'B'
owner_codes: ['BAR-LENGTH']
not_run_reason: null
checks: 'check_bar_stock_length'
# ---- CONNECTORS ON AN INSULATED PANEL, added 2026-08-31 ---------------
#
# Owner: "insulated panel 加一个 check,检查 connector 是不是存在,以及够不够"
# and, the same day, "再增加 connector edge distance 的 check".
#
# THE FIRST QUESTIONS EVER ADDED UNDER THE THREE-LAYER ARCHITECTURE, and
# the procedure is the point: a new QUESTION is three entries here (public,
# because what a connector is and why it matters is trade knowledge), three
# check functions in `vibe.ticketchecks` bound by these ids, and a line in
# the plant's own board saying where it sits in ITS numbering. Nothing
# about them is plant-specific except the part numbers, which live in the
# plant layer where part numbers belong.
#
# ALL THREE ARE INSULATED-ONLY, through `checks.only_applies_to` in the
# plant layer -- NOT through anything written here. On a solid panel there
# is no insulation for a connector to cross, so the question is not quiet,
# it is meaningless; and on a piece nothing could classify they read NOT
# RUN in red, because answering an insulated question about a piece that
# may not be one is exactly the silent pass this tool exists to refuse.
- id: '5.8'
name: 'Connectors present on an insulated panel'
grade: 'B'
owner_codes: ['CONNECTOR-EXIST']
not_run_reason: null
checks: 'check_connectors_exist'
# Grade B and not A: the count is mechanical, but WHAT THE DESIGN REQUIRES
# is a line of the design summary the parser cannot read yet -- WP115's
# 8.1 lists `HK ST100'S @1'-4" O.C.` among its uninterpreted lines. The
# model honours it exactly (measured 2026-08-31: five rows across at 16.0
# in field spacing, which IS 1'-4"), so the comparison has a calibrated
# example waiting for a parser that can state the requirement.
- id: '5.9'
name: 'Enough connectors for what the design calls for'
grade: 'B'
owner_codes: ['CONNECTOR-COUNT']
not_run_reason: null
checks: 'check_connectors_sufficient'
# The 6.2 shape, one part along. The minimum is a property of the PRODUCT
# and lives in the public catalogue beside its capacity; it resolves
# product-first through the plant's alias, and a plant-wide fallback is
# second-class and named as such in the finding, because "6 inches" and
# "6 inches for this product" are different claims.
- id: '5.10'
name: 'Connector edge distances'
grade: 'B'
owner_codes: ['CONNECTOR-EDGE']
not_run_reason: null
checks: 'check_connector_edge_distance'
# ---- SOLID ZONES AND INSULATION RECESSES, added 2026-08-31 -----------
#
# Owner, twice, having asked for it before: "没有 solid zone 相关的检查。
# lifter 自带 solid zone,没检查这些 solid zone 是不是被加入了 assembly,
# 是不是被显示在 ticket 上面,hatch pattern 对不对。这些都没有啊" -- and
# then "不光是 solid zone,insulation recess 也得加上。你看看 page 2 里面
# 的那些 hatch pattern 就知道了。"
#
# WHAT A SOLID ZONE IS, in trade terms and therefore here: a block of
# concrete that replaces the insulation locally so a lifting insert has
# solid material to pull against. On this job every lifter NESTS one as a
# sub-component of its own family, and there are standalone ones at
# openings as well.
#
# WHAT A RECESS IS: a partial-depth pocket in the insulation. It is NOT a
# model element on this job -- the recesses are DRAFTED filled regions on
# the ticket, of types named for their depth -- so nothing about assembly
# membership applies to them and 5.14/5.15 ask a different pair of
# questions from 5.11/5.12 on purpose.
#
# ALL FIVE ARE INSULATED-ONLY, through `checks.only_applies_to` in the
# plant layer and NOT through anything written here: a solid panel has no
# insulation for a zone to interrupt or a recess to be cut in, so the
# question is meaningless rather than quiet. Same ruling as 5.8-5.10.
#
# WHY 5.11-5.13 ARE THREE ROWS AND NOT ONE. They are three different
# failures with three different repairs: a zone modelled but left out of
# the assembly (add it), a zone in the assembly but not reaching the
# layout view (a view or filter problem), and a zone that draws with the
# wrong hatch (a filter override). Collapsing them would tell a modeler
# something is wrong and not what to do.
- id: '5.11'
name: 'Every lifter''s solid zone is a member of the assembly'
grade: 'B'
owner_codes: ['SOLID-ZONE-ASM', 'SOLID-ZONE-ABSENT']
not_run_reason: null
checks: 'check_solid_zone_members'
# SOLID-ZONE-ABSENT is a SECOND code on this row, not a second row, and
# it is the distinct finding "a lifter that nests NO solid zone at all".
# Measured 2026-08-31 it is zero across 192 lifters on 27 insulated
# pieces -- and the day it is not zero it is a bigger defect than any
# membership question, because there is no zone to add. Two codes, worst
# severity wins, so it cannot be hidden behind the membership verdict.
- id: '5.12'
name: 'Solid zones appear on the ticket''s insulation layout'
grade: 'B'
owner_codes: ['SOLID-ZONE-TICKET']
not_run_reason: null
checks: 'check_solid_zone_on_ticket'
# The reference for the hatch is THE LEGEND'S OWN SWATCH, resolved live
# from the sheet where it can be, with the plant's expected-pattern map
# named as a fallback when it cannot. The legend is the sheet's own
# statement of what each hatch means and it is the only reference that
# updates itself when the plant changes one; a table in a YAML file is a
# copy of it, and a copy is what goes stale.
- id: '5.13'
name: 'Solid zone hatch matches the legend swatch'
grade: 'B'
owner_codes: ['SOLID-ZONE-HATCH']
not_run_reason: null
checks: 'check_solid_zone_hatch'
# GRADE C AND SCOPED HONESTLY. There is no model counterpart to compare a
# drafted region against, and the design side -- which recesses this
# piece is supposed to have -- is not parsed by anything today. So this
# row's first life is presence-and-inventory, and where it cannot compare
# it says what it lacks instead of comparing something else. A row that
# counts regions and calls it agreement would be the silent pass this
# table exists to refuse.
- id: '5.14'
name: 'Insulation recess regions present where the design calls for them'
grade: 'C'
owner_codes: ['RECESS-PRESENT']
not_run_reason: null
checks: 'check_recess_regions'
# THE SIZE DISCREPANCY IS NOT THIS ROW'S QUESTION and the finding says so:
# the legend's text says 1" and 2" while the region type names say 2in and
# 3in. That is a real thing somebody should answer and the owner has been
# asked. This row compares PATTERNS, which is a question with an exact
# answer, and refuses to launder the size question through it.
- id: '5.15'
name: 'Recess region hatch matches the legend swatch'
grade: 'B'
owner_codes: ['RECESS-HATCH']
not_run_reason: null
checks: 'check_recess_hatch'
# Counts the MODELLED bars flagged as opening / blockout bars (a scheduled
# rebar family member carries a 'bar type blockout' yes/no) at the openings
# read off the piece -- the tickets carry no bar list. Short is a WARN a
# person resolves (small openings often cannot take the bars), never a FAIL.
- id: '5.16'
name: 'Door openings / blockouts: opening bar type flagged and #3 diagonals present'
grade: 'B'
owner_codes: ['OPENING-DIAG']
not_run_reason: null
checks: 'check_opening_diagonal_bars'
- id: '6.1'
name: 'Insert capacity vs load'
grade: 'A'
owner_codes: ['LIFT-CAPACITY']
not_run_reason: null
checks: 'check_lifter_capacity'
- id: '6.2'
name: 'Insert edge distances'
grade: 'A'
owner_codes: ['LIFT-EDGE']
not_run_reason: null
checks: 'check_lifter_edge_distance'
- id: '6.3'
name: 'Current handling result exists & passes'
grade: 'A'
owner_codes: ['HANDLE-']
not_run_reason: null
checks: 'check_handling_result'
- id: '6.4'
name: 'Dunnage not under an opening'
grade: 'A'
owner_codes: ['DUN-OPENING']
not_run_reason: null
checks: 'check_dunnage_openings'
# Re-graded A -> B 2026-08-30. What is deterministic is EMPTINESS -- a
# note view holding nothing but its own heading -- and that needs the
# plant's names for those views. Whether present content is the RIGHT
# instruction is not decided here and is not claimed to be.
- id: '6.5'
name: 'DUNNAGE / ERECTION / PICKING notes say something'
grade: 'B'
owner_codes: ['NOTE-CONTENT']
not_run_reason: null
checks: 'check_note_views'
# Built 2026-08-31 on the owner's ruling (PARITY-NOTE,
# ticketpage.parity_note_status / ticketchecks.check_parity_note): when
# the count of one part on a piece is ODD, the BED PLAN must carry a
# particular phrase. In section 6 because the trigger is a lifting
# insert's count and the consequence is an erection instruction; the
# BED PLAN is only where the instruction is printed.
#
# Grade B, not A, and the reason is not modesty. The count rests on a
# family name the PLANT declares and the phrase match proves only that
# the words are on the plan, not that they are about this part -- the
# same wording covers a temporary erection-only element on a `*.A`
# packet. Mechanical rule, plant-local constants: B.
- id: '6.6'
name: 'Odd part count carries the BED PLAN note it requires'
grade: 'B'
owner_codes: ['PARITY-NOTE']
not_run_reason: null
checks: 'check_parity_note'
# ---- MINIMUM ANCHOR SPACING, added 2026-08-31 -------------------------
#
# ALP catalog p53, 02/2024, footnote under the capacity table: "Minimum
# anchor spacing is 2x the published edge distance". The owner supplied
# that page; its edge-distance column became 6.2 and its capacities became
# `catalogue.inserts`, and this sentence on the same page was read past.
# Until today nothing in the tool asked how far apart two anchors must
# sit, so two 8T anchors 20 in apart passed every row on the board while
# the page asked for 28.
#
# Grade A, like 6.2, and for the same reason: the measurement is
# mechanical and the minimum is PUBLISHED PRODUCT DATA in this file,
# reached through the plant's alias. Nothing about the rule is a plant
# habit. What is per-plant is only which parts are on the piece.
#
# The minimum is usually DERIVED rather than printed -- 2x the part's own
# published edge distance -- and the finding prints that arithmetic,
# because "28 inches" and "twice this part's published 14 inches" are
# different claims and only the second can be checked. A part whose sheet
# states a spacing outright overrides the multiple
# (`min_spacing_rule.published_in`), and a part with no published edge
# distance to double is NOT judged and is named: there is no plant-wide
# fallback here, because a general "anchors go two feet apart" is not
# something any layer states and inventing one is how a safety minimum
# gets borrowed from a product it does not belong to.
- id: '6.7'
name: 'Minimum spacing between lifting anchors'
grade: 'A'
owner_codes: ['LIFT-SPACING']
not_run_reason: null
checks: 'check_lifter_anchor_spacing'
# ---- PICKING STATIONS, added 2026-08-31 ------------------------------
#
# THE SECOND CHECK THAT RAN AND REACHED NO ROW. `check_picking_match`
# emits PICK-STATIONS / PICK-UNREADABLE and NOTHING ON THE CHECKLIST
# ASKED ABOUT PICKING STATIONS AT ALL -- so unlike 4.8 there was not even
# a neighbouring row that could have claimed it. Same ruling, same day.
#
# WHY IT IS NOT PART OF 6.5. 6.5 asks whether the PICKING note view says
# anything at all; this asks whether the stations printed in the picking
# DIAGRAM are where the picking inserts actually are. A note that says
# something and a diagram that is right are different claims, and folding
# them together would let a full note view carry a wrong station past the
# reader.
#
# SECTION 6 BECAUSE THE SUBJECT IS THE LIFTING INSERTS, and it sits after
# 6.7 rather than beside 6.3 for the reason the board's own note gives:
# nothing he already reads by number moves.
#
# GRADE B AND WARN BY RULING. The arithmetic is exact -- the diagram
# prints segments from EACH END and `stations_from_ends` resolves them --
# but which families are picking inserts is a plant fact
# (`handling.lifter_groups.face.families`), the tolerance is one
# (`handling.picking_tolerance_ft`), and reading the diagram at all rests
# on a measured drafting habit: picking prints at the highest y of the
# size notes. Owner, 2026-08-27: a dunnage-style finding is never the
# tool's verdict, so a mismatch is WARN and an engineer judges.
- id: '6.8'
name: 'Printed picking stations match the modelled picking inserts'
grade: 'B'
owner_codes: ['PICK-STATIONS', 'PICK-UNREADABLE']
not_run_reason: null
checks: 'check_picking_match'
# ---- LIFTING-POINT COUNT vs LENGTH, added 2026-09-02 -------------------
#
# An MC plant rule, not a published catalogue number: owner, verbatim,
# "ticketqc在MC加一条check:lifting points: MC的规矩是,panel长度小于20ft
# 则2p lifting,大于20则是4p lifting。实际上25以内都可以2p。你按照20-25给
# warn,25以上给fail写。" Nominal rule: under 20 ft, 2-point; 20 ft and
# over, 4-point. The real ceiling is looser than the nominal rule --
# 2-point actually holds to 25 ft -- so the grey zone between the two is a
# WARN and only past 25 ft does 2-point FAIL. 4 or more points is always
# OK; this only flags too FEW points for the length, never too many.
#
# Grade A, like 6.2/6.7/6.4: the arithmetic is mechanical (count the
# modelled points, compare to the panel length) once the thresholds are
# known, and what is per-plant is only the two numbers themselves --
# `lifters.lifting_points.two_point_max_length_ft` / `.two_point_
# absolute_max_ft` -- landed in MC's own plant layer. A layer that has
# not set both -- any non-MC plant -- gets no judgement from this rule at
# all: the check stands down naming the missing keys rather than holding
# another plant's panels to MC's numbers.
- id: '6.9'
name: 'Lifting-point count vs panel length (plant rule)'
grade: 'A'
owner_codes: ['LIFT-POINTS', 'LIFT-POINTS-UNREAD']
not_run_reason: null
checks: 'check_lifting_points'
# ---- LIFTING-POINT POSITION vs PCI, added 2026-09-02 --------------------
#
# DISTINCT FROM 6.9, on the same day. 6.9 grades the COUNT of lifting
# points against panel length; this grades WHERE each already-counted
# point sits against PCI's recommended station. An MC plant rule again,
# not a published catalogue number: owner, verbatim, "follow PCI的意思是
# 检查一下lifter的位置,如果跟PCI相差大于6" 则报WARN。很多时候没办法完全
# follow PCI,但是这件事儿让用户判断可行不可行。"
#
# WARN-ONLY, NEVER FAIL -- by that same ruling. Real panels often cannot
# follow PCI's ideal two-point location exactly, and whether a given miss
# is acceptable is the engineer's judgement, not this tool's verdict --
# same posture as 6.8's picking-station mismatch.
#
# THE 0.207L TWO-POINT LOCATION IS A PCI PUBLIC CONSTANT (the equal-moment
# two-point pick, confirmed on the sample_job drawings and mirrored by
# `panellift.pci.OPTIMAL_TWO_LINE_FRACTION`), not plant knowledge -- only
# the 6" TOLERANCE (`lifters.lifting_points.pci_position_tolerance_in`) is
# MC's own number, landed beside the 6.9 thresholds. A plant that has not
# set it gets no judgement from this rule, exactly like 6.9's stand-down.
#
# FOUR-POINT (OR ANY COUNT OTHER THAN TWO) IS NOT YET CONFIGURED. PCI's
# recommended stations for a 4-point pick are real published numbers this
# tool has not yet been given, so any count other than two stands the row
# down naming the count rather than guessing a 4-point rule.
#
# Grade A: the arithmetic is mechanical (compare a modelled station to
# 0.207L/0.793L) once the tolerance is known, and reuses 6.9's own point
# clustering rather than re-deriving it.
- id: '6.10'
name: 'Lifting-point position vs PCI recommendation (plant rule)'
grade: 'A'
owner_codes: ['LIFT-POS', 'LIFT-POS-UNREAD']
not_run_reason: null
checks: 'check_lift_positions'
# ---- DUNNAGE-POINT COUNT vs STRIPPING-POINT COUNT, added 2026-09-02 -----
#
# A THIRD MC plant rule, not a published catalogue number: owner,
# verbatim, "默认MC panels dunnage point=stripping points。4p stripping
# 则4p dunnage。" -- by default an MC panel's dunnage-point count equals
# its stripping-point count. And: "stripping point就是face lifters位置"
# / "LPA开头的就是face lifter" -- a stripping point is a face
# (back-stripping) lifter position, and the LPA-prefixed anchors are the
# face lifters.
#
# A DEFAULT, NOT AN ABSOLUTE RULE -- a mismatch is a deviation from it and
# the owner judges feasibility, exactly like 6.9/6.10: WARN, never FAIL.
#
# Grade A, like 6.9/6.10: the arithmetic is mechanical (count the two
# kinds of point, compare) once the plant fact -- which lifters are the
# face/stripping ones -- is known. That fact is read structurally, off
# `lifters.capacity_lb.<family>.usage: back_stripping` (the same tree and
# resolver `lifter_capacity()` uses, so this row can never disagree with
# 6.1 about which entry a family resolves to), with the bare `LPA`
# family-name prefix as a fallback for a family with no usage tag at all.
# `lifters.lifting_points.dunnage_matches_stripping` gates the rule -- a
# layer that has not set it gets no judgement from this row at all,
# exactly like 6.9/6.10's own stand-down.
- id: '6.11'
name: 'Dunnage-point count vs stripping-point count (plant rule)'
grade: 'A'
owner_codes: ['DUN-STRIP', 'DUN-STRIP-UNREAD']
not_run_reason: null
checks: 'check_dunnage_stripping_match'
# ---- LIFTER ACCESSORY (CUP/PATCH) ASSEMBLY MEMBERSHIP, added 2026-09-08,
# REVIT issue #26 ---------------------------------------------------------
#
# 6.12 IS DELIBERATELY SKIPPED -- see the `FLEXURAL_CHECKS_ALREADY_ASKED`
# comment in `ticketchecks.py`: a "6.12" built to an earlier draft of the
# flexural table would have duplicated 6.10, so this row is numbered 6.13
# rather than reuse a number that already almost meant something else.
#
# A lifter can nest a small accessory family that is not itself a
# recognised lifter -- a cup / void former that forms the crane-hook hole
# (QL121 under LIFT_QL589G_8T, QL062 under LIFT_QL548G_4T) or a patch
# (DB_PATCH, nested under either LPA pin-anchor family, `parent` given as
# a list) -- and none of it is reachable through the SAME family-name
# recognition 6.1's lifter capacity uses, because these are not lifters
# themselves. `lifters.accessories` in the plant layer names which
# families count and what role each plays; a nested child matching one of
# those families that never joined the piece's assembly is invisible on
# the ticket the same way a stray solid zone is (5.11's own reasoning,
# one part class over) -- the layout view is assembly-scoped and cannot
# show what the assembly does not contain.
#
# GRADE A: the gather is mechanical (`GetSubComponentIds()` on every
# recognised lifter, generalising 5.11's own reader rather than adding a
# second one) and the comparison is a plain membership test once
# `lifters.accessories` says which families to look for. What is
# per-plant is only that table -- which families are accessories at all
# and what each one is called.
#
# A child NOT in `lifters.accessories` and also not a member is a
# separate WARN (`LIFT-ACCESSORY-UNCLASSIFIED`), never folded into the
# FAIL: this check has no basis to call an unrecognised nested part a
# defect, only to flag it for a person to look at.
#
# LIVE-VALIDATED 2026-09-08 on the open MC model: WPA648 (assembly
# 9141343) FAILs naming 8 DB_PATCH children nested in its LPA pin anchors
# (e.g. child 9521037 under anchor 9521036) that never joined the
# assembly; WPA655 (9141344) and SMD516 (6113164) are OK.
- id: '6.13'
name: 'Lifter accessory (cup/patch) is a member of the assembly'
grade: 'A'
owner_codes: ['LIFT-ACCESSORY-NOT-IN-ASSEMBLY', 'LIFT-ACCESSORY-UNCLASSIFIED']
not_run_reason: null
checks: 'check_lifter_accessory_in_assembly'
- id: '7.1'
name: 'Interference check'
grade: 'B'
owner_codes: ['CLASH', 'CLASH-UNREADABLE']
not_run_reason: null
checks: 'check_member_interference'
- id: '8.1'
name: 'No unresolved design-summary lines'
grade: 'C'
owner_codes: ['SPEC-UNRESOLVED']
not_run_reason: null
checks: 'check_spec_unresolved'
- id: '8.2'
name: 'No invisible characters in ticket text'
grade: 'A'
owner_codes: ['INVISIBLE']
not_run_reason: null
checks: 'check_invisible'