25592fa6c2
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.
1375 lines
62 KiB
Python
1375 lines
62 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""The binding: ligament features, the production `covers`, the donor filter.
|
|
|
|
Every piece here is invented and every mark is a `PNL...` placeholder, which
|
|
is this project's convention -- no real panel mark, job or client name appears
|
|
in this file. The worked-example shapes are rebuilt with invented marks
|
|
because the geometry is what the tests are about, not the job.
|
|
"""
|
|
# pylint: disable=import-error,invalid-name
|
|
import os
|
|
|
|
import pytest
|
|
|
|
from vibe import reinforcecore as core
|
|
from vibe import reinforcebind as B
|
|
from vibe import reinforcematch as match
|
|
|
|
|
|
# ---------------------------------------------------------------- fixtures
|
|
|
|
# The design number every fixture carries unless a test says otherwise.
|
|
#
|
|
# It is SHARED on purpose. A donor must be the same design as its target
|
|
# (owner ruling, 2026-09-01, enforced in `DonorFilter`), so a fixture set
|
|
# where every piece had its own design number would be a fixture set where
|
|
# nothing can ever donate to anything -- and every test about geometry,
|
|
# ranking, MASTER or the cap would be testing the design filter instead of
|
|
# what it says on its name. Tests that are about the design rule pass `design`
|
|
# explicitly; everything else stays inside one design and reads as before.
|
|
DESIGN = 'PNL1.0'
|
|
|
|
|
|
def piece(name, length, width, thickness, rects, reinforced=1, on_hold=0,
|
|
master=False, design=DESIGN, elevation_band=None,
|
|
elevation_group=None):
|
|
"""An index piece record. `rects` are (x0, x1, y0, y1[, through]).
|
|
|
|
`elevation_band` is a `{'axis': ..., 'min': ..., 'max': ...}` dict (or
|
|
None) -- the geometric FLOOR (owner's 2026-09-02 ruling). `band(...)`
|
|
below is the convenience most tests actually want. `elevation_group` is
|
|
the optional ACCELERATOR -- an opaque plant-declared string (MC's is
|
|
`Tagged_Sheet`) -- present on neither fixture unless a test says so,
|
|
which mirrors most real jobs.
|
|
"""
|
|
feats = []
|
|
for i, r in enumerate(rects):
|
|
through = r[4] if len(r) > 4 else True
|
|
feats.append(core.feature(name, i, r[0], r[1], r[2], r[3],
|
|
through, 0.0 if through else 3.5,
|
|
length, width, thickness))
|
|
return core.piece_record(name, 1, 'sig', reinforced, on_hold,
|
|
design_number=design,
|
|
length_ft=length, width_ft=width,
|
|
thickness_in=thickness, features=feats,
|
|
master=master,
|
|
master_note='MASTER' if master else None,
|
|
elevation_band=elevation_band,
|
|
elevation_group=elevation_group)
|
|
|
|
|
|
def band(tag, axis='X'):
|
|
"""A distinct elevation band for a fixture, keyed by a short tag.
|
|
|
|
Not a real coordinate -- these tests are about the RANKING and the
|
|
AFFINITY logic, not about `elevation_band()`'s own geometry (that is
|
|
`test_reinforcecore.py`'s job). Each distinct `tag` gets its own
|
|
100 ft-wide slot on `axis` so two different tags never accidentally
|
|
overlap, and the same `tag` always returns the identical band, so two
|
|
fixtures built with `band('ELEV-3')` genuinely share one elevation.
|
|
"""
|
|
if tag is None:
|
|
return None
|
|
# A plain character-code sum, not `hash()` -- Python randomises string
|
|
# hashing per process, and this has to return the SAME band for the
|
|
# SAME tag every time it is called, not just within one run.
|
|
slot = sum(ord(c) for c in (axis + str(tag))) % 1000
|
|
lo = float(slot) * 100.0
|
|
return {'axis': axis, 'min': lo, 'max': lo + 0.667}
|
|
|
|
|
|
def kinds(feats):
|
|
out = {}
|
|
for f in feats:
|
|
out[f['kind']] = out.get(f['kind'], 0) + 1
|
|
return out
|
|
|
|
|
|
def find(feats, kind, x0, x1, y0, y1):
|
|
for f in feats:
|
|
if (f['kind'] == kind and abs(f['x0'] - x0) < 1e-6 and
|
|
abs(f['x1'] - x1) < 1e-6 and abs(f['y0'] - y0) < 1e-6 and
|
|
abs(f['y1'] - y1) < 1e-6):
|
|
return f
|
|
return None
|
|
|
|
|
|
# ------------------------------------------------- what a ligament is
|
|
|
|
def test_two_openings_leave_the_strip_between_them():
|
|
"""The reframe, in one test: the concrete between the holes is a feature
|
|
belonging to neither hole."""
|
|
p = piece('PNL601', 20.0, 10.0, 8.0,
|
|
[(4.0, 6.0, 2.0, 5.0), (9.0, 11.0, 2.0, 5.0)])
|
|
feats = B.features_for(p)
|
|
strip = find(feats, B.STRIP_X, 6.0, 9.0, 2.0, 5.0)
|
|
assert strip is not None
|
|
assert strip['strip_ft'] == 3.0 # the gap between them
|
|
assert strip['span_ft'] == 3.0 # how far it runs across
|
|
assert strip['between'] == B.VOID_VOID
|
|
|
|
|
|
def test_identical_holes_different_spacing_are_different_features():
|
|
"""Same two holes, moved apart. The holes are identical; the strip is not.
|
|
This is the whole point of the reframe."""
|
|
near = B.features_for(piece('PNL602', 20.0, 10.0, 8.0,
|
|
[(4.0, 6.0, 2.0, 5.0), (6.5, 8.5, 2.0, 5.0)]))
|
|
far = B.features_for(piece('PNL603', 20.0, 10.0, 8.0,
|
|
[(4.0, 6.0, 2.0, 5.0), (9.0, 11.0, 2.0, 5.0)]))
|
|
assert find(near, B.STRIP_X, 6.0, 6.5, 2.0, 5.0)['strip_ft'] == 0.5
|
|
assert find(far, B.STRIP_X, 6.0, 9.0, 2.0, 5.0)['strip_ft'] == 3.0
|
|
|
|
|
|
def test_ligament_runs_to_the_panel_edge():
|
|
p = piece('PNL604', 20.0, 10.0, 8.0, [(4.0, 6.0, 2.0, 5.0)])
|
|
feats = B.features_for(p)
|
|
left = find(feats, B.STRIP_X, 0.0, 4.0, 2.0, 5.0)
|
|
right = find(feats, B.STRIP_X, 6.0, 20.0, 2.0, 5.0)
|
|
below = find(feats, B.STRIP_Y, 4.0, 6.0, 0.0, 2.0)
|
|
above = find(feats, B.STRIP_Y, 4.0, 6.0, 5.0, 10.0)
|
|
for f in (left, right, below, above):
|
|
assert f is not None
|
|
assert f['between'] == B.VOID_EDGE
|
|
assert left['edge'] == 'x0' and right['edge'] == 'x1'
|
|
assert below['edge'] == 'y0' and above['edge'] == 'y1'
|
|
|
|
|
|
def test_a_void_flush_with_the_edge_leaves_no_ligament_there():
|
|
"""No concrete, no feature. The flush condition shows up as the ABSENCE
|
|
of the strip, which the set cover reads correctly."""
|
|
p = piece('PNL605', 20.0, 10.0, 8.0, [(0.0, 2.0, 2.0, 5.0)])
|
|
feats = B.features_for(p)
|
|
assert not [f for f in feats if f['edge'] == 'x0']
|
|
assert [f for f in feats if f['edge'] == 'x1']
|
|
|
|
|
|
def test_no_ligament_is_claimed_through_a_third_void():
|
|
"""A strip must be concrete you can point at. Two voids far apart with a
|
|
third between them do not leave a strip spanning the lot."""
|
|
p = piece('PNL606', 30.0, 10.0, 8.0,
|
|
[(4.0, 6.0, 2.0, 5.0), (12.0, 14.0, 2.0, 5.0),
|
|
(20.0, 22.0, 2.0, 5.0)])
|
|
feats = B.features_for(p)
|
|
assert find(feats, B.STRIP_X, 6.0, 20.0, 2.0, 5.0) is None # through v1
|
|
assert find(feats, B.STRIP_X, 6.0, 12.0, 2.0, 5.0) is not None
|
|
assert find(feats, B.STRIP_X, 14.0, 20.0, 2.0, 5.0) is not None
|
|
|
|
|
|
def test_voids_in_different_height_bands_leave_no_strip_between_them():
|
|
"""Requirement 6, at the construction end: an x-gap is only a strip if the
|
|
two voids actually overlap in y."""
|
|
p = piece('PNL607', 20.0, 12.0, 8.0,
|
|
[(4.0, 6.0, 1.0, 3.0), (9.0, 11.0, 8.0, 10.0)])
|
|
feats = B.features_for(p)
|
|
assert not [f for f in feats if f['between'] == B.VOID_VOID]
|
|
|
|
|
|
def test_never_measured_stays_never_measured():
|
|
"""`features is None` must not become an empty list on the way through."""
|
|
p = core.piece_record('PNL608', 1, 'sig', 1, 0, error='solid failed')
|
|
assert B.features_for(p) is None
|
|
|
|
|
|
def test_measured_and_empty_is_a_list_not_none():
|
|
p = piece('PNL609', 20.0, 10.0, 8.0, [])
|
|
assert B.features_for(p) == []
|
|
|
|
|
|
def test_include_voids_off_leaves_only_ligaments():
|
|
p = piece('PNL610', 20.0, 10.0, 8.0, [(4.0, 6.0, 2.0, 5.0)])
|
|
assert kinds(B.features_for(p, include_voids=False)).get(B.OPENING) is None
|
|
assert kinds(B.features_for(p)).get(B.OPENING) == 1
|
|
|
|
|
|
# ------------------------------------------------------- the covers contract
|
|
|
|
def test_identical_features_are_exact():
|
|
a = piece('PNL611', 20.0, 10.0, 8.0, [(4.0, 6.0, 2.0, 5.0)])
|
|
b = piece('PNL612', 20.0, 10.0, 8.0, [(4.0, 6.0, 2.0, 5.0)])
|
|
fa, fb = B.features_for(a), B.features_for(b)
|
|
for i in range(len(fa)):
|
|
assert B.covers(fa[i], fb[i]) == 0.0
|
|
|
|
|
|
def test_covers_never_raises_on_junk():
|
|
"""Requirement 1: total. Anything unjudgeable is None, not an exception."""
|
|
good = B.features_for(piece('PNL613', 20.0, 10.0, 8.0,
|
|
[(4.0, 6.0, 2.0, 5.0)]))[0]
|
|
for junk in (None, {}, {'kind': B.OPENING}, 'nonsense', 42, [],
|
|
{'kind': B.OPENING, 'x0': 'a', 'x1': 1, 'y0': 0, 'y1': 1}):
|
|
assert B.covers(junk, good) is None
|
|
assert B.covers(good, junk) is None
|
|
|
|
|
|
def test_covers_is_deterministic():
|
|
a = B.features_for(piece('PNL614', 20.0, 10.0, 8.0,
|
|
[(4.0, 6.0, 2.0, 5.0)]))[0]
|
|
b = B.features_for(piece('PNL615', 20.0, 10.0, 8.0,
|
|
[(4.1, 6.1, 2.0, 5.0)]))[0]
|
|
assert len(set(B.covers(a, b) for _ in range(20))) == 1
|
|
|
|
|
|
def test_a_strip_never_covers_an_opening():
|
|
"""Requirement: kinds do not mix, however well the rectangles line up."""
|
|
p = piece('PNL616', 20.0, 10.0, 8.0,
|
|
[(4.0, 6.0, 2.0, 5.0), (9.0, 11.0, 2.0, 5.0)])
|
|
feats = B.features_for(p)
|
|
opening = [f for f in feats if f['kind'] == B.OPENING][0]
|
|
strip = find(feats, B.STRIP_X, 6.0, 9.0, 2.0, 5.0)
|
|
assert B.covers(strip, opening) is None
|
|
assert B.covers(opening, strip) is None
|
|
|
|
|
|
def test_strips_on_the_two_axes_do_not_mix():
|
|
x_strip = {'kind': B.STRIP_X, 'x0': 4.0, 'x1': 6.0, 'y0': 2.0, 'y1': 5.0,
|
|
'strip_ft': 2.0}
|
|
y_strip = {'kind': B.STRIP_Y, 'x0': 4.0, 'x1': 6.0, 'y0': 2.0, 'y1': 5.0,
|
|
'strip_ft': 3.0}
|
|
assert B.covers(x_strip, y_strip) is None
|
|
|
|
|
|
def test_donor_strip_may_be_narrower_but_never_wider():
|
|
"""The directional rule (requirement 3): bars detailed for a narrow strip
|
|
fit a wider one; a cage for a wide strip does not fit narrower concrete."""
|
|
narrow = {'kind': B.STRIP_X, 'x0': 5.0, 'x1': 6.0, 'y0': 2.0, 'y1': 5.0,
|
|
'strip_ft': 1.0}
|
|
wide = {'kind': B.STRIP_X, 'x0': 4.5, 'x1': 6.5, 'y0': 2.0, 'y1': 5.0,
|
|
'strip_ft': 2.0}
|
|
assert B.covers(narrow, wide) is not None # narrow donor -> wide target
|
|
assert B.covers(wide, narrow) is None # will not fit
|
|
|
|
|
|
def test_a_feature_in_a_different_height_band_is_not_covered():
|
|
"""Requirement 6. The measurement this rule comes from: two features share
|
|
an x-range while sitting in different Z bands."""
|
|
low = {'kind': B.OPENING, 'x0': 4.0, 'x1': 6.0, 'y0': 1.0, 'y1': 3.0}
|
|
high = {'kind': B.OPENING, 'x0': 4.0, 'x1': 6.0, 'y0': 8.0, 'y1': 10.0}
|
|
assert B.covers(low, high) is None
|
|
|
|
|
|
def test_position_is_absolute_not_proportional():
|
|
"""Requirement 5: the same rectangle on panels of different lengths is the
|
|
same feature, and a mid-panel rectangle at the same FRACTION is not."""
|
|
short = piece('PNL617', 20.0, 10.0, 8.0, [(4.0, 6.0, 2.0, 5.0)])
|
|
long = piece('PNL618', 40.0, 10.0, 8.0, [(4.0, 6.0, 2.0, 5.0)])
|
|
scaled = piece('PNL619', 40.0, 10.0, 8.0, [(8.0, 12.0, 2.0, 5.0)])
|
|
s = [f for f in B.features_for(short) if f['kind'] == B.OPENING][0]
|
|
l = [f for f in B.features_for(long) if f['kind'] == B.OPENING][0]
|
|
p = [f for f in B.features_for(scaled) if f['kind'] == B.OPENING][0]
|
|
assert B.covers(l, s) == 0.0
|
|
assert B.covers(p, s) is None
|
|
|
|
|
|
def test_distance_is_in_inches_and_comparable_across_kinds():
|
|
"""Requirement 4: one unit for every kind, and that unit is INCHES.
|
|
|
|
The records are feet -- 0.5 ft of corner offset -- and `covers` crosses
|
|
the unit seam exactly once on the way out, so both kinds come back as
|
|
6.00 in. A strip and an opening that are off by the same amount get the
|
|
same number; there is no per-kind weighting and none was asked for.
|
|
"""
|
|
o1 = {'kind': B.OPENING, 'x0': 4.0, 'x1': 6.0, 'y0': 2.0, 'y1': 5.0}
|
|
o2 = {'kind': B.OPENING, 'x0': 4.5, 'x1': 6.0, 'y0': 2.0, 'y1': 5.0}
|
|
s1 = {'kind': B.STRIP_Y, 'x0': 4.0, 'x1': 6.0, 'y0': 2.0, 'y1': 5.0,
|
|
'strip_ft': 3.0}
|
|
s2 = {'kind': B.STRIP_Y, 'x0': 4.5, 'x1': 6.0, 'y0': 2.0, 'y1': 5.0,
|
|
'strip_ft': 3.0}
|
|
assert B.covers(o2, o1) == pytest.approx(6.0) # 0.5 ft -> 6 in
|
|
assert B.covers(s2, s1) == pytest.approx(6.0)
|
|
|
|
|
|
def test_the_feet_to_inch_conversion_happens_exactly_once():
|
|
"""THE 12x GUARD. This project has already lost time to a conversion
|
|
applied at the wrong boundary, so the factor is pinned by test.
|
|
|
|
The records are and stay feet -- `covers` must not mutate them -- and the
|
|
returned distance is exactly twelve times the feet figure, not once and
|
|
not one hundred and forty-four times.
|
|
"""
|
|
a = {'kind': B.OPENING, 'x0': 0.0, 'x1': 10.0, 'y0': 0.0, 'y1': 10.0}
|
|
b = {'kind': B.OPENING, 'x0': 1.0, 'x1': 10.0, 'y0': 0.0, 'y1': 10.0}
|
|
assert B.covers(b, a) == pytest.approx(12.0) # 1.0 ft of offset
|
|
assert a['x0'] == 0.0 and b['x0'] == 1.0 # records untouched
|
|
assert B.EXACT_TOL_IN == pytest.approx(B.EXACT_TOL_FT * 12.0)
|
|
|
|
|
|
# ------------------------------------------------------------- the grain
|
|
#
|
|
# Two decimal inches is 0.01 in, an order of magnitude finer than any
|
|
# tolerance this tool uses. These are the tests that keep the DISPLAY the
|
|
# owner asked for from turning into a claim of PRECISION nobody measured.
|
|
|
|
def test_a_non_exact_distance_lands_on_the_eighth_inch_grain():
|
|
"""Every distance the report can print is a multiple of 1/8 in."""
|
|
a = {'kind': B.OPENING, 'x0': 0.0, 'x1': 10.0, 'y0': 0.0, 'y1': 10.0}
|
|
for offset_ft in (0.05, 0.1, 0.2, 0.37, 0.5, 1.0, 2.13):
|
|
b = dict(a, x0=offset_ft)
|
|
got = B.covers(b, a)
|
|
steps = got / B.GRAIN_IN
|
|
assert steps == pytest.approx(round(steps), abs=1e-9), (offset_ft, got)
|
|
|
|
|
|
def test_differences_finer_than_the_grain_do_not_reorder_anything():
|
|
"""0.01 in apart is not a difference this tool can measure, so the two
|
|
donors come back EQUAL and fall through to the next tie-break rather than
|
|
being separated by noise."""
|
|
t = {'kind': B.OPENING, 'x0': 0.0, 'x1': 10.0, 'y0': 0.0, 'y1': 10.0}
|
|
near = dict(t, x0=0.0400) # 0.480 in of offset
|
|
nearer = dict(t, x0=0.0392) # 0.470 in of offset -- 0.01 in closer
|
|
assert B.covers(near, t) == B.covers(nearer, t)
|
|
assert B.covers(near, t) == pytest.approx(0.5)
|
|
|
|
|
|
def test_the_grain_never_swallows_the_difference_between_off_and_exact():
|
|
"""Quantizing to 1/8 in would round a 1/16 in miss down to zero, and zero
|
|
means EXACT -- the one word in this report that has to keep meaning
|
|
measured-identical. A hair off is floored, never rounded to exact."""
|
|
t = {'kind': B.OPENING, 'x0': 0.0, 'x1': 10.0, 'y0': 0.0, 'y1': 10.0}
|
|
hair = dict(t, x0=0.004) # 0.048 in -- under half a grain step
|
|
got = B.covers(hair, t)
|
|
assert got > 0.0
|
|
assert got == pytest.approx(B.EXACT_TOL_IN)
|
|
assert B.covers(t, t) == 0.0
|
|
|
|
|
|
def test_through_and_blind_are_not_the_same_reinforcing():
|
|
a = {'kind': B.OPENING, 'x0': 4.0, 'x1': 6.0, 'y0': 2.0, 'y1': 5.0,
|
|
'through': True}
|
|
b = dict(a, through=False)
|
|
assert B.covers(a, b) is None
|
|
assert B.covers(a, dict(a)) == 0.0
|
|
|
|
|
|
def test_unjudged_penetration_is_not_read_as_blind():
|
|
"""`through=None` is could-not-judge. Treating it as False would invent a
|
|
judgement nobody made -- the standing rule on this project."""
|
|
through = {'kind': B.OPENING, 'x0': 4.0, 'x1': 6.0, 'y0': 2.0, 'y1': 5.0,
|
|
'through': True}
|
|
unjudged = dict(through, through=None)
|
|
blind = dict(through, through=False)
|
|
assert B.covers(unjudged, through) == 0.0
|
|
assert B.covers(through, unjudged) == 0.0
|
|
assert B.covers(unjudged, blind) == 0.0
|
|
assert B.covers(blind, through) is None
|
|
|
|
|
|
# --------------------------------------------- the approximate-outline flag
|
|
|
|
def test_an_approximated_edge_never_scores_exact():
|
|
"""A piece whose outline the index cannot see must not be reported as an
|
|
exact edge match. It ranks just behind exact, and is never called exact."""
|
|
p = piece('PNL620', 20.0, 10.0, 8.0, [(4.0, 6.0, 2.0, 5.0)])
|
|
exact = B.features_for(p, outline_approximate=False)
|
|
approx = B.features_for(p, outline_approximate=True)
|
|
edge = [i for i, f in enumerate(exact) if f['between'] == B.VOID_EDGE]
|
|
assert edge
|
|
for i in edge:
|
|
assert B.covers(exact[i], exact[i]) == 0.0
|
|
assert B.covers(approx[i], exact[i]) == B.EXACT_TOL_IN
|
|
assert B.covers(exact[i], approx[i]) == B.EXACT_TOL_IN
|
|
|
|
|
|
def test_an_approximated_piece_still_covers_interior_ligaments_exactly():
|
|
"""The flag is about edges. A strip between two voids does not touch the
|
|
outline, so nothing about it was approximated."""
|
|
p = piece('PNL621', 20.0, 10.0, 8.0,
|
|
[(4.0, 6.0, 2.0, 5.0), (9.0, 11.0, 2.0, 5.0)])
|
|
exact = B.features_for(p, outline_approximate=False)
|
|
approx = B.features_for(p, outline_approximate=True)
|
|
i = exact.index(find(exact, B.STRIP_X, 6.0, 9.0, 2.0, 5.0))
|
|
assert B.covers(approx[i], exact[i]) == 0.0
|
|
|
|
|
|
def test_an_approximate_donor_ranks_behind_an_exact_one():
|
|
"""End to end: the flag has to change the ANSWER, not just a field."""
|
|
target = piece('PNL622', 20.0, 10.0, 8.0, [(4.0, 6.0, 2.0, 5.0)])
|
|
good = piece('PNL623', 20.0, 10.0, 8.0, [(4.0, 6.0, 2.0, 5.0)])
|
|
approx = piece('PNL624', 20.0, 10.0, 8.0, [(4.0, 6.0, 2.0, 5.0)])
|
|
sug, info = B.propose(target, [good, approx], donor_filter=PNL,
|
|
approximate={'PNL624'})
|
|
assert sug.best.donors == ('PNL623',)
|
|
assert info['approximate_in_pool'] == ['PNL624']
|
|
|
|
|
|
# ------------------------------------------------------------ the filter
|
|
|
|
def base(name, thickness=8.0, width=10.0, **kw):
|
|
return piece(name, 20.0, width, thickness, [(4.0, 6.0, 2.0, 5.0)], **kw)
|
|
|
|
|
|
# Marks here are the project's `PNL...` placeholders, so the tests state the
|
|
# prefix they mean rather than borrowing the real one. That the SHIPPED
|
|
# default is wall-panels-only is asserted directly, just below.
|
|
PNL = B.DonorFilter(prefixes=('PNL',))
|
|
|
|
|
|
def test_the_shipped_default_declares_no_prefix_of_its_own():
|
|
"""The owner's ruling still stands -- only wall panels donate -- but it
|
|
is a JOB fact now, declared in the job knowledge and handed in by
|
|
`suggest_for_piece` (see vibe/scope.py). A prefix baked in here would be
|
|
one job's division of labour hardcoded into a shareable repository,
|
|
which is the same silent exclusion this codebase exists to refuse.
|
|
|
|
None means no prefix rule, which is what an UNDECLARED scope must
|
|
produce: judge everything, and say so on the report.
|
|
"""
|
|
assert B.DonorFilter().prefixes is None
|
|
|
|
|
|
def test_only_the_wanted_prefix_survives():
|
|
kept, rejected = B.select_donors(
|
|
[base('PNL002'), base('BM001'), base('SS001'), base('CL001')],
|
|
base('PNL001'), PNL)
|
|
assert [r['name'] for r in kept] == ['PNL002']
|
|
assert rejected['prefix'] == ['BM001', 'CL001', 'SS001']
|
|
|
|
|
|
def test_thickness_must_match():
|
|
kept, rejected = B.select_donors(
|
|
[base('PNL002', thickness=8.0), base('PNL003', thickness=12.0)],
|
|
base('PNL001', thickness=8.0), PNL)
|
|
assert [r['name'] for r in kept] == ['PNL002']
|
|
assert rejected['thickness'] == ['PNL003']
|
|
|
|
|
|
def test_width_is_not_filtered_by_default():
|
|
"""The arguable one. Off by default so a filter never excludes a
|
|
better-shaped donor -- the owner's standing correction."""
|
|
assert B.DonorFilter().max_width_diff_ft is None
|
|
kept, _ = B.select_donors([base('PNL002', width=30.0)],
|
|
base('PNL001', width=10.0), PNL)
|
|
assert [r['name'] for r in kept] == ['PNL002']
|
|
|
|
|
|
def test_width_can_be_switched_on():
|
|
kept, rejected = B.select_donors(
|
|
[base('PNL002', width=30.0), base('PNL003', width=10.5)],
|
|
base('PNL001', width=10.0),
|
|
B.DonorFilter(prefixes=('PNL',), max_width_diff_ft=1.0))
|
|
assert [r['name'] for r in kept] == ['PNL003']
|
|
assert rejected['width'] == ['PNL002']
|
|
|
|
|
|
def test_a_piece_is_not_its_own_donor():
|
|
kept, rejected = B.select_donors(
|
|
[base('PNL001'), base('PNL002')], base('PNL001'), PNL)
|
|
assert [r['name'] for r in kept] == ['PNL002']
|
|
assert rejected['self'] == ['PNL001']
|
|
|
|
|
|
def test_on_hold_is_never_a_donor():
|
|
"""The index already drops these; the filter re-asserts it, because a
|
|
record can reach `propose` from somewhere other than the cache."""
|
|
kept, rejected = B.select_donors(
|
|
[base('PNL002', on_hold=1), base('PNL003', reinforced=0)],
|
|
base('PNL001'), PNL)
|
|
assert kept == []
|
|
assert rejected['not_eligible'] == ['PNL002', 'PNL003']
|
|
|
|
|
|
def test_never_measured_and_measured_empty_are_rejected_separately():
|
|
"""The distinction the index keeps must survive the filter."""
|
|
unjudged = core.piece_record('PNL002', 1, 'sig', 1, 0, error='no solid')
|
|
empty = piece('PNL003', 20.0, 10.0, 8.0, [])
|
|
kept, rejected = B.select_donors([unjudged, empty], base('PNL001'), PNL)
|
|
assert kept == []
|
|
assert rejected['unjudged'] == ['PNL002']
|
|
assert rejected['no_features'] == ['PNL003']
|
|
|
|
|
|
# --------------------------------- require_features INVERTS for a featureless
|
|
# target (owner, 2026-09-01, hit on a real WP2.0 piece with no openings): "如
|
|
# 果扫描到目标没有opening,那么就找另一块没有opening的板子给target当doner。"
|
|
|
|
|
|
def test_a_featureless_donor_is_kept_when_the_target_is_also_featureless():
|
|
"""The reported defect, as a fixture: WP397-shaped, not WP397. A target
|
|
measured to have no openings must not have its featureless donor dropped
|
|
by the same rule that (correctly) drops one when the target has voids."""
|
|
target = piece('PNL001', 20.0, 10.0, 8.0, [])
|
|
twin = piece('PNL002', 20.0, 10.0, 8.0, [])
|
|
kept, rejected = B.select_donors([twin], target, PNL)
|
|
assert [r['name'] for r in kept] == ['PNL002']
|
|
assert 'no_features' not in rejected
|
|
|
|
|
|
def test_the_inversion_does_not_relax_design_thickness_or_scope():
|
|
"""The owner's very next sentence: "很多其他东西依然是有用的,比如说design
|
|
number。" One rule inverted; the rest of the filter is unmoved."""
|
|
target = piece('PNL001', 20.0, 10.0, 8.0, [], design='PNL2.0')
|
|
wrong_design = piece('PNL002', 20.0, 10.0, 8.0, [], design='PNL1.0')
|
|
wrong_thickness = piece('PNL003', 20.0, 10.0, 12.0, [], design='PNL2.0')
|
|
out_of_scope = piece('BM001', 20.0, 10.0, 8.0, [], design='PNL2.0')
|
|
kept, rejected = B.select_donors(
|
|
[wrong_design, wrong_thickness, out_of_scope], target, PNL)
|
|
assert kept == []
|
|
assert rejected['design'] == ['PNL002']
|
|
assert rejected['thickness'] == ['PNL003']
|
|
assert rejected['prefix'] == ['BM001']
|
|
|
|
|
|
def test_an_unjudged_target_does_not_take_the_inverted_path():
|
|
"""`features is None` (never measured) must NOT read as `features == []`
|
|
(measured, empty) -- the project's cardinal distinction. A donor nobody
|
|
would want to see offered for a piece nobody could even measure."""
|
|
unjudged_target = core.piece_record('PNL001', 1, 'sig', 1, 0,
|
|
error='no solid')
|
|
featureless_donor = piece('PNL002', 20.0, 10.0, 8.0, [])
|
|
kept, rejected = B.select_donors([featureless_donor], unjudged_target,
|
|
PNL)
|
|
assert kept == []
|
|
assert rejected['no_features'] == ['PNL002']
|
|
|
|
|
|
def test_a_donor_with_features_survives_a_featureless_target_too():
|
|
"""Never rejected for this reason either way -- the question of whether
|
|
it should rank behind a featureless twin is `shape_search_for`'s, not the
|
|
filter's."""
|
|
target = piece('PNL001', 20.0, 10.0, 8.0, [])
|
|
has_features = base('PNL002')
|
|
kept, _rejected = B.select_donors([has_features], target, PNL)
|
|
assert [r['name'] for r in kept] == ['PNL002']
|
|
|
|
|
|
# --------------------------------------------- the featureless-target search
|
|
|
|
def test_shape_search_ranks_featureless_donors_above_ones_with_openings():
|
|
target = piece('PNL001', 20.0, 10.0, 8.0, [])
|
|
featureless = piece('PNL002', 20.0, 10.0, 8.0, [])
|
|
has_features = base('PNL003')
|
|
search = B.shape_search_for(target, [has_features, featureless])
|
|
assert [r['name'] for r in search['eligible']] == ['PNL002']
|
|
assert [r['name'] for r in search['secondary']] == ['PNL003']
|
|
assert search['considered'] == 2
|
|
|
|
|
|
def test_shape_search_ranks_the_closer_shape_first():
|
|
target = piece('PNL001', 20.0, 10.0, 8.0, [])
|
|
close = piece('PNL002', 20.5, 10.0, 8.0, [])
|
|
far = piece('PNL003', 24.0, 10.0, 8.0, [])
|
|
search = B.shape_search_for(target, [far, close])
|
|
assert [r['name'] for r in search['eligible']] == ['PNL002', 'PNL003']
|
|
assert search['degenerate'] is False
|
|
|
|
|
|
def test_shape_search_flags_a_genuine_tie_as_degenerate():
|
|
"""Two panels of the same design very often ARE the same length and
|
|
width -- see `precast-nomenclature`. Ranking a coin flip as an order
|
|
would be a false precision this tool refuses everywhere else."""
|
|
target = piece('PNL001', 20.0, 10.0, 8.0, [])
|
|
twin_a = piece('PNL003', 20.0, 10.0, 8.0, [])
|
|
twin_b = piece('PNL002', 20.0, 10.0, 8.0, [])
|
|
search = B.shape_search_for(target, [twin_a, twin_b])
|
|
assert search['degenerate'] is True
|
|
# Still ordered -- by name, the last tie-break -- just not claimed as a
|
|
# ranking by fit.
|
|
assert [r['name'] for r in search['eligible']] == ['PNL002', 'PNL003']
|
|
|
|
|
|
def test_shape_search_one_donor_is_not_degenerate():
|
|
"""Nothing to rank between is a plain single answer, not a tie."""
|
|
target = piece('PNL001', 20.0, 10.0, 8.0, [], design='PNL2.0')
|
|
lone = piece('PNL002', 20.0, 10.0, 8.0, [], design='PNL2.0')
|
|
search = B.shape_search_for(target, [lone])
|
|
assert search['degenerate'] is False
|
|
assert [r['name'] for r in search['eligible']] == ['PNL002']
|
|
|
|
|
|
def test_propose_finds_the_featureless_donor_for_a_featureless_target():
|
|
"""End to end, off Revit: the fixture analogue of the real defect. A
|
|
target with no openings must come back with its matching, featureless
|
|
donor as the eligible pool, must not reject it, and must still refuse a
|
|
donor of a different design."""
|
|
target = piece('PNL2_001', 26.4167, 9.8333, 8.0, [], design='PNL2.0')
|
|
twin = piece('PNL2_002', 26.4167, 9.8333, 8.0, [], design='PNL2.0')
|
|
wrong_design = piece('PNL1_001', 26.4167, 9.8333, 8.0, [],
|
|
design='PNL1.0')
|
|
has_openings = base('PNL2_003', design='PNL2.0')
|
|
sug, info = B.propose(target, [twin, wrong_design, has_openings],
|
|
donor_filter=PNL)
|
|
assert sug is None
|
|
assert info['target_status'] == 'no_features'
|
|
search = info['shape_search']
|
|
assert search['ran'] is True
|
|
assert [r['name'] for r in search['eligible']] == ['PNL2_002']
|
|
assert [r['name'] for r in search['secondary']] == ['PNL2_003']
|
|
all_named = ([r['name'] for r in search['eligible']] +
|
|
[r['name'] for r in search['secondary']])
|
|
assert 'PNL1_001' not in all_named
|
|
assert info['rejected_names']['design'] == ['PNL1_001']
|
|
assert 'no_features' not in info['rejected_names']
|
|
|
|
|
|
# ------------------------------------------------- the design-number filter
|
|
#
|
|
# Owner, 2026-09-01: "选择母版的目标,必须得是跟自己的design number一样的panel。
|
|
# 我刚在一个WP2.0上面跑了reinforce,给我俩WP1.0的母版,压根用不了。" 必须 -- a
|
|
# filter, not a ranking term. Every test below is about that one word.
|
|
|
|
|
|
def test_a_donor_of_another_design_is_not_a_donor():
|
|
"""The reported defect, as one test: a `PNL2.0` target offered `PNL1.0`
|
|
templates. It must not be a low-ranked suggestion; it must be gone."""
|
|
kept, rejected = B.select_donors(
|
|
[base('PNL002', design='PNL1.0'), base('PNL003', design='PNL2.0')],
|
|
base('PNL001', design='PNL2.0'), PNL)
|
|
assert [r['name'] for r in kept] == ['PNL003']
|
|
assert rejected['design'] == ['PNL002']
|
|
|
|
|
|
def test_the_same_design_survives_and_nothing_else_does():
|
|
kept, _rejected = B.select_donors(
|
|
[base('PNL002', design='PNL2.0'), base('PNL003', design='PNL2.0'),
|
|
base('PNL004', design='PNL1.0'), base('PNL005', design='PNL30.0')],
|
|
base('PNL001', design='PNL2.0'), PNL)
|
|
assert [r['name'] for r in kept] == ['PNL002', 'PNL003']
|
|
|
|
|
|
def test_a_packet_suffix_is_not_a_different_design():
|
|
"""The established nomenclature ruling, reused rather than reinvented:
|
|
`.0` is a cover sheet, `.A` is a note further into the same packet, and a
|
|
piece may carry the bare number. One design, one family, four spellings.
|
|
|
|
This is `specformat.design_key` -- the same reduction Ticket QC already
|
|
checks a piece against its ticket with -- and not a rule written here.
|
|
"""
|
|
kept, rejected = B.select_donors(
|
|
[base('PNL002', design='PNL1.A'), base('PNL003', design='PNL1'),
|
|
base('PNL004', design='PNL1.B1'), base('PNL005', design='PNL2.A')],
|
|
base('PNL001', design='PNL1.0'), PNL)
|
|
assert [r['name'] for r in kept] == ['PNL002', 'PNL003', 'PNL004']
|
|
assert rejected['design'] == ['PNL005']
|
|
|
|
|
|
def test_a_donor_whose_design_could_not_be_read_is_refused_not_assumed():
|
|
"""COULD NOT JUDGE IS NOT A MATCH. `None == None` is true in Python and
|
|
false in precast: two pieces nobody read are not the same design."""
|
|
kept, rejected = B.select_donors(
|
|
[base('PNL002', design=None), base('PNL003', design=' '),
|
|
base('PNL004', design='...')],
|
|
base('PNL001', design='PNL1.0'), PNL)
|
|
assert kept == []
|
|
assert rejected['design_unknown'] == ['PNL002', 'PNL003', 'PNL004']
|
|
|
|
|
|
def test_a_target_with_no_readable_design_matches_nobody():
|
|
kept, rejected = B.select_donors(
|
|
[base('PNL002', design='PNL1.0')], base('PNL001', design=None), PNL)
|
|
assert kept == []
|
|
assert rejected['design_target_unknown'] == ['PNL002']
|
|
|
|
|
|
def test_the_design_rule_is_on_by_default():
|
|
assert B.DonorFilter().match_design is True
|
|
assert B.DonorFilter().design_re is None
|
|
|
|
|
|
def test_the_scope_is_reported_before_the_design():
|
|
"""Another engineer's beam is out of SCOPE; it was never a candidate, and
|
|
calling it a design mismatch would bury the real reason."""
|
|
_kept, rejected = B.select_donors(
|
|
[base('BM001', design='BM9.0')], base('PNL001', design='PNL1.0'), PNL)
|
|
assert rejected == {'prefix': ['BM001']}
|
|
|
|
|
|
def test_the_plant_pattern_is_used_when_one_is_given():
|
|
"""`nomenclature.design_number_re` is plant knowledge and is handed in,
|
|
never read here -- this module has to run with no knowledge layer at all.
|
|
A pattern that stops at the letters makes PNL1 and PNL2 one design."""
|
|
loose = B.DonorFilter(prefixes=('PNL',), design_re=r'^([A-Za-z]+)')
|
|
kept, _rejected = B.select_donors(
|
|
[base('PNL002', design='PNL2.0')],
|
|
base('PNL001', design='PNL1.0'), loose)
|
|
assert [r['name'] for r in kept] == ['PNL002']
|
|
|
|
|
|
def test_ranking_inside_one_design_is_untouched():
|
|
"""The filter removes candidates; it must not reorder the survivors. Same
|
|
three donors, same target, with and without a cross-design distractor in
|
|
the pool: identical proposals in identical order."""
|
|
target = piece('PNL801', 20.0, 10.0, 8.0,
|
|
[(4.0, 6.0, 2.0, 5.0), (10.0, 12.0, 2.0, 5.0)])
|
|
near = piece('PNL802', 20.0, 10.0, 8.0,
|
|
[(4.0, 6.0, 2.0, 5.0), (10.0, 12.0, 2.0, 5.0)])
|
|
far = piece('PNL803', 20.0, 10.0, 8.0,
|
|
[(4.05, 6.05, 2.0, 5.0), (10.05, 12.05, 2.0, 5.0)])
|
|
other = piece('PNL804', 20.0, 10.0, 8.0,
|
|
[(4.0, 6.0, 2.0, 5.0), (10.0, 12.0, 2.0, 5.0)],
|
|
design='PNL9.0')
|
|
|
|
clean, _i = B.propose(target, [near, far], donor_filter=PNL)
|
|
mixed, _j = B.propose(target, [near, far, other], donor_filter=PNL)
|
|
assert clean.status == mixed.status
|
|
assert ([p.donors for p in clean.proposals] ==
|
|
[p.donors for p in mixed.proposals])
|
|
assert ([p.distances for p in clean.proposals] ==
|
|
[p.distances for p in mixed.proposals])
|
|
assert 'PNL804' not in (mixed.best.donors or ())
|
|
|
|
|
|
# --------------------------------------------- the four design refusals
|
|
#
|
|
# None of them may degrade into offering a donor of another design, labelled
|
|
# or otherwise. `propose` returns NO suggestion for every one.
|
|
|
|
|
|
def test_no_donor_of_this_design_refuses_rather_than_offering_another():
|
|
target = piece('PNL901', 20.0, 10.0, 8.0, [(4.0, 6.0, 2.0, 5.0)],
|
|
design='PNL2.0')
|
|
pool = [piece('PNL902', 20.0, 10.0, 8.0, [(4.0, 6.0, 2.0, 5.0)],
|
|
design='PNL1.0'),
|
|
piece('PNL903', 20.0, 10.0, 8.0, [(4.0, 6.0, 2.0, 5.0)],
|
|
design='PNL1.A')]
|
|
sug, info = B.propose(target, pool, donor_filter=PNL)
|
|
assert sug is None # not a ranked list of PNL1s
|
|
assert info['design']['status'] == B.DESIGN_NONE_MATCH
|
|
assert info['design']['refused'] is True
|
|
assert info['design']['target_key'] == 'PNL2'
|
|
assert info['design']['candidates'] == 2
|
|
assert info['design']['designs_seen'] == {'PNL1': 2}
|
|
|
|
|
|
def test_an_index_that_never_recorded_the_design_is_unjudged_not_matching():
|
|
"""A record written before the field existed. It is not evidence of
|
|
anything, least of all of a match."""
|
|
target = piece('PNL911', 20.0, 10.0, 8.0, [(4.0, 6.0, 2.0, 5.0)],
|
|
design='PNL2.0')
|
|
old = base('PNL912')
|
|
del old['design_number']
|
|
sug, info = B.propose(target, [old], donor_filter=PNL)
|
|
assert sug is None
|
|
assert info['design']['status'] == B.DESIGN_INDEX_NOT_RECORDED
|
|
assert info['design']['index_status'] == core.DESIGN_NOT_RECORDED
|
|
|
|
|
|
def test_an_index_whose_design_numbers_are_all_empty_is_unjudged():
|
|
"""Measured on the real index, 2026-09-01: 773 pieces, every one of them
|
|
`design_number: null`, because the build ran with no knowledge layer. The
|
|
field was there; nobody had read it. Not one donor may be offered off
|
|
that, and the refusal has to name a different fix from the one above."""
|
|
target = piece('PNL921', 20.0, 10.0, 8.0, [(4.0, 6.0, 2.0, 5.0)],
|
|
design='PNL2.0')
|
|
blank = [base('PNL922', design=None), base('PNL923', design=None)]
|
|
sug, info = B.propose(target, blank, donor_filter=PNL)
|
|
assert sug is None
|
|
assert info['design']['status'] == B.DESIGN_INDEX_UNREAD
|
|
assert info['design']['index_status'] == core.DESIGN_UNREAD
|
|
|
|
|
|
def test_a_target_with_no_design_number_refuses_before_it_compares():
|
|
target = piece('PNL931', 20.0, 10.0, 8.0, [(4.0, 6.0, 2.0, 5.0)],
|
|
design=None)
|
|
pool = [base('PNL932', design='PNL1.0')]
|
|
sug, info = B.propose(target, pool, donor_filter=PNL)
|
|
assert sug is None
|
|
assert info['design']['status'] == B.DESIGN_TARGET_UNKNOWN
|
|
|
|
|
|
def test_a_pool_emptied_before_the_design_test_is_not_a_design_refusal():
|
|
"""The scope, eligibility and 'never measured' all empty the pool BEFORE
|
|
the design is looked at. Blaming the design rule for their work would hide
|
|
the real reason under a confident sentence, so the design verdict stands
|
|
down and NO_DONORS -- the older finding -- is what the page reports."""
|
|
target = piece('PNL941', 20.0, 10.0, 8.0, [(4.0, 6.0, 2.0, 5.0)],
|
|
design='PNL2.0')
|
|
held = base('PNL942', design='PNL1.0', on_hold=1)
|
|
sug, info = B.propose(target, [held], donor_filter=PNL)
|
|
assert info['design']['refused'] is False
|
|
assert info['design']['ran'] is False
|
|
assert sug is not None and sug.status == match.NO_DONORS
|
|
|
|
|
|
def test_the_four_refusals_are_four_different_answers():
|
|
"""They have four different fixes -- rebuild the index, map the role, fix
|
|
the piece, detail a first one by hand -- so they may never collapse."""
|
|
assert len(set(B.DESIGN_REFUSALS)) == 4
|
|
assert B.DESIGN_OK not in B.DESIGN_REFUSALS
|
|
|
|
|
|
def test_every_rejection_is_named_and_counted():
|
|
kept, rejected = B.select_donors(
|
|
[base('PNL001'), base('BM001'), base('PNL002', thickness=12.0),
|
|
base('PNL003')], base('PNL001'), PNL)
|
|
assert [r['name'] for r in kept] == ['PNL003']
|
|
assert set(rejected) == {'self', 'prefix', 'thickness'}
|
|
|
|
|
|
# -------------------------------------------------------------- end to end
|
|
|
|
def test_the_worked_example_shape_picks_the_matching_donor():
|
|
"""Rebuilt from the worked example with invented marks: a donor that
|
|
matches the left two-thirds, and a distractor that matches nothing."""
|
|
target = piece('PNL701', 27.625, 11.5, 8.0, [
|
|
(0.0, 0.875, 1.125, 3.0), (0.0, 0.667, 11.292, 11.5, False),
|
|
(6.5, 10.583, 0.354, 4.479), (14.344, 18.427, 0.354, 4.479),
|
|
(24.042, 25.875, 1.125, 3.0), (24.25, 27.625, 6.25, 11.5)])
|
|
twin = piece('PNL702', 24.917, 11.5, 8.0, [
|
|
(0.0, 0.875, 1.125, 3.0), (0.0, 0.667, 11.292, 11.5, False),
|
|
(6.5, 10.583, 0.354, 4.479), (14.344, 18.427, 0.354, 4.479),
|
|
(24.042, 24.917, 1.125, 3.0), (24.25, 24.917, 11.292, 11.5, False)])
|
|
other = piece('PNL703', 21.0, 11.5, 8.0, [(3.0, 4.0, 7.0, 9.0)])
|
|
sug, info = B.propose(target, [twin, other], donor_filter=PNL)
|
|
assert sug.status == match.PARTIAL
|
|
assert 'PNL702' in sug.best.donors
|
|
assert sug.best.exact_count > 0
|
|
# The right-end features have no donor here, and the result says so
|
|
# rather than quietly covering them.
|
|
assert sug.uncovered
|
|
|
|
|
|
def test_no_donors_is_not_no_coverage():
|
|
target = base('PNL001')
|
|
sug, info = B.propose(target, [], donor_filter=PNL)
|
|
assert sug.status == match.NO_DONORS
|
|
assert info['donors'] == 0
|
|
|
|
|
|
def test_a_target_never_measured_yields_no_suggestion():
|
|
target = core.piece_record('PNL001', 1, 'sig', 1, 0, error='no solid')
|
|
sug, info = B.propose(target, [base('PNL002')], donor_filter=PNL)
|
|
assert sug is None
|
|
assert info['target_status'] == 'unjudged'
|
|
|
|
|
|
def test_a_target_measured_with_no_voids_is_a_different_answer():
|
|
sug, info = B.propose(piece('PNL001', 20.0, 10.0, 8.0, []),
|
|
[base('PNL002')], donor_filter=PNL)
|
|
assert sug is None
|
|
assert info['target_status'] == 'no_features'
|
|
|
|
|
|
def test_explain_names_the_donor_for_every_target_feature():
|
|
target = base('PNL001')
|
|
sug, info = B.propose(target, [base('PNL002')], donor_filter=PNL)
|
|
rows = B.explain(sug, info)
|
|
assert len(rows) == len(info['target_features'])
|
|
assert all(r['donor'] == 'PNL002' and r['exact'] for r in rows)
|
|
|
|
|
|
def test_explain_reports_an_uncovered_feature_as_uncovered():
|
|
target = piece('PNL001', 20.0, 10.0, 8.0,
|
|
[(4.0, 6.0, 2.0, 5.0), (14.0, 16.0, 2.0, 5.0)])
|
|
donor = piece('PNL002', 20.0, 10.0, 8.0, [(4.0, 6.0, 2.0, 5.0)])
|
|
sug, info = B.propose(target, [donor], donor_filter=PNL)
|
|
rows = B.explain(sug, info)
|
|
assert [r['donor'] for r in rows].count(None) > 0
|
|
|
|
|
|
def test_explain_surfaces_registry_feature_ids_for_target_and_donor():
|
|
"""The rows carry the shared-vocabulary ids so a reader can see the
|
|
numbering. An exact-twin donor shares the target's descriptor, hence its
|
|
id -- 'target #k <-> donor #k'."""
|
|
target = base('PNL001')
|
|
donor = base('PNL002')
|
|
sug, info = B.propose(target, [donor], donor_filter=PNL)
|
|
# Stamp the vocabulary the way `suggest_from_index` does: built from the
|
|
# donor pool, carried as the plain registry dict.
|
|
cache = core.new_cache('m')
|
|
core.register_features(cache, [donor])
|
|
info['registry'] = cache['registry']
|
|
rows = B.explain(sug, info)
|
|
assert rows
|
|
row = rows[0]
|
|
assert row['target_feature_id'] is not None
|
|
assert row['donor_feature_id'] == row['target_feature_id']
|
|
|
|
|
|
def test_explain_target_id_is_none_when_no_donor_carries_the_shape():
|
|
"""A target feature whose SHAPE no donor produced projects to None -- a
|
|
read-only lookup miss, the informative "nobody has this shape" signal, and
|
|
it does not grow the vocabulary."""
|
|
target = piece('PNL001', 20.0, 10.0, 8.0,
|
|
[(4.0, 6.0, 2.0, 5.0), (14.0, 17.0, 2.0, 6.0)])
|
|
donor = piece('PNL002', 20.0, 10.0, 8.0, [(4.0, 6.0, 2.0, 5.0)])
|
|
sug, info = B.propose(target, [donor], donor_filter=PNL)
|
|
cache = core.new_cache('m')
|
|
core.register_features(cache, [donor])
|
|
next_before = core.Registry.deserialize(cache['registry']).serialize()['next_id']
|
|
info['registry'] = cache['registry']
|
|
rows = B.explain(sug, info)
|
|
# the target feature the donor cannot cover has a shape no donor produced
|
|
novel = [r for r in rows if r['donor'] is None]
|
|
assert novel
|
|
assert all(r['target_feature_id'] is None for r in novel)
|
|
# the read-only projection did not assign it a fresh id
|
|
next_after = core.Registry.deserialize(info['registry']).serialize()['next_id']
|
|
assert next_after == next_before
|
|
|
|
|
|
# --------------------------------------------------- MASTER, end to end
|
|
#
|
|
# The index records it, the matcher ranks on it, and this is the join. Two
|
|
# things have to hold here and nowhere else: the pool's masters are reported
|
|
# AFTER the donor filter (a master the thickness filter dropped is not one
|
|
# this answer could have used), and the flag reaches the matcher at all.
|
|
|
|
|
|
def test_the_pool_reports_only_the_masters_that_survived_the_filter():
|
|
target = base('PNL001')
|
|
kept_master = base('PNL002', master=True)
|
|
dropped_master = base('PNL003', thickness=12.0, master=True)
|
|
plain = base('PNL004')
|
|
sug, info = B.propose(target, [kept_master, dropped_master, plain],
|
|
donor_filter=PNL)
|
|
assert info['master_donors'] == ['PNL002']
|
|
assert 'PNL003' in info['rejected_names']['thickness']
|
|
|
|
|
|
def test_a_pool_with_no_master_says_so_with_an_empty_list_not_a_missing_key():
|
|
sug, info = B.propose(base('PNL001'), [base('PNL002')], donor_filter=PNL)
|
|
assert info['master_donors'] == []
|
|
|
|
|
|
def test_the_master_flag_reaches_the_ranking():
|
|
"""Two identical donors, one reviewed. The reviewed one is proposed first.
|
|
|
|
They are geometrically indistinguishable -- same rectangle, same panel --
|
|
so the distance vectors are equal and only the tie-break separates them.
|
|
PNL002 is listed first, so input order alone would put it on top.
|
|
"""
|
|
target = base('PNL001')
|
|
sug, info = B.propose(target, [base('PNL002'), base('PNL003', master=True)],
|
|
donor_filter=PNL)
|
|
assert sug.best.donors == ('PNL003',)
|
|
assert sug.best.masters == ('PNL003',)
|
|
assert sug.proposals[1].donors == ('PNL002',)
|
|
|
|
|
|
def test_a_master_does_not_outrank_a_better_fitting_donor():
|
|
"""The owner's ruling, held at the layer that actually runs on the index.
|
|
|
|
PNL003 is reviewed but its opening is 2 ft off; PNL002 is an exact twin
|
|
of the target and is not reviewed. Fit wins.
|
|
"""
|
|
target = piece('PNL001', 20.0, 10.0, 8.0, [(4.0, 6.0, 2.0, 5.0)])
|
|
twin = piece('PNL002', 20.0, 10.0, 8.0, [(4.0, 6.0, 2.0, 5.0)])
|
|
loose = piece('PNL003', 20.0, 10.0, 8.0, [(4.5, 6.5, 2.0, 5.0)],
|
|
master=True)
|
|
sug, info = B.propose(target, [twin, loose], donor_filter=PNL)
|
|
assert sug.best.donors == ('PNL002',)
|
|
assert sug.best.master_count == 0
|
|
|
|
|
|
def test_explain_rows_carry_whether_the_covering_donor_was_reviewed():
|
|
target = base('PNL001')
|
|
sug, info = B.propose(target, [base('PNL002', master=True)],
|
|
donor_filter=PNL)
|
|
rows = B.explain(sug, info)
|
|
assert rows
|
|
assert all(r['donor_master'] for r in rows if r['donor'] is not None)
|
|
|
|
|
|
def test_an_unread_signal_is_reported_as_not_run_not_as_no_masters():
|
|
"""Every donor `master=None` -- the role is unmapped, nothing was read."""
|
|
target = base('PNL001')
|
|
pool = [base('PNL002'), base('PNL003')]
|
|
for rec in pool:
|
|
rec['master'] = None
|
|
sug, info = B.propose(target, pool, donor_filter=PNL)
|
|
assert info['master_donors'] == []
|
|
assert info['master_unjudged'] is True
|
|
|
|
|
|
def test_a_pool_that_was_read_is_never_reported_as_unjudged():
|
|
sug, info = B.propose(base('PNL001'), [base('PNL002')], donor_filter=PNL)
|
|
assert info['master_unjudged'] is False
|
|
|
|
|
|
# --------------------------------------------- SAME ELEVATION, end to end
|
|
#
|
|
# The incident, and the ruling that fixed it. Owner, 2026-09-02, on a real
|
|
# run against WP425: five donors tied on every existing criterion -- 1
|
|
# donor, 5/5 covered, 4 exact, 0 reviewed, 0.04 in total distance, all five
|
|
# identical -- and the tie fell through to mark order, so WP133 printed
|
|
# first though WP234 shares WP425's own building elevation:
|
|
# "WP234明明在同一个elevation,为什么排名没有提前?" Approved as briefed the
|
|
# same day ("同意,就这么做"): elevation is an AFFINITY, the LAST tie-break,
|
|
# never a filter -- a cross-elevation donor with better coverage still wins.
|
|
#
|
|
# RULED AGAIN THE SAME DAY: elevation is a purely GEOMETRIC fact (a
|
|
# thickness-axis coordinate band), never a hand-typed parameter -- see
|
|
# `reinforcecore.elevation_band`'s module note. Fixtures below use `band(tag)`
|
|
# (defined above, next to `piece`/`base`) rather than a bare string: the same
|
|
# `tag` always returns the identical band, so `band('ELEV-3')` on two
|
|
# fixtures genuinely means "the same wall plane" the way a shared string
|
|
# label used to.
|
|
|
|
|
|
def test_the_incident_five_tied_donors_one_shares_elevation_wins():
|
|
"""WP425, reproduced with invented marks. Five donors, geometrically
|
|
identical to the target and to each other -- a dead tie on donor count,
|
|
exactness, MASTER and total distance -- and only one shares the
|
|
target's building elevation band. It ranks first, and the report can
|
|
say why: `sug.best.same_elevation` names it, exactly as `masters` would.
|
|
|
|
Without the affinity term this fell through to mark order and printed
|
|
the alphabetically-first donor instead of the one actually on the
|
|
target's own elevation -- PNL133, not PNL234, which is exactly what
|
|
happened on the real run this reproduces.
|
|
"""
|
|
target = base('PNL425', elevation_band=band('ELEV-3'))
|
|
donors = [
|
|
base('PNL133', elevation_band=band('ELEV-1')),
|
|
base('PNL166', elevation_band=band('ELEV-2')),
|
|
base('PNL234', elevation_band=band('ELEV-3')),
|
|
base('PNL267', elevation_band=band('ELEV-4')),
|
|
base('PNL271', elevation_band=None),
|
|
]
|
|
sug, info = B.propose(target, donors, donor_filter=PNL)
|
|
assert sug.best.donors == ('PNL234',)
|
|
assert sug.best.same_elevation == ('PNL234',)
|
|
assert info['target_elevation'] == {'group': None, 'band': band('ELEV-3')}
|
|
assert info['same_elevation_donors'] == ['PNL234']
|
|
assert info['same_elevation_sources'] == {'PNL234': core.ELEVATION_BY_BAND}
|
|
|
|
|
|
def test_cross_elevation_donor_with_better_coverage_still_wins():
|
|
"""The ranking-not-filtering rule, as an executable statement.
|
|
|
|
PNL501 is on a different elevation and fits the target's opening
|
|
exactly. PNL502 shares the target's elevation but is 0.5 ft off. Fit
|
|
wins, every time -- elevation never promotes a worse-shaped donor past
|
|
one the geometry has already ranked ahead of it.
|
|
"""
|
|
target = piece('PNL425', 20.0, 10.0, 8.0, [(4.0, 6.0, 2.0, 5.0)],
|
|
elevation_band=band('ELEV-3'))
|
|
exact_other_elevation = piece(
|
|
'PNL501', 20.0, 10.0, 8.0, [(4.0, 6.0, 2.0, 5.0)],
|
|
elevation_band=band('ELEV-9'))
|
|
loose_same_elevation = piece(
|
|
'PNL502', 20.0, 10.0, 8.0, [(4.5, 6.5, 2.0, 5.0)],
|
|
elevation_band=band('ELEV-3'))
|
|
sug, info = B.propose(target, [exact_other_elevation,
|
|
loose_same_elevation], donor_filter=PNL)
|
|
assert sug.best.donors == ('PNL501',)
|
|
assert sug.best.same_elevation == ()
|
|
|
|
|
|
def test_a_target_with_no_determined_elevation_prefers_nobody():
|
|
"""UNKNOWN IS A STATE, NOT A BLANK. The target's own elevation band
|
|
could not be determined, so nothing in the pool can be preferred on it
|
|
-- not even a donor whose band happens to be equally undetermined."""
|
|
target = base('PNL425', elevation_band=None)
|
|
donors = [base('PNL133', elevation_band=band('ELEV-1')),
|
|
base('PNL166', elevation_band=None)]
|
|
sug, info = B.propose(target, donors, donor_filter=PNL)
|
|
assert info['same_elevation_donors'] == []
|
|
assert sug.best.same_elevation == ()
|
|
|
|
|
|
def test_a_donor_with_no_determined_elevation_is_never_preferred():
|
|
"""The reverse case: the TARGET's elevation band is known, but a
|
|
donor's own could not be determined. That donor is never added to the
|
|
affinity set, even though a real match might exist -- could-not-judge
|
|
is never guessed as a match."""
|
|
target = base('PNL425', elevation_band=band('ELEV-1'))
|
|
donors = [base('PNL133', elevation_band=None),
|
|
base('PNL166', elevation_band=band('ELEV-9'))]
|
|
sug, info = B.propose(target, donors, donor_filter=PNL)
|
|
assert info['same_elevation_donors'] == []
|
|
|
|
|
|
def test_a_record_missing_the_elevation_band_key_entirely_does_not_crash():
|
|
"""A pre-migration cache record -- built before this field existed --
|
|
has no 'elevation_band' key at all, not even one holding None. The
|
|
affinity computation must read that the same as an explicit None rather
|
|
than raising, because a stale cache is exactly what the first press
|
|
after this change serves before a refresh rebuilds it."""
|
|
target = base('PNL425', elevation_band=band('ELEV-1'))
|
|
donor = base('PNL133', elevation_band=band('ELEV-1'))
|
|
del donor['elevation_band']
|
|
sug, info = B.propose(target, [donor], donor_filter=PNL)
|
|
assert info['same_elevation_donors'] == []
|
|
assert sug.best.same_elevation == ()
|
|
|
|
|
|
def test_a_pool_with_no_same_elevation_donor_says_so_with_an_empty_list():
|
|
sug, info = B.propose(base('PNL425', elevation_band=band('ELEV-1')),
|
|
[base('PNL133', elevation_band=band('ELEV-9'))],
|
|
donor_filter=PNL)
|
|
assert info['same_elevation_donors'] == []
|
|
|
|
|
|
def test_a_perpendicular_axis_donor_never_matches_even_numerically_close():
|
|
"""The owner's explicit confirmation: a donor thin in global Y never
|
|
shares an elevation with a target thin in global X, however close the
|
|
coordinates happen to be -- that is a wall returning a corner, not the
|
|
same wall plane."""
|
|
target = base('PNL425', elevation_band={'axis': 'X', 'min': 100.0,
|
|
'max': 100.667})
|
|
donor = base('PNL133', elevation_band={'axis': 'Y', 'min': 100.0,
|
|
'max': 100.667})
|
|
sug, info = B.propose(target, [donor], donor_filter=PNL)
|
|
assert info['same_elevation_donors'] == []
|
|
assert sug.best.same_elevation == ()
|
|
|
|
|
|
def test_a_donor_whose_band_only_abuts_the_targets_does_not_match():
|
|
"""The pinned edge case: touching at exactly one coordinate, no
|
|
interior overlap, is a stepped facade, not a shared wall plane."""
|
|
target = base('PNL425', elevation_band={'axis': 'X', 'min': 100.0,
|
|
'max': 100.667})
|
|
donor = base('PNL133', elevation_band={'axis': 'X', 'min': 100.667,
|
|
'max': 101.334})
|
|
sug, info = B.propose(target, [donor], donor_filter=PNL)
|
|
assert info['same_elevation_donors'] == []
|
|
assert sug.best.same_elevation == ()
|
|
|
|
|
|
# ---------------------------------------- THE ELEVATION ACCELERATOR (group)
|
|
#
|
|
# Owner's ruling, 2026-09-02, refining the geometric-only ruling the SAME
|
|
# day once MC's real model turned up a usable grouping parameter:
|
|
# "对MC而言,所有的墙应该都有一个参数叫Tagged_Sheet。如果俩panel这个参数一
|
|
# 样,那么他们就是同一个elevation,不用跑哪个几何引擎。" It is checked FIRST
|
|
# where it exists, but geometry stays the floor -- most jobs (plenty of
|
|
# plants, simple MC jobs) never map it, and that absence is normal, not a
|
|
# stand-down.
|
|
|
|
|
|
def test_same_group_matches_with_no_geometry_agreement_at_all():
|
|
"""The shortcut, at its starkest: matching groups, PERPENDICULAR bands
|
|
(which would never agree geometrically) -- the group still decides it,
|
|
with `core.ELEVATION_BY_GROUP` naming the source."""
|
|
target = base('PNL425', elevation_group='E3.16',
|
|
elevation_band={'axis': 'X', 'min': 100.0, 'max': 100.667})
|
|
donor = base('PNL133', elevation_group=' e3.16 ',
|
|
elevation_band={'axis': 'Y', 'min': 500.0, 'max': 500.667})
|
|
sug, info = B.propose(target, [donor], donor_filter=PNL)
|
|
assert info['same_elevation_donors'] == ['PNL133']
|
|
assert info['same_elevation_sources'] == {'PNL133': core.ELEVATION_BY_GROUP}
|
|
assert sug.best.same_elevation == ('PNL133',)
|
|
|
|
|
|
def test_a_different_group_falls_back_to_geometry_rather_than_vetoing_it():
|
|
"""A group mismatch does not exclude the donor -- it just fails to
|
|
answer tier 1, and the geometric floor gets to try (and here, agrees)."""
|
|
shared = {'axis': 'X', 'min': 100.0, 'max': 100.667}
|
|
target = base('PNL425', elevation_group='E3.16', elevation_band=shared)
|
|
donor = base('PNL133', elevation_group='E3.22', elevation_band=shared)
|
|
sug, info = B.propose(target, [donor], donor_filter=PNL)
|
|
assert info['same_elevation_donors'] == ['PNL133']
|
|
assert info['same_elevation_sources'] == {'PNL133': core.ELEVATION_BY_BAND}
|
|
|
|
|
|
def test_an_unmapped_group_on_both_sides_is_the_normal_case_geometry_answers():
|
|
"""No `elevation_group` anywhere -- the common state on a job with no
|
|
such parameter mapped -- and the geometric floor answers exactly as it
|
|
would have if the accelerator did not exist."""
|
|
shared = {'axis': 'X', 'min': 100.0, 'max': 100.667}
|
|
target = base('PNL425', elevation_band=shared)
|
|
donor = base('PNL133', elevation_band=shared)
|
|
sug, info = B.propose(target, [donor], donor_filter=PNL)
|
|
assert info['same_elevation_donors'] == ['PNL133']
|
|
assert info['same_elevation_sources'] == {'PNL133': core.ELEVATION_BY_BAND}
|
|
|
|
|
|
def test_group_on_only_one_side_falls_back_to_geometry():
|
|
shared = {'axis': 'X', 'min': 100.0, 'max': 100.667}
|
|
target = base('PNL425', elevation_group='E3.16', elevation_band=shared)
|
|
donor = base('PNL133', elevation_group=None, elevation_band=shared)
|
|
sug, info = B.propose(target, [donor], donor_filter=PNL)
|
|
assert info['same_elevation_donors'] == ['PNL133']
|
|
assert info['same_elevation_sources'] == {'PNL133': core.ELEVATION_BY_BAND}
|
|
|
|
|
|
# ----------------------------------------------------- the donor cap, plumbed
|
|
#
|
|
# The cap is the matcher's, and it is tested there. What has to be true HERE
|
|
# is that `propose` hands it down and reports it, because a cap the join
|
|
# silently drops is a cap that only exists in the unit tests.
|
|
|
|
|
|
def test_propose_declares_the_cap_it_ran_under():
|
|
target = piece('PNL640', 20.0, 10.0, 8.0, [(4.0, 6.0, 2.0, 5.0)])
|
|
donor = piece('PNL641', 20.0, 10.0, 8.0, [(4.0, 6.0, 2.0, 5.0)])
|
|
sug, info = B.propose(target, [donor], donor_filter=PNL)
|
|
assert info['donor_cap'] == 4
|
|
assert sug.donor_cap == 4
|
|
|
|
|
|
def test_the_shipped_cap_is_four():
|
|
"""The owner's number, asserted where it ships rather than trusted."""
|
|
assert match.DEFAULT_MAX_DONORS == 4
|
|
|
|
|
|
def test_propose_passes_the_cap_through_to_the_matcher():
|
|
"""Driven to 1 so the cap has to bite on a two-donor problem: the answer
|
|
is the better single donor plus a named gap, never a second donor."""
|
|
target = piece('PNL642', 30.0, 10.0, 8.0,
|
|
[(4.0, 6.0, 2.0, 5.0), (20.0, 22.0, 2.0, 5.0)])
|
|
left = piece('PNL643', 30.0, 10.0, 8.0, [(4.0, 6.0, 2.0, 5.0)])
|
|
right = piece('PNL644', 30.0, 10.0, 8.0, [(20.0, 22.0, 2.0, 5.0)])
|
|
two, _ = B.propose(target, [left, right], donor_filter=PNL)
|
|
assert len(two.best.donors) == 2
|
|
covered_by_two = two.best.covered_count
|
|
|
|
one, info = B.propose(target, [left, right], donor_filter=PNL,
|
|
max_donors=1)
|
|
assert info['donor_cap'] == 1
|
|
assert len(one.best.donors) == 1 # never a second donor
|
|
assert one.best.covered_count < covered_by_two
|
|
assert one.capped_out # and it says what it dropped
|
|
# The two kinds of gap stay apart: what the cap dropped is not what
|
|
# nobody has, whatever else this pair of donors could not reach.
|
|
assert not set(one.capped_out) & set(one.uncovered)
|
|
|
|
|
|
# ------------------------------------------------- the whole run, NO DOCUMENT
|
|
#
|
|
# `suggest_from_index` is `suggest_for_piece` with the cached target record in
|
|
# place of the live measurement, and it is the only thing standing between
|
|
# this feature and a Revit licence. What is asserted here is that it is the
|
|
# SAME run -- same filter, same covers, same info dict -- and that the two
|
|
# places it must say something different (where the target came from, and what
|
|
# a missing target means) really do say something different.
|
|
|
|
def _snapshot(pieces, built_at='2026-08-30 09:00:00', scope=None):
|
|
cache = core.new_cache('a model')
|
|
cache['built_at'] = built_at
|
|
cache['scope'] = scope
|
|
cache['pieces'] = dict((p['name'], p) for p in pieces)
|
|
return cache
|
|
|
|
|
|
def test_a_target_is_answered_straight_out_of_the_snapshot():
|
|
target = piece('PNL700', 30.0, 10.0, 8.0,
|
|
[(4.0, 6.0, 2.0, 5.0), (20.0, 22.0, 2.0, 5.0)],
|
|
reinforced=0)
|
|
donor = piece('PNL701', 30.0, 10.0, 8.0,
|
|
[(4.0, 6.0, 2.0, 5.0), (20.0, 22.0, 2.0, 5.0)])
|
|
cache = _snapshot([target, donor])
|
|
|
|
sug, info = B.suggest_from_index(cache, 'PNL700')
|
|
assert sug is not None
|
|
assert info['offline'] is True
|
|
assert info['target_source'] == 'index'
|
|
assert info['built_at'] == '2026-08-30 09:00:00'
|
|
assert info['donor_names'] == ['PNL701']
|
|
assert sug.status == match.COMPLETE
|
|
|
|
|
|
def test_the_target_record_is_never_offered_as_its_own_donor_pool():
|
|
"""A not-yet-reinforced target sits in the same file as the donors now.
|
|
It may not donate to anybody, including to another target."""
|
|
a = piece('PNL702', 30.0, 10.0, 8.0, [(4.0, 6.0, 2.0, 5.0)], reinforced=0)
|
|
b = piece('PNL703', 30.0, 10.0, 8.0, [(4.0, 6.0, 2.0, 5.0)], reinforced=0)
|
|
sug, info = B.suggest_from_index(_snapshot([a, b]), 'PNL702')
|
|
assert info['donors'] == 0
|
|
assert sug.status == match.NO_DONORS
|
|
|
|
|
|
def test_a_missing_target_offline_is_not_the_same_as_missing_from_the_model():
|
|
"""With no model open nobody can say whether the piece exists."""
|
|
sug, info = B.suggest_from_index(_snapshot([]), 'PNL704')
|
|
assert sug is None
|
|
assert info['target_status'] == 'not_in_index'
|
|
assert info['offline'] is True
|
|
|
|
|
|
def test_the_same_call_from_the_live_path_still_says_not_in_model():
|
|
sug, info = B.suggest_from_index(_snapshot([]), 'PNL705', offline=False)
|
|
assert sug is None
|
|
assert info['target_status'] == 'not_in_model'
|
|
|
|
|
|
# -------------------------------------------------------- the scope, and D2
|
|
|
|
def _scope(prefixes):
|
|
from vibe import scope as scopelib
|
|
return scopelib.Scope(prefixes=prefixes)
|
|
|
|
|
|
def test_an_in_scope_target_is_never_annotated_as_out_of_scope():
|
|
"""Verified against a live run, 2026-08-30: the info dict carried
|
|
`target_in_scope: True` AND the "JUDGED OUTSIDE THE DECLARED SCOPE, AT
|
|
YOUR REQUEST" annotation at the same time, because the name was passed to
|
|
`Scope.annotation` unconditionally. A report that contradicts itself in
|
|
one dict teaches the reader to skip both lines."""
|
|
target = piece('PNL706', 30.0, 10.0, 8.0, [(4.0, 6.0, 2.0, 5.0)],
|
|
reinforced=0)
|
|
donor = piece('PNL707', 30.0, 10.0, 8.0, [(4.0, 6.0, 2.0, 5.0)])
|
|
_sug, info = B.suggest_from_index(_snapshot([target, donor]), 'PNL706',
|
|
the_scope=_scope(['PNL']))
|
|
assert info['scope']['target_in_scope'] is True
|
|
assert info['scope']['target_annotation'] is None
|
|
|
|
|
|
def test_an_out_of_scope_target_is_still_annotated():
|
|
"""The fix must not silence the case the annotation exists for."""
|
|
target = piece('BM708', 30.0, 10.0, 8.0, [(4.0, 6.0, 2.0, 5.0)],
|
|
reinforced=0)
|
|
donor = piece('PNL709', 30.0, 10.0, 8.0, [(4.0, 6.0, 2.0, 5.0)])
|
|
_sug, info = B.suggest_from_index(_snapshot([target, donor]), 'BM708',
|
|
the_scope=_scope(['PNL']))
|
|
assert info['scope']['target_in_scope'] is False
|
|
assert 'OUTSIDE THE DECLARED SCOPE' in info['scope']['target_annotation']
|
|
|
|
|
|
def test_the_scope_bounds_the_offline_donor_pool_the_same_way():
|
|
target = piece('PNL710', 30.0, 10.0, 8.0, [(4.0, 6.0, 2.0, 5.0)],
|
|
reinforced=0)
|
|
mine = piece('PNL711', 30.0, 10.0, 8.0, [(4.0, 6.0, 2.0, 5.0)])
|
|
theirs = piece('BM712', 30.0, 10.0, 8.0, [(4.0, 6.0, 2.0, 5.0)])
|
|
_sug, info = B.suggest_from_index(_snapshot([target, mine, theirs]),
|
|
'PNL710', the_scope=_scope(['PNL']))
|
|
assert info['donor_names'] == ['PNL711']
|
|
assert info['rejected']['prefix'] == 1
|
|
|
|
|
|
# --------------------------------------------------------- the outline audit
|
|
|
|
def test_the_audit_is_read_by_folder_when_there_is_no_document(tmp_path):
|
|
import json
|
|
folder = str(tmp_path)
|
|
handle = open(os.path.join(folder, B.AUDIT_FILE), 'wb')
|
|
try:
|
|
handle.write(json.dumps({'approximate': ['PNL713']}).encode('utf-8'))
|
|
finally:
|
|
handle.close()
|
|
assert B.load_audit_at(folder) == set(['PNL713'])
|
|
|
|
|
|
def test_never_audited_stays_None_and_never_becomes_an_empty_set(tmp_path):
|
|
"""Never checked and checked-and-found-none are different answers."""
|
|
assert B.load_audit_at(str(tmp_path)) is None
|
|
|
|
|
|
def test_the_offline_run_reduces_designs_with_the_pattern_in_the_file():
|
|
"""`nomenclature.design_number_re` is plant knowledge and there is no
|
|
knowledge layer with no model open, so the index stamps it and this is
|
|
where it gets picked up. Without it, offline and the button could reduce
|
|
the same two numbers to different families -- and the whole point of the
|
|
family is deciding who may donate."""
|
|
target = piece('PNL950', 20.0, 10.0, 8.0, [(4.0, 6.0, 2.0, 5.0)],
|
|
reinforced=0, design='PNL1.0')
|
|
other = piece('PNL951', 20.0, 10.0, 8.0, [(4.0, 6.0, 2.0, 5.0)],
|
|
design='PNL2.0')
|
|
snap = _snapshot([target, other])
|
|
|
|
strict, info = B.suggest_from_index(snap, 'PNL950')
|
|
assert strict is None # PNL1 is not PNL2
|
|
assert info['design']['status'] == B.DESIGN_NONE_MATCH
|
|
|
|
# The same file, stamped with a pattern that stops at the letters: now
|
|
# they are one design and the donor is offered.
|
|
snap['nomenclature'] = {'design_number_re': r'^([A-Za-z]+)'}
|
|
loose, info = B.suggest_from_index(snap, 'PNL950')
|
|
assert info['donor_names'] == ['PNL951']
|
|
assert loose is not None
|
|
|
|
|
|
def test_an_index_with_no_stamped_pattern_falls_back_rather_than_refusing():
|
|
"""An index built before the stamp is not an index that cannot be read.
|
|
It falls back to the built-in pattern, which is what every other reader of
|
|
an unmapped nomenclature in this codebase already does."""
|
|
target = piece('PNL960', 20.0, 10.0, 8.0, [(4.0, 6.0, 2.0, 5.0)],
|
|
reinforced=0, design='PNL1.0')
|
|
donor = piece('PNL961', 20.0, 10.0, 8.0, [(4.0, 6.0, 2.0, 5.0)],
|
|
design='PNL1.A')
|
|
snap = _snapshot([target, donor])
|
|
del snap['nomenclature']
|
|
sug, info = B.suggest_from_index(snap, 'PNL960')
|
|
assert info['donor_names'] == ['PNL961']
|
|
assert sug is not None
|