Coverage for presidio_analyzer / predefined_recognizers / country_specific / italy / it_fiscal_code_recognizer.py: 100%
29 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 typing import List, Optional
3from presidio_analyzer import Pattern, PatternRecognizer
6class ItFiscalCodeRecognizer(PatternRecognizer):
7 """
8 Recognizes IT Fiscal Code using regex.
10 :param patterns: List of patterns to be used by this recognizer
11 :param context: List of context words to increase confidence in detection
12 :param supported_language: Language this recognizer supports
13 :param supported_entity: The entity this recognizer can detect
14 """
16 PATTERNS = [
17 Pattern(
18 "Fiscal Code",
19 (
20 r"(?i)((?:[A-Z][AEIOU][AEIOUX]|[AEIOU]X{2}"
21 r"|[B-DF-HJ-NP-TV-Z]{2}[A-Z]){2}"
22 r"(?:[\dLMNP-V]{2}(?:[A-EHLMPR-T](?:[04LQ][1-9MNP-V]|[15MR][\dLMNP-V]"
23 r"|[26NS][0-8LMNP-U])|[DHPS][37PT][0L]|[ACELMRT][37PT][01LM]"
24 r"|[AC-EHLMPR-T][26NS][9V])|(?:[02468LNQSU][048LQU]"
25 r"|[13579MPRTV][26NS])B[26NS][9V])(?:[A-MZ][1-9MNP-V][\dLMNP-V]{2}"
26 r"|[A-M][0L](?:[1-9MNP-V][\dLMNP-V]|[0L][1-9MNP-V]))[A-Z])"
27 ),
28 0.3,
29 ),
30 ]
31 CONTEXT = ["codice fiscale", "cf"]
33 def __init__(
34 self,
35 patterns: Optional[List[Pattern]] = None,
36 context: Optional[List[str]] = None,
37 supported_language: str = "it",
38 supported_entity: str = "IT_FISCAL_CODE",
39 name: Optional[str] = None,
40 ):
41 patterns = patterns if patterns else self.PATTERNS
42 context = context if context else self.CONTEXT
43 super().__init__(
44 supported_entity=supported_entity,
45 patterns=patterns,
46 context=context,
47 supported_language=supported_language,
48 name=name,
49 )
51 def validate_result(self, pattern_text: str) -> Optional[bool]:
52 """
53 Validate the pattern logic e.g., by running checksum on a detected pattern.
55 :param pattern_text: the text to validated.
56 Only the part in text that was detected by the regex engine
57 :return: A bool indicating whether the validation was successful.
58 """
59 pattern_text = pattern_text.upper()
60 control = pattern_text[-1]
61 text_to_validate = pattern_text[:-1]
62 odd_values = text_to_validate[0::2]
63 even_values = text_to_validate[1::2]
65 # Odd values
66 map_odd = {
67 "0": 1,
68 "1": 0,
69 "2": 5,
70 "3": 7,
71 "4": 9,
72 "5": 13,
73 "6": 15,
74 "7": 17,
75 "8": 19,
76 "9": 21,
77 "A": 1,
78 "B": 0,
79 "C": 5,
80 "D": 7,
81 "E": 9,
82 "F": 13,
83 "G": 15,
84 "H": 17,
85 "I": 19,
86 "J": 21,
87 "K": 2,
88 "L": 4,
89 "M": 18,
90 "N": 20,
91 "O": 11,
92 "P": 3,
93 "Q": 6,
94 "R": 8,
95 "S": 12,
96 "T": 14,
97 "U": 16,
98 "V": 10,
99 "W": 22,
100 "X": 25,
101 "Y": 24,
102 "Z": 23,
103 }
105 odd_sum = 0
106 for char in odd_values:
107 odd_sum += map_odd[char]
109 # Even values
110 map_even = {
111 "0": 0,
112 "1": 1,
113 "2": 2,
114 "3": 3,
115 "4": 4,
116 "5": 5,
117 "6": 6,
118 "7": 7,
119 "8": 8,
120 "9": 9,
121 "A": 0,
122 "B": 1,
123 "C": 2,
124 "D": 3,
125 "E": 4,
126 "F": 5,
127 "G": 6,
128 "H": 7,
129 "I": 8,
130 "J": 9,
131 "K": 10,
132 "L": 11,
133 "M": 12,
134 "N": 13,
135 "O": 14,
136 "P": 15,
137 "Q": 16,
138 "R": 17,
139 "S": 18,
140 "T": 19,
141 "U": 20,
142 "V": 21,
143 "W": 22,
144 "X": 23,
145 "Y": 24,
146 "Z": 25,
147 }
149 even_sum = 0
150 for char in even_values:
151 even_sum += map_even[char]
153 # Mod value
154 map_mod = {
155 0: "A",
156 1: "B",
157 2: "C",
158 3: "D",
159 4: "E",
160 5: "F",
161 6: "G",
162 7: "H",
163 8: "I",
164 9: "J",
165 10: "K",
166 11: "L",
167 12: "M",
168 13: "N",
169 14: "O",
170 15: "P",
171 16: "Q",
172 17: "R",
173 18: "S",
174 19: "T",
175 20: "U",
176 21: "V",
177 22: "W",
178 23: "X",
179 24: "Y",
180 25: "Z",
181 }
182 check_value = map_mod[((odd_sum + even_sum) % 26)]
184 if check_value == control:
185 result = True
186 else:
187 result = None
189 return result