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
« prev ^ index » next coverage.py v7.13.1, created at 2026-03-29 09:03 +0000
1import re
2from typing import List, Optional
4from presidio_analyzer import Pattern, PatternRecognizer
7class DeSocialSecurityRecognizer(PatternRecognizer):
8 """
9 Recognizes German Rentenversicherungsnummer (RVNR / Sozialversicherungsnummer).
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.
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.
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)
31 Example (fictitious): 65070803A012
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).
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 """
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 ]
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 ]
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 )
100 def validate_result(self, pattern_text: str) -> Optional[bool]:
101 """
102 Validate the Rentenversicherungsnummer using the official checksum.
104 Algorithm source: Deutsche Rentenversicherung Bund, technical specification
105 for RVNR validation.
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()
112 if len(pattern_text) != 12:
113 return False
115 if not re.match(r"^\d{8}[A-Z]\d{3}$", pattern_text):
116 return False
118 letter = pattern_text[8]
119 letter_val = str(ord(letter) - ord("A") + 1).zfill(2)
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]
125 check_digit = int(pattern_text[11])
126 weights = [2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1]
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
135 return (total % 10) == check_digit