euredact 0.5.1 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/NOTICE +11 -0
  2. package/README.md +658 -45
  3. package/dist/cjs/cache.js +2 -8
  4. package/dist/cjs/cloud/account.js +180 -0
  5. package/dist/cjs/cloud/batches.js +469 -0
  6. package/dist/cjs/cloud/client.js +131 -18
  7. package/dist/cjs/cloud/config.js +28 -2
  8. package/dist/cjs/cloud/errors.js +39 -2
  9. package/dist/cjs/cloud/index.js +16 -1
  10. package/dist/cjs/index.js +9 -1
  11. package/dist/cjs/platform.browser.js +98 -0
  12. package/dist/cjs/platform.js +140 -0
  13. package/dist/cjs/rules/bicGleif.js +57 -0
  14. package/dist/cjs/rules/bicRegistry.js +37 -11
  15. package/dist/cjs/rules/countries/index.js +138 -48
  16. package/dist/cjs/rules/cues.js +29 -9
  17. package/dist/cjs/rules/engine.js +14 -6
  18. package/dist/cjs/rules/suppressors.js +418 -23
  19. package/dist/cjs/rules/validators.js +103 -0
  20. package/dist/cjs/sdk.js +150 -44
  21. package/dist/cjs/types.js +4 -1
  22. package/dist/esm/cache.js +2 -8
  23. package/dist/esm/cloud/account.d.ts +155 -0
  24. package/dist/esm/cloud/account.js +175 -0
  25. package/dist/esm/cloud/batches.d.ts +166 -0
  26. package/dist/esm/cloud/batches.js +464 -0
  27. package/dist/esm/cloud/client.d.ts +90 -5
  28. package/dist/esm/cloud/client.js +126 -26
  29. package/dist/esm/cloud/config.d.ts +6 -0
  30. package/dist/esm/cloud/config.js +27 -2
  31. package/dist/esm/cloud/errors.d.ts +26 -1
  32. package/dist/esm/cloud/errors.js +35 -1
  33. package/dist/esm/cloud/index.d.ts +4 -2
  34. package/dist/esm/cloud/index.js +4 -2
  35. package/dist/esm/index.d.ts +2 -2
  36. package/dist/esm/index.js +1 -1
  37. package/dist/esm/platform.browser.d.ts +27 -0
  38. package/dist/esm/platform.browser.js +92 -0
  39. package/dist/esm/platform.d.ts +20 -0
  40. package/dist/esm/platform.js +101 -0
  41. package/dist/esm/rules/bicGleif.d.ts +4 -0
  42. package/dist/esm/rules/bicGleif.js +53 -0
  43. package/dist/esm/rules/bicRegistry.d.ts +25 -11
  44. package/dist/esm/rules/bicRegistry.js +36 -11
  45. package/dist/esm/rules/countries/index.js +138 -48
  46. package/dist/esm/rules/cues.d.ts +1 -1
  47. package/dist/esm/rules/cues.js +29 -9
  48. package/dist/esm/rules/engine.js +14 -6
  49. package/dist/esm/rules/suppressors.d.ts +1 -0
  50. package/dist/esm/rules/suppressors.js +417 -22
  51. package/dist/esm/rules/validators.d.ts +14 -0
  52. package/dist/esm/rules/validators.js +100 -0
  53. package/dist/esm/sdk.d.ts +63 -19
  54. package/dist/esm/sdk.js +148 -44
  55. package/dist/esm/types.d.ts +58 -2
  56. package/dist/esm/types.js +4 -1
  57. package/package.json +12 -4
@@ -1,5 +1,14 @@
1
1
  import { EntityType } from "../../types.js";
2
2
  import { IBAN_LENGTHS } from "../validators.js";
