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,318 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""The declared scope: what the tool is answerable for, and what it says
|
||||
about everything else.
|
||||
|
||||
Every mark here is a placeholder. `PNL` stands for the pieces in scope and
|
||||
`XX` / `YY` for somebody else's -- the real prefixes on a real job are job
|
||||
information and belong in the job knowledge file, never in this repository.
|
||||
|
||||
What these tests are actually protecting, in one sentence each:
|
||||
|
||||
* an undeclared scope judges EVERYTHING (an empty list is not an empty
|
||||
scope, and this is the assertion that stops it becoming one);
|
||||
* a mark whose prefix cannot be read is never set aside;
|
||||
* the report line exists in all three states and never reads as a pass;
|
||||
* nothing the tool sets aside is summed away without being named;
|
||||
* a NEAR MISS -- a set-aside prefix that is one letter off a declared one
|
||||
-- is reported and never resolved. `PNLA` is used for that throughout:
|
||||
it is `PNL` plus a letter, which is the shape that made this a hole.
|
||||
"""
|
||||
import pytest
|
||||
|
||||
from vibe import scope as S
|
||||
|
||||
|
||||
# ------------------------------------------------------------ the predicate
|
||||
|
||||
def test_no_declaration_includes_everything():
|
||||
"""The single most important behaviour here. 'Nobody said' must never
|
||||
collapse into 'nothing is in scope'."""
|
||||
empty = S.Scope()
|
||||
assert empty.is_declared is False
|
||||
for name in ('PNL001', 'XX001', 'YY9', '', None, '12345'):
|
||||
assert empty.includes(name) is True
|
||||
|
||||
|
||||
def test_declared_scope_keeps_its_own_and_sets_the_rest_aside():
|
||||
scope = S.Scope(prefixes=('PNL',))
|
||||
assert scope.includes('PNL001') is True
|
||||
assert scope.includes('pnl001') is True # case is not a fact
|
||||
assert scope.includes('XX001') is False
|
||||
assert scope.includes('YY12') is False
|
||||
|
||||
|
||||
def test_several_prefixes_may_be_declared():
|
||||
scope = S.Scope(prefixes=('PNL', 'XX'))
|
||||
assert scope.includes('XX001') is True
|
||||
assert scope.includes('YY001') is False
|
||||
|
||||
|
||||
def test_a_mark_with_no_readable_prefix_is_never_set_aside():
|
||||
"""'I could not tell' may only ever resolve towards judging it."""
|
||||
scope = S.Scope(prefixes=('PNL',))
|
||||
assert scope.includes('12345') is True
|
||||
assert scope.includes('') is True
|
||||
assert scope.includes(None) is True
|
||||
assert scope.is_unreadable('12345') is True
|
||||
assert scope.kind('12345') == S.UNREADABLE
|
||||
|
||||
|
||||
def test_blank_and_duplicate_prefixes_are_cleaned_not_honoured():
|
||||
scope = S.Scope(prefixes=['PNL', ' pnl ', '', None])
|
||||
assert scope.prefixes == ('PNL',)
|
||||
|
||||
|
||||
def test_an_all_blank_declaration_is_no_declaration():
|
||||
assert S.Scope(prefixes=['', ' ']).is_declared is False
|
||||
|
||||
|
||||
# ------------------------------------------------------------- mark shapes
|
||||
|
||||
@pytest.mark.parametrize('name,expected', [
|
||||
('PNL001', True), ('PNL 1', True), ('XX12', True),
|
||||
('GENERAL ARRANGEMENT', False), ('', False), (None, False),
|
||||
('12345', False),
|
||||
])
|
||||
def test_looks_like_mark(name, expected):
|
||||
"""A sheet name that is not a mark is not somebody else's piece, and
|
||||
must not be counted as one."""
|
||||
assert S.looks_like_mark(name) is expected
|
||||
|
||||
|
||||
def test_as_mark_hands_back_nothing_for_a_non_mark():
|
||||
scope = S.Scope(prefixes=('PNL',))
|
||||
assert scope.includes(S.as_mark('GENERAL ARRANGEMENT')) is True
|
||||
|
||||
|
||||
# ----------------------------------------------------------------- splits
|
||||
|
||||
def test_split_preserves_order_and_keeps_both_halves():
|
||||
scope = S.Scope(prefixes=('PNL',))
|
||||
inside, outside = scope.split_names(
|
||||
['PNL001', 'XX001', 'PNL002', 'YY001'])
|
||||
assert inside == ['PNL001', 'PNL002']
|
||||
assert outside == ['XX001', 'YY001']
|
||||
|
||||
|
||||
def test_split_reads_a_key():
|
||||
scope = S.Scope(prefixes=('PNL',))
|
||||
rows = [{'mark': 'PNL001'}, {'mark': 'XX001'}]
|
||||
inside, outside = scope.split(rows, key=lambda r: r['mark'])
|
||||
assert inside == [{'mark': 'PNL001'}]
|
||||
assert outside == [{'mark': 'XX001'}]
|
||||
|
||||
|
||||
def test_tally_names_every_kind_biggest_first():
|
||||
"""Counted AND named. '435 pieces' sends nobody anywhere."""
|
||||
scope = S.Scope(prefixes=('PNL',))
|
||||
assert scope.tally(['XX1', 'XX2', 'YY1', 'XX3', 'ZZ1', 'ZZ2']) == [
|
||||
('XX', 3), ('ZZ', 2), ('YY', 1)]
|
||||
|
||||
|
||||
# ------------------------------------------------------------ the reporting
|
||||
|
||||
def test_the_line_when_work_was_set_aside_names_the_kinds():
|
||||
scope = S.Scope(prefixes=('PNL',))
|
||||
line = scope.line(['XX1', 'XX2', 'YY1'], total=10)
|
||||
assert 'NOT JUDGED, OUT OF SCOPE' in line
|
||||
assert 'XX 2' in line and 'YY 1' in line
|
||||
assert '3 of 10' in line
|
||||
# Never a pass, and never a failure -- it says both out loud.
|
||||
assert 'Not judged is not clean, and not a failure either.' in line
|
||||
|
||||
|
||||
def test_the_line_is_printed_even_when_nothing_was_set_aside():
|
||||
"""A run that filtered nothing looks exactly like a run that filtered
|
||||
everything once the answer is on the screen."""
|
||||
line = S.Scope(prefixes=('PNL',)).line([])
|
||||
assert 'none' in line
|
||||
assert 'declared scope is piece marks beginning PNL' in line
|
||||
|
||||
|
||||
def test_the_undeclared_line_says_everything_was_judged_and_why_that_matters():
|
||||
line = S.Scope().line([])
|
||||
assert 'No scope is declared' in line
|
||||
assert 'every piece was judged' in line
|
||||
assert 'never been validated' in line
|
||||
|
||||
|
||||
def test_the_line_reports_unreadable_marks_separately():
|
||||
scope = S.Scope(prefixes=('PNL',))
|
||||
line = scope.line(['XX1'], total=3, unreadable_names=['12345'])
|
||||
assert 'no readable mark prefix' in line
|
||||
assert 'judged rather than set aside' in line
|
||||
|
||||
|
||||
def test_the_annotation_is_for_work_the_user_picked_himself():
|
||||
"""Nothing the user selects is ever dropped. It is judged, and the
|
||||
report says the verdict rests on unvalidated knowledge."""
|
||||
scope = S.Scope(prefixes=('PNL',))
|
||||
note = scope.annotation(['XX1'])
|
||||
assert 'AT YOUR REQUEST' in note
|
||||
assert 'not been validated' in note
|
||||
assert scope.annotation([]) is None
|
||||
assert S.Scope().annotation(['XX1']) is None # nothing declared, nothing odd
|
||||
|
||||
|
||||
def test_label_reads_as_a_sentence_a_person_can_check():
|
||||
assert S.Scope(prefixes=('PNL',)).label() == 'piece marks beginning PNL'
|
||||
assert S.Scope(prefixes=('PNL', 'XX')).label() == \
|
||||
'piece marks beginning PNL or XX'
|
||||
assert S.Scope().label() == 'no scope declared'
|
||||
|
||||
|
||||
def test_declaration_lines_carry_who_said_so():
|
||||
scope = S.Scope(prefixes=('PNL',), declared_by='somebody',
|
||||
note='because of a reason')
|
||||
assert scope.declaration_lines() == ['scope declared by somebody',
|
||||
'because of a reason']
|
||||
assert S.Scope(prefixes=('PNL',)).declaration_lines() == []
|
||||
|
||||
|
||||
# --------------------------------------------------------- the near miss
|
||||
#
|
||||
# The hole: `prefix_of` reads the whole leading letter run, so a declared
|
||||
# `PNL` does not reach `PNLA001` and that piece is handed to another
|
||||
# engineer on the strength of one letter. The decision recorded here is that
|
||||
# the MATCH IS NOT WIDENED and the near miss is REPORTED -- widening would
|
||||
# also swallow every other run starting `PNL`, and a false in-scope is judged
|
||||
# silently while a false out-of-scope is at least printed.
|
||||
|
||||
def test_the_match_is_still_the_whole_letter_run_and_was_not_widened():
|
||||
"""The regression lock on the decision. If somebody later widens the
|
||||
match, they must come here, read why it was not, and change it on
|
||||
purpose -- with the owner's answer in hand, not instead of it."""
|
||||
scope = S.Scope(prefixes=('PNL',))
|
||||
assert scope.includes('PNLA001') is False
|
||||
assert scope.includes('PNLZ001') is False
|
||||
assert scope.prefixes == ('PNL',)
|
||||
|
||||
|
||||
def test_a_nearly_matching_prefix_is_named_as_a_near_miss():
|
||||
scope = S.Scope(prefixes=('PNL',))
|
||||
assert scope.near_misses(['PNLA1', 'PNLA2', 'XX1']) == [('PNLA', 2, 'PNL')]
|
||||
|
||||
|
||||
def test_a_near_miss_is_found_in_both_directions():
|
||||
"""Either spelling may be the longer one. Declaring `PNLA` and finding
|
||||
`PNL001` is the same mistake seen from the other end."""
|
||||
assert S.Scope(prefixes=('PNLA',)).near_misses(['PNL1']) == \
|
||||
[('PNL', 1, 'PNLA')]
|
||||
|
||||
|
||||
def test_an_exact_match_is_not_a_near_miss_and_neither_is_an_unrelated_one():
|
||||
scope = S.Scope(prefixes=('PNL',))
|
||||
assert scope.nearly('PNL') is None # it is a match, not a near one
|
||||
assert scope.nearly('XX') is None
|
||||
assert scope.near_misses(['XX1', 'YY2']) == []
|
||||
|
||||
|
||||
def test_an_unreadable_mark_is_never_a_near_miss():
|
||||
"""It has no prefix to be nearly anything, and it was judged anyway."""
|
||||
scope = S.Scope(prefixes=('PNL',))
|
||||
assert scope.nearly(S.UNREADABLE) is None
|
||||
assert scope.near_misses(['12345']) == []
|
||||
|
||||
|
||||
def test_nothing_is_a_near_miss_when_nothing_was_declared():
|
||||
assert S.Scope().near_misses(['PNLA1']) == []
|
||||
assert S.Scope().near_miss_line(['PNLA1']) is None
|
||||
|
||||
|
||||
def test_the_near_miss_line_says_both_spellings_and_refuses_to_guess():
|
||||
line = S.Scope(prefixes=('PNL',)).near_miss_line(['PNLA1'], unit='piece')
|
||||
assert 'PNLA' in line and 'PNL' in line
|
||||
assert 'NEAR MISS' in line
|
||||
assert 'will not guess' in line
|
||||
# It names the fix without making it: the human widens the declaration.
|
||||
assert 'scope.mark_prefixes' in line
|
||||
|
||||
|
||||
def test_the_near_miss_rides_on_the_ordinary_report_line():
|
||||
"""So that every existing consumer of `line()` reports it without
|
||||
having been changed to know it exists."""
|
||||
line = S.Scope(prefixes=('PNL',)).line(['PNLA1', 'XX1'], total=9)
|
||||
assert 'NOT JUDGED, OUT OF SCOPE' in line
|
||||
assert 'NEAR MISS' in line
|
||||
assert 'PNLA 1' in line # still tallied by kind as before
|
||||
|
||||
|
||||
def test_an_ordinary_out_of_scope_line_carries_no_near_miss_noise():
|
||||
line = S.Scope(prefixes=('PNL',)).line(['XX1', 'YY2'], total=9)
|
||||
assert 'NEAR MISS' not in line
|
||||
|
||||
|
||||
def test_the_annotation_carries_the_near_miss_too():
|
||||
"""Here it matters more: the reader is being told his own selection
|
||||
rests on unvalidated knowledge, and that warning may be the wrong one."""
|
||||
note = S.Scope(prefixes=('PNL',)).annotation(['PNLA1'])
|
||||
assert 'AT YOUR REQUEST' in note
|
||||
assert 'NEAR MISS' in note
|
||||
|
||||
|
||||
# ------------------------------------------------------- the short headline
|
||||
|
||||
def test_the_short_line_says_the_count_in_all_three_states():
|
||||
scope = S.Scope(prefixes=('PNL',))
|
||||
assert 'none declared' in S.Scope().short_line(['XX1'])
|
||||
assert 'no piece' in scope.short_line([], total=9)
|
||||
busy = scope.short_line(['XX1', 'XX2', 'YY1'], total=9, unit='assembly')
|
||||
assert 'NOT JUDGED, OUT OF SCOPE' in busy
|
||||
assert '3 of 9 assembly(s)' in busy
|
||||
assert 'XX 2' in busy and 'YY 1' in busy
|
||||
|
||||
|
||||
def test_the_short_line_caps_the_kinds_so_it_stays_short():
|
||||
scope = S.Scope(prefixes=('PNL',))
|
||||
text = scope.short_line(['AA1', 'BB1', 'CC1', 'DD1', 'EE1'])
|
||||
assert '+2 more' in text
|
||||
assert len(text) < 200
|
||||
|
||||
|
||||
def test_the_short_line_flags_a_near_miss_without_explaining_it():
|
||||
"""It is a pointer at the full sentence, not a replacement for it."""
|
||||
text = S.Scope(prefixes=('PNL',)).short_line(['PNLA1'])
|
||||
assert text.endswith('-- NEAR MISS')
|
||||
assert 'will not guess' not in text
|
||||
|
||||
|
||||
# ------------------------------------------------------- reading the layer
|
||||
|
||||
def _get(know, dotted, default=None):
|
||||
node = know
|
||||
for part in dotted.split('.'):
|
||||
if not isinstance(node, dict) or part not in node:
|
||||
return default
|
||||
node = node[part]
|
||||
return default if node is None else node
|
||||
|
||||
|
||||
def test_of_reads_the_job_key():
|
||||
know = {'scope': {'mark_prefixes': ['PNL'], 'declared_by': 'somebody',
|
||||
'note': 'a reason'}}
|
||||
scope = S.of(know, _get)
|
||||
assert scope.prefixes == ('PNL',)
|
||||
assert scope.declared_by == 'somebody'
|
||||
assert scope.note == 'a reason'
|
||||
|
||||
|
||||
def test_of_accepts_a_single_scalar_because_yaml_readers_hand_back_strings():
|
||||
assert S.of({'scope': {'mark_prefixes': 'PNL'}}, _get).prefixes == ('PNL',)
|
||||
|
||||
|
||||
def test_of_on_an_absent_or_empty_key_declares_nothing():
|
||||
for know in ({}, None, {'scope': {}}, {'scope': {'mark_prefixes': []}},
|
||||
{'scope': {'mark_prefixes': None}}):
|
||||
assert S.of(know, _get).is_declared is False
|
||||
|
||||
|
||||
def test_the_shipped_default_declares_nothing():
|
||||
"""The repository must never carry one job's division of labour."""
|
||||
from vibe import knowledge
|
||||
assert knowledge.DEFAULTS['scope']['mark_prefixes'] == []
|
||||
assert S.of(knowledge.DEFAULTS, knowledge.get).is_declared is False
|
||||
|
||||
|
||||
def test_a_folded_yaml_note_does_not_carry_its_newline_into_the_report():
|
||||
scope = S.Scope(prefixes=('PNL',), note='one sentence.\n')
|
||||
assert scope.declaration_lines() == ['one sentence.']
|
||||
Reference in New Issue
Block a user