@stll/anonymize 0.0.2

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.
@@ -0,0 +1,939 @@
1
+ import { Validator } from "@stll/stdnum";
2
+ import { Match, PatternEntry, TextSearch } from "@stll/text-search";
3
+ import { Tokenizer } from "@huggingface/tokenizers";
4
+
5
+ //#region src/types.d.ts
6
+ /**
7
+ * Source of a detected entity span.
8
+ * Ordered by detection layer in the pipeline.
9
+ */
10
+ declare const DETECTION_SOURCES: {
11
+ readonly TRIGGER: "trigger";
12
+ readonly REGEX: "regex";
13
+ readonly DENY_LIST: "deny-list";
14
+ readonly LEGAL_FORM: "legal-form";
15
+ readonly GAZETTEER: "gazetteer";
16
+ readonly NER: "ner";
17
+ readonly COREFERENCE: "coreference";
18
+ };
19
+ type DetectionSource = (typeof DETECTION_SOURCES)[keyof typeof DETECTION_SOURCES];
20
+ /**
21
+ * Priority levels for detection sources.
22
+ * Higher = more structurally reliable. Used during
23
+ * overlap resolution so deterministic detectors beat
24
+ * probabilistic ones regardless of raw score.
25
+ */
26
+ declare const DETECTOR_PRIORITY: Record<DetectionSource, number>;
27
+ /**
28
+ * A detected PII entity span in the source text.
29
+ * Every detection layer produces these.
30
+ */
31
+ type Entity = {
32
+ start: number;
33
+ end: number;
34
+ label: string;
35
+ text: string;
36
+ score: number;
37
+ source: DetectionSource;
38
+ };
39
+ /**
40
+ * Entity after human review. Extends the base Entity
41
+ * with a review decision.
42
+ */
43
+ type ReviewDecision = "confirmed" | "rejected" | "relabeled";
44
+ type ReviewedEntity = Entity & {
45
+ decision?: ReviewDecision;
46
+ originalLabel?: string;
47
+ };
48
+ /**
49
+ * A single entry in the workspace-scoped gazetteer
50
+ * (deny list). Persisted in IndexedDB.
51
+ */
52
+ type GazetteerEntry = {
53
+ id: string;
54
+ canonical: string;
55
+ label: string;
56
+ variants: string[];
57
+ workspaceId: string;
58
+ createdAt: number;
59
+ source: "manual" | "confirmed-from-model";
60
+ };
61
+ /** Extraction strategy — closed discriminated union. */
62
+ type TriggerStrategy = {
63
+ type: "to-next-comma";
64
+ } | {
65
+ type: "to-end-of-line";
66
+ } | {
67
+ type: "n-words";
68
+ count: number;
69
+ } | {
70
+ type: "company-id-value";
71
+ } | {
72
+ type: "address";
73
+ maxChars?: number;
74
+ };
75
+ /** Validation rules — closed discriminated union. */
76
+ type TriggerValidation = {
77
+ type: "starts-uppercase";
78
+ } | {
79
+ type: "min-length";
80
+ min: number;
81
+ } | {
82
+ type: "max-length";
83
+ max: number;
84
+ } | {
85
+ type: "no-digits";
86
+ } | {
87
+ type: "has-digits";
88
+ } | {
89
+ type: "matches-pattern";
90
+ pattern: string;
91
+ flags?: string;
92
+ };
93
+ /** Auto-generated trigger variants — closed set. */
94
+ type TriggerExtension = "add-colon" | "add-trailing-space" | "add-colon-space" | "normalize-spaces";
95
+ /** V2 trigger config entry (JSON shape). */
96
+ type TriggerGroupConfig = {
97
+ id?: string;
98
+ triggers: string[];
99
+ label: string;
100
+ strategy: TriggerStrategy;
101
+ extensions?: TriggerExtension[];
102
+ validations?: TriggerValidation[];
103
+ /** When true, include the trigger text in the
104
+ * entity span (e.g., court names). */
105
+ includeTrigger?: boolean;
106
+ };
107
+ /** Compiled validation with pre-built regex. */
108
+ type CompiledValidation = {
109
+ type: "starts-uppercase";
110
+ re: RegExp;
111
+ } | {
112
+ type: "min-length";
113
+ min: number;
114
+ } | {
115
+ type: "max-length";
116
+ max: number;
117
+ } | {
118
+ type: "no-digits";
119
+ re: RegExp;
120
+ } | {
121
+ type: "has-digits";
122
+ re: RegExp;
123
+ } | {
124
+ type: "matches-pattern";
125
+ re: RegExp;
126
+ };
127
+ /**
128
+ * Runtime rule — one per trigger string after
129
+ * expansion. Fed to the Aho-Corasick automaton.
130
+ */
131
+ type TriggerRule = {
132
+ trigger: string;
133
+ label: string;
134
+ strategy: TriggerStrategy;
135
+ validations: CompiledValidation[];
136
+ includeTrigger: boolean;
137
+ };
138
+ /**
139
+ * Anonymisation operator types. Each operator defines
140
+ * how a confirmed entity is replaced in the output.
141
+ */
142
+ declare const OPERATOR_TYPES: readonly ["replace", "redact"];
143
+ type OperatorType = (typeof OPERATOR_TYPES)[number];
144
+ /** Per-label operator selection. Key is the entity label. */
145
+ type OperatorConfig = {
146
+ /** Operator per label. Missing labels default to "replace". */
147
+ operators: Record<string, OperatorType>;
148
+ /** Custom replacement string for the redact operator. */
149
+ redactString: string;
150
+ };
151
+ /** Whether an operator produces a reversible redaction entry. */
152
+ type OperatorReversibility = "reversible" | "irreversible";
153
+ type AnonymisationOperator = {
154
+ type: OperatorType;
155
+ reversibility: OperatorReversibility;
156
+ /**
157
+ * Apply the operator to a single entity occurrence.
158
+ * Returns the replacement string to embed in the document.
159
+ */
160
+ apply: (text: string, label: string, placeholder: string, redactString: string) => string;
161
+ };
162
+ /**
163
+ * Redacted document output with stable entity mapping.
164
+ */
165
+ type RedactionResult = {
166
+ redactedText: string;
167
+ /**
168
+ * Maps placeholder to original text. Only populated for
169
+ * reversible operators (replace). Empty for redact.
170
+ */
171
+ redactionMap: Map<string, string>;
172
+ /** Maps placeholder to the operator that produced it. */
173
+ operatorMap: Map<string, OperatorType>;
174
+ entityCount: number;
175
+ };
176
+ /**
177
+ * Configuration for the detection pipeline.
178
+ */
179
+ type DenyListCategory = "Names" | "Places" | "Addresses" | "Courts" | "Financial" | "Government" | "Healthcare" | "Education" | "Political" | "Organizations" | "International";
180
+ type PipelineConfig = {
181
+ threshold: number;
182
+ enableTriggerPhrases: boolean;
183
+ enableRegex: boolean;
184
+ enableNameCorpus: boolean;
185
+ enableDenyList: boolean;
186
+ denyListCountries?: string[];
187
+ denyListRegions?: string[];
188
+ denyListExcludeCategories?: string[];
189
+ enableGazetteer: boolean;
190
+ enableNer: boolean;
191
+ enableConfidenceBoost: boolean;
192
+ enableCoreference: boolean;
193
+ enableZoneClassification?: boolean;
194
+ enableHotwordRules?: boolean;
195
+ labels: string[];
196
+ workspaceId: string;
197
+ };
198
+ /**
199
+ * Canonical entity labels used across the pipeline.
200
+ * NER models may use different native labels; the bench
201
+ * NER wrapper maps model output to these canonical names.
202
+ *
203
+ * These labels are ephemeral: entities are regenerated on
204
+ * every pipeline run and never persisted to the database.
205
+ * Renaming a label here requires no migration.
206
+ */
207
+ declare const DEFAULT_ENTITY_LABELS: readonly ["person", "organization", "phone number", "address", "email address", "date", "date of birth", "bank account number", "iban", "tax identification number", "identity card number", "registration number", "credit card number", "passport number", "monetary amount"];
208
+ //#endregion
209
+ //#region src/detectors/regex.d.ts
210
+ type RegexMeta = {
211
+ label: string;
212
+ score: number;
213
+ /** Post-match stdnum validator for confirmation. */
214
+ validator?: Validator;
215
+ };
216
+ /** Flat pattern array for text-search. */
217
+ declare const REGEX_PATTERNS: readonly string[];
218
+ /** Parallel metadata. Index = pattern index. */
219
+ declare const REGEX_META: readonly RegexMeta[];
220
+ /**
221
+ * Get dynamically built date patterns from
222
+ * date-months.json. Returns a cached promise; the JSON
223
+ * is loaded only once.
224
+ */
225
+ declare const getDatePatterns: () => Promise<string[]>;
226
+ /** Date pattern metadata (all are score 1 dates). */
227
+ declare const DATE_PATTERN_META: Readonly<RegexMeta>;
228
+ /**
229
+ * Get dynamically built monetary amount patterns from
230
+ * currencies.json. Returns a cached promise; the JSON
231
+ * is loaded only once.
232
+ */
233
+ declare const getCurrencyPatterns: () => Promise<string[]>;
234
+ /** Currency pattern metadata (score 0.9). */
235
+ declare const CURRENCY_PATTERN_META: Readonly<RegexMeta>;
236
+ /**
237
+ * Process regex matches from the unified search.
238
+ * Receives all matches; filters to the regex slice
239
+ * via sliceStart/sliceEnd. Local index into META is
240
+ * match.pattern - sliceStart.
241
+ *
242
+ * For stdnum-derived patterns (those with a validator
243
+ * in META), the matched text is passed through the
244
+ * validator's validate() method. If validation fails,
245
+ * the match is discarded as a false positive.
246
+ */
247
+ declare const processRegexMatches: (allMatches: Match[], sliceStart: number, sliceEnd: number, meta_: readonly RegexMeta[]) => Entity[];
248
+ //#endregion
249
+ //#region src/detectors/deny-list.d.ts
250
+ type DenyListConfig = Pick<PipelineConfig, "enableDenyList" | "denyListCountries" | "denyListRegions" | "denyListExcludeCategories">;
251
+ /**
252
+ * Source tag for each pattern in the automaton.
253
+ * "deny-list" = standard deny list entry
254
+ * "first-name" = name corpus first name
255
+ * "surname" = name corpus surname
256
+ * "title" = academic/professional title
257
+ */
258
+ type PatternSource = "deny-list" | "first-name" | "surname" | "title";
259
+ /**
260
+ * Pre-built deny list data. Constructed once by
261
+ * `buildDenyList`, reused across `processDenyListMatches`
262
+ * calls. Contains PatternEntry[] for the unified builder
263
+ * plus parallel label/source arrays for post-processing.
264
+ */
265
+ type DenyListData = {
266
+ /**
267
+ * Maps pattern index → entity labels (plural).
268
+ * Same pattern can have multiple labels when it
269
+ * appears in multiple dictionaries (e.g., "Denver"
270
+ * is both a person name and a city name).
271
+ */
272
+ labels: string[][];
273
+ /** Maps pattern index → original pattern text. */
274
+ originals: string[];
275
+ /** Maps pattern index → source types (plural). */
276
+ sources: PatternSource[][];
277
+ };
278
+ /**
279
+ * Resolve which dictionaries to load based on country
280
+ * and category filters, load them, and build the deny
281
+ * list data. The returned data provides PatternEntry[]
282
+ * for the unified builder and parallel arrays for
283
+ * post-processing.
284
+ *
285
+ * Requires `@stll/anonymize-data` to be installed.
286
+ * Returns null if the data package is not available.
287
+ */
288
+ declare const buildDenyList: (config: DenyListConfig, ctx?: PipelineContext) => Promise<DenyListData | null>;
289
+ /**
290
+ * Ensure all deny-list support data (stopwords, allow
291
+ * list, person stopwords, generic roles) is loaded on
292
+ * the given context. Call this before
293
+ * processDenyListMatches / filterFalsePositives when
294
+ * the search instance was built on a different context
295
+ * (e.g. cachedSearch).
296
+ */
297
+ declare const ensureDenyListData: (ctx?: PipelineContext) => Promise<void>;
298
+ /**
299
+ * Process deny list matches from the unified search.
300
+ * Receives all matches; filters to the deny list slice
301
+ * via sliceStart/sliceEnd. Local index into data.labels,
302
+ * data.originals, data.sources is match.pattern - sliceStart.
303
+ *
304
+ * Two-pass approach to reduce false positives:
305
+ * 1. Collect all matches (case-insensitive,
306
+ * whole-word via Rust automaton)
307
+ * 2. Require uppercase start in source text
308
+ * 3. For person names, require at least one
309
+ * mid-sentence occurrence to prove proper noun
310
+ * 4. Return all occurrences of validated terms
311
+ */
312
+ declare const processDenyListMatches: (allMatches: Match[], sliceStart: number, sliceEnd: number, fullText: string, data: DenyListData, ctx?: PipelineContext) => Entity[];
313
+ //#endregion
314
+ //#region src/build-unified-search.d.ts
315
+ type PatternSlice = {
316
+ start: number;
317
+ end: number;
318
+ };
319
+ type GazetteerData = {
320
+ /** Maps local pattern index to entry label. */
321
+ labels: string[];
322
+ /**
323
+ * Whether each pattern is fuzzy (distance > 0).
324
+ * Used by the post-processor to assign scores.
325
+ */
326
+ isFuzzy: boolean[];
327
+ };
328
+ type UnifiedSearchInstance = {
329
+ /** Regex + triggers + legal-forms. */
330
+ tsRegex: TextSearch;
331
+ /** Deny-list + street-types + gazetteer. */
332
+ tsLiterals: TextSearch;
333
+ slices: {
334
+ regex: PatternSlice;
335
+ legalForms: PatternSlice;
336
+ triggers: PatternSlice;
337
+ denyList: PatternSlice;
338
+ streetTypes: PatternSlice;
339
+ gazetteer: PatternSlice;
340
+ };
341
+ regexMeta: readonly RegexMeta[];
342
+ triggerRules: readonly TriggerRule[];
343
+ denyListData: DenyListData | null;
344
+ gazetteerData: GazetteerData | null;
345
+ };
346
+ declare const buildUnifiedSearch: (config: PipelineConfig, gazetteerEntries?: GazetteerEntry[], ctx?: PipelineContext) => Promise<UnifiedSearchInstance>;
347
+ //#endregion
348
+ //#region src/context.d.ts
349
+ /**
350
+ * Build a stable cache key for an entity that survives
351
+ * shallow copies (spread). Uses position + label so the
352
+ * key is identical for the original object and any
353
+ * `{ ...entity }` copy produced by mergeAndDedup.
354
+ */
355
+ declare const corefKey: (e: Entity) => string;
356
+ /**
357
+ * Compiled RegExp pattern used for coreference
358
+ * definition extraction.
359
+ */
360
+ type DefinitionPattern = {
361
+ pattern: RegExp;
362
+ };
363
+ /**
364
+ * Cached data for the name corpus detector.
365
+ * Populated by initNameCorpus; consumed by
366
+ * detectNameCorpus and deny-list AC integration.
367
+ */
368
+ type NameCorpusData = {
369
+ firstNames: ReadonlySet<string>;
370
+ surnames: ReadonlySet<string>;
371
+ titleTokens: ReadonlySet<string>;
372
+ excludedWords: ReadonlySet<string>;
373
+ /** Raw arrays exposed for deny-list AC integration. */
374
+ firstNamesList: readonly string[];
375
+ surnamesList: readonly string[];
376
+ titlesList: readonly string[];
377
+ excludedList: readonly string[];
378
+ };
379
+ /**
380
+ * All cached state for a single pipeline run (or
381
+ * sequence of runs sharing the same config). Replacing
382
+ * module-level singletons with this object enables
383
+ * concurrent pipelines with different configs and
384
+ * simplifies testing.
385
+ *
386
+ * Each field starts null and is populated lazily on
387
+ * first use by the corresponding loader function.
388
+ */
389
+ type PipelineContext = {
390
+ search: UnifiedSearchInstance | null;
391
+ searchKey: string;
392
+ searchPromise: Promise<UnifiedSearchInstance> | null;
393
+ nameCorpus: NameCorpusData | null;
394
+ nameCorpusPromise: Promise<void> | null;
395
+ stopwords: ReadonlySet<string> | null;
396
+ stopwordsPromise: Promise<ReadonlySet<string>> | null;
397
+ allowList: ReadonlySet<string> | null;
398
+ allowListPromise: Promise<ReadonlySet<string>> | null;
399
+ personStopwords: ReadonlySet<string> | null;
400
+ personStopwordsPromise: Promise<ReadonlySet<string>> | null;
401
+ /** First-name exclusions for stopword filtering. */
402
+ firstNameExclusions: ReadonlySet<string> | null;
403
+ firstNameExclusionCorpusLen: number;
404
+ genericRoles: ReadonlySet<string> | null;
405
+ genericRolesPromise: Promise<ReadonlySet<string>> | null;
406
+ corefPatterns: DefinitionPattern[] | null;
407
+ corefPatternsPromise: Promise<DefinitionPattern[]> | null;
408
+ corefLoadAttempted: boolean;
409
+ roleStopSet: ReadonlySet<string> | null;
410
+ roleStopSetPromise: Promise<ReadonlySet<string>> | null;
411
+ zoneHeadingPatterns: RegExp[] | null;
412
+ zoneSigningPatterns: RegExp[] | null;
413
+ zoneInitPromise: Promise<void> | null;
414
+ /**
415
+ * Maps coreference entities to their source entity
416
+ * text. Populated by findCoreferenceSpans, consumed
417
+ * by buildPlaceholderMap for consistent placeholder
418
+ * numbering across aliases and source entities.
419
+ *
420
+ * Keyed by `start:end:label` composite string so
421
+ * lookups survive shallow copies (e.g. from
422
+ * mergeAndDedup's spread operator).
423
+ */
424
+ corefSourceMap: Map<string, string>;
425
+ };
426
+ /** Create a fresh, empty pipeline context. */
427
+ declare const createPipelineContext: () => PipelineContext;
428
+ /**
429
+ * Module-level default context. Used when callers
430
+ * don't provide an explicit context, preserving full
431
+ * backward compatibility with the existing API.
432
+ */
433
+ //#endregion
434
+ //#region src/pipeline.d.ts
435
+ /** Strip leading/trailing whitespace and punctuation. */
436
+ declare const sanitizeEntities: (entities: Entity[]) => Entity[];
437
+ declare const mergeAndDedup: (...layers: Entity[][]) => Entity[];
438
+ type NerInferenceFn = (fullText: string, labels: string[], threshold: number, signal?: AbortSignal) => Promise<Entity[]>;
439
+ /**
440
+ * Options for {@link runPipeline}.
441
+ *
442
+ * @property cachedSearch Pre-built search instance.
443
+ * When provided, `config` and `gazetteerEntries`
444
+ * are not used for building; the caller must
445
+ * ensure the instance matches both parameters.
446
+ */
447
+ type PipelineOptions = {
448
+ fullText: string;
449
+ config: PipelineConfig;
450
+ gazetteerEntries: GazetteerEntry[];
451
+ nerInference?: NerInferenceFn | null;
452
+ onProgress?: (step: string, detail: string) => void;
453
+ cachedSearch?: UnifiedSearchInstance;
454
+ signal?: AbortSignal;
455
+ context?: PipelineContext;
456
+ };
457
+ /**
458
+ * Run the full detection pipeline.
459
+ *
460
+ * Two TextSearch instances scan the text (regex +
461
+ * literals). Results are dispatched to each
462
+ * detector's post-processor by pattern index range.
463
+ *
464
+ * Pass an AbortSignal to cancel the pipeline between
465
+ * stages. Throws a DOMException with name "AbortError"
466
+ * when cancelled.
467
+ *
468
+ * Pass an optional `context` to isolate cached state
469
+ * from other pipeline runs. If omitted, a module-level
470
+ * default context is used (backward compatible).
471
+ */
472
+ declare const runPipeline: (options: PipelineOptions) => Promise<Entity[]>;
473
+ //#endregion
474
+ //#region src/redact.d.ts
475
+ /**
476
+ * Build a stable mapping from entity text to numbered
477
+ * placeholders. Same real-world value always maps to the
478
+ * same placeholder (e.g., "Dr. Muller" and "Dr. Muller"
479
+ * both become [PERSON_1]).
480
+ *
481
+ * Placeholder format: [LABEL_N] where LABEL is uppercase
482
+ * and N is a 1-based counter per label.
483
+ *
484
+ * @param ctx Pipeline context. Must be the same instance
485
+ * passed to `runPipeline` (or `findCoreferenceSpans`)
486
+ * so coreference placeholder links are preserved.
487
+ * Defaults to `defaultContext` for single-tenant usage.
488
+ */
489
+ declare const buildPlaceholderMap: (entities: Entity[], ctx?: PipelineContext) => Map<string, string>;
490
+ /**
491
+ * Apply redactions to the source text, replacing each
492
+ * confirmed entity span using the configured operator.
493
+ *
494
+ * Co-references are consistent: if the same text appears
495
+ * multiple times, all occurrences get the same placeholder.
496
+ *
497
+ * @param ctx Pipeline context. Must be the same instance
498
+ * passed to `runPipeline` (or `findCoreferenceSpans`)
499
+ * so coreference placeholder links are preserved.
500
+ * Defaults to `defaultContext` for single-tenant usage.
501
+ */
502
+ declare const redactText: (fullText: string, entities: Entity[], config?: OperatorConfig, ctx?: PipelineContext) => RedactionResult;
503
+ /**
504
+ * Serialize the redaction key to JSON for export.
505
+ * Includes operator metadata so the export is self-describing.
506
+ */
507
+ declare const exportRedactionKey: (redactionMap: Map<string, string>, operatorMap: Map<string, OperatorType>) => string;
508
+ /**
509
+ * De-anonymise text using a redaction key.
510
+ * Replaces placeholders back with original values.
511
+ * Only works for reversible operators (replace).
512
+ */
513
+ declare const deanonymise: (redactedText: string, redactionMap: Map<string, string>) => string;
514
+ //#endregion
515
+ //#region src/operators.d.ts
516
+ declare const OPERATOR_REGISTRY: {
517
+ readonly replace: AnonymisationOperator;
518
+ readonly redact: AnonymisationOperator;
519
+ };
520
+ /**
521
+ * Default operator config: replace for all labels.
522
+ * Preserves existing pipeline behaviour.
523
+ */
524
+ declare const DEFAULT_OPERATOR_CONFIG: OperatorConfig;
525
+ /**
526
+ * Resolve the operator for a label, falling back to "replace".
527
+ */
528
+ declare const resolveOperator: (config: OperatorConfig, label: string) => OperatorType;
529
+ //#endregion
530
+ //#region src/detectors/legal-forms.d.ts
531
+ /**
532
+ * Build legal form regex pattern strings.
533
+ * Returns an array of regex strings for the unified
534
+ * TextSearch builder. Empty if data package is not
535
+ * installed.
536
+ */
537
+ declare const buildLegalFormPatterns: () => Promise<string[]>;
538
+ /**
539
+ * Process legal form matches from the unified search.
540
+ * Receives all matches; filters to the legal forms
541
+ * slice via sliceStart/sliceEnd.
542
+ */
543
+ declare const processLegalFormMatches: (allMatches: Match[], sliceStart: number, sliceEnd: number, fullText?: string) => Entity[];
544
+ //#endregion
545
+ //#region src/detectors/triggers.d.ts
546
+ /**
547
+ * Build trigger patterns and rules from data configs.
548
+ * Returns string[] for the unified TextSearch
549
+ * builder and the parallel rules array.
550
+ */
551
+ declare const buildTriggerPatterns: () => Promise<{
552
+ patterns: string[];
553
+ rules: TriggerRule[];
554
+ }>;
555
+ /**
556
+ * Process trigger matches from the unified search.
557
+ * Receives all matches; filters to the trigger slice
558
+ * via sliceStart/sliceEnd. Uses fullText for value
559
+ * extraction (the unified search runs on lowercased
560
+ * text, but extraction needs original casing).
561
+ */
562
+ declare const processTriggerMatches: (allMatches: Match[], sliceStart: number, sliceEnd: number, fullText: string, rules: readonly TriggerRule[]) => Entity[];
563
+ //#endregion
564
+ //#region src/detectors/address-seeds.d.ts
565
+ /**
566
+ * Build street type patterns for the unified search.
567
+ * Returns string[] for the unified TextSearch
568
+ * builder. Empty if data package is not installed.
569
+ */
570
+ declare const buildStreetTypePatterns: () => Promise<string[]>;
571
+ /**
572
+ * Process address seeds from the unified search.
573
+ * Receives all matches; filters to the street types
574
+ * slice via sliceStart/sliceEnd. Uses fullText and
575
+ * existingEntities for seed collection, clustering,
576
+ * expansion, and scoring.
577
+ *
578
+ * Runs as a post-processor after all other detectors,
579
+ * using their output as seed sources.
580
+ */
581
+ declare const processAddressSeeds: (allMatches: Match[], sliceStart: number, sliceEnd: number, fullText: string, existingEntities: Entity[]) => Promise<Entity[]>;
582
+ //#endregion
583
+ //#region src/detectors/gazetteer.d.ts
584
+ /**
585
+ * Build TextSearch-compatible patterns from gazetteer
586
+ * entries. Returns:
587
+ * - Exact literal patterns for all terms
588
+ * - Fuzzy patterns (distance: 2) for terms >= 4 chars
589
+ * - Parallel metadata arrays for post-processing
590
+ *
591
+ * Patterns are ordered: all exact first, then all
592
+ * fuzzy. The isFuzzy array marks which are which.
593
+ */
594
+ declare const buildGazetteerPatterns: (entries: GazetteerEntry[]) => {
595
+ patterns: PatternEntry[];
596
+ data: GazetteerData;
597
+ };
598
+ /**
599
+ * Process gazetteer matches from the unified literal
600
+ * search. Receives all matches; filters to the
601
+ * gazetteer slice via sliceStart/sliceEnd.
602
+ *
603
+ * Exact matches get score 0.9; fuzzy matches get
604
+ * 0.85. Fuzzy matches that overlap an exact match
605
+ * are dropped.
606
+ *
607
+ * For exact matches, attempts prefix extension for
608
+ * legal suffixes ("a.s.", "GmbH", "s.r.o." after
609
+ * the matched term).
610
+ */
611
+ declare const processGazetteerMatches: (allMatches: Match[], sliceStart: number, sliceEnd: number, fullText: string, data: GazetteerData) => Entity[];
612
+ //#endregion
613
+ //#region src/detectors/coreference.d.ts
614
+ type DefinedTerm = {
615
+ alias: string;
616
+ label: string;
617
+ /** Position of the definition in the source text */
618
+ definitionStart: number;
619
+ /** Original entity text the alias refers to */
620
+ sourceText: string;
621
+ };
622
+ declare const extractDefinedTerms: (fullText: string, entities: Entity[], ctx?: PipelineContext) => Promise<DefinedTerm[]>;
623
+ /**
624
+ * Find all occurrences of defined-term aliases in the
625
+ * full text. Returns Entity spans for each match.
626
+ *
627
+ * Respects word boundaries: "Kupující" must not match
628
+ * inside "Kupujícímu". A match is valid only if the
629
+ * character before the start and after the end are NOT
630
+ * word characters (letter/digit).
631
+ *
632
+ * Populates `ctx.corefSourceMap` with entries linking
633
+ * each coref entity to its source entity text, for
634
+ * consistent placeholder numbering.
635
+ */
636
+ declare const findCoreferenceSpans: (fullText: string, terms: DefinedTerm[], ctx?: PipelineContext) => Entity[];
637
+ //#endregion
638
+ //#region src/detectors/org-propagation.d.ts
639
+ /**
640
+ * After the main detection pass, collect organization
641
+ * entities with a legal form suffix, strip the suffix
642
+ * to get the base name, and re-scan the full text for
643
+ * bare mentions of that base name. Returns new entities
644
+ * for occurrences not already covered.
645
+ */
646
+ declare const propagateOrgNames: (entities: Entity[], fullText: string) => Entity[];
647
+ //#endregion
648
+ //#region src/detectors/names.d.ts
649
+ /**
650
+ * Load name corpus data from JSON config files.
651
+ * Safe to call multiple times; only loads once per
652
+ * context. Must be called before detectNameCorpus or
653
+ * the getNameCorpus*() accessors are used.
654
+ */
655
+ declare const initNameCorpus: (ctx?: PipelineContext) => Promise<void>;
656
+ /**
657
+ * Detect person names by looking up tokens against the
658
+ * name corpus, then chaining adjacent name-like tokens.
659
+ *
660
+ * Requires initNameCorpus() to have been called first.
661
+ * If not initialized, returns an empty array.
662
+ *
663
+ * Scoring:
664
+ * TITLE + NAME/SURNAME → 0.95
665
+ * NAME + NAME/SURNAME → 0.9
666
+ * SURNAME + NAME/SURNAME → 0.9
667
+ * NAME + CAPITALIZED → 0.7
668
+ * ABBREVIATION + NAME → 0.7
669
+ * Standalone NAME → 0.5 (low confidence)
670
+ * Standalone SURNAME → skip (too ambiguous)
671
+ */
672
+ declare const detectNameCorpus: (fullText: string, ctx?: PipelineContext) => Entity[];
673
+ //#endregion
674
+ //#region src/unified-search.d.ts
675
+ type UnifiedResult = {
676
+ /** All matches from both instances combined. */
677
+ regexMatches: Match[];
678
+ literalMatches: Match[];
679
+ };
680
+ declare const runUnifiedSearch: (instance: UnifiedSearchInstance, fullText: string) => UnifiedResult;
681
+ //#endregion
682
+ //#region src/regions.d.ts
683
+ /**
684
+ * Geographic regions and country code mappings for
685
+ * scoping deny list dictionaries.
686
+ */
687
+ declare const REGIONS: {
688
+ readonly Global: null;
689
+ readonly International: null;
690
+ readonly Europe: readonly ["AL", "AD", "AT", "BE", "BA", "BG", "HR", "CY", "CZ", "DK", "EE", "FI", "FR", "DE", "GR", "HU", "IS", "IE", "IT", "XK", "LV", "LI", "LT", "LU", "MD", "ME", "MK", "MT", "MC", "NL", "NO", "PL", "PT", "RO", "RS", "SK", "SI", "ES", "SE", "CH", "UA", "GB"];
691
+ readonly Americas: readonly ["US", "CA", "MX", "BR", "AR", "CL", "CO", "PE", "EC", "VE", "UY", "PY", "BO", "CR", "PA", "DO", "GT", "HN", "SV", "NI", "CU"];
692
+ readonly AsiaPacific: readonly ["AU", "NZ", "JP", "KR", "CN", "TW", "SG", "MY", "TH", "VN", "PH", "ID", "IN", "PK", "BD", "LK", "NP", "HK", "MO"];
693
+ readonly MENA: readonly ["AE", "SA", "IL", "TR", "EG", "JO", "LB", "IQ", "IR", "QA", "KW", "BH", "OM", "MA", "TN", "DZ", "LY", "SY", "YE", "PS"];
694
+ readonly SubSaharanAfrica: readonly ["ZA", "NG", "KE", "GH", "TZ", "ET", "SN", "CI", "CM", "UG", "RW", "MZ", "AO", "ZW", "BW", "NA", "MU"];
695
+ readonly EU: readonly ["AT", "BE", "BG", "HR", "CY", "CZ", "DK", "EE", "FI", "FR", "DE", "GR", "HU", "IE", "IT", "LV", "LT", "LU", "MT", "NL", "PL", "PT", "RO", "SK", "SI", "ES", "SE"];
696
+ readonly DACH: readonly ["DE", "AT", "CH"];
697
+ readonly Nordics: readonly ["DK", "SE", "NO", "FI", "IS"];
698
+ readonly CEE: readonly ["CZ", "SK", "PL", "HU", "RO", "BG", "HR", "SI", "LT", "LV", "EE"];
699
+ readonly Anglosphere: readonly ["GB", "US", "CA", "AU", "NZ", "IE"];
700
+ readonly Benelux: readonly ["BE", "NL", "LU"];
701
+ readonly GulfStates: readonly ["AE", "SA", "QA", "KW", "BH", "OM"];
702
+ readonly SouthAsia: readonly ["IN", "PK", "BD", "LK", "NP"];
703
+ readonly EastAsia: readonly ["CN", "JP", "KR", "TW"];
704
+ readonly SoutheastAsia: readonly ["SG", "MY", "TH", "VN", "PH", "ID"];
705
+ readonly Oceania: readonly ["AU", "NZ"];
706
+ };
707
+ type RegionId = keyof typeof REGIONS;
708
+ type RegionArrays = { [K in RegionId]: (typeof REGIONS)[K] };
709
+ type NonNullRegion = { [K in RegionId as RegionArrays[K] extends null ? never : K]: RegionArrays[K] };
710
+ type CountryCode = NonNullRegion[keyof NonNullRegion][number];
711
+ /**
712
+ * Expand region names to country codes and merge with
713
+ * explicit country codes. Returns null when both inputs
714
+ * are empty/undefined (meaning "match all countries").
715
+ */
716
+ declare const resolveCountries: (regions?: string[], countries?: string[]) => Set<string> | null;
717
+ //#endregion
718
+ //#region src/filters/false-positives.d.ts
719
+ /**
720
+ * Filter out entities that are likely false positives:
721
+ * template placeholders, clause/section numbers,
722
+ * standalone years, and generic legal role terms.
723
+ *
724
+ * Runs as a post-processing step after all detection
725
+ * layers have merged.
726
+ */
727
+ declare const filterFalsePositives: (entities: Entity[], ctx?: PipelineContext) => Entity[];
728
+ //#endregion
729
+ //#region src/filters/confidence-boost.d.ts
730
+ /**
731
+ * Boost confidence of near-miss NER entities that appear
732
+ * near high-confidence detections (regex, trigger phrase).
733
+ *
734
+ * If an NER entity scored between (threshold - 0.15) and
735
+ * threshold, count how many confirmed entities exist within
736
+ * a 150-char window. Add +0.05 per co-located entity.
737
+ * If the boosted score crosses the threshold, include it.
738
+ *
739
+ * Only mutates score on near-miss entities; high-confidence
740
+ * entities pass through unchanged.
741
+ */
742
+ declare const boostNearMissEntities: (entities: Entity[], threshold: number) => Entity[];
743
+ /** Ensure preposition data is loaded. */
744
+ //#endregion
745
+ //#region src/filters/hotword-rules.d.ts
746
+ type HotwordRule = {
747
+ hotwords: string[];
748
+ targetLabels: string[];
749
+ scoreAdjustment: number;
750
+ reclassifyTo?: string;
751
+ proximityBefore: number;
752
+ proximityAfter: number;
753
+ };
754
+ /**
755
+ * Load hotword rules from the data package.
756
+ * Safe to call multiple times; subsequent calls
757
+ * are no-ops.
758
+ */
759
+ declare const initHotwordRules: () => Promise<void>;
760
+ /**
761
+ * Apply hotword context rules to detected entities.
762
+ *
763
+ * Scans `fullText` once with a single AC automaton
764
+ * for all hotwords across all rules, then checks
765
+ * proximity to each entity. Distance-decayed
766
+ * adjustment: closer hotwords give a stronger boost.
767
+ *
768
+ * Returns a new array; input entities are not mutated.
769
+ */
770
+ declare const applyHotwordRules: (entities: Entity[], fullText: string) => Entity[];
771
+ //#endregion
772
+ //#region src/filters/zone-classifier.d.ts
773
+ type DocumentZone = "header" | "signature" | "body" | "table";
774
+ type ZoneSpan = {
775
+ zone: DocumentZone;
776
+ start: number;
777
+ end: number;
778
+ };
779
+ /**
780
+ * Additive score adjustments per document zone.
781
+ * Header and signature blocks are dense with PII;
782
+ * tables often contain structured identifying data.
783
+ */
784
+ declare const ZONE_SCORE_ADJUSTMENTS: {
785
+ readonly header: 0.1;
786
+ readonly signature: 0.15;
787
+ readonly body: 0;
788
+ readonly table: 0.05;
789
+ };
790
+ /**
791
+ * Ensure config data is loaded. Call once before
792
+ * classifyZones. Safe to call multiple times.
793
+ */
794
+ declare const initZoneClassifier: (ctx?: PipelineContext) => Promise<void>;
795
+ /**
796
+ * Classify a document into zones based on
797
+ * structural heuristics. Zones are non-overlapping
798
+ * and cover the entire text.
799
+ *
800
+ * Must call `initZoneClassifier()` first.
801
+ */
802
+ declare const classifyZones: (fullText: string, ctx?: PipelineContext) => ZoneSpan[];
803
+ /**
804
+ * Apply zone-based score adjustments to entities.
805
+ * Entities in header/signature/table zones get a
806
+ * small additive boost reflecting the higher PII
807
+ * density in those regions.
808
+ *
809
+ * Returns a new array; does not mutate inputs.
810
+ */
811
+ declare const applyZoneAdjustments: (entities: Entity[], zones: ZoneSpan[]) => Entity[];
812
+ //#endregion
813
+ //#region src/gliner/types.d.ts
814
+ /**
815
+ * GLiNER inference types.
816
+ *
817
+ * Forked from gliner@0.0.19 (MIT), stripped to runtime-
818
+ * agnostic core. Original: github.com/Ingvarstep/GLiNER.js
819
+ */
820
+ type EntityResult = {
821
+ spanText: string;
822
+ start: number;
823
+ end: number;
824
+ label: string;
825
+ score: number;
826
+ };
827
+ /**
828
+ * Raw inference output: per-batch array of
829
+ * [spanText, start, end, label, score] tuples.
830
+ */
831
+ type RawInferenceResult = [string, number, number, string, number][][];
832
+ //#endregion
833
+ //#region src/gliner/decoder.d.ts
834
+ /**
835
+ * Decode span-level model logits into entity results.
836
+ */
837
+ declare const decodeSpans: (batchSize: number, inputLength: number, maxWidth: number, numEntities: number, texts: string[], batchIds: number[], batchWordsStartIdx: number[][], batchWordsEndIdx: number[][], idToClass: Record<number, string>, modelOutput: ArrayLike<number>, flatNer: boolean, threshold: number, multiLabel: boolean) => RawInferenceResult;
838
+ //#endregion
839
+ //#region src/gliner/token-decoder.d.ts
840
+ /**
841
+ * Decode token-level BIO logits into entity spans.
842
+ *
843
+ * For each word, checks if the B(egin) logit for any class
844
+ * exceeds the threshold. If so, extends the span by consuming
845
+ * subsequent I(nside) tokens of the same class.
846
+ */
847
+ declare const decodeTokenSpans: (batchSize: number, numWords: number, numEntities: number, texts: string[], batchIds: number[], batchWordsStartIdx: number[][], batchWordsEndIdx: number[][], idToClass: Record<number, string>, modelOutput: ArrayLike<number>, threshold: number) => RawInferenceResult;
848
+ //#endregion
849
+ //#region src/gliner/processor.d.ts
850
+ /** Tokenize text into words with character offsets. */
851
+ declare const tokenizeText: (text: string) => [words: string[], starts: number[], ends: number[]];
852
+ /** Pad a 2D or 3D array to uniform inner length. */
853
+
854
+ /** Prepare a complete batch for ONNX inference. */
855
+ declare const prepareBatch: (tokenizer: Tokenizer, texts: string[], entities: string[], maxWidth: number) => {
856
+ inputsIds: number[][];
857
+ attentionMasks: number[][];
858
+ wordsMasks: number[][];
859
+ textLengths: number[];
860
+ spanIdxs: number[][][];
861
+ spanMasks: boolean[][];
862
+ idToClass: Record<number, string>;
863
+ batchTokens: string[][];
864
+ batchWordsStartIdx: number[][];
865
+ batchWordsEndIdx: number[][];
866
+ };
867
+ //#endregion
868
+ //#region src/util/chunker.d.ts
869
+ /**
870
+ * Split text into overlapping chunks for GLiNER's
871
+ * ~512 token context window. Character-based splitting
872
+ * (rough approximation of token limits).
873
+ *
874
+ * Tries to break at sentence boundaries when possible.
875
+ */
876
+ declare const chunkText: (text: string) => string[];
877
+ /**
878
+ * Compute the byte offset of each chunk within the
879
+ * original document text.
880
+ */
881
+ declare const computeChunkOffsets: (fullText: string, chunks: string[]) => number[];
882
+ /**
883
+ * Merge entities from overlapping chunks back to
884
+ * document-level offsets. Deduplicates entities that
885
+ * appear in overlap regions (keeps highest score).
886
+ *
887
+ * Dedup invariant: each incoming entity is compared
888
+ * against the highest-scored same-label near-dup in
889
+ * its proximity window. If it loses, it is dropped.
890
+ * This does NOT guarantee that all pairwise near-dup
891
+ * relationships in the output are resolved; a lower-
892
+ * scored entity can survive if the bridging entity
893
+ * that would have replaced it was itself dropped by
894
+ * a higher-scored match.
895
+ *
896
+ * Uses a reverse-scan over the sorted merged array
897
+ * so each entity only compares against nearby
898
+ * predecessors — O(n * w) average where w is the max
899
+ * entities per POSITION_THRESHOLD window, O(n²) worst
900
+ * case when replacements dominate (splice is O(n)).
901
+ */
902
+ declare const mergeChunkEntities: (chunkOffsets: number[], chunkResults: Entity[][]) => Entity[];
903
+ //#endregion
904
+ //#region src/util/levenshtein.d.ts
905
+ /**
906
+ * Compute the Levenshtein edit distance between two
907
+ * strings. O(n*m) time, O(min(n,m)) space using a
908
+ * single-row DP approach.
909
+ */
910
+ declare const levenshtein: (rawA: string, rawB: string) => number;
911
+ //#endregion
912
+ //#region src/util/normalize.d.ts
913
+ /**
914
+ * Normalize typographic variants for search matching.
915
+ *
916
+ * Legal documents (especially Czech/German) use
917
+ * non-breaking spaces, smart quotes, and en/em dashes
918
+ * that differ from their ASCII equivalents. Since all
919
+ * replacements are same-length (single code unit →
920
+ * single code unit), character offsets remain valid.
921
+ *
922
+ * Lives here (application layer) rather than in the
923
+ * AC library: what to normalize is domain-specific.
924
+ *
925
+ * Uses a char-code lookup (`Map<number, number>`) and
926
+ * `Uint16Array` instead of 7 sequential `replaceAll`
927
+ * calls. For a 50 KB document this eliminates ~350 KB
928
+ * of intermediate string allocations.
929
+ *
930
+ * When no replaceable characters are present (common
931
+ * for plain-text inputs), a fast-path scan returns the
932
+ * original string without any allocation. When special
933
+ * characters exist, the string is scanned twice: once
934
+ * to detect, once to build the replacement array.
935
+ */
936
+ declare const normalizeForSearch: (text: string) => string;
937
+ //#endregion
938
+ export { type AnonymisationOperator, CURRENCY_PATTERN_META, type CountryCode, DATE_PATTERN_META, DEFAULT_ENTITY_LABELS, DEFAULT_OPERATOR_CONFIG, DETECTION_SOURCES, DETECTOR_PRIORITY, type DefinitionPattern, type DenyListCategory, type DenyListData, type DetectionSource, type DocumentZone, type Entity, type EntityResult, type GazetteerData, type GazetteerEntry, type HotwordRule, type NameCorpusData, type NerInferenceFn, OPERATOR_REGISTRY, OPERATOR_TYPES, type OperatorConfig, type OperatorType, type PipelineConfig, type PipelineContext, type PipelineOptions, REGEX_META, REGEX_PATTERNS, REGIONS, type RawInferenceResult, type RedactionResult, type RegexMeta, type RegionId, type ReviewDecision, type ReviewedEntity, type TriggerExtension, type TriggerGroupConfig, type TriggerRule, type TriggerStrategy, type TriggerValidation, type UnifiedResult, type UnifiedSearchInstance, ZONE_SCORE_ADJUSTMENTS, type ZoneSpan, applyHotwordRules, applyZoneAdjustments, boostNearMissEntities, buildDenyList, buildGazetteerPatterns, buildLegalFormPatterns, buildPlaceholderMap, buildStreetTypePatterns, buildTriggerPatterns, buildUnifiedSearch, chunkText, classifyZones, computeChunkOffsets, corefKey, createPipelineContext, deanonymise, decodeSpans, decodeTokenSpans, detectNameCorpus, ensureDenyListData, exportRedactionKey, extractDefinedTerms, filterFalsePositives, findCoreferenceSpans, getCurrencyPatterns, getDatePatterns, initHotwordRules, initNameCorpus, initZoneClassifier, levenshtein, mergeAndDedup, mergeChunkEntities, normalizeForSearch, prepareBatch, processAddressSeeds, processDenyListMatches, processGazetteerMatches, processLegalFormMatches, processRegexMatches, processTriggerMatches, propagateOrgNames, redactText, resolveCountries, resolveOperator, runPipeline, runUnifiedSearch, sanitizeEntities, tokenizeText };
939
+ //# sourceMappingURL=index.d.ts.map