# -*- 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