3
+ import { MONTH_NAMES } from "../suppressors.js";
4
+ /** Day, month as a word, year ("4. März 1948"), behind a date label (rules-engine#115). */
5
+ function writtenDatePattern() {
6
+ const names = new Set(MONTH_NAMES.split("|").filter(Boolean));
7
+ for (const m of [...names])
8
+ names.add(m.charAt(0).toUpperCase() + m.slice(1));
9
+ const months = [...names].sort((a, b) => b.length - a.length || (a < b ? -1 : a > b ? 1 : 0)).join("|");
10
+ return String.raw `\b(?:0?[1-9]|[12][0-9]|3[01])\.?\s+(?:` + months + String.raw `)\.?\s+(?:19|20)\d{2}\b`;
11
+ }
3
12
  /**
4
13
  * Build a country-independent IBAN pattern from the ISO 13616 length table.
5
14
  *
@@ -102,23 +111,56 @@ const SECRET_CONTEXT = [
102
111
  // ---------------------------------------------------------------------------
103
112
  // DOB context keywords (reused across SHARED patterns)
104
113
  // ---------------------------------------------------------------------------
114
+ // Every way the documents this engine sees introduce a date of birth.
115
+ //
116
+ // DOB recall was 62.8% -- 33,441 unmasked birth dates -- because this covered
117
+ // seven languages of thirty-one. Eleven countries scored exactly 100% (the
118
+ // listed languages) while twenty sat at 42-51%, which is what proved it was
119
+ // coverage rather than capability (rules-engine#38).
120
+ //
121
+ // Matching is substring, not word-boundary, so every entry was screened against
122
+ // the 29.2-million-character corpus for occurrences inside a longer word. Two
123
+ // were rejected: `fædd` lives inside `fæddur`, and `born` was found inside
124
+ // `gabornagy` and `gabornemeth` -- Hungarian names as e-mail local parts -- so
125
+ // `"born "` with the trailing space is listed instead.
126
+ //
127
+ // DOB_CONTEXT is gone: it was a second, shorter copy missing `nato il`,
128
+ // `nascido`, `geburtstag` and more, so ISO dates were gated on fewer keywords
129
+ // than DD/MM/YYYY ones for no stated reason.
105
130
  const DOB_CONTEXT = [
131
+ // Germanic / Romance -- the original set, kept verbatim.
106
132
  "geboren", "geboortedatum", "date de naissance", "né le", "née le",
107
133
  "né(e) le", "nee le", "nee(e) le", "date of birth", "DOB",
108
134
  "Geburtsdatum", "geboren am", "geboren op", "nascido", "nacido",
109
135
  "data di nascita", "nato il", "nata il", "geb.", "geb.datum", "geb ",
110
136
  "birth date", "birthday", "naissance", "geboorte", "geburtstag",
111
- ];
112
- const DOB_ISO_CONTEXT = [
113
- "geboren", "geboortedatum", "date de naissance", "né le", "née le",
114
- "né(e) le", "nee le", "date of birth", "DOB", "Geburtsdatum",
115
- "geboren am", "geboren op", "geb.", "birth date", "birthday",
116
- "naissance", "geboorte",
137
+ // English and Italian forms the list was missing.
138
+ "born ", "d.o.b", "nato/a il",
139
+ // Nordic.
140
+ "født", "fødselsdato", "född", "födelsedatum",
141
+ "syntynyt", "syntymäaika", "fæddur", "fæðingardagur",
142
+ // Greek -- Greece and Cyprus.
143
+ "γεννηθείς", "γεννήθηκε", "ημερομηνία γέννησης",
144
+ // West Slavic.
145
+ "urodzony", "urodzona", "data urodzenia",
146
+ "narozen", "datum narození", "narodený", "dátum narodenia",
147
+ // Hungarian, Romanian, Bulgarian.
148
+ "született", "születési", "născut", "născută", "data nașterii",
149
+ "роден", "родена", "дата на раждане",
150
+ // South Slavic.
151
+ "rođen", "rođena", "datum rođenja", "rojen", "rojena", "datum rojstva",
152
+ // Baltic.
153
+ "sündinud", "sünniaeg", "dzimis", "dzimusi", "dzimšanas datums",
154
+ "gimęs", "gimusi", "gimimo data",
155
+ // Maltese, Irish, Turkish (Cyprus).
156
+ "twieled", "twieldet", "data tat-twelid",
157
+ "rugadh", "dáta breithe", "doğum tarihi",
117
158
  ];
118
159
  const DATE_OF_DEATH_CONTEXT = [
119
160
  "overleden", "overlijdensdatum", "date de décès", "décédé le",
120
161
  "date of death", "Sterbedatum", "verstorben am", "gestorven",
121
162
  "death date", "died on", "mort le", "décès",
163
+ "datum van overlijden", "décédée le", "Sterbetag",
122
164
  ];
123
165
  // ---------------------------------------------------------------------------
124
166
  // Country configurations
@@ -173,6 +215,9 @@ const SHARED = {
173
215
  p(EntityType.BIC, String.raw `\b[A-Z]{4} [A-Z]{2} [A-Z0-9]{2}(?: [A-Z0-9]{3})?\b`, "bic", "BIC/SWIFT code (space-separated groups)"),
174
216
  p(EntityType.CREDIT_CARD, String.raw `\b(?:4[0-9]{3}[\s\-]?[0-9]{4}[\s\-]?[0-9]{4}[\s\-]?[0-9]{4}|5[1-5][0-9]{2}[\s\-]?[0-9]{4}[\s\-]?[0-9]{4}[\s\-]?[0-9]{4}|3[47][0-9]{2}[\s\-]?[0-9]{6}[\s\-]?[0-9]{5})\b`, "luhn"),
175
217
  p(EntityType.VIN, String.raw `\b[A-HJ-NPR-Z0-9]{17}\b`, "vin"),
218
+ // Behind its label: any case, or in its three sections 3-6-8 (rules-engine#110).
219
+ p(EntityType.VIN, String.raw `\b[A-HJ-NPR-Za-hj-npr-z0-9]{13}\d{4}\b`, "vin", "VIN — any case, behind its label", ["VIN", "FIN:", "FIN-Nr", "FIN-Nummer", "Fahrgestellnummer", "Fahrzeug-Identifizierungsnummer", "Fahrzeugidentifizierungsnummer", "châssis", "chassis", "chassisnummer", "chasisnummer", "numéro de série", "vehicle identification", "numero di telaio", "telaio", "bastidor", "número de bastidor", "VIN-code", "VIN-kod"], true),
220
+ p(EntityType.VIN, String.raw `\b[A-HJ-NPR-Za-hj-npr-z0-9]{3}[\s\-][A-HJ-NPR-Za-hj-npr-z0-9]{6}[\s\-][A-HJ-NPR-Za-hj-npr-z0-9]{4}\d{4}\b`, "vin", "VIN — in sections, behind its label", ["VIN", "FIN:", "FIN-Nr", "FIN-Nummer", "Fahrgestellnummer", "Fahrzeug-Identifizierungsnummer", "Fahrzeugidentifizierungsnummer", "châssis", "chassis", "chassisnummer", "chasisnummer", "numéro de série", "vehicle identification", "numero di telaio", "telaio", "bastidor", "número de bastidor", "VIN-code", "VIN-kod"], true),
176
221
  p(EntityType.IP_ADDRESS, String.raw `\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b`),
177
222
  // Branch order is load-bearing: alternation is leftmost-first, not
178
223
  // longest-match, so the form that consumes most is tried first, and the
@@ -187,6 +232,8 @@ const SHARED = {
187
232
  p(EntityType.MAC_ADDRESS, String.raw `\b[0-9a-fA-F]{4}\.[0-9a-fA-F]{4}\.[0-9a-fA-F]{4}\b`, null, "Cisco"),
188
233
  p(EntityType.IMEI, String.raw `\b\d{15}\b`, "imei"),
189
234
  p(EntityType.IMEI, String.raw `\b\d{2}[\-\s]\d{6}[\-\s]\d{6}[\-\s]\d\b`, "imei", "formatted"),
235
+ // Behind "IMEI": 14 digits, the 16-digit IMEISV, or 15 with a failed check (rules-engine#110).
236
+ p(EntityType.IMEI, String.raw `\b\d{14,16}\b`, null, "IMEI / IMEISV — behind its label", ["IMEI"], true),
190
237
  p(EntityType.GPS_COORDINATES, String.raw `-?(?:[1-8]?\d(?:\.\d{4,})|90(?:\.0{4,}))\s*[,;/]\s*-?(?:1[0-7]\d(?:\.\d{4,})|180(?:\.0{4,})|\d{1,2}(?:\.\d{4,}))`),
191
238
  p(EntityType.UUID, String.raw `\b[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}\b`),
192
239
  p(EntityType.SOCIAL_HANDLE, String.raw `(?<![\p{L}\p{N}_])@\p{L}[\p{L}\p{N}_.]{1,29}\b`),
@@ -220,7 +267,12 @@ const SHARED = {
220
267
  // --- Secret / API Key (connection strings with embedded credentials) ---
221
268
  p(EntityType.SECRET, String.raw `(?:mongodb|mysql|postgres(?:ql)?|redis|amqp|rabbitmq):\/\/[^\s:]{1,256}:[^\s@]{1,256}@[^\s]+`, null, "Connection string with credentials"),
222
269
  // --- Secret / API Key (assignment-based: KEY=value or KEY: value) ---
223
- p(EntityType.SECRET, String.raw `(?<=[:=] )\S{8,}|(?<=[:=])\S{8,}`, "high_entropy", "Assigned secret value", SECRET_CONTEXT, true),
270
+ // The final character may not be sentence punctuation. `\S` does not stop
271
+ // at a full stop, so "Reisepass: CA1234567." was claimed as the
272
+ // ten-character span "CA1234567." -- a passport number plus the sentence's
273
+ // period, masked as a credential because a "credentials" word sat within
274
+ // the context window (rules-engine#35).
275
+ p(EntityType.SECRET, String.raw `(?<=[:=] )\S{7,}[^\s.,;:!?)'"]|(?<=[:=])\S{7,}[^\s.,;:!?)'"]`, "high_entropy", "Assigned secret value", SECRET_CONTEXT, true),
224
276
  // --- Secret / API Key (entropy-based fallback for longer tokens) ---
225
277
  // Only the *trailing* \b is replaced. It could not match when the token run
226
278
  // ended on "-", "+" or "/", so the engine backtracked across two
@@ -238,19 +290,24 @@ const SHARED = {
238
290
  p(EntityType.SECRET, String.raw `\b[A-Za-z0-9_\-+/]{24,}[A-Za-z0-9_\-+/=]*(?![A-Za-z0-9_\-+/=])`, "high_entropy", "High-entropy token", SECRET_CONTEXT, true),
239
291
  p(EntityType.DOB, String.raw `\b(?:0[1-9]|[12][0-9]|3[01])[/.\-](?:0[1-9]|1[0-2])[/.\-](?:19|20)\d{2}\b`, null, "DD/MM/YYYY", DOB_CONTEXT, true),
240
292
  p(EntityType.DATE_OF_DEATH, String.raw `\b(?:0[1-9]|[12][0-9]|3[01])[/.\-](?:0[1-9]|1[0-2])[/.\-](?:19|20)\d{2}\b`, null, "", DATE_OF_DEATH_CONTEXT, true),
241
- p(EntityType.DOB, String.raw `\b(?:19|20)\d{2}[/.\-](?:0[1-9]|1[0-2])[/.\-](?:0[1-9]|[12][0-9]|3[01])\b`, null, "ISO YYYY-MM-DD", DOB_ISO_CONTEXT, true),
293
+ // Written-month dates, behind the same labels (rules-engine#115).
294
+ p(EntityType.DOB, writtenDatePattern(), null, "Date with a written month", DOB_CONTEXT, true),
295
+ p(EntityType.DATE_OF_DEATH, writtenDatePattern(), null, "Date of death with a written month", DATE_OF_DEATH_CONTEXT, true),
296
+ p(EntityType.DOB, String.raw `\b(?:19|20)\d{2}[/.\-](?:0[1-9]|1[0-2])[/.\-](?:0[1-9]|[12][0-9]|3[01])\b`, null, "ISO YYYY-MM-DD", DOB_CONTEXT, true),
242
297
  ],
243
298
  };
244
299
  const NL = {
245
300
  code: "NL",
246
301
  name: "Netherlands",
247
302
  patterns: [
303
+ // Healthcare-provider numbers, behind their label: no shape of their own, so without a pattern nothing claimed them, or a phone or a foreign checksum did (canon HEALTHCARE_PROVIDER; rules-engine#111).
304
+ p(EntityType.HEALTHCARE_PROVIDER, String.raw `\b\d{8,11}\b`, null, "Dutch BIG-registratienummer — 8-11 digits", ["BIG-nummer", "BIG nummer", "BIG-registratie", "BIG registratie", "BIG-nr", "BIG nr", "BIG:"], true),
248
305
  p(EntityType.NATIONAL_ID, String.raw `\b[0-9]{9}\b`, "bsn"),
249
306
  p(EntityType.NATIONAL_ID, String.raw `\b[0-9]{2,4}\.[0-9]{2,4}\.[0-9]{2,4}\b`, "bsn"),
250
307
  p(EntityType.IBAN, String.raw `\bNL\d{2}\s?[A-Z]{4}\s?\d{4}\s?\d{4}\s?\d{2}\b`, "iban"),
251
- p(EntityType.VAT, String.raw `\bNL\d{9}B\d{2}\b`, "vat_nl"),
308
+ p(EntityType.VAT, String.raw `\bNL[\s.]?\d{9}B\d{2}\b`, "vat_nl"),
252
309
  p(EntityType.CHAMBER_OF_COMMERCE, String.raw `\b\d{8}\b`, "kvk", "", ["KvK", "Kamer van Koophandel", "kvk-nummer", "KVK-nummer", "handelsregister", "chamber of commerce"], true),
253
- p(EntityType.PHONE, String.raw `\b0[1-9]\d[\s\-]?\d{3}[\s\-]?\d{4}\b`),
310
+ p(EntityType.PHONE, String.raw `\b0[1-9]\d(?:\s-\s|[\s\-])?\d{3}[\s\-]?\d{4}\b`),
254
311
  p(EntityType.PHONE, String.raw `\b0[1-9]\d{2}[\s\-]?\d{6,7}\b`),
255
312
  p(EntityType.PHONE, String.raw `\b06[\s\-]?\d{4}[\s\-]?\d{4}\b`),
256
313
  p(EntityType.PHONE, String.raw `\b06\d{8}\b`),
@@ -275,7 +332,7 @@ const BE = {
275
332
  p(EntityType.PASSPORT, String.raw `\b[A-Z]{2}\d{6}\b`, null, "", PASSPORT_CONTEXT, true),
276
333
  p(EntityType.DRIVERS_LICENSE, String.raw `\b\d{10}\b`, null, "", ["rijbewijs", "permis de conduire", "driving licence", "driving license", "rijbewijsnummer"], true),
277
334
  p(EntityType.LICENSE_PLATE, String.raw `\b[12][\-\s]?[A-Z]{3}[\-\s]?\d{3}\b`),
278
- p(EntityType.POSTAL_CODE, String.raw `\b[1-9]\d{3}\b`, null, "", ["postcode", "code postal", "postnummer", "postal code", "zip", "B-", "adres", "adresse", "wonende", "woonplaats", "rue", "straat", "laan", "avenue", "boulevard", "plein", "steenweg", "chaussée", "domicilié", "gedomicilieerd", "Levering:", "siège"], true),
335
+ p(EntityType.POSTAL_CODE, String.raw `\b[1-9]\d{3}\b`, null, "", ["postcode", "code postal", "postnummer", "postal code", "zip", "B-", "adres", "adresse", "wonende", "woonplaats", "domicile", "rue", "straat", "laan", "avenue", "boulevard", "plein", "steenweg", "chaussée", "domicilié", "gedomicilieerd", "Levering:", "siège"], true),
279
336
  p(EntityType.POSTAL_CODE, String.raw `(?<=, )[1-9]\d{3}(?= [A-Z])`),
280
337
  p(EntityType.HEALTHCARE_PROVIDER, String.raw `\b\d{1}[\-.]?\d{5}[\-.]?\d{2}[\-.]?\d{3}\b`, null, "", ["RIZIV", "INAMI", "arts", "médecin", "zorgverlener", "prestataire", "dokter", "doctor"], true),
281
338
  ],
@@ -284,13 +341,17 @@ const DE = {
284
341
  code: "DE",
285
342
  name: "Germany",
286
343
  patterns: [
344
+ // Healthcare-provider numbers, behind their label: no shape of their own, so without a pattern nothing claimed them, or a phone or a foreign checksum did (canon HEALTHCARE_PROVIDER; rules-engine#111).
345
+ p(EntityType.HEALTHCARE_PROVIDER, String.raw `\b\d{9}\b`, null, "German LANR — 9 digits", ["LANR", "Lebenslange Arztnummer", "Arztnummer"], true),
346
+ p(EntityType.HEALTHCARE_PROVIDER, String.raw `\b\d{9}\b`, null, "German BSNR — 9 digits", ["BSNR", "Betriebsstättennummer", "Betriebsstätten-Nr"], true),
287
347
  p(EntityType.TAX_ID, String.raw `\b[1-9]\d{10}\b`, "german_tax_id"),
288
348
  p(EntityType.TAX_ID, String.raw `\b[1-9]\d[\s.]?\d{3}[\s.]?\d{3}[\s.]?\d{3}\b`, "german_tax_id"),
289
349
  p(EntityType.NATIONAL_ID, String.raw `\b[CFGHJKLMNPRTVWXYZ][0-9CFGHJKLMNPRTVWXYZ]{8}\d?\b`, null, "", ["Personalausweis", "Personalausweisnummer", "Ausweis", "Ausweisnummer", "Ausweis-Nr", "identity card", "Identitätskarte", "Perso", "PA-Nummer"], true),
290
350
  p(EntityType.IBAN, String.raw `\bDE\d{2}\s?\d{4}\s?\d{4}\s?\d{4}\s?\d{4}\s?\d{2}\b`, "iban"),
291
351
  p(EntityType.VAT, String.raw `\bDE\s?\d{9}\b`, "vat_de"),
292
- p(EntityType.PHONE, String.raw `\b0[1-9]\d{1,4}[\s/\-]?\d{3,8}\b`),
293
- p(EntityType.PHONE, String.raw `\+49\s?\d{2,5}[\s/\-]?\d{3,8}\b`),
352
+ // Prefix may be set off by " / "; up to three trailing two-digit groups (rules-engine#51).
353
+ p(EntityType.PHONE, String.raw `\b0[1-9]\d{1,4}(?:\s?[/\-]\s?|\s)?\d{3,8}\b(?:[ \-]\d{2}\b(?![.,/:\-]\d)){0,3}`),
354
+ p(EntityType.PHONE, String.raw `\+49\s?\d{2,5}(?:\s?[/\-]\s?|\s)?\d{3,8}\b(?:[ \-]\d{2}\b(?![.,/:\-]\d)){0,3}`),
294
355
  p(EntityType.PASSPORT, String.raw `\b[CFGHJK][0-9CFGHJKLMNPRTVWXYZ]{8}\d?\b`, null, "", PASSPORT_CONTEXT, true),
295
356
  // The separator after the city code is mandatory. When it was optional, a
296
357
  // contiguous letter run split across both groups and the hyphen was
@@ -300,7 +361,7 @@ const DE = {
300
361
  // separates the city code: "B-AB 1234", "M-XY 99".
301
362
  p(EntityType.LICENSE_PLATE, String.raw `\b[A-ZÄÖÜ]{1,3}[\s\-][A-Z]{1,2}[\s\-]?\d{1,4}[EH]?\b`),
302
363
  p(EntityType.POSTAL_CODE, String.raw `\b\d{5}\b`, null, "", ["PLZ", "Postleitzahl", "postal code", "postcode", "Anschrift", "Adresse", "Wohnort", "Ort", "Stadt", "wohnt", "wohnhaft", "Straße", "Str.", "Weg", "Platz", "Allee", "Ring"], true),
303
- p(EntityType.CHAMBER_OF_COMMERCE, String.raw `\bHR[AB]\s?\d{4,6}\b`),
364
+ p(EntityType.CHAMBER_OF_COMMERCE, String.raw `\bHR[AB]\s?\d{4,6}(?:\s[A-Z]\b)?`),
304
365
  p(EntityType.SSN, String.raw `\b\d{2}[\s]?\d{6}[\s]?[A-Z][\s]?\d{3}\b`, null, "", ["Rentenversicherung", "RV-Nummer", "Sozialversicherung", "SV-Nummer", "Versicherungsnummer", "Rentenversicherungsnummer", "Sozialversicherungsnummer", "SVNR", "SV-Nr", "RVNR"], true),
305
366
  p(EntityType.TAX_ID, String.raw `\b\d{2,3}/\d{3,4}/\d{4,5}\b`, null, "", ["Steuernummer", "St.-Nr", "StNr", "Finanzamt", "Steuer"], true),
306
367
  p(EntityType.TAX_ID, String.raw `\b\d{13}\b`, null, "", ["Steuernummer", "St.-Nr", "StNr", "Finanzamt", "Steuer"], true),
@@ -311,15 +372,16 @@ const AT = {
311
372
  code: "AT",
312
373
  name: "Austria",
313
374
  patterns: [
314
- p(EntityType.NATIONAL_ID, String.raw `\b\d{4}\s?\d{6}\b`, "austrian_svnr"),
375
+ // SSN: Austria has no national ID; the SVNr is its separate social-insurance number (rules-engine#111).
376
+ p(EntityType.SSN, String.raw `\b\d{4}\s?\d{6}\b`, "austrian_svnr"),
315
377
  p(EntityType.IBAN, String.raw `\bAT\d{2}\s?\d{4}\s?\d{4}\s?\d{4}\s?\d{4}\b`, "iban"),
316
- p(EntityType.VAT, String.raw `\bATU\d{8}\b`),
378
+ p(EntityType.VAT, String.raw `\bATU[\s.]?\d{8}\b`),
317
379
  p(EntityType.PHONE, String.raw `\b0\d{3,4}[\s\-]?\d{5,8}\b`),
318
380
  // Short area codes (Vienna is "01") split across three groups:
319
381
  // "01 53460 2215". The pattern above requires 3-4 digits after the trunk
320
382
  // 0, so it never matched these. Both separators are mandatory here, which
321
383
  // keeps "01 2025" (a date fragment) out.
322
- p(EntityType.PHONE, String.raw `\b0\d{1,4}[\s\-/]\d{3,7}[\s\-/]\d{2,6}\b`, null, "Austrian national phone — short area code, grouped"),
384
+ p(EntityType.PHONE, String.raw `\b0\d{1,4}[\s\-/]\d{3,7}[\s\-/]\d{2,6}\b(?![.,/:]\d)`, null, "Austrian national phone — short area code, grouped"),
323
385
  p(EntityType.PHONE, String.raw `\+43\s?\d{3,4}[\s\-]?\d{5,8}`),
324
386
  p(EntityType.LICENSE_PLATE, String.raw `\b[A-ZÄÖÜ]{1,2}\s?\d{1,5}\s?[A-Z]{1,2}\b`, null, "", ["Kennzeichen", "Nummernschild", "Kfz-Kennzeichen"], true),
325
387
  p(EntityType.POSTAL_CODE, String.raw `\b[1-9]\d{3}\b`, null, "", ["PLZ", "Postleitzahl", "Adresse", "Anschrift", "Straße", "Str.", "Gasse", "Weg", "Platz", "Postal:", "Wohnort"], true),
@@ -331,6 +393,8 @@ const CH = {
331
393
  name: "Switzerland",
332
394
  patterns: [
333
395
  p(EntityType.NATIONAL_ID, String.raw `\b756\.\d{4}\.\d{4}\.\d{2}\b`, "swiss_ahv"),
396
+ // Dotted, check digit not required when the document names it nearby (rules-engine#82).
397
+ p(EntityType.NATIONAL_ID, String.raw `\b756\.\d{4}\.\d{4}\.\d{2}\b`, null, "Swiss AHV number — dotted, beside AVS/AHV", ["AVS", "AHV"], true),
334
398
  p(EntityType.NATIONAL_ID, String.raw `\b756\d{10}\b`, "swiss_ahv"),
335
399
  p(EntityType.CHAMBER_OF_COMMERCE, String.raw `\bCHE[\-\s]?\d{3}\.?\d{3}\.?\d{3}\b`),
336
400
  p(EntityType.VAT, String.raw `\bCHE[\-\s]?\d{3}\.?\d{3}\.?\d{3}\s?(?:MWST|TVA|IVA)\b`),
@@ -339,13 +403,16 @@ const CH = {
339
403
  p(EntityType.PHONE, String.raw `\+41\s?\d{2}\s?\d{3}\s?\d{2}\s?\d{2}`),
340
404
  p(EntityType.LICENSE_PLATE, String.raw `\b[A-Z]{2}\s?\d{1,6}\b`, null, "", ["Kontrollschild", "Nummernschild", "plaque", "immatriculation", "Kennzeichen"], true),
341
405
  p(EntityType.POSTAL_CODE, String.raw `\b[1-9]\d{3}\b`, null, "", ["PLZ", "Postleitzahl", "code postal", "NPA", "Adresse", "Anschrift", "adresse", "rue", "Straße", "Str.", "Gasse", "chemin", "Postal:", "Wohnort"], true),
342
- p(EntityType.POSTAL_CODE, String.raw `(?<=, )[1-9]\d{3}(?= [A-Z])`),
406
+ p(EntityType.POSTAL_CODE, String.raw `(?:(?<=, )|(?<=, CH-)|(?<=^CH-)|(?<=\nCH-))[1-9]\d{3}(?= [A-Z])`), // also "…, CH-8004 Zürich" and a "CH-8004 Zürich" line (rules-engine#58)
343
407
  ],
344
408
  };
345
409
  const FR = {
346
410
  code: "FR",
347
411
  name: "France",
348
412
  patterns: [
413
+ // Healthcare-provider numbers, behind their label: no shape of their own, so without a pattern nothing claimed them, or a phone or a foreign checksum did (canon HEALTHCARE_PROVIDER; rules-engine#111).
414
+ p(EntityType.HEALTHCARE_PROVIDER, String.raw `\b\d{11}\b`, null, "French RPPS — 11 digits", ["RPPS", "Répertoire Partagé des Professionnels"], true),
415
+ p(EntityType.HEALTHCARE_PROVIDER, String.raw `\b\d{9}\b`, null, "French ADELI — 9 digits", ["ADELI"], true),
349
416
  p(EntityType.NATIONAL_ID, String.raw `\b[12]\s?\d{2}\s?(?:0[1-9]|1[0-2]|[2-9]\d)\s?\d{2}[0-9AB]?\s?\d{3}\s?\d{3}\s?\d{2}\b`, "french_nir"),
350
417
  p(EntityType.NATIONAL_ID, String.raw `\b\d{12}\b`, null, "", ["carte d'identité", "CNI", "carte nationale", "identity card", "pièce d'identité"], true),
351
418
  p(EntityType.IBAN, String.raw `\bFR\d{2}\s?\d{4}\s?\d{4}\s?\d{4}\s?\d{4}\s?\d{4}\s?\d{3}\b`, "iban"),
@@ -354,7 +421,7 @@ const FR = {
354
421
  p(EntityType.PHONE, String.raw `\+33\s?[1-9][\s.\-]?\d{2}[\s.\-]?\d{2}[\s.\-]?\d{2}[\s.\-]?\d{2}`),
355
422
  p(EntityType.PASSPORT, String.raw `\b\d{2}[A-Z]{2}\d{5}\b`, null, "", PASSPORT_CONTEXT, true),
356
423
  p(EntityType.LICENSE_PLATE, String.raw `\b[A-Z]{2}[\-\s]?\d{3}[\-\s]?[A-Z]{2}\b`),
357
- p(EntityType.POSTAL_CODE, String.raw `\b(?:0[1-9]|[1-8]\d|9[0-5]|97[1-6])\d{3}\b`, null, "", ["code postal", "CP", "postal code", "postcode", "adresse", "domicilié", "résidant", "rue", "avenue", "boulevard", "place", "chemin", "allée", "impasse", "ville"], true),
424
+ p(EntityType.POSTAL_CODE, String.raw `\b(?:0[1-9]|[1-8]\d|9[0-5]|97[1-6])\d{3}\b`, null, "", ["code postal", "CP", "postal code", "postcode", "adresse", "domicile", "domicilié", "résidant", "rue", "avenue", "boulevard", "place", "chemin", "allée", "impasse", "ville"], true),
358
425
  p(EntityType.CHAMBER_OF_COMMERCE, String.raw `\b\d{3}\s?\d{3}\s?\d{3}\b`, null, "", ["SIREN", "siren", "RCS", "entreprise", "immatricul", "numéro d'entreprise", "registre du commerce"], true),
359
426
  p(EntityType.CHAMBER_OF_COMMERCE, String.raw `\b\d{3}\s?\d{3}\s?\d{3}\s?\d{5}\b`, null, "", ["SIRET", "siret", "établissement", "immatricul"], true),
360
427
  p(EntityType.TAX_ID, String.raw `\b\d{13}\b`, null, "", ["numéro fiscal", "SPI", "référence fiscale", "avis d'impôt", "impôt sur le revenu", "déclaration fiscale", "fiscal"], true),
@@ -387,9 +454,9 @@ const SE = {
387
454
  p(EntityType.PHONE, String.raw `\b0\d{1,3}[\-\s]?\d{2,3}[\s]?\d{2,3}[\s]?\d{2}\b`),
388
455
  p(EntityType.PHONE, String.raw `\+46\s?\d{1,3}[\s\-]?\d{2,3}[\s\-]?\d{2,3}[\s\-]?\d{2}`),
389
456
  p(EntityType.LICENSE_PLATE, String.raw `\b[A-Z]{3}\s?\d{2}[A-Z0-9]\b`),
390
- p(EntityType.POSTAL_CODE, String.raw `\b\d{3}\s?\d{2}\b`, null, "", ["postnummer", "postort", "postkod", "adress", "bostadsadress", "gatuadress", "boende", "gatan", "vägen", "gata", "väg", "allé", "plats", "torg", "stigen", "Bostadsadress"], true),
391
- p(EntityType.POSTAL_CODE, String.raw `\b\d{4}\s\d{2}\b`, null, "", ["postnummer", "postort", "postkod", "adress", "bostadsadress", "gatuadress", "boende", "gatan", "vägen", "gata", "väg", "allé"], true),
392
- p(EntityType.POSTAL_CODE, String.raw `\b\d{6}\b`, null, "", ["postnummer", "postort", "postkod", "adress", "bostadsadress", "gatuadress", "boende", "gatan", "vägen", "gata", "väg", "allé"], true),
457
+ p(EntityType.POSTAL_CODE, String.raw `\b\d{3}\s?\d{2}\b`, null, "", ["postnummer", "postort", "postkod", "adress", "Postal:", "Address:", "bostadsadress", "gatuadress", "boende", "gatan", "vägen", "gata", "väg", "allé", "plats", "torg", "stigen", "Bostadsadress"], true),
458
+ p(EntityType.POSTAL_CODE, String.raw `\b\d{4}\s\d{2}\b`, null, "", ["postnummer", "postort", "postkod", "adress", "Postal:", "Address:", "bostadsadress", "gatuadress", "boende", "gatan", "vägen", "gata", "väg", "allé"], true),
459
+ p(EntityType.POSTAL_CODE, String.raw `\b\d{6}\b`, null, "", ["postnummer", "postort", "postkod", "adress", "Postal:", "Address:", "bostadsadress", "gatuadress", "boende", "gatan", "vägen", "gata", "väg", "allé"], true),
393
460
  p(EntityType.CHAMBER_OF_COMMERCE, String.raw `\b\d{6}-?\d{4}\b`, null, "", ["organisationsnummer", "org.nr", "org nr", "Bolagsverket", "registreringsnummer"], true),
394
461
  ],
395
462
  };
@@ -405,7 +472,7 @@ const NO = {
405
472
  p(EntityType.PHONE, String.raw `\b[2-9]\d\s?\d{2}\s?\d{2}\s?\d{2}\b`),
406
473
  p(EntityType.PHONE, String.raw `\+47\s?\d{2}\s?\d{2}\s?\d{2}\s?\d{2}`),
407
474
  p(EntityType.LICENSE_PLATE, String.raw `\b[A-Z]{2}\s?\d{5}\b`),
408
- p(EntityType.POSTAL_CODE, String.raw `\b\d{4}\b`, null, "", ["postnummer", "poststed", "postnr", "adresse", "bostedsadresse", "gate", "vei", "veien", "gata", "plass", "stien", "allé"], true),
475
+ p(EntityType.POSTAL_CODE, String.raw `\b\d{4}\b`, null, "", ["postnummer", "poststed", "postnr", "adresse", "bostedsadresse", "gate", "vei", "veien", "gata", "plass", "stien", "allé", "Postal:", "Address:"], true),
409
476
  ],
410
477
  };
411
478
  const FI = {
@@ -414,7 +481,7 @@ const FI = {
414
481
  patterns: [
415
482
  p(EntityType.NATIONAL_ID, String.raw `\b\d{6}[-+ABCDEFYXWVU]\d{3}[0-9A-FHJK-NPR-Y]\b`, "finnish_hetu"),
416
483
  p(EntityType.IBAN, String.raw `\bFI\d{2}\s?\d{4}\s?\d{4}\s?\d{4}\s?\d{2}\b`, "iban"),
417
- p(EntityType.VAT, String.raw `\bFI\d{8}\b`),
484
+ p(EntityType.VAT, String.raw `\bFI[\s.]?\d{8}\b`),
418
485
  p(EntityType.CHAMBER_OF_COMMERCE, String.raw `\b\d{7}-\d\b`, "finnish_business_id"),
419
486
  p(EntityType.PHONE, String.raw `\b0\d{1,2}\s?\d{3,4}\s?\d{3,4}\b`),
420
487
  p(EntityType.PHONE, String.raw `\+358\s?\d{1,3}[\s\-]?\d{3,4}[\s\-]?\d{3,4}`),
@@ -432,7 +499,7 @@ const IS = {
432
499
  p(EntityType.PHONE, String.raw `\b[3-9]\d{6}\b`, null, "", ["sími", "símanúmer", "farsími", "gsm", "phone", "tel", "hringja", "hringdu", "ná í", "nás á", "nás", "SMS", "sent to"], true),
433
500
  p(EntityType.PHONE, String.raw `\+354\s?\d{3}\s?\d{4}`),
434
501
  p(EntityType.LICENSE_PLATE, String.raw `\b[A-Z]{2}\s?\d{3}\b`, null, "", ["skráningarnúmer", "bílnúmer", "ökutæki", "bifreið", "plata"], true),
435
- p(EntityType.POSTAL_CODE, String.raw `\b[1-9]\d{2}\b`, null, "", ["póstnúmer", "póstfang", "staður", "heimilisfang"], true),
502
+ p(EntityType.POSTAL_CODE, String.raw `\b[1-9]\d{2}\b`, null, "", ["póstnúmer", "póstfang", "staður", "heimilisfang", "Postal:", "Address:"], true),
436
503
  ],
437
504
  };
438
505
  const IT = {
@@ -442,7 +509,7 @@ const IT = {
442
509
  p(EntityType.NATIONAL_ID, String.raw `\b[A-Z]{6}\d{2}[ABCDEHLMPRST]\d{2}[A-Z]\d{3}[A-Z]\b`, "italian_cf"),
443
510
  p(EntityType.IBAN, String.raw `\bIT\d{2}\s?[A-Z]\d{3}\s?\d{4}\s?\d{4}\s?\d{4}\s?\d{4}\s?\d{3}\b`, "iban"),
444
511
  p(EntityType.IBAN, String.raw `\bIT\d{2}[A-Z]\d{22}\b`, "iban"),
445
- p(EntityType.VAT, String.raw `\bIT\d{11}\b`),
512
+ p(EntityType.VAT, String.raw `\bIT[\s.]?\d{11}\b`),
446
513
  p(EntityType.PHONE, String.raw `\b3\d{2}[\s\-]?\d{3}[\s\-]?\d{4}\b`),
447
514
  p(EntityType.PHONE, String.raw `\b0\d{1,3}[\s\-]?\d{6,8}\b`),
448
515
  p(EntityType.PHONE, String.raw `\b0\d{1,2}[\s\-]\d{3}[\s\-]\d{4}\b`),
@@ -458,7 +525,7 @@ const ES = {
458
525
  p(EntityType.NATIONAL_ID, String.raw `\b\d{8}[A-Z]\b`, "spanish_dni"),
459
526
  p(EntityType.NATIONAL_ID, String.raw `\b[XYZ]\d{7}[A-Z]\b`, "spanish_nie"),
460
527
  p(EntityType.IBAN, String.raw `\bES\d{2}\s?\d{4}\s?\d{4}\s?\d{4}\s?\d{4}\s?\d{4}\b`, "iban"),
461
- p(EntityType.VAT, String.raw `\bES[A-Z0-9]\d{7}[A-Z0-9]\b`),
528
+ p(EntityType.VAT, String.raw `\bES[\s.]?[A-Z0-9]\d{7}[A-Z0-9]\b`),
462
529
  p(EntityType.PHONE, String.raw `\b[679]\d{2}[\s\-]?\d{3}[\s\-]?\d{3}\b`),
463
530
  p(EntityType.PHONE, String.raw `\b[679]\d{8}\b`),
464
531
  p(EntityType.PHONE, String.raw `\b[679]\d[\s\-]?\d{3}[\s\-]?\d{2}[\s\-]?\d{2}\b`),
@@ -481,7 +548,7 @@ const PT = {
481
548
  p(EntityType.NATIONAL_ID, String.raw `\b[1-35-9]\d{8}\b`, "portuguese_nif"),
482
549
  p(EntityType.NATIONAL_ID, String.raw `\b[1-35-9]\d{2}[\s.]\d{3}[\s.]\d{3}\b`, "portuguese_nif"),
483
550
  p(EntityType.IBAN, String.raw `\bPT\d{2}\s?\d{4}\s?\d{4}\s?\d{4}\s?\d{4}\s?\d{5}\b`, "iban"),
484
- p(EntityType.VAT, String.raw `\bPT\d{9}\b`),
551
+ p(EntityType.VAT, String.raw `\bPT[\s.]?\d{9}\b`),
485
552
  p(EntityType.PHONE, String.raw `\b[29]\d{2}[\s\-]?\d{3}[\s\-]?\d{3}\b`),
486
553
  p(EntityType.PHONE, String.raw `\b[29]\d{8}\b`),
487
554
  p(EntityType.PHONE, String.raw `\+351\s?[29]\d{2}[\s\-]?\d{3}[\s\-]?\d{3}`),
@@ -493,7 +560,8 @@ const LU = {
493
560
  code: "LU",
494
561
  name: "Luxembourg",
495
562
  patterns: [
496
- p(EntityType.NATIONAL_ID, String.raw `\b(?:19|20)\d{2}\s(?:0[1-9]|1[0-2])\s(?:0[1-9]|[12]\d|3[01])\s\d{3}\s\d{2}\b`),
563
+ // Grouped: one horizontal space is optional at each boundary of YYYY MM DD XXX XX (rules-engine#49).
564
+ p(EntityType.NATIONAL_ID, String.raw `\b(?:19|20)\d{2}[^\S\r\n]?(?:0[1-9]|1[0-2])[^\S\r\n]?(?:0[1-9]|[12]\d|3[01])[^\S\r\n]?\d{3}[^\S\r\n]?\d{2}\b`),
497
565
  p(EntityType.NATIONAL_ID, String.raw `\b(?:19|20)\d{2}(?:0[1-9]|1[0-2])(?:0[1-9]|[12]\d|3[01])\d{5}\b`),
498
566
  p(EntityType.IBAN, String.raw `\bLU\d{2}\s?[A-Z0-9]{4}\s?[A-Z0-9]{4}\s?[A-Z0-9]{4}\s?[A-Z0-9]{4}\b`, "iban"),
499
567
  p(EntityType.VAT, String.raw `\bLU\s?\d{8}\b`, "vat_lu"),
@@ -510,14 +578,32 @@ const PL = {
510
578
  code: "PL",
511
579
  name: "Poland",
512
580
  patterns: [
581
+ // Healthcare-provider numbers, behind their label: no shape of their own, so without a pattern nothing claimed them, or a phone or a foreign checksum did (canon HEALTHCARE_PROVIDER; rules-engine#111).
582
+ p(EntityType.HEALTHCARE_PROVIDER, String.raw `\b\d{7}\b`, null, "Polish PWZ — 7 digits", ["PWZ", "prawa wykonywania zawodu", "prawo wykonywania zawodu"], true),
513
583
  p(EntityType.NATIONAL_ID, String.raw `\b\d{11}\b`, "polish_pesel"),
514
- p(EntityType.TAX_ID, String.raw `\b\d{3}-?\d{3}-?\d{2}-?\d{2}\b`, "polish_nip"),
584
+ // NIP in the company (3-3-2-2) or the personal (3-2-2-3) grouping (rules-engine#94).
585
+ p(EntityType.TAX_ID, String.raw `\b(?:\d{3}-?\d{3}-?\d{2}-?\d{2}|\d{3}-\d{2}-\d{2}-\d{3})\b`, "polish_nip"),
515
586
  p(EntityType.IBAN, String.raw `\bPL\d{2}\s?\d{4}\s?\d{4}\s?\d{4}\s?\d{4}\s?\d{4}\s?\d{4}\b`, "iban"),
516
587
  p(EntityType.IBAN, String.raw `\bPL\d{26}\b`, "iban"),
517
- p(EntityType.VAT, String.raw `\bPL\d{10}\b`),
588
+ // NRB, the domestic account number: the IBAN without "PL" (rules-engine#93).
589
+ p(EntityType.BANK_ACCOUNT, String.raw `\b\d{2}(?: ?\d{4}){6}\b`, "polish_nrb", "Polish NRB"),
590
+ p(EntityType.VAT, String.raw `\bPL[\s.]?\d{10}\b`),
518
591
  p(EntityType.PHONE, String.raw `\b[5-8]\d{2}[\s\-]?\d{3}[\s\-]?\d{3}\b`),
519
592
  p(EntityType.PHONE, String.raw `\b[5-8]\d{8}\b`),
520
593
  p(EntityType.PHONE, String.raw `\+48\s?[5-8]\d{2}[\s\-]?\d{3}[\s\-]?\d{3}`),
594
+ // Identity card, REGON, driving licence: each behind its own label (rules-engine#75, #76, #77).
595
+ p(EntityType.NATIONAL_ID, String.raw `\b[A-Z]{3}\s?\d{6}\b`, "polish_id_card", "Polish identity card", ["dowód osobisty", "dowodu osobistego", "dowodem osobistym", "dowód", "dowodu", "nr dowodu", "identity card", "ID card"], true),
596
+ p(EntityType.CHAMBER_OF_COMMERCE, String.raw `\b\d{9}(?:\d{5})?\b`, "polish_regon", "Polish REGON", ["REGON"], true),
597
+ // KRS: ten digits with the leading zeros, so it begins "00", which no dialled number does (rules-engine#90).
598
+ p(EntityType.CHAMBER_OF_COMMERCE, String.raw `\b00\d{8}\b`, null, "Polish KRS", ["KRS"], true),
599
+ p(EntityType.DRIVERS_LICENSE, String.raw `\b\d{3,6}/\d{2}/\d{3,7}\b`, null, "Polish driving licence number — field 5", ["prawo jazdy", "prawa jazdy", "prawem jazdy", "driving licence", "driving license"], true),
600
+ p(EntityType.DRIVERS_LICENSE, String.raw `\b[A-Z]{2}\s?\d{6,7}\b`, null, "Polish driving licence document number", ["prawo jazdy", "prawa jazdy", "prawem jazdy", "driving licence", "driving license"], true),
601
+ // Military booklet: a 2- or 3-letter series + 7 digits, one identifier, also "seria MS nr 6620419" (rules-engine#102).
602
+ p(EntityType.INTERNAL_ID, String.raw `\b[A-Z]{2,3}(?:\s?|\s+nr\.?\s+)\d{7}\b`, null, "Polish military booklet", ["książeczka wojskowa", "książeczki wojskowej", "książeczkę wojskową", "książeczką wojskową", "książeczce wojskowej", "military booklet", "military record book"], true),
603
+ // Residence card and national-visa sticker: 2 letters + 7 digits (rules-engine#103).
604
+ p(EntityType.RESIDENCE_PERMIT, String.raw `\b[A-Z]{2}\s?\d{7}\b`, null, "Polish residence card or visa sticker", ["karta pobytu", "karty pobytu", "kartę pobytu", "kartą pobytu", "CUKR", "naklejka wizowa", "naklejki wizowej", "naklejkę wizową", "naklejką wizową", "residence card", "visa sticker"], true),
605
+ // Case reference (znak sprawy): prefix.class.number.year, one INTERNAL_ID (rules-engine#105).
606
+ p(EntityType.INTERNAL_ID, String.raw `\b[A-ZĄĆĘŁŃÓŚŹŻ]{2,}(?:-[A-Z0-9]{1,6})*(?:\.[A-Z0-9]{1,6})*\.\d{3,4}(?:\.\d{1,6})+\.(?:19|20)\d{2}\b`, null, "Polish case reference", ["znak sprawy", "znak pisma", "znak:", "sygnatura", "sygn.", "sprawa nr", "sprawy nr", "nr sprawy", "decyzj", "postanowieni", "pismo nr"], true),
521
607
  p(EntityType.POSTAL_CODE, String.raw `\b\d{2}-\d{3}\b`, null, "", ["kod pocztowy", "adres", "ulica", "Postal:", "Address:"]),
522
608
  ],
523
609
  };
@@ -525,10 +611,11 @@ const IE = {
525
611
  code: "IE",
526
612
  name: "Ireland",
527
613
  patterns: [
528
- p(EntityType.NATIONAL_ID, String.raw `\b\d{7}[A-W][ABWTXZ]?\b`, "irish_pps"),
614
+ // The PPSN is SSN, never NATIONAL_ID: Ireland has no national ID (canon SSN; rules-engine#111).
615
+ p(EntityType.SSN, String.raw `\b\d{7}[A-W][ABWTXZ]?\b`, "irish_pps"),
529
616
  p(EntityType.IBAN, String.raw `\bIE\d{2}\s?[A-Z]{4}\s?\d{4}\s?\d{4}\s?\d{4}\s?\d{2}\b`, "iban"),
530
617
  p(EntityType.IBAN, String.raw `\bIE\d{2}[A-Z]{4}\d{14}\b`, "iban"),
531
- p(EntityType.VAT, String.raw `\bIE\d{7}[A-Z]\b`),
618
+ p(EntityType.VAT, String.raw `\bIE[\s.]?\d{7}[A-Z]\b`),
532
619
  p(EntityType.PHONE, String.raw `\b0[89]\d[\s\-]?\d{3}[\s\-]?\d{4}\b`),
533
620
  p(EntityType.PHONE, String.raw `\b0[89]\d{8}\b`),
534
621
  p(EntityType.PHONE, String.raw `\b0[1-9]\d{0,2}[\s\-]?\d{5,8}\b`),
@@ -540,11 +627,14 @@ const UK = {
540
627
  code: "UK",
541
628
  name: "United Kingdom",
542
629
  patterns: [
543
- p(EntityType.NATIONAL_ID, String.raw `\b[A-CEGHJ-PR-TW-Z][A-CEGHJ-NPR-TW-Z]\s?\d{2}\s?\d{2}\s?\d{2}\s?[ABCD]\b`),
630
+ // Healthcare-provider numbers, behind their label: no shape of their own, so without a pattern nothing claimed them, or a phone or a foreign checksum did (canon HEALTHCARE_PROVIDER; rules-engine#111).
631
+ p(EntityType.HEALTHCARE_PROVIDER, String.raw `\b\d{7}\b`, null, "UK GMC number — 7 digits", ["GMC number", "GMC no", "GMC reference", "GMC reg", "GMC:", "GMC-Nr", "General Medical Council"], true),
632
+ // UK NINO: a social-security number, so SSN, as the canon defaults it (rules-engine#47).
633
+ p(EntityType.SSN, String.raw `\b[A-CEGHJ-PR-TW-Z][A-CEGHJ-NPR-TW-Z]\s?\d{2}\s?\d{2}\s?\d{2}\s?[ABCD]\b`),
544
634
  p(EntityType.HEALTH_INSURANCE, String.raw `\b\d{3}\s?\d{3}\s?\d{4}\b`, "uk_nhs", "", ["NHS", "NHS number", "health number", "NHS no"], true),
545
635
  p(EntityType.IBAN, String.raw `\bGB\d{2}\s?[A-Z]{4}\s?\d{4}\s?\d{4}\s?\d{4}\s?\d{2}\b`, "iban"),
546
636
  p(EntityType.IBAN, String.raw `\bGB\d{2}[A-Z]{4}\d{14}\b`, "iban"),
547
- p(EntityType.VAT, String.raw `\bGB\d{9}\b`),
637
+ p(EntityType.VAT, String.raw `\bGB[\s.]?\d{3}[\s.]?\d{4}[\s.]?\d{2}\b`),
548
638
  p(EntityType.PHONE, String.raw `\b07[4-9]\d{2}[\s\-]?\d{3}[\s\-]?\d{3}\b`),
549
639
  p(EntityType.PHONE, String.raw `\b07[4-9]\d{8}\b`),
550
640
  p(EntityType.PHONE, String.raw `\b0[12]\d{2}[\s\-]?\d{3}[\s\-]?\d{4}\b`),
@@ -560,7 +650,7 @@ const BG = {
560
650
  patterns: [
561
651
  p(EntityType.NATIONAL_ID, String.raw `\b\d{10}\b`, "bulgarian_egn"),
562
652
  p(EntityType.IBAN, String.raw `\bBG\d{2}\s?[A-Z]{4}\s?\d{4}\s?\d{4}\s?\d{4}\s?\d{2}\b`, "iban"),
563
- p(EntityType.VAT, String.raw `\bBG\d{9,10}\b`),
653
+ p(EntityType.VAT, String.raw `\bBG[\s.]?\d{9,10}\b`),
564
654
  p(EntityType.PHONE, String.raw `\b0?[89][789][\s\-]?\d{3}[\s\-]?\d{3,4}\b`),
565
655
  p(EntityType.PHONE, String.raw `\b0?[89][789]\d{7,8}\b`),
566
656
  p(EntityType.PHONE, String.raw `\+359\s?[89][789][\s\-]?\d{3}[\s\-]?\d{3,4}`),
@@ -575,7 +665,7 @@ const CY = {
575
665
  p(EntityType.NATIONAL_ID, String.raw `\b\d{8}[A-Z]\b`),
576
666
  p(EntityType.IBAN, String.raw `\bCY\d{2}\s?\d{4}\s?\d{4}\s?\d{4}\s?\d{4}\s?\d{4}\s?\d{4}\b`, "iban"),
577
667
  p(EntityType.IBAN, String.raw `\bCY\d{26}\b`, "iban"),
578
- p(EntityType.VAT, String.raw `\bCY\d{8}[A-Z]\b`),
668
+ p(EntityType.VAT, String.raw `\bCY[\s.]?\d{8}[A-Z]\b`),
579
669
  p(EntityType.PHONE, String.raw `\b[29]\d{3}[\s\-]?\d{4}\b`),
580
670
  p(EntityType.PHONE, String.raw `\b[29]\d[\s\-]?\d{3}[\s\-]?\d{3}\b`),
581
671
  p(EntityType.PHONE, String.raw `\b[29]\d{7}\b`),
@@ -591,7 +681,7 @@ const CZ = {
591
681
  p(EntityType.NATIONAL_ID, String.raw `\b\d{6}/?\d{3,4}\b`, "czech_birth_number"),
592
682
  p(EntityType.IBAN, String.raw `\bCZ\d{2}\s?\d{4}\s?\d{4}\s?\d{4}\s?\d{4}\s?\d{4}\b`, "iban"),
593
683
  p(EntityType.IBAN, String.raw `\bCZ\d{22}\b`, "iban"),
594
- p(EntityType.VAT, String.raw `\bCZ\d{8,10}\b`),
684
+ p(EntityType.VAT, String.raw `\bCZ[\s.]?\d{8,10}\b`),
595
685
  p(EntityType.PHONE, String.raw `\b[67]\d{2}[\s\-]?\d{3}[\s\-]?\d{3}\b`),
596
686
  p(EntityType.PHONE, String.raw `\b[67]\d{8}\b`),
597
687
  p(EntityType.PHONE, String.raw `\+420\s?[67]\d{2}[\s\-]?\d{3}[\s\-]?\d{3}`),
@@ -605,7 +695,7 @@ const SK = {
605
695
  p(EntityType.NATIONAL_ID, String.raw `\b\d{6}/?\d{3,4}\b`, "czech_birth_number"),
606
696
  p(EntityType.IBAN, String.raw `\bSK\d{2}\s?\d{4}\s?\d{4}\s?\d{4}\s?\d{4}\s?\d{4}\b`, "iban"),
607
697
  p(EntityType.IBAN, String.raw `\bSK\d{22}\b`, "iban"),
608
- p(EntityType.VAT, String.raw `\bSK\d{10}\b`),
698
+ p(EntityType.VAT, String.raw `\bSK[\s.]?\d{10}\b`),
609
699
  p(EntityType.PHONE, String.raw `\b0?9\d{2}[\s\-]?\d{3}[\s\-]?\d{3}\b`),
610
700
  p(EntityType.PHONE, String.raw `\+421\s?9\d{2}[\s\-]?\d{3}[\s\-]?\d{3}`),
611
701
  p(EntityType.POSTAL_CODE, String.raw `\b\d{3}\s?\d{2}\b`, null, "", ["PSČ", "poštové", "adresa", "ulica", "Postal:", "Address:"], true),
@@ -618,7 +708,7 @@ const EE = {
618
708
  p(EntityType.NATIONAL_ID, String.raw `\b[1-6]\d{10}\b`, "estonian_id"),
619
709
  p(EntityType.IBAN, String.raw `\bEE\d{2}\s?\d{4}\s?\d{4}\s?\d{4}\s?\d{4}\b`, "iban"),
620
710
  p(EntityType.IBAN, String.raw `\bEE\d{18}\b`, "iban"),
621
- p(EntityType.VAT, String.raw `\bEE\d{9}\b`),
711
+ p(EntityType.VAT, String.raw `\bEE[\s.]?\d{9}\b`),
622
712
  p(EntityType.PHONE, String.raw `\b5\d{3}[\s\-]?\d{4}\b`),
623
713
  p(EntityType.PHONE, String.raw `\b5\d{7}\b`),
624
714
  p(EntityType.PHONE, String.raw `\+372\s?5\d{3}[\s\-]?\d{3,4}`),
@@ -632,7 +722,7 @@ const LT = {
632
722
  p(EntityType.NATIONAL_ID, String.raw `\b[1-6]\d{10}\b`, "estonian_id"),
633
723
  p(EntityType.IBAN, String.raw `\bLT\d{2}\s?\d{4}\s?\d{4}\s?\d{4}\s?\d{4}\b`, "iban"),
634
724
  p(EntityType.IBAN, String.raw `\bLT\d{18}\b`, "iban"),
635
- p(EntityType.VAT, String.raw `\bLT\d{9,12}\b`),
725
+ p(EntityType.VAT, String.raw `\bLT[\s.]?\d{9,12}\b`),
636
726
  p(EntityType.PHONE, String.raw `\b6\d{2}[\s\-]?\d{2}[\s\-]?\d{3}\b`),
637
727
  p(EntityType.PHONE, String.raw `\b6\d{7}\b`),
638
728
  p(EntityType.PHONE, String.raw `\b8[\s\-]?6\d{2}[\s\-]?\d{2}[\s\-]?\d{3}\b`),
@@ -648,7 +738,7 @@ const LV = {
648
738
  p(EntityType.NATIONAL_ID, String.raw `\b(?:0[1-9]|[12]\d|3[01])(?:0[1-9]|1[0-2])\d{7}\b`, null, "", ["personas kods", "PK", "identifikācijas"], true),
649
739
  p(EntityType.IBAN, String.raw `\bLV\d{2}\s?[A-Z]{4}\s?\d{4}\s?\d{4}\s?\d{4}\s?\d\b`, "iban"),
650
740
  p(EntityType.IBAN, String.raw `\bLV\d{2}[A-Z]{4}\d{13}\b`, "iban"),
651
- p(EntityType.VAT, String.raw `\bLV\d{11}\b`),
741
+ p(EntityType.VAT, String.raw `\bLV[\s.]?\d{11}\b`),
652
742
  p(EntityType.PHONE, String.raw `\b[2]\d{3}[\s\-]?\d{4}\b`),
653
743
  p(EntityType.PHONE, String.raw `\b[2]\d{7}\b`),
654
744
  p(EntityType.PHONE, String.raw `\+371\s?[2]\d{3}[\s\-]?\d{3,4}`),
@@ -663,7 +753,7 @@ const HR = {
663
753
  p(EntityType.IBAN, String.raw `\bHR\d{2}\s?\d{4}\s?\d{4}\s?\d{4}\s?\d{4}\s?\d{3}\b`, "iban"),
664
754
  p(EntityType.IBAN, String.raw `\bHR\d{2}\s\d{4}\s\d{4}\s\d{4}\s\d{5}\b`, "iban"),
665
755
  p(EntityType.IBAN, String.raw `\bHR\d{19}\b`, "iban"),
666
- p(EntityType.VAT, String.raw `\bHR\d{11}\b`),
756
+ p(EntityType.VAT, String.raw `\bHR[\s.]?\d{11}\b`),
667
757
  p(EntityType.PHONE, String.raw `\b0?9[12579][\s\-]?\d{3}[\s\-]?\d{3,4}\b`),
668
758
  p(EntityType.PHONE, String.raw `\b0?9[12579]\d{7,8}\b`),
669
759
  p(EntityType.PHONE, String.raw `\+385\s?9[12579][\s\-]?\d{3}[\s\-]?\d{3,4}`),
@@ -677,7 +767,7 @@ const HU = {
677
767
  p(EntityType.NATIONAL_ID, String.raw `\b\d{9}\b`, "hungarian_taj"),
678
768
  p(EntityType.NATIONAL_ID, String.raw `\b\d{3}\s\d{3}\s\d{3}\b`, "hungarian_taj"),
679
769
  p(EntityType.TAX_ID, String.raw `\b8\d{9}\b`, null, "", ["adóazonosító", "adószám", "adóazonosító jel"], true),
680
- p(EntityType.VAT, String.raw `\bHU\d{8,11}\b`, null, "", ["adószám", "ÁFA"]),
770
+ p(EntityType.VAT, String.raw `\bHU[\s.]?\d{8,11}\b`, null, "", ["adószám", "ÁFA"]),
681
771
  p(EntityType.IBAN, String.raw `\bHU\d{2}\s?\d{4}\s?\d{4}\s?\d{4}\s?\d{4}\s?\d{4}\s?\d{4}\b`, "iban"),
682
772
  p(EntityType.IBAN, String.raw `\bHU\d{26}\b`, "iban"),
683
773
  p(EntityType.PHONE, String.raw `\b06\s?\d{1,2}[\s\-]?\d{3}[\s\-]?\d{3,4}\b`),
@@ -692,7 +782,7 @@ const RO = {
692
782
  patterns: [
693
783
  p(EntityType.NATIONAL_ID, String.raw `\b[1-8]\d{12}\b`, "romanian_cnp"),
694
784
  p(EntityType.IBAN, String.raw `\bRO\d{2}[A-Z]{4}\d{16}\b`, "iban"),
695
- p(EntityType.VAT, String.raw `\bRO\d{2,10}\b`),
785
+ p(EntityType.VAT, String.raw `\bRO[\s.]?\d{2,10}\b`),
696
786
  p(EntityType.PHONE, String.raw `\b0?7[2-9]\d{1}[\s\-]?\d{3}[\s\-]?\d{3,4}\b`),
697
787
  p(EntityType.PHONE, String.raw `\b0?7[2-9]\d{7,8}\b`),
698
788
  p(EntityType.PHONE, String.raw `\+40\s?7[2-9]\d{1}[\s\-]?\d{3}[\s\-]?\d{3,4}`),
@@ -707,7 +797,7 @@ const EL = {
707
797
  p(EntityType.NATIONAL_ID, String.raw `\b\d{11}\b`, "greek_amka"),
708
798
  p(EntityType.IBAN, String.raw `\bGR\d{2}\s?\d{4}\s?\d{4}\s?\d{4}\s?\d{4}\s?\d{4}\s?\d{3}\b`, "iban"),
709
799
  p(EntityType.IBAN, String.raw `\bGR\d{25}\b`, "iban"),
710
- p(EntityType.VAT, String.raw `\bEL\d{9}\b`),
800
+ p(EntityType.VAT, String.raw `\bEL[\s.]?\d{9}\b`),
711
801
  p(EntityType.PHONE, String.raw `\b69\d[\s\-]?\d{3,4}[\s\-]?\d{4}\b`),
712
802
  p(EntityType.PHONE, String.raw `\b69\d{8}\b`),
713
803
  p(EntityType.PHONE, String.raw `\b2\d{1,3}[\s\-]?\d{3,4}[\s\-]?\d{3,4}\b`),
@@ -722,7 +812,7 @@ const SI = {
722
812
  p(EntityType.NATIONAL_ID, String.raw `\b\d{13}\b`, "slovenian_emso"),
723
813
  p(EntityType.IBAN, String.raw `\bSI\d{2}\s?\d{4}\s?\d{4}\s?\d{4}\s?\d{3}\b`, "iban"),
724
814
  p(EntityType.IBAN, String.raw `\bSI\d{17}\b`, "iban"),
725
- p(EntityType.VAT, String.raw `\bSI\d{8}\b`),
815
+ p(EntityType.VAT, String.raw `\bSI[\s.]?\d{8}\b`),
726
816
  p(EntityType.PHONE, String.raw `\b0?[34567]\d{1}[\s\-]?\d{3}[\s\-]?\d{3}\b`),
727
817
  p(EntityType.PHONE, String.raw `\b0?[34567]\d{7}\b`),
728
818
  p(EntityType.PHONE, String.raw `\+386\s?[34567]\d{1}[\s\-]?\d{3}[\s\-]?\d{3}`),
@@ -737,7 +827,7 @@ const MT = {
737
827
  p(EntityType.NATIONAL_ID, String.raw `\b\d{5,7}[MGAPLHBZ]\b`),
738
828
  p(EntityType.IBAN, String.raw `\bMT\d{2}\s?[A-Z]{4}\s?\d{4}\s?\d{4}\s?[A-Z0-9]{4}\s?[A-Z0-9]{4}\s?[A-Z0-9]{4}\s?[A-Z0-9]{3}\b`, "iban"),
739
829
  p(EntityType.IBAN, String.raw `\bMT\d{2}[A-Z]{4}\d{5}[A-Z0-9]{18}\b`, "iban"),
740
- p(EntityType.VAT, String.raw `\bMT\d{8}\b`),
830
+ p(EntityType.VAT, String.raw `\bMT[\s.]?\d{8}\b`),
741
831
  p(EntityType.PHONE, String.raw `\b[79]\d{3}[\s\-]?\d{4}\b`),
742
832
  p(EntityType.PHONE, String.raw `\b[79]\d{7}\b`),
743
833
  p(EntityType.PHONE, String.raw `\+356\s?[79]\d{3}[\s\-]?\d{3,4}`),
@@ -130,4 +130,4 @@ export declare const RESCUE_TARGETS: Set<string>;
130
130
  * `countryScore` is the document's support for the country the winning pattern
131
131
  * came from; see RETYPABLE_UNCORROBORATED.
132
132
  */
