Coverage for presidio_analyzer / predefined_recognizers / country_specific / finland / fi_personal_identity_code_recognizer.py: 96%

23 statements  

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

1from datetime import datetime 

2from typing import List, Optional 

3 

4from presidio_analyzer import Pattern, PatternRecognizer 

5 

6 

7class FiPersonalIdentityCodeRecognizer(PatternRecognizer): 

8 """ 

9 Recognizes and validates Finnish Personal Identity Codes (Henkilötunnus). 

10 

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

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

13 :param supported_language: Language this recognizer supports 

14 :param supported_entity: The entity this recognizer can detect 

15 """ 

16 

17 PATTERNS = [ 

18 Pattern( 

19 "Finnish Personal Identity Code (Medium)", 

20 r"\b(\d{6})([+-ABCDEFYXWVU])(\d{3})([0123456789ABCDEFHJKLMNPRSTUVWXY])\b", 

21 0.5, 

22 ), 

23 Pattern( 

24 "Finnish Personal Identity Code (Very Weak)", 

25 r"(\d{6})([+-ABCDEFYXWVU])(\d{3})([0123456789ABCDEFHJKLMNPRSTUVWXY])", 

26 0.1, 

27 ), 

28 ] 

29 CONTEXT = ["hetu", "henkilötunnus", "personbeteckningen", "personal identity code"] 

30 

31 def __init__( 

32 self, 

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

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

35 supported_language: str = "fi", 

36 supported_entity: str = "FI_PERSONAL_IDENTITY_CODE", 

37 name: Optional[str] = None, 

38 ): 

39 patterns = patterns if patterns else self.PATTERNS 

40 context = context if context else self.CONTEXT 

41 super().__init__( 

42 supported_entity=supported_entity, 

43 patterns=patterns, 

44 context=context, 

45 supported_language=supported_language, 

46 name=name, 

47 ) 

48 

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

50 """Validate the pattern by using the control character.""" 

51 

52 # More information on the validation logic from: 

53 # https://dvv.fi/en/personal-identity-code 

54 # Under "How is the control character for a personal identity code calculated?". 

55 if len(pattern_text) != 11: 

56 return False 

57 

58 date_part = pattern_text[0:6] 

59 try: 

60 # Checking if we do not have invalid dates e.g. 310211. 

61 datetime.strptime(date_part, "%d%m%y") 

62 except ValueError: 

63 return False 

64 individual_number = pattern_text[7:10] 

65 control_character = pattern_text[-1] 

66 valid_control_characters = "0123456789ABCDEFHJKLMNPRSTUVWXY" 

67 number_to_check = int(date_part + individual_number) 

68 return valid_control_characters[number_to_check % 31] == control_character