Coverage for presidio_analyzer / predefined_recognizers / country_specific / nigeria / ng_nin_recognizer.py: 100%

23 statements  

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

1from typing import List, Optional 

2 

3from presidio_analyzer import Pattern, PatternRecognizer 

4 

5 

6class NgNinRecognizer(PatternRecognizer): 

7 """ 

8 Recognizes Nigerian National Identification Number (NIN). 

9 

10 The NIN is an 11-digit number issued by the National Identity Management 

11 Commission (NIMC). The last digit is a Verhoeff checksum. 

12 

13 Reference: https://nimc.gov.ng/ 

14 

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

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

17 :param supported_language: Language this recognizer supports 

18 :param supported_entity: The entity this recognizer can detect 

19 """ 

20 

21 PATTERNS = [ 

22 Pattern( 

23 "NIN (Very Weak)", 

24 r"\b\d{11}\b", 

25 0.01, 

26 ), 

27 ] 

28 

29 CONTEXT = [ 

30 "nin", 

31 "national identification number", 

32 "national identity number", 

33 "nimc", 

34 "national identity", 

35 "nigeria id", 

36 "nigerian identification", 

37 ] 

38 

39 def __init__( 

40 self, 

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

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

43 supported_language: str = "en", 

44 supported_entity: str = "NG_NIN", 

45 name: Optional[str] = None, 

46 ) -> None: 

47 patterns = patterns if patterns else self.PATTERNS 

48 context = context if context else self.CONTEXT 

49 super().__init__( 

50 supported_entity=supported_entity, 

51 patterns=patterns, 

52 context=context, 

53 supported_language=supported_language, 

54 name=name, 

55 ) 

56 

57 def validate_result(self, pattern_text: str) -> bool: 

58 """Validate the NIN by checking length, digits, and Verhoeff checksum.""" 

59 return self.__check_nin(pattern_text) 

60 

61 def __check_nin(self, value: str) -> bool: 

62 return ( 

63 len(value) == 11 

64 and value.isnumeric() 

65 and self._is_verhoeff_number(int(value)) 

66 ) 

67 

68 @staticmethod 

69 def _is_verhoeff_number(input_number: int) -> bool: 

70 """ 

71 Check if the input number is a true Verhoeff number. 

72 

73 :param input_number: Number to validate 

74 :return: True if the number passes the Verhoeff checksum 

75 """ 

76 __d__ = [ 

77 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], 

78 [1, 2, 3, 4, 0, 6, 7, 8, 9, 5], 

79 [2, 3, 4, 0, 1, 7, 8, 9, 5, 6], 

80 [3, 4, 0, 1, 2, 8, 9, 5, 6, 7], 

81 [4, 0, 1, 2, 3, 9, 5, 6, 7, 8], 

82 [5, 9, 8, 7, 6, 0, 4, 3, 2, 1], 

83 [6, 5, 9, 8, 7, 1, 0, 4, 3, 2], 

84 [7, 6, 5, 9, 8, 2, 1, 0, 4, 3], 

85 [8, 7, 6, 5, 9, 3, 2, 1, 0, 4], 

86 [9, 8, 7, 6, 5, 4, 3, 2, 1, 0], 

87 ] 

88 __p__ = [ 

89 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], 

90 [1, 5, 7, 6, 2, 8, 3, 0, 9, 4], 

91 [5, 8, 0, 3, 7, 9, 6, 1, 4, 2], 

92 [8, 9, 1, 6, 0, 4, 3, 5, 2, 7], 

93 [9, 4, 5, 3, 1, 2, 6, 8, 7, 0], 

94 [4, 2, 8, 6, 5, 7, 3, 9, 0, 1], 

95 [2, 7, 9, 3, 8, 0, 6, 4, 1, 5], 

96 [7, 0, 4, 6, 9, 1, 3, 2, 5, 8], 

97 ] 

98 __inv__ = [0, 4, 3, 2, 1, 5, 6, 7, 8, 9] 

99 

100 c = 0 

101 inverted_number = list(map(int, reversed(str(input_number)))) 

102 for i in range(len(inverted_number)): 

103 c = __d__[c][__p__[i % 8][inverted_number[i]]] 

104 return __inv__[c] == 0