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

22 statements  

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

1from typing import List, Optional 

2 

3from presidio_analyzer import Pattern, PatternRecognizer 

4 

5 

6class DeLanrRecognizer(PatternRecognizer): 

7 r""" 

8 Recognizes German Lebenslange Arztnummer (LANR). 

9 

10 The LANR is a 9-digit lifetime physician number assigned by the 

11 Kassenärztliche Vereinigung (KV) to every licensed physician participating 

12 in the German statutory health insurance system (Vertragsarzt). It appears 

13 on prescriptions (Rezepte), billing records, discharge letters, and other 

14 statutory healthcare documents. 

15 

16 Legal basis: § 75 Abs. 7 SGB V (Sozialgesetzbuch Fünftes Buch). 

17 Standard: KBV-Richtlinie nach § 75 Abs. 7 SGB V zur Vergabe der Arzt-, 

18 Betriebsstätten-, Praxisnetz- sowie der Netzverbundnummern. 

19 Data protection: DSGVO Art. 9 (Gesundheitsdaten), BDSG § 22. 

20 

21 Format (9 digits): 

22 Pos 1–6: Arztnummer (physician identifier, assigned by KV) 

23 Pos 7: Prüfziffer (check digit, derived from pos 1–6) 

24 Pos 8–9: Arztgruppe / Fachgruppe (specialty / physician group code) 

25 

26 Examples (fictitious): 123456901, 234567601, 100000414 

27 

28 Check digit algorithm (KBV specification): 

29 1. Apply weights [4, 9, 2, 10, 5, 3] to digits at positions 1–6. 

30 2. For each product > 9, replace it with the cross-sum of its digits 

31 (e.g. 18 → 1+8 = 9, 40 → 4+0 = 4). 

32 3. Sum all six values. 

33 4. Check digit (pos 7) = sum mod 10. 

34 

35 Accuracy note: The base pattern ``\\b\\d{9}\\b`` matches any 9-digit token. 

36 Because LANRs share the same surface form as other 9-digit identifiers 

37 (e.g. DE_BSNR), the checksum in ``validate_result()`` is the primary guard 

38 against false positives; context words are required for high-confidence 

39 results without a valid checksum. Formal accuracy evaluation has not been 

40 performed on a labelled dataset. 

41 

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

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

44 :param supported_language: Language this recognizer supports 

45 :param supported_entity: The entity this recognizer can detect 

46 """ 

47 

48 PATTERNS = [ 

49 Pattern( 

50 "Lebenslange Arztnummer LANR (9 digits)", 

51 r"\b\d{9}\b", 

52 0.3, 

53 ), 

54 ] 

55 

56 CONTEXT = [ 

57 "arztnummer", 

58 "lanr", 

59 "lebenslange arztnummer", 

60 "arzt-nr", 

61 "arzt nr", 

62 "arzt-nummer", 

63 "vertragsarzt", 

64 "kassenarzt", 

65 "niedergelassener arzt", 

66 "kbv", 

67 "kassenärztliche vereinigung", 

68 "kv-nummer", 

69 "rezept", 

70 "verschreibung", 

71 "behandelnder arzt", 

72 "hausarzt", 

73 "facharzt", 

74 ] 

75 

76 def __init__( 

77 self, 

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

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

80 supported_language: str = "de", 

81 supported_entity: str = "DE_LANR", 

82 name: Optional[str] = None, 

83 ): 

84 patterns = patterns if patterns else self.PATTERNS 

85 context = context if context else self.CONTEXT 

86 super().__init__( 

87 supported_entity=supported_entity, 

88 patterns=patterns, 

89 context=context, 

90 supported_language=supported_language, 

91 name=name, 

92 ) 

93 

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

95 """ 

96 Validate the LANR using the KBV check digit algorithm. 

97 

98 Algorithm source: KBV-Richtlinie nach § 75 Abs. 7 SGB V. 

99 

100 :param pattern_text: the text to validate (9 digits) 

101 :return: True if check digit is valid, False otherwise 

102 """ 

103 pattern_text = pattern_text.strip() 

104 

105 if len(pattern_text) != 9 or not pattern_text.isdigit(): 

106 return False 

107 

108 weights = [4, 9, 2, 10, 5, 3] 

109 total = 0 

110 for digit_char, weight in zip(pattern_text[:6], weights): 

111 product = int(digit_char) * weight 

112 if product > 9: 

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

114 total += product 

115 

116 expected_check = total % 10 

117 return int(pattern_text[6]) == expected_check