Coverage for presidio_analyzer / predefined_recognizers / country_specific / germany / de_social_security_recognizer.py: 100%

28 statements  

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

1import re 

2from typing import List, Optional 

3 

4from presidio_analyzer import Pattern, PatternRecognizer 

5 

6 

7class DeSocialSecurityRecognizer(PatternRecognizer): 

8 """ 

9 Recognizes German Rentenversicherungsnummer (RVNR / Sozialversicherungsnummer). 

10 

11 The Rentenversicherungsnummer (also called Versicherungsnummer or RVNR) is a 

12 unique 12-character identifier assigned to every person insured under the German 

13 statutory pension insurance scheme (gesetzliche Rentenversicherung). It encodes 

14 date of birth, gender information, and a serial number. 

15 

16 Legal basis: § 147 SGB VI (Sozialgesetzbuch Sechstes Buch – Gesetzliche 

17 Rentenversicherung), § 33a SGB I. 

18 Data protection: DSGVO Art. 4 Nr. 1 (personenbezogene Daten), BDSG. 

19 

20 Format (12 characters): 

21 Pos 1–2: Bereichsnummer (2 digits, issuing regional office code, 01–99) 

22 Pos 3–4: Geburtstag (birth day, 01–31; or 51–81 for women with 

23 Ergänzungsmerkmal) 

24 Pos 5–6: Geburtsmonat (birth month, 01–12) 

25 Pos 7–8: Geburtsjahr (last 2 digits of birth year) 

26 Pos 9: Buchstabenkennung (first letter of birth surname, A–Z) 

27 Pos 10–11: Seriennummer (2-digit ordinal, 01–49 male / 50–99 female as 

28 Ergänzungsmerkmal) 

29 Pos 12: Prüfziffer (check digit) 

30 

31 Example (fictitious): 65070803A012 

32 

33 Check digit algorithm (Deutsche Rentenversicherung): 

34 1. Replace the letter at position 9 with its 2-digit ordinal value 

35 (A=01, B=02, …, Z=26), yielding an effective 13-digit string. 

36 2. Apply weights [2,1,2,1,2,1,2,1,2,1,2,1] to the first 12 effective digits. 

37 3. For each product ≥ 10, replace it with the sum of its digits. 

38 4. Sum all 12 values, compute sum mod 10. 

39 5. The result must equal the check digit at position 12 (pos 13 in effective). 

40 

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

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

43 :param supported_language: Language this recognizer supports 

44 :param supported_entity: The entity this recognizer can detect 

45 """ 

46 

47 PATTERNS = [ 

48 Pattern( 

49 "Rentenversicherungsnummer (Strict, with birth date structure)", 

50 r"\b\d{2}" 

51 r"(0[1-9]|[12]\d|3[01]|5[1-9]|[67]\d|8[01])" # day: 01-31 or 51-81 

52 r"(0[1-9]|1[0-2])" # month 01-12 

53 r"\d{2}" # year 

54 r"[A-Z]" # surname initial 

55 r"\d{2}" # serial 

56 r"[0-9]\b", # check digit 

57 0.5, 

58 ), 

59 Pattern( 

60 "Rentenversicherungsnummer (Relaxed)", 

61 r"\b\d{8}[A-Z]\d{3}\b", 

62 0.3, 

63 ), 

64 ] 

65 

66 CONTEXT = [ 

67 "rentenversicherungsnummer", 

68 "sozialversicherungsnummer", 

69 "versicherungsnummer", 

70 "rvnr", 

71 "svnr", 

72 "sv-nummer", 

73 "rente", 

74 "rentenversicherung", 

75 "deutsche rentenversicherung", 

76 "drv", 

77 "sozialversicherung", 

78 "sozialversicherungsausweis", 

79 "rentenausweis", 

80 ] 

81 

82 def __init__( 

83 self, 

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

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

86 supported_language: str = "de", 

87 supported_entity: str = "DE_SOCIAL_SECURITY", 

88 name: Optional[str] = None, 

89 ): 

90 patterns = patterns if patterns else self.PATTERNS 

91 context = context if context else self.CONTEXT 

92 super().__init__( 

93 supported_entity=supported_entity, 

94 patterns=patterns, 

95 context=context, 

96 supported_language=supported_language, 

97 name=name, 

98 ) 

99 

100 def validate_result(self, pattern_text: str) -> Optional[bool]: 

101 """ 

102 Validate the Rentenversicherungsnummer using the official checksum. 

103 

104 Algorithm source: Deutsche Rentenversicherung Bund, technical specification 

105 for RVNR validation. 

106 

107 :param pattern_text: the text to validate (12 characters) 

108 :return: True if valid, False if invalid 

109 """ 

110 pattern_text = pattern_text.upper().strip() 

111 

112 if len(pattern_text) != 12: 

113 return False 

114 

115 if not re.match(r"^\d{8}[A-Z]\d{3}$", pattern_text): 

116 return False 

117 

118 letter = pattern_text[8] 

119 letter_val = str(ord(letter) - ord("A") + 1).zfill(2) 

120 

121 # Effective 12-digit string: 

122 # positions 1-8 + letter as 2 digits + positions 10-11 

123 effective = pattern_text[:8] + letter_val + pattern_text[9:11] 

124 

125 check_digit = int(pattern_text[11]) 

126 weights = [2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1] 

127 

128 total = 0 

129 for digit_char, weight in zip(effective, weights): 

130 product = int(digit_char) * weight 

131 if product >= 10: 

132 product = (product // 10) + (product % 10) 

133 total += product 

134 

135 return (total % 10) == check_digit