WillTools 2026-09-09 -- clean snapshot of the development repo for review
ci / test (push) Successful in 6m14s
ci / test (push) Successful in 6m14s
History-free snapshot of the private development repository at 77a82eb. Code, docs and the public catalogue only: no plant or job knowledge layers, no client, job or producer names (tools/scrub.py clean against the widened term list; a second word-level grep clean). The intro deck is left out because its slide images cannot be checked for job drawings.
This commit is contained in:
@@ -0,0 +1,276 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""`ticketchecks.PLANT_GATE_KEYS` / `plant_gate_reason` / `plant_scoped_out`,
|
||||
and the row-filtering `ticketqc_run.py` builds on top of them.
|
||||
|
||||
Added 2026-09-08, owner report: a Ticket QC on another plant's job showed "a pile
|
||||
of MC checks" -- 6.9/6.10/6.11 (`check_lifting_points` / `check_lift_
|
||||
positions` / `check_dunnage_stripping_match`) are MC plant rules, gated on
|
||||
knowledge keys under `lifters.lifting_points.*` that only MC's own plant
|
||||
layer sets, and on any other plant they printed inline as an alarming red
|
||||
NOT RUN row -- indistinguishable from a check that failed for a real reason
|
||||
on this piece.
|
||||
|
||||
`ticketqc_run.py` itself cannot be imported on host CPython -- see
|
||||
`conftest.py`'s docstring and `test_reinforcing_qc.py`'s own note: it pulls
|
||||
in `vibe.ticketqc`, `vibe.designspec`, `pyrevit.script/forms` and more, all
|
||||
of which touch the Revit API at module scope. So this file tests the two
|
||||
PURE pieces `ticketqc_run.run()` composes -- `ticketchecks.plant_scoped_out`
|
||||
and `ticketpage.rows_owned_by` -- and reproduces, line for line, the same
|
||||
filter-then-tally the report itself runs
|
||||
(`r['rows'] = [row for row in r['rows'] if row[0] not in _plant_scoped_nums]`
|
||||
then `r['counts'] = TP.checklist_tally(r['rows'])`), so a passing test here
|
||||
really is proof of what the report renders.
|
||||
"""
|
||||
# pylint: disable=import-error,invalid-name,broad-except
|
||||
import sys
|
||||
import types
|
||||
|
||||
import pytest
|
||||
|
||||
from vibe import ticketpage as TP
|
||||
|
||||
|
||||
def _stub_revit():
|
||||
if 'Autodesk.Revit.DB' in sys.modules:
|
||||
return []
|
||||
autodesk = types.ModuleType('Autodesk')
|
||||
revit = types.ModuleType('Autodesk.Revit')
|
||||
db = types.ModuleType('Autodesk.Revit.DB')
|
||||
for name in ('FilteredElementCollector', 'AssemblyInstance', 'View',
|
||||
'ViewSheet', 'ViewType', 'BuiltInCategory', 'Element',
|
||||
'StorageType', 'ElementId', 'XYZ', 'Transform',
|
||||
'Options', 'Solid', 'BoundingBoxXYZ'):
|
||||
setattr(db, name, type(str(name), (object,), {}))
|
||||
autodesk.Revit = revit
|
||||
revit.DB = db
|
||||
sys.modules['Autodesk'] = autodesk
|
||||
sys.modules['Autodesk.Revit'] = revit
|
||||
sys.modules['Autodesk.Revit.DB'] = db
|
||||
return ['Autodesk.Revit.DB', 'Autodesk.Revit', 'Autodesk']
|
||||
|
||||
|
||||
_ADDED = _stub_revit()
|
||||
try:
|
||||
from vibe import ticketchecks as TC # noqa: E402
|
||||
finally:
|
||||
for _name in _ADDED:
|
||||
sys.modules.pop(_name, None)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------
|
||||
# PLANT_GATE_KEYS / plant_gate_reason -- the check-by-check declaration.
|
||||
|
||||
_FULLY_MC = {
|
||||
'lifters': {
|
||||
'lifting_points': {
|
||||
'two_point_max_length_ft': 20,
|
||||
'two_point_absolute_max_ft': 25,
|
||||
'pci_position_tolerance_in': 6,
|
||||
'dunnage_matches_stripping': True,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def test_plant_gate_keys_names_exactly_the_three_mc_rows():
|
||||
"""The classification this task was asked to produce, pinned so a
|
||||
future check gains or loses plant-scoping only as a visible edit here,
|
||||
the same discipline `test_reinforcing_qc.py` uses for
|
||||
`REINFORCING_EXCLUDED`."""
|
||||
assert set(TC.PLANT_GATE_KEYS) == {
|
||||
'check_lifting_points', 'check_lift_positions',
|
||||
'check_dunnage_stripping_match'}
|
||||
|
||||
|
||||
def test_plant_gate_reason_is_none_for_an_ungated_check():
|
||||
assert TC.plant_gate_reason('check_lifter_capacity', {}) is None
|
||||
assert TC.plant_gate_reason('check_lifter_capacity', _FULLY_MC) is None
|
||||
|
||||
|
||||
def test_plant_gate_reason_is_none_once_every_key_is_set():
|
||||
for name in TC.PLANT_GATE_KEYS:
|
||||
assert TC.plant_gate_reason(name, _FULLY_MC) is None, name
|
||||
|
||||
|
||||
def test_plant_gate_reason_names_every_missing_key_on_an_empty_layer():
|
||||
reason = TC.plant_gate_reason('check_lifting_points', {})
|
||||
assert reason
|
||||
assert 'two_point_max_length_ft' in reason
|
||||
assert 'two_point_absolute_max_ft' in reason
|
||||
|
||||
reason = TC.plant_gate_reason('check_lift_positions', {})
|
||||
assert reason
|
||||
assert 'pci_position_tolerance_in' in reason
|
||||
|
||||
reason = TC.plant_gate_reason('check_dunnage_stripping_match', {})
|
||||
assert reason
|
||||
assert 'dunnage_matches_stripping' in reason
|
||||
|
||||
|
||||
def test_plant_gate_reason_needs_every_key_and_names_only_the_missing_one():
|
||||
"""6.9 needs BOTH its keys -- one set and one missing still gates the
|
||||
row, and the reason names the missing key without re-naming the one
|
||||
that is already set."""
|
||||
half = {'lifters': {'lifting_points': {'two_point_max_length_ft': 20}}}
|
||||
reason = TC.plant_gate_reason('check_lifting_points', half)
|
||||
assert reason
|
||||
assert 'two_point_absolute_max_ft' in reason
|
||||
assert 'two_point_max_length_ft' not in reason
|
||||
|
||||
|
||||
def test_plant_scoped_out_is_empty_on_a_fully_configured_plant():
|
||||
assert TC.plant_scoped_out(_FULLY_MC) == {}
|
||||
|
||||
|
||||
def test_plant_scoped_out_names_all_three_on_an_empty_layer():
|
||||
scoped = TC.plant_scoped_out({})
|
||||
assert set(scoped) == {
|
||||
'check_lifting_points', 'check_lift_positions',
|
||||
'check_dunnage_stripping_match'}
|
||||
for name, reason in scoped.items():
|
||||
assert isinstance(reason, str) and reason.strip(), name
|
||||
|
||||
|
||||
def test_plant_scoped_out_is_per_check_not_all_or_nothing_globally():
|
||||
"""A plant that has set 6.9's keys but not 6.10/6.11's is scoped out of
|
||||
exactly the two it never declared -- this is not one big MC switch."""
|
||||
partial = {
|
||||
'lifters': {
|
||||
'lifting_points': {
|
||||
'two_point_max_length_ft': 20,
|
||||
'two_point_absolute_max_ft': 25,
|
||||
},
|
||||
},
|
||||
}
|
||||
scoped = TC.plant_scoped_out(partial)
|
||||
assert set(scoped) == {'check_lift_positions',
|
||||
'check_dunnage_stripping_match'}
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------
|
||||
# The skip probes agree with `plant_gate_reason` -- never a second opinion.
|
||||
|
||||
def test_probes_report_a_reason_exactly_when_plant_gate_reason_does():
|
||||
t = {'lifters': [{'family': 'X'}]} # a lifter exists, so the probe
|
||||
# cannot defer to 6.1 instead
|
||||
for name, know in (('check_lifting_points', {}),
|
||||
('check_lift_positions', {}),
|
||||
('check_dunnage_stripping_match', {})):
|
||||
probe = TC._SKIP_PROBES[name]
|
||||
assert TC.plant_gate_reason(name, know) is not None
|
||||
assert probe(t, {}, know, None) is not None
|
||||
|
||||
for name in TC.PLANT_GATE_KEYS:
|
||||
probe = TC._SKIP_PROBES[name]
|
||||
assert TC.plant_gate_reason(name, _FULLY_MC) is None
|
||||
# with the gate open and a lifter present, the probe defers to
|
||||
# piece-level silences (or None) -- never repeats the plant gate.
|
||||
result = probe(t, {}, _FULLY_MC, None)
|
||||
assert result is None or 'not set' not in (result or '')
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------
|
||||
# `ticketpage.rows_owned_by` -- the checklist-row resolver both
|
||||
# `_excluded_row_numbers` (stage-scope) and `_plant_scoped_row_numbers`
|
||||
# (plant-scope) in `ticketqc_run.py` are thin wrappers over.
|
||||
|
||||
def test_rows_owned_by_finds_exactly_69_610_611_on_an_empty_plant():
|
||||
scoped = TC.plant_scoped_out({})
|
||||
assert TP.rows_owned_by(scoped) == {'6.9', '6.10', '6.11'}
|
||||
|
||||
|
||||
def test_rows_owned_by_finds_nothing_on_a_fully_configured_plant():
|
||||
scoped = TC.plant_scoped_out(_FULLY_MC)
|
||||
assert TP.rows_owned_by(scoped) == set()
|
||||
|
||||
|
||||
def test_rows_owned_by_is_empty_for_an_empty_names_container():
|
||||
assert TP.rows_owned_by({}) == set()
|
||||
assert TP.rows_owned_by(set()) == set()
|
||||
|
||||
|
||||
def test_row_names_carry_the_neutral_phrase_not_mc():
|
||||
"""The catalogue names for 6.9/6.10/6.11 no longer say "(MC)" -- the
|
||||
literal client/plant tag the owner asked stripped, 2026-09-08. This is
|
||||
the golden that catches a regression back to it."""
|
||||
names = dict((num, name) for (num, name, _g, _o, _r, _c)
|
||||
in TP.CHECKLIST_ITEMS if num in ('6.9', '6.10', '6.11'))
|
||||
assert names['6.9'] == 'Lifting-point count vs panel length (plant rule)'
|
||||
assert names['6.10'] == (
|
||||
'Lifting-point position vs PCI recommendation (plant rule)')
|
||||
assert names['6.11'] == (
|
||||
'Dunnage-point count vs stripping-point count (plant rule)')
|
||||
for name in names.values():
|
||||
assert '(MC)' not in name
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------
|
||||
# THE FULL PIPELINE `ticketqc_run.py` runs, reproduced exactly:
|
||||
#
|
||||
# _plant_scoped = TC.plant_scoped_out(know)
|
||||
# _plant_scoped_nums = TP.rows_owned_by(_plant_scoped)
|
||||
# r['rows'] = [row for row in r['rows'] if row[0] not in _plant_scoped_nums]
|
||||
# r['counts'] = TP.checklist_tally(r['rows'])
|
||||
#
|
||||
# A passing test here is proof of what the report itself does with these
|
||||
# three rows, not just proof the pieces exist.
|
||||
|
||||
def _synthetic_rows():
|
||||
"""One NOT RUN row per plant-gated check (as `checklist_states` would
|
||||
render them on an empty knowledge layer, gate-closed), plus one
|
||||
ordinary OK row that must survive untouched."""
|
||||
scoped = TC.plant_scoped_out({})
|
||||
return [
|
||||
('6.9', 'Lifting-point count vs panel length (plant rule)', 'A',
|
||||
'NOT RUN', scoped['check_lifting_points']),
|
||||
('6.10', 'Lifting-point position vs PCI recommendation (plant rule)',
|
||||
'A', 'NOT RUN', scoped['check_lift_positions']),
|
||||
('6.11', 'Dunnage-point count vs stripping-point count (plant rule)',
|
||||
'A', 'NOT RUN', scoped['check_dunnage_stripping_match']),
|
||||
('6.1', 'Insert capacity vs load', 'A', 'OK', 'lifters clear'),
|
||||
], scoped
|
||||
|
||||
|
||||
def test_a_plant_without_the_gating_keys_pulls_all_three_out_of_the_table():
|
||||
rows, scoped = _synthetic_rows()
|
||||
scoped_nums = TP.rows_owned_by(scoped)
|
||||
|
||||
kept = [row for row in rows if row[0] not in scoped_nums]
|
||||
|
||||
assert [row[0] for row in kept] == ['6.1']
|
||||
assert set(row[0] for row in rows) - set(row[0] for row in kept) == {
|
||||
'6.9', '6.10', '6.11'}
|
||||
|
||||
|
||||
def test_a_plant_without_the_gating_keys_excludes_them_from_header_counts():
|
||||
rows, scoped = _synthetic_rows()
|
||||
scoped_nums = TP.rows_owned_by(scoped)
|
||||
kept = [row for row in rows if row[0] not in scoped_nums]
|
||||
|
||||
tally = TP.checklist_tally(kept)
|
||||
assert tally == {'OK': 1}
|
||||
assert 'NOT RUN' not in tally
|
||||
|
||||
# The unfiltered tally, for contrast: this is the leak the fix closes.
|
||||
unfiltered_tally = TP.checklist_tally(rows)
|
||||
assert unfiltered_tally.get('NOT RUN') == 3
|
||||
|
||||
|
||||
def test_a_plant_with_the_gating_keys_keeps_all_rows_in_the_table():
|
||||
scoped = TC.plant_scoped_out(_FULLY_MC)
|
||||
scoped_nums = TP.rows_owned_by(scoped)
|
||||
assert scoped_nums == set()
|
||||
|
||||
rows = [
|
||||
('6.9', 'Lifting-point count vs panel length (plant rule)', 'A',
|
||||
'OK', '2 lifting points on a 18\'-0" panel'),
|
||||
('6.10', 'Lifting-point position vs PCI recommendation (plant rule)',
|
||||
'A', 'OK', 'within tolerance'),
|
||||
('6.11', 'Dunnage-point count vs stripping-point count (plant rule)',
|
||||
'A', 'OK', 'matches stripping'),
|
||||
('6.1', 'Insert capacity vs load', 'A', 'OK', 'lifters clear'),
|
||||
]
|
||||
kept = [row for row in rows if row[0] not in scoped_nums]
|
||||
assert [row[0] for row in kept] == ['6.9', '6.10', '6.11', '6.1']
|
||||
tally = TP.checklist_tally(kept)
|
||||
assert tally == {'OK': 4}
|
||||
Reference in New Issue
Block a user