# -*- coding: utf-8 -*- """The takeoff's Revit-free half: classification, both totals, and the refusals. No Revit, no model, no index. Every fixture here is a synthetic index cache in the shape `reinforcecore.piece_record` produces, with invented family names -- the point of the split is that the counting rules can be wrong on paper and caught here, before anybody opens a model. The rules under test are the ones this project has already paid to learn: a mark is not a piece, a nested family is counted twice, an attached bar is not a separate order, and a family nobody could classify is a finding rather than a silence. """ # pylint: disable=import-error,invalid-name from vibe import takeoffcore as core def piece(name, families, **extra): record = {'name': name, 'families': families, 'features': [], 'error': None, 'design_number': extra.pop('design', None)} record.update(extra) return record def cache(*records): return {'schema': 3, 'built_at': 'once', 'pieces': dict((r['name'], r) for r in records)} CFG = core.settings({}) # --------------------------------------------------------------- classifying def test_a_bar_prefix_family_is_a_bar_and_keeps_its_mark(): assert core.classify('SR9001', CFG) == ('bar', 'SR9001', 'rule:bar_prefix') def test_a_part_number_is_dug_out_of_a_longer_family_name(): klass, key, basis = core.classify('MAKER_EMBED_P70 with bar', CFG) assert (klass, key, basis) == ('hardware', 'P70', 'rule:part_prefix') def test_the_longer_prefix_wins_so_one_series_is_not_read_as_another(): """SP70 must never come back as the P70 inside it.""" assert core.classify('SP70', CFG)[1] == 'SP70' def test_a_bend_shape_family_is_reinforcing_with_no_mark(): """A shape family carries a different bar mark on every piece. It must not borrow a mark from its own name, and it must not be dropped: the instances are real steel whose SKU this index cannot name. """ klass, key, basis = core.classify('99A_(SCHEDULED)', CFG) assert klass == core.BAR_SHAPE assert key == '99A_(SCHEDULED)' assert basis == 'rule:shape_marker' def test_a_shape_marker_beats_a_bar_prefix_in_the_family_name(): assert core.classify('SR9001_(SCHEDULED)', CFG)[0] == core.BAR_SHAPE def test_an_unknown_family_is_unclassified_and_keeps_its_own_name(): assert core.classify('WIDGET_THING', CFG) == ( 'unclassified', 'WIDGET_THING', 'none') def test_a_declared_family_beats_every_pattern(): cfg = core.settings({'takeoff': {'family_class': {'SR9001': 'cast'}}}) assert core.classify('SR9001', cfg) == ('cast', 'SR9001', 'declared') def test_not_ordered_is_a_substring_declaration(): cfg = core.settings({'takeoff': {'not_ordered': ['PANEL_HOST']}}) assert core.classify('PANEL_HOST_FLAT', cfg)[0] == core.NOT_ORDERED def test_strand_and_mesh_come_from_the_existing_family_rules(): assert core.classify('X_STRAND', CFG, is_strand=lambda f: 'STRAND' in f)[0] \ == core.STRAND assert core.classify('WWF_6X6', CFG, is_mesh=lambda f: f.startswith('WWF'))[0] \ == core.MESH # ---------------------------------------------------- the control multiplier def test_per_mark_and_per_job_are_both_reported_and_differ_by_the_multiplier(): out = core.build(cache(piece('A', {'P70': 4})), CFG, multipliers={'A': (3, 'ticket_files')}) row = out['totals'][0] assert row['per_mark_total'] == 4 assert row['per_job_total'] == 12 assert row['multiplier'] == 3.0 def test_a_mark_with_no_sourced_multiplier_is_one_and_is_named_an_undercount(): """Counted once, and listed, because a multiplier nobody sourced is not 1.""" out = core.build(cache(piece('A', {'P70': 4})), CFG) assert out['totals'][0]['per_job_total'] == 4 assert out['undercounted'] == ['A'] assert out['by_piece']['A']['multiplier_basis'] == 'assumed_one' def test_counting_marks_instead_of_pieces_is_the_under_order_this_prevents(): """Two marks, one of them three pieces: 4 per mark, 8 in the job.""" out = core.build( cache(piece('A', {'P70': 2}), piece('B', {'P70': 2})), CFG, multipliers={'A': (3, 'ticket_files'), 'B': (1, 'ticket_files')}) row = out['totals'][0] assert (row['per_mark_total'], row['per_job_total']) == (4, 8) assert out['pieces_on_the_floor'] == 4 # --------------------------------------------------------------- attached bars def test_an_attached_bar_is_counted_and_kept_off_the_order_by_default(): out = core.build(cache(piece('A', {'SR9001': 6, 'P70': 2})), CFG, attached={'SR9001': ['P70']}) order, ships, _other = core.order_rows(out['totals'], out['order_attached_separately']) assert [r['key'] for r in ships] == ['SR9001'] assert [r['key'] for r in order] == ['P70'] assert ships[0]['per_mark_total'] == 6 # counted, not dropped assert ships[0]['attached_to'] == ['P70'] def test_the_switch_puts_attached_bars_back_on_the_order(): cfg = core.settings({'takeoff': {'order_attached_bars_separately': True}}) out = core.build(cache(piece('A', {'SR9001': 6})), cfg, attached={'SR9001': ['P70']}) order, ships, _other = core.order_rows(out['totals'], out['order_attached_separately']) assert [r['key'] for r in order] == ['SR9001'] assert ships == [] def test_attached_parents_are_read_from_the_drawing_knowledge(): know = {'drawings': {'annotation_covered_by_parent': {'SR9001': ['P70']}}} assert core.attached_parents(CFG, know) == {'SR9001': ['P70']} def test_a_single_parent_written_as_a_scalar_still_reads(): know = {'drawings': {'annotation_covered_by_parent': {'SR9001': 'P70'}}} assert core.attached_parents(CFG, know) == {'SR9001': ['P70']} # ------------------------------------------------------ what it will not count def test_an_unclassified_family_is_listed_by_name_never_bucketed(): out = core.build(cache(piece('A', {'WIDGET_THING': 5})), CFG, multipliers={'A': (2, 'ticket_files')}) assert [r['family'] for r in out['unclassified']] == ['WIDGET_THING'] assert out['unclassified'][0]['per_job_total'] == 10 assert out['unclassified'][0]['examples'] == ['A'] def test_unclassified_is_never_orderable(): out = core.build(cache(piece('A', {'WIDGET_THING': 5})), CFG) order, _ships, other = core.order_rows(out['totals']) assert order == [] assert [r['class'] for r in other] == ['unclassified'] def test_a_piece_with_no_families_is_could_not_judge_not_an_empty_piece(): """The distinction the whole index is built around, kept on this side.""" record = piece('A', {}) record['error'] = 'members unreadable' record['features'] = None out = core.build(cache(record), CFG) assert out['unjudged'] == [{'piece': 'A', 'reason': 'members unreadable'}] assert out['by_piece']['A']['judged'] is False assert out['totals'] == [] def test_a_measured_piece_with_no_openings_is_still_counted(): """features=None is about GEOMETRY. A takeoff does not need geometry.""" record = piece('A', {'P70': 2}) record['features'] = None record['error'] = 'no concrete solid' out = core.build(cache(record), CFG) assert out['unjudged'] == [] assert out['totals'][0]['per_mark_total'] == 2 def test_coverage_names_the_marks_the_index_never_built(): out = core.build(cache(piece('A', {'P70': 1})), CFG, population={'assemblies': 5}) assert out['coverage']['indexed_marks'] == 1 assert out['coverage']['marks_not_indexed'] == 4 assert out['coverage']['complete'] is False def test_every_row_records_what_decided_it(): out = core.build(cache(piece('A', {'P70': 1, 'WIDGET': 1})), CFG) assert out['bases'] == {'rule:part_prefix': 1, 'none': 1} # -------------------------------------------------------------- nesting def test_a_family_that_follows_another_count_for_count_is_flagged(): out = core.suspected_nesting({ 'A': {'families': {'PARENT': 4, 'CHILD': 4}}, 'B': {'families': {'PARENT': 8, 'CHILD': 8}}, 'C': {'families': {'PARENT': 2, 'CHILD': 2}}, }) assert len(out) == 1 assert set([out[0]['child'], out[0]['parent']]) == set(['PARENT', 'CHILD']) assert out[0]['direction'] == 'undecided' assert out[0]['agreement'] == 1.0 def test_two_parts_that_are_one_each_are_not_evidence_of_anything(): """The filter that took this list from forty rows of noise to three.""" assert core.suspected_nesting({ 'A': {'families': {'ONE': 1, 'TWO': 1}}, 'B': {'families': {'ONE': 1, 'TWO': 1}}, 'C': {'families': {'ONE': 1, 'TWO': 1}}, }) == [] def test_a_pair_that_disagrees_on_one_mark_still_surfaces_with_its_agreement(): out = core.suspected_nesting({ 'A': {'families': {'PARENT': 4, 'CHILD': 4}}, 'B': {'families': {'PARENT': 8, 'CHILD': 8}}, 'C': {'families': {'PARENT': 2, 'CHILD': 2}}, 'D': {'families': {'PARENT': 2, 'CHILD': 1}}, 'E': {'families': {'PARENT': 4, 'CHILD': 4}}, 'F': {'families': {'PARENT': 8, 'CHILD': 8}}, 'G': {'families': {'PARENT': 2, 'CHILD': 2}}, 'H': {'families': {'PARENT': 4, 'CHILD': 4}}, 'I': {'families': {'PARENT': 8, 'CHILD': 8}}, 'J': {'families': {'PARENT': 2, 'CHILD': 2}}, }) pair = [r for r in out if r['child'] == 'CHILD' or r['parent'] == 'CHILD'] assert pair and pair[0]['agreement'] < 1.0 def test_nothing_is_ever_subtracted_on_a_suspicion(): """The total is unchanged by the detector. It only prints.""" pieces = cache(piece('A', {'PARENT': 4, 'CHILD': 4}), piece('B', {'PARENT': 8, 'CHILD': 8}), piece('C', {'PARENT': 2, 'CHILD': 2})) out = core.build(pieces, CFG) total = dict((r['key'], r['per_mark_total']) for r in out['totals']) assert total['CHILD'] == 14 assert out['nesting_suspects'] def test_a_declared_nesting_leaves_the_order_list(): cfg = core.settings({'takeoff': {'nested_in': {'CHILD': 'PARENT'}}}) out = core.build(cache(piece('A', {'CHILD': 4})), cfg) assert out['totals'][0]['class'] == core.NESTED order, _ships, other = core.order_rows(out['totals']) assert order == [] assert other[0]['key'] == 'CHILD' # ------------------------------------------- the index field that does not exist def test_a_marks_counter_resolves_bend_shapes_into_real_bar_marks(): """The one-field index change, exercised before the index has it. With `marks` on the record the shape families drop out and named bar marks take their place. Nothing else about the takeoff changes, which is the whole reason it was written this way. """ record = piece('A', {'99A_(SCHEDULED)': 12, 'P70': 2}) record['marks'] = {'SR9001': 8, 'SR9002': 4, 'P70': 2} out = core.build(cache(record), CFG) got = dict(((r['class'], r['key']), r['per_mark_total']) for r in out['totals']) assert got[('bar', 'SR9001')] == 8 assert got[('bar', 'SR9002')] == 4 assert got[('hardware', 'P70')] == 2 # counted once, not twice assert ('bar_shape', '99A_(SCHEDULED)') not in got