Coverage for presidio_analyzer / predefined_recognizers / country_specific / germany / de_health_insurance_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 DeHealthInsuranceRecognizer(PatternRecognizer): 

8 """ 

9 Recognizes German statutory health insurance numbers (KVNR). 

10 

11 Also called Krankenversicherungsnummer, Krankenversichertennummer, or 

12 Versichertennummer. 

13 

14 The KVNR is assigned to every person insured under the German statutory 

15 health insurance system (gesetzliche Krankenversicherung, GKV). It is printed on the 

16 Gesundheitskarte (eGK – elektronische Gesundheitskarte). 

17 

18 Legal basis: § 290 SGB V (Sozialgesetzbuch Fünftes Buch – Gesetzliche 

19 Krankenversicherung). 

20 Data protection: DSGVO Art. 9 (besondere Kategorien personenbezogener Daten – 

21 Gesundheitsdaten), BDSG § 22. 

22 

23 Format (10 characters): 

24 Pos 1: Buchstabe (first letter of birth surname, A–Z) 

25 Pos 2–9: 8 digits (birth date encoded + serial) 

26 Pos 10: Prüfziffer (check digit, 0–9) 

27 

28 Example (fictitious): A123456787 

29 

30 Check digit algorithm (GKV-Spitzenverband specification): 

31 1. Convert the letter at position 1 to its 2-digit ordinal value 

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

33 2. Apply weights [2, 9, 8, 7, 6, 5, 4, 3, 2, 1] to the first 10 

34 effective digits (before the check digit at effective position 11). 

35 Note: the check digit is the last digit of the original 10-char string 

36 (position 10), which maps to effective position 11. 

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

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

39 5. The result must equal the check digit at position 10. 

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 # Accuracy note: The base pattern `[A-Z]\d{9}` is intentionally broad (any 

48 # uppercase letter followed by 9 digits) because no more specific structural 

49 # constraint exists in the KVNR format beyond length and the leading letter. 

50 # The GKV checksum validation in validate_result() is the primary defence 

51 # against false positives; the base confidence is therefore kept low (0.3) 

52 # and context words are required for high-confidence matches. 

53 # Formal accuracy evaluation has not been performed on a labelled dataset. 

54 PATTERNS = [ 

55 Pattern( 

56 "Krankenversicherungsnummer KVNR (letter + 9 digits)", 

57 r"\b[A-Z]\d{9}\b", 

58 0.3, 

59 ), 

60 ] 

61 

62 CONTEXT = [ 

63 "krankenversicherungsnummer", 

64 "krankenversichertennummer", 

65 "versichertennummer", 

66 "kvnr", 

67 "krankenkasse", 

68 "krankenversicherung", 

69 "gesundheitskarte", 

70 "egk", 

71 "elektronische gesundheitskarte", 

72 "gkv", 

73 "gesetzliche krankenversicherung", 

74 "krankenversicherungsausweis", 

75 "versichertenausweis", 

76 "versichertenkarte", 

77 "aok", 

78 "tkk", 

79 "barmer", 

80 "dak", 

81 ] 

82 

83 def __init__( 

84 self, 

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

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

87 supported_language: str = "de", 

88 supported_entity: str = "DE_HEALTH_INSURANCE", 

89 name: Optional[str] = None, 

90 ): 

91 patterns = patterns if patterns else self.PATTERNS 

92 context = context if context else self.CONTEXT 

93 super().__init__( 

94 supported_entity=supported_entity, 

95 patterns=patterns, 

96 context=context, 

97 supported_language=supported_language, 

98 name=name, 

99 ) 

100 

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

102 """ 

103 Validate the KVNR using the GKV-Spitzenverband checksum algorithm. 

104 

105 Algorithm source: GKV-Spitzenverband technical specification (§ 290 SGB V). 

106 

107 :param pattern_text: the text to validate (10 characters: 1 letter + 9 digits) 

108 :return: True if valid, False if invalid 

109 """ 

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

111 

112 if len(pattern_text) != 10: 

113 return False 

114 

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

116 return False 

117 

118 letter = pattern_text[0] 

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

120 

121 # Effective 11-digit string: 2 (from letter) + 8 data digits + 1 check digit 

122 # We apply weights to the first 10 effective positions (before check digit) 

123 effective = letter_val + pattern_text[1:9] # 2 + 8 = 10 digits 

124 

125 check_digit = int(pattern_text[9]) 

126 weights = [2, 9, 8, 7, 6, 5, 4, 3, 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