133
- export declare function retypedBy(text: string, start: number, entityType: EntityType | string, countryScore: number): EntityType | null;
133
+ export declare function retypedBy(text: string, start: number, entityType: EntityType | string, countryScore: number, country?: string | null): EntityType | null;
@@ -90,7 +90,7 @@ export const CUES = [
90
90
  /(?<![A-Za-z0-9_])(?:phone|tel|telephone|téléphone|telefoon|telefon|telefono|teléfono|tlf|mobil|mobile|gsm|handy|sími|puh|móvil|cell|tālrunis|τηλέφωνο|τηλ)(?:(?:[A-Za-z0-9_]|[^\x00-\x7F])*|\s+[^\s:.\-\d,;()\/]{2,20})\s*\)?\s*[:.\-]*\s*$/i],
91
91
  // National identity number.
92
92
  [EntityType.NATIONAL_ID,
93
- /(?<![A-Za-z0-9_])(?:bsn|burgerservicenummer(?:\s*\(bsn\))?|personnummer|rijksregisternummer|nationaal\s*nummer|numéro\s*national|national\s*(?:id|number)|identiteitsnummer|henkilötunnus|kennitala|cpr(?:-?nummer)?|nif|nie|dni|rr|nn|nir|insz|niss|nis|matricule|ausweisnummer|personalausweis|osobní\s*číslo|ЕГН|EGN)(?:(?:[A-Za-z0-9_]|[^\x00-\x7F])*|\s+[^\s:.\-\d,;()\/]{2,20})\s*\)?\s*[:.\-]*\s*$/i],
93
+ /(?<![A-Za-z0-9_])(?:bsn(?!r)|burgerservicenummer(?:\s*\(bsn\))?|personnummer|rijksregisternummer|nationaal\s*nummer|numéro\s*national|national\s*(?:id|number)|identiteitsnummer|henkilötunnus|kennitala|cpr(?:-?nummer)?|nif|nie|dnie?(?![A-Za-z]|[^\x00-\x7F])|rr|nn|nir|insz|niss|nis|matricule|id\s*card(?:\s*(?:no|nr|number))?|ausweisnummer|personalausweis|osobní\s*číslo|ЕГН|EGN|dow[óo]d(?:u|em)?\s*osobist(?:y|ego|ym)|nr\s*dowodu|ahv(?:-?(?:nr|nummer))?|avs)(?:(?:[A-Za-z0-9_]|[^\x00-\x7F])*|\s+[^\s:.\-\d,;()\/]{2,20})\s*\)?\s*[:.\-]*\s*$/i],
94
94
  // Cyprus: "ID number" in both official languages, plus the Greek abbreviation
