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.
136 lines
5.3 KiB
Python
136 lines
5.3 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""The `panel.weight` role's generic name-pattern floor in `ticketqc`.
|
|
|
|
THE DEFECT THIS GUARDS AGAINST
|
|
-------------------------------
|
|
`read_ticket` used to read the piece's stated weight ONLY through the
|
|
plant-mapped role `panel.weight`. On a job whose plant has not mapped that
|
|
role the value is `None`, and Panel Lift's weight gate refused every piece
|
|
with "The panel has no weight parameter to check against" -- even though the
|
|
panel carries its cast weight under a generic name (measured live: a panel's
|
|
`MEMBER WEIGHT`-style parameter held it). Reading a model must be generic:
|
|
the role is an accelerator, a name-pattern read is the floor (owner,
|
|
2026-09-03: pre-fill must work on the next job with no code change).
|
|
|
|
WHAT IS PINNED HERE
|
|
--------------------
|
|
a. an instance `MEMBER_WEIGHT` whose display reads "9,974 lb" parses to
|
|
9974.0 -- the display carries the unit, so the leading numeric run is
|
|
taken before the shared number reader finishes;
|
|
b. a `WEIGHT PER FOOT` parameter is NOT a total weight -- 'per' excludes
|
|
it, and with nothing else matching the answer is None, never a guess;
|
|
c. an instance value beats a type value (the instance is what was set for
|
|
this particular piece);
|
|
d. an empty instance value falls through to the type's `PANEL WEIGHT`.
|
|
|
|
THE REVIT STUB
|
|
---------------
|
|
`vibe.ticketqc` imports from `Autodesk.Revit.DB` at module scope, so it
|
|
cannot be imported on host CPython without help. The stub below is the same
|
|
one `test_ticketqc_evidence.py` uses and it is PUT BACK afterwards for the
|
|
same reason: `sys.modules` is process-wide, and `test_reinforceoffline.py`
|
|
proves the offline path pulls in no Revit by asserting no `Autodesk*`
|
|
module is loaded.
|
|
|
|
Nothing under test here calls the Revit API -- the fakes below are plain
|
|
objects. If a future edit makes the helper reach for Revit for real, these
|
|
tests raise rather than quietly pass.
|
|
"""
|
|
# pylint: disable=import-error,invalid-name,broad-except
|
|
import sys
|
|
import types
|
|
|
|
|
|
def _stub_revit():
|
|
"""`Autodesk.Revit.DB`, inert, for a host interpreter."""
|
|
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 ticketqc as QC # noqa: E402
|
|
finally:
|
|
for _name in _ADDED:
|
|
sys.modules.pop(_name, None)
|
|
|
|
|
|
# --------------------------------------------------------------- fake model
|
|
#
|
|
# Plain stand-ins for the three surfaces `_weight_by_name_pattern` reads:
|
|
# `Parameter.Definition.Name`, `AsValueString()` / `AsString()`, the
|
|
# instance's `Parameters` list, and the type element that
|
|
# `Document.GetElement(GetTypeId())` hands back.
|
|
|
|
class _Param(object):
|
|
def __init__(self, name, display=None):
|
|
self.Definition = type('_Def', (object,), {'Name': name})()
|
|
self._display = display
|
|
|
|
def AsValueString(self):
|
|
return self._display
|
|
|
|
def AsString(self):
|
|
return None
|
|
|
|
|
|
class _Element(object):
|
|
"""A fake family instance, optionally with a reachable type."""
|
|
|
|
def __init__(self, instance_params, type_params=None):
|
|
self.Parameters = instance_params
|
|
self._type = None
|
|
if type_params is not None:
|
|
self._type = type('_Type', (object,),
|
|
{'Parameters': type_params})()
|
|
self.Document = type('_Doc', (object,), {})()
|
|
self.Document.GetElement = lambda type_id: self._type
|
|
|
|
def GetTypeId(self):
|
|
return 'TYPE-1'
|
|
|
|
|
|
def test_member_weight_instance_display_with_unit_parses():
|
|
el = _Element([_Param('MEMBER_WEIGHT', '9,974 lb')])
|
|
assert QC._weight_by_name_pattern(el) == 9974.0
|
|
|
|
|
|
def test_weight_per_foot_is_not_a_total_weight_and_nothing_else_matches():
|
|
el = _Element([_Param('WEIGHT PER FOOT', '150')])
|
|
assert QC._weight_by_name_pattern(el) is None
|
|
|
|
|
|
def test_weight_fragment_matching_an_exclude_is_skipped():
|
|
"""The excludes arm, pinned on its own: 'per' must veto a parameter
|
|
whose name still carries a positive 'total weight' fragment. The bare
|
|
'WEIGHT PER FOOT' above would pass even if the excludes were deleted
|
|
(it holds no positive fragment at all); this one would not."""
|
|
el = _Element([_Param('TOTAL WEIGHT PER FOOT', '150')])
|
|
assert QC._weight_by_name_pattern(el) is None
|
|
|
|
|
|
def test_instance_value_beats_a_type_value():
|
|
el = _Element([_Param('MEMBER_WEIGHT', '9,974 lb')],
|
|
type_params=[_Param('PANEL_WEIGHT', '8,000 lb')])
|
|
assert QC._weight_by_name_pattern(el) == 9974.0
|
|
|
|
|
|
def test_blank_instance_value_falls_through_to_the_types_panel_weight():
|
|
el = _Element([_Param('MEMBER_WEIGHT', '')],
|
|
type_params=[_Param('PANEL_WEIGHT', '12,500 lb')])
|
|
assert QC._weight_by_name_pattern(el) == 12500.0
|