"""What a scanned badge looks like, in one place. Two kiosks read the same physical badges and disagreed about them. The parts kiosk understood both shapes a scanner emits; the USB kiosk took the string raw and compared it to an SSO, so the wrapped form was simply "not recognized". An operator whose keyboard-wedge scanner emits `0270015376BZ` could take a 3D-printed part and could not check out a drive - with the same badge, at two cabinets a few metres apart. The shapes: 270015376 the SSO as printed, what a keypad entry looks like 0270015376BZ the PayNo wrapper some scanners emit around it Both mean the same person. Nothing else is a badge - and in particular a string of NON-ASCII digits is not, however much it looks like one: Arabic-Indic digits pass Python's str.isdigit(), and a badge written in them was accepted, stored as the employee's SSO, and joined to nothing afterwards. This lives in core rather than in either plugin because a plugin may not import another plugin, and because a badge is a property of the site, not of a cabinet. """ import re # The PayNo wrapper: a leading zero, the digits, and a BZ suffix. PAYNO_BADGE = re.compile(r'^0(\d+)BZ$', re.IGNORECASE) # ASCII digits only. str.isdigit() is true for Arabic-Indic and other decimal # forms, and int() happily parses them, so a badge in those characters resolved # to a real employee and was then written back in a form nothing else matches. ASCII_DIGITS = re.compile(r'^[0-9]+$') class BadgeShapeError(ValueError): """The scanned string is not a badge this site recognises.""" def normalise_badge(raw): """Return the plain SSO digits for a scanned badge. Accepts the bare number and the PayNo wrapper, and raises BadgeShapeError for anything else. The caller decides what to do about a badge that is well-formed but matches no employee - that is a directory question, not a shape one. """ badge = (raw or '').strip() if not badge: raise BadgeShapeError('Scan or enter a badge') if ASCII_DIGITS.match(badge): return badge match = PAYNO_BADGE.match(badge) if match and ASCII_DIGITS.match(match.group(1)): return match.group(1) raise BadgeShapeError('Unrecognized badge format') def badge_kind(raw): """'sso' or 'payno' for a recognised badge; raises for anything else. The external HR lookup searches a different column for each, so the shape survives normalisation for that one caller. """ badge = (raw or '').strip() if ASCII_DIGITS.match(badge): return 'sso' if PAYNO_BADGE.match(badge): return 'payno' raise BadgeShapeError('Unrecognized badge format')