Coverage for presidio_analyzer / predefined_recognizers / country_specific / us / medical_license_recognizer.py: 100%

25 statements  

« prev     ^ index     » next       coverage.py v7.13.1, created at 2026-03-29 09:03 +0000

1from typing import List, Optional, Tuple 

2 

3from presidio_analyzer import EntityRecognizer, Pattern, PatternRecognizer 

4 

5# https://www.meditec.com/blog/dea-numbers-what-do-they-mean 

6 

7 

8class MedicalLicenseRecognizer(PatternRecognizer): 

9 """ 

10 Recognize common Medical license numbers using regex + checksum. 

11 

12 :param patterns: List of patterns to be used by this recognizer 

13 :param context: List of context words to increase confidence in detection 

14 :param supported_language: Language this recognizer supports 

15 :param supported_entity: The entity this recognizer can detect 

16 :param replacement_pairs: List of tuples with potential replacement values 

17 for different strings to be used during pattern matching. 

18 This can allow a greater variety in input, for example by removing dashes or spaces. 

19 """ 

20 

21 PATTERNS = [ 

22 Pattern( 

23 "USA DEA Certificate Number (weak)", 

24 r"[abcdefghjklmprstuxABCDEFGHJKLMPRSTUX]{1}[a-zA-Z]{1}\d{7}|" 

25 r"[abcdefghjklmprstuxABCDEFGHJKLMPRSTUX]{1}9\d{7}", 

26 0.4, 

27 ), 

28 ] 

29 

30 CONTEXT = ["medical", "certificate", "DEA"] 

31 

32 def __init__( 

33 self, 

34 patterns: Optional[List[Pattern]] = None, 

35 context: Optional[List[str]] = None, 

36 supported_language: str = "en", 

37 supported_entity: str = "MEDICAL_LICENSE", 

38 replacement_pairs: Optional[List[Tuple[str, str]]] = None, 

39 name: Optional[str] = None, 

40 ): 

41 self.replacement_pairs = ( 

42 replacement_pairs if replacement_pairs else [("-", ""), (" ", "")] 

43 ) 

44 patterns = patterns if patterns else self.PATTERNS 

45 context = context if context else self.CONTEXT 

46 super().__init__( 

47 supported_entity=supported_entity, 

48 patterns=patterns, 

49 context=context, 

50 supported_language=supported_language, 

51 name=name, 

52 ) 

53 

54 def validate_result(self, pattern_text: str) -> bool: # noqa: D102 

55 sanitized_value = EntityRecognizer.sanitize_value( 

56 pattern_text, self.replacement_pairs 

57 ) 

58 checksum = self.__luhn_checksum(sanitized_value) 

59 

60 return checksum 

61 

62 @staticmethod 

63 def __luhn_checksum(sanitized_value: str) -> bool: 

64 def digits_of(n: str) -> List[int]: 

65 return [int(dig) for dig in str(n)] 

66 

67 digits = digits_of(sanitized_value[2:]) 

68 checksum = digits.pop() 

69 even_digits = digits[-1::-2] 

70 odd_digits = digits[-2::-2] 

71 checksum *= -1 

72 checksum += 2 * sum(even_digits) + sum(odd_digits) 

73 return checksum % 10 == 0