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
« prev ^ index » next coverage.py v7.13.1, created at 2026-03-29 09:03 +0000
1from typing import List, Optional, Tuple, Union
3from presidio_analyzer import EntityRecognizer, Pattern, PatternRecognizer
6class ThTninRecognizer(PatternRecognizer):
7 """
8 Recognize Thai National ID Number (TNIN).
10 The Thai National ID Number (TNIN) is a unique 13-digit number
11 issued to all Thai residents.
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
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
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
35 Reference: https://th.wikipedia.org/wiki/เลขประจำตัวประชาชนไทย
36 https://th.wikipedia.org/wiki/ISO_3166-2:TH
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 """
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 ]
55 CONTEXT = [
56 "Thai National ID",
57 "Thai ID Number",
58 "TNIN",
59 "เลขประจำตัวประชาชน",
60 "เลขบัตรประชาชน",
61 "รหัสปชช",
62 ]
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 []
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 )
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.
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 )
98 # Check if the sanitized value has the correct length (13 digits)
99 if len(sanitized_value) != 13:
100 return False
102 # Check if all characters are digits
103 if not sanitized_value.isdigit():
104 return False
106 # Validate TNIN checksum (format validation is handled by regex)
107 return self._validate_checksum(sanitized_value)
109 def _validate_checksum(self, tnin: str) -> bool:
110 """
111 Validate the checksum of Thai TNIN.
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
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]
125 total_sum = 0
126 for i in range(12): # First 12 digits
127 total_sum += weights[i] * int(tnin[i])
129 # Calculate x = S mod 11
130 x = total_sum % 11
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
138 # Compare with actual check digit
139 actual_check_digit = int(tnin[12])
141 return expected_check_digit == actual_check_digit