@finbheara/names 0.16.0 → 0.17.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.
- package/README.md +33 -0
- package/dist/chunks/index-n6fpj92s.js +1418 -0
- package/dist/chunks/{index-74fyytnh.js.map → index-n6fpj92s.js.map} +3 -5
- package/dist/chunks/{index-74fyytnh.js → index-qmn87bdh.js} +21 -1411
- package/dist/chunks/index-qmn87bdh.js.map +11 -0
- package/dist/cli/bin.js +109 -0
- package/dist/cli/bin.js.map +11 -0
- package/dist/index.js +8 -6
- package/dist/index.js.map +1 -1
- package/dist/normalize/index.js +8 -6
- package/dist/normalize/index.js.map +1 -1
- package/dist/types/cli/bin.d.ts +10 -0
- package/dist/types/cli/tables.d.ts +13 -0
- package/dist/types/normalize/given-diminutives.d.ts +15 -6
- package/dist/types/normalize/given-families.d.ts +5 -0
- package/package.json +2 -1
|
@@ -0,0 +1,1418 @@
|
|
|
1
|
+
import {
|
|
2
|
+
isSurname2,
|
|
3
|
+
isGiven2,
|
|
4
|
+
isAmbiguous2,
|
|
5
|
+
Lexicon2
|
|
6
|
+
} from "./index-0wpvbdgh.js";
|
|
7
|
+
import {
|
|
8
|
+
givenFamilyTable2,
|
|
9
|
+
givenFamilyList2
|
|
10
|
+
} from "./index-qmn87bdh.js";
|
|
11
|
+
import {
|
|
12
|
+
isPlaceWordFlags
|
|
13
|
+
} from "./index-tqdbxd92.js";
|
|
14
|
+
import {
|
|
15
|
+
doubleMetaphone2
|
|
16
|
+
} from "./index-ebebeamh.js";
|
|
17
|
+
import {
|
|
18
|
+
nameFold2,
|
|
19
|
+
isPlaceholderPrint2,
|
|
20
|
+
isNoNamePrint2,
|
|
21
|
+
isNameSuffix,
|
|
22
|
+
tokenize,
|
|
23
|
+
commaReading2,
|
|
24
|
+
levelOf2,
|
|
25
|
+
defaultPersonNameFactory2
|
|
26
|
+
} from "./index-tk01chxw.js";
|
|
27
|
+
import {
|
|
28
|
+
isTeamPrint2
|
|
29
|
+
} from "./index-0phgwy52.js";
|
|
30
|
+
import {
|
|
31
|
+
isStandaloneNameParticle2
|
|
32
|
+
} from "./index-23rzmwe2.js";
|
|
33
|
+
// src/normalize/name-mojibake.ts
|
|
34
|
+
var CP1252 = new Map([
|
|
35
|
+
[8364, 128],
|
|
36
|
+
[8218, 130],
|
|
37
|
+
[402, 131],
|
|
38
|
+
[8222, 132],
|
|
39
|
+
[8230, 133],
|
|
40
|
+
[8224, 134],
|
|
41
|
+
[8225, 135],
|
|
42
|
+
[710, 136],
|
|
43
|
+
[8240, 137],
|
|
44
|
+
[352, 138],
|
|
45
|
+
[8249, 139],
|
|
46
|
+
[338, 140],
|
|
47
|
+
[381, 142],
|
|
48
|
+
[8216, 145],
|
|
49
|
+
[8217, 146],
|
|
50
|
+
[8220, 147],
|
|
51
|
+
[8221, 148],
|
|
52
|
+
[8226, 149],
|
|
53
|
+
[8211, 150],
|
|
54
|
+
[8212, 151],
|
|
55
|
+
[732, 152],
|
|
56
|
+
[8482, 153],
|
|
57
|
+
[353, 154],
|
|
58
|
+
[8250, 155],
|
|
59
|
+
[339, 156],
|
|
60
|
+
[382, 158],
|
|
61
|
+
[376, 159]
|
|
62
|
+
].map(([c, b]) => [String.fromCharCode(c), b]));
|
|
63
|
+
var UTF8 = new TextDecoder("utf-8", { fatal: true });
|
|
64
|
+
var A0_LEAD = /^[Â-Å]$/u;
|
|
65
|
+
function repairMojibake2(v) {
|
|
66
|
+
if (!/[Â-ô]/u.test(v))
|
|
67
|
+
return v;
|
|
68
|
+
const bytes = [];
|
|
69
|
+
const cs = [...v];
|
|
70
|
+
for (let i = 0;i < cs.length; i++) {
|
|
71
|
+
const c = cs[i];
|
|
72
|
+
const code = c.codePointAt(0);
|
|
73
|
+
const mapped = CP1252.get(c);
|
|
74
|
+
if (mapped !== undefined)
|
|
75
|
+
bytes.push(mapped);
|
|
76
|
+
else if (code === 32 && i > 1 && A0_LEAD.test(cs[i - 1]) && /\S/u.test(cs[i - 2])) {
|
|
77
|
+
bytes.push(160);
|
|
78
|
+
} else if (code < 256)
|
|
79
|
+
bytes.push(code);
|
|
80
|
+
else
|
|
81
|
+
return v;
|
|
82
|
+
}
|
|
83
|
+
try {
|
|
84
|
+
return UTF8.decode(new Uint8Array(bytes));
|
|
85
|
+
} catch {
|
|
86
|
+
return v;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
// src/normalize/multiset.ts
|
|
90
|
+
function subMultiset(outer, inner) {
|
|
91
|
+
const left = [...inner];
|
|
92
|
+
for (const part of outer) {
|
|
93
|
+
const at = left.indexOf(part);
|
|
94
|
+
if (at < 0)
|
|
95
|
+
return false;
|
|
96
|
+
left.splice(at, 1);
|
|
97
|
+
}
|
|
98
|
+
return true;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// src/normalize/name-middle.ts
|
|
102
|
+
function suffixCut(p) {
|
|
103
|
+
return Math.max(1, p.givenFields().length - p.suffixes.length);
|
|
104
|
+
}
|
|
105
|
+
function middleFields(p) {
|
|
106
|
+
return p.givenFields().slice(1, suffixCut(p));
|
|
107
|
+
}
|
|
108
|
+
function suffixFields(p) {
|
|
109
|
+
return p.givenFields().slice(suffixCut(p));
|
|
110
|
+
}
|
|
111
|
+
var sameList = (x, y) => x.length === y.length && x.every((v, i) => v === y[i]);
|
|
112
|
+
var isInitial = (field) => field.length === 1;
|
|
113
|
+
function comparePosition2(x, y) {
|
|
114
|
+
if (x === y)
|
|
115
|
+
return null;
|
|
116
|
+
if (isInitial(x) && isInitial(y))
|
|
117
|
+
return "initials-differ";
|
|
118
|
+
if (isInitial(x))
|
|
119
|
+
return y.startsWith(x) ? null : "initial-differs-from-middle";
|
|
120
|
+
if (isInitial(y))
|
|
121
|
+
return x.startsWith(y) ? null : "initial-differs-from-middle";
|
|
122
|
+
return "middles-differ";
|
|
123
|
+
}
|
|
124
|
+
function nameRelation2(a, b) {
|
|
125
|
+
if (!a.isPerson() || !b.isPerson())
|
|
126
|
+
return { relation: "different", reason: "not-a-person" };
|
|
127
|
+
const ga = a.givenFields();
|
|
128
|
+
const gb = b.givenFields();
|
|
129
|
+
if (a.surnameKey() !== b.surnameKey() || ga[0] !== gb[0] || ga[0] === undefined) {
|
|
130
|
+
return { relation: "different", reason: "head-differs" };
|
|
131
|
+
}
|
|
132
|
+
return tailRelation(a, b);
|
|
133
|
+
}
|
|
134
|
+
function tailRelation(a, b) {
|
|
135
|
+
if (!sameList(suffixFields(a), suffixFields(b))) {
|
|
136
|
+
return { relation: "different", reason: "suffixes-differ" };
|
|
137
|
+
}
|
|
138
|
+
const ma = middleFields(a);
|
|
139
|
+
const mb = middleFields(b);
|
|
140
|
+
if (sameList(ma, mb))
|
|
141
|
+
return { relation: "same", reason: "middles-equal" };
|
|
142
|
+
if (ma.length === 0 || mb.length === 0)
|
|
143
|
+
return { relation: "candidate", reason: "middle-absent" };
|
|
144
|
+
if (ma.length !== mb.length) {
|
|
145
|
+
return { relation: "different", reason: "middle-count-differs" };
|
|
146
|
+
}
|
|
147
|
+
for (let i = 0;i < ma.length; i++) {
|
|
148
|
+
const refused = comparePosition2(ma[i], mb[i]);
|
|
149
|
+
if (refused !== null)
|
|
150
|
+
return { relation: "different", reason: refused };
|
|
151
|
+
}
|
|
152
|
+
return { relation: "candidate", reason: "initial-agrees" };
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// src/normalize/name-compose.ts
|
|
156
|
+
var READER_RANK = ["as-parsed", "boundary", "order"];
|
|
157
|
+
var VIA_ORDER = [
|
|
158
|
+
"boundary",
|
|
159
|
+
"order",
|
|
160
|
+
"middle-absent",
|
|
161
|
+
"initial",
|
|
162
|
+
"surname-part"
|
|
163
|
+
];
|
|
164
|
+
var MAX_READINGS2 = 4;
|
|
165
|
+
var fold = (s) => nameFold2(s).toLocaleUpperCase();
|
|
166
|
+
var isInitial2 = (field) => field.length === 1;
|
|
167
|
+
function surnameParts2(p) {
|
|
168
|
+
const flat = (part) => part.parts.length > 0 ? part.parts.flatMap(flat) : [part.base];
|
|
169
|
+
const leaves = flat(p.surname);
|
|
170
|
+
const glued = [];
|
|
171
|
+
let held = "";
|
|
172
|
+
for (let i = 0;i < leaves.length; i++) {
|
|
173
|
+
const leaf = leaves[i];
|
|
174
|
+
if (i + 1 < leaves.length && isStandaloneNameParticle2(leaf)) {
|
|
175
|
+
held += leaf;
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
glued.push(held + leaf);
|
|
179
|
+
held = "";
|
|
180
|
+
}
|
|
181
|
+
const tail = glued.map(fold).filter((s) => s !== "");
|
|
182
|
+
const head = fold(p.surname.particles.join(""));
|
|
183
|
+
if (head === "")
|
|
184
|
+
return tail;
|
|
185
|
+
return tail.length === 0 ? [head] : [head + tail[0], ...tail.slice(1)];
|
|
186
|
+
}
|
|
187
|
+
var suffixCut2 = (p) => Math.max(1, p.givenFields().length - p.suffixes.length);
|
|
188
|
+
function printAttestsOrder2(p) {
|
|
189
|
+
return /,/.test(p.nn1);
|
|
190
|
+
}
|
|
191
|
+
function nameReadings2(p, opts) {
|
|
192
|
+
if (!p.isPerson())
|
|
193
|
+
return [];
|
|
194
|
+
const given = p.givenFields();
|
|
195
|
+
const cut = suffixCut2(p);
|
|
196
|
+
const middles = given.slice(1, cut);
|
|
197
|
+
const suffixes = given.slice(cut);
|
|
198
|
+
const surname = surnameParts2(p);
|
|
199
|
+
const first = given[0] ?? null;
|
|
200
|
+
const asParsed = {
|
|
201
|
+
reader: "as-parsed",
|
|
202
|
+
surname,
|
|
203
|
+
first,
|
|
204
|
+
middles: middles.length === 0 ? null : middles,
|
|
205
|
+
suffixes
|
|
206
|
+
};
|
|
207
|
+
if (surname.length === 0 && first === null)
|
|
208
|
+
return [];
|
|
209
|
+
const out = [asParsed];
|
|
210
|
+
const last = middles[middles.length - 1];
|
|
211
|
+
if (surname.length === 1 && suffixes.length === 0 && last !== undefined && !isInitial2(last) && !printAttestsOrder2(p)) {
|
|
212
|
+
const kept = middles.slice(0, -1);
|
|
213
|
+
out.push({
|
|
214
|
+
reader: "boundary",
|
|
215
|
+
surname: [last, ...surname],
|
|
216
|
+
first,
|
|
217
|
+
middles: kept.length === 0 ? null : kept,
|
|
218
|
+
suffixes
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
if (!opts.orderAttested && !printAttestsOrder2(p) && surname.length === 1 && suffixes.length === 0 && middles.length === 0 && first !== null) {
|
|
222
|
+
out.push({
|
|
223
|
+
reader: "order",
|
|
224
|
+
surname: [first],
|
|
225
|
+
first: surname[0],
|
|
226
|
+
middles: null,
|
|
227
|
+
suffixes
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
return out.slice(0, MAX_READINGS2);
|
|
231
|
+
}
|
|
232
|
+
var readingsOf = (p, o) => nameReadings2(p, o ?? { orderAttested: printAttestsOrder2(p) });
|
|
233
|
+
function meetFirst(x, y) {
|
|
234
|
+
if (x === null)
|
|
235
|
+
return y;
|
|
236
|
+
if (y === null)
|
|
237
|
+
return x;
|
|
238
|
+
return comparePosition2(x, y) === null ? x.length >= y.length ? x : y : undefined;
|
|
239
|
+
}
|
|
240
|
+
function meetMiddles(x, y) {
|
|
241
|
+
if (x === null)
|
|
242
|
+
return y;
|
|
243
|
+
if (y === null)
|
|
244
|
+
return x;
|
|
245
|
+
if (x.length !== y.length)
|
|
246
|
+
return;
|
|
247
|
+
const out = [];
|
|
248
|
+
for (let i = 0;i < x.length; i++) {
|
|
249
|
+
const a = x[i];
|
|
250
|
+
const b = y[i];
|
|
251
|
+
if (comparePosition2(a, b) !== null)
|
|
252
|
+
return;
|
|
253
|
+
out.push(a.length >= b.length ? a : b);
|
|
254
|
+
}
|
|
255
|
+
return out;
|
|
256
|
+
}
|
|
257
|
+
function meetSurname(x, y) {
|
|
258
|
+
if (subMultiset(x, y))
|
|
259
|
+
return y;
|
|
260
|
+
if (subMultiset(y, x))
|
|
261
|
+
return x;
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
function meetReading2(x, y) {
|
|
265
|
+
if (x.suffixes.length !== y.suffixes.length)
|
|
266
|
+
return null;
|
|
267
|
+
if (!x.suffixes.every((s, i) => s === y.suffixes[i]))
|
|
268
|
+
return null;
|
|
269
|
+
const surname = meetSurname(x.surname, y.surname);
|
|
270
|
+
if (surname === undefined)
|
|
271
|
+
return null;
|
|
272
|
+
const first = meetFirst(x.first, y.first);
|
|
273
|
+
if (first === undefined)
|
|
274
|
+
return null;
|
|
275
|
+
const middles = meetMiddles(x.middles, y.middles);
|
|
276
|
+
if (middles === undefined)
|
|
277
|
+
return null;
|
|
278
|
+
return {
|
|
279
|
+
reader: READER_RANK.indexOf(x.reader) >= READER_RANK.indexOf(y.reader) ? x.reader : y.reader,
|
|
280
|
+
surname,
|
|
281
|
+
first,
|
|
282
|
+
middles,
|
|
283
|
+
suffixes: x.suffixes
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
function refinesReading2(outer, inner) {
|
|
287
|
+
const m = meetReading2(outer, inner);
|
|
288
|
+
return m !== null && sameReading2(m, inner);
|
|
289
|
+
}
|
|
290
|
+
function sameReading2(x, y) {
|
|
291
|
+
const list = (a, b) => a === null || b === null ? a === b : a.length === b.length && a.every((v, i) => v === b[i]);
|
|
292
|
+
return x.first === y.first && list(x.surname, y.surname) && list(x.middles, y.middles) && list(x.suffixes, y.suffixes);
|
|
293
|
+
}
|
|
294
|
+
function viaOf(x, y) {
|
|
295
|
+
const out = [];
|
|
296
|
+
if (x.reader !== "as-parsed")
|
|
297
|
+
out.push(x.reader);
|
|
298
|
+
if (y.reader !== "as-parsed")
|
|
299
|
+
out.push(y.reader);
|
|
300
|
+
if (x.middles === null !== (y.middles === null))
|
|
301
|
+
out.push("middle-absent");
|
|
302
|
+
const pairs = [[x.first, y.first]];
|
|
303
|
+
if (x.middles !== null && y.middles !== null && x.middles.length === y.middles.length) {
|
|
304
|
+
for (let i = 0;i < x.middles.length; i++) {
|
|
305
|
+
pairs.push([x.middles[i], y.middles[i]]);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
for (const [l, r] of pairs) {
|
|
309
|
+
if (l === null || r === null)
|
|
310
|
+
continue;
|
|
311
|
+
if (l !== r && (isInitial2(l) || isInitial2(r)))
|
|
312
|
+
out.push("initial");
|
|
313
|
+
}
|
|
314
|
+
if (x.surname.length !== y.surname.length)
|
|
315
|
+
out.push("surname-part");
|
|
316
|
+
return out;
|
|
317
|
+
}
|
|
318
|
+
var orderVia = (found) => VIA_ORDER.filter((v) => found.has(v));
|
|
319
|
+
var LADDER = [
|
|
320
|
+
"same",
|
|
321
|
+
"contains",
|
|
322
|
+
"overlaps",
|
|
323
|
+
"conflict"
|
|
324
|
+
];
|
|
325
|
+
function relateOnePair(x, y) {
|
|
326
|
+
const meet = meetReading2(x, y);
|
|
327
|
+
if (meet === null)
|
|
328
|
+
return { a: x, b: y, composition: "conflict", direction: null, meet };
|
|
329
|
+
if (sameReading2(x, y))
|
|
330
|
+
return { a: x, b: y, composition: "same", direction: null, meet };
|
|
331
|
+
if (sameReading2(meet, y)) {
|
|
332
|
+
return { a: x, b: y, composition: "contains", direction: "a-contains-b", meet };
|
|
333
|
+
}
|
|
334
|
+
if (sameReading2(meet, x)) {
|
|
335
|
+
return { a: x, b: y, composition: "contains", direction: "b-contains-a", meet };
|
|
336
|
+
}
|
|
337
|
+
return { a: x, b: y, composition: "overlaps", direction: null, meet };
|
|
338
|
+
}
|
|
339
|
+
var readersUsed = (p) => (p.a.reader === "as-parsed" ? 0 : 1) + (p.b.reader === "as-parsed" ? 0 : 1);
|
|
340
|
+
function chooseReadingPair2(ra, rb) {
|
|
341
|
+
let best = null;
|
|
342
|
+
let bestScore = null;
|
|
343
|
+
for (const x of ra) {
|
|
344
|
+
for (const y of rb) {
|
|
345
|
+
const pair = relateOnePair(x, y);
|
|
346
|
+
const keys = [showReading2(x), showReading2(y)].sort();
|
|
347
|
+
const score = [
|
|
348
|
+
LADDER.indexOf(pair.composition),
|
|
349
|
+
readersUsed(pair),
|
|
350
|
+
keys[0],
|
|
351
|
+
keys[1]
|
|
352
|
+
];
|
|
353
|
+
if (bestScore === null || less(score, bestScore)) {
|
|
354
|
+
best = pair;
|
|
355
|
+
bestScore = score;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
return best;
|
|
360
|
+
}
|
|
361
|
+
function less(x, y) {
|
|
362
|
+
for (let i = 0;i < 4; i++) {
|
|
363
|
+
if (x[i] === y[i])
|
|
364
|
+
continue;
|
|
365
|
+
return x[i] < y[i];
|
|
366
|
+
}
|
|
367
|
+
return false;
|
|
368
|
+
}
|
|
369
|
+
function composeReadings2(ra, rb) {
|
|
370
|
+
const pair = chooseReadingPair2(ra, rb);
|
|
371
|
+
if (pair === null)
|
|
372
|
+
return { composition: "silent", direction: null, via: [] };
|
|
373
|
+
if (pair.composition === "conflict") {
|
|
374
|
+
return { composition: "conflict", direction: null, via: [] };
|
|
375
|
+
}
|
|
376
|
+
return {
|
|
377
|
+
composition: pair.composition,
|
|
378
|
+
direction: pair.direction,
|
|
379
|
+
via: orderVia(new Set(viaOf(pair.a, pair.b)))
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
function composeNames2(a, b, opts) {
|
|
383
|
+
return composeReadings2(readingsOf(a, opts?.a), readingsOf(b, opts?.b));
|
|
384
|
+
}
|
|
385
|
+
function meetReadings2(ra, rb) {
|
|
386
|
+
const pair = chooseReadingPair2(ra, rb);
|
|
387
|
+
if (pair === null || pair.meet === null)
|
|
388
|
+
return null;
|
|
389
|
+
return [pair.meet];
|
|
390
|
+
}
|
|
391
|
+
function nameMeet2(a, b, opts) {
|
|
392
|
+
return meetReadings2(readingsOf(a, opts?.a), readingsOf(b, opts?.b));
|
|
393
|
+
}
|
|
394
|
+
function showReading2(r) {
|
|
395
|
+
const mid = r.middles === null ? "-" : r.middles.join("<");
|
|
396
|
+
const suf = r.suffixes.length === 0 ? "" : `<${r.suffixes.join("<")}`;
|
|
397
|
+
return `${r.surname.join("-")}<<${r.first ?? "?"}|${mid}${suf}[${r.reader}]`;
|
|
398
|
+
}
|
|
399
|
+
var BLOCK_KEY_JOIN = "|";
|
|
400
|
+
function blockKeysOfReadings2(readings) {
|
|
401
|
+
const out = new Set;
|
|
402
|
+
for (const r of readings) {
|
|
403
|
+
const initial = r.first === null ? "" : r.first[0];
|
|
404
|
+
for (const part of r.surname)
|
|
405
|
+
out.add(`${part}${BLOCK_KEY_JOIN}${initial}`);
|
|
406
|
+
}
|
|
407
|
+
return [...out].sort();
|
|
408
|
+
}
|
|
409
|
+
function nameBlockKeys2(p, opts) {
|
|
410
|
+
return blockKeysOfReadings2(readingsOf(p, opts));
|
|
411
|
+
}
|
|
412
|
+
var slotsOf = (r) => ({
|
|
413
|
+
first: r.first,
|
|
414
|
+
middles: r.middles,
|
|
415
|
+
suffixes: r.suffixes
|
|
416
|
+
});
|
|
417
|
+
var slotKey = (s) => `${s.first ?? "?"}|${s.middles === null ? "-" : s.middles.join("<")}|${s.suffixes.join("<")}`;
|
|
418
|
+
function meetSlots(x, y) {
|
|
419
|
+
if (x.suffixes.length !== y.suffixes.length)
|
|
420
|
+
return;
|
|
421
|
+
if (!x.suffixes.every((v, i) => v === y.suffixes[i]))
|
|
422
|
+
return;
|
|
423
|
+
const first = meetFirst(x.first, y.first);
|
|
424
|
+
if (first === undefined)
|
|
425
|
+
return;
|
|
426
|
+
const middles = meetMiddles(x.middles, y.middles);
|
|
427
|
+
if (middles === undefined)
|
|
428
|
+
return;
|
|
429
|
+
return { first, middles, suffixes: x.suffixes };
|
|
430
|
+
}
|
|
431
|
+
function namesFeasible2(names, opts) {
|
|
432
|
+
const attester = opts?.attester ?? "as-parsed";
|
|
433
|
+
const silent = [];
|
|
434
|
+
const perPrint = [];
|
|
435
|
+
const seen = new Map;
|
|
436
|
+
for (let i = 0;i < names.length; i++) {
|
|
437
|
+
const p = names[i];
|
|
438
|
+
const rs = readingsOf(p, opts?.readings?.[i]);
|
|
439
|
+
if (rs.length === 0) {
|
|
440
|
+
silent.push(i);
|
|
441
|
+
continue;
|
|
442
|
+
}
|
|
443
|
+
const key = rs.map(showReading2).join("\x00");
|
|
444
|
+
const at = seen.get(key);
|
|
445
|
+
if (at !== undefined)
|
|
446
|
+
continue;
|
|
447
|
+
seen.set(key, i);
|
|
448
|
+
perPrint.push({ index: i, readings: rs });
|
|
449
|
+
}
|
|
450
|
+
if (perPrint.length === 0) {
|
|
451
|
+
return { feasible: false, name: null, bridge: null, silent };
|
|
452
|
+
}
|
|
453
|
+
const mayAttest = (r) => attester === "any" || r.reader === "as-parsed";
|
|
454
|
+
let bestBridge = null;
|
|
455
|
+
let bestName = null;
|
|
456
|
+
for (const cand of perPrint) {
|
|
457
|
+
for (const r of cand.readings) {
|
|
458
|
+
if (!mayAttest(r))
|
|
459
|
+
continue;
|
|
460
|
+
const folded = foldUnder(perPrint, r.surname);
|
|
461
|
+
if (folded === null)
|
|
462
|
+
continue;
|
|
463
|
+
const bridge = { index: cand.index, reader: r.reader, surname: r.surname };
|
|
464
|
+
if (bestBridge === null || rank(bridge) < rank(bestBridge)) {
|
|
465
|
+
bestBridge = bridge;
|
|
466
|
+
bestName = { reader: r.reader, surname: r.surname, ...folded };
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
return {
|
|
471
|
+
feasible: bestBridge !== null,
|
|
472
|
+
name: bestName,
|
|
473
|
+
bridge: bestBridge,
|
|
474
|
+
silent
|
|
475
|
+
};
|
|
476
|
+
}
|
|
477
|
+
var rank = (b) => (b.reader === "as-parsed" ? 0 : 1) * 1e9 + b.index;
|
|
478
|
+
function foldUnder(prints, surname) {
|
|
479
|
+
let states = null;
|
|
480
|
+
let covered = false;
|
|
481
|
+
for (const p of prints) {
|
|
482
|
+
const usable = p.readings.filter((r) => subMultiset(r.surname, surname));
|
|
483
|
+
if (usable.length === 0)
|
|
484
|
+
return null;
|
|
485
|
+
if (usable.some((r) => r.surname.length === surname.length))
|
|
486
|
+
covered = true;
|
|
487
|
+
const next = new Map;
|
|
488
|
+
for (const r of usable) {
|
|
489
|
+
const s = slotsOf(r);
|
|
490
|
+
if (states === null) {
|
|
491
|
+
next.set(slotKey(s), s);
|
|
492
|
+
continue;
|
|
493
|
+
}
|
|
494
|
+
for (const prev of states.values()) {
|
|
495
|
+
const m = meetSlots(prev, s);
|
|
496
|
+
if (m !== undefined)
|
|
497
|
+
next.set(slotKey(m), m);
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
if (next.size === 0)
|
|
501
|
+
return null;
|
|
502
|
+
states = next;
|
|
503
|
+
}
|
|
504
|
+
if (states === null || !covered)
|
|
505
|
+
return null;
|
|
506
|
+
return [...states.values()].sort((x, y) => {
|
|
507
|
+
const mx = x.middles === null ? -1 : x.middles.length;
|
|
508
|
+
const my = y.middles === null ? -1 : y.middles.length;
|
|
509
|
+
return mx - my || (slotKey(x) < slotKey(y) ? -1 : 1);
|
|
510
|
+
})[0];
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
// src/normalize/name-kinship.ts
|
|
514
|
+
var KINSHIP_VIAS2 = Object.freeze({
|
|
515
|
+
"surname-part": Object.freeze({ field: "surname", gateSafe: true }),
|
|
516
|
+
"given-variant": Object.freeze({ field: "given", gateSafe: true }),
|
|
517
|
+
"given-family": Object.freeze({ field: "given", gateSafe: true }),
|
|
518
|
+
"given-family-not-gate": Object.freeze({ field: "given", gateSafe: false }),
|
|
519
|
+
"given-maybe": Object.freeze({ field: "given", gateSafe: false }),
|
|
520
|
+
"middle-absent": Object.freeze({ field: "middle", gateSafe: true }),
|
|
521
|
+
"initial-agrees": Object.freeze({ field: "middle", gateSafe: true })
|
|
522
|
+
});
|
|
523
|
+
var VIA_ORDER2 = Object.freeze(Object.keys(KINSHIP_VIAS2));
|
|
524
|
+
var different = (reason) => ({
|
|
525
|
+
relation: "different",
|
|
526
|
+
via: [],
|
|
527
|
+
reason,
|
|
528
|
+
gateSafe: false
|
|
529
|
+
});
|
|
530
|
+
function surnamePartOf(a, b) {
|
|
531
|
+
const pa = surnameParts2(a);
|
|
532
|
+
const pb = surnameParts2(b);
|
|
533
|
+
if (pa.length === pb.length)
|
|
534
|
+
return false;
|
|
535
|
+
const [short, long] = pa.length < pb.length ? [pa, pb] : [pb, pa];
|
|
536
|
+
return subMultiset(short, long);
|
|
537
|
+
}
|
|
538
|
+
function createNameKinship2(options = {}) {
|
|
539
|
+
const providers = Object.freeze([
|
|
540
|
+
...options.given ?? [givenFamilyTable2, givenFamilyList2]
|
|
541
|
+
]);
|
|
542
|
+
const givenVia = (x, y) => {
|
|
543
|
+
const [lo, hi] = x < y ? [x, y] : [y, x];
|
|
544
|
+
for (const provider of providers) {
|
|
545
|
+
const via = provider(lo, hi);
|
|
546
|
+
if (via !== null)
|
|
547
|
+
return via;
|
|
548
|
+
}
|
|
549
|
+
return null;
|
|
550
|
+
};
|
|
551
|
+
return (a, b) => {
|
|
552
|
+
if (!a.isPerson() || !b.isPerson())
|
|
553
|
+
return different("not-a-person");
|
|
554
|
+
const found = new Set;
|
|
555
|
+
if (a.surnameKey() !== b.surnameKey()) {
|
|
556
|
+
if (!surnamePartOf(a, b))
|
|
557
|
+
return different("surname-differs");
|
|
558
|
+
found.add("surname-part");
|
|
559
|
+
}
|
|
560
|
+
const ga = a.givenFields()[0];
|
|
561
|
+
const gb = b.givenFields()[0];
|
|
562
|
+
if (ga === undefined || gb === undefined)
|
|
563
|
+
return different("given-differs");
|
|
564
|
+
if (ga !== gb) {
|
|
565
|
+
const via = givenVia(ga, gb);
|
|
566
|
+
if (via === null)
|
|
567
|
+
return different("given-differs");
|
|
568
|
+
found.add(via);
|
|
569
|
+
}
|
|
570
|
+
const tail = tailRelation(a, b);
|
|
571
|
+
if (tail.relation === "different")
|
|
572
|
+
return different(tail.reason);
|
|
573
|
+
if (tail.relation === "candidate")
|
|
574
|
+
found.add(tail.reason);
|
|
575
|
+
const via = VIA_ORDER2.filter((v) => found.has(v));
|
|
576
|
+
return {
|
|
577
|
+
relation: via.length === 0 ? "same" : "candidate",
|
|
578
|
+
via,
|
|
579
|
+
reason: tail.reason,
|
|
580
|
+
gateSafe: via.every((v) => KINSHIP_VIAS2[v].gateSafe)
|
|
581
|
+
};
|
|
582
|
+
};
|
|
583
|
+
}
|
|
584
|
+
var nameKinship2 = createNameKinship2();
|
|
585
|
+
|
|
586
|
+
// src/normalize/name-compare.ts
|
|
587
|
+
function nameCompare2(a, b, dop) {
|
|
588
|
+
const x = levelOf2(a, dop);
|
|
589
|
+
const y = levelOf2(b, dop);
|
|
590
|
+
if (x < y)
|
|
591
|
+
return -1;
|
|
592
|
+
return x > y ? 1 : 0;
|
|
593
|
+
}
|
|
594
|
+
function probablySame2(a, b, dop) {
|
|
595
|
+
if (!a.isPerson() || !b.isPerson())
|
|
596
|
+
return false;
|
|
597
|
+
if (dop === "nn5")
|
|
598
|
+
return nameRelation2(a, b).relation !== "different";
|
|
599
|
+
return levelOf2(a, dop) === levelOf2(b, dop);
|
|
600
|
+
}
|
|
601
|
+
function slotOf(name, dop) {
|
|
602
|
+
return name.isPerson() ? levelOf2(name, dop) : null;
|
|
603
|
+
}
|
|
604
|
+
var loner = 0;
|
|
605
|
+
var lonerSlot = () => `\x1F\x1F${loner++}`;
|
|
606
|
+
function createNameSet2(dop) {
|
|
607
|
+
const held = new Map;
|
|
608
|
+
const set = {
|
|
609
|
+
dop,
|
|
610
|
+
get size() {
|
|
611
|
+
return held.size;
|
|
612
|
+
},
|
|
613
|
+
get: (name) => {
|
|
614
|
+
const slot = slotOf(name, dop);
|
|
615
|
+
return slot === null ? undefined : held.get(slot);
|
|
616
|
+
},
|
|
617
|
+
has: (name) => {
|
|
618
|
+
const slot = slotOf(name, dop);
|
|
619
|
+
return slot === null ? false : held.has(slot);
|
|
620
|
+
},
|
|
621
|
+
add(name) {
|
|
622
|
+
const slot = slotOf(name, dop);
|
|
623
|
+
if (slot === null)
|
|
624
|
+
held.set(lonerSlot(), name);
|
|
625
|
+
else if (!held.has(slot))
|
|
626
|
+
held.set(slot, name);
|
|
627
|
+
return this;
|
|
628
|
+
},
|
|
629
|
+
delete: (name) => {
|
|
630
|
+
const slot = slotOf(name, dop);
|
|
631
|
+
return slot === null ? false : held.delete(slot);
|
|
632
|
+
},
|
|
633
|
+
clear: () => held.clear(),
|
|
634
|
+
values: () => held.values(),
|
|
635
|
+
[Symbol.iterator]: () => held.values()
|
|
636
|
+
};
|
|
637
|
+
return set;
|
|
638
|
+
}
|
|
639
|
+
function createNameMap2(dop, entries) {
|
|
640
|
+
const held = new Map;
|
|
641
|
+
const map = {
|
|
642
|
+
dop,
|
|
643
|
+
get size() {
|
|
644
|
+
return held.size;
|
|
645
|
+
},
|
|
646
|
+
get: (name) => {
|
|
647
|
+
const key = slotOf(name, dop);
|
|
648
|
+
return key === null ? undefined : held.get(key)?.[1];
|
|
649
|
+
},
|
|
650
|
+
has: (name) => {
|
|
651
|
+
const key = slotOf(name, dop);
|
|
652
|
+
return key === null ? false : held.has(key);
|
|
653
|
+
},
|
|
654
|
+
set(name, value) {
|
|
655
|
+
const key = slotOf(name, dop);
|
|
656
|
+
const slot = key === null ? undefined : held.get(key);
|
|
657
|
+
if (slot === undefined)
|
|
658
|
+
held.set(key ?? lonerSlot(), [name, value]);
|
|
659
|
+
else
|
|
660
|
+
slot[1] = value;
|
|
661
|
+
return this;
|
|
662
|
+
},
|
|
663
|
+
delete: (name) => {
|
|
664
|
+
const key = slotOf(name, dop);
|
|
665
|
+
return key === null ? false : held.delete(key);
|
|
666
|
+
},
|
|
667
|
+
clear: () => held.clear(),
|
|
668
|
+
*keys() {
|
|
669
|
+
for (const [name] of held.values())
|
|
670
|
+
yield name;
|
|
671
|
+
},
|
|
672
|
+
*values() {
|
|
673
|
+
for (const [, value] of held.values())
|
|
674
|
+
yield value;
|
|
675
|
+
},
|
|
676
|
+
*entries() {
|
|
677
|
+
for (const slot of held.values())
|
|
678
|
+
yield [slot[0], slot[1]];
|
|
679
|
+
},
|
|
680
|
+
[Symbol.iterator]() {
|
|
681
|
+
return this.entries();
|
|
682
|
+
}
|
|
683
|
+
};
|
|
684
|
+
for (const [name, value] of entries ?? [])
|
|
685
|
+
map.set(name, value);
|
|
686
|
+
return map;
|
|
687
|
+
}
|
|
688
|
+
function kinshipBuckets(name) {
|
|
689
|
+
return [...new Set([name.surnameKey(), ...surnameParts2(name)])];
|
|
690
|
+
}
|
|
691
|
+
var RELATION_RANK = {
|
|
692
|
+
same: 0,
|
|
693
|
+
candidate: 1,
|
|
694
|
+
different: 2
|
|
695
|
+
};
|
|
696
|
+
function createNameIndex2(dop, options = {}) {
|
|
697
|
+
const kinship = options.kinship ?? nameKinship2;
|
|
698
|
+
const map = createNameMap2(dop);
|
|
699
|
+
const filed = new Map;
|
|
700
|
+
const buckets = new Map;
|
|
701
|
+
let seq = 0;
|
|
702
|
+
const file = (slot, name) => {
|
|
703
|
+
filed.set(slot, { name, seq: seq++ });
|
|
704
|
+
for (const key of kinshipBuckets(name)) {
|
|
705
|
+
const slots = buckets.get(key);
|
|
706
|
+
if (slots === undefined)
|
|
707
|
+
buckets.set(key, new Set([slot]));
|
|
708
|
+
else
|
|
709
|
+
slots.add(slot);
|
|
710
|
+
}
|
|
711
|
+
};
|
|
712
|
+
const unfile = (slot) => {
|
|
713
|
+
const held = filed.get(slot);
|
|
714
|
+
if (held === undefined)
|
|
715
|
+
return;
|
|
716
|
+
filed.delete(slot);
|
|
717
|
+
for (const key of kinshipBuckets(held.name)) {
|
|
718
|
+
const slots = buckets.get(key);
|
|
719
|
+
slots?.delete(slot);
|
|
720
|
+
if (slots?.size === 0)
|
|
721
|
+
buckets.delete(key);
|
|
722
|
+
}
|
|
723
|
+
};
|
|
724
|
+
const index = {
|
|
725
|
+
dop,
|
|
726
|
+
get size() {
|
|
727
|
+
return map.size;
|
|
728
|
+
},
|
|
729
|
+
get: (name) => map.get(name),
|
|
730
|
+
has: (name) => map.has(name),
|
|
731
|
+
set(name, value) {
|
|
732
|
+
const slot = slotOf(name, dop);
|
|
733
|
+
if (slot !== null && !filed.has(slot))
|
|
734
|
+
file(slot, name);
|
|
735
|
+
map.set(name, value);
|
|
736
|
+
return this;
|
|
737
|
+
},
|
|
738
|
+
delete: (name) => {
|
|
739
|
+
const slot = slotOf(name, dop);
|
|
740
|
+
if (slot !== null)
|
|
741
|
+
unfile(slot);
|
|
742
|
+
return map.delete(name);
|
|
743
|
+
},
|
|
744
|
+
clear: () => {
|
|
745
|
+
filed.clear();
|
|
746
|
+
buckets.clear();
|
|
747
|
+
map.clear();
|
|
748
|
+
},
|
|
749
|
+
keys: () => map.keys(),
|
|
750
|
+
values: () => map.values(),
|
|
751
|
+
entries: () => map.entries(),
|
|
752
|
+
[Symbol.iterator]: () => map.entries(),
|
|
753
|
+
related(name) {
|
|
754
|
+
const slots = new Set;
|
|
755
|
+
for (const key of kinshipBuckets(name)) {
|
|
756
|
+
for (const slot of buckets.get(key) ?? [])
|
|
757
|
+
slots.add(slot);
|
|
758
|
+
}
|
|
759
|
+
const found = [];
|
|
760
|
+
for (const slot of slots) {
|
|
761
|
+
const held = filed.get(slot);
|
|
762
|
+
if (held === undefined)
|
|
763
|
+
continue;
|
|
764
|
+
const k = kinship(name, held.name);
|
|
765
|
+
if (k.relation === "different")
|
|
766
|
+
continue;
|
|
767
|
+
found.push({
|
|
768
|
+
name: held.name,
|
|
769
|
+
value: map.get(held.name),
|
|
770
|
+
kinship: k,
|
|
771
|
+
seq: held.seq
|
|
772
|
+
});
|
|
773
|
+
}
|
|
774
|
+
found.sort((x, y) => RELATION_RANK[x.kinship.relation] - RELATION_RANK[y.kinship.relation] || x.seq - y.seq);
|
|
775
|
+
return found.map(({ name: n, value, kinship: k }) => ({ name: n, value, kinship: k }));
|
|
776
|
+
}
|
|
777
|
+
};
|
|
778
|
+
for (const [name, value] of options.entries ?? [])
|
|
779
|
+
index.set(name, value);
|
|
780
|
+
return index;
|
|
781
|
+
}
|
|
782
|
+
function createNameClusters2(dop, linkage) {
|
|
783
|
+
if (dop === "nn5") {
|
|
784
|
+
throw new Error('createNameClusters at nn5 is unimplemented: a linkage is defined over a distance and NN5 answers a relation. Use nameRelation / probablySame(a, b, "nn5"); see docs/NAME-NORMALIZATION.md § NN5 — the middle-name relation.');
|
|
785
|
+
}
|
|
786
|
+
const transitive = dop;
|
|
787
|
+
const held = new Map;
|
|
788
|
+
const out = {
|
|
789
|
+
dop,
|
|
790
|
+
linkage,
|
|
791
|
+
get size() {
|
|
792
|
+
return held.size;
|
|
793
|
+
},
|
|
794
|
+
add(name) {
|
|
795
|
+
const key = slotOf(name, transitive);
|
|
796
|
+
const cluster = key === null ? undefined : held.get(key);
|
|
797
|
+
if (cluster === undefined)
|
|
798
|
+
held.set(key ?? lonerSlot(), [name]);
|
|
799
|
+
else
|
|
800
|
+
cluster.push(name);
|
|
801
|
+
return this;
|
|
802
|
+
},
|
|
803
|
+
clusterOf: (name) => {
|
|
804
|
+
const key = slotOf(name, transitive);
|
|
805
|
+
return key === null ? undefined : held.get(key);
|
|
806
|
+
},
|
|
807
|
+
clusters: () => [...held.values()],
|
|
808
|
+
[Symbol.iterator]: () => held.values()
|
|
809
|
+
};
|
|
810
|
+
return out;
|
|
811
|
+
}
|
|
812
|
+
// src/normalize/name-infeasible.ts
|
|
813
|
+
var INFEASIBLE_CLASSES2 = ["merge?", "spelling", "rule-gap"];
|
|
814
|
+
function editDistance2(a, b) {
|
|
815
|
+
const prev = Array.from({ length: b.length + 1 }, (_, i) => i);
|
|
816
|
+
const cur = new Array(b.length + 1).fill(0);
|
|
817
|
+
for (let i = 1;i <= a.length; i++) {
|
|
818
|
+
cur[0] = i;
|
|
819
|
+
for (let j = 1;j <= b.length; j++) {
|
|
820
|
+
const sub = prev[j - 1] + (a[i - 1] === b[j - 1] ? 0 : 1);
|
|
821
|
+
cur[j] = Math.min(sub, prev[j] + 1, cur[j - 1] + 1);
|
|
822
|
+
}
|
|
823
|
+
for (let j = 0;j <= b.length; j++)
|
|
824
|
+
prev[j] = cur[j];
|
|
825
|
+
}
|
|
826
|
+
return prev[b.length];
|
|
827
|
+
}
|
|
828
|
+
function nearSpelling2(a, b) {
|
|
829
|
+
if (a[0] !== b[0])
|
|
830
|
+
return false;
|
|
831
|
+
const budget = Math.max(a.length, b.length) >= 6 ? 2 : 1;
|
|
832
|
+
return editDistance2(a, b) <= budget;
|
|
833
|
+
}
|
|
834
|
+
var compoundGiven2 = (a, b) => {
|
|
835
|
+
const [short, long] = a.length <= b.length ? [a, b] : [b, a];
|
|
836
|
+
return long.startsWith(short) && long.length - short.length >= 3;
|
|
837
|
+
};
|
|
838
|
+
function middleForm2(p) {
|
|
839
|
+
const given = p.givenFields();
|
|
840
|
+
return given.slice(1, Math.max(1, given.length - p.suffixes.length)).join("<");
|
|
841
|
+
}
|
|
842
|
+
var allPairs = (xs, f) => {
|
|
843
|
+
for (let i = 0;i < xs.length; i++) {
|
|
844
|
+
for (let j = i + 1;j < xs.length; j++) {
|
|
845
|
+
if (!f(xs[i], xs[j]))
|
|
846
|
+
return false;
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
return true;
|
|
850
|
+
};
|
|
851
|
+
var anyPair = (xs, f) => {
|
|
852
|
+
for (let i = 0;i < xs.length; i++) {
|
|
853
|
+
for (let j = i + 1;j < xs.length; j++) {
|
|
854
|
+
if (f(xs[i], xs[j]))
|
|
855
|
+
return true;
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
return false;
|
|
859
|
+
};
|
|
860
|
+
function infeasibleClass2(names) {
|
|
861
|
+
const surnames = [...new Set(names.map((p) => surnameParts2(p).join("-")))];
|
|
862
|
+
const firsts = [...new Set(names.map((p) => p.givenFields()[0] ?? ""))];
|
|
863
|
+
if (firsts.length > 1) {
|
|
864
|
+
if (anyPair(firsts, compoundGiven2))
|
|
865
|
+
return "rule-gap";
|
|
866
|
+
if (allPairs(firsts, nearSpelling2))
|
|
867
|
+
return "spelling";
|
|
868
|
+
return "merge?";
|
|
869
|
+
}
|
|
870
|
+
if (surnames.length > 1) {
|
|
871
|
+
if (allPairs(surnames, nearSpelling2))
|
|
872
|
+
return "spelling";
|
|
873
|
+
return "merge?";
|
|
874
|
+
}
|
|
875
|
+
const middles = [...new Set(names.map(middleForm2))].filter((f) => f !== "");
|
|
876
|
+
if (middles.length > 1 && allPairs(middles, nearSpelling2))
|
|
877
|
+
return "spelling";
|
|
878
|
+
return "rule-gap";
|
|
879
|
+
}
|
|
880
|
+
// src/normalize/person-list.ts
|
|
881
|
+
var JOINERS = new Set(["&", "and", "+"]);
|
|
882
|
+
function isListJoiner2(field) {
|
|
883
|
+
return JOINERS.has(field.trim().toLocaleLowerCase());
|
|
884
|
+
}
|
|
885
|
+
var SEPARATOR = /\s*[&;,]\s*|\s+\/\s+|\s+and\s+|\s+\+\s+/giu;
|
|
886
|
+
var LETTERS = new Set([
|
|
887
|
+
"adcrg",
|
|
888
|
+
"adcg",
|
|
889
|
+
"adcrn",
|
|
890
|
+
"bg",
|
|
891
|
+
"bgcrg",
|
|
892
|
+
"odcrn",
|
|
893
|
+
"sdcrg",
|
|
894
|
+
"tcg",
|
|
895
|
+
"tcrg",
|
|
896
|
+
"tmcrn",
|
|
897
|
+
"tmrf",
|
|
898
|
+
"phd",
|
|
899
|
+
"esq",
|
|
900
|
+
"jd"
|
|
901
|
+
]);
|
|
902
|
+
var TITLES = new Set(["dr", "mr", "mrs", "ms", "miss"]);
|
|
903
|
+
var MAX_NAME_TOKENS = 4;
|
|
904
|
+
var EDGE = /^[("“'‘[]+|[)"”'’\].:]+$/gu;
|
|
905
|
+
var bareOf = (token) => token.replace(EDGE, "").split("/")[0].replace(/\./gu, "");
|
|
906
|
+
function isLetters(token, begun, allCaps) {
|
|
907
|
+
if (/\p{N}/u.test(token))
|
|
908
|
+
return true;
|
|
909
|
+
const bare = bareOf(token);
|
|
910
|
+
if (bare === "" || LETTERS.has(bare.toLocaleLowerCase()))
|
|
911
|
+
return true;
|
|
912
|
+
return begun && !allCaps && bare.length >= 2 && /^\p{Lu}+$/u.test(bare);
|
|
913
|
+
}
|
|
914
|
+
function nameTokens(member, allCaps) {
|
|
915
|
+
let out = [];
|
|
916
|
+
for (const t of member.split(/\s+/u)) {
|
|
917
|
+
if (t === "")
|
|
918
|
+
continue;
|
|
919
|
+
if (t.endsWith(":")) {
|
|
920
|
+
out = [];
|
|
921
|
+
continue;
|
|
922
|
+
}
|
|
923
|
+
if (isLetters(t, out.length > 0, allCaps)) {
|
|
924
|
+
if (out.length > 0)
|
|
925
|
+
break;
|
|
926
|
+
continue;
|
|
927
|
+
}
|
|
928
|
+
if (out.length === 0 && TITLES.has(bareOf(t).toLocaleLowerCase()))
|
|
929
|
+
continue;
|
|
930
|
+
out.push(t);
|
|
931
|
+
}
|
|
932
|
+
return out;
|
|
933
|
+
}
|
|
934
|
+
var surnamePrint = (p) => [...p.surname.particles, p.surname.base].filter((s) => s !== "").join(" ");
|
|
935
|
+
function personList2(print, factory = defaultPersonNameFactory2()) {
|
|
936
|
+
const allCaps = !/\p{Ll}/u.test(print);
|
|
937
|
+
const members = print.split(SEPARATOR).map((m) => nameTokens(m, allCaps)).filter((tokens) => tokens.length > 0);
|
|
938
|
+
if (members.length === 0)
|
|
939
|
+
return [];
|
|
940
|
+
const whole = () => [
|
|
941
|
+
{ printed: print.trim(), name: factory.parse(print, "given-first"), surnameFrom: null }
|
|
942
|
+
];
|
|
943
|
+
if (members.some((tokens) => tokens.length > MAX_NAME_TOKENS))
|
|
944
|
+
return whole();
|
|
945
|
+
const out = [];
|
|
946
|
+
let pending = [];
|
|
947
|
+
for (const tokens of members) {
|
|
948
|
+
const printed = tokens.join(" ");
|
|
949
|
+
if (tokens.length === 1) {
|
|
950
|
+
pending.push(printed);
|
|
951
|
+
continue;
|
|
952
|
+
}
|
|
953
|
+
const name = factory.parse(printed, "given-first");
|
|
954
|
+
const surname = name.isPerson() ? surnamePrint(name) : "";
|
|
955
|
+
if (surname !== "")
|
|
956
|
+
for (const given of pending)
|
|
957
|
+
out.push({
|
|
958
|
+
printed: given,
|
|
959
|
+
name: factory.parse(`${given} ${surname}`, "given-first"),
|
|
960
|
+
surnameFrom: surname
|
|
961
|
+
});
|
|
962
|
+
pending = [];
|
|
963
|
+
out.push({ printed, name, surnameFrom: null });
|
|
964
|
+
}
|
|
965
|
+
return out;
|
|
966
|
+
}
|
|
967
|
+
// src/normalize/block-keys.ts
|
|
968
|
+
function blockLetters(surname) {
|
|
969
|
+
return surname.toUpperCase().replace(/[^A-Z]/g, "");
|
|
970
|
+
}
|
|
971
|
+
function firstLetter(s) {
|
|
972
|
+
const m = s.match(/[A-Za-z]/);
|
|
973
|
+
return m ? m[0].toUpperCase() : null;
|
|
974
|
+
}
|
|
975
|
+
function surnameBlock2(surname) {
|
|
976
|
+
return blockLetters(surname) || null;
|
|
977
|
+
}
|
|
978
|
+
function initialsBlock2(given, surname) {
|
|
979
|
+
const first = firstLetter(given);
|
|
980
|
+
const last = firstLetter(blockLetters(surname));
|
|
981
|
+
return first && last ? first + last : null;
|
|
982
|
+
}
|
|
983
|
+
function metaphoneBlocks2(surname) {
|
|
984
|
+
const letters = blockLetters(surname);
|
|
985
|
+
if (!letters)
|
|
986
|
+
return [];
|
|
987
|
+
const [primary, secondary] = doubleMetaphone2(letters);
|
|
988
|
+
if (!primary)
|
|
989
|
+
return [];
|
|
990
|
+
return secondary && secondary !== primary ? [primary, secondary] : [primary];
|
|
991
|
+
}
|
|
992
|
+
// src/normalize/name-columns.ts
|
|
993
|
+
var NAME_LEVEL_COLUMNS2 = [
|
|
994
|
+
"nn0",
|
|
995
|
+
"nn1",
|
|
996
|
+
"nn2",
|
|
997
|
+
"nn3",
|
|
998
|
+
"nn4"
|
|
999
|
+
];
|
|
1000
|
+
var NAME_COLUMNS2 = [
|
|
1001
|
+
...NAME_LEVEL_COLUMNS2,
|
|
1002
|
+
"surname_key",
|
|
1003
|
+
"given_key",
|
|
1004
|
+
"blk_surname",
|
|
1005
|
+
"blk_initials",
|
|
1006
|
+
"blk_metaphone",
|
|
1007
|
+
"blk_metaphone_alt"
|
|
1008
|
+
];
|
|
1009
|
+
function nameColumns2(name) {
|
|
1010
|
+
const person = name.isPerson();
|
|
1011
|
+
const level = (dop) => person ? name[dop] : null;
|
|
1012
|
+
const surname = person ? name.surnameKey() : "";
|
|
1013
|
+
const [metaphone, metaphoneAlt] = metaphoneBlocks2(surname);
|
|
1014
|
+
return {
|
|
1015
|
+
nn0: level("nn0"),
|
|
1016
|
+
nn1: level("nn1"),
|
|
1017
|
+
nn2: level("nn2"),
|
|
1018
|
+
nn3: level("nn3"),
|
|
1019
|
+
nn4: level("nn4"),
|
|
1020
|
+
surname_key: surname || null,
|
|
1021
|
+
given_key: person && name.givenName() || null,
|
|
1022
|
+
blk_surname: surnameBlock2(surname),
|
|
1023
|
+
blk_initials: person ? initialsBlock2(name.givenFields()[0] ?? "", surname) : null,
|
|
1024
|
+
blk_metaphone: metaphone ?? null,
|
|
1025
|
+
blk_metaphone_alt: metaphoneAlt ?? null
|
|
1026
|
+
};
|
|
1027
|
+
}
|
|
1028
|
+
// src/normalize/name-format.ts
|
|
1029
|
+
var NAME_FORMATS2 = ["First Last", "Last, First", "Last First"];
|
|
1030
|
+
function nameFormatOrder2(verdict) {
|
|
1031
|
+
if (verdict === "unknown")
|
|
1032
|
+
return "unknown";
|
|
1033
|
+
return verdict === "First Last" ? "given-first" : "surname-first";
|
|
1034
|
+
}
|
|
1035
|
+
var ORDER_READINGS2 = [
|
|
1036
|
+
"comma",
|
|
1037
|
+
"given-first:two-token",
|
|
1038
|
+
"surname-first:two-token",
|
|
1039
|
+
"given-first:one-token",
|
|
1040
|
+
"surname-first:one-token"
|
|
1041
|
+
];
|
|
1042
|
+
var HAS_LETTER = /\p{L}/u;
|
|
1043
|
+
var SPACED_DASH = /\s[-–—]\s/gu;
|
|
1044
|
+
function isPlaceField(field, flagsOf) {
|
|
1045
|
+
const words = field.split(/[\s,/]+/u).map((w) => w.replace(/^[.(]+|[.)]+$/gu, "")).filter((w) => w !== "");
|
|
1046
|
+
return words.length > 0 && words.every((w) => isPlaceWordFlags(flagsOf(w) ?? ""));
|
|
1047
|
+
}
|
|
1048
|
+
function givenOnly(flags) {
|
|
1049
|
+
return flags !== null && isGiven2(flags) && !isSurname2(flags) && !isAmbiguous2(flags);
|
|
1050
|
+
}
|
|
1051
|
+
function surnameOnly(flags) {
|
|
1052
|
+
return flags !== null && isSurname2(flags) && !isGiven2(flags) && !isAmbiguous2(flags);
|
|
1053
|
+
}
|
|
1054
|
+
function tokenOrder(a, b) {
|
|
1055
|
+
if (givenOnly(a) && surnameOnly(b))
|
|
1056
|
+
return "given-first:two-token";
|
|
1057
|
+
if (surnameOnly(a) && givenOnly(b))
|
|
1058
|
+
return "surname-first:two-token";
|
|
1059
|
+
if (a !== null && b !== null)
|
|
1060
|
+
return;
|
|
1061
|
+
if (b === null && givenOnly(a))
|
|
1062
|
+
return "given-first:one-token";
|
|
1063
|
+
if (a === null && surnameOnly(b))
|
|
1064
|
+
return "given-first:one-token";
|
|
1065
|
+
if (b === null && surnameOnly(a))
|
|
1066
|
+
return "surname-first:one-token";
|
|
1067
|
+
if (a === null && givenOnly(b))
|
|
1068
|
+
return "surname-first:one-token";
|
|
1069
|
+
return;
|
|
1070
|
+
}
|
|
1071
|
+
function orderReadingPoints(r) {
|
|
1072
|
+
return r === "given-first:two-token" || r === "given-first:one-token" ? "given-first" : "surname-first";
|
|
1073
|
+
}
|
|
1074
|
+
var flagLookups = new WeakMap;
|
|
1075
|
+
function flagsOfLexicon(lexicon) {
|
|
1076
|
+
let f = flagLookups.get(lexicon);
|
|
1077
|
+
if (f === undefined) {
|
|
1078
|
+
f = (t) => lexicon.getNameFlags(t);
|
|
1079
|
+
flagLookups.set(lexicon, f);
|
|
1080
|
+
}
|
|
1081
|
+
return f;
|
|
1082
|
+
}
|
|
1083
|
+
function readNameCell2(cell, options = {}) {
|
|
1084
|
+
const print = typeof cell === "string" ? cell : cell.name;
|
|
1085
|
+
const rowSchool = typeof cell === "string" ? "" : (cell.school ?? "").replace(/\s+/gu, " ").trim();
|
|
1086
|
+
const flagsOf = flagsOfLexicon(options.lexicon ?? Lexicon2.default());
|
|
1087
|
+
const readLocation = options.readLocation ?? true;
|
|
1088
|
+
const skip = (skipped) => ({
|
|
1089
|
+
print,
|
|
1090
|
+
skipped,
|
|
1091
|
+
name: "",
|
|
1092
|
+
commas: [],
|
|
1093
|
+
tokens: 0
|
|
1094
|
+
});
|
|
1095
|
+
let s = print.replace(/\s+/gu, " ").trim();
|
|
1096
|
+
if (s === "")
|
|
1097
|
+
return skip("empty");
|
|
1098
|
+
if (isNoNamePrint2(s) || isPlaceholderPrint2(s))
|
|
1099
|
+
return skip("no-name");
|
|
1100
|
+
if (isTeamPrint2(s))
|
|
1101
|
+
return skip("team");
|
|
1102
|
+
const places = [];
|
|
1103
|
+
const commas = [];
|
|
1104
|
+
let school;
|
|
1105
|
+
if (readLocation) {
|
|
1106
|
+
const colon = s.indexOf(":");
|
|
1107
|
+
if (colon > 0) {
|
|
1108
|
+
let tail = s.slice(colon + 1).trim();
|
|
1109
|
+
s = s.slice(0, colon).trim();
|
|
1110
|
+
const tailPlaces = [];
|
|
1111
|
+
for (;; ) {
|
|
1112
|
+
if (isPlaceField(tail, flagsOf)) {
|
|
1113
|
+
tailPlaces.unshift(tail);
|
|
1114
|
+
for (const _ of tail.matchAll(/,/gu))
|
|
1115
|
+
commas.push("place");
|
|
1116
|
+
tail = "";
|
|
1117
|
+
break;
|
|
1118
|
+
}
|
|
1119
|
+
const at = tail.lastIndexOf(",");
|
|
1120
|
+
if (at <= 0)
|
|
1121
|
+
break;
|
|
1122
|
+
const field = tail.slice(at + 1).trim();
|
|
1123
|
+
if (field === "" || !isPlaceField(field, flagsOf))
|
|
1124
|
+
break;
|
|
1125
|
+
tailPlaces.unshift(field);
|
|
1126
|
+
commas.push("place");
|
|
1127
|
+
tail = tail.slice(0, at).trim();
|
|
1128
|
+
}
|
|
1129
|
+
if (tailPlaces.length > 0)
|
|
1130
|
+
places.unshift(tailPlaces.join(", "));
|
|
1131
|
+
if (HAS_LETTER.test(tail))
|
|
1132
|
+
school = tail;
|
|
1133
|
+
}
|
|
1134
|
+
const paren = /\s*\(([^()]*)\)\s*$/u.exec(s);
|
|
1135
|
+
if (paren !== null && paren.index > 0 && isPlaceField(paren[1], flagsOf)) {
|
|
1136
|
+
places.unshift(paren[1].trim());
|
|
1137
|
+
s = s.slice(0, paren.index).trim();
|
|
1138
|
+
}
|
|
1139
|
+
for (;; ) {
|
|
1140
|
+
const dashes = [...s.matchAll(SPACED_DASH)];
|
|
1141
|
+
const dash = dashes[dashes.length - 1];
|
|
1142
|
+
if (dash === undefined || dash.index <= 0)
|
|
1143
|
+
break;
|
|
1144
|
+
const tail = s.slice(dash.index + dash[0].length).trim();
|
|
1145
|
+
if (!isPlaceField(tail, flagsOf))
|
|
1146
|
+
break;
|
|
1147
|
+
places.unshift(tail);
|
|
1148
|
+
for (const _ of tail.matchAll(/,/gu))
|
|
1149
|
+
commas.push("place");
|
|
1150
|
+
s = s.slice(0, dash.index).trim();
|
|
1151
|
+
}
|
|
1152
|
+
for (;; ) {
|
|
1153
|
+
const at = s.lastIndexOf(",");
|
|
1154
|
+
if (at <= 0)
|
|
1155
|
+
break;
|
|
1156
|
+
const tail = s.slice(at + 1).trim();
|
|
1157
|
+
if (tail === "" || !isPlaceField(tail, flagsOf))
|
|
1158
|
+
break;
|
|
1159
|
+
const head = s.slice(0, at).trim();
|
|
1160
|
+
if (tokenize(head.replace(/,/gu, " ")).filter((t) => HAS_LETTER.test(t)).length < 2)
|
|
1161
|
+
break;
|
|
1162
|
+
places.unshift(tail);
|
|
1163
|
+
commas.push("place");
|
|
1164
|
+
s = head;
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
s = s.replace(/\([^()]*\)/gu, " ").replace(/\s+/gu, " ").trim();
|
|
1168
|
+
if (!HAS_LETTER.test(s)) {
|
|
1169
|
+
if (places.length === 0)
|
|
1170
|
+
return skip("no-name");
|
|
1171
|
+
s = "";
|
|
1172
|
+
}
|
|
1173
|
+
if (school === undefined && rowSchool !== "" && HAS_LETTER.test(rowSchool)) {
|
|
1174
|
+
const at = s.toLocaleLowerCase().lastIndexOf(rowSchool.toLocaleLowerCase());
|
|
1175
|
+
if (at > 0 && at + rowSchool.length === s.length && /[\s,;:-]/u.test(s[at - 1] ?? "")) {
|
|
1176
|
+
const head = s.slice(0, at);
|
|
1177
|
+
if (HAS_LETTER.test(head)) {
|
|
1178
|
+
school = s.slice(at);
|
|
1179
|
+
if (/,\s*$/u.test(head))
|
|
1180
|
+
commas.push("glue");
|
|
1181
|
+
s = head.replace(/[\s,;:-]+$/u, "");
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
}
|
|
1185
|
+
if (school === undefined && options.segmenter && s !== "") {
|
|
1186
|
+
const words = s.split(" ");
|
|
1187
|
+
const kept = [];
|
|
1188
|
+
const clean = [];
|
|
1189
|
+
for (const [i, w] of words.entries()) {
|
|
1190
|
+
const c = w.replace(/^[,;.]+|[,;.]+$/gu, "");
|
|
1191
|
+
if (!HAS_LETTER.test(c))
|
|
1192
|
+
continue;
|
|
1193
|
+
kept.push(i);
|
|
1194
|
+
clean.push(c);
|
|
1195
|
+
}
|
|
1196
|
+
if (clean.length >= 2) {
|
|
1197
|
+
const seg = options.segmenter.segmentTokens(clean);
|
|
1198
|
+
if (seg.person.length > 0 && seg.school.length > 0) {
|
|
1199
|
+
const cut = kept[seg.person.length];
|
|
1200
|
+
school = seg.school.join(" ");
|
|
1201
|
+
if (seg.location.length > 0)
|
|
1202
|
+
places.unshift(seg.location.join(" "));
|
|
1203
|
+
const tail = words.slice(cut).join(" ");
|
|
1204
|
+
for (const _ of tail.matchAll(/,/gu))
|
|
1205
|
+
commas.push("glue");
|
|
1206
|
+
s = words.slice(0, cut).join(" ").replace(/[\s,;-]+$/u, "");
|
|
1207
|
+
if (/,\s*$/u.test(words.slice(0, cut).join(" ")))
|
|
1208
|
+
commas.push("glue");
|
|
1209
|
+
}
|
|
1210
|
+
}
|
|
1211
|
+
}
|
|
1212
|
+
let comma = commaReading2(s);
|
|
1213
|
+
if (comma === "separator" && readLocation && options.placeColumn) {
|
|
1214
|
+
const at = s.lastIndexOf(",");
|
|
1215
|
+
const head = s.slice(0, at).trim();
|
|
1216
|
+
const tail = s.slice(at + 1).trim();
|
|
1217
|
+
const headTokens = tokenize(head).filter((t) => HAS_LETTER.test(t));
|
|
1218
|
+
const tailTokens = tokenize(tail).filter((t) => HAS_LETTER.test(t));
|
|
1219
|
+
const givenInTail = tailTokens.some((t) => {
|
|
1220
|
+
const f = flagsOf(t);
|
|
1221
|
+
return f !== null && isGiven2(f);
|
|
1222
|
+
});
|
|
1223
|
+
if (headTokens.length >= 2 && tailTokens.length > 0 && !givenInTail) {
|
|
1224
|
+
places.push(tail);
|
|
1225
|
+
commas.push("place");
|
|
1226
|
+
s = head;
|
|
1227
|
+
comma = commaReading2(s);
|
|
1228
|
+
}
|
|
1229
|
+
}
|
|
1230
|
+
if (comma !== "none")
|
|
1231
|
+
commas.push(comma);
|
|
1232
|
+
const out = {
|
|
1233
|
+
print,
|
|
1234
|
+
name: s,
|
|
1235
|
+
commas,
|
|
1236
|
+
tokens: 0,
|
|
1237
|
+
...school !== undefined ? { school } : {},
|
|
1238
|
+
...places.length > 0 ? { location: places.join(", ") } : {}
|
|
1239
|
+
};
|
|
1240
|
+
const tokens = tokenize(s.replace(/,/gu, " ")).filter((t) => HAS_LETTER.test(t) && !isNameSuffix(t));
|
|
1241
|
+
out.tokens = tokens.length;
|
|
1242
|
+
if (comma === "separator") {
|
|
1243
|
+
out.order = "comma";
|
|
1244
|
+
} else if (comma !== "glue" && comma !== "particle" && tokens.length === 2) {
|
|
1245
|
+
const order = tokenOrder(flagsOf(tokens[0]), flagsOf(tokens[1]));
|
|
1246
|
+
if (order !== undefined)
|
|
1247
|
+
out.order = order;
|
|
1248
|
+
}
|
|
1249
|
+
if (out.order !== undefined)
|
|
1250
|
+
out.orderPoints = orderReadingPoints(out.order);
|
|
1251
|
+
return out;
|
|
1252
|
+
}
|
|
1253
|
+
function logGamma(x) {
|
|
1254
|
+
if (x < 0.5)
|
|
1255
|
+
return Math.log(Math.PI / Math.sin(Math.PI * x)) - logGamma(1 - x);
|
|
1256
|
+
const c = [
|
|
1257
|
+
0.9999999999998099,
|
|
1258
|
+
676.5203681218851,
|
|
1259
|
+
-1259.1392167224028,
|
|
1260
|
+
771.3234287776531,
|
|
1261
|
+
-176.6150291621406,
|
|
1262
|
+
12.507343278686905,
|
|
1263
|
+
-0.13857109526572012,
|
|
1264
|
+
0.000009984369578019572,
|
|
1265
|
+
0.00000015056327351493116
|
|
1266
|
+
];
|
|
1267
|
+
const z = x - 1;
|
|
1268
|
+
let a = c[0];
|
|
1269
|
+
const t = z + 7.5;
|
|
1270
|
+
for (let i = 1;i < 9; i++)
|
|
1271
|
+
a += c[i] / (z + i);
|
|
1272
|
+
return 0.5 * Math.log(2 * Math.PI) + (z + 0.5) * Math.log(t) - t + Math.log(a);
|
|
1273
|
+
}
|
|
1274
|
+
function binomialTail(k, n, p) {
|
|
1275
|
+
if (k <= 0)
|
|
1276
|
+
return 1;
|
|
1277
|
+
if (p <= 0)
|
|
1278
|
+
return 0;
|
|
1279
|
+
if (p >= 1)
|
|
1280
|
+
return 1;
|
|
1281
|
+
const lp = Math.log(p);
|
|
1282
|
+
const lq = Math.log1p(-p);
|
|
1283
|
+
let tail = 0;
|
|
1284
|
+
for (let j = k;j <= n; j++)
|
|
1285
|
+
tail += Math.exp(logGamma(n + 1) - logGamma(j + 1) - logGamma(n - j + 1) + j * lp + (n - j) * lq);
|
|
1286
|
+
return Math.min(1, tail);
|
|
1287
|
+
}
|
|
1288
|
+
function posterior(logs) {
|
|
1289
|
+
const m = Math.max(...logs);
|
|
1290
|
+
const e = logs.map((l) => Math.exp(l - m));
|
|
1291
|
+
const z = e.reduce((x, y) => x + y, 0);
|
|
1292
|
+
return e.map((x) => x / z);
|
|
1293
|
+
}
|
|
1294
|
+
var NAME_FORMAT_MODEL = {
|
|
1295
|
+
comma: { "First Last": 0.00001237, "Last, First": 0.9989, "Last First": 0.0009921 },
|
|
1296
|
+
surnameFirst: {
|
|
1297
|
+
"First Last": { twoToken: 0.0008603, oneToken: 0.02476 },
|
|
1298
|
+
"Last, First": { twoToken: 0.5, oneToken: 0.0625 },
|
|
1299
|
+
"Last First": { twoToken: 0.9972, oneToken: 0.9604 }
|
|
1300
|
+
},
|
|
1301
|
+
dissent: { "First Last": 0.004387, "Last, First": 0.0004406, "Last First": 0.01895 },
|
|
1302
|
+
smoothing: 0.25,
|
|
1303
|
+
mixedTail: 0.01,
|
|
1304
|
+
school: { recall: 0.9945, falseRate: 0.0248 },
|
|
1305
|
+
location: { recall: 0.6772, falseRate: 0.0001503 }
|
|
1306
|
+
};
|
|
1307
|
+
var DEFAULT_NAME_FORMAT_THRESHOLD2 = 0.95;
|
|
1308
|
+
function orderCounts(readings) {
|
|
1309
|
+
const counts = ORDER_READINGS2.map(() => 0);
|
|
1310
|
+
for (const r of readings)
|
|
1311
|
+
if (r.order !== undefined)
|
|
1312
|
+
counts[ORDER_READINGS2.indexOf(r.order)]++;
|
|
1313
|
+
return counts;
|
|
1314
|
+
}
|
|
1315
|
+
function orderLogLikelihood(counts, read, model, f) {
|
|
1316
|
+
const [comma, gf2, sf2, gf1, sf1] = counts;
|
|
1317
|
+
const k = model.comma[f];
|
|
1318
|
+
const s = model.surnameFirst[f];
|
|
1319
|
+
return comma * Math.log(k) + (read - comma) * Math.log1p(-k) + sf2 * Math.log(s.twoToken) + gf2 * Math.log1p(-s.twoToken) + sf1 * Math.log(s.oneToken) + gf1 * Math.log1p(-s.oneToken);
|
|
1320
|
+
}
|
|
1321
|
+
function partProbability(k, n, reader) {
|
|
1322
|
+
if (n === 0)
|
|
1323
|
+
return 0.5;
|
|
1324
|
+
const steps = 200;
|
|
1325
|
+
let lo = 0;
|
|
1326
|
+
let hi = 0;
|
|
1327
|
+
const logs = [];
|
|
1328
|
+
for (let i = 0;i < steps; i++) {
|
|
1329
|
+
const theta = (i + 0.5) / steps;
|
|
1330
|
+
const q = theta * reader.recall + (1 - theta) * reader.falseRate;
|
|
1331
|
+
logs.push(k * Math.log(q) + (n - k) * Math.log1p(-q));
|
|
1332
|
+
}
|
|
1333
|
+
const m = Math.max(...logs);
|
|
1334
|
+
for (const [i, l] of logs.entries()) {
|
|
1335
|
+
const w = Math.exp(l - m);
|
|
1336
|
+
if ((i + 0.5) / steps >= 0.5)
|
|
1337
|
+
hi += w;
|
|
1338
|
+
else
|
|
1339
|
+
lo += w;
|
|
1340
|
+
}
|
|
1341
|
+
return hi / (lo + hi);
|
|
1342
|
+
}
|
|
1343
|
+
function scoreReadings(readings, model, threshold, schoolRead) {
|
|
1344
|
+
const evidence = {
|
|
1345
|
+
prints: readings.length,
|
|
1346
|
+
skipped: { empty: 0, "no-name": 0, team: 0 },
|
|
1347
|
+
separatorCommas: 0,
|
|
1348
|
+
discountedCommas: { place: 0, glue: 0, trailing: 0, suffix: 0, particle: 0 },
|
|
1349
|
+
lexicon: {
|
|
1350
|
+
"given-first:two-token": 0,
|
|
1351
|
+
"surname-first:two-token": 0,
|
|
1352
|
+
"given-first:one-token": 0,
|
|
1353
|
+
"surname-first:one-token": 0
|
|
1354
|
+
},
|
|
1355
|
+
unread: 0,
|
|
1356
|
+
school: schoolRead ? 0 : null,
|
|
1357
|
+
location: 0
|
|
1358
|
+
};
|
|
1359
|
+
let read = 0;
|
|
1360
|
+
for (const r of readings) {
|
|
1361
|
+
if (r.skipped !== undefined) {
|
|
1362
|
+
evidence.skipped[r.skipped]++;
|
|
1363
|
+
continue;
|
|
1364
|
+
}
|
|
1365
|
+
read++;
|
|
1366
|
+
for (const c of r.commas) {
|
|
1367
|
+
if (c === "separator")
|
|
1368
|
+
evidence.separatorCommas++;
|
|
1369
|
+
else if (c !== "none")
|
|
1370
|
+
evidence.discountedCommas[c]++;
|
|
1371
|
+
}
|
|
1372
|
+
if (r.order === undefined)
|
|
1373
|
+
evidence.unread++;
|
|
1374
|
+
else if (r.order !== "comma")
|
|
1375
|
+
evidence.lexicon[r.order]++;
|
|
1376
|
+
if (r.school !== undefined && evidence.school !== null)
|
|
1377
|
+
evidence.school++;
|
|
1378
|
+
if (r.location !== undefined)
|
|
1379
|
+
evidence.location++;
|
|
1380
|
+
}
|
|
1381
|
+
const counts = orderCounts(readings);
|
|
1382
|
+
const oriented = counts.reduce((a, b) => a + b, 0);
|
|
1383
|
+
const smooth = (x, n, k) => n + model.smoothing === 0 ? x : (n * x + model.smoothing / k) / (n + model.smoothing);
|
|
1384
|
+
const p = posterior(NAME_FORMATS2.map((f) => orderLogLikelihood(counts, read, model, f))).map((x) => smooth(x, oriented, NAME_FORMATS2.length));
|
|
1385
|
+
const probabilities = Object.fromEntries(NAME_FORMATS2.map((f, i) => [f, p[i]]));
|
|
1386
|
+
let best = 0;
|
|
1387
|
+
for (let i = 1;i < p.length; i++)
|
|
1388
|
+
if (p[i] > p[best])
|
|
1389
|
+
best = i;
|
|
1390
|
+
const leading = NAME_FORMATS2[best];
|
|
1391
|
+
const against = nameFormatOrder2(leading) === "given-first" ? "surname-first" : "given-first";
|
|
1392
|
+
const dissent = readings.filter((r) => r.orderPoints === against);
|
|
1393
|
+
const mixed = dissent.length > 0 && binomialTail(dissent.length, oriented, model.dissent[leading]) < model.mixedTail;
|
|
1394
|
+
const verdict = !mixed && p[best] >= threshold ? leading : "unknown";
|
|
1395
|
+
return {
|
|
1396
|
+
probabilities,
|
|
1397
|
+
verdict,
|
|
1398
|
+
hasSchool: evidence.school === null ? null : smooth(partProbability(evidence.school, read, model.school), read, 2),
|
|
1399
|
+
hasLocation: smooth(partProbability(evidence.location, read, model.location), read, 2),
|
|
1400
|
+
mixed,
|
|
1401
|
+
evidence,
|
|
1402
|
+
dissent,
|
|
1403
|
+
readings: [...readings]
|
|
1404
|
+
};
|
|
1405
|
+
}
|
|
1406
|
+
function deriveNameFormat2(prints, options = {}) {
|
|
1407
|
+
const cells = [...prints];
|
|
1408
|
+
const threshold = options.threshold ?? DEFAULT_NAME_FORMAT_THRESHOLD2;
|
|
1409
|
+
const schoolRead = Boolean(options.segmenter) || cells.some((c) => typeof c !== "string" && Boolean(c.school));
|
|
1410
|
+
const first = scoreReadings(cells.map((c) => readNameCell2(c, options)), NAME_FORMAT_MODEL, threshold, schoolRead);
|
|
1411
|
+
if (first.hasLocation <= 0.5 || options.readLocation === false || options.placeColumn)
|
|
1412
|
+
return first;
|
|
1413
|
+
return scoreReadings(cells.map((c) => readNameCell2(c, { ...options, placeColumn: true })), NAME_FORMAT_MODEL, threshold, schoolRead);
|
|
1414
|
+
}
|
|
1415
|
+
export { repairMojibake2, comparePosition2, nameRelation2, MAX_READINGS2, surnameParts2, printAttestsOrder2, nameReadings2, meetReading2, refinesReading2, sameReading2, chooseReadingPair2, composeReadings2, composeNames2, meetReadings2, nameMeet2, showReading2, blockKeysOfReadings2, nameBlockKeys2, namesFeasible2, KINSHIP_VIAS2, createNameKinship2, nameKinship2, nameCompare2, probablySame2, createNameSet2, createNameMap2, createNameIndex2, createNameClusters2, INFEASIBLE_CLASSES2, editDistance2, nearSpelling2, compoundGiven2, middleForm2, infeasibleClass2, isListJoiner2, personList2, surnameBlock2, initialsBlock2, metaphoneBlocks2, NAME_LEVEL_COLUMNS2, NAME_COLUMNS2, nameColumns2, NAME_FORMATS2, nameFormatOrder2, ORDER_READINGS2, readNameCell2, DEFAULT_NAME_FORMAT_THRESHOLD2, deriveNameFormat2 };
|
|
1416
|
+
|
|
1417
|
+
//# debugId=3A882E8BB4656FA364756E2164756E21
|
|
1418
|
+
//# sourceMappingURL=index-n6fpj92s.js.map
|