52d94303bc
ci / test (push) Has been cancelled
Public demo snapshot of an in-house pyRevit extension for precast concrete shop-ticket QC (37 checks / 46 checklist rows) and a standalone panel handling solver (PCI Chapter 8). - No client, job, plant or product data: knowledge layers ship as structural templates only. - Ticket QC's model-read escalation targets a LAN model server endpoint (configurable), not a cloud provider. - Built from the private development repo via tools/make_dist.py.
1354 lines
54 KiB
Python
1354 lines
54 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""What the Reinforcing Suggestion button says, tested without Revit.
|
|
|
|
The button script itself cannot be exercised here -- it imports the Revit
|
|
API at module scope, as every pyRevit script does. So every sentence it
|
|
prints that could be wrong lives in `vibe.reinforcereport`, and this is
|
|
where those sentences are held to account.
|
|
|
|
The tests are mostly about DISTINCTIONS rather than formatting: that "no
|
|
donors were offered" never reads like "no donor matches", that a piece
|
|
measured to have nothing never reads like a piece nobody measured, that a
|
|
partial cover reads as an answer, and that a match resting on an
|
|
approximated outline can never print the bare word "exact".
|
|
|
|
No real panel marks appear anywhere in here. The names are P1 / P2 / T1.
|
|
"""
|
|
import re
|
|
|
|
import pytest
|
|
|
|
from vibe import foldreport as fold
|
|
from vibe import reinforcecore as core
|
|
from vibe import reinforcematch as match
|
|
from vibe import reinforcereport as R
|
|
|
|
|
|
# --------------------------------------------------------------- formatting
|
|
|
|
def test_a_stored_length_converts_feet_to_inches_once():
|
|
"""`length_in` takes FEET off a record and prints INCHES."""
|
|
assert R.length_in(2.5) == '30.00 in'
|
|
assert R.length_in(None) == '—'
|
|
|
|
|
|
def test_a_covers_distance_is_printed_without_converting_again():
|
|
"""`distance_in` takes INCHES -- `reinforcebind.covers` already
|
|
crossed the seam. A second conversion here is the 12x error this
|
|
project has paid for once, so the two formatters are named for what
|
|
they TAKE and are tested apart."""
|
|
assert R.distance_in(2.5) == '2.50 in'
|
|
assert R.distance_in(None) == '—'
|
|
# The whole point, as arithmetic: same number in, twelve times apart
|
|
# out. Nothing may ever route a covers distance through `length_in`.
|
|
assert R.length_in(2.5) != R.distance_in(2.5)
|
|
|
|
|
|
def test_rect_is_absolute_inches_both_axes():
|
|
"""Feet on the record, inches on the page, absolute from the start of
|
|
the piece -- never a fraction of panel length."""
|
|
assert R.rect((6.5, 10.583, 0.0, 8.0)) == '78.00..127.00 x 0.00..96.00'
|
|
assert R.rect(None) == '—'
|
|
|
|
|
|
def test_feature_label_names_what_bounds_a_strip():
|
|
assert R.feature_label({'kind': R.OPENING}) == 'opening'
|
|
assert R.feature_label({'kind': R.STRIP_X,
|
|
'between': R.VOID_VOID}) == \
|
|
'strip between two openings (along length)'
|
|
assert R.feature_label({'kind': R.STRIP_Y, 'between': R.VOID_EDGE,
|
|
'edge': 'y1'}) == \
|
|
'strip, opening to top (across height)'
|
|
|
|
|
|
# ------------------------------------------------- exactness and the asterisk
|
|
|
|
def test_exact_match_prints_exact():
|
|
assert R.exactness({'donor': 'P1', 'exact': True,
|
|
'approximate': False}) == 'exact'
|
|
|
|
|
|
def test_approximate_exact_match_is_always_starred():
|
|
"""HARD RULE: a piece flagged `outline_approximate` may never be
|
|
presented as an exact edge-condition match."""
|
|
got = R.exactness({'donor': 'P1', 'exact': True, 'approximate': True})
|
|
assert got == 'exact*'
|
|
assert got != 'exact'
|
|
|
|
|
|
def test_inexact_match_prints_the_distance_in_inches():
|
|
"""The distance arrives in inches and prints as inches, unconverted."""
|
|
assert R.exactness({'donor': 'P1', 'exact': False,
|
|
'distance': 0.25, 'approximate': False}) \
|
|
== 'off 0.25 in'
|
|
|
|
|
|
def test_a_tiny_distance_never_prints_as_a_rounded_zero():
|
|
"""`off 0.00 in` reads as exact. Only `exact` may."""
|
|
got = R.exactness({'donor': 'P1', 'exact': False, 'distance': 0.002,
|
|
'approximate': False})
|
|
assert got == 'off <0.01 in'
|
|
assert '0.00' not in got
|
|
|
|
|
|
def test_the_grain_note_says_the_numbers_are_not_measured_to_a_hundredth():
|
|
"""Two decimals is what was asked for; 0.01 in of precision is not
|
|
what it means, and the page says so where the numbers are rather than
|
|
in a footnote nobody reaches."""
|
|
note = R.GRAIN_NOTE
|
|
assert 'inches' in note
|
|
assert '1/8 in' in note
|
|
assert '0.01 in' in note
|
|
|
|
|
|
def test_short_label_distinguishes_the_two_kinds_of_strip():
|
|
assert R.short_label({'kind': R.STRIP_X,
|
|
'between': R.VOID_EDGE}) == 'strip to an edge'
|
|
assert R.short_label({'kind': R.STRIP_X,
|
|
'between': R.VOID_VOID}) == 'strip between openings'
|
|
|
|
|
|
def test_uncovered_feature_has_no_exactness():
|
|
assert R.exactness({'donor': None}) == '--'
|
|
|
|
|
|
# ------------------------------------------------------- index and staleness
|
|
|
|
def test_index_line_always_says_when_it_was_built():
|
|
cache = {'built_at': '2026-08-30 09:00:00', 'pieces': {'P1': {}}}
|
|
line = R.index_line(cache, {'built': 0}, False, False)
|
|
assert '2026-08-30 09:00:00' in line
|
|
assert 'cache' in line
|
|
|
|
|
|
def test_index_line_says_never_when_it_never_was():
|
|
line = R.index_line({'pieces': {}}, None, False, False)
|
|
assert 'never' in line
|
|
|
|
|
|
def test_index_line_reports_a_full_rebuild_as_such():
|
|
line = R.index_line({'built_at': 'x', 'pieces': {}}, {'built': 5},
|
|
True, True)
|
|
assert 'FULL REBUILD' in line
|
|
|
|
|
|
def test_staleness_note_names_the_edit_the_signature_cannot_see():
|
|
note = R.staleness_note(False)
|
|
assert 'opening moved or resized in place' in note
|
|
assert 'shift-click' in note.lower()
|
|
|
|
|
|
def test_staleness_note_is_withdrawn_after_a_full_rebuild():
|
|
note = R.staleness_note(True)
|
|
assert 'stale' in note
|
|
assert 'shift-click' not in note.lower()
|
|
|
|
|
|
def test_coverage_note_keeps_unmeasured_apart_from_empty():
|
|
notes = R.coverage_note({'coverage_unjudged': ['P9'], 'audited': True})
|
|
assert len(notes) == 1
|
|
assert 'could not be measured' in notes[0]
|
|
assert 'nobody knows what they have' in notes[0]
|
|
|
|
|
|
def test_coverage_note_says_when_the_outline_audit_never_ran():
|
|
notes = R.coverage_note({'audited': False})
|
|
assert any('never been run' in n for n in notes)
|
|
|
|
|
|
def test_coverage_note_is_silent_when_there_is_nothing_to_say():
|
|
assert R.coverage_note({'audited': True}) == []
|
|
|
|
|
|
# ------------------------------------------------------------- target states
|
|
|
|
def test_unjudged_target_does_not_read_as_no_suggestions():
|
|
head, _lines, judged = R.target_block(
|
|
{'target': 'T1', 'target_status': 'unjudged'})
|
|
assert judged is False
|
|
assert 'could not be measured' in head
|
|
assert 'not "no suggestions"' in head
|
|
|
|
|
|
def test_measured_but_featureless_target_is_a_measured_answer():
|
|
head, _lines, judged = R.target_block(
|
|
{'target': 'T1', 'target_status': 'no_features'})
|
|
assert judged is False
|
|
assert 'was measured and has nothing to reinforce' in head
|
|
assert 'not a failure to look' in head
|
|
|
|
|
|
# ------------------------------------ the featureless-target search, in words
|
|
#
|
|
# Owner, 2026-09-01, after this exact report stayed silent about it: "这件儿
|
|
# 不应该你告诉我,应该reinforce告诉我。" `target_status == 'no_features'` used
|
|
# to stop at the sentence above. These hold the rest of the sentence to
|
|
# account -- what the report says once `shape_search_for` has actually run.
|
|
|
|
def _search(eligible=(), secondary=(), degenerate=False, considered=None):
|
|
return {'ran': True,
|
|
'considered': (considered if considered is not None
|
|
else len(eligible) + len(secondary)),
|
|
'eligible': list(eligible), 'secondary': list(secondary),
|
|
'degenerate': degenerate}
|
|
|
|
|
|
def _donor_row(name, tier='featureless', t=0.0, l=0.0, w=0.0,
|
|
design='WP2.0', master=False):
|
|
return {'name': name, 'tier': tier, 'thickness_diff_in': t,
|
|
'length_diff_ft': l, 'width_diff_ft': w,
|
|
'design_number': design, 'master': master}
|
|
|
|
|
|
def test_no_features_with_no_shape_search_reads_exactly_as_before():
|
|
"""An info dict with no `shape_search` key -- an older caller, or a bare
|
|
fixture -- must not change what the pinned test above already checks."""
|
|
head, lines, judged = R.target_block(
|
|
{'target': 'T1', 'target_status': 'no_features'})
|
|
assert judged is False
|
|
assert lines == []
|
|
assert 'was measured and has nothing to reinforce' in head
|
|
|
|
|
|
def test_no_features_reports_the_inverted_search_and_the_count():
|
|
_head, lines, judged = R.target_block(
|
|
{'target': 'T1', 'target_status': 'no_features',
|
|
'shape_search': _search(eligible=[_donor_row('P2')])})
|
|
assert judged is False
|
|
assert any('search inverted' in l for l in lines)
|
|
assert any('1 featureless donor(s) found' in l for l in lines)
|
|
|
|
|
|
def test_no_features_with_nobody_eligible_says_so_plainly():
|
|
_head, lines, _judged = R.target_block(
|
|
{'target': 'T1', 'target_status': 'no_features',
|
|
'shape_search': _search(considered=4)})
|
|
assert any('Nobody survived the filter' in l for l in lines)
|
|
|
|
|
|
def test_no_features_with_only_donors_that_have_openings():
|
|
"""Requirement 3: a donor WITH openings is not obviously wrong when the
|
|
target has none -- it is offered, but the report says why it is second
|
|
best rather than presenting it as an ordinary match."""
|
|
_head, lines, _judged = R.target_block(
|
|
{'target': 'T1', 'target_status': 'no_features',
|
|
'shape_search': _search(
|
|
secondary=[_donor_row('P9', tier='has_features')])})
|
|
assert any('No donor in the pool is also featureless' in l
|
|
for l in lines)
|
|
assert any('DO have openings of their own' in l for l in lines)
|
|
|
|
|
|
def test_no_features_flags_a_degenerate_ranking_instead_of_a_false_order():
|
|
_head, lines, _judged = R.target_block(
|
|
{'target': 'T1', 'target_status': 'no_features',
|
|
'shape_search': _search(
|
|
eligible=[_donor_row('A'), _donor_row('B')], degenerate=True)})
|
|
assert any('cannot be told apart' in l for l in lines)
|
|
assert not any('ranked by thickness' in l for l in lines)
|
|
|
|
|
|
def test_no_features_notes_the_secondary_tier_alongside_the_eligible_one():
|
|
_head, lines, _judged = R.target_block(
|
|
{'target': 'T1', 'target_status': 'no_features',
|
|
'shape_search': _search(
|
|
eligible=[_donor_row('P2')],
|
|
secondary=[_donor_row('P9', tier='has_features')])})
|
|
assert any('featureless donor(s) found' in l for l in lines)
|
|
assert any('further' in l and 'openings of their own' in l
|
|
for l in lines)
|
|
|
|
|
|
def test_shape_search_rows_put_featureless_before_has_features():
|
|
search = _search(eligible=[_donor_row('P2')],
|
|
secondary=[_donor_row('P9', tier='has_features', l=0.5)])
|
|
rows = R.shape_search_rows(search)
|
|
assert rows[0][0] == 'P2' and rows[0][1] == 'no openings'
|
|
assert rows[1][0] == 'P9' and rows[1][1] == 'has openings'
|
|
|
|
|
|
def test_shape_search_rows_mark_a_master_donor():
|
|
search = _search(eligible=[_donor_row('P2', master=True)])
|
|
rows = R.shape_search_rows(search)
|
|
assert R.MASTER_MARK in rows[0][0]
|
|
|
|
|
|
def test_target_not_in_model_is_said_out_loud():
|
|
head, _lines, judged = R.target_block(
|
|
{'target': 'T1', 'target_status': 'not_in_model'})
|
|
assert judged is False
|
|
assert 'not in this model' in head
|
|
|
|
|
|
def test_ordinary_target_is_judged_and_counts_its_features():
|
|
head, lines, judged = R.target_block(
|
|
{'target': 'T1', 'target_status': 'ok',
|
|
'target_features': [{}, {}, {}]})
|
|
assert judged is True
|
|
assert '3 feature(s)' in head
|
|
assert lines == []
|
|
|
|
|
|
def test_target_flags_are_reported_without_blocking_the_run():
|
|
_head, lines, judged = R.target_block(
|
|
{'target': 'T1', 'target_status': 'ok', 'target_features': [{}]},
|
|
reinforced=1, on_hold=1)
|
|
assert judged is True
|
|
assert any('TICKET_IS_REINFORCED' in line for line in lines)
|
|
assert any('ON_HOLD' in line for line in lines)
|
|
|
|
|
|
def test_approximate_target_outline_is_flagged():
|
|
_head, lines, _judged = R.target_block(
|
|
{'target': 'T1', 'target_status': 'ok', 'target_features': [{}],
|
|
'target_approximate': True})
|
|
assert any('outline is approximated' in line for line in lines)
|
|
|
|
|
|
# ------------------------------------------------------------- the verdicts
|
|
|
|
def _suggestion(status, uncovered=(), needed=0, proposals=(), n=4,
|
|
donor_count=3, cover_count=0):
|
|
return match.Suggestion(status, proposals, uncovered, donor_count, n,
|
|
needed, cover_count)
|
|
|
|
|
|
def test_no_donors_says_nothing_was_judged():
|
|
head, lines = R.verdict(_suggestion(match.NO_DONORS, uncovered=(0, 1, 2, 3),
|
|
donor_count=0),
|
|
{'considered': 255, 'donors': 0})
|
|
assert 'NOT JUDGED' in head
|
|
body = ' '.join(lines)
|
|
assert 'not one comparison was made' in body
|
|
assert 'nothing was examined' in body
|
|
|
|
|
|
def test_a_cold_db_says_run_a_snapshot_not_no_match():
|
|
"""An empty donor index reads NO_DONORS -- but a COLD DB (nothing indexed
|
|
yet) must say "run a Snapshot", never "no donor matches" and never a silent
|
|
all-clear. This is the cardinal sin the distinction exists to prevent."""
|
|
head, lines = R.verdict(
|
|
_suggestion(match.NO_DONORS, uncovered=(0, 1, 2, 3), donor_count=0),
|
|
{'considered': 0, 'donors': 0, 'cold_db': True})
|
|
assert 'NOT JUDGED' in head
|
|
assert 'Snapshot' in head
|
|
body = ' '.join(lines)
|
|
assert 'no pieces at all' in body
|
|
# it explicitly contrasts itself with the all-clear and the no-match reads
|
|
assert 'not "all clear"' in body
|
|
assert 'Snapshot' in body
|
|
|
|
|
|
def test_a_cold_db_names_a_schema_discard_when_that_is_why():
|
|
"""When the old index was thrown away by a schema bump, `cold_reason`
|
|
carries the load error and the verdict says the index has to be rebuilt --
|
|
a different fix from a never-snapshotted index, so it says so."""
|
|
head, lines = R.verdict(
|
|
_suggestion(match.NO_DONORS, donor_count=0),
|
|
{'cold_db': True, 'cold_reason': 'schema 5, expected 6'})
|
|
body = ' '.join(lines)
|
|
assert 'schema 5, expected 6' in body
|
|
assert 'rebuilt' in body
|
|
|
|
|
|
def test_a_cold_db_and_an_ordinary_no_donors_never_read_the_same():
|
|
"""A cold DB and a pool that was there but wholly filtered out are two
|
|
different findings with two different fixes; the headline separates them."""
|
|
cold = R.verdict(_suggestion(match.NO_DONORS, donor_count=0),
|
|
{'considered': 0, 'donors': 0, 'cold_db': True})
|
|
filtered = R.verdict(_suggestion(match.NO_DONORS, donor_count=0),
|
|
{'considered': 255, 'donors': 0, 'cold_db': False})
|
|
assert cold[0] != filtered[0]
|
|
assert cold[1] != filtered[1]
|
|
# and a cold DB is still NOT the "judged, found nothing" answer
|
|
coverage = R.verdict(_suggestion(match.NO_COVERAGE),
|
|
{'considered': 12, 'donors': 12})
|
|
assert cold[0] != coverage[0]
|
|
|
|
|
|
def test_no_coverage_is_a_finding_not_a_gap():
|
|
head, lines = R.verdict(
|
|
_suggestion(match.NO_COVERAGE, uncovered=(0, 1, 2, 3)),
|
|
{'considered': 255, 'donors': 12})
|
|
assert 'JUDGED' in head
|
|
assert 'NOT JUDGED' not in head
|
|
assert 'a finding, not a gap' in ' '.join(lines)
|
|
|
|
|
|
def test_no_donors_and_no_coverage_never_read_the_same():
|
|
a = R.verdict(_suggestion(match.NO_DONORS), {'considered': 1, 'donors': 0})
|
|
b = R.verdict(_suggestion(match.NO_COVERAGE),
|
|
{'considered': 1, 'donors': 1})
|
|
assert a[0] != b[0]
|
|
assert a[1] != b[1]
|
|
|
|
|
|
def test_partial_is_presented_as_an_answer():
|
|
head, lines = R.verdict(
|
|
_suggestion(match.PARTIAL, uncovered=(3,), needed=2, n=4),
|
|
{'considered': 255, 'donors': 40})
|
|
assert head.startswith('PARTIAL COVER')
|
|
assert '2 donor(s) account for 3 of 4' in head
|
|
assert 'an answer, not a failure' in ' '.join(lines)
|
|
|
|
|
|
def test_complete_counts_the_covers_found():
|
|
head, lines = R.verdict(
|
|
_suggestion(match.COMPLETE, needed=2, n=4, cover_count=7),
|
|
{'considered': 255, 'donors': 40})
|
|
assert head.startswith('COMPLETE COVER')
|
|
assert '7 cover(s)' in ' '.join(lines)
|
|
|
|
|
|
# ------------------------------------------------------------------- tables
|
|
|
|
def _row(i, kind=R.OPENING, donor=None, exact=False, distance=None,
|
|
approximate=False, through_unjudged=False, strip=None,
|
|
between=None, edge=None, donor_index=0):
|
|
return {'target_index': i, 'kind': kind, 'between': between, 'edge': edge,
|
|
'rect': (0.0, 1.0, 0.0, 2.0), 'strip_ft': strip,
|
|
'donor': donor, 'donor_index': donor_index, 'distance': distance,
|
|
'exact': exact, 'donor_rect': None, 'approximate': approximate,
|
|
'through_unjudged': through_unjudged}
|
|
|
|
|
|
# ------------------------------------------------------- the feature-ID column
|
|
|
|
def test_feature_id_cell_pairs_target_and_donor_ids():
|
|
"""A covered feature names both registry ids -- target and donor -- so a
|
|
reader can see the numbering the shared vocabulary assigned."""
|
|
cell = R.feature_id_cell({'target_feature_id': 7, 'donor': 'P1',
|
|
'donor_feature_id': 7})
|
|
assert 'target #7' in cell
|
|
assert 'donor #7' in cell
|
|
|
|
|
|
def test_feature_id_cell_says_when_no_donor_carries_the_shape():
|
|
"""A target descriptor no donor produced projects to None -- the
|
|
informative "nobody has this shape" signal, never blank, never an error."""
|
|
cell = R.feature_id_cell({'target_feature_id': None, 'donor': None})
|
|
assert 'not in donor vocabulary' in cell
|
|
# and it is not silently empty
|
|
assert cell.strip() != ''
|
|
|
|
|
|
def test_feature_id_cell_uncovered_target_still_shows_its_own_id():
|
|
"""A target the proposal did not cover still has a registry id if some
|
|
donor somewhere carries its shape; the cell shows it with no donor half."""
|
|
cell = R.feature_id_cell({'target_feature_id': 3, 'donor': None})
|
|
assert 'target #3' in cell
|
|
assert 'donor' not in cell
|
|
|
|
|
|
def test_feature_id_column_is_present_and_last():
|
|
"""The feature-by-feature table carries the id column, appended so the
|
|
existing column order is unchanged."""
|
|
assert R.FEATURE_COLUMNS[-1] == 'feature-ID'
|
|
rows = R.feature_rows([_row(0, donor='P1')])
|
|
assert len(rows[0]) == len(R.FEATURE_COLUMNS)
|
|
|
|
|
|
def test_every_target_feature_gets_a_row():
|
|
rows = R.feature_rows([_row(0, donor='P1', exact=True), _row(1),
|
|
_row(2, donor='P2', distance=0.2)],
|
|
_suggestion(match.PARTIAL, uncovered=(1,), n=3))
|
|
assert len(rows) == 3
|
|
|
|
|
|
def test_a_feature_nobody_has_says_so_loudly():
|
|
rows = R.feature_rows([_row(0)],
|
|
_suggestion(match.PARTIAL, uncovered=(0,), n=1))
|
|
assert rows[0][4] == 'NOBODY HAS IT'
|
|
|
|
|
|
def test_a_feature_this_set_missed_is_not_the_same_as_nobody_having_it():
|
|
rows = R.feature_rows([_row(0)], _suggestion(match.COMPLETE, n=1))
|
|
assert rows[0][4] == 'not in this set (another donor has it)'
|
|
assert 'NOBODY' not in rows[0][4]
|
|
|
|
|
|
def test_unjudged_penetration_is_noted_on_the_feature_row():
|
|
rows = R.feature_rows([_row(0, donor='P1', exact=True,
|
|
through_unjudged=True)], None)
|
|
assert rows[0][6] == 'penetration not judged'
|
|
|
|
|
|
def test_donor_rows_star_an_approximate_donor():
|
|
rows = R.donor_rows([_row(0, donor='P1', exact=True, approximate=True)])
|
|
assert rows[0][0] == 'P1*'
|
|
|
|
|
|
def test_donor_rows_group_by_donor_and_count_exacts():
|
|
rows = R.donor_rows([
|
|
_row(0, donor='P1', exact=True),
|
|
_row(1, donor='P1', distance=0.3),
|
|
_row(2, donor='P2', exact=True),
|
|
])
|
|
assert [r[0] for r in rows] == ['P1', 'P2']
|
|
# column 1 is provenance; the counts moved right when it was added
|
|
assert rows[0][R.DONOR_COLUMNS.index('features it covers')] == '2'
|
|
assert rows[0][R.DONOR_COLUMNS.index('of those, exact')] == '1'
|
|
|
|
|
|
def test_donor_rows_star_a_donor_named_approximate_by_the_pool():
|
|
rows = R.donor_rows([_row(0, donor='P1', exact=True)],
|
|
approximate_names=['P1'])
|
|
assert rows[0][0] == 'P1*'
|
|
|
|
|
|
def test_uncovered_rows_list_only_what_nobody_has():
|
|
rows = R.uncovered_rows(
|
|
[_row(0, donor='P1'), _row(1), _row(2)],
|
|
_suggestion(match.PARTIAL, uncovered=(2,), n=3))
|
|
assert len(rows) == 1
|
|
assert rows[0][0] == '2'
|
|
|
|
|
|
def test_rejection_rows_explain_every_reason_in_order_of_size():
|
|
rows = R.rejection_rows({'rejected': {'prefix': 400, 'thickness': 11,
|
|
'unjudged': 2}})
|
|
assert [r[0] for r in rows] == ['prefix', 'thickness', 'unjudged']
|
|
assert 'could not be judged' in rows[2][2]
|
|
|
|
|
|
def test_every_design_rejection_reason_is_explained_in_the_table():
|
|
"""A pool that shrank without saying why is a silent exclusion, and the
|
|
design rule is the biggest cut this filter makes. Its three reasons are
|
|
three different findings and the table must not blur them."""
|
|
rows = R.rejection_rows({'rejected': {'design': 300, 'design_unknown': 5,
|
|
'design_target_unknown': 1}})
|
|
why = dict((r[0], r[2]) for r in rows)
|
|
assert 'DIFFERENT DESIGN NUMBER' in why['design']
|
|
assert 'never assumed to match' in why['design_unknown']
|
|
assert 'TARGET' in why['design_target_unknown']
|
|
assert len(set(why.values())) == 3
|
|
|
|
|
|
def test_proposal_rows_report_size_and_exactness():
|
|
p = match.Proposal((0, 1), ('P1', 'P2'),
|
|
((0, 0, 0.0), (1, 0, 0.25)), ())
|
|
rows = R.proposal_rows(_suggestion(match.COMPLETE, proposals=(p,), n=2))
|
|
assert rows[0][1] == 'P1, P2'
|
|
assert rows[0][3] == '2 / 2'
|
|
assert rows[0][4] == '1'
|
|
|
|
|
|
# ---------------------------------------------------------------- footnotes
|
|
|
|
@pytest.mark.parametrize('rows,info', [
|
|
([_row(0, donor='P1', approximate=True)], {}),
|
|
([], {'target_approximate': True}),
|
|
([], {'approximate_in_pool': ['P1']}),
|
|
])
|
|
def test_footnote_shows_whenever_a_star_could_appear(rows, info):
|
|
assert R.needs_footnote(rows, info) is True
|
|
|
|
|
|
def test_footnote_is_withheld_when_nothing_is_approximated():
|
|
assert R.needs_footnote([_row(0, donor='P1')], {}) is False
|
|
|
|
|
|
def test_footnote_text_says_a_star_is_not_a_measured_exact():
|
|
assert 'never a measured exact match' in R.APPROX_FOOTNOTE
|
|
|
|
|
|
def test_read_only_line_says_it_writes_nothing():
|
|
assert 'writes nothing' in R.read_only_line()
|
|
|
|
|
|
# ------------------------------------------------- MASTER, said out loud
|
|
#
|
|
# The mark is worth nothing if the report does not say what it means. A
|
|
# reader who sees a dagger and guesses is exactly the failure this module
|
|
# exists to prevent -- and the thing most likely to be guessed WRONG here is
|
|
# that MASTER outranks a better fit. It does not, and the footnote says so.
|
|
|
|
|
|
def test_a_master_donor_is_marked_and_the_mark_is_not_the_approximate_star():
|
|
assert R.mark('P1', ['P1']) == 'P1' + R.MASTER_MARK
|
|
assert R.mark('P1', []) == 'P1'
|
|
assert R.MASTER_MARK != '*'
|
|
|
|
|
|
def test_a_donor_can_be_both_approximate_and_a_master():
|
|
both = R.mark('P1', ['P1'], approximate=True)
|
|
assert '*' in both and R.MASTER_MARK in both
|
|
|
|
|
|
def test_donor_rows_name_the_provenance_of_every_donor():
|
|
rows = R.donor_rows([_row(0, donor='P1', exact=True),
|
|
_row(1, donor='P2', exact=True)],
|
|
master_names=['P1'])
|
|
prov = R.DONOR_COLUMNS.index('provenance')
|
|
assert 'MASTER' in rows[0][prov] and 'reviewed' in rows[0][prov]
|
|
# and the un-reviewed donor is not left blank, which would read as
|
|
# "unknown" rather than as the finding it is
|
|
assert rows[1][prov] == 'reinforced only'
|
|
|
|
|
|
def test_donor_rows_take_the_master_flag_off_the_explain_row_too():
|
|
rows = R.donor_rows([dict(_row(0, donor='P1'), donor_master=True)])
|
|
assert rows[0][0] == 'P1' + R.MASTER_MARK
|
|
|
|
|
|
def test_proposal_rows_count_the_reviewed_donors():
|
|
p = match.Proposal((0, 1), ('P1', 'P2'),
|
|
((0, 0, 0.0), (1, 0, 0.0)), (), master_keys=('P2',))
|
|
rows = R.proposal_rows(_suggestion(match.COMPLETE, n=2, proposals=(p,)))
|
|
col = R.PROPOSAL_COLUMNS.index('reviewed (MASTER)')
|
|
assert rows[0][col] == '1'
|
|
assert 'P2' + R.MASTER_MARK in rows[0][1]
|
|
|
|
|
|
def test_the_footnote_says_master_is_a_tie_break_not_a_trump():
|
|
body = R.MASTER_FOOTNOTE
|
|
assert 'reviewed' in body
|
|
assert 'tie-break' in body
|
|
assert 'never outranks a donor that fits better' in body
|
|
# and it must not let anyone read MASTER as merely "reinforced"
|
|
assert 'TICKET_IS_REINFORCED' in body
|
|
|
|
|
|
def test_the_footnote_only_shows_when_a_mark_is_actually_printed():
|
|
assert R.needs_master_footnote([], {'donors': 5}) is False
|
|
assert R.needs_master_footnote([], {'master_donors': ['P1']}) is True
|
|
assert R.needs_master_footnote(
|
|
[dict(_row(0, donor='P1'), donor_master=True)], {}) is True
|
|
|
|
|
|
def test_a_pool_with_no_master_says_so_rather_than_staying_silent():
|
|
"""Absence of daggers must be readable as a fact, not as a missing feature."""
|
|
lines = R.master_note({'donors': 12, 'master_donors': []})
|
|
body = ' '.join(lines)
|
|
assert 'No donor in this pool is typed MASTER' in body
|
|
assert 'not' in body and 'reviewed' in body
|
|
|
|
|
|
def test_nothing_is_claimed_when_there_was_no_pool_at_all():
|
|
assert R.master_note({'donors': 0, 'master_donors': []}) == []
|
|
|
|
|
|
def test_the_master_note_names_them_and_repeats_the_ruling():
|
|
lines = R.master_note({'donors': 12, 'master_donors': ['P1', 'P2']})
|
|
body = ' '.join(lines)
|
|
assert 'P1' in body and 'P2' in body
|
|
assert 'never outranks a better fit' in body
|
|
assert 'fit equally well' in body
|
|
|
|
|
|
def test_an_unmapped_role_reads_as_not_run_never_as_no_masters():
|
|
"""The distinction this whole module exists to protect, one more time.
|
|
|
|
'nobody looked' and 'looked and found none' are different answers about
|
|
provenance, and the second is the one that would quietly reassure a
|
|
reader who has no reviewed donors and no idea the check is off.
|
|
"""
|
|
not_run = ' '.join(R.master_note(
|
|
{'donors': 12, 'master_donors': [], 'master_unjudged': True}))
|
|
none_found = ' '.join(R.master_note(
|
|
{'donors': 12, 'master_donors': []}))
|
|
assert 'did not run' in not_run
|
|
assert 'nobody looked' in not_run.lower()
|
|
assert 'identity_description' in not_run
|
|
assert not_run != none_found
|
|
assert 'did not run' not in none_found
|
|
|
|
|
|
# ------------------------------------------ SAME ELEVATION, said out loud
|
|
#
|
|
# The mark is worth nothing if the report does not say what it means, and the
|
|
# incident this exists for (WP425, 2026-09-02) was invisible precisely
|
|
# because nothing on the page said why WP133 beat WP234. Mirrors the MASTER
|
|
# section exactly: the mark, the column, the footnote, and the note for an
|
|
# empty pool.
|
|
|
|
|
|
def test_a_same_elevation_donor_is_marked_and_the_mark_differs_from_master():
|
|
assert R.mark('P1', same_elevation_names=['P1']) == 'P1' + R.ELEVATION_MARK
|
|
assert R.mark('P1', same_elevation_names=[]) == 'P1'
|
|
assert R.ELEVATION_MARK != R.MASTER_MARK
|
|
assert R.ELEVATION_MARK != '*'
|
|
|
|
|
|
def test_a_donor_can_carry_all_three_marks_at_once():
|
|
all_three = R.mark('P1', ['P1'], approximate=True,
|
|
same_elevation_names=['P1'])
|
|
assert '*' in all_three
|
|
assert R.MASTER_MARK in all_three
|
|
assert R.ELEVATION_MARK in all_three
|
|
|
|
|
|
def test_proposal_rows_count_the_same_elevation_donors():
|
|
p = match.Proposal((0, 1), ('P1', 'P2'),
|
|
((0, 0, 0.0), (1, 0, 0.0)), (),
|
|
same_elevation_keys=('P2',))
|
|
rows = R.proposal_rows(_suggestion(match.COMPLETE, n=2, proposals=(p,)))
|
|
col = R.PROPOSAL_COLUMNS.index('same elevation')
|
|
assert rows[0][col] == '1'
|
|
assert 'P2' + R.ELEVATION_MARK in rows[0][1]
|
|
|
|
|
|
def test_the_proposals_header_names_same_elevation_as_the_last_criterion():
|
|
"""The header sentence is a claim about ranking order, and it went stale
|
|
the moment a new criterion was added below the ones it already named."""
|
|
head = R.render(_S(), _info(), [], {'built_at': _now_stamp(),
|
|
'pieces': {}})
|
|
prose = ' '.join(_headline(head))
|
|
assert ('fewest donors first, then most exact, then most reviewed, '
|
|
'then same elevation') in prose
|
|
|
|
|
|
def test_the_elevation_footnote_says_it_is_a_tie_break_not_a_trump():
|
|
body = R.ELEVATION_FOOTNOTE
|
|
assert 'building elevation' in body
|
|
assert 'last' in body.lower()
|
|
assert 'never outranks a donor that fits better' in body
|
|
|
|
|
|
def test_the_elevation_footnote_only_shows_when_a_mark_is_actually_printed():
|
|
assert R.needs_elevation_footnote({'donors': 5}) is False
|
|
assert R.needs_elevation_footnote(
|
|
{'same_elevation_donors': ['P1']}) is True
|
|
p = match.Proposal((0,), ('P1',), ((0, 0, 0.0),), (),
|
|
same_elevation_keys=('P1',))
|
|
assert R.needs_elevation_footnote({}, _suggestion(
|
|
match.COMPLETE, n=1, proposals=(p,))) is True
|
|
|
|
|
|
_BAND = {'axis': 'X', 'min': 10.0, 'max': 10.5}
|
|
_BAND_SIGNAL = {'group': None, 'band': _BAND}
|
|
_GROUP_SIGNAL = {'group': 'E3.16', 'band': _BAND}
|
|
_NO_SIGNAL = {'group': None, 'band': None}
|
|
|
|
|
|
def test_band_label_formats_axis_and_inches():
|
|
assert R.band_label(_BAND) == 'X @ 120.00 in to 126.00 in'
|
|
assert R.band_label(None) is None
|
|
assert R.band_label({'axis': 'X'}) is None # missing min/max
|
|
assert R.band_label('not a dict') is None # never raises
|
|
|
|
|
|
def test_elevation_signal_label_prefers_the_group_when_present():
|
|
"""The group (the ACCELERATOR) is more legible to an engineer than a
|
|
coordinate band, so it wins when both happen to be on the record --
|
|
neither is privileged in correctness, only in what prints."""
|
|
assert R.elevation_signal_label(_GROUP_SIGNAL) == 'sheet E3.16'
|
|
|
|
|
|
def test_elevation_signal_label_falls_back_to_the_band():
|
|
assert (R.elevation_signal_label(_BAND_SIGNAL) ==
|
|
'X @ 120.00 in to 126.00 in')
|
|
|
|
|
|
def test_elevation_signal_label_is_none_when_neither_source_answered():
|
|
assert R.elevation_signal_label(_NO_SIGNAL) is None
|
|
assert R.elevation_signal_label(None) is None
|
|
assert R.elevation_signal_label({}) is None
|
|
|
|
|
|
def test_elevation_source_label_names_which_source_answered():
|
|
assert R.elevation_source_label(core.ELEVATION_BY_GROUP) == 'same sheet'
|
|
assert R.elevation_source_label(core.ELEVATION_BY_BAND) == 'same geometry'
|
|
assert R.elevation_source_label(None) is None
|
|
assert R.elevation_source_label('nonsense') is None
|
|
|
|
|
|
def test_an_undetermined_target_elevation_reads_as_could_not_judge():
|
|
"""Nobody looked, not nobody matches -- the same distinction
|
|
`master_note` already draws for the reviewed-template signal. Neither
|
|
source -- the group nor the geometric floor -- answered."""
|
|
lines = R.elevation_note({'target': 'T1', 'donors': 12,
|
|
'target_elevation': _NO_SIGNAL,
|
|
'same_elevation_donors': []})
|
|
body = ' '.join(lines)
|
|
assert 'could not be determined' in body
|
|
assert 'nobody looked' in body.lower()
|
|
|
|
|
|
def test_an_absent_target_elevation_key_also_reads_as_could_not_judge():
|
|
"""A caller that never set the key at all (or set it to None) must read
|
|
the same as the explicit two-None-fields shape -- never raise."""
|
|
lines = R.elevation_note({'target': 'T1', 'donors': 12,
|
|
'same_elevation_donors': []})
|
|
assert 'could not be determined' in ' '.join(lines)
|
|
|
|
|
|
def test_a_pool_with_no_same_elevation_donor_says_so_rather_than_staying_silent():
|
|
lines = R.elevation_note({'target': 'T1', 'donors': 12,
|
|
'target_elevation': _BAND_SIGNAL,
|
|
'same_elevation_donors': []})
|
|
body = ' '.join(lines)
|
|
assert 'No donor in this pool shares' in body
|
|
assert 'X @ 120.00 in to 126.00 in' in body
|
|
|
|
|
|
def test_nothing_is_claimed_about_elevation_when_there_was_no_pool_at_all():
|
|
assert R.elevation_note({'target': 'T1', 'donors': 0,
|
|
'target_elevation': _BAND_SIGNAL,
|
|
'same_elevation_donors': []}) == []
|
|
|
|
|
|
def test_the_elevation_note_names_them_and_repeats_the_ruling():
|
|
lines = R.elevation_note({
|
|
'target': 'T1', 'donors': 12,
|
|
'target_elevation': _BAND_SIGNAL,
|
|
'same_elevation_donors': ['P1', 'P2'],
|
|
'same_elevation_sources': {'P1': core.ELEVATION_BY_GROUP,
|
|
'P2': core.ELEVATION_BY_BAND}})
|
|
body = ' '.join(lines)
|
|
assert 'P1' in body and 'P2' in body
|
|
assert 'X @ 120.00 in to 126.00 in' in body
|
|
assert 'never outranks a donor that fits better' in body
|
|
|
|
|
|
def test_the_elevation_note_says_which_source_answered_each_donor():
|
|
lines = R.elevation_note({
|
|
'target': 'T1', 'donors': 12,
|
|
'target_elevation': _GROUP_SIGNAL,
|
|
'same_elevation_donors': ['P1', 'P2'],
|
|
'same_elevation_sources': {'P1': core.ELEVATION_BY_GROUP,
|
|
'P2': core.ELEVATION_BY_BAND}})
|
|
body = ' '.join(lines)
|
|
assert 'P1 (same sheet)' in body
|
|
assert 'P2 (same geometry)' in body
|
|
assert 'sheet E3.16' in body
|
|
|
|
|
|
# ------------------------------------------------------- the declared scope
|
|
|
|
def test_scope_lines_say_which_pool_the_answer_came_from():
|
|
"""A pool bounded by a declared scope and a pool bounded by nothing
|
|
print the same shaped page, so the page has to say which it had."""
|
|
lines = R.scope_lines({'scope': {
|
|
'declared': True, 'label': 'piece marks beginning PNL',
|
|
'declaration': ['scope declared by somebody'],
|
|
'line': 'NOT JUDGED, OUT OF SCOPE: 3 indexed donor(s) ...',
|
|
'target_in_scope': True, 'target_annotation': None}})
|
|
assert any('NOT JUDGED, OUT OF SCOPE' in ln for ln in lines)
|
|
assert any('scope declared by somebody' in ln for ln in lines)
|
|
|
|
|
|
def test_scope_lines_flag_a_target_outside_the_scope():
|
|
"""Nothing the user selected is refused -- it is answered, with the
|
|
warning that the knowledge behind it was never validated for it."""
|
|
lines = R.scope_lines({'scope': {
|
|
'declared': True, 'label': 'piece marks beginning PNL',
|
|
'declaration': [], 'line': 'x',
|
|
'target_in_scope': False,
|
|
'target_annotation': 'JUDGED OUTSIDE THE DECLARED SCOPE ...'}})
|
|
assert any('JUDGED OUTSIDE THE DECLARED SCOPE' in ln for ln in lines)
|
|
|
|
|
|
def test_scope_lines_are_empty_only_when_there_is_no_scope_information():
|
|
assert R.scope_lines({}) == []
|
|
|
|
|
|
def test_the_prefix_rejection_reads_as_a_scope_decision():
|
|
"""It used to read 'not a WP wall panel' -- one job's division of labour,
|
|
in a shareable repository, in the user-visible wording."""
|
|
why = dict((row[0], row[2]) for row in
|
|
R.rejection_rows({'rejected': {'prefix': 2}}))
|
|
assert 'declared scope' in why['prefix']
|
|
|
|
|
|
# ------------------------------------------------------ the donor cap, in words
|
|
#
|
|
# The report is where the two kinds of "not covered" are easiest to collapse
|
|
# into one apology, and they must not be: a feature the cap left out has a
|
|
# donor waiting; a feature nobody has must be detailed from scratch.
|
|
|
|
|
|
class _FakeSuggestion(object):
|
|
"""Just the fields the report reads. The matcher is tested elsewhere."""
|
|
|
|
def __init__(self, status, uncovered=(), capped_out=(), cap=4,
|
|
needed=4, features=8, proposals=(), cover_count=1):
|
|
self.status = status
|
|
self.uncovered = tuple(uncovered)
|
|
self.capped_out = tuple(capped_out)
|
|
self.donor_cap = cap
|
|
self.donors_needed = needed
|
|
self.target_feature_count = features
|
|
self.proposals = tuple(proposals)
|
|
self.cover_count = cover_count
|
|
self.donor_count = 9
|
|
|
|
|
|
def test_a_capped_answer_says_somebody_has_the_missing_features():
|
|
head, lines = R.verdict(
|
|
_FakeSuggestion(match.CAPPED, capped_out=(4, 5, 6, 7)), {})
|
|
assert head.startswith('CAPPED')
|
|
body = ' '.join(lines)
|
|
assert 'is** covered by somebody' in body or 'covered by somebody' in body
|
|
assert 'at most 4' in body
|
|
|
|
|
|
def test_a_capped_answer_is_not_worded_as_nobody_has_it():
|
|
"""The distinction the whole feature turns on."""
|
|
capped = ' '.join(R.verdict(
|
|
_FakeSuggestion(match.CAPPED, capped_out=(4,)), {})[1])
|
|
partial = ' '.join(R.verdict(
|
|
_FakeSuggestion(match.PARTIAL, uncovered=(4,)), {})[1])
|
|
assert 'no indexed donor at all' in partial
|
|
assert 'no indexed donor at all' not in capped
|
|
|
|
|
|
def test_a_partial_answer_also_reports_what_the_cap_left_out():
|
|
"""Both can be true at once, and both get said."""
|
|
head, lines = R.verdict(
|
|
_FakeSuggestion(match.PARTIAL, uncovered=(8,), capped_out=(4, 5),
|
|
features=9), {})
|
|
assert head.startswith('PARTIAL')
|
|
body = ' '.join(lines)
|
|
assert 'no indexed donor at all' in body
|
|
assert 'covered by somebody' in body
|
|
# 9 features, 1 nobody has, 2 the cap dropped -> 6 actually covered
|
|
assert 'cover 6 of the 9' in body
|
|
|
|
|
|
def test_capped_rows_list_only_what_the_cap_dropped():
|
|
rows = [_row(0, donor='P1'), _row(1), _row(2), _row(3)]
|
|
got = R.capped_rows(rows, _FakeSuggestion(match.CAPPED,
|
|
uncovered=(3,),
|
|
capped_out=(1, 2)))
|
|
assert [r[0] for r in got] == ['1', '2']
|
|
|
|
|
|
def test_cap_note_is_silent_when_the_cap_did_not_bite():
|
|
assert R.cap_note(_FakeSuggestion(match.COMPLETE)) == []
|
|
assert R.cap_note(None) == []
|
|
said = R.cap_note(_FakeSuggestion(match.CAPPED, capped_out=(1,)))
|
|
assert said and 'at most 4 donors' in said[0]
|
|
|
|
|
|
def test_a_feature_the_cap_dropped_does_not_read_as_nobody_has_it():
|
|
"""On the feature-by-feature table the two look identical -- both simply
|
|
uncovered -- so the words have to carry the difference."""
|
|
rows = [_row(0), _row(1)]
|
|
got = R.feature_rows(rows, _FakeSuggestion(match.PARTIAL,
|
|
uncovered=(0,),
|
|
capped_out=(1,)))
|
|
assert got[0][4] == 'NOBODY HAS IT'
|
|
assert got[1][4] == 'LEFT OUT BY THE 4-DONOR CAP'
|
|
|
|
|
|
# ------------------------------------------------------------------- offline
|
|
#
|
|
# An offline answer is a strictly weaker claim than a live one and the page
|
|
# has to say so in the same breath as the answer, not in a footnote. Three
|
|
# things are held to account here: that the banner is present and unmissable,
|
|
# that the build stamp travels with it, and that the staleness note offline
|
|
# names EVERYTHING it cannot see rather than only the geometry edit the live
|
|
# note names.
|
|
|
|
def test_the_offline_banner_says_snapshot_and_says_not_the_model():
|
|
line = R.offline_banner({'built_at': '2026-08-30 09:00:00'})
|
|
assert 'OFFLINE' in line
|
|
assert 'snapshot' in line
|
|
assert 'not the model' in line
|
|
assert '2026-08-30 09:00:00' in line
|
|
|
|
|
|
def test_the_offline_banner_admits_an_index_that_was_never_built():
|
|
assert 'never' in R.offline_banner({})
|
|
|
|
|
|
def test_index_line_offline_says_the_model_was_never_opened():
|
|
line = R.index_line({'built_at': 'x', 'pieces': {}}, None, False, False,
|
|
offline=True)
|
|
assert 'never opened' in line
|
|
assert 'nothing needed rebuilding' not in line
|
|
|
|
|
|
def test_index_line_counts_donors_apart_from_targets():
|
|
"""A piece indexed only so it can be a target is not a donor, and calling
|
|
it one overstates the pool that was actually searched."""
|
|
cache = {'built_at': 'x', 'pieces': {
|
|
'P1': {'reinforced': 1, 'on_hold': 0},
|
|
'T1': {'reinforced': 0, 'on_hold': 0},
|
|
'H1': {'reinforced': 1, 'on_hold': 1},
|
|
}}
|
|
line = R.index_line(cache, None, False, False)
|
|
assert '**1** indexed donor(s)' in line
|
|
assert '**2** piece(s) measured as possible targets' in line
|
|
|
|
|
|
def test_staleness_offline_names_everything_it_cannot_see():
|
|
note = R.staleness_note(False, offline=True)
|
|
assert 'opening moved or resized' in note
|
|
assert 'newly reinforced' in note # caught live, blind offline
|
|
assert 'added or deleted' in note
|
|
assert 'shift-click' not in note.lower() # meaningless at a command line
|
|
assert 'refresh' in note.lower()
|
|
|
|
|
|
def test_staleness_offline_is_not_withdrawn_by_a_full_rebuild_flag():
|
|
"""A rebuild happened in some earlier session, on some earlier model
|
|
state. It rules out nothing about the file being read now."""
|
|
assert R.staleness_note(True, offline=True) == \
|
|
R.staleness_note(False, offline=True)
|
|
|
|
|
|
def test_not_in_the_index_is_not_not_in_the_model():
|
|
head, _lines, judged = R.target_block({'target': 'T1',
|
|
'target_status': 'not_in_index'})
|
|
assert judged is False
|
|
assert 'not in the index' in head
|
|
assert 'does not exist' in head # explicitly denied, not implied
|
|
other, _l, _j = R.target_block({'target': 'T1',
|
|
'target_status': 'not_in_model'})
|
|
assert head != other
|
|
|
|
|
|
# --------------------------------------------------- the design refusal
|
|
#
|
|
# Owner, 2026-09-01: "选择母版的目标,必须得是跟自己的design number一样的panel。
|
|
# ... 给我俩WP1.0的母版,压根用不了。" What this section holds to account is that
|
|
# the page REFUSES -- names the design, the counts and the way out -- and never
|
|
# prints a donor of another design, however it is labelled.
|
|
|
|
|
|
def _design(**kw):
|
|
base = {'status': 'none_match', 'refused': True, 'enforced': True,
|
|
'ran': True, 'target': 'WP2.0', 'target_key': 'WP2',
|
|
'index_status': 'recorded', 'candidates': 412, 'considered': 690,
|
|
'same_design': [], 'unreadable': [],
|
|
'designs_seen': {'WP1': 300, 'WP3': 110, '?': 2}}
|
|
base.update(kw)
|
|
return base
|
|
|
|
|
|
def test_no_refusal_when_the_design_rule_is_content():
|
|
assert R.design_refusal({'design': _design(status='ok', refused=False)}) \
|
|
is None
|
|
assert R.design_refusal({}) is None
|
|
|
|
|
|
def test_the_refusal_names_the_design_the_counts_and_the_way_out():
|
|
head, lines = R.design_refusal({'target': 'WP204',
|
|
'design': _design()})
|
|
assert 'WP2' in head and 'WP204' in head
|
|
body = ' '.join(lines)
|
|
assert 'WP1 (300)' in body and 'WP3 (110)' in body
|
|
assert 'unreadable (2)' in body # never counted as a design
|
|
assert '412' in body and '690' in body
|
|
assert 'by hand' in body # the way out
|
|
|
|
|
|
def test_the_refusal_says_the_rule_is_a_filter_not_a_preference():
|
|
_head, lines = R.design_refusal({'target': 'WP204', 'design': _design()})
|
|
body = ' '.join(lines)
|
|
assert 'filter' in body
|
|
assert 'not offer a donor of another design' in body
|
|
|
|
|
|
def test_the_refusal_states_the_packet_suffix_rule():
|
|
"""A reader has to be able to tell "no WP2 donor" from "your WP2.A donor
|
|
was thrown away for its suffix". The nomenclature ruling is on the page."""
|
|
_head, lines = R.design_refusal({'target': 'WP204', 'design': _design()})
|
|
body = ' '.join(lines)
|
|
assert 'WP2.0' in body and 'WP2.A' in body
|
|
|
|
|
|
def test_an_unrecorded_index_and_an_unread_one_read_differently():
|
|
"""Two different fixes -- rebuild the index versus map the role -- so two
|
|
different sentences. Both say the snapshot has to be re-run."""
|
|
a = R.design_refusal({'target': 'WP204',
|
|
'design': _design(status='index_not_recorded',
|
|
index_status='not_recorded')})
|
|
b = R.design_refusal({'target': 'WP204',
|
|
'design': _design(status='index_unread',
|
|
index_status='unread')})
|
|
assert a[0] != b[0]
|
|
assert 'predates' in a[0]
|
|
assert 'Re-run the snapshot' in ' '.join(a[1])
|
|
assert 'Re-run the snapshot' in ' '.join(b[1])
|
|
assert 'panel.design_number' in ' '.join(b[1])
|
|
# And neither may read as "these all match".
|
|
for got in (a, b):
|
|
assert 'not' in ' '.join(got[1]) and 'match' in ' '.join(got[1])
|
|
|
|
|
|
def test_a_target_with_no_design_number_gets_its_own_refusal():
|
|
head, lines = R.design_refusal({
|
|
'target': 'WP204',
|
|
'design': _design(status='target_unknown', target=None,
|
|
target_key=None)})
|
|
assert 'WP204' in head
|
|
assert 'empty' in ' '.join(lines)
|
|
assert 'design_number' in ' '.join(lines)
|
|
|
|
|
|
def test_designs_seen_never_trails_off_into_nothing():
|
|
assert R.designs_seen_text({}) == 'none'
|
|
assert R.designs_seen_text({'WP1': 1, 'WP2': 9}) == 'WP2 (9), WP1 (1)'
|
|
|
|
|
|
def test_the_design_refusal_reaches_the_target_block_as_not_judged():
|
|
head, lines, judged = R.target_block(
|
|
{'target': 'WP204', 'target_status': 'ok',
|
|
'target_features': [{}], 'design': _design()})
|
|
assert judged is False
|
|
assert 'WP2' in head
|
|
assert lines # the counts and the way out
|
|
|
|
|
|
def test_a_target_that_could_not_be_measured_outranks_the_design_refusal():
|
|
"""Two findings at once. The geometry one is the more fundamental and is
|
|
what the reader needs first; the design rule never even ran on a piece
|
|
nobody could measure."""
|
|
head, _lines, judged = R.target_block(
|
|
{'target': 'WP204', 'target_status': 'unjudged',
|
|
'design': _design()})
|
|
assert judged is False
|
|
assert 'could not be measured' in head
|
|
|
|
|
|
# ------------------------------------------------------------- the whole page
|
|
|
|
def _info(**kw):
|
|
base = {'target': 'T1', 'target_status': 'ok', 'target_features': [{}],
|
|
'considered': 3, 'donors': 1, 'rejected': {}, 'rejected_names': {},
|
|
'master_donors': [], 'approximate_in_pool': [],
|
|
'features_by_donor': {}, 'audited': True}
|
|
base.update(kw)
|
|
return base
|
|
|
|
|
|
def _blocks(sections):
|
|
"""Every md block of every section, folded or not -- these tests are about
|
|
what the report SAYS, and `foldreport` is where folding is tested."""
|
|
out = []
|
|
for section in sections:
|
|
out.extend(b[1] for b in section.blocks if b[0] == 'md')
|
|
return out
|
|
|
|
|
|
def _headline(sections):
|
|
return [b[1] for b in sections[0].blocks if b[0] == 'md']
|
|
|
|
|
|
def _section(sections, key):
|
|
for section in sections:
|
|
if section.key == key:
|
|
return section
|
|
return None
|
|
|
|
|
|
def test_render_opens_with_the_offline_banner_and_the_index_line():
|
|
out = R.render(None, _info(target_status='not_in_index'), [],
|
|
{'built_at': 'x', 'pieces': {}}, offline=True)
|
|
md = _headline(out)
|
|
assert md[0].startswith('# ')
|
|
assert 'OFFLINE' in md[1]
|
|
assert any('Index:' in line for line in md)
|
|
|
|
|
|
def test_render_never_prints_the_offline_banner_when_it_is_live():
|
|
out = R.render(None, _info(target_status='not_in_index'), [],
|
|
{'built_at': 'x', 'pieces': {}})
|
|
assert not any('OFFLINE' in line for line in _blocks(out))
|
|
|
|
|
|
def test_the_whole_page_refuses_and_offers_no_donor_of_another_design():
|
|
"""End to end: a WP2 target, a pool of WP1s. The page must say so, must
|
|
keep every paragraph of the refusal, and must not print a proposal."""
|
|
out = R.render(None, _info(target='WP204', design=_design(),
|
|
rejected={'design': 412},
|
|
rejected_names={'design': ['WP101']}),
|
|
[], {'built_at': 'x', 'pieces': {}})
|
|
md = ' '.join(_blocks(out))
|
|
assert 'NOT JUDGED' in md
|
|
assert 'No donor with design number WP2' in md
|
|
assert 'by hand' in md # the way out survived the render
|
|
assert 'Proposals' not in md
|
|
# The index stamp and the staleness note still print, because they are
|
|
# what tells a reader whether to believe a refusal.
|
|
assert any('Index:' in line for line in _headline(out))
|
|
assert _section(out, 'staleness') is not None
|
|
|
|
|
|
def test_a_could_not_judge_page_still_carries_the_staleness_note():
|
|
"""The one page most likely to be read as 'nothing to see here'. It used
|
|
to print a stripped-down 'NOT JUDGED' and exit, losing the index stamp and
|
|
the staleness note -- which are what tell a reader whether to believe the
|
|
refusal."""
|
|
out = R.render(None, _info(target_status='not_in_index'), [],
|
|
{'pieces': {}}, offline=True)
|
|
assert any('checked against the model' in line for line in _blocks(out))
|
|
assert _section(out, 'staleness').problem # and it is OPEN
|
|
|
|
|
|
def test_render_puts_the_featureless_donor_table_on_the_headline():
|
|
"""The owner's real complaint was silence, so this table belongs on the
|
|
part of the page that is never folded -- the headline, same as an
|
|
ordinary proposal."""
|
|
search = _search(eligible=[_donor_row('P2')])
|
|
out = R.render(None, _info(target_status='no_features',
|
|
shape_search=search),
|
|
[], {'built_at': 'x', 'pieces': {}})
|
|
assert 'MEASURED -- NO OPENINGS' in ' '.join(_headline(out))
|
|
tables = [b for b in out[0].blocks if b[0] == 'table']
|
|
assert tables and tables[0][2][0][0] == 'P2'
|
|
|
|
|
|
def test_render_with_no_shape_search_keeps_the_old_not_judged_heading():
|
|
out = R.render(None, _info(target_status='no_features'), [],
|
|
{'built_at': 'x', 'pieces': {}})
|
|
assert 'NOT JUDGED' in ' '.join(_headline(out))
|
|
assert not any(b[0] == 'table' for b in out[0].blocks)
|
|
|
|
|
|
# ---------------------------------------------- what folds and what opens
|
|
#
|
|
# The owner's rule, 2026-08-30: everything below the answer starts folded,
|
|
# and a section opens itself only when it has a problem in it. The contract
|
|
# that buys is "if nothing below the headline is open, nothing needs me",
|
|
# so what these tests hold to account is which sections claim a problem.
|
|
|
|
|
|
class _P(object):
|
|
donors = ('P1',)
|
|
masters = ()
|
|
same_elevation = ()
|
|
covered_count = 1
|
|
exact_count = 1
|
|
total_distance = 0.0
|
|
|
|
def donor_for(self, _i):
|
|
return None
|
|
|
|
|
|
class _S(object):
|
|
status = match.COMPLETE
|
|
proposals = (_P(),)
|
|
best = _P()
|
|
uncovered = ()
|
|
capped_out = ()
|
|
donor_cap = 4
|
|
donors_needed = 1
|
|
cover_count = 1
|
|
target_feature_count = 1
|
|
|
|
def summary(self):
|
|
return 'one donor covers it'
|
|
|
|
|
|
def test_the_answer_is_the_headline_and_is_never_folded():
|
|
out = R.render(_S(), _info(), [], {'pieces': {}})
|
|
assert out[0].headline is True
|
|
assert out[0].is_open is True
|
|
heads = ' '.join(_headline(out))
|
|
assert 'Target' in heads and 'Proposals' in heads
|
|
|
|
|
|
def test_render_emits_tables_as_tables_not_as_text():
|
|
"""A terminal has to be able to align them, so they stay structured."""
|
|
out = R.render(_S(), _info(), [], {'pieces': {}}, offline=True)
|
|
tables = [b for b in out[0].blocks if b[0] == 'table']
|
|
assert tables
|
|
assert tables[0][1] == R.PROPOSAL_COLUMNS
|
|
assert tables[0][2][0][1] == 'P1'
|
|
|
|
|
|
def test_the_routine_sections_are_folded_by_default():
|
|
"""`howto` is the standing grain note and the read-only line: exactly the
|
|
'information I do not care about' the rule is aimed at."""
|
|
out = R.render(_S(), _info(), [], {'built_at': _now_stamp(),
|
|
'pieces': {}})
|
|
assert _section(out, 'howto').is_open is False
|
|
assert _section(out, 'staleness').is_open is False
|
|
|
|
|
|
def test_a_fresh_index_folds_the_staleness_note_and_an_old_one_opens_it():
|
|
fresh = R.render(_S(), _info(), [], {'built_at': _now_stamp(),
|
|
'pieces': {}})
|
|
assert _section(fresh, 'staleness').is_open is False
|
|
old = R.render(_S(), _info(), [], {'built_at': '2020-01-01 00:00:00',
|
|
'pieces': {}})
|
|
assert _section(old, 'staleness').problem
|
|
assert 'day(s) old' in _section(old, 'staleness').problem
|
|
|
|
|
|
def test_offline_always_opens_the_staleness_section():
|
|
out = R.render(_S(), _info(), [], {'built_at': _now_stamp(),
|
|
'pieces': {}}, offline=True)
|
|
assert 'nothing was checked against the model' in _section(out, 'staleness').problem
|
|
|
|
|
|
def test_a_check_that_did_not_run_opens_its_section():
|
|
out = R.render(_S(), _info(master_unjudged=True), [], {'pieces': {}})
|
|
assert 'DID NOT RUN' in _section(out, 'provenance').problem
|
|
|
|
|
|
def test_a_check_that_ran_and_found_nothing_stays_folded():
|
|
out = R.render(_S(), _info(donors=3), [], {'built_at': _now_stamp(),
|
|
'pieces': {}})
|
|
prov = _section(out, 'provenance')
|
|
assert prov is not None and prov.is_open is False
|
|
|
|
|
|
def test_a_target_outside_the_declared_scope_opens_the_pool_section():
|
|
info = _info(scope={'declared': True, 'label': 'marks beginning PNL',
|
|
'line': 'x', 'declaration': [],
|
|
'target_in_scope': False,
|
|
'target_annotation': 'judged anyway'})
|
|
out = R.render(_S(), info, [], {'pieces': {}})
|
|
assert 'OUTSIDE the declared scope' in _section(out, 'pool').problem
|
|
|
|
|
|
def test_features_nobody_has_open_themselves():
|
|
class _Partial(_S):
|
|
status = match.PARTIAL
|
|
uncovered = (0,)
|
|
|
|
rows = [{'target_index': 0, 'kind': 'opening', 'donor': None,
|
|
'rect': (0.0, 1.0, 0.0, 1.0), 'strip_ft': None}]
|
|
out = R.render(_Partial(), _info(), rows, {'pieces': {}})
|
|
assert 'still need somebody' in _section(out, 'gaps').problem
|
|
|
|
|
|
def test_a_folded_section_still_says_what_is_inside_it():
|
|
"""Folded is not gone: a header with no summary tells the reader nothing
|
|
about whether to open it."""
|
|
out = R.render(_S(), _info(), [], {'built_at': _now_stamp(),
|
|
'pieces': {}})
|
|
for section in out[1:]:
|
|
assert section.title
|
|
if not section.is_open:
|
|
assert section.summary, section.key
|
|
|
|
|
|
def _now_stamp():
|
|
import time
|
|
return time.strftime('%Y-%m-%d %H:%M:%S')
|
|
|
|
|
|
# ------------------------------------------------------ demo-safe: no CJK
|
|
#
|
|
# Owner, 2026-09-01: no Chinese anywhere in a report or a UI surface, even
|
|
# when quoting him -- he is demoing this to colleagues before the weekend.
|
|
# The owner's own verbatim rulings live on in code COMMENTS (provenance),
|
|
# never in a string a render can put on screen. This is the durable guard:
|
|
# render both surfaces this module builds and fail if a CJK character ever
|
|
# reaches them again.
|
|
_CJK_RE = re.compile(u'[一-鿿]')
|
|
|
|
|
|
def test_the_rendered_report_carries_no_chinese_text_or_html():
|
|
"""Both the design-refusal path and the ordinary proposal path, through
|
|
both `foldreport` renderers -- text (the .txt log) and HTML (the panel).
|
|
Neither may ever show the owner's rulings in the language he gave them
|
|
in; the Chinese stays in the module's `#` comments only."""
|
|
refusal_out = R.render(None, _info(target='WP204', design=_design(),
|
|
rejected={'design': 412},
|
|
rejected_names={'design': ['WP101']}),
|
|
[], {'built_at': 'x', 'pieces': {}})
|
|
proposal_out = R.render(_S(), _info(), [],
|
|
{'built_at': _now_stamp(), 'pieces': {}})
|
|
for sections in (refusal_out, proposal_out):
|
|
text_out = fold.text(sections)
|
|
html_out = fold.html(sections)
|
|
assert not _CJK_RE.search(text_out), text_out
|
|
assert not _CJK_RE.search(html_out), html_out
|