WillTools 2026-09-09 -- clean snapshot of the development repo for review
ci / test (push) Successful in 6m14s
ci / test (push) Successful in 6m14s
History-free snapshot of the private development repository at 77a82eb. Code, docs and the public catalogue only: no plant or job knowledge layers, no client, job or producer names (tools/scrub.py clean against the widened term list; a second word-level grep clean). The intro deck is left out because its slide images cannot be checked for job drawings.
This commit is contained in:
@@ -0,0 +1,176 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Panel Analysis Handling page pre-fill: `face_lifters` / `edge_lifters`
|
||||
read straight off Panel Lift's own `job['panel']['lifters']`, split face vs
|
||||
edge the way `knowledge.handling.lifter_groups` groups them for the rest of
|
||||
the tool.
|
||||
"""
|
||||
from vibe import knowledge
|
||||
from vibe import pa_lifters
|
||||
|
||||
|
||||
def _know():
|
||||
know = knowledge._deep_copy(knowledge.DEFAULTS)
|
||||
know['handling']['lifter_groups'] = {
|
||||
'face': {'families': ['FACE_X']},
|
||||
'edge_top': {'families': ['EDGE_Y']},
|
||||
}
|
||||
return know
|
||||
|
||||
|
||||
def _lifter(family, x, y, on_edge=False, mark='m'):
|
||||
return {'family': family, 'mark': mark, 'x': x, 'y': y,
|
||||
'on_edge': on_edge}
|
||||
|
||||
|
||||
# --------------------------------------------------------------- lifter_lines
|
||||
|
||||
def test_face_and_edge_split_by_knowledge_groups():
|
||||
know = _know()
|
||||
lifters = [
|
||||
_lifter('FACE_X_ANCHOR', 2.5, 0.75, mark='f1'),
|
||||
_lifter('EDGE_Y_ANCHOR', 25.0, 2.25, mark='e1'),
|
||||
]
|
||||
face_text, edge_text, why = pa_lifters.lifter_lines(lifters, 3.0, know)
|
||||
assert face_text == '2.50 @ 0.25'
|
||||
assert edge_text == '25.00 @ 0.75'
|
||||
assert 'FACE_X_ANCHOR' in why
|
||||
assert 'EDGE_Y_ANCHOR' in why
|
||||
assert '1 face lifter' in why
|
||||
assert '1 edge lifter' in why
|
||||
|
||||
|
||||
def test_on_edge_flag_counts_as_edge_even_off_the_edge_top_family_list():
|
||||
know = _know()
|
||||
lifters = [_lifter('SOME_OTHER_FAMILY', 10.0, 1.5, on_edge=True)]
|
||||
face_text, edge_text, why = pa_lifters.lifter_lines(lifters, 3.0, know)
|
||||
assert face_text == ''
|
||||
assert edge_text == '10.00 @ 0.50'
|
||||
|
||||
|
||||
def test_family_matching_neither_group_and_not_on_edge_is_unclassified():
|
||||
know = _know()
|
||||
lifters = [_lifter('MYSTERY_FAMILY', 5.0, 1.0)]
|
||||
face_text, edge_text, why = pa_lifters.lifter_lines(lifters, 2.0, know)
|
||||
assert face_text == ''
|
||||
assert edge_text == ''
|
||||
assert 'MYSTERY_FAMILY' in why
|
||||
assert 'matched neither' in why
|
||||
|
||||
|
||||
def test_lines_sorted_by_x_then_frac_same_x_is_a_row():
|
||||
know = _know()
|
||||
lifters = [
|
||||
_lifter('FACE_X_A', 10.0, 2.0, mark='a'),
|
||||
_lifter('FACE_X_B', 2.0, 1.0, mark='b'),
|
||||
_lifter('FACE_X_C', 10.0, 1.0, mark='c'),
|
||||
]
|
||||
face_text, _edge_text, _why = pa_lifters.lifter_lines(lifters, 4.0, know)
|
||||
assert face_text == ('2.00 @ 0.25\n'
|
||||
'10.00 @ 0.25\n'
|
||||
'10.00 @ 0.50')
|
||||
|
||||
|
||||
def test_missing_x_or_y_is_skipped_and_counted_in_why():
|
||||
know = _know()
|
||||
lifters = [
|
||||
_lifter('FACE_X_A', None, 1.0, mark='a'),
|
||||
_lifter('FACE_X_B', 2.0, None, mark='b'),
|
||||
_lifter('FACE_X_C', 2.0, 1.0, mark='c'),
|
||||
]
|
||||
face_text, _edge_text, why = pa_lifters.lifter_lines(lifters, 4.0, know)
|
||||
assert face_text == '2.00 @ 0.25'
|
||||
assert '2 lifters skipped' in why
|
||||
|
||||
|
||||
def test_no_width_skips_every_lifter_and_says_so():
|
||||
know = _know()
|
||||
lifters = [_lifter('FACE_X_A', 2.0, 1.0)]
|
||||
face_text, edge_text, why = pa_lifters.lifter_lines(lifters, None, know)
|
||||
assert face_text == '' and edge_text == ''
|
||||
assert '1 lifter skipped' in why
|
||||
|
||||
|
||||
def test_empty_lifter_list():
|
||||
know = _know()
|
||||
face_text, edge_text, why = pa_lifters.lifter_lines([], 4.0, know)
|
||||
assert face_text == '' and edge_text == ''
|
||||
assert '0 face lifters' in why
|
||||
assert '0 edge lifters' in why
|
||||
|
||||
|
||||
# ---------------------------------------------------------------- read_lifters
|
||||
|
||||
def test_read_lifters_from_a_job_dict():
|
||||
know = _know()
|
||||
job = {'panel': {'width_ft': 3.0, 'lifters': [
|
||||
_lifter('FACE_X_ANCHOR', 2.5, 0.75),
|
||||
_lifter('EDGE_Y_ANCHOR', 25.0, 2.25),
|
||||
]}}
|
||||
out = pa_lifters.read_lifters(job, know)
|
||||
assert out['face_lifters'] == '2.50 @ 0.25'
|
||||
assert out['edge_lifters'] == '25.00 @ 0.75'
|
||||
assert 'lifters_basis' in out
|
||||
|
||||
|
||||
def test_read_lifters_omits_empty_text_but_keeps_basis():
|
||||
know = _know()
|
||||
job = {'panel': {'width_ft': 3.0, 'lifters': [
|
||||
_lifter('FACE_X_ANCHOR', 2.5, 0.75),
|
||||
]}}
|
||||
out = pa_lifters.read_lifters(job, know)
|
||||
assert 'face_lifters' in out
|
||||
assert 'edge_lifters' not in out
|
||||
assert 'lifters_basis' in out
|
||||
|
||||
|
||||
def test_read_lifters_no_job_is_empty_dict():
|
||||
know = _know()
|
||||
assert pa_lifters.read_lifters(None, know) == {}
|
||||
|
||||
|
||||
def test_read_lifters_no_lifters_key_is_empty_dict():
|
||||
know = _know()
|
||||
assert pa_lifters.read_lifters({'panel': {'width_ft': 3.0}}, know) == {}
|
||||
assert pa_lifters.read_lifters(
|
||||
{'panel': {'width_ft': 3.0, 'lifters': []}}, know) == {}
|
||||
|
||||
|
||||
def test_read_lifters_is_defensive_on_bad_input():
|
||||
know = _know()
|
||||
# 'panel' is not a dict -- .get on a list raises AttributeError inside;
|
||||
# this must come back {} rather than propagate.
|
||||
assert pa_lifters.read_lifters({'panel': ['not', 'a', 'dict']}, know) \
|
||||
== {}
|
||||
|
||||
|
||||
# ----------------------------------------------------------- parse_lifter_lines
|
||||
|
||||
def test_parse_lifter_lines_round_trip():
|
||||
text = '2.50 @ 0.25\n10.00 @ 0.50'
|
||||
rows, problems = pa_lifters.parse_lifter_lines(text)
|
||||
assert problems == []
|
||||
assert rows == [{'x_ft': 2.5, 'frac': 0.25}, {'x_ft': 10.0, 'frac': 0.5}]
|
||||
|
||||
|
||||
def test_parse_lifter_lines_blank_text():
|
||||
assert pa_lifters.parse_lifter_lines('') == ([], [])
|
||||
assert pa_lifters.parse_lifter_lines(None) == ([], [])
|
||||
assert pa_lifters.parse_lifter_lines(' \n \n') == ([], [])
|
||||
|
||||
|
||||
def test_parse_lifter_lines_refuses_the_whole_field_on_one_bad_line():
|
||||
text = '2.50 @ 0.25\nnot a lifter line\n10.00 @ 0.50'
|
||||
rows, problems = pa_lifters.parse_lifter_lines(text)
|
||||
assert rows == []
|
||||
assert len(problems) == 1
|
||||
assert 'line 2' in problems[0]
|
||||
|
||||
|
||||
def test_parse_lifter_lines_bad_numbers_named_by_line():
|
||||
rows, problems = pa_lifters.parse_lifter_lines('abc @ 0.25')
|
||||
assert rows == []
|
||||
assert 'line 1' in problems[0] and 'x_ft' in problems[0]
|
||||
|
||||
rows, problems = pa_lifters.parse_lifter_lines('2.5 @ xyz')
|
||||
assert rows == []
|
||||
assert 'line 1' in problems[0] and 'frac' in problems[0]
|
||||
Reference in New Issue
Block a user