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
« 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
4from presidio_analyzer import Pattern, PatternRecognizer
7class FiPersonalIdentityCodeRecognizer(PatternRecognizer):
8 """
9 Recognizes and validates Finnish Personal Identity Codes (Henkilötunnus).
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 """
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"]
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 )
49 def validate_result(self, pattern_text: str) -> Optional[bool]:
50 """Validate the pattern by using the control character."""
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
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