WillTools 2026-09-09 -- clean snapshot of the development repo for review
ci / test (push) Successful in 6m14s
ci / test (push) Successful in 6m14s
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.
This commit is contained in:
@@ -0,0 +1,436 @@
|
||||
# panellift — plan
|
||||
|
||||
PCI Chapter 8 handling checks for precast wall panels, fed from the Revit
|
||||
model that already holds the geometry.
|
||||
|
||||
Scope is the "first tier" agreed on 2026-08-18: **stripping and yard handling,
|
||||
flat**. Transportation (needs dunnage positions the model does not have) and
|
||||
erection (the panel rotating up, load shedding onto the lowest pick point) are
|
||||
explicitly out.
|
||||
|
||||
---
|
||||
|
||||
## Why the calculation does not run inside Revit
|
||||
|
||||
The extension runs on IronPython 2.7 on Revit's UI thread. A calculation whose
|
||||
output goes to a plant has to be verifiable, and verifiable means a test suite
|
||||
that runs in a second without a Revit licence, a model, or a Windows box.
|
||||
|
||||
So: **Revit writes a JSON file, a plain CPython package reads it.** Same
|
||||
pattern already proven by `vibe.aiextract`. The package has **no
|
||||
dependencies** — every dependency is something that can be missing at the
|
||||
moment somebody needs a number.
|
||||
|
||||
```
|
||||
Revit ──liftexport──> <mark>.json ──panellift──> report.html
|
||||
(IronPython 2.7) (CPython 3.9+)
|
||||
```
|
||||
|
||||
A consequence worth keeping: a panel can be checked by hand-writing the JSON,
|
||||
with no model at all.
|
||||
|
||||
## Decisions taken
|
||||
|
||||
| Question | Decision |
|
||||
|---|---|
|
||||
| Lifter capacity vs handling multiplier | **Not** stacked. PCI Table 8.3.1 note (a): those factors are for flexural design and are not to be applied to lifting-device safety factors. Inserts are checked against unfactored reactions and against **the published rating of the part itself** — safety factors are per product in `knowledge/catalogue.yaml` and an insert with none published is reported unjudged. *This row used to end "with the manufacturer's 4:1 tension / 2.67:1 shear". No manufacturer published that. The sentence was written the day after the constant it justified and it cited nothing; the one datasheet in this repository for the parts it reached (ALP catalog p53, 02/2024) heads its capacity table "Capacity in Concrete 4:1 SWL" across both load columns — 4:1 in shear as well. Corrected 2026-08-31, and recorded rather than quietly deleted, because an invented pedigree on a safety factor is the kind of thing that gets re-derived from the same document twice.* |
|
||||
| Sling angle | Engineer's config file, in degrees. Not inferred from the model. |
|
||||
| Rolling block vs static lift | Engineer's config file. **Two different mechanics**, not a coefficient — see below. |
|
||||
| Load cases | Stripping + yard handling. |
|
||||
| Flexural capacity | Shipped 2026-08-31 as ACI 318-19 approximate `fps` (Eq. 20.3.2.3.1), strand only, in `capacity.py`. **The columns were informational for one day.** Owner's ruling 2026-09-01 — *"M/pMn 对我而言>1.0是可以的,我能盖章。你不行,大于1.0你就报fail,我做最后决策。"* — so `M/φMn > 1.0` is a `FLEX-CAP` **FAIL** and `V/φVn > 1.0` a `SHEAR-CAP` FAIL, checked at every station of every stage, with **no development-length exclusion**: the tool may not bless a ratio over 1.0 and the engineer stamps past it. The stress check keeps its own verdict; a station that cannot be judged stays a dash and is neither. Full strain compatibility remains the upgrade: it needs the strand stress-strain curve and mild-steel/WWF input paths that no layer holds yet. `fy` defaults to 60 ksi, `fyt` from the design summary; both overridable in config. |
|
||||
|
||||
### Rolling block vs static lift
|
||||
|
||||
- **rolling** — equalising sheaves, every line in a cable group carries the
|
||||
same force. Two groups, two equations of statics, determinate. Reactions do
|
||||
not depend on stiffness, so a blockout does not move them.
|
||||
- **static** — fixed-length slings. The panel is a continuous beam on rigid
|
||||
supports; three or more support lines are indeterminate and the reactions
|
||||
follow flexural stiffness. A blockout beside a support really does shed load
|
||||
off it.
|
||||
|
||||
Measured on the ASPIRE panel with three support lines at 4 / 16 / 28 ft:
|
||||
|
||||
```
|
||||
rolling 15,167 15,167 15,167 lb
|
||||
static 13,508 18,484 13,508 lb middle line +22%
|
||||
```
|
||||
|
||||
Picking the wrong one is a 22% error on an insert. Hence: the engineer says.
|
||||
|
||||
## The design decision that matters most
|
||||
|
||||
**PCI Figure 8.3.2's coefficients are a test, not the engine.**
|
||||
|
||||
Those coefficients only hold when the picks sit where positive and negative
|
||||
moment come out equal. The entire reason to check a real ticket is that the
|
||||
detailer may not have put them there — so using the closed form as the engine
|
||||
would assume the answer.
|
||||
|
||||
Instead the moment comes from a real beam solve at the *actual* insert
|
||||
positions, and Figure 8.3.2 becomes the regression test: put the supports at
|
||||
`(sqrt(2)-1)/2` of the span and the solver must return `0.0214 w a b²`.
|
||||
|
||||
It does. That identity is also *derived* in `pci.py` rather than quoted —
|
||||
solving `w(aL)²/2 = wL²(1-2a)²/8 - w(aL)²/2` gives `4a² + 4a - 1 = 0`, hence
|
||||
`a = 0.20711` and a coefficient of `0.02145`, which is PCI's 0.0214.
|
||||
|
||||
## Known idealisation
|
||||
|
||||
The panel is treated as a beam spanning its long direction. This *is* PCI's
|
||||
own method for `My`, and it is what the ASPIRE examples do. It is not valid
|
||||
for a near-square panel, where two-way plate action governs — that is
|
||||
finite-element territory (PanelsPlus). **The code must refuse rather than
|
||||
answer** when the aspect ratio gets close to 1. Not yet implemented.
|
||||
|
||||
The transverse direction `Mx` will use PCI's closed form with the "resisted by
|
||||
15t or b/2" rule, as a secondary check.
|
||||
|
||||
## Status
|
||||
|
||||
### Done and tested — 34 tests, all passing
|
||||
|
||||
| Module | |
|
||||
|---|---|
|
||||
| `model.py` | `Panel`, `Blockout`, `Lifter`, `Rigging`, `Reinforcement`, `Stage`, `Material` |
|
||||
| `section.py` | `b_eff(x)`, `S(x)`, `I(x)`, `w(x)`, blockout merging, weight reconciliation |
|
||||
| `pci.py` | Table 8.3.1, `5√f'c`, Figure 8.3.2 closed forms, optimal support fraction |
|
||||
| `linalg.py` | dense solver, so there are no dependencies |
|
||||
| `statics.py` | insert clustering → support lines → cable groups; rolling and static solvers; V(x), M(x); sling forces |
|
||||
|
||||
Reproduced from ASPIRE Winter 2019 (Brown), which reprints PCI Table 8.3.1 and
|
||||
Figure 8.3.2:
|
||||
|
||||
| | ours | published |
|
||||
|---|---|---|
|
||||
| unit weight | 109.4 psf | 109.4 |
|
||||
| S solid | 1531 in³ | 1531 |
|
||||
| S, five 6"×24" pockets | 1148 in³ | 1148 |
|
||||
| unfactored moment | 24,020 lb-ft | 23,973 |
|
||||
| stripping stress, solid | 245 psi | 244 — OK vs 316 |
|
||||
| transport stress, solid | 282 psi | 282 — OK vs 354 |
|
||||
| stripping stress, pocketed | 326 psi | 326 — **no good** vs 316 |
|
||||
| transport stress, pocketed | 377 psi | 376 — **no good** vs 354 |
|
||||
|
||||
The moment differs by 0.2% because the article rounds the coefficient to
|
||||
0.0214; we carry `(√2−1)²/8`.
|
||||
|
||||
### Done since — 76 tests
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| `checks.py` | cracking, insert capacity with sling amplification, edge-anchor shear and plumb-hang tension, per-stage derating, aspect-ratio refusal, unrated inserts as a warning |
|
||||
| `report.py` | self-contained HTML; `build_many` puts every stage of one piece in one file behind a verdict table |
|
||||
| `layout.py` | recommended insert positions and the delta from the model |
|
||||
| `cli.py` | `panellift job.json -o report.html [--json]` |
|
||||
| `vibe/liftexport.py` | the Revit side of the job file, plus the subprocess |
|
||||
| `Panel Lift Check` | the button: select an assembly, confirm the picks, choose where the HTML goes |
|
||||
|
||||
### Next
|
||||
|
||||
1. Strain-compatibility `φMn` (upgrade of `capacity.py`'s approximate-`fps`
|
||||
columns, shipped 2026-08-31): strand power curve, mild steel and WWF layers
|
||||
— the input paths for both are still owed before this can be honest.
|
||||
2. Transverse equilibrium. `layout.py` reports how far a row's centroid sits
|
||||
off the width centreline, but a beam model cannot say what that costs. On a
|
||||
panel with large openings the recommended rows can be forced entirely to
|
||||
one side, and whether the piece twists is currently the engineer's call
|
||||
with no number attached.
|
||||
3. Per-piece config overrides (sling angle, lift mode) without editing the
|
||||
plant knowledge file.
|
||||
|
||||
### Answered against the live model (sample_job, 2026-08-18)
|
||||
|
||||
Probed on `PNL204` — a `PANEL_VERTICAL::08"`, design PNL1.0,
|
||||
13'-10" x 4'-0" x 8", 5,533 lb, 51 members.
|
||||
|
||||
**Panel-local frame.** `x` is the width, `y` is the thickness, `z` is the
|
||||
length. On PNL204: `x ∈ [-2.000, 2.000]`, `y ∈ [-0.667, 0.000]`,
|
||||
`z ∈ [0, 13.917]` ft. So the form face is `y = -0.667` and the exposed face
|
||||
`y = 0`.
|
||||
|
||||
**1. Blockouts are family instances, not `Opening` elements.** The document
|
||||
contains **zero** `Opening` instances. Voids are a family group under Generic
|
||||
Models:
|
||||
|
||||
| family | count |
|
||||
| --- | --- |
|
||||
| `VOID_RECTANGULAR` | 622 |
|
||||
| `VOID_RECESS_3_SIDES` | 234 |
|
||||
| `VOID_POUR_STRIP` | 102 |
|
||||
| `VOID_DT_DAP` | 54 |
|
||||
| `VOID_ROUND` | 46 |
|
||||
| `V-PLATE_VOID_RECTANGULAR` | 22 |
|
||||
| `VOID_TAPER_ROUND` | 17 |
|
||||
|
||||
`STEM_BLOCKOUT` (2694) is under Specialty Equipment and belongs to double-tee
|
||||
stems, not wall panels. So `b_eff(x)` gets populated the same way the lifters
|
||||
do: instance position transformed to panel-local, size from instance
|
||||
parameters. Whether these families actually cut the solid, or only mark the
|
||||
void for the detailer, still needs checking — it decides whether the geometric
|
||||
weight reconciliation will agree.
|
||||
|
||||
**2. Insert role separates cleanly by geometry.** On PNL204:
|
||||
|
||||
| family | count | x | y | z |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| `ANCHOR_FACE_4T` | 4 | ±1.000 | **-0.667** (form face) | 2.583, 11.417 |
|
||||
| `LIFT_EDGE_4T` | 2 | ±1.000 | **-0.333** (mid-thickness) | **13.917** (top end) |
|
||||
|
||||
Face-mounted and inboard → **stripping**. Mid-thickness and at an end →
|
||||
**erection**, exactly as suspected of the `LIFT_EDGE_8T` seen at 0'-0".
|
||||
Two independent signals agree, family prefix and position, so the rule can
|
||||
check itself rather than trusting a name list.
|
||||
|
||||
The real pick is 4 points on 2 support lines at `z/L` = 0.186 and 0.180,
|
||||
against the PCI optimum of 0.207 — a good first live test case.
|
||||
|
||||
**Capacity is encoded in the family name**: `ANCHOR_FACE_4T` → 4T, `LIFT_EDGE_4T`
|
||||
→ 4T, `LIFT_EDGE_8T` → 8T. That makes `knowledge.yaml`'s `capacity_lb`
|
||||
table largely derivable. **Open question for the engineer: is `4T` the working
|
||||
load limit or the ultimate?** It is a factor of four on every insert check.
|
||||
|
||||
**3. Strand positions are recoverable, through-thickness included.** Four
|
||||
`1-2_STRAND` at `y = -0.500` and `y = -0.167` — two layers, 2 in.
|
||||
from each face of the 8 in. panel. That `y` is precisely the depth strain
|
||||
compatibility needs. `WWF_MESH_A` sits in the same two layers, confirming
|
||||
`per_face` in the spec schema.
|
||||
|
||||
Unresolved: the four strands sit at only two `x` stations (-1.500 and 0.000)
|
||||
on a panel spanning -2.000 to 2.000, which is not symmetric. Either the family
|
||||
insertion point is off-centre or `_point_of` is returning a curve start.
|
||||
Worth pinning before trusting a lever arm.
|
||||
|
||||
**4. Panels are boxes, and there are only two thicknesses.** PNL204's local
|
||||
bounding box is a clean 4.000 x 0.667 x 13.917. The whole model uses only
|
||||
`PANEL_VERTICAL` and `PANEL_HORIZONTAL`, each in `08"` and `12"`.
|
||||
|
||||
One discrepancy to carry: the bounding box gives 13.917 ft = 13'-11" while
|
||||
the panel's stated length parameter says 13'-10". Use the stated dimensions
|
||||
for the calculation and report the delta, the same way a ticket's printed
|
||||
length runs over the modelled one on stemmed pieces.
|
||||
|
||||
### Smaller known gaps
|
||||
|
||||
- Three support lines with equal gaps on both sides make the cable grouping
|
||||
ambiguous; currently resolved to the first. Should warn.
|
||||
- Partial-depth blockouts are carried but do not reduce `S`.
|
||||
- Inclined slings: the horizontal component is reported, but the secondary
|
||||
moment it induces is not analysed. LECWall does not either, and says so.
|
||||
|
||||
---
|
||||
|
||||
## Insulated (sandwich) panels
|
||||
|
||||
Added 2026-08-31. Read this before touching `sandwich.py` or the refusal in
|
||||
`cli._refuse_sandwich`.
|
||||
|
||||
### Why it is a refusal first and mechanics second
|
||||
|
||||
Every section property in this package is built on **one prism** of concrete
|
||||
`panel.thickness_in` deep — `section.NetSection`, `section_modulus_in3`, the
|
||||
four-corner stress. An insulated body is two concrete wythes with insulation
|
||||
between them, and idealising it as solid concrete at the envelope depth
|
||||
**overstates the section modulus and understates the tensile stress**. The
|
||||
owner, 2026-08-31:
|
||||
|
||||
> "把夹芯板当实心算并不 conservative。因为 insulation 并不会 100% composite,
|
||||
> 你忽视了中间的 composite ratio 的计算和可能出现的问题。这是一个 insulated
|
||||
> panel design 的时候非常大的一个问题。"
|
||||
|
||||
On the dimensions of a real 16 in. piece (4 in. architectural wythe, 4 in.
|
||||
EPS, 8 in. structural wythe, 64 in. of clear width) the arithmetic is:
|
||||
|
||||
| | I, in⁴ | governing S, in³ |
|
||||
| --- | --- | --- |
|
||||
| solid at the 16 in. envelope | 21,845 | 2,731 |
|
||||
| fully composite | 20,139 | 2,324 |
|
||||
| non-composite | 3,072 | 768 |
|
||||
| structural wythe alone | 2,731 | 683 |
|
||||
|
||||
So the solid idealisation overstates the governing section modulus by
|
||||
**3.56× against the non-composite bound and still 1.18× against the fully
|
||||
composite one**. Both above 1.0: there is **no reading of a sandwich under
|
||||
which treating it as solid is the safe direction**, because a sandwich has a
|
||||
hole in the middle of its depth and is weaker than a solid piece of the same
|
||||
envelope however well the connectors work. `test_sandwich.py` keeps every one
|
||||
of those numbers with the arithmetic in a comment.
|
||||
|
||||
**Until 2026-08-31 this never fired.** The weight gate refused every insulated
|
||||
piece for an unrelated reason — the solid sum was one wythe — and that refusal
|
||||
*accidentally* protected against this. The weight is right now, so the gate
|
||||
opens, and it opens onto a wrong section. `cli._refuse_sandwich` is what
|
||||
closes that window, and it is deliberately **upstream-independent**: it does
|
||||
not care whether the weight gate let the piece through. If a sandwich job file
|
||||
reaches this package at all, it fires.
|
||||
|
||||
### The detection signal travels in the job file
|
||||
|
||||
`source.solid_parts` — the per-solid breakdown the Revit side has written
|
||||
since 2026-08-31. Nothing on this side re-derives it: the file is the
|
||||
boundary, and a second opinion about a model this process cannot see would be
|
||||
a guess with a different accent. The ladder is in `sandwich.detect` and the
|
||||
only rung that may return `False` is *one concrete solid and nothing left
|
||||
out*. No signal at all returns **None**, never `False`.
|
||||
|
||||
### The conventions chosen, and why each is the conservative one
|
||||
|
||||
Every one of these is stated on the number it touches, in the report and in
|
||||
`sandwich.py`, and every one is on the owner-question list below.
|
||||
|
||||
| decision | what was chosen | why it is the conservative reading |
|
||||
| --- | --- | --- |
|
||||
| **wythe load sharing, non-composite bound** | the wythes are tied by the connectors so they deflect together; each takes the moment in proportion to its own stiffness, `M_i = M·I_i/ΣI`, so `f = M·(t_max/2)/ΣI` | it is the compatible answer, and the extreme fibre lands on the **thickest** wythe rather than being averaged over both. The alternative — sharing by mass — puts less moment on the stiff wythe than compatibility does |
|
||||
| **is the face wythe structural** | reported **both ways**. The non-composite bound assumes both wythes resist; `structural_wythe_alone` assumes the thickest carries the whole piece and the thinner is dead load | the second is the smallest section modulus of the three (683 in³ against 768), so printing it is the conservative half and printing only it would hide the bound the owner may actually want |
|
||||
| **which wythe is structural** | the **thickest**, by trade convention | not measured, and it is named as not measured every time it is used. There is no conservative direction available here, only a disclosed one |
|
||||
| **openings** | assumed to run through **both** wythes, so `b_eff(x)` is shared | the measured plan areas of the two wythes on the real piece differ by ~1%, which is inside the rounding on the volumes. If they diverge on some piece, each wythe needs its own opening set |
|
||||
| **insulation** | contributes **nothing** to the section | its modulus is three orders below concrete's. This is the standard treatment, not an approximation invented here |
|
||||
| **modular ratio** | 1, no transformation | both wythes are the same mix on every piece measured. Where the densities differ the report says so rather than quietly scaling one |
|
||||
| **connector demand distribution** | the mean over a row, at the fully-composite `VQ/I`, **named as a mean and not as a bound** | it is honest about being an assumption. It is *not* conservative — see below — and saying so is the only available treatment until `k` exists |
|
||||
|
||||
### The bounds are scaffolding, not the product
|
||||
|
||||
The owner's instruction, and it reshapes what this is for. The two-bound
|
||||
envelope is what can be computed **while the connector data is absent**. The
|
||||
partial-composite middle is the thing insulated-panel design is actually
|
||||
about, and the capacities and stiffnesses being hunted are the *inputs to
|
||||
computing it*, not garnish. The report presents the bounds as an interim
|
||||
envelope with the missing computation named.
|
||||
|
||||
### 可能出现的问题 — what the two bounds do NOT bracket
|
||||
|
||||
`sandwich.concerns()` is the enumeration, and reading it is the point.
|
||||
**Three quantities are bracketed. Everything else about a sandwich panel is
|
||||
not a bounds question at all**, and a reader who takes a two-bound report as
|
||||
an envelope over all of it is the failure the list exists to prevent.
|
||||
|
||||
Bracketed:
|
||||
|
||||
- **wythe flexural tension** — maximal non-composite, monotone in between.
|
||||
With one trap: the **inner face of a wythe changes sign** between the
|
||||
bounds (compression non-composite, tension fully composite), so both faces
|
||||
of both wythes have to be evaluated, not only the envelope faces.
|
||||
- **interface shear flow** — maximal fully composite, `q = VQ/I`, zero
|
||||
non-composite. A true upper bound on the **total** interface force, which
|
||||
is why it is computed even where the plant designs the panel non-composite.
|
||||
- **deflection** — maximal non-composite. Differential bowing from
|
||||
temperature and shrinkage is a service problem, not a handling one, and is
|
||||
**declared out of scope** rather than silently omitted.
|
||||
|
||||
Not bracketed:
|
||||
|
||||
- **connector force distribution along the span.** Both bounds are
|
||||
distribution-free — one puts zero on every connector, the other spreads
|
||||
demand smoothly as `V(x)`. Real partial interaction concentrates slip, and
|
||||
therefore connector force, **at the ends of each shear span**, decaying
|
||||
over a length set by the connector stiffness `k` against the wythes' axial
|
||||
stiffness `EA`. A uniform-demand assumption **understates the end
|
||||
connectors**. When `k` arrives, the standard partial-interaction treatment
|
||||
is what goes in here; until then the mean is reported and named as a mean.
|
||||
- **connector force at discontinuities** — an opening edge or a pick point
|
||||
restarts the slip profile and the connectors beside it see a local peak
|
||||
neither bound shows.
|
||||
- **connector NORMAL (tension) demand.** A flat pick hangs one wythe off the
|
||||
other through the connectors in direct tension; an erection pick peels the
|
||||
face wythe as the piece rotates. Not an interface-shear question at all, so
|
||||
**neither bound speaks to it** — and where the panel is designed
|
||||
non-composite it is likely the *governing* connector demand. This is why a
|
||||
capacity is needed **per failure mode**, not as one number.
|
||||
- **local flexure of the face wythe between connectors** — a slab strip on
|
||||
discrete supports at the connector spacing. A different mechanism from
|
||||
anything in this package.
|
||||
- **percent composite is not one number.** The **stiffness** ratio
|
||||
(`κ_I = (I_eff − I_nc)/(I_full − I_nc)`, what governs deflection and
|
||||
bowing) and the **strength** ratio (`η`, the degree of shear connection,
|
||||
what governs capacity) are different quantities in the trade's own methods.
|
||||
Any percentage this report prints says which one it is.
|
||||
|
||||
### What stands down, and what unblocks it
|
||||
|
||||
| stood down | why | unblocked by |
|
||||
| --- | --- | --- |
|
||||
| **composite ratio** (both of them) | a computed ratio without connector data is not hard, it is *impossible*. The width of the band is printed; the position in it is not | connector slip stiffness `k`, lb/in per connector (stiffness ratio); connector capacity per failure mode (strength ratio) |
|
||||
| **connector sufficiency** | no capacity is published in any layer. `verdict` stays `None` — never OK, never a pass | capacity **per failure mode**: interface shear **and** normal/tension, lb per connector |
|
||||
| **connector recognition** on the gather side | nothing reads the connector instances off the piece. `source.connectors` is written with every field null and the reason beside it, because a *missing* key reads downstream as "this piece has no connectors" | a recognition rule. Measured by the bridge holder, and it is plant knowledge so the parameter names live in the plant layer: identity is a **type-level** parameter, **not** the family name, and a second type-level parameter is the gate. Family-name matching silently finds nothing here — a decoy family with no instances carries the same identity string |
|
||||
| **the layup on an old job file** | `solid_parts` without `z0_in`/`z1_in` says a sandwich exists and cannot say what its section is. `Layup.measured` is False and `section_bounds` refuses rather than inventing a layup | re-export the piece |
|
||||
|
||||
### The plant calls its pins non-composite, and that settles nothing
|
||||
|
||||
The insulation family on these pieces carries the description *"EPS with Holes
|
||||
for Non-composite pins"*. That is **evidence about design intent, carried
|
||||
verbatim in `source.connectors.declared_action` and quoted in the report**.
|
||||
It is not a structural ruling, and nothing in the mechanics branches on it:
|
||||
partial composite action exists physically whatever the label says, and the
|
||||
connector product has published stiffness data. What the owner's confirmation
|
||||
would settle is **which bound the pass/fail line is drawn against** — the
|
||||
report carries both either way.
|
||||
|
||||
### Which end is up, and why a disclosed fallback is allowed
|
||||
|
||||
`knowledge.up_end_from_origin` is this project's flagship declared-coverage
|
||||
refusal — three measured conditions, and it never returns `False`. When it
|
||||
declines, `liftexport.erected_orientation` reads global elevation and answers
|
||||
anyway, writing the refusal's own reason into `source.up_end_basis_why` —
|
||||
and **nothing read that key**, partly because `read_panel` looked for
|
||||
`source` inside `panel` while the exporter writes it at job level, so
|
||||
`panel.source` came out empty on every real job file.
|
||||
|
||||
Both halves are fixed. The job-level `source` is merged onto the panel, and
|
||||
`report.up_end_disclosure` renders the fallback on the page, in the CLI's
|
||||
stdout and in the summary's `basis` key — written **only** when the ruled
|
||||
mechanism did not answer, the same "only when it is not the ordinary case"
|
||||
rule `searched` and `enabled` follow.
|
||||
|
||||
The deliberate decision, because "never fall back" is not the answer either:
|
||||
|
||||
- The rule declines on a **vertical wall panel** because it is only confirmed
|
||||
for horizontal ones. Global elevation is the mechanism that is *sound* for
|
||||
a piece erected on end — its two ends really are at different heights — so
|
||||
on that decline the fallback is the right tool for the case the rule handed
|
||||
over, not a guess.
|
||||
- It is meaningless for a piece erected long-ways, and that case never
|
||||
reaches here: `MIN_VERTICAL_SHARE` refuses any piece with no axis
|
||||
convincingly up, and the aspect-ratio gate refuses a near-square one.
|
||||
- **It reaches one stage.** Stripping and transportation are analysed flat
|
||||
and are invariant under mirroring `x` — reverse the piece end for end and
|
||||
the beam problem is its own mirror image. Only erection depends on it:
|
||||
which row `exclude_top_rows` drops, and which edge the anchors are on.
|
||||
|
||||
So the fallback answers, and it is stated on every report it touched, naming
|
||||
the rule's own reason for standing down. `default_stages` gets the same
|
||||
treatment one step further on: an absent `source.erected_vertical_axis` used
|
||||
to be read as `or 'length'`, and now says so in `problems`.
|
||||
|
||||
### Owner questions
|
||||
|
||||
Nothing below is inferred and nothing below is guessed at. Numbered so they
|
||||
can be answered out of order.
|
||||
|
||||
1. **Are the insulated panels designed, and to be checked, as
|
||||
non-composite?** The plant's own family description says the pins are
|
||||
non-composite. Confirming it picks which bound the pass/fail line is drawn
|
||||
against; the report carries both either way.
|
||||
2. **Which wythe is structural?** The thickest is assumed, by trade
|
||||
convention, and named as an assumption every time. A ruling makes it a
|
||||
fact.
|
||||
3. **Is the architectural wythe to be relied on in flexure at all**, or
|
||||
carried by the connectors and resisting nothing? The two readings differ
|
||||
by 768 in³ against 683 in³ on the reference piece.
|
||||
4. **What depth should the beam use once the refusal is lifted** — the
|
||||
envelope, one wythe, or a bounded pair? This is the question
|
||||
`docs/lift-check.md` has been carrying as open since 2026-08-31, and the
|
||||
bounds are now computed for all three so it can be answered against
|
||||
numbers.
|
||||
5. **Connector capacity per failure mode** — interface shear, and
|
||||
normal/tension — in lb per connector, and the concrete strength the
|
||||
ratings were derived at.
|
||||
6. **Connector slip stiffness `k`**, lb/in per connector. Without it there is
|
||||
no ratio and no end-concentration factor, and both stay stood down.
|
||||
7. **Do the openings run through both wythes on every piece?** Assumed yes;
|
||||
the measured plan areas on the reference piece agree to ~1%, which is
|
||||
inside the rounding.
|
||||
8. **Is the local thickness axis' minimum face the form face?** The layup is
|
||||
measured from the lowest face of any solid the body draws, which is the
|
||||
same convention the strand and insert `z_in` already use, and which face
|
||||
that is has never been established. A symmetric layup is the same either
|
||||
way; an asymmetric one may be the mirror of what was cast.
|
||||
Reference in New Issue
Block a user