Files
willtools-demo/WillTools.extension/tests/test_pa_reinforcement.py
T
will 52d94303bc
ci / test (push) Has been cancelled
WillTools: precast shop-ticket QC and panel handling tools
Public demo snapshot of an in-house pyRevit extension for precast concrete
shop-ticket QC (37 checks / 46 checklist rows) and a standalone panel
handling solver (PCI Chapter 8).

- No client, job, plant or product data: knowledge layers ship as
  structural templates only.
- Ticket QC's model-read escalation targets a LAN model server endpoint
  (configurable), not a cloud provider.
- Built from the private development repo via tools/make_dist.py.
2026-09-03 16:33:47 -05:00

279 lines
10 KiB
Python

# -*- coding: utf-8 -*-
"""Tests for `vibe.pa_reinforcement.page` -- the Reinforcement section of the
Panel Analysis input form.
Runs on plain CPython via `tests/conftest.py`'s import trick (no Revit, no
pyRevit, no WPF -- Revit is NOT running for this suite). Covers the doctrine
the module docstring commits to:
* only block kinds from the fixed vocabulary are ever emitted;
* every 'field' block carries a plain-English label and a hint;
* every optional sub-block (strands / rebar / mesh / interrupts) states an
empty-case sentence when it has nothing, never a blank;
* a missing `values['strand_rows']` degrades to the raw-text fallback with
a `problems` entry, rather than raising;
* no summary quantity (area, cgx, ex, ey) is ever invented;
* `values` is never mutated.
"""
import copy
from vibe import pa_reinforcement as par
from vibe import panelanalysisform as pf
_KNOWN_KINDS = set(['heading', 'columns', 'field', 'table', 'kv', 'sentence',
'drawing', 'rule', 'space'])
def _walk(blocks):
"""Yield every block, recursing into 'columns' children."""
for b in blocks:
yield b
if b.get('kind') == 'columns':
for col in b.get('columns') or []:
for sub in _walk(col):
yield sub
def _fields(blocks):
return [b for b in _walk(blocks) if b.get('kind') == 'field']
def _sentences(blocks):
return [b['text'] for b in _walk(blocks) if b.get('kind') == 'sentence']
# ------------------------------------------------------------------- shape
def test_only_known_block_kinds_empty_values():
blocks, problems = par.page({})
for b in _walk(blocks):
assert b.get('kind') in _KNOWN_KINDS, b
def test_only_known_block_kinds_filled_values():
values = {
'strand_rail_location': 'Left edge', 'strand_rail_offset_in': '0',
'ps_count': '4', 'ps_dia_in': '1/2', 'ps_pull_lb': '30000',
'ps_loss_pct': '8', 'ps_rows': '2 @ 2\n2 @ 6 @ 70',
'mild_as': '#4 @ 12" o.c.', 'mesh': '6x6 W1.4xW1.4',
}
blocks, problems = par.page(values)
for b in _walk(blocks):
assert b.get('kind') in _KNOWN_KINDS, b
def test_page_never_raises_on_garbage_values():
garbage = {'strand_rows': 'not a list', 'ps_rows': 12345,
'ps_dia_in': None, 'mild_as': object()}
blocks, problems = par.page(garbage)
assert isinstance(blocks, list)
assert isinstance(problems, list)
def test_page_never_raises_on_none_values():
blocks, problems = par.page(None)
assert isinstance(blocks, list)
# ------------------------------------------------------------- field labels
def test_every_field_has_plain_english_label_and_hint():
blocks, _ = par.page({'ps_rows': '2 @ 2'})
fields = _fields(blocks)
assert fields, 'expected at least one field block'
for f in fields:
assert f['label'] and isinstance(f['label'], str)
# A plain-English label is not just the raw key repeated back.
assert f['label'] != f['key']
assert f['hint'], 'field {0!r} has no hint'.format(f['key'])
def test_field_blocks_reference_real_panelanalysisform_keys():
blocks, problems = par.page({})
known_keys = set(f['key'] for f in pf.FIELDS)
for f in _fields(blocks):
assert f['key'] in known_keys, (
'{0!r} is not a real panelanalysisform key'.format(f['key']))
# ---------------------------------------------------------- empty-case text
def test_strands_blank_prints_no_prestressing_sentence():
blocks, _ = par.page({})
assert 'Design contains no prestressing' in _sentences(blocks)
def test_rebar_blank_prints_no_rebar_sentence():
blocks, _ = par.page({})
assert 'Design contains no rebar' in _sentences(blocks)
def test_mesh_blank_prints_no_mesh_sentence():
blocks, _ = par.page({})
assert 'Design contains no mesh' in _sentences(blocks)
def test_interrupts_always_could_not_judge_never_no_interrupts():
"""No field exists for interrupts, so this must never claim "none"."""
blocks, problems = par.page({})
sentences = _sentences(blocks)
assert any('could not judge' in s.lower() for s in sentences)
assert 'Design contains no interrupts' not in sentences
assert any('interrupts' in p.lower() for p in problems)
def test_rebar_filled_has_no_empty_sentence():
blocks, _ = par.page({'mild_as': '#4 @ 12" o.c. each face'})
assert 'Design contains no rebar' not in _sentences(blocks)
def test_mesh_filled_has_no_empty_sentence():
blocks, _ = par.page({'mesh': '6x6 W1.4xW1.4'})
assert 'Design contains no mesh' not in _sentences(blocks)
def test_strands_filled_has_no_empty_sentence_and_has_table():
blocks, _ = par.page({'ps_rows': '2 @ 2 @ 70', 'ps_dia_in': '1/2',
'ps_pull_lb': '30000'})
assert 'Design contains no prestressing' not in _sentences(blocks)
tables = [b for b in blocks if b['kind'] == 'table']
assert len(tables) == 1
assert tables[0]['rows']
# ------------------------------------------------------- strand_rows intake
def test_structured_strand_rows_used_when_present_no_fallback_problem():
values = {
'strand_rows': [{'count': 2, 'depth_in': 2.0, 'pull_pct': 70.0}],
'ps_dia_in': '1/2', 'ps_pull_lb': '30000',
}
blocks, problems = par.page(values)
table = [b for b in blocks if b['kind'] == 'table'][0]
assert table['rows'] == [['1/2', '0.153', '1/2', '2', '70%', '2"']]
assert not any('parsed the raw strand-group text' in p for p in problems)
def test_missing_strand_rows_falls_back_to_ps_rows_text_with_problem():
values = {'ps_rows': '2 @ 2 @ 70', 'ps_dia_in': '1/2',
'ps_pull_lb': '30000'}
blocks, problems = par.page(values)
table = [b for b in blocks if b['kind'] == 'table'][0]
assert table['rows'] and table['rows'][0][3] == '2'
assert any('ps_rows' in p for p in problems)
def test_missing_strand_rows_and_blank_ps_rows_is_quiet_no_prestressing():
"""Absent structured input AND blank text is a real "nothing typed yet"
answer, not an error -- must not appear in `problems`."""
blocks, problems = par.page({})
assert 'Design contains no prestressing' in _sentences(blocks)
assert not any('ps_rows' in p or 'strand_rows' in p for p in problems)
def test_malformed_strand_rows_text_reported_not_silently_dropped():
blocks, problems = par.page({'ps_rows': 'garbage line with no at-sign'})
assert any('could not' in p.lower() for p in problems)
assert 'Design contains no prestressing' in _sentences(blocks)
def test_malformed_structured_strand_rows_entry_skipped_with_problem():
values = {'strand_rows': [{'count': 'oops', 'depth_in': 2.0}]}
blocks, problems = par.page(values)
assert any('skipped it' in p for p in problems)
assert 'Design contains no prestressing' in _sentences(blocks)
# --------------------------------------------------------- never fabricated
def test_unknown_strand_size_refused_not_guessed():
values = {'ps_rows': '2 @ 2', 'ps_dia_in': 'made-up-size',
'ps_pull_lb': '30000'}
blocks, problems = par.page(values)
table = [b for b in blocks if b['kind'] == 'table'][0]
for row in table['rows']:
assert row[1] == '?' # area column never guessed
assert any('no section area is known' in p for p in problems)
kvs = [b for b in blocks if b['kind'] == 'kv']
assert not kvs # no Aps/cgy summary without an area
def test_strand_summary_never_prints_cgx_ex_ey_as_numbers():
values = {'ps_rows': '2 @ 2 @ 70\n2 @ 6', 'ps_dia_in': '1/2',
'ps_pull_lb': '30000'}
blocks, problems = par.page(values)
kv = [b for b in blocks if b['kind'] == 'kv'][0]
rows_by_label = dict(kv['rows'])
assert 'not shown' in rows_by_label['cgx, in']
assert 'not shown' in rows_by_label['ex, in']
assert 'not shown' in rows_by_label['ey, in']
# Aps and cgy ARE real derived numbers, not refused.
assert rows_by_label['Aps, in²'].replace('.', '').isdigit()
assert any('cgx' in p and 'not computed' in p for p in problems)
def test_aps_and_cgy_derived_correctly_from_entered_rows():
values = {'ps_rows': '2 @ 2\n2 @ 6', 'ps_dia_in': '1/2',
'ps_pull_lb': '30000'}
blocks, _ = par.page(values)
kv = [b for b in blocks if b['kind'] == 'kv'][0]
rows_by_label = dict(kv['rows'])
area = 0.153
expected_aps = 4 * area
expected_cgy = (2 * 2.0 + 2 * 6.0) / 4.0
assert abs(float(rows_by_label['Aps, in²']) - expected_aps) < 1e-6
assert abs(float(rows_by_label['cgy, in (depth to CG)']) -
expected_cgy) < 1e-6
def test_no_spacing_column_in_strand_table():
"""Horizontal strand spacing is not an input on this form -- the table
must not imply it is known."""
blocks, _ = par.page({'ps_rows': '2 @ 2', 'ps_dia_in': '1/2',
'ps_pull_lb': '30000'})
table = [b for b in blocks if b['kind'] == 'table'][0]
assert not any('spac' in h.lower() for h in table['headers'])
# --------------------------------------------------------------- no mutation
def test_values_dict_not_mutated():
values = {
'strand_rail_location': 'Left edge', 'strand_rail_offset_in': '0',
'ps_rows': '2 @ 2 @ 70', 'ps_dia_in': '1/2', 'ps_pull_lb': '30000',
'mild_as': '', 'mesh': '',
}
before = copy.deepcopy(values)
par.page(values)
assert values == before
def test_missing_field_key_reports_problem_not_crash(monkeypatch):
"""A field this module asks panelanalysisform for that no longer exists
there degrades to a MISSING problem, never a crash."""
real_spec = pf._spec
def _fake_spec(key):
if key == 'mesh':
return None
return real_spec(key)
monkeypatch.setattr(pf, '_spec', _fake_spec)
blocks, problems = par.page({})
assert any('mesh' in p and 'MISSING' in p for p in problems)
# The section still renders -- heading + empty sentence, just no field.
assert 'Design contains no mesh' in _sentences(blocks)
# -------------------------------------------------------------- drawing tag
def test_drawing_block_names_section_source_never_calls_panelsketch():
blocks, _ = par.page({})
drawings = [b for b in blocks if b['kind'] == 'drawing']
assert len(drawings) == 1
assert drawings[0]['source'] == 'section'
assert drawings[0]['caption']
assert isinstance(drawings[0]['w'], int)
assert isinstance(drawings[0]['h'], int)