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

1from typing import List, Optional, Tuple 

2 

3from presidio_analyzer import EntityRecognizer, Pattern, PatternRecognizer 

4 

5 

6class KrRrnRecognizer(PatternRecognizer): 

7 """ 

8 Recognize Korean Resident Registration Number (RRN). 

9 

10 The Korean Resident Registration Number (RRN) is 

11 a 13-digit number issued to all Korean residents. 

12 

13 The format is YYMMDD-GHIJKLX where: 

14 - YYMMDD represents the birth date 

15 - G determines gender and century of birth 

16 

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 

20 

21 For RRNs issued after October 2020: 

22 - HIJKLX is a random number 

23 

24 Reference: https://en.wikipedia.org/wiki/Resident_registration_number 

25 

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 """ 

34 

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 ] 

42 

43 CONTEXT = [ 

44 "Korean RRN", 

45 "Korean Resident Registration Number", 

46 "Resident Registration Number", 

47 "RRN", 

48 "rrn", 

49 "rrn#", 

50 ] 

51 

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 [("-", "")] 

62 

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 ) 

72 

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. 

76 

77 This validation is only for RRNs issued before October 2020. 

78 Therefore, it returns None, not False, at the end of the method. 

79 

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 ) 

88 

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

90 if len(sanitized_value) != 13: 

91 return False 

92 

93 # Check if all characters are digits 

94 if not sanitized_value.isdigit(): 

95 return False 

96 

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 

103 

104 return None 

105 

106 def _validate_region_code(self, region_code: int) -> bool: 

107 """ 

108 Validate the region code of Korean RRN. 

109 

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 

114 

115 def _compute_checksum(self, rn: str) -> int: 

116 """ 

117 Compute the weighted digit sum used for the RRN checksum calculation. 

118 

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]. 

121 

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)) 

128 

129 def _validate_checksum(self, rrn: str) -> bool: 

130 """ 

131 Validate the checksum of Korean RRN. 

132 

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 

135 

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])