solidprivacy commited on
Commit
babc312
·
verified ·
1 Parent(s): 5beca75

Add Dutch EU recognizers

Browse files
Files changed (1) hide show
  1. dutch_recognizers.py +171 -0
dutch_recognizers.py ADDED
@@ -0,0 +1,171 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Dutch / European recognizers for the SolidPrivacy Scrub Presidio app.
2
+
3
+ These recognizers are intentionally pattern-based and conservative. They are
4
+ registered under language='en' because the current demo analyses with
5
+ language='en' and uses English NER models. The entity names are Dutch/EU
6
+ specific, but the recognizers can run without requiring a Dutch NLP model.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import re
12
+ from typing import Iterable, List
13
+
14
+ from presidio_analyzer import Pattern, PatternRecognizer
15
+
16
+
17
+ DUTCH_ENTITY_NAMES = [
18
+ "NL_BSN",
19
+ "NL_POSTCODE",
20
+ "NL_IBAN",
21
+ "NL_KVK_NUMBER",
22
+ "NL_VAT_NUMBER",
23
+ "NL_PHONE_NUMBER",
24
+ "NL_LICENSE_PLATE",
25
+ "NL_DRIVER_LICENSE",
26
+ "NL_BIG_NUMBER",
27
+ ]
28
+
29
+
30
+ class DutchBSNRecognizer(PatternRecognizer):
31
+ """Recognize Dutch BSN candidates using pattern + 11-test validation."""
32
+
33
+ PATTERNS = [
34
+ Pattern(
35
+ name="nl_bsn_candidate_8_or_9_digits",
36
+ regex=r"(?<!\d)(?:\d[\s\-.]?){8,9}(?!\d)",
37
+ score=0.35,
38
+ )
39
+ ]
40
+
41
+ CONTEXT = [
42
+ "bsn",
43
+ "burgerservicenummer",
44
+ "sofinummer",
45
+ "persoonsnummer",
46
+ "burger service nummer",
47
+ ]
48
+
49
+ def __init__(self, supported_language: str = "en"):
50
+ super().__init__(
51
+ supported_entity="NL_BSN",
52
+ patterns=self.PATTERNS,
53
+ context=self.CONTEXT,
54
+ supported_language=supported_language,
55
+ )
56
+
57
+ @staticmethod
58
+ def _digits(value: str) -> str:
59
+ return re.sub(r"\D", "", value or "")
60
+
61
+ @classmethod
62
+ def is_valid_bsn(cls, value: str) -> bool:
63
+ digits = cls._digits(value)
64
+
65
+ if len(digits) == 8:
66
+ # BSNs can effectively be represented with a leading zero.
67
+ digits = "0" + digits
68
+
69
+ if len(digits) != 9:
70
+ return False
71
+
72
+ # Avoid obvious false positives such as 000000000 or 111111111.
73
+ if len(set(digits)) == 1:
74
+ return False
75
+
76
+ numbers = [int(d) for d in digits]
77
+ checksum = sum(numbers[i] * (9 - i) for i in range(8)) - numbers[8]
78
+ return checksum % 11 == 0
79
+
80
+ def validate_result(self, pattern_text: str) -> bool:
81
+ return self.is_valid_bsn(pattern_text)
82
+
83
+
84
+ def _pattern_recognizer(
85
+ entity: str,
86
+ regex: str,
87
+ score: float,
88
+ context: Iterable[str] | None = None,
89
+ supported_language: str = "en",
90
+ ) -> PatternRecognizer:
91
+ return PatternRecognizer(
92
+ supported_entity=entity,
93
+ patterns=[Pattern(name=entity.lower(), regex=regex, score=score)],
94
+ context=list(context or []),
95
+ supported_language=supported_language,
96
+ )
97
+
98
+
99
+ def get_dutch_entity_names() -> List[str]:
100
+ return list(DUTCH_ENTITY_NAMES)
101
+
102
+
103
+ def get_dutch_recognizers(supported_language: str = "en") -> List[PatternRecognizer]:
104
+ """Return Dutch/EU recognizers for the current app.
105
+
106
+ Keep these recognizers conservative enough for legal/business documents:
107
+ high-confidence formats use higher scores; generic numeric identifiers use
108
+ context words and lower scores to reduce false positives.
109
+ """
110
+
111
+ return [
112
+ DutchBSNRecognizer(supported_language=supported_language),
113
+ _pattern_recognizer(
114
+ entity="NL_POSTCODE",
115
+ regex=r"\b[1-9][0-9]{3}\s?[A-Z]{2}\b",
116
+ score=0.80,
117
+ context=["postcode", "adres", "woonplaats", "plaats"],
118
+ supported_language=supported_language,
119
+ ),
120
+ _pattern_recognizer(
121
+ entity="NL_IBAN",
122
+ regex=r"\bNL\s?\d{2}\s?[A-Z]{4}\s?(?:\d\s?){10}\b",
123
+ score=0.90,
124
+ context=["iban", "rekening", "rekeningnummer", "bankrekening"],
125
+ supported_language=supported_language,
126
+ ),
127
+ _pattern_recognizer(
128
+ entity="NL_VAT_NUMBER",
129
+ regex=r"\bNL\s?\d{9}\s?B\s?\d{2}\b",
130
+ score=0.90,
131
+ context=["btw", "btw-nummer", "vat", "vat number", "omzetbelasting"],
132
+ supported_language=supported_language,
133
+ ),
134
+ _pattern_recognizer(
135
+ entity="NL_KVK_NUMBER",
136
+ regex=r"\b\d{8}\b",
137
+ score=0.35,
138
+ context=["kvk", "kamer van koophandel", "handelsregister", "kvk-nummer"],
139
+ supported_language=supported_language,
140
+ ),
141
+ _pattern_recognizer(
142
+ entity="NL_PHONE_NUMBER",
143
+ regex=r"(?<!\w)(?:\+31|0031|0)(?:[\s\-\.]?\d){8,10}(?!\w)",
144
+ score=0.65,
145
+ context=["tel", "telefoon", "mobiel", "phone", "nummer"],
146
+ supported_language=supported_language,
147
+ ),
148
+ _pattern_recognizer(
149
+ entity="NL_LICENSE_PLATE",
150
+ # Conservative Dutch-style license plate pattern, requiring separators.
151
+ regex=r"\b(?:[A-Z]{2}-\d{2}-\d{2}|\d{2}-\d{2}-[A-Z]{2}|\d{2}-[A-Z]{2}-\d{2}|[A-Z]{2}-\d{2}-[A-Z]{2}|[A-Z]{2}-[A-Z]{2}-\d{2}|\d{2}-[A-Z]{2}-[A-Z]{2})\b",
152
+ score=0.70,
153
+ context=["kenteken", "license plate", "voertuig", "auto"],
154
+ supported_language=supported_language,
155
+ ),
156
+ _pattern_recognizer(
157
+ entity="NL_DRIVER_LICENSE",
158
+ # Kept context-sensitive because document number formats can vary.
159
+ regex=r"\b\d{10}\b",
160
+ score=0.30,
161
+ context=["rijbewijs", "rijbewijsnummer", "driver license", "driving licence"],
162
+ supported_language=supported_language,
163
+ ),
164
+ _pattern_recognizer(
165
+ entity="NL_BIG_NUMBER",
166
+ regex=r"\b\d{11}\b",
167
+ score=0.35,
168
+ context=["big", "big-register", "big nummer", "big-nummer", "zorgverlener"],
169
+ supported_language=supported_language,
170
+ ),
171
+ ]