"""What a scanned badge looks like, in one place. Two callers read the same physical badges and disagreed about them. One understood both shapes a scanner emits; the other took the string raw and compared it to an SSO, so the wrapped form was simply "not recognized". The same operator, with the same badge, was recognised at one screen and not at the next. Anything that turns a scan into a person uses this module, so there is one answer to the question. 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 a plugin because a plugin may not import another plugin, and because a badge is a property of the site. Its consumer today is the notifications plugin, which resolves an employee by SSO. """ 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')