# -*- 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