Coverage for presidio_analyzer / predefined_recognizers / country_specific / us / us_ssn_recognizer.py: 100%
26 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 collections import defaultdict
2from typing import List, Optional
4from presidio_analyzer import Pattern, PatternRecognizer
7class UsSsnRecognizer(PatternRecognizer):
8 """Recognize US Social Security Number (SSN) 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("SSN1 (very weak)", r"\b([0-9]{5})-([0-9]{4})\b", 0.05),
18 Pattern("SSN2 (very weak)", r"\b([0-9]{3})-([0-9]{6})\b", 0.05),
19 Pattern("SSN3 (very weak)", r"\b(([0-9]{3})-([0-9]{2})-([0-9]{4}))\b", 0.05),
20 Pattern("SSN4 (very weak)", r"\b[0-9]{9}\b", 0.05),
21 Pattern("SSN5 (medium)", r"\b([0-9]{3})[- .]([0-9]{2})[- .]([0-9]{4})\b", 0.5),
22 ]
24 CONTEXT = [
25 "social",
26 "security",
27 # "sec", # Task #603: Support keyphrases ("social sec")
28 "ssn",
29 "ssns",
30 # "ssn#", # iss:1452 - a # does not work with LemmaContextAwareEnhancer
31 # "ss#", # iss:1452 - a # does not work with LemmaContextAwareEnhancer
32 "ssid",
33 ]
35 def __init__(
36 self,
37 patterns: Optional[List[Pattern]] = None,
38 context: Optional[List[str]] = None,
39 supported_language: str = "en",
40 supported_entity: str = "US_SSN",
41 name: Optional[str] = None,
42 ):
43 patterns = patterns if patterns else self.PATTERNS
44 context = context if context else self.CONTEXT
45 super().__init__(
46 supported_entity=supported_entity,
47 patterns=patterns,
48 context=context,
49 supported_language=supported_language,
50 name=name,
51 )
53 def invalidate_result(self, pattern_text: str) -> bool:
54 """
55 Check if the pattern text cannot be validated as a US_SSN entity.
57 :param pattern_text: Text detected as pattern by regex
58 :return: True if invalidated
59 """
60 # if there are delimiters, make sure both delimiters are the same
61 delimiter_counts = defaultdict(int)
62 for c in pattern_text:
63 if c in (".", "-", " "):
64 delimiter_counts[c] += 1
65 if len(delimiter_counts.keys()) > 1:
66 # mismatched delimiters
67 return True
69 only_digits = "".join(c for c in pattern_text if c.isdigit())
70 if all(only_digits[0] == c for c in only_digits):
71 # cannot be all same digit
72 return True
74 if only_digits[3:5] == "00" or only_digits[5:] == "0000":
75 # groups cannot be all zeros
76 return True
78 for sample_ssn in ("000", "666", "123456789", "98765432", "078051120"):
79 if only_digits.startswith(sample_ssn):
80 return True
82 return False