Files
willtools-demo/WillTools.extension/tests/test_takeoffcount.py
T
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

337 lines
14 KiB
Python

# -*- coding: utf-8 -*-
"""The control-number multiplier: where it comes from and what it refuses to say.
This is the one number that decides whether the takeoff is right, so the
tests are about its FAILURE modes rather than its arithmetic. A source that
returns a confident 1 for a mark it knows nothing about is the bug this
module exists to make impossible.
Filenames here are invented. The shape they exercise -- job, control number,
mark -- is the plant's export convention, not a job's data.
"""
# pylint: disable=import-error,invalid-name
from vibe import takeoffcount as count
# ---------------------------------------------------------- ticket filenames
def test_a_ticket_filename_yields_its_mark_and_its_control_number():
assert count.parse_ticket_name('11111-20001_PNL101.pdf') == \
('PNL101', '20001')
def test_a_control_number_with_no_digit_in_it_is_not_a_control_number():
"""The export folder also holds the detail sheets, and their names take
the identical three-part shape. Twelve of them were being counted as
marks until `join_quality` said so."""
assert count.parse_ticket_name('11111-SHEET_X4-1.pdf') == (None, None)
assert count.from_ticket_files(['11111-SHEET_X4-1.pdf']) == {}
assert count.unparsed_ticket_files(
['11111-SHEET_X4-1.pdf', '11111-20001_PNL101.pdf']) == \
['11111-SHEET_X4-1.pdf']
def test_a_lettered_control_number_is_still_a_control_number():
assert count.parse_ticket_name('11111-A20001_PNL101.pdf') == \
('PNL101', 'A20001')
def test_a_file_named_by_mark_alone_is_ignored_not_counted_as_one_piece():
"""The older export was named by mark. One file per mark is exactly how
one piece per mark came to be believed, so it must contribute nothing."""
assert count.parse_ticket_name('PNL101.pdf') == (None, None)
assert count.from_ticket_files(['PNL101.pdf']) == {}
def test_a_revision_is_a_reissued_ticket_not_a_second_piece():
assert count.from_ticket_files([
'11111-20001_PNL101.pdf',
'11111-20001_PNL101_R1.pdf',
]) == {'PNL101': (1, 'ticket_files')}
def test_several_control_numbers_on_one_mark_are_several_pieces():
got = count.from_ticket_files([
'11111-20001_PNL101.pdf',
'11111-20003_PNL101.pdf',
'11111-20005_PNL101.pdf',
'11111-20007_PNL102.pdf',
])
assert got == {'PNL101': (3, 'ticket_files'), 'PNL102': (1, 'ticket_files')}
# ------------------------------------------------------- the other two sources
def test_duplicate_assembly_names_are_a_multiplier():
assert count.from_assembly_names(
['PNL101', 'PNL101', 'PNL102']) == {'PNL101': (2, 'assembly_instances')}
def test_a_name_that_appears_once_is_absence_of_evidence_not_a_count_of_one():
"""The bug this caught, kept as a test so it cannot come back.
Measured through the bridge on a real job: 755 assemblies, 755 distinct
names, zero duplicates. An earlier version returned 1 for each, every
mark got a confident basis, and every per-job figure silently equalled
its per-mark figure -- a whole-job undercount that looked sourced.
"""
assert count.from_assembly_names(['A', 'B', 'C']) == {}
def test_control_numbers_off_the_model_elements_are_the_real_source():
got, unreadable = count.from_piece_records([
{'id': 1, 'mark': 'PNL101', 'control': '20001'},
{'id': 2, 'mark': 'PNL101', 'control': '20003'},
{'id': 3, 'mark': 'PNL102', 'control': '20005'},
])
assert got == {'PNL101': (2, 'control_numbers'),
'PNL102': (1, 'control_numbers')}
assert unreadable == []
def test_a_control_number_on_several_elements_is_still_one_piece():
"""2480 control-numbered elements resolved to 1776 pieces on this job.
The difference is exactly this sharing, and counting elements would
over-order by it."""
got, _bad = count.from_piece_records([
{'id': 1, 'mark': 'PNL101', 'control': '20001'},
{'id': 2, 'mark': 'PNL101', 'control': '20001'},
])
assert got == {'PNL101': (1, 'control_numbers')}
def test_a_printed_range_is_expanded_by_the_one_shared_expander():
"""`50120-50123` is four pieces. The expansion is jobrecon's, not a
second copy of it living here."""
got, _bad = count.from_piece_records(
[{'id': 1, 'mark': 'PNL101', 'control': '50120-50123'}])
assert got == {'PNL101': (4, 'control_numbers')}
def test_an_element_with_a_control_number_and_no_mark_is_named_not_dropped():
got, unreadable = count.from_piece_records(
[{'id': 7, 'mark': '', 'control': '20001'}])
assert got == {}
assert unreadable[0]['id'] == 7
assert 'no mark' in unreadable[0]['why']
def test_an_unreadable_control_field_is_returned_rather_than_swallowed():
got, unreadable = count.from_piece_records(
[{'id': 9, 'mark': 'PNL101', 'control': 'PL 1/4" X 28"'}])
assert got == {}
assert unreadable and unreadable[0]['id'] == 9
def test_a_mark_with_no_readable_control_numbers_is_left_out_entirely():
"""Not zero, and not one: absent, so the next source gets a turn."""
assert count.from_control_numbers({'PNL101': [], 'PNL102': ['20001']}) == {
'PNL102': (1, 'control_numbers')}
def test_control_numbers_are_counted_distinct():
assert count.from_control_numbers(
{'PNL101': ['20001', '20001', '20003']})['PNL101'][0] == 2
# ----------------------------------------------------------------- precedence
def test_the_model_control_numbers_outrank_every_other_source():
merged = count.merge(
{'PNL101': (3, 'control_numbers')},
{'PNL101': (1, 'ticket_files')},
{'PNL101': (9, 'assembly_instances')})
assert merged['PNL101'] == (3, 'control_numbers')
def test_assembly_instances_is_ranked_last_of_the_three():
assert count.ORDER[-1] == 'assembly_instances'
merged = count.merge({'PNL101': (9, 'assembly_instances')},
{'PNL101': (2, 'ticket_files')})
assert merged['PNL101'] == (2, 'ticket_files')
def test_a_worse_source_still_covers_a_mark_the_better_one_missed():
merged = count.merge({'PNL101': (3, 'control_numbers')},
{'PNL102': (2, 'ticket_files')})
assert merged['PNL102'] == (2, 'ticket_files')
def test_the_sources_are_compared_because_nothing_else_can_check_them():
got = count.disagreements({'PNL101': (2, 'control_numbers')},
{'PNL101': (3, 'ticket_files')})
assert got == [{'mark': 'PNL101',
'left': 2, 'left_basis': 'control_numbers',
'right': 3, 'right_basis': 'ticket_files'}]
def test_agreement_is_silent():
assert count.disagreements({'PNL101': (3, 'control_numbers')},
{'PNL101': (3, 'ticket_files')}) == []
# ------------------------------------------- what one exported ticket covers
#
# `nomenclature.ticket_unit` decides whether a folder of exported tickets is
# a count of PIECES or a count of DRAWINGS, and the same folder reads to two
# different numbers under the two answers. The tests below are the pin: for
# the declared value the numbers must be exactly what they were when the
# ranking was a constant, and every other state must be a visible refusal
# rather than a quiet fallback to it.
# One fixture, exercised by every case, built so that all three sources have
# something to say and each of them is the ONLY source for at least one mark.
# A mark that only the tickets cover is the whole question: it is the mark
# that changes value if the ticket unit is guessed.
CONTROL = {'PNL101': (3, 'control_numbers'), 'PNL102': (1, 'control_numbers')}
TICKETS = {'PNL101': (2, 'ticket_files'), 'PNL103': (4, 'ticket_files')}
ASSEMBLIES = {'PNL101': (9, 'assembly_instances'),
'PNL104': (2, 'assembly_instances')}
SOURCES = {'control_numbers': CONTROL, 'ticket_files': TICKETS,
'assembly_instances': ASSEMBLIES}
# What the ranking produced when it was hardcoded. Written down rather than
# recomputed, because a pin that recomputes both sides pins nothing.
AS_IT_ALWAYS_WAS = {
'PNL101': (3, 'control_numbers'),
'PNL102': (1, 'control_numbers'),
'PNL103': (4, 'ticket_files'),
'PNL104': (2, 'assembly_instances'),
}
def test_one_ticket_per_control_number_ranks_all_three_sources():
plan = count.counting_plan('control_number', SOURCES)
assert plan['order'] == count.ORDER
assert plan['withdrawn'] == ()
assert plan['refusal'] is None
assert 'one ticket per control number' in plan['reason']
def test_the_declared_unit_does_not_move_a_single_count():
"""The pin. Wiring the key changed the SHAPE and must not change a number
on a job that declares the value the ranking used to assume."""
plan = count.counting_plan('control_number', SOURCES)
merged = count.merge(CONTROL, TICKETS, ASSEMBLIES, order=plan['order'])
assert merged == AS_IT_ALWAYS_WAS
assert count.summarise(merged)['pieces'] == 10
def test_merge_with_no_order_is_still_the_full_ranking():
"""Every caller that has not asked the question gets what it always got."""
assert count.merge(CONTROL, TICKETS, ASSEMBLIES) == AS_IT_ALWAYS_WAS
def test_one_ticket_per_mark_withdraws_the_exported_tickets():
"""A ticket covering a whole mark carries ONE control number in its
filename and several pieces on its face. Counting the filename would
return a confident 1 for a mark that is really seven."""
plan = count.counting_plan('piece_mark', SOURCES)
assert plan['withdrawn'] == ('ticket_files',)
assert plan['refusal'] is None # somebody declared it
merged = count.merge(CONTROL, TICKETS, ASSEMBLIES, order=plan['order'])
assert 'PNL103' not in merged # the tickets' own mark is gone
assert merged['PNL101'] == (3, 'control_numbers')
def test_an_undeclared_ticket_unit_refuses_rather_than_assuming_one():
"""The judgement call, pinned. Falling back to `control_number` would
under-count a job ticketed the other way, and it would do it wearing a
source's name instead of `assumed_one`."""
plan = count.counting_plan(None, SOURCES)
assert plan['declared'] is False
assert plan['withdrawn'] == ('ticket_files',)
assert plan['refusal']
assert 'nomenclature.ticket_unit' in plan['refusal']
assert 'control_number' in plan['refusal'] # names both accepted values
assert 'piece_mark' in plan['refusal']
def test_an_unrecognised_value_is_a_refusal_not_a_fallback():
"""A key set to something nobody implemented is an intention, misspelt.
Reading it as the default does the opposite of what was asked."""
plan = count.counting_plan('one per truck', SOURCES)
assert plan['declared'] is True
assert plan['recognised'] is False
assert 'one per truck' in plan['refusal']
assert plan['withdrawn'] == ('ticket_files',)
def test_a_withdrawn_source_is_dropped_and_never_ranked_last():
"""Ranking it 99th would let it through on exactly the marks nothing else
covers -- which is where a wrong multiplier does its damage."""
plan = count.counting_plan(None)
merged = count.merge(CONTROL, TICKETS, ASSEMBLIES, order=plan['order'])
assert 'PNL103' not in merged
got = count.summarise(merged, marks=['PNL101', 'PNL102', 'PNL103'])
assert got['by_basis']['assumed_one'] == 1
assert got['assumed_one'] == 1
def test_the_plan_says_what_the_withdrawal_actually_cost():
"""A rule and a finding are different things: the report opens on the
marks lost, not on the key being unset."""
plan = count.counting_plan(None, SOURCES)
assert plan['withdrawn_marks'] == 2 # PNL101 and PNL103
assert plan['cost'] == 1 # only PNL103 had nothing else
def test_an_undeclared_unit_with_no_tickets_to_withdraw_costs_nothing():
plan = count.counting_plan(None, {'control_numbers': CONTROL})
assert plan['refusal']
assert plan['cost'] == 0
def test_the_other_two_sources_do_not_turn_on_the_ticket_unit():
"""A control number off a model element is one physical piece however the
plant tickets it, and a duplicated assembly name is a duplicated piece
either way. Only the ticket files read differently."""
for unit in (None, 'control_number', 'piece_mark', 'nonsense'):
order = count.counting_plan(unit)['order']
assert 'control_numbers' in order and 'assembly_instances' in order
assert order[0] == 'control_numbers'
def test_every_state_carries_a_sentence_to_print():
for unit in (None, 'control_number', 'piece_mark', 'nonsense'):
plan = count.counting_plan(unit)
assert plan['reason']
assert plan['key'] == 'nomenclature.ticket_unit'
# ------------------------------------------------------------------ summary
def test_a_mark_nobody_could_source_is_counted_and_named_assumed_one():
got = count.summarise({'PNL101': (3, 'ticket_files')},
marks=['PNL101', 'PNL102'])
assert got['marks'] == 2
assert got['pieces'] == 4
assert got['assumed_one'] == 1
assert got['by_basis'] == {'ticket_files': 1, 'assumed_one': 1}
def test_the_average_multiplier_is_reported_so_a_reader_can_smell_it():
got = count.summarise({'A': (2, 'ticket_files'), 'B': (4, 'ticket_files')})
assert got['multiplier'] == 3.0
# ------------------------------------------------------------------- the join
def test_a_source_naming_things_the_takeoff_never_heard_of_is_visible():
"""The silent-and-total failure mode: the two sides key on different
strings, every mark falls through to assumed_one, and the source still
reports hundreds of marks found."""
got = count.join_quality({'OTHER_1': (2, 'control_numbers')},
marks=['PNL101', 'PNL102'])
assert got['matched'] == 0
assert got['unmatched_source'] == ['OTHER_1']
assert got['unsourced'] == ['PNL101', 'PNL102']
def test_a_clean_join_reports_no_strays():
got = count.join_quality({'PNL101': (2, 'control_numbers')},
marks=['PNL101'])
assert got['matched'] == 1
assert got['unmatched_source'] == []
assert got['unsourced'] == []