@stll/anonymize-wasm 1.4.7 → 1.4.9
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.
- package/dist/triggers.global.mjs +73 -28
- package/dist/wasm.d.mts +53 -25
- package/dist/wasm.mjs +314 -58
- package/dist/wasm.mjs.map +1 -1
- package/package.json +3 -3
package/dist/wasm.mjs
CHANGED
|
@@ -33,6 +33,10 @@ const isLegalFormsEnabled = (config) => config.enableLegalForms !== false;
|
|
|
33
33
|
* shallow copies (spread). Uses position + label so the
|
|
34
34
|
* key is identical for the original object and any
|
|
35
35
|
* `{ ...entity }` copy produced by mergeAndDedup.
|
|
36
|
+
*
|
|
37
|
+
* @deprecated No longer used internally: coref alias
|
|
38
|
+
* links travel on the entities themselves
|
|
39
|
+
* (`corefSourceText`). Kept for API compatibility.
|
|
36
40
|
*/
|
|
37
41
|
const corefKey = (e) => `${e.start}:${e.end}:${e.label}`;
|
|
38
42
|
/** Create a fresh, empty pipeline context. */
|
|
@@ -62,8 +66,7 @@ const createPipelineContext = () => ({
|
|
|
62
66
|
roleStopSetPromise: null,
|
|
63
67
|
zoneHeadingPatterns: null,
|
|
64
68
|
zoneSigningPatterns: null,
|
|
65
|
-
zoneInitPromise: null
|
|
66
|
-
corefSourceMap: /* @__PURE__ */ new Map()
|
|
69
|
+
zoneInitPromise: null
|
|
67
70
|
});
|
|
68
71
|
/**
|
|
69
72
|
* Module-level default context. Used when callers
|
|
@@ -1761,11 +1764,14 @@ const isWordChar = (ch) => {
|
|
|
1761
1764
|
* character before the start and after the end are NOT
|
|
1762
1765
|
* word characters (letter/digit).
|
|
1763
1766
|
*
|
|
1764
|
-
*
|
|
1765
|
-
*
|
|
1766
|
-
*
|
|
1767
|
+
* Each returned alias carries `corefSourceText` linking
|
|
1768
|
+
* it to its source entity text, for consistent
|
|
1769
|
+
* placeholder numbering.
|
|
1770
|
+
*
|
|
1771
|
+
* @param _ctx Unused. Kept for signature compatibility;
|
|
1772
|
+
* alias links now travel on the entities themselves.
|
|
1767
1773
|
*/
|
|
1768
|
-
const findCoreferenceSpans = (fullText, terms,
|
|
1774
|
+
const findCoreferenceSpans = (fullText, terms, _ctx = defaultContext) => {
|
|
1769
1775
|
const results = [];
|
|
1770
1776
|
for (const term of terms) {
|
|
1771
1777
|
let searchFrom = 0;
|
|
@@ -1779,16 +1785,15 @@ const findCoreferenceSpans = (fullText, terms, ctx = defaultContext) => {
|
|
|
1779
1785
|
searchFrom = idx + 1;
|
|
1780
1786
|
continue;
|
|
1781
1787
|
}
|
|
1782
|
-
|
|
1788
|
+
results.push({
|
|
1783
1789
|
start: idx,
|
|
1784
1790
|
end: matchEnd,
|
|
1785
1791
|
label: term.label,
|
|
1786
1792
|
text: term.alias,
|
|
1787
1793
|
score: .95,
|
|
1788
|
-
source: DETECTION_SOURCES.COREFERENCE
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
results.push(entity);
|
|
1794
|
+
source: DETECTION_SOURCES.COREFERENCE,
|
|
1795
|
+
corefSourceText: term.sourceText
|
|
1796
|
+
});
|
|
1792
1797
|
searchFrom = matchEnd;
|
|
1793
1798
|
}
|
|
1794
1799
|
}
|
|
@@ -8055,32 +8060,216 @@ const initNameCorpus = (ctx = defaultContext, dictionaries, languages) => {
|
|
|
8055
8060
|
ctx.nameCorpusPromise = promise;
|
|
8056
8061
|
return promise;
|
|
8057
8062
|
};
|
|
8058
|
-
const
|
|
8059
|
-
|
|
8060
|
-
|
|
8061
|
-
|
|
8062
|
-
|
|
8063
|
-
|
|
8064
|
-
|
|
8065
|
-
|
|
8063
|
+
const DECLENSION_RULES = [
|
|
8064
|
+
{
|
|
8065
|
+
ending: "",
|
|
8066
|
+
gate: /[bdfghklmnpqrstvwxzł]$/u,
|
|
8067
|
+
forms: [
|
|
8068
|
+
"a",
|
|
8069
|
+
"u",
|
|
8070
|
+
"ovi",
|
|
8071
|
+
"em",
|
|
8072
|
+
"om"
|
|
8073
|
+
]
|
|
8074
|
+
},
|
|
8075
|
+
{
|
|
8076
|
+
ending: "",
|
|
8077
|
+
gate: /[bdflmnpstvwz]$/u,
|
|
8078
|
+
forms: ["e"]
|
|
8079
|
+
},
|
|
8080
|
+
{
|
|
8081
|
+
ending: "",
|
|
8082
|
+
gate: /[cčďťňřšžjľ]$/u,
|
|
8083
|
+
forms: [
|
|
8084
|
+
"e",
|
|
8085
|
+
"i",
|
|
8086
|
+
"a",
|
|
8087
|
+
"ovi",
|
|
8088
|
+
"em",
|
|
8089
|
+
"om"
|
|
8090
|
+
]
|
|
8091
|
+
},
|
|
8092
|
+
{
|
|
8093
|
+
ending: "ek",
|
|
8094
|
+
gate: /[^aeiouyáéěíóôúůý]ek$/u,
|
|
8095
|
+
forms: [
|
|
8096
|
+
"ka",
|
|
8097
|
+
"ku",
|
|
8098
|
+
"kovi",
|
|
8099
|
+
"kem",
|
|
8100
|
+
"kom"
|
|
8101
|
+
]
|
|
8102
|
+
},
|
|
8103
|
+
{
|
|
8104
|
+
ending: "el",
|
|
8105
|
+
gate: /[^aeiouyáéěíóôúůý]el$/u,
|
|
8106
|
+
forms: [
|
|
8107
|
+
"la",
|
|
8108
|
+
"lu",
|
|
8109
|
+
"le",
|
|
8110
|
+
"lovi",
|
|
8111
|
+
"lem",
|
|
8112
|
+
"lom"
|
|
8113
|
+
]
|
|
8114
|
+
},
|
|
8115
|
+
{
|
|
8116
|
+
ending: "ec",
|
|
8117
|
+
gate: /[^aeiouyáéěíóôúůý]ec$/u,
|
|
8118
|
+
forms: [
|
|
8119
|
+
"ce",
|
|
8120
|
+
"ci",
|
|
8121
|
+
"covi",
|
|
8122
|
+
"cem",
|
|
8123
|
+
"com"
|
|
8124
|
+
]
|
|
8125
|
+
},
|
|
8126
|
+
{
|
|
8127
|
+
ending: "a",
|
|
8128
|
+
gate: /[^cčďťňřšžji]a$/u,
|
|
8129
|
+
forms: [
|
|
8130
|
+
"y",
|
|
8131
|
+
"u",
|
|
8132
|
+
"o",
|
|
8133
|
+
"ou",
|
|
8134
|
+
"ovi"
|
|
8135
|
+
]
|
|
8136
|
+
},
|
|
8137
|
+
{
|
|
8138
|
+
ending: "a",
|
|
8139
|
+
gate: /[cčďťňřšžj]a$/u,
|
|
8140
|
+
forms: [
|
|
8141
|
+
"i",
|
|
8142
|
+
"u",
|
|
8143
|
+
"o",
|
|
8144
|
+
"ou",
|
|
8145
|
+
"ovi"
|
|
8146
|
+
]
|
|
8147
|
+
},
|
|
8148
|
+
{
|
|
8149
|
+
ending: "a",
|
|
8150
|
+
gate: /ia$/u,
|
|
8151
|
+
forms: [
|
|
8152
|
+
"e",
|
|
8153
|
+
"i",
|
|
8154
|
+
"u",
|
|
8155
|
+
"ou"
|
|
8156
|
+
]
|
|
8157
|
+
},
|
|
8158
|
+
{
|
|
8159
|
+
ending: "ka",
|
|
8160
|
+
gate: /ka$/u,
|
|
8161
|
+
forms: ["ce"]
|
|
8162
|
+
},
|
|
8163
|
+
{
|
|
8164
|
+
ending: "ra",
|
|
8165
|
+
gate: /ra$/u,
|
|
8166
|
+
forms: ["ře", "re"]
|
|
8167
|
+
},
|
|
8168
|
+
{
|
|
8169
|
+
ending: "ha",
|
|
8170
|
+
gate: /ha$/u,
|
|
8171
|
+
forms: ["ze"]
|
|
8172
|
+
},
|
|
8173
|
+
{
|
|
8174
|
+
ending: "ga",
|
|
8175
|
+
gate: /ga$/u,
|
|
8176
|
+
forms: ["ze"]
|
|
8177
|
+
},
|
|
8178
|
+
{
|
|
8179
|
+
ending: "cha",
|
|
8180
|
+
gate: /cha$/u,
|
|
8181
|
+
forms: ["še"]
|
|
8182
|
+
},
|
|
8183
|
+
{
|
|
8184
|
+
ending: "a",
|
|
8185
|
+
gate: /[bdfmnptv]a$/u,
|
|
8186
|
+
forms: ["ě", "e"]
|
|
8187
|
+
},
|
|
8188
|
+
{
|
|
8189
|
+
ending: "a",
|
|
8190
|
+
gate: /[szl]a$/u,
|
|
8191
|
+
forms: ["e"]
|
|
8192
|
+
},
|
|
8193
|
+
{
|
|
8194
|
+
ending: "á",
|
|
8195
|
+
gate: /á$/u,
|
|
8196
|
+
forms: [
|
|
8197
|
+
"é",
|
|
8198
|
+
"ou",
|
|
8199
|
+
"ej",
|
|
8200
|
+
"ú"
|
|
8201
|
+
]
|
|
8202
|
+
},
|
|
8203
|
+
{
|
|
8204
|
+
ending: "ý",
|
|
8205
|
+
gate: /ý$/u,
|
|
8206
|
+
forms: [
|
|
8207
|
+
"ého",
|
|
8208
|
+
"ému",
|
|
8209
|
+
"ém",
|
|
8210
|
+
"ým"
|
|
8211
|
+
]
|
|
8212
|
+
},
|
|
8213
|
+
{
|
|
8214
|
+
ending: "",
|
|
8215
|
+
gate: /[íiy]$/u,
|
|
8216
|
+
forms: [
|
|
8217
|
+
"ho",
|
|
8218
|
+
"mu",
|
|
8219
|
+
"m"
|
|
8220
|
+
]
|
|
8221
|
+
},
|
|
8222
|
+
{
|
|
8223
|
+
ending: "e",
|
|
8224
|
+
gate: /[^aeouyáéěíóôúůý]e$/u,
|
|
8225
|
+
forms: ["i", "í"]
|
|
8226
|
+
},
|
|
8227
|
+
{
|
|
8228
|
+
ending: "o",
|
|
8229
|
+
gate: /o$/u,
|
|
8230
|
+
forms: [
|
|
8231
|
+
"a",
|
|
8232
|
+
"ovi",
|
|
8233
|
+
"em",
|
|
8234
|
+
"om"
|
|
8235
|
+
]
|
|
8236
|
+
}
|
|
8066
8237
|
];
|
|
8067
8238
|
/**
|
|
8068
|
-
*
|
|
8069
|
-
*
|
|
8070
|
-
*
|
|
8071
|
-
*
|
|
8072
|
-
*
|
|
8073
|
-
|
|
8074
|
-
|
|
8239
|
+
* Generate declined Czech/Slovak variants of a
|
|
8240
|
+
* nominative name. Only variants licensed by the
|
|
8241
|
+
* ending-shape rules are produced, which bounds the
|
|
8242
|
+
* deny-list AC pattern growth. Returns an empty array
|
|
8243
|
+
* for names too short to decline safely.
|
|
8244
|
+
*/
|
|
8245
|
+
const expandNameDeclensions = (name) => {
|
|
8246
|
+
if (name.length < 3) return [];
|
|
8247
|
+
const lower = name.toLowerCase();
|
|
8248
|
+
const variants = [];
|
|
8249
|
+
for (const rule of DECLENSION_RULES) {
|
|
8250
|
+
if (!rule.gate.test(lower)) continue;
|
|
8251
|
+
const stem = name.slice(0, name.length - rule.ending.length);
|
|
8252
|
+
if (stem.length < 2) continue;
|
|
8253
|
+
for (const form of rule.forms) variants.push(stem + form);
|
|
8254
|
+
}
|
|
8255
|
+
return variants;
|
|
8256
|
+
};
|
|
8257
|
+
/**
|
|
8258
|
+
* Strip Czech/Slovak case endings from a token using
|
|
8259
|
+
* the inverse of DECLENSION_RULES. Returns candidate
|
|
8260
|
+
* nominative forms; each candidate is validated against
|
|
8261
|
+
* the rule's shape gate, so only bases the paradigm
|
|
8262
|
+
* could actually have declined are proposed. Callers
|
|
8263
|
+
* check candidates against the name corpus.
|
|
8075
8264
|
*/
|
|
8076
8265
|
const stripInflection = (token) => {
|
|
8077
8266
|
const candidates = [];
|
|
8078
|
-
for (const
|
|
8079
|
-
|
|
8080
|
-
|
|
8081
|
-
|
|
8082
|
-
|
|
8083
|
-
|
|
8267
|
+
for (const rule of DECLENSION_RULES) for (const form of rule.forms) {
|
|
8268
|
+
if (token.length <= form.length + 2 || !token.endsWith(form)) continue;
|
|
8269
|
+
const base = token.slice(0, token.length - form.length) + rule.ending;
|
|
8270
|
+
if (!/^\p{Lu}/u.test(base)) continue;
|
|
8271
|
+
if (!rule.gate.test(base.toLowerCase())) continue;
|
|
8272
|
+
candidates.push(base);
|
|
8084
8273
|
}
|
|
8085
8274
|
return candidates;
|
|
8086
8275
|
};
|
|
@@ -8340,7 +8529,7 @@ const detectNameCorpus = (fullText, ctx = defaultContext) => {
|
|
|
8340
8529
|
const SLASH_S_RE = /\/s\/[ \t]*/g;
|
|
8341
8530
|
const LABELLED_NAME_RE = /\b(?:By|Name)[ \t]*:[ \t]*(?:\/s\/[ \t]+)?([^\n]*?)(?=\s{3,}|[\t]|\n|$)/gi;
|
|
8342
8531
|
const WITNESS_ANCHOR_RE = /\bIN WITNESS WHEREOF\b[^\n]*\n/gi;
|
|
8343
|
-
const NAME_PARTICLE = "(?:de|del|della|der|den|di|du|el|la|le|van|von|y|zu|af|ben|bin|al|d'|d’)";
|
|
8532
|
+
const NAME_PARTICLE = "(?:de|del|della|der|den|di|du|da|das|do|dos|el|la|le|van|von|y|zu|af|ben|bin|al|d'|d’)";
|
|
8344
8533
|
const CAP_TOKEN = "\\p{Lu}[\\p{L}\\p{M}.'\\-]{0,30}";
|
|
8345
8534
|
const NAME_SHAPE_RE = new RegExp(`^${CAP_TOKEN}(?:[ \\t]+(?:${NAME_PARTICLE}|${CAP_TOKEN})){1,4}$`, "u");
|
|
8346
8535
|
const MAX_NAME_LEN = 60;
|
|
@@ -8592,6 +8781,35 @@ const HONORIFIC_BOUNDARY = new Set([
|
|
|
8592
8781
|
"Señora"
|
|
8593
8782
|
]);
|
|
8594
8783
|
/**
|
|
8784
|
+
* Honorifics that are abbreviations: a dot after them is an
|
|
8785
|
+
* abbreviation dot (same sentence), so the detector keeps an
|
|
8786
|
+
* optional `.` between the title and the name ("Mr. Smith").
|
|
8787
|
+
* Every other honorific is a full word — a trailing dot ends a
|
|
8788
|
+
* sentence, so the detector must NOT consume it (otherwise a span
|
|
8789
|
+
* like "President. The Employee" crosses the sentence boundary).
|
|
8790
|
+
*
|
|
8791
|
+
* Maintained explicitly, NOT derived from "ends in a dot": "Mr" is
|
|
8792
|
+
* an abbreviation written without a dot, and "Lord" is a full word.
|
|
8793
|
+
*/
|
|
8794
|
+
const HONORIFIC_ABBREVIATION = new Set([
|
|
8795
|
+
"Avv.",
|
|
8796
|
+
"Dott.",
|
|
8797
|
+
"M.",
|
|
8798
|
+
"Me",
|
|
8799
|
+
"Messrs",
|
|
8800
|
+
"Mlle",
|
|
8801
|
+
"Mme",
|
|
8802
|
+
"Mr",
|
|
8803
|
+
"Mrs",
|
|
8804
|
+
"Ms",
|
|
8805
|
+
"Pr",
|
|
8806
|
+
"Pr.",
|
|
8807
|
+
"Sig.",
|
|
8808
|
+
"Sig.ra",
|
|
8809
|
+
"Sr.",
|
|
8810
|
+
"Sra."
|
|
8811
|
+
]);
|
|
8812
|
+
/**
|
|
8595
8813
|
* Post-nominal degrees (comma or space separated after name).
|
|
8596
8814
|
* Plain text; the detector auto-escapes for regex.
|
|
8597
8815
|
*/
|
|
@@ -9118,11 +9336,14 @@ const POST_NOMINAL = POST_NOMINALS.toSorted((a, b) => b.length - a.length).map(e
|
|
|
9118
9336
|
const NAME_WORD = `\\p{Lu}\\p{Ll}+`;
|
|
9119
9337
|
const PARTICLE = "(?:van der|van den|de la|della|von|van|dos|ibn|ben|bin|del|zum|zur|ten|ter|da|de|di|al|el|le|la|zu|af|av)";
|
|
9120
9338
|
const SP = "[^\\S\\n]";
|
|
9121
|
-
/** Honorific alternation built from titles.ts config.
|
|
9122
|
-
|
|
9339
|
+
/** Honorific alternation built from titles.ts config. Sorted
|
|
9340
|
+
* longest-first so e.g. "Sig.ra" wins over "Sig.". */
|
|
9341
|
+
const buildHonorificAlt = (entries) => [...entries].toSorted((a, b) => b.length - a.length).map((h) => {
|
|
9123
9342
|
const escaped = escapeRegex$2(h);
|
|
9124
9343
|
return HONORIFIC_BOUNDARY.has(h) ? `\\b${escaped}` : escaped;
|
|
9125
9344
|
}).join("|");
|
|
9345
|
+
const HONORIFIC_ABBREV_ALT = buildHonorificAlt(HONORIFICS.filter((h) => HONORIFIC_ABBREVIATION.has(h)));
|
|
9346
|
+
const HONORIFIC_FULLWORD_ALT = buildHonorificAlt(HONORIFICS.filter((h) => !HONORIFIC_ABBREVIATION.has(h)));
|
|
9126
9347
|
const AMOUNT_WORDS = amount_words_default;
|
|
9127
9348
|
const toEntry = (validator, label, score) => {
|
|
9128
9349
|
const pattern = toRegex(validator).source;
|
|
@@ -9214,7 +9435,7 @@ const TITLED_PERSON = {
|
|
|
9214
9435
|
score: .95
|
|
9215
9436
|
};
|
|
9216
9437
|
const HONORIFIC_PERSON = {
|
|
9217
|
-
pattern: `(?:${
|
|
9438
|
+
pattern: `(?:(?:${HONORIFIC_ABBREV_ALT})\\.?|(?:${HONORIFIC_FULLWORD_ALT}))${SP}+${NAME_WORD}(?:(?:${SP}|-){1,2}(?:${PARTICLE}${SP}+)?${NAME_WORD}){0,3}(?:${SP}+(?:QC|KC|SC|LJ|AG))?`,
|
|
9218
9439
|
label: "person",
|
|
9219
9440
|
score: .95
|
|
9220
9441
|
};
|
|
@@ -10089,7 +10310,8 @@ const dropOverlapping = (candidates) => {
|
|
|
10089
10310
|
//#region src/detectors/triggers.ts
|
|
10090
10311
|
const VALID_ID_VALIDATORS = {
|
|
10091
10312
|
"br.cpf": br.cpf,
|
|
10092
|
-
"br.cnpj": br.cnpj
|
|
10313
|
+
"br.cnpj": br.cnpj,
|
|
10314
|
+
"us.rtn": us.rtn
|
|
10093
10315
|
};
|
|
10094
10316
|
const TRIGGER_SCORE = .95;
|
|
10095
10317
|
const WHITESPACE_RE$1 = /\s+/;
|
|
@@ -10105,6 +10327,8 @@ const DECIMAL_COMMA_RE = new RegExp(`^,(?:\\d|${DASH}{1,2})`);
|
|
|
10105
10327
|
* etc.), skip the comma and degree, then continue.
|
|
10106
10328
|
*/
|
|
10107
10329
|
const POST_NOMINAL_RE = new RegExp(`^,\\s*(?:${POST_NOMINALS.toSorted((a, b) => b.length - a.length).map((d) => d.replace(/[.*+?^${}()|[\]\\]/g, "\\$&").replace(/\\\./g, "\\.\\s*")).join("|")})\\.?`, "i");
|
|
10330
|
+
const PERSON_NAME_TOKEN = "\\p{Lu}[\\p{L}\\p{M}.'’\\-]*";
|
|
10331
|
+
const PERSON_NAME_RUN_RE = new RegExp(`^${PERSON_NAME_TOKEN}(?:,?[ \\t]+(?:${`(?:${NAME_PARTICLE}[ \\t]+|d['’])`}){0,2}${PERSON_NAME_TOKEN})*`, "u");
|
|
10108
10332
|
let cachedLegalFormCheckSource = null;
|
|
10109
10333
|
let cachedLegalFormCheckForms = [];
|
|
10110
10334
|
const LEGAL_FORM_ALNUM_RE = /[\p{L}\p{N}]/u;
|
|
@@ -10772,9 +10996,16 @@ const processTriggerMatches = (allMatches, sliceStart, sliceEnd, fullText, rules
|
|
|
10772
10996
|
if (!applyValidations(value.text, rule.validations)) continue;
|
|
10773
10997
|
if (rule.label === "phone number" && !isPlausiblePhoneTriggerValue(value.text)) continue;
|
|
10774
10998
|
const entityStart = rule.includeTrigger ? match.start : value.start;
|
|
10775
|
-
|
|
10776
|
-
|
|
10999
|
+
let entityEnd = value.end;
|
|
11000
|
+
let entityText = fullText.slice(entityStart, entityEnd);
|
|
10777
11001
|
const effectiveLabel = rule.label === "person" && hasKnownLegalFormSuffix(entityText) ? "organization" : rule.label;
|
|
11002
|
+
if (effectiveLabel === "person") {
|
|
11003
|
+
const run = PERSON_NAME_RUN_RE.exec(value.text);
|
|
11004
|
+
if (run !== null && run[0].length < value.text.length) {
|
|
11005
|
+
entityEnd = value.start + run[0].length;
|
|
11006
|
+
entityText = fullText.slice(entityStart, entityEnd);
|
|
11007
|
+
}
|
|
11008
|
+
}
|
|
10778
11009
|
results.push({
|
|
10779
11010
|
start: entityStart,
|
|
10780
11011
|
end: entityEnd,
|
|
@@ -11398,6 +11629,9 @@ const EMPTY_ALLOW_LIST = /* @__PURE__ */ new Set();
|
|
|
11398
11629
|
const getAllowList = (ctx) => ctx.allowList ?? EMPTY_ALLOW_LIST;
|
|
11399
11630
|
let commonWordsPromise = null;
|
|
11400
11631
|
let commonWordsCache = null;
|
|
11632
|
+
const EMPTY_COMMON_WORDS = /* @__PURE__ */ new Set();
|
|
11633
|
+
/** Sync accessor — returns empty set before init. */
|
|
11634
|
+
const getCommonWords = () => commonWordsCache ?? EMPTY_COMMON_WORDS;
|
|
11401
11635
|
const loadCommonWords = () => {
|
|
11402
11636
|
if (commonWordsCache) return Promise.resolve(commonWordsCache);
|
|
11403
11637
|
if (commonWordsPromise) return commonWordsPromise;
|
|
@@ -11845,8 +12079,21 @@ const appendNameCorpusEntries = (config, ctx, patternList, labelList, sourceList
|
|
|
11845
12079
|
sourceList.push(source);
|
|
11846
12080
|
}
|
|
11847
12081
|
};
|
|
11848
|
-
|
|
11849
|
-
|
|
12082
|
+
const commonWords = getCommonWords();
|
|
12083
|
+
const addDeclinedVariants = (name, source) => {
|
|
12084
|
+
for (const variant of expandNameDeclensions(name)) {
|
|
12085
|
+
if (commonWords.has(variant.toLowerCase())) continue;
|
|
12086
|
+
addNameEntry(variant, source);
|
|
12087
|
+
}
|
|
12088
|
+
};
|
|
12089
|
+
for (const name of getNameCorpusFirstNames(ctx)) {
|
|
12090
|
+
addNameEntry(name, "first-name");
|
|
12091
|
+
addDeclinedVariants(name, "first-name");
|
|
12092
|
+
}
|
|
12093
|
+
for (const name of getNameCorpusSurnames(ctx)) {
|
|
12094
|
+
addNameEntry(name, "surname");
|
|
12095
|
+
addDeclinedVariants(name, "surname");
|
|
12096
|
+
}
|
|
11850
12097
|
for (const title of getNameCorpusTitles(ctx)) {
|
|
11851
12098
|
const norm = stripCuratedPatternSyntax(normalizeForSearch(title));
|
|
11852
12099
|
if (norm.length === 0) continue;
|
|
@@ -12623,25 +12870,33 @@ const isCallerOwnedEntity$2 = (entity) => entity.sourceDetail === "custom-deny-l
|
|
|
12623
12870
|
* to get the base name, and re-scan the full text for
|
|
12624
12871
|
* bare mentions of that base name. Returns new entities
|
|
12625
12872
|
* for occurrences not already covered.
|
|
12873
|
+
*
|
|
12874
|
+
* Propagated mentions are coref aliases: each carries
|
|
12875
|
+
* `corefSourceText` linking it to the full seed entity
|
|
12876
|
+
* text, so placeholder numbering assigns the bare
|
|
12877
|
+
* mention the same placeholder as its source ("Acme"
|
|
12878
|
+
* and "Acme Corp." both become [ORGANIZATION_1]).
|
|
12626
12879
|
*/
|
|
12627
12880
|
const propagateOrgNames = (entities, fullText) => {
|
|
12628
|
-
const
|
|
12629
|
-
const seenBases = /* @__PURE__ */ new Set();
|
|
12881
|
+
const seedByBase = /* @__PURE__ */ new Map();
|
|
12630
12882
|
for (const e of entities) {
|
|
12631
12883
|
if (e.label !== "organization") continue;
|
|
12632
12884
|
if (isCallerOwnedEntity$2(e)) continue;
|
|
12633
12885
|
for (const suffix of LEGAL_SUFFIXES) if (e.text.endsWith(suffix)) {
|
|
12634
12886
|
const base = e.text.slice(0, -suffix.length).replace(TRAILING_SEP, "").trim();
|
|
12635
|
-
if (base.length >= 3
|
|
12636
|
-
|
|
12637
|
-
|
|
12887
|
+
if (base.length >= 3) {
|
|
12888
|
+
const existing = seedByBase.get(base);
|
|
12889
|
+
if (existing === void 0) seedByBase.set(base, {
|
|
12638
12890
|
baseName: base,
|
|
12639
|
-
label: e.label
|
|
12891
|
+
label: e.label,
|
|
12892
|
+
sourceText: e.text
|
|
12640
12893
|
});
|
|
12894
|
+
else if (existing.sourceText !== e.text) existing.sourceText = base;
|
|
12641
12895
|
}
|
|
12642
12896
|
break;
|
|
12643
12897
|
}
|
|
12644
12898
|
}
|
|
12899
|
+
const seeds = [...seedByBase.values()];
|
|
12645
12900
|
if (seeds.length === 0) return [];
|
|
12646
12901
|
const covered = entities.map((e) => [e.start, e.end]);
|
|
12647
12902
|
const isOverlapping = (start, end) => covered.some(([cs, ce]) => start < ce && end > cs);
|
|
@@ -12675,7 +12930,8 @@ const propagateOrgNames = (entities, fullText) => {
|
|
|
12675
12930
|
label,
|
|
12676
12931
|
text: fullText.slice(spanStart, matchEnd),
|
|
12677
12932
|
score: ORG_PROPAGATION_SCORE,
|
|
12678
|
-
source: DETECTION_SOURCES.COREFERENCE
|
|
12933
|
+
source: DETECTION_SOURCES.COREFERENCE,
|
|
12934
|
+
corefSourceText: seed.sourceText
|
|
12679
12935
|
});
|
|
12680
12936
|
covered.push([spanStart, matchEnd]);
|
|
12681
12937
|
}
|
|
@@ -14764,7 +15020,6 @@ const runPipeline = async (options) => {
|
|
|
14764
15020
|
const merged = filterFalsePositives(postOrgEntities, ctx, fullText);
|
|
14765
15021
|
if (merged.length < postOrgEntities.length) log("filter", `removed ${postOrgEntities.length - merged.length} FPs`);
|
|
14766
15022
|
checkAbort(signal);
|
|
14767
|
-
ctx.corefSourceMap.clear();
|
|
14768
15023
|
if (config.enableCoreference) {
|
|
14769
15024
|
if (!legalFormsEnabled) await warmLegalRoleHeads();
|
|
14770
15025
|
const terms = await extractDefinedTerms(fullText, merged.filter((entity) => !isCallerOwnedEntity(entity)), ctx);
|
|
@@ -14830,12 +15085,11 @@ const normalizeEntityText = (label, text) => {
|
|
|
14830
15085
|
* Placeholder format: [LABEL_N] where LABEL is uppercase
|
|
14831
15086
|
* and N is a 1-based counter per label.
|
|
14832
15087
|
*
|
|
14833
|
-
* @param
|
|
14834
|
-
*
|
|
14835
|
-
*
|
|
14836
|
-
* Defaults to `defaultContext` for single-tenant usage.
|
|
15088
|
+
* @param _ctx Unused. Kept for signature compatibility;
|
|
15089
|
+
* coref alias links now travel on the entities
|
|
15090
|
+
* themselves (`corefSourceText`).
|
|
14837
15091
|
*/
|
|
14838
|
-
const buildPlaceholderMap = (entities,
|
|
15092
|
+
const buildPlaceholderMap = (entities, _ctx = defaultContext) => {
|
|
14839
15093
|
const counters = /* @__PURE__ */ new Map();
|
|
14840
15094
|
const textLabelToPlaceholder = /* @__PURE__ */ new Map();
|
|
14841
15095
|
const normalizedToPlaceholder = /* @__PURE__ */ new Map();
|
|
@@ -14844,9 +15098,9 @@ const buildPlaceholderMap = (entities, ctx = defaultContext) => {
|
|
|
14844
15098
|
const compositeKey = `${entity.label}\0${entity.text}`;
|
|
14845
15099
|
if (textLabelToPlaceholder.has(compositeKey)) continue;
|
|
14846
15100
|
const labelKey = entity.label.toUpperCase().replace(WHITESPACE_RE, "_");
|
|
14847
|
-
const sourceText =
|
|
14848
|
-
|
|
14849
|
-
|
|
15101
|
+
const sourceText = entity.source === "coreference" ? entity.corefSourceText : void 0;
|
|
15102
|
+
const sourceNormalizedKey = sourceText === void 0 ? void 0 : `${labelKey}\0${normalizeEntityText(entity.label, sourceText)}`;
|
|
15103
|
+
if (sourceNormalizedKey !== void 0) {
|
|
14850
15104
|
const sourceExisting = normalizedToPlaceholder.get(sourceNormalizedKey);
|
|
14851
15105
|
if (sourceExisting) {
|
|
14852
15106
|
textLabelToPlaceholder.set(compositeKey, sourceExisting);
|
|
@@ -14857,6 +15111,7 @@ const buildPlaceholderMap = (entities, ctx = defaultContext) => {
|
|
|
14857
15111
|
const existing = normalizedToPlaceholder.get(normalizedKey);
|
|
14858
15112
|
if (existing) {
|
|
14859
15113
|
textLabelToPlaceholder.set(compositeKey, existing);
|
|
15114
|
+
if (sourceNormalizedKey !== void 0) normalizedToPlaceholder.set(sourceNormalizedKey, existing);
|
|
14860
15115
|
continue;
|
|
14861
15116
|
}
|
|
14862
15117
|
const count = (counters.get(labelKey) ?? 0) + 1;
|
|
@@ -14864,6 +15119,7 @@ const buildPlaceholderMap = (entities, ctx = defaultContext) => {
|
|
|
14864
15119
|
const placeholder = `[${labelKey}_${count}]`;
|
|
14865
15120
|
textLabelToPlaceholder.set(compositeKey, placeholder);
|
|
14866
15121
|
normalizedToPlaceholder.set(normalizedKey, placeholder);
|
|
15122
|
+
if (sourceNormalizedKey !== void 0) normalizedToPlaceholder.set(sourceNormalizedKey, placeholder);
|
|
14867
15123
|
}
|
|
14868
15124
|
return textLabelToPlaceholder;
|
|
14869
15125
|
};
|
|
@@ -14906,7 +15162,7 @@ const redactText = (fullText, entities, config = DEFAULT_OPERATOR_CONFIG, ctx =
|
|
|
14906
15162
|
const replacement = operator.apply(entity.text, entity.label, placeholder, config.redactString);
|
|
14907
15163
|
parts.push(replacement);
|
|
14908
15164
|
operatorMap.set(placeholder, opType);
|
|
14909
|
-
if (operator.reversibility === "reversible" && !redactionMap.has(placeholder)) redactionMap.set(placeholder, entity.text);
|
|
15165
|
+
if (operator.reversibility === "reversible" && !redactionMap.has(placeholder)) redactionMap.set(placeholder, entity.source === "coreference" ? entity.corefSourceText : entity.text);
|
|
14910
15166
|
cursor = entity.end;
|
|
14911
15167
|
}
|
|
14912
15168
|
if (cursor < fullText.length) parts.push(fullText.slice(cursor));
|