95
95
  // ΑΔΤ (Αστυνομική Ταυτότητα). Neither has a phone reading.
96
96
  [EntityType.NATIONAL_ID,
@@ -111,16 +111,16 @@ export const CUES = [
111
111
  /(?<![A-Za-z0-9_])(?:αφμ|α\.φ\.μ\.|steuerliche\s*identifikations-?nummer|steuer-?identifikations-?nummer|steuer-?id-?nr\.?|steuer-?id|steuernummer|st\.?-?nr|stnr|tin|finanzamt\s+ist|tax\s*(?:id|no|number)|daňové\s*číslo|numer\s*podatkowy)(?:(?:[A-Za-z0-9_]|[^\x00-\x7F])*|\s+[^\s:.\-\d,;()\/]{2,20})\s*\)?\s*[:.\-]*\s*$/i],
112
112
  // Health insurance: the insured person.
113
113
  [EntityType.HEALTH_INSURANCE,
114
- /(?<![A-Za-z0-9_])(?:versichertennummer|krankenversicherung|kvnr|kv-?nr|mutualité|ziekenfonds|aansluitingsnummer|medical\s*card(?:\s*(?:no|number))?)(?:(?:[A-Za-z0-9_]|[^\x00-\x7F])*|\s+[^\s:.\-\d,;()\/]{2,20})\s*\)?\s*[:.\-]*\s*$/i],
114
+ /(?<![A-Za-z0-9_])(?:versichertennummer|krankenversicherung|kvnr|kv-?nr|mutualité|ziekenfonds|aansluitingsnummer|medical\s*card(?:\s*(?:no|number))?|nhs(?:\s*(?:no|number))?)(?:(?:[A-Za-z0-9_]|[^\x00-\x7F])*|\s+[^\s:.\-\d,;()\/]{2,20})\s*\)?\s*[:.\-]*\s*$/i],
115
115
  // Healthcare provider: the clinician or practice, not the patient — the Dutch
