# -*- 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, '') == ''