Coverage for presidio_analyzer / predefined_recognizers / country_specific / thai / th_tnin_recognizer.py: 100%

28 statements  

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

1from typing import List, Optional, Tuple, Union 

2 

3from presidio_analyzer import EntityRecognizer, Pattern, PatternRecognizer 

4 

5 

6class ThTninRecognizer(PatternRecognizer): 

7 """ 

8 Recognize Thai National ID Number (TNIN). 

9 

10 The Thai National ID Number (TNIN) is a unique 13-digit number 

11 issued to all Thai residents. 

12 

13 The format is N1N2N3N4N5N6N7N8N9N10N11N12N13 where: 

14 - N1-N12 are the main digits 

15 - N13 is a check digit calculated using the preceding 12 digits 

16 

17 Validation rules: 

18 - Must be exactly 13 digits 

19 - First digit (N1) cannot be 0 

20 - Second digit (N2) cannot be 0 

21 - Second and third digits (N2N3) cannot be: 28, 29, 59, 68, 69, 78, 79, 

22 87, 88, 89, 97, 98, 99 

23 These second and third digits in Thai ID number correspond to Thai provinces, 

24 so we exclude non-existent or unassigned combinations in Thailand's 

25 administrative division system. See ISO 3166-2:TH for reference. 

26 - The 13th digit is a checksum computed modulo 11 from the first 12 digits 

27 

28 Checksum algorithm: 

29 - Label first 12 digits N1…N12 (left to right) 

30 - Compute S = 13·N1 + 12·N2 + … + 2·N12 

31 - Let x = S mod 11 

32 - Then check digit N13 = (11 − x) mod 10 

33 - Equivalently: if x ≤ 1 then N13 = 1 − x; otherwise N13 = 11 − x 

34 

35 Reference: https://th.wikipedia.org/wiki/เลขประจำตัวประชาชนไทย 

36 https://th.wikipedia.org/wiki/ISO_3166-2:TH 

37 

38 

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

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

41 :param supported_language: Language this recognizer supports 

42 :param supported_entity: The entity this recognizer can detect 

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

44 for different strings to be used during pattern matching. 

45 """ 

46 

47 PATTERNS = [ 

48 Pattern( 

49 "TNIN (Medium)", 

50 r"\b[1-9](?:[134][0-9]|[25][0134567]|[67][01234567]|[89][0123456])\d{10}\b", 

51 0.5, 

52 ) 

53 ] 

54 

55 CONTEXT = [ 

56 "Thai National ID", 

57 "Thai ID Number", 

58 "TNIN", 

59 "เลขประจำตัวประชาชน", 

60 "เลขบัตรประชาชน", 

61 "รหัสปชช", 

62 ] 

63 

64 def __init__( 

65 self, 

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

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

68 supported_language: str = "th", 

69 supported_entity: str = "TH_TNIN", 

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

71 name: Optional[str] = None, 

72 ): 

73 self.replacement_pairs = replacement_pairs if replacement_pairs else [] 

74 

75 patterns = patterns if patterns else self.PATTERNS 

76 context = context if context else self.CONTEXT 

77 super().__init__( 

78 supported_entity=supported_entity, 

79 patterns=patterns, 

80 context=context, 

81 supported_language=supported_language, 

82 name=name, 

83 ) 

84 

85 def validate_result(self, pattern_text: str) -> Union[bool, None]: 

86 """ 

87 Validate the pattern logic e.g., by running checksum on a detected pattern. 

88 

89 :param pattern_text: the text to validated. 

90 Only the part in text that was detected by the regex engine 

91 :return: A bool or None, indicating whether the validation was successful. 

92 """ 

93 # Pre-processing before validation checks 

94 sanitized_value = EntityRecognizer.sanitize_value( 

95 pattern_text, self.replacement_pairs 

96 ) 

97 

98 # Check if the sanitized value has the correct length (13 digits) 

99 if len(sanitized_value) != 13: 

100 return False 

101 

102 # Check if all characters are digits 

103 if not sanitized_value.isdigit(): 

104 return False 

105 

106 # Validate TNIN checksum (format validation is handled by regex) 

107 return self._validate_checksum(sanitized_value) 

108 

109 def _validate_checksum(self, tnin: str) -> bool: 

110 """ 

111 Validate the checksum of Thai TNIN. 

112 

113 Checksum algorithm: 

114 - Label first 12 digits N1…N12 (left to right) 

115 - Compute S = 13·N1 + 12·N2 + … + 2·N12 

116 - Let x = S mod 11 

117 - Then check digit N13 = (11 − x) mod 10 

118 

119 :param tnin: The TNIN to validate 

120 :return: True if checksum is valid, False otherwise 

121 """ 

122 # Calculate weighted sum: 13*N1 + 12*N2 + ... + 2*N12 

123 weights = list(range(13, 1, -1)) # [13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2] 

124 

125 total_sum = 0 

126 for i in range(12): # First 12 digits 

127 total_sum += weights[i] * int(tnin[i]) 

128 

129 # Calculate x = S mod 11 

130 x = total_sum % 11 

131 

132 # Calculate expected check digit: (11 - x) mod 10 

133 if x <= 1: 

134 expected_check_digit = 1 - x 

135 else: 

136 expected_check_digit = 11 - x 

137 

138 # Compare with actual check digit 

139 actual_check_digit = int(tnin[12]) 

140 

141 return expected_check_digit == actual_check_digit