116
116
  // AGB-code, the German LANR, the UK GMC number.
117
117
  [EntityType.HEALTHCARE_PROVIDER,
118
- /(?<![A-Za-z0-9_])(?:agb-?code|lanr|bsnr|gmc(?:\s*(?:no|number|reg))?|big-?nummer|zorgverlener(?:snummer)?)(?:(?:[A-Za-z0-9_]|[^\x00-\x7F])*|\s+[^\s:.\-\d,;()\/]{2,20})\s*\)?\s*[:.\-]*\s*$/i],
118
+ /(?<![A-Za-z0-9_])(?:agb-?code|lanr|bsnr|gmc(?:\s*(?:no|number|reg))?|big(?:-?registratie)?-?(?:nummer|nr)|zorgverlener(?:snummer)?|lebenslange\s*arztnummer|betriebsst[äa]ttennummer|rpps|adeli|pwz)(?:(?:[A-Za-z0-9_]|[^\x00-\x7F])*|\s+[^\s:.\-\d,;()\/]{2,20})\s*\)?\s*[:.\-]*\s*$/i],
119
119
  // Company / trade register. "Companies House" and "Company Registration
120
120
  // Number" are written out: the tail allows a run-on *or* one qualifier word,
121
121
  // never a second word, so a three-word label needs to be spelled.
