Files
will 25592fa6c2
ci / test (push) Successful in 6m14s
WillTools 2026-09-09 -- clean snapshot of the development repo for review
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.
2026-09-09 08:35:15 -05:00

278 lines
11 KiB
Python

# -*- 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