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,133 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""The panelanalysis/1 In-Place SERVICE section (docs sec.6) + the P-Delta note.
|
||||
|
||||
Host CPython, loaded exactly like its siblings: reaches the tool through
|
||||
`tools/` with the `vibe` stub in place. A wired deck (restraints + wind +
|
||||
service inputs) must render the In-Place Service Summary -- tension/cracking,
|
||||
compression, deflection -- and the strength section must carry the P-Delta
|
||||
(2nd-order) magnifier. The could-not-judge discipline is enforced: a deflection
|
||||
with no L/n limit is NOT JUDGED, named, never a fabricated pass; nothing on the
|
||||
page is CJK; nothing is a traceback.
|
||||
|
||||
Everything written at runtime goes under tmp_path.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import io
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
import pytest
|
||||
|
||||
ROOT = os.path.dirname(os.path.dirname(os.path.dirname(
|
||||
os.path.abspath(__file__))))
|
||||
TOOLS = os.path.join(ROOT, 'tools')
|
||||
PANELLIFT_SRC = os.path.join(ROOT, 'panellift', 'src')
|
||||
for p in (TOOLS, PANELLIFT_SRC):
|
||||
if p not in sys.path:
|
||||
sys.path.insert(0, p)
|
||||
|
||||
from vibe import knowledge # noqa: E402,F401 (registers the vibe stub)
|
||||
from vibe import plantpath # noqa: E402,F401
|
||||
|
||||
pa = pytest.importorskip('panelanalysis')
|
||||
schema = pytest.importorskip('panelanalysis_schema')
|
||||
|
||||
CJK = re.compile(u'[一-鿿]')
|
||||
|
||||
|
||||
def _wired_deck(deflection_limit_ratio=150.0, wind=True, class_t=False):
|
||||
"""A mild solid wall pinned top and bottom, with a service wind pressure."""
|
||||
deck = schema.new_deck(length_ft=30.0, width_ft=10.0, thickness_in=12.0,
|
||||
fc_psi=6000.0, strands=[])
|
||||
deck['structural_model']['restraints'] = [
|
||||
{'x_ft': 0.0, 'type': 'pin'}, {'x_ft': 30.0, 'type': 'pin'}]
|
||||
if wind:
|
||||
deck['loading']['wind'] = {'zones': [
|
||||
{'x0_ft': 0.0, 'x1_ft': 30.0, 'pressure_psf': 50.0, 'label': 'W1'}]}
|
||||
svc = {}
|
||||
if deflection_limit_ratio is not None:
|
||||
svc['deflection_limit_ratio'] = deflection_limit_ratio
|
||||
if class_t:
|
||||
svc['cracking_class_t'] = True
|
||||
if svc:
|
||||
deck['loading']['service'] = svc
|
||||
return deck
|
||||
|
||||
|
||||
# ------------------------------------------------------------ the run itself
|
||||
|
||||
def test_wired_deck_produces_a_service_result():
|
||||
deck = _wired_deck()
|
||||
panel = schema.to_panel(deck)
|
||||
result, reason = pa.solve_in_place(deck, panel)
|
||||
assert reason is None and result is not None and not result.refusal
|
||||
svc = result.service
|
||||
assert svc is not None and svc.stations
|
||||
# a wind-loaded wall develops real service tension and compression
|
||||
assert svc.max_tension_psi > 0.0
|
||||
assert svc.max_compression_psi > 0.0
|
||||
# cracking limit is the Class U 7.5 sqrt(6000) = 580.9 psi
|
||||
assert abs(svc.stations[0].cracking_limit_psi - 580.9) < 0.5
|
||||
# deflection judged against L/150 = 2.4 in
|
||||
d = svc.deflection
|
||||
assert d.combo == 'SC5' and d.util is not None
|
||||
assert abs(d.limit_in - 30.0 * 12.0 / 150.0) < 1e-6
|
||||
|
||||
|
||||
def test_class_t_deck_raises_the_cracking_limit():
|
||||
deck = _wired_deck(class_t=True)
|
||||
panel = schema.to_panel(deck)
|
||||
result, _reason = pa.solve_in_place(deck, panel)
|
||||
import math
|
||||
assert abs(result.service.stations[0].cracking_limit_psi
|
||||
- 12.0 * math.sqrt(6000.0)) < 0.5
|
||||
|
||||
|
||||
# ------------------------------------------------------------- the rendering
|
||||
|
||||
def test_page_renders_the_service_summary_and_pdelta():
|
||||
deck = _wired_deck()
|
||||
html = pa.build_analysis_page(deck)
|
||||
assert '>In-Place Service Summary<' in html
|
||||
for needle in ('Tension vs cracking', 'Total compression', 'Deflection',
|
||||
'Cracking (psi)', 'Combination'):
|
||||
assert needle in html, needle
|
||||
# the P-Delta 2nd-order magnifier appears in the strength section
|
||||
assert 'P-Δ (2nd-order' in html
|
||||
assert 'magnifier' in html
|
||||
assert 'Traceback' not in html
|
||||
m = CJK.search(html)
|
||||
assert not m, 'CJK: {0!r}'.format(m.group() if m else '')
|
||||
|
||||
|
||||
def test_deflection_without_limit_is_not_judged_not_faked():
|
||||
deck = _wired_deck(deflection_limit_ratio=None)
|
||||
html = pa.build_analysis_page(deck)
|
||||
assert '>In-Place Service Summary<' in html
|
||||
# the deflection is computed, but the LIMIT check is named NOT JUDGED
|
||||
assert 'Deflection limit NOT' in html or 'NOT JUDGED' in html
|
||||
result, _r = pa.solve_in_place(deck, schema.to_panel(deck))
|
||||
assert result.service.deflection.util is None
|
||||
assert result.service.deflection.why
|
||||
|
||||
|
||||
def test_unwired_deck_keeps_the_explicit_service_placeholder():
|
||||
deck = schema.new_deck(support_xs=(2.0, 22.0)) # no restraints
|
||||
html = pa.build_analysis_page(deck)
|
||||
assert '>In-Place Service (not computed)<' in html
|
||||
assert 'Traceback' not in html
|
||||
|
||||
|
||||
def test_cli_end_to_end_renders_service_under_tmp_path(tmp_path):
|
||||
deck = _wired_deck()
|
||||
deck_path = tmp_path / 'PNL101.pa.json'
|
||||
out_path = tmp_path / 'out.html'
|
||||
with io.open(str(deck_path), 'w', encoding='utf-8') as fh:
|
||||
json.dump(deck, fh)
|
||||
rc = pa.main([str(deck_path), '-o', str(out_path)])
|
||||
assert rc == 0 and out_path.exists()
|
||||
html = out_path.read_text(encoding='utf-8')
|
||||
assert 'In-Place Service Summary' in html
|
||||
Reference in New Issue
Block a user