swedish-pii 1.2.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.
@@ -0,0 +1,163 @@
1
+ /** All PII labels this library can detect. */
2
+ type PiiLabel = "PER_FIRST" | "PER_LAST" | "AMEX_CREDIT_CARD" | "MASTERCARD_CREDIT_CARD" | "VISA_CREDIT_CARD" | "IBAN_CODE" | "BIC_CODE" | "SE_BANK_NUMBER" | "SE_BANKGIRO" | "SE_PLUSGIRO" | "SE_VAT_NUMBER" | "CRYPTO_WALLET" | "SE_PERSONAL_IDENTITY_NUMBER_MALE" | "SE_PERSONAL_IDENTITY_NUMBER_FEMALE" | "SE_COORDINATION_NUMBER_MALE" | "SE_COORDINATION_NUMBER_FEMALE" | "SE_PASSPORT_NUMBER" | "EMAIL_ADDRESS" | "PHONE_NUMBER" | "SOCIAL_MEDIA" | "SE_STREET_ADDRESS" | "SE_POSTAL_CODE" | "SE_MUNICIPALITY" | "SE_COUNTY" | "SE_CITY" | "SE_PROPERTY_DESIGNATION" | "COORDINATE" | "SE_WORK_ORGANIZATION" | "SE_EDUCATION_ORGANIZATION" | "SE_EDUCATION_PROGRAM" | "SE_WORK_PROFESSION" | "SE_ORGANIZATION_NUMBER" | "MARITAL_STATUS" | "GENETIC_SEX" | "DISABILITY" | "RELIGION" | "SEXUAL_ORIENTATION" | "DEMOGRAPHIC" | "POLITICAL_IDEOLOGIES" | "SE_LABOR_UNION" | "SE_LICENSE_PLATE" | "IP_ADDRESS" | "MAC_ADDRESS" | "DATE" | "TIME" | "SE_CASE_NUMBER" | "AGE";
3
+ /** A single detected PII entity, anchored to the original text. */
4
+ interface PiiEntity {
5
+ /** Unique id within one detection run, e.g. `PHONE_NUMBER_1`. */
6
+ id: string;
7
+ label: PiiLabel;
8
+ /** The matched text exactly as it appears in the input. */
9
+ value: string;
10
+ /** Start offset (inclusive) in the original text. */
11
+ start: number;
12
+ /** End offset (exclusive) in the original text. */
13
+ end: number;
14
+ /**
15
+ * Confidence in (0, 1]. Checksum-validated matches score ~0.95,
16
+ * exact gazetteer hits ~0.85–0.9, plain pattern shapes ~0.6, matches
17
+ * that failed a checksum ~0.45, and context-starved shapes ~0.25
18
+ * (below the default threshold).
19
+ */
20
+ score: number;
21
+ }
22
+ /** An entity candidate produced by a detector, before ids are assigned. */
23
+ type EntitySpan = Omit<PiiEntity, "id">;
24
+ interface DetectOptions {
25
+ /**
26
+ * When true, matches that carry a checksum (credit cards, personal
27
+ * identity numbers, organization numbers, IBANs) must pass validation,
28
+ * and date-based numbers must encode a real calendar date.
29
+ * Defaults to false so that synthetic/example data is still masked.
30
+ */
31
+ strict?: boolean;
32
+ /**
33
+ * Entities scoring below this are dropped. Defaults to
34
+ * DEFAULT_SCORE_THRESHOLD (0.4): checksum failures (0.45) survive,
35
+ * context-starved shapes (0.25) do not. Lower it to catch e.g. bank
36
+ * account numbers in bare CSV columns with no surrounding words;
37
+ * raise it to keep only high-confidence entities.
38
+ * Must be a finite number from 0 through 1; other values throw a
39
+ * RangeError.
40
+ */
41
+ scoreThreshold?: number;
42
+ }
43
+ /** A detector produces entity spans for one or more labels. */
44
+ interface Detector {
45
+ /** The label(s) this detector can emit — used for documentation/tooling. */
46
+ labels: readonly PiiLabel[];
47
+ detect(text: string, options: Required<DetectOptions>): EntitySpan[];
48
+ }
49
+ interface MaskedValue {
50
+ id: string;
51
+ value: string;
52
+ }
53
+ /** Detected values grouped per label, in order of appearance. */
54
+ type MaskedData = Partial<Record<PiiLabel, MaskedValue[]>>;
55
+ interface MaskResult {
56
+ /** Input text with every detected entity replaced by `<LABEL_n>`. */
57
+ maskedText: string;
58
+ /** Detected values grouped per label. */
59
+ maskedData: MaskedData;
60
+ /** Flat list of detected entities with offsets into the original text. */
61
+ entities: PiiEntity[];
62
+ }
63
+
64
+ /**
65
+ * Detectors in priority order: when two detectors match overlapping
66
+ * text, the one listed first wins. Names run first, then patterns from
67
+ * most to least specific — in particular, the generic Swedish bank
68
+ * account number (10–11 digits) runs after personnummer, organization
69
+ * numbers, cards and phone numbers, all of which it would otherwise
70
+ * shadow.
71
+ */
72
+ declare const detectors: Detector[];
73
+ /**
74
+ * Run all detectors in priority order. Each detector sees the input
75
+ * with higher-priority matches blanked out (never with placeholder text,
76
+ * so placeholders can't be re-matched or corrupted). Blanking — rather
77
+ * than dropping overlapping candidates outright — lets a lower-priority
78
+ * detector still match the uncovered remainder: in "Lilla Vägen 7",
79
+ * where "Lilla" is a registered first name, the street detector can
80
+ * still mask "Vägen 7".
81
+ *
82
+ * Every candidate carries a confidence score; candidates below
83
+ * `scoreThreshold` are discarded before overlap resolution.
84
+ */
85
+ declare function detectPII(text: string, options?: DetectOptions): PiiEntity[];
86
+ /**
87
+ * Mask every detected entity in `text`, replacing it with `<LABEL_n>`.
88
+ * The returned `maskedData` maps each label to its values in order of
89
+ * appearance, and `entities` carries exact offsets into the input.
90
+ */
91
+ declare function maskPII(text: string, options?: DetectOptions): MaskResult;
92
+
93
+ /**
94
+ * Match a "First Last" string against the name lists. Returns the
95
+ * canonical matched names, or null when either part misses.
96
+ */
97
+ declare function matchFullName(fullName: string): {
98
+ first: string;
99
+ last: string;
100
+ } | null;
101
+
102
+ /**
103
+ * Luhn (mod 10) checksum, used by payment card numbers, Swedish personal
104
+ * identity numbers (personnummer), coordination numbers and organization
105
+ * numbers.
106
+ */
107
+ declare function luhnCheck(digits: string): boolean;
108
+ /**
109
+ * Validate the checksum of a Swedish personnummer / samordningsnummer /
110
+ * organisationsnummer. The Luhn sum is always computed over the final
111
+ * 10 digits (century digits are excluded).
112
+ */
113
+ declare function swedishIdChecksum(value: string): boolean;
114
+
115
+ /**
116
+ * IBAN mod-97 checksum (ISO 13616): move the first four characters to
117
+ * the end, convert letters to numbers (A=10 … Z=35), and the whole
118
+ * number must be ≡ 1 (mod 97). Computed digit-by-digit to stay within
119
+ * safe integer range regardless of IBAN length.
120
+ */
121
+ declare function ibanChecksum(iban: string): boolean;
122
+
123
+ /**
124
+ * True when year/month/day form a real calendar date (leap years included).
125
+ * Month and day are 1-based.
126
+ */
127
+ declare function isRealDate(year: number, month: number, day: number): boolean;
128
+ /**
129
+ * Validate the date part of a Swedish identity number.
130
+ *
131
+ * @param digits - The identity number's digits (10 or 12, separators removed).
132
+ * @param dayOffset - 60 for samordningsnummer (day is stored as day + 60).
133
+ */
134
+ declare function isValidIdentityDate(digits: string, dayOffset?: number): boolean;
135
+
136
+ declare function jaroWinkler(s1: string, s2: string): number;
137
+
138
+ /**
139
+ * Confidence levels, Presidio-style: recognizers report how sure they
140
+ * are, callers filter with `scoreThreshold`.
141
+ */
142
+ declare const SCORE: {
143
+ /** Checksum (Luhn / mod-97) or calendar validation passed. */
144
+ readonly VALIDATED: 0.95;
145
+ /** Exact hit in a curated gazetteer (streets, municipalities). */
146
+ readonly EXACT_MATCH: 0.9;
147
+ /** Fuzzy full-name match or context-corroborated shape. */
148
+ readonly CONTEXT: 0.85;
149
+ /** Exact single-word lookup in a large list (names). */
150
+ readonly LOOKUP: 0.55;
151
+ /** A plain pattern/term match with no further evidence. */
152
+ readonly PATTERN: 0.6;
153
+ /** Heuristic shape (capitalized words + street suffix). */
154
+ readonly HEURISTIC: 0.5;
155
+ /** Shape matched but its checksum failed. */
156
+ readonly FAILED_VALIDATION: 0.45;
157
+ /** Shape matched but the expected nearby context is missing. */
158
+ readonly NO_CONTEXT: 0.25;
159
+ };
160
+ /** Entities scoring below this are dropped unless the caller opts in. */
161
+ declare const DEFAULT_SCORE_THRESHOLD = 0.4;
162
+
163
+ export { DEFAULT_SCORE_THRESHOLD, type DetectOptions, type Detector, type EntitySpan, type MaskResult, type MaskedData, type MaskedValue, type PiiEntity, type PiiLabel, SCORE, detectPII, detectors, ibanChecksum, isRealDate, isValidIdentityDate, jaroWinkler, luhnCheck, maskPII, matchFullName, swedishIdChecksum };
@@ -0,0 +1,163 @@
1
+ /** All PII labels this library can detect. */
2
+ type PiiLabel = "PER_FIRST" | "PER_LAST" | "AMEX_CREDIT_CARD" | "MASTERCARD_CREDIT_CARD" | "VISA_CREDIT_CARD" | "IBAN_CODE" | "BIC_CODE" | "SE_BANK_NUMBER" | "SE_BANKGIRO" | "SE_PLUSGIRO" | "SE_VAT_NUMBER" | "CRYPTO_WALLET" | "SE_PERSONAL_IDENTITY_NUMBER_MALE" | "SE_PERSONAL_IDENTITY_NUMBER_FEMALE" | "SE_COORDINATION_NUMBER_MALE" | "SE_COORDINATION_NUMBER_FEMALE" | "SE_PASSPORT_NUMBER" | "EMAIL_ADDRESS" | "PHONE_NUMBER" | "SOCIAL_MEDIA" | "SE_STREET_ADDRESS" | "SE_POSTAL_CODE" | "SE_MUNICIPALITY" | "SE_COUNTY" | "SE_CITY" | "SE_PROPERTY_DESIGNATION" | "COORDINATE" | "SE_WORK_ORGANIZATION" | "SE_EDUCATION_ORGANIZATION" | "SE_EDUCATION_PROGRAM" | "SE_WORK_PROFESSION" | "SE_ORGANIZATION_NUMBER" | "MARITAL_STATUS" | "GENETIC_SEX" | "DISABILITY" | "RELIGION" | "SEXUAL_ORIENTATION" | "DEMOGRAPHIC" | "POLITICAL_IDEOLOGIES" | "SE_LABOR_UNION" | "SE_LICENSE_PLATE" | "IP_ADDRESS" | "MAC_ADDRESS" | "DATE" | "TIME" | "SE_CASE_NUMBER" | "AGE";
3
+ /** A single detected PII entity, anchored to the original text. */
4
+ interface PiiEntity {
5
+ /** Unique id within one detection run, e.g. `PHONE_NUMBER_1`. */
6
+ id: string;
7
+ label: PiiLabel;
8
+ /** The matched text exactly as it appears in the input. */
9
+ value: string;
10
+ /** Start offset (inclusive) in the original text. */
11
+ start: number;
12
+ /** End offset (exclusive) in the original text. */
13
+ end: number;
14
+ /**
15
+ * Confidence in (0, 1]. Checksum-validated matches score ~0.95,
16
+ * exact gazetteer hits ~0.85–0.9, plain pattern shapes ~0.6, matches
17
+ * that failed a checksum ~0.45, and context-starved shapes ~0.25
18
+ * (below the default threshold).
19
+ */
20
+ score: number;
21
+ }
22
+ /** An entity candidate produced by a detector, before ids are assigned. */
23
+ type EntitySpan = Omit<PiiEntity, "id">;
24
+ interface DetectOptions {
25
+ /**
26
+ * When true, matches that carry a checksum (credit cards, personal
27
+ * identity numbers, organization numbers, IBANs) must pass validation,
28
+ * and date-based numbers must encode a real calendar date.
29
+ * Defaults to false so that synthetic/example data is still masked.
30
+ */
31
+ strict?: boolean;
32
+ /**
33
+ * Entities scoring below this are dropped. Defaults to
34
+ * DEFAULT_SCORE_THRESHOLD (0.4): checksum failures (0.45) survive,
35
+ * context-starved shapes (0.25) do not. Lower it to catch e.g. bank
36
+ * account numbers in bare CSV columns with no surrounding words;
37
+ * raise it to keep only high-confidence entities.
38
+ * Must be a finite number from 0 through 1; other values throw a
39
+ * RangeError.
40
+ */
41
+ scoreThreshold?: number;
42
+ }
43
+ /** A detector produces entity spans for one or more labels. */
44
+ interface Detector {
45
+ /** The label(s) this detector can emit — used for documentation/tooling. */
46
+ labels: readonly PiiLabel[];
47
+ detect(text: string, options: Required<DetectOptions>): EntitySpan[];
48
+ }
49
+ interface MaskedValue {
50
+ id: string;
51
+ value: string;
52
+ }
53
+ /** Detected values grouped per label, in order of appearance. */
54
+ type MaskedData = Partial<Record<PiiLabel, MaskedValue[]>>;
55
+ interface MaskResult {
56
+ /** Input text with every detected entity replaced by `<LABEL_n>`. */
57
+ maskedText: string;
58
+ /** Detected values grouped per label. */
59
+ maskedData: MaskedData;
60
+ /** Flat list of detected entities with offsets into the original text. */
61
+ entities: PiiEntity[];
62
+ }
63
+
64
+ /**
65
+ * Detectors in priority order: when two detectors match overlapping
66
+ * text, the one listed first wins. Names run first, then patterns from
67
+ * most to least specific — in particular, the generic Swedish bank
68
+ * account number (10–11 digits) runs after personnummer, organization
69
+ * numbers, cards and phone numbers, all of which it would otherwise
70
+ * shadow.
71
+ */
72
+ declare const detectors: Detector[];
73
+ /**
74
+ * Run all detectors in priority order. Each detector sees the input
75
+ * with higher-priority matches blanked out (never with placeholder text,
76
+ * so placeholders can't be re-matched or corrupted). Blanking — rather
77
+ * than dropping overlapping candidates outright — lets a lower-priority
78
+ * detector still match the uncovered remainder: in "Lilla Vägen 7",
79
+ * where "Lilla" is a registered first name, the street detector can
80
+ * still mask "Vägen 7".
81
+ *
82
+ * Every candidate carries a confidence score; candidates below
83
+ * `scoreThreshold` are discarded before overlap resolution.
84
+ */
85
+ declare function detectPII(text: string, options?: DetectOptions): PiiEntity[];
86
+ /**
87
+ * Mask every detected entity in `text`, replacing it with `<LABEL_n>`.
88
+ * The returned `maskedData` maps each label to its values in order of
89
+ * appearance, and `entities` carries exact offsets into the input.
90
+ */
91
+ declare function maskPII(text: string, options?: DetectOptions): MaskResult;
92
+
93
+ /**
94
+ * Match a "First Last" string against the name lists. Returns the
95
+ * canonical matched names, or null when either part misses.
96
+ */
97
+ declare function matchFullName(fullName: string): {
98
+ first: string;
99
+ last: string;
100
+ } | null;
101
+
102
+ /**
103
+ * Luhn (mod 10) checksum, used by payment card numbers, Swedish personal
104
+ * identity numbers (personnummer), coordination numbers and organization
105
+ * numbers.
106
+ */
107
+ declare function luhnCheck(digits: string): boolean;
108
+ /**
109
+ * Validate the checksum of a Swedish personnummer / samordningsnummer /
110
+ * organisationsnummer. The Luhn sum is always computed over the final
111
+ * 10 digits (century digits are excluded).
112
+ */
113
+ declare function swedishIdChecksum(value: string): boolean;
114
+
115
+ /**
116
+ * IBAN mod-97 checksum (ISO 13616): move the first four characters to
117
+ * the end, convert letters to numbers (A=10 … Z=35), and the whole
118
+ * number must be ≡ 1 (mod 97). Computed digit-by-digit to stay within
119
+ * safe integer range regardless of IBAN length.
120
+ */
121
+ declare function ibanChecksum(iban: string): boolean;
122
+
123
+ /**
124
+ * True when year/month/day form a real calendar date (leap years included).
125
+ * Month and day are 1-based.
126
+ */
127
+ declare function isRealDate(year: number, month: number, day: number): boolean;
128
+ /**
129
+ * Validate the date part of a Swedish identity number.
130
+ *
131
+ * @param digits - The identity number's digits (10 or 12, separators removed).
132
+ * @param dayOffset - 60 for samordningsnummer (day is stored as day + 60).
133
+ */
134
+ declare function isValidIdentityDate(digits: string, dayOffset?: number): boolean;
135
+
136
+ declare function jaroWinkler(s1: string, s2: string): number;
137
+
138
+ /**
139
+ * Confidence levels, Presidio-style: recognizers report how sure they
140
+ * are, callers filter with `scoreThreshold`.
141
+ */
142
+ declare const SCORE: {
143
+ /** Checksum (Luhn / mod-97) or calendar validation passed. */
144
+ readonly VALIDATED: 0.95;
145
+ /** Exact hit in a curated gazetteer (streets, municipalities). */
146
+ readonly EXACT_MATCH: 0.9;
147
+ /** Fuzzy full-name match or context-corroborated shape. */
148
+ readonly CONTEXT: 0.85;
149
+ /** Exact single-word lookup in a large list (names). */
150
+ readonly LOOKUP: 0.55;
151
+ /** A plain pattern/term match with no further evidence. */
152
+ readonly PATTERN: 0.6;
153
+ /** Heuristic shape (capitalized words + street suffix). */
154
+ readonly HEURISTIC: 0.5;
155
+ /** Shape matched but its checksum failed. */
156
+ readonly FAILED_VALIDATION: 0.45;
157
+ /** Shape matched but the expected nearby context is missing. */
158
+ readonly NO_CONTEXT: 0.25;
159
+ };
160
+ /** Entities scoring below this are dropped unless the caller opts in. */
161
+ declare const DEFAULT_SCORE_THRESHOLD = 0.4;
162
+
163
+ export { DEFAULT_SCORE_THRESHOLD, type DetectOptions, type Detector, type EntitySpan, type MaskResult, type MaskedData, type MaskedValue, type PiiEntity, type PiiLabel, SCORE, detectPII, detectors, ibanChecksum, isRealDate, isValidIdentityDate, jaroWinkler, luhnCheck, maskPII, matchFullName, swedishIdChecksum };