WillTools 2026-09-09 -- clean snapshot of the development repo for review
ci / test (push) Successful in 6m18s

History-free snapshot of the private development repository at cbe45c5
(2026-09-09). Code, docs and public catalogue only; no plant or job
knowledge layers, no client or job names (tools/scrub.py clean). The
intro deck is left out of this snapshot because its slide images could
not be checked for job drawings.
This commit is contained in:
2026-09-09 08:32:15 -05:00
commit 8e6ee31596
406 changed files with 223435 additions and 0 deletions
+244
View File
@@ -0,0 +1,244 @@
# -*- coding: utf-8 -*-
"""tension_fos / shear_fos: a number that had no source, and its removal.
They divide a lifter's rated capacity -- `shear_fos` when the lifter sits on
an edge, `tension_fos` otherwise -- and only when `capacity_is_ultimate` is
true, i.e. only where the catalogue quotes an ultimate rather than a working
load. Nothing in this repository sets that flag, so the arithmetic is not
reached on any shipped path; it is reachable, it is public API, and what it
would have computed was wrong.
WHAT WAS WRONG. `shear_fos` was 2.67, as a `Rigging` default, as a
`read_rigging` default, as a `catalogue.safety_factors` entry and as what
`liftexport` wrote into every job file. No page published it, nobody typed
it, and the comment justifying it as "the manufacturer's 4:1 tension /
2.67:1 shear" was written after the constant and 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" over BOTH
load columns and prints shear equal to tension on all seven QLPD rows.
WHAT IT WOULD HAVE DONE. 32,000 lb ultimate / 2.67 = 11,985 lb allowed,
against a published SWL of 8,000. Half again as much load on an anchor as
the manufacturer permits, in the direction that passes one which would fail.
SO THE GLOBAL IS GONE, and it was not replaced by a global 4.0 -- that is
the same defect wearing the right number, because the next manufacturer's
insert would inherit a factor read off ALP's page. The factors are per
product in the catalogue and per insert in `Lifter`, and an insert with none
is NOT JUDGED: `INSERT-NO-FOS`, named, never defaulted.
"""
import re
from pathlib import Path
import pytest
from panellift import checks, cli, statics
from panellift.model import Lifter, Rigging, Stage
from conftest import make_panel
CATALOGUE = (Path(__file__).resolve().parents[2]
/ 'knowledge' / 'catalogue.yaml')
# This is the one file in this suite that is NOT self-contained -- it pins
# published catalogue rows against the real, tracked `knowledge/catalogue.yaml`
# rather than a fixture, on purpose (see the module docstring: this is a
# golden-value check against a page, not a recomputation). A knowledge-less
# distribution (tools/make_dist.py) ships no catalogue.yaml at all, so the
# question this file asks -- "does the catalogue publish X" -- has no
# catalogue to ask it of. SKIP, not FAIL: the same "could not judge" vs.
# "judged, found nothing" discipline the rest of this project uses, applied
# to a test file rather than a knowledge lookup.
pytestmark = pytest.mark.skipif(
not CATALOGUE.is_file(),
reason='knowledge/catalogue.yaml is not shipped in this checkout '
'(see knowledge/README.md) -- nothing to pin these golden values '
'against')
# The seven QuikLift disk-anchor rows of ALP catalog p53, 02/2024, typed in
# from the page: (part, tension SWL, shear SWL, ultimate tension).
# A pin against a written-down golden, not a recomputation.
PAGE_53 = [
('QLPD2T3WG', 4000, 4000, 16000),
('QLPD4T3WG', 6257, 6257, 32000),
('QLPD4T4WG', 7200, 7200, 32000),
('QLPD4T5WG', 8000, 8000, 32000),
('QLPD8T6WG', 11019, 11019, 64000),
('QLPD8T7WG', 14640, 14640, 64000),
('QLPD8T8WG', 16000, 16000, 64000),
]
def catalogue_text():
return CATALOGUE.read_text(encoding='utf-8')
def entry_text(part):
"""One `catalogue.inserts` entry, as raw YAML text.
Read with a regex rather than a parser: this package has no YAML
dependency and is not going to grow one to check a handful of numbers.
"""
block = re.search(r'\n %s:\n((?: \S.*\n| .*\n)+)' % part,
catalogue_text())
assert block, 'catalogue.yaml has no entry for {0}'.format(part)
return block.group(1)
def field(part, name):
found = re.search(r'^\s+%s:\s*([-\d.]+)\s*$' % name, entry_text(part),
re.M)
return float(found.group(1)) if found else None
# ------------------------------------------------------------------ the page
@pytest.mark.parametrize('part,tension,shear,_ultimate', PAGE_53)
def test_the_catalogue_publishes_four_to_one_in_shear_for_every_qlpd_row(
part, tension, shear, _ultimate):
"""The page's own heading, landed as data on the part it belongs to."""
text = entry_text(part)
assert 'safety_factors:' in text, (
'{0} carries no safety_factors block, so an ultimate rating for it '
'has no published factor and it will be reported unjudged'
.format(part))
assert field(part, 'tension_fos') == 4.0
assert field(part, 'shear_fos') == 4.0, (
'ALP catalog p53 heads the capacity table "Capacity in Concrete 4:1 '
'SWL" across BOTH load columns, so shear is 4:1 here too. 2.67 is '
'the number this test exists to keep out.')
assert 'p53' in text and '02/2024' in text, (
'a safety factor with no page beside it is how 2.67 survived for '
'twelve days')
# ...and the working loads it is a factor on are still the page's.
assert field(part, 'working_load_lb') == tension
assert field(part, 'shear_load_lb') == shear
def test_no_global_safety_factor_survives_anywhere():
"""A tombstone. The failure was not the value 2.67 but the SHAPE that
let one number apply to every manufacturer's insert, so replacing it
with a global 4.0 would have been the same defect with a better number.
"""
block = re.search(r'\n safety_factors:\n((?: \w+:.*\n| .*\n)+)',
catalogue_text())
assert block, 'catalogue.yaml has no catalogue.safety_factors block'
assert re.search(r'^ tension_fos: null\s*$', block.group(1), re.M)
assert re.search(r'^ shear_fos: null\s*$', block.group(1), re.M)
assert Rigging().tension_fos is None
assert Rigging().shear_fos is None
assert cli.read_rigging({}).tension_fos is None
assert cli.read_rigging({}).shear_fos is None
assert Lifter(mark='m', family='f', x=0.0, y=0.0).shear_fos is None
# 2.67 may still be NAMED in the catalogue -- the comment that records
# what it was and why it went is the provenance, and deleting the number
# would leave the next reader to rediscover it from the same page. What
# may not come back is 2.67 as a VALUE.
live = re.findall(r'^\s*\w*fos:\s*2\.67\s*$', catalogue_text(), re.M)
assert not live, live
def test_the_ultimate_route_overshoots_the_published_swl_even_at_four_to_one():
"""Why `capacity_is_ultimate` stays false for these parts.
The page shades four of its seven rows and says the shading marks where
the anchor's own mechanical capacity governs -- so the printed SWL is
the LESSER of ultimate/4 and what the concrete holds. Reading an
ultimate through the right factor is still unconservative on those four,
which is a fact about the route and not about the factor, and it is here
so that nobody "fixes" the factor and then turns the flag on.
"""
over = [(part, ultimate / 4.0, tension)
for (part, tension, _shear, ultimate) in PAGE_53
if ultimate / 4.0 > tension + 0.5]
assert [p for (p, _a, _b) in over] == [
'QLPD4T3WG', 'QLPD4T4WG', 'QLPD8T6WG', 'QLPD8T7WG']
assert max(a / b for (_p, a, b) in over) > 1.4 # QLPD8T6WG, +45%
# ------------------------------------------------------- what the check does
def _panel_with(factor_kind, factor):
"""A rating and a factor on ONLY the axis under test.
2026-09-02: check_insert_capacity now decomposes the sling's line force
into a tension and a shear share and judges both, for every lifter --
`on_edge` no longer picks a single axis to check. These tests are about
factor resolution on one column, not about the decomposition, so the
OTHER column is left with no capacity at all (rated() returns None,
joining INSERT-UNRATED, never INSERT-NO-FOS) rather than a rating with
no factor of its own, which would otherwise also land in
INSERT-NO-FOS and be mistaken for the thing under test.
"""
panel = make_panel()
lifters = [
Lifter(mark=l.mark, family=l.family, x=l.x, y=l.y, role=l.role,
capacity_lb=32000.0 if factor_kind == 'tension' else None,
shear_capacity_lb=32000.0 if factor_kind == 'shear' else None,
on_edge=(factor_kind == 'shear'),
**{factor_kind + '_fos': factor})
for l in panel.lifters]
return dataclass_replace_lifters(panel, lifters)
def dataclass_replace_lifters(panel, lifters):
import dataclasses
return dataclasses.replace(panel, lifters=lifters)
def _findings(panel, stage, ultimate=True):
rigging = Rigging(mode='rolling', sling_angle_deg=60.0)
analysis = statics.analyse(panel, stage, rigging, reconcile_weight=False)
return checks.check_insert_capacity(analysis, rigging, ultimate)
def test_an_insert_with_no_published_factor_is_named_not_defaulted(stripping):
"""The whole point. An ultimate rating and no factor used to become an
allowed load through a constant; it now becomes a sentence."""
out = _findings(_panel_with('shear', None), stripping)
codes = [f.code for f in out]
assert 'INSERT-NO-FOS' in codes
note = [f for f in out if f.code == 'INSERT-NO-FOS'][0]
assert note.severity == 'WARN'
assert 'NOT judged' in note.message
assert 'borrowed' in note.message
# and no insert was judged behind its back
assert not [f for f in out if f.code == 'INSERT']
def test_the_page_factor_gives_exactly_the_published_working_load(stripping):
"""32,000 / 4 = 8,000, which is what p53 prints for QLPD4T5WG. The old
2.67 gave 11,985 for the same insert -- allowing nearly half again as
much shear as the manufacturer's own page. Both numbers appear in the
message (2026-09-02: on the SHEAR clause specifically, since the check
now decomposes the line force rather than comparing it whole), so this
compares them."""
with_page = _findings(_panel_with('shear', 4.0), stripping)
with_2_67 = _findings(_panel_with('shear', 2.67), stripping)
page_msg = ' '.join(f.message for f in with_page if f.code == 'INSERT')
old_msg = ' '.join(f.message for f in with_2_67 if f.code == 'INSERT')
assert 'shear' in page_msg
assert 'vs 8,000 lb' in page_msg or 'vs 8000 lb' in page_msg
assert page_msg != old_msg
assert '11,98' in old_msg or '11985' in old_msg
def test_the_insert_s_own_factor_beats_the_job_wide_one(stripping):
"""Resolution order, and the direction that matters: a job file naming
2.67 must not override what the part's datasheet publishes."""
panel = _panel_with('shear', 4.0)
rigging = Rigging(mode='rolling', sling_angle_deg=60.0, shear_fos=2.67)
analysis = statics.analyse(panel, stripping, rigging,
reconcile_weight=False)
out = checks.check_insert_capacity(analysis, rigging, True)
assert not [f for f in out if f.code == 'INSERT-NO-FOS']
msg = ' '.join(f.message for f in out if f.code == 'INSERT')
assert '11,98' not in msg and '11985' not in msg
def test_a_working_load_reading_never_touches_a_factor(stripping):
"""The default path, which is every shipped path: with the ratings read
as working loads the factors are not applied at all, so an insert with
none is judged normally and no INSERT-NO-FOS is raised."""
out = _findings(_panel_with('shear', None), stripping, ultimate=False)
assert not [f for f in out if f.code == 'INSERT-NO-FOS']
assert [f for f in out if f.code == 'INSERT']