8e6ee31596
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.
369 lines
14 KiB
Python
369 lines
14 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""Host tests for `vibe.pa_defaults` -- the aggressive Panel Analysis
|
|
defaults pass. See that module's docstring for the owner's instruction this
|
|
implements (2026-09-04: press the button, type the loading, everything else
|
|
already has a number) and for what deliberately stays blank.
|
|
|
|
Runs on plain CPython via `tests/conftest.py`'s `vibe` stub -- no Revit, no
|
|
WPF. `pa_defaults` imports `panelanalysisform` / `pa_criteria` /
|
|
`pa_structural` lazily and defensively, so these tests exercise the real
|
|
field tables those modules carry today, not a copy of them.
|
|
"""
|
|
|
|
from vibe import pa_defaults as D
|
|
|
|
|
|
# --------------------------------------------------------------- unknown_keys
|
|
|
|
def test_unknown_keys_is_empty():
|
|
"""Every key this module defaults must already exist somewhere in the
|
|
live form (panelanalysisform.FIELDS, the stage matrix, pa_criteria._F,
|
|
or a real pa_structural.page() field block). A non-empty list here is
|
|
the sibling field-model agents' punch list, printed so it is actionable
|
|
rather than just "some test failed".
|
|
"""
|
|
unknown = D.unknown_keys()
|
|
assert unknown == [], (
|
|
'pa_defaults knows how to default {0} key(s) the form does not '
|
|
'recognise yet: {1}'.format(len(unknown), unknown))
|
|
|
|
|
|
def test_known_keys_is_a_real_nonempty_set():
|
|
# Sanity: the live import chain actually worked in this environment,
|
|
# so unknown_keys()==[] above is not vacuously true because every
|
|
# lookup silently failed.
|
|
known = D.known_keys()
|
|
assert len(known) > 50
|
|
assert 'fc_psi' in known
|
|
assert 'stage.stripping.include' in known
|
|
assert 'design_criteria.phi_compression' in known
|
|
|
|
|
|
# ------------------------------------------------------------------ blanks
|
|
|
|
def test_apply_defaults_fills_blanks_and_never_overwrites():
|
|
values = {
|
|
'fc_psi': '6000', # already filled -- must survive
|
|
'design_spec': '',
|
|
'phi_shear': '0.8', # already filled, non-default value
|
|
}
|
|
applied = D.apply_defaults(values)
|
|
assert values['fc_psi'] == '6000'
|
|
assert values['phi_shear'] == '0.8'
|
|
applied_keys = dict((k, v) for k, v, _w in applied)
|
|
assert applied_keys.get('design_spec') == 'ACI 318-19'
|
|
assert values['design_spec'] == 'ACI 318-19'
|
|
# phi_shear was never blank, so it must not appear in what was applied.
|
|
assert 'phi_shear' not in applied_keys
|
|
|
|
|
|
def test_defaults_for_does_not_mutate_values():
|
|
values = {'fc_psi': ''}
|
|
before = dict(values)
|
|
D.defaults_for(values)
|
|
assert values == before
|
|
|
|
|
|
def test_apply_defaults_is_idempotent():
|
|
# Two fresh, independently-blank dicts get the same defaults...
|
|
values_a = {}
|
|
values_b = {}
|
|
first = D.apply_defaults(values_a)
|
|
second = D.apply_defaults(values_b)
|
|
assert sorted(first) == sorted(second)
|
|
# ...and a second pass over an already-filled dict finds nothing left
|
|
# to do among the keys the first pass just filled.
|
|
filled_keys = set(k for k, _v, _w in first)
|
|
third = D.apply_defaults(values_a)
|
|
assert not (set(k for k, _v, _w in third) & filled_keys)
|
|
|
|
|
|
# ------------------------------------------------------------ plant knowledge
|
|
|
|
def test_stage_multiplier_falls_back_to_1_4_with_no_plant_data():
|
|
values = {}
|
|
applied = dict((k, (v, w)) for k, v, w in D.apply_defaults(values, {}))
|
|
value, why = applied['stage.stripping.load_multiplier']
|
|
assert value == '1.4'
|
|
assert 'Eriksson' in why
|
|
|
|
|
|
def test_plant_stage_multiplier_wins_over_the_1_4_fallback():
|
|
know = {
|
|
'handling': {
|
|
'stages': [
|
|
{'name': 'stripping', 'multiplier': 1.6, 'strength': 'fci'},
|
|
],
|
|
},
|
|
}
|
|
values = {}
|
|
applied = dict((k, (v, w)) for k, v, w in D.apply_defaults(values, know))
|
|
value, why = applied['stage.stripping.load_multiplier']
|
|
assert value == '1.6'
|
|
assert 'plant knowledge' in why
|
|
assert values['stage.stripping.load_multiplier'] == '1.6'
|
|
|
|
|
|
def test_transportation_and_erection_fallback_multipliers():
|
|
values = {}
|
|
D.apply_defaults(values, {})
|
|
assert values['stage.transportation.load_multiplier'] == '1.5'
|
|
assert values['stage.primary_erection.load_multiplier'] == '1.4'
|
|
assert values['stage.primary_erection.max_tilt_deg'] == '90'
|
|
assert values['stage.stripping.max_tilt_deg'] == '0'
|
|
assert values['stage.transportation.max_tilt_deg'] == '0'
|
|
|
|
|
|
def test_secondary_erection_only_fills_include_no():
|
|
values = {}
|
|
D.apply_defaults(values, {})
|
|
assert values['stage.secondary_erection.include'] == 'No'
|
|
for opt in ('load_multiplier', 'lift_type', 'concrete_strength',
|
|
'stress_limits', 'rigging', 'sling_angle',
|
|
'ground_reaction', 'min_tilt_deg', 'max_tilt_deg',
|
|
'top_crane_carries'):
|
|
key = 'stage.secondary_erection.{0}'.format(opt)
|
|
assert key not in values or values.get(key) in (None, ''), key
|
|
|
|
|
|
def test_strength_mapping_fci_to_initial_and_fc_to_in_place():
|
|
know = {
|
|
'handling': {
|
|
'stages': [
|
|
{'name': 'stripping', 'multiplier': None, 'strength': 'fci'},
|
|
{'name': 'transportation', 'multiplier': None,
|
|
'strength': 'fc'},
|
|
],
|
|
},
|
|
}
|
|
values = {}
|
|
D.apply_defaults(values, know)
|
|
assert values['stage.stripping.concrete_strength'] == 'Initial'
|
|
assert values['stage.transportation.concrete_strength'] == 'In-Place'
|
|
# Primary Erection has no matching plant stage in this `know` -> fallback.
|
|
assert values['stage.primary_erection.concrete_strength'] == 'In-Place'
|
|
|
|
|
|
def test_sling_angle_from_plant_else_60():
|
|
values = {}
|
|
D.apply_defaults(values, {})
|
|
assert values['stage.stripping.sling_angle'] == '60'
|
|
|
|
values2 = {}
|
|
know = {'handling': {'sling_angle_deg': 45}}
|
|
D.apply_defaults(values2, know)
|
|
assert values2['stage.stripping.sling_angle'] == '45'
|
|
assert values2['stage.transportation.sling_angle'] == '45'
|
|
assert values2['stage.primary_erection.sling_angle'] == '45'
|
|
|
|
|
|
def test_rails_strand_datum_mapping():
|
|
for datum, expected in (('left_rail', 'Left edge'),
|
|
('right_rail', 'Right edge'),
|
|
('centerline', 'Centerline')):
|
|
values = {}
|
|
know = {'rails': {'strand_datum': datum}}
|
|
D.apply_defaults(values, know)
|
|
assert values['strand_rail_location'] == expected
|
|
|
|
# No plant value -> the Eriksson-sample fallback.
|
|
values = {}
|
|
D.apply_defaults(values, {})
|
|
assert values['strand_rail_location'] == 'Left edge'
|
|
|
|
# An unrecognised token degrades to the fallback rather than a raw
|
|
# unmapped string leaking into the form.
|
|
values = {}
|
|
D.apply_defaults(values, {'rails': {'strand_datum': 'mystery_rail'}})
|
|
assert values['strand_rail_location'] == 'Left edge'
|
|
|
|
|
|
def test_engineer_initials_only_filled_when_plant_names_one():
|
|
values = {}
|
|
applied = D.apply_defaults(values, {})
|
|
assert 'engineer_initials' not in dict((k, v) for k, v, _w in applied)
|
|
assert values.get('engineer_initials', '') == ''
|
|
|
|
values2 = {}
|
|
D.apply_defaults(values2, {'panelanalysis': {'engineer_initials': 'WS'}})
|
|
assert values2['engineer_initials'] == 'WS'
|
|
|
|
|
|
def test_stress_ignore_lateral_ecc_plant_override():
|
|
values = {}
|
|
D.apply_defaults(values, {})
|
|
assert values['design_criteria.stress_ignore_lateral_ecc'] == 'No'
|
|
|
|
values2 = {}
|
|
know = {'panelanalysis': {'settings':
|
|
{'stress_ignore_lateral_ecc': 'yes'}}}
|
|
D.apply_defaults(values2, know)
|
|
assert values2['design_criteria.stress_ignore_lateral_ecc'] == 'Yes'
|
|
|
|
|
|
def test_top_in_form_plant_override_and_fallback():
|
|
values = {}
|
|
D.apply_defaults(values, {})
|
|
assert values['top_in_form'] == 'Interior'
|
|
|
|
values2 = {}
|
|
D.apply_defaults(values2, {'panelanalysis': {'top_in_form': 'exterior'}})
|
|
assert values2['top_in_form'] == 'Exterior'
|
|
|
|
|
|
# ---------------------------------------------------------------- geometry
|
|
|
|
def test_wind_fields_are_no_longer_defaulted_2026_09_04():
|
|
"""ASCE site data (Vult, risk/exposure/enclosure, code version, C&C/
|
|
MWFRS chapter, mean roof height, effective wind area) moved out of this
|
|
form entirely, into project config -- this module no longer defaults
|
|
any of it (`panelanalysisform`'s module docstring, Wind section)."""
|
|
values = {'length_ft': '40', 'width_ft': '8', 'top_elevation_ft': '22.5'}
|
|
applied_keys = set(k for k, _v, _w in D.apply_defaults(values, {}))
|
|
for gone in ('wind_speed_vult', 'wind_risk_category', 'wind_exposure',
|
|
'wind_enclosure', 'wind_asce_version', 'wind_cc_chapter',
|
|
'wind_mwfrs_chapter', 'wind_mean_roof_height_ft',
|
|
'wind_effective_wind_area_ft2', 'wind_pressure_psf',
|
|
'wind_suction_psf'):
|
|
assert gone not in applied_keys, gone
|
|
assert gone not in values, gone
|
|
|
|
|
|
def test_temp_alpha_t_is_the_mantissa_the_field_asks_for():
|
|
"""The field's own label is 'alphaT x 10^-6' (pa_structural.py), and
|
|
nothing consumes this value yet -- so the default is the mantissa the
|
|
box expects (5.5), not the raw coefficient (0.0000055)."""
|
|
values = {}
|
|
applied = dict((k, (v, w)) for k, v, w in D.apply_defaults(values))
|
|
assert values['temp_alpha_t'] == '5.5'
|
|
value, why = applied['temp_alpha_t']
|
|
assert value == '5.5'
|
|
assert '10' in why and '/' in why # names the x10^-6 mantissa framing
|
|
assert D.why_of('temp_alpha_t') == why
|
|
|
|
|
|
def test_project_date_is_todays_iso_date():
|
|
import datetime
|
|
values = {}
|
|
D.apply_defaults(values)
|
|
assert values['project_date'] == datetime.date.today().isoformat()
|
|
|
|
|
|
# ---------------------------------------------------------- choice vocab
|
|
|
|
def test_every_default_with_choices_is_a_valid_choice():
|
|
"""Where the form declares a choice list for a key (FIELD_CHOICES,
|
|
pa_criteria._F's own choices column, a pa_structural.page() `choice`
|
|
field, or the Handling stage matrix's per-column choices), the value
|
|
this module picks must be a member of it.
|
|
"""
|
|
values = {'length_ft': '10', 'width_ft': '10',
|
|
'top_elevation_ft': '10'}
|
|
applied = D.apply_defaults(values, {
|
|
'handling': {'sling_angle_deg': 45,
|
|
'stages': [{'name': 'stripping', 'multiplier': 1.6,
|
|
'strength': 'fci'}]},
|
|
'rails': {'strand_datum': 'left_rail'},
|
|
'panelanalysis': {'engineer_initials': 'WS', 'top_in_form': 'Interior',
|
|
'settings': {'stress_ignore_lateral_ecc': 'no'}},
|
|
})
|
|
bad = []
|
|
for key, value_text, _why in applied:
|
|
choices = D.choices_for(key)
|
|
if choices is None:
|
|
continue
|
|
if value_text not in choices:
|
|
bad.append((key, value_text, choices))
|
|
assert bad == [], 'default not in the editor\'s own choice list: {0}'.format(bad)
|
|
|
|
|
|
def test_specific_choice_defaults():
|
|
values = {}
|
|
D.apply_defaults(values, {})
|
|
# restraints are an editable row table since 2026-09-04 -- no scalar
|
|
# foundation / floor-tie defaults any more
|
|
assert 'restraint_foundation_connection' not in values
|
|
assert values['left_miter'] == 'No'
|
|
assert values['right_miter'] == 'No'
|
|
assert values['second_order'] == 'Yes'
|
|
choices = D.choices_for('load_opt_offset_from')
|
|
assert choices is not None and values['load_opt_offset_from'] in choices
|
|
beta_choices = D.choices_for('design_criteria.analysis_beta_d')
|
|
assert beta_choices is not None and \
|
|
values['design_criteria.analysis_beta_d'] in beta_choices
|
|
|
|
|
|
# --------------------------------------------------------------------- why_of
|
|
|
|
def test_why_of_returns_a_sentence_for_every_filled_key():
|
|
values = {'length_ft': '10', 'width_ft': '10'}
|
|
applied = D.apply_defaults(values, {
|
|
'handling': {'sling_angle_deg': 45,
|
|
'stages': [{'name': 'erection', 'multiplier': 1.7,
|
|
'strength': 'fc'}]},
|
|
})
|
|
missing = []
|
|
for key, _value_text, _why in applied:
|
|
sentence = D.why_of(key)
|
|
if not sentence:
|
|
missing.append(key)
|
|
assert missing == [], 'why_of() has nothing for: {0}'.format(missing)
|
|
|
|
|
|
def test_why_of_unknown_key_is_none():
|
|
assert D.why_of('this_key_does_not_exist_anywhere') is None
|
|
|
|
|
|
# -------------------------------------------------------------- robustness
|
|
|
|
def test_robust_to_values_none():
|
|
assert D.defaults_for(None) == []
|
|
assert D.apply_defaults(None) == []
|
|
|
|
|
|
def test_robust_to_know_garbage():
|
|
values = {}
|
|
applied = D.apply_defaults(values, 'garbage')
|
|
# Should still fill the plant-independent defaults without raising.
|
|
assert values.get('design_spec') == 'ACI 318-19'
|
|
assert isinstance(applied, list) and applied
|
|
|
|
|
|
def test_robust_to_garbage_values_type():
|
|
assert D.defaults_for('garbage') == []
|
|
assert D.defaults_for(['a', 'list']) == []
|
|
assert D.apply_defaults(42) == []
|
|
|
|
|
|
def test_robust_to_malformed_plant_stage_shapes():
|
|
values = {}
|
|
know = {'handling': {'stages': 'not a list'}}
|
|
applied = D.apply_defaults(values, know)
|
|
assert values['stage.stripping.load_multiplier'] == '1.4'
|
|
|
|
values2 = {}
|
|
know2 = {'handling': {'stages': [{'name': 'stripping'},
|
|
'not a dict', 42, None]}}
|
|
applied2 = D.apply_defaults(values2, know2)
|
|
assert values2['stage.stripping.load_multiplier'] == '1.4'
|
|
assert isinstance(applied2, list)
|
|
|
|
|
|
def test_choices_for_unknown_key_is_none():
|
|
assert D.choices_for('this_key_does_not_exist_anywhere') is None
|
|
|
|
|
|
# ------------------------------------------------------------- never blank
|
|
|
|
def test_loading_keys_are_never_defaulted():
|
|
"""The owner's one hard exclusion: real loading stays blank for the
|
|
user, no matter what."""
|
|
values = {}
|
|
applied_keys = set(k for k, _v, _w in D.apply_defaults(values, {
|
|
'handling': {'sling_angle_deg': 60}}))
|
|
for key in ('wind_speed_vult', 'wind_pressure_psf', 'wind_suction_psf',
|
|
'seismic_cs', 'seismic_zone_location', 'ps_loss_pct',
|
|
'ext_soil_loads', 'int_soil_loads', 'conc_loads'):
|
|
assert key not in applied_keys, key
|
|
assert values.get(key, '') == ''
|