122
122
  [EntityType.CHAMBER_OF_COMMERCE,
123
- /(?<![A-Za-z0-9_])(?:siren|siret|kbo|bce|kvk|ondernemingsnummer|ondernemingen\s+onder\s+nummer|kruispuntbank|numéro\s*d'entreprise|enterprise\s*number|handelsregister|companies\s*house(?:\s*(?:registration|reg|no|number))?|company\s*registration(?:\s*(?:number|no))?|company\s*(?:no|number|reg)|organisationsnummer|orgnr|i[čc]o|identifikační\s*číslo|ЕИК|eik|bulstat|cvr(?:-?nummer)?|virksomhedsnummer)(?:(?:[A-Za-z0-9_]|[^\x00-\x7F])*|\s+[^\s:.\-\d,;()\/]{2,20})\s*\)?\s*[:.\-]*\s*$/i],
123
+ /(?<![A-Za-z0-9_])(?:siren|siret|kbo|bce|kvk|ondernemingsnummer|ondernemingen\s+onder\s+nummer|kruispuntbank|numéro\s*d'entreprise|enterprise\s*number|handelsregister|companies\s*house(?:\s*(?:registration|reg|no|number))?|company\s*registration(?:\s*(?:number|no))?|company\s*(?:no|number|reg)|organisationsnummer|orgnr|i[čc]o|identifikační\s*číslo|ЕИК|eik|bulstat|cvr(?:-?nummer)?|virksomhedsnummer|regon|krs)(?:(?:[A-Za-z0-9_]|[^\x00-\x7F])*|\s+[^\s:.\-\d,;()\/]{2,20})\s*\)?\s*[:.\-]*\s*$/i],
124
124
  // VAT.
125
125
  [EntityType.VAT,
126
126
  /(?<![A-Za-z0-9_])(?:vat|btw|tva|ust|iva|moms|alv|mwst|dič|vsk)(?:(?:[A-Za-z0-9_.\-]|[^\x00-\x7F])*|\s+[^\s:.\-\d,;()\/]{2,20})\s*\)?\s*[:.\-]*\s*$/i],
@@ -130,6 +130,15 @@ export const CUES = [
130
130
  [EntityType.BANK_ACCOUNT,
131
131
  /(?<![A-Za-z0-9_])(?:sort\s*code|account\s*(?:no|number)|rekeningnummer|kontonummer|numéro\s*de\s*compte|sort-?code)(?:(?:[A-Za-z0-9_]|[^\x00-\x7F])*|\s+[^\s:.\-\d,;()\/]{2,20})\s*\)?\s*[:.\-]*\s*$/i],
132
132
  // Passport.
133
+ // Residence permit. No permit patterns exist, so a permit number was filed
134
+ // under whichever ID, passport or phone pattern fitted it (rules-engine#53).
135
+ // The tail admits the Belgian card category ("nr. B 565992336"), unmasked.
136
+ [EntityType.RESIDENCE_PERMIT,
137
+ /(?<![A-Za-z0-9_])(?:aufenthalts(?:titel|erlaubnis|karte|gestattung)|niederlassungs(?:erlaubnis|bewilligung)|verblijfs(?:vergunning|kaart|document|titel)|titre\s*de\s*s[ée]jour|carte\s*de\s*s[ée]jour|permis\s*de\s*s[ée]jour|carte\s*de\s*r[ée]sident|residence\s*(?:permit|card|document)|biometric\s*residence\s*permit|permesso\s*di\s*soggiorno|carta\s*di\s*soggiorno|kart(?:a|y|ę|ą)\s*pobytu(?:\s*cukr)?|naklejk(?:a|i|ę|ą)\s*wizow(?:a|ej|ą)|uppehållstillstånd|opholdstilladelse|oppholdstillatelse|oleskelulupa)(?:(?:[A-Za-z0-9_]|[^\x00-\x7F])*|\s+[^\s:.\-\d,;()\/]{2,20})\s*\)?\s*[:.\-]*\s*(?:[A-Z]\s+)?$/i],
138
+ // Driving licence: Polish labels; the document number has the passport
139
+ // shape, so without this cue it lost the span to the passport rule (rules-engine#77).
140
+ [EntityType.DRIVERS_LICENSE,
141
+ /(?<![A-Za-z0-9_])(?:(?:nr\s*dokumentu\s*)?(?:prawo|prawa|prawem)\s*jazdy|numer\s*blankietu\s*prawa\s*jazdy)(?:(?:[A-Za-z0-9_]|[^\x00-\x7F])*|\s+[^\s:.\-\d,;()\/]{2,20})\s*\)?\s*[:.\-]*\s*$/i],
133
142
  [EntityType.PASSPORT,
134
143
  /(?<![A-Za-z0-9_])(?:passport(?:\s*(?:no|number))?|paspoort(?:nummer)?|reisepass(?:nummer)?|passeport|passnummer)(?:(?:[A-Za-z0-9_]|[^\x00-\x7F])*|\s+[^\s:.\-\d,;()\/]{2,20})\s*\)?\s*[:.\-]*\s*$/i],
