# -*- coding: utf-8 -*-
"""The numbers behind the curves, station by station.
Eriksson Wall prints, after each stage's plots, a summary table of the same
quantities at engineering stations with the governing row bolded. This is our
version of it, and these are the properties that make it worth printing:
1. It carries the columns we actually compute, in the units the engineer
states them in -- ft, kip, kip-ft, psi -- and does NOT carry the ones we
don't. A zero in a capacity column is worse than no column at all,
because somebody would divide by it. Six capacity columns landed
2026-08-31 (M_cr, phi, phi-Mn, M/phi-Mn, phi-Vn, V/phi-Vn); the rest are
still named as absent in the footnote, and a station whose capacity
cannot be computed prints a dash and a REASON -- never a zero, never the
neighbouring station's number.
2. Its rows are the analysis's own stations. Thinning may drop a station
that is only there because the grid is uniform; it may never drop an
opening edge, a pick line, a peak, or the governing station.
3. The governing row is marked once. Not zero times, and not twice --
"which row is the answer" has exactly one answer.
4. It is closed on load like every other section, and it survives the
print overrides, because a printed report that hides its numbers is a
report that says nothing.
"""
import re
from pathlib import Path
import pytest
from conftest import aspire_pockets, make_panel
from panellift import checks, layout, report, statics
from panellift.model import Blockout, Stage
from panellift.units import parse_length
def _analyse(panel, stage, rigging):
return statics.analyse(panel, stage, rigging)
def _table_of(page, after='Station table'):
"""The one
that follows this heading."""
i = page.index(after)
j = page.index('', j) + 8]
def _rows(table):
return re.findall(r']*>(.*?)
', table[table.index(''):])
def _cells(row):
return [re.sub(r'<[^>]+>', '', c)
for c in re.findall(r']*>(.*?) | ', row)]
# ------------------------------------------------ 1. the columns, converted
def test_the_columns_are_the_ones_we_compute_in_display_units(stripping,
rolling):
a = _analyse(make_panel(blockouts=aspire_pockets()), stripping, rolling)
table = _table_of(report.station_table(a), ']+>', '|', table[:table.index('')])
for column in ('x, ft', 'V, kip', 'M, kip-ft', 'S, in', 'allow, psi',
'/allow', 'note'):
assert column in head, column
assert head.count(', psi') == 3 # f_t, f_c, allow
def test_the_capacity_columns_are_carried_and_the_rest_are_named_absent(
stripping, rolling):
"""The load-bearing honesty of the whole table, in its 2026-08-31 form.
Six capacity columns are computed and carried. The ones that are still
not computed are NAMED in the footnote rather than being quietly missing,
and the sentence that forbids zero-filling them survives.
"""
a = _analyse(make_panel(), stripping, rolling)
html = report.station_table(a)
table = _table_of(html, '')]
for carried in ('Mcr', 'φMn',
'M/φMn', 'φVn',
'V/φVn', 'φ'):
assert carried in head, carried
footnote = html[html.index(''):]
for still_absent in ('Vci', 'Vcw', 'deflection',
'Av'):
assert still_absent in footnote, still_absent
assert 'left out rather than filled with zeros' in footnote
# and the footnote does not claim the new ones are missing any more
assert 'This revision computes none of those' not in footnote
def test_every_value_is_the_engines_value_converted_once(stripping, rolling):
"""A row's V and M are diagram.shear_lb / moment_lbft at that station
over LB_PER_KIP -- not a re-solve, not an interpolation."""
a = _analyse(make_panel(blockouts=aspire_pockets()), stripping, rolling)
points = dict((p.x, p) for p in checks.stress_profile(a))
table = _table_of(report.station_table(a), '= 2
table = _table_of(report.station_table(a), '') == 1
assert table.count('governing') == 1
gov = [r for r in _rows(table) if 'gov' in r][0]
assert 'governing' in gov
def test_the_governing_row_is_the_worst_ratio_on_the_page(stripping, rolling):
"""It has to be the same station the verdict, the red dot on the stress
plot and the CRACK finding all point at."""
a = _analyse(make_panel(blockouts=aspire_pockets()), stripping, rolling)
points = checks.stress_profile(a)
worst = max(points, key=lambda p: p.stress_psi)
table = _table_of(report.station_table(a, points), '') == 2
assert page.count('>Station table') == 2
# ------------------------------------------- 4. collapsed, and it prints
def test_the_section_is_closed_on_load_like_every_other(stripping, rolling):
a = _analyse(make_panel(), stripping, rolling)
page = report.build(a, rolling, checks.run_all(a, rolling),
recommendation=False)
assert ('Station table'
'
') in page
assert ' is hidden on screen by two different mechanisms in
two generations of engine, and the print block overrides both. The table
must be inside that, and its scroll box must open up on paper."""
a = _analyse(make_panel(), stripping, rolling)
page = report.build(a, rolling, checks.run_all(a, rolling),
recommendation=False)
printed = page[page.index('@media print'):]
printed = printed[:printed.index('')]
assert 'details>*:not(summary){display:block !important}' in printed
assert 'details::details-content' in printed
assert '.tablewrap{overflow:visible !important}' in printed
assert 'table.stations thead{display:table-header-group}' in printed
assert 'details.stationsec{break-inside:auto}' in printed
# and the table is inside a details, so those overrides reach it
assert '' in page
without = page[:page.index('