Coverage for presidio_analyzer / predefined_recognizers / country_specific / korea / kr_rrn_recognizer.py: 93%
29 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
3from presidio_analyzer import EntityRecognizer, Pattern, PatternRecognizer
6class KrRrnRecognizer(PatternRecognizer):
7 """
8 Recognize Korean Resident Registration Number (RRN).
10 The Korean Resident Registration Number (RRN) is
11 a 13-digit number issued to all Korean residents.
13 The format is YYMMDD-GHIJKLX where:
14 - YYMMDD represents the birth date
15 - G determines gender and century of birth
17 For RRNs issued before October 2020:
18 - HIJKL is a serial number assigned by district
19 - X is a check digit calculated using the preceding 12 digits
21 For RRNs issued after October 2020:
22 - HIJKLX is a random number
24 Reference: https://en.wikipedia.org/wiki/Resident_registration_number
26 :param patterns: List of patterns to be used by this recognizer
27 :param context: List of context words to increase confidence in detection
28 :param supported_language: Language this recognizer supports
29 :param supported_entity: The entity this recognizer can detect
30 :param replacement_pairs: List of tuples with potential replacement values
31 for different strings to be used during pattern matching.
32 This can allow a greater variety in input, for example by removing dashes.
33 """
35 PATTERNS = [
36 Pattern(
37 "RRN (Medium)",
38 r"(?<!\d)\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])(-?)[1-4]\d{6}(?!\d)",
39 0.5,
40 )
41 ]
43 CONTEXT = [
44 "Korean RRN",
45 "Korean Resident Registration Number",
46 "Resident Registration Number",
47 "RRN",
48 "rrn",
49 "rrn#",
50 ]
52 def __init__(
53 self,
54 patterns: Optional[List[Pattern]] = None,
55 context: Optional[List[str]] = None,
56 supported_language: str = "ko",
57 supported_entity: str = "KR_RRN",
58 replacement_pairs: Optional[List[Tuple[str, str]]] = None,
59 name: Optional[str] = None,
60 ):
61 self.replacement_pairs = replacement_pairs if replacement_pairs else [("-", "")]
63 patterns = patterns if patterns else self.PATTERNS
64 context = context if context else self.CONTEXT
65 super().__init__(
66 supported_entity=supported_entity,
67 patterns=patterns,
68 context=context,
69 supported_language=supported_language,
70 name=name,
71 )
73 def validate_result(self, pattern_text: str) -> Optional[bool]:
74 """
75 Validate the pattern logic e.g., by running checksum on a detected pattern.
77 This validation is only for RRNs issued before October 2020.
78 Therefore, it returns None, not False, at the end of the method.
80 :param pattern_text: the text to validated.
81 Only the part in text that was detected by the regex engine
82 :return: A bool or None, indicating whether the validation was successful.
83 """
84 # Pre-processing before validation checks
85 sanitized_value = EntityRecognizer.sanitize_value(
86 pattern_text, self.replacement_pairs
87 )
89 # Check if the sanitized value has the correct length (13 digits)
90 if len(sanitized_value) != 13:
91 return False
93 # Check if all characters are digits
94 if not sanitized_value.isdigit():
95 return False
97 # Validate region code (HI) and checksum (X)
98 region_code = int(sanitized_value[7:9]) # HI
99 if self._validate_region_code(region_code) and self._validate_checksum(
100 sanitized_value
101 ):
102 return True
104 return None
106 def _validate_region_code(self, region_code: int) -> bool:
107 """
108 Validate the region code of Korean RRN.
110 :param region_code: The region code to validate
111 :return: True if region code is valid, False otherwise
112 """
113 return True if 0 <= region_code <= 95 else False
115 def _compute_checksum(self, rn: str) -> int:
116 """
117 Compute the weighted digit sum used for the RRN checksum calculation.
119 The sum is calculated over the first 12 digits of the RRN using
120 the weights [2, 3, 4, 5, 6, 7, 8, 9, 2, 3, 4, 5].
122 :param rn: The resident registration number as a string.
123 Only the first 12 digits are used in the computation.
124 :return: The integer sum of the products of digits and weights.
125 """
126 weights = [2, 3, 4, 5, 6, 7, 8, 9, 2, 3, 4, 5]
127 return sum(int(rn[i]) * weights[i] for i in range(12))
129 def _validate_checksum(self, rrn: str) -> bool:
130 """
131 Validate the checksum of Korean RRN.
133 The checksum is calculated using the preceding 12 digits.
134 X = (11 - (2A+3B+4C+5D+6E+7F+8G+9H+2I+3J+4K+5L) mod 11) mod 10
136 :param rrn: The RRN to validate
137 :return: True if checksum is valid, False otherwise
138 """
139 digit_sum = self._compute_checksum(rrn)
140 checksum = (11 - (digit_sum % 11)) % 10
141 return checksum == int(rrn[12])