135
144
  // BIC / SWIFT.
@@ -146,7 +155,7 @@ export const CUES = [
146
155
  // only thing that identifies one, which is why the engine emits this type
147
156
  // from here and nowhere else.
148
157
  [EntityType.INTERNAL_ID,
149
- /(?<![A-Za-z0-9_])(?:medarbejdernummer|ansattnummer|anställningsnummer|personalnummer|personeelsnummer|matricule\s*salarié|employee\s*(?:no|number|id)|badge(?:\s*(?:no|number))?|betriebsstätten(?:nr|nummer)|grundstücks(?:nr|nummer)|dossier-?(?:nr|nummer)|aktenzeichen|osobní\s*(?:číslo\s*)?zaměstnance)(?:(?:[A-Za-z0-9_]|[^\x00-\x7F])*|\s+[^\s:.\-\d,;()\/]{2,20})\s*\)?\s*[:.\-]*\s*$/i],
158
+ /(?<![A-Za-z0-9_])(?:medarbejdernummer|ansattnummer|anställningsnummer|personalnummer|personeelsnummer|matricule\s*salarié|employee\s*(?:no|number|id)|badge(?:\s*(?:no|number))?|betriebsstätten(?:nr|nummer)|grundstücks(?:nr|nummer)|dossier-?(?:nr|nummer)|aktenzeichen|schade(?:claim|nummer|dossier)|schadens?-?(?:nummer|nr)|num[ée]ro\s*de\s*sinistre|numero\s*(?:di\s*)?sinistro|n[úu]mero\s*de\s*siniestro|skade(?:nummer|sag)|claim\s*(?:no|number|id)|collar|osobní\s*(?:číslo\s*)?zaměstnance)(?:(?:[A-Za-z0-9_]|[^\x00-\x7F])*|\s+[^\s:.\-\d,;()\/]{2,20})\s*\)?\s*[:.\-]*\s*$/i],
150
159
  // Credential. Typed SECRET because that is the type this SDK has; the
151
160
  // training pipeline's report asks for CREDENTIAL, which does not exist here.
152
161
  [EntityType.SECRET,
@@ -192,6 +201,13 @@ const RETYPABLE = new Set([
192
201
  * "Betriebsstättennr. 260326822" both landed, each reported as Czech.
193
202
  */
194
203
  const RETYPABLE_UNCORROBORATED = new Set([EntityType.NATIONAL_ID]);
204
+ /**
205
+ * Retypable to RESIDENCE_PERMIT by a permit label touching the span, whatever
206
+ * the country support: a German eAT number fits the identity-card pattern and a
207
+ * Dutch permit number the passport pattern (rules-engine#53). Not a Spanish
208
+ * NIE, which the canon keeps NATIONAL_ID.
209
+ */
210
+ const RETYPABLE_BY_PERMIT_LABEL = new Set([EntityType.NATIONAL_ID, EntityType.PASSPORT]);
195
211
  /**
196
212
  * Types a label can assert on its own.
197
213
  *
@@ -205,7 +221,7 @@ export const CUE_TARGETS = new Set([
205
221
  EntityType.HEALTH_INSURANCE, EntityType.HEALTHCARE_PROVIDER,
206
222
  EntityType.CHAMBER_OF_COMMERCE, EntityType.VAT, EntityType.POSTAL_CODE,
207
223
  EntityType.PASSPORT, EntityType.INTERNAL_ID, EntityType.BANK_ACCOUNT,
208
- EntityType.SECRET,
224
+ EntityType.SECRET, EntityType.RESIDENCE_PERMIT,
209
225
  ]);
210
226
  /**
211
227
  * Which of those a label may also *rescue* — re-admit after its checksum
@@ -242,13 +258,17 @@ export const RESCUE_TARGETS = new Set([
242
258
  * `countryScore` is the document's support for the country the winning pattern
243
259
  * came from; see RETYPABLE_UNCORROBORATED.
244
260
  */
245
- export function retypedBy(text, start, entityType, countryScore) {
261
+ export function retypedBy(text, start, entityType, countryScore, country = null) {
246
262
  const type = String(entityType);
247
- if (!RETYPABLE.has(type)
248
- && !(RETYPABLE_UNCORROBORATED.has(type) && countryScore === 0))
263
+ const general = RETYPABLE.has(type)
264
+ || (RETYPABLE_UNCORROBORATED.has(type) && countryScore === 0);
265
+ const byPermit = RETYPABLE_BY_PERMIT_LABEL.has(type) && country !== "ES";
266
+ if (!general && !byPermit)
249
267
  return null;
250
268
  const cued = cuedType(text, start);
251
269
  if (cued === null || cued === entityType || !CUE_TARGETS.has(String(cued)))
252
270
  return null;
271
+ if (!general && cued !== EntityType.RESIDENCE_PERMIT)
272
+ return null;
253
273
  return cued;
254
274
  }