@finbheara/names 0.12.0 → 0.13.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.
@@ -1,13 +1,9 @@
1
1
  import {
2
2
  repairMojibake2,
3
3
  sameGivenFamily2,
4
+ givenFamilyTable2,
4
5
  comparePosition2,
5
6
  nameRelation2,
6
- nameCompare2,
7
- probablySame2,
8
- createNameSet2,
9
- createNameMap2,
10
- createNameClusters2,
11
7
  MAX_READINGS2,
12
8
  surnameParts2,
13
9
  printAttestsOrder2,
@@ -24,6 +20,15 @@ import {
24
20
  blockKeysOfReadings2,
25
21
  nameBlockKeys2,
26
22
  namesFeasible2,
23
+ KINSHIP_VIAS2,
24
+ createNameKinship2,
25
+ nameKinship2,
26
+ nameCompare2,
27
+ probablySame2,
28
+ createNameSet2,
29
+ createNameMap2,
30
+ createNameIndex2,
31
+ createNameClusters2,
27
32
  INFEASIBLE_CLASSES2,
28
33
  editDistance2,
29
34
  nearSpelling2,
@@ -44,7 +49,7 @@ import {
44
49
  readNameCell2,
45
50
  DEFAULT_NAME_FORMAT_THRESHOLD2,
46
51
  deriveNameFormat2
47
- } from "../chunks/index-vqbvesky.js";
52
+ } from "../chunks/index-wq6kn9jq.js";
48
53
  import"../chunks/index-kw6gbrnq.js";
49
54
  import {
50
55
  NAME_REFUSED2,
@@ -110,6 +115,7 @@ export {
110
115
  DEFAULT_PERSON_NAME_CAPACITY2 as DEFAULT_PERSON_NAME_CAPACITY,
111
116
  GIVEN_SEPARATOR2 as GIVEN_SEPARATOR,
112
117
  INFEASIBLE_CLASSES2 as INFEASIBLE_CLASSES,
118
+ KINSHIP_VIAS2 as KINSHIP_VIAS,
113
119
  MAX_READINGS2 as MAX_READINGS,
114
120
  NAME_COLUMNS2 as NAME_COLUMNS,
115
121
  NAME_FORMATS2 as NAME_FORMATS,
@@ -135,6 +141,8 @@ export {
135
141
  compoundGiven2 as compoundGiven,
136
142
  createCachingPersonNameFactory2 as createCachingPersonNameFactory,
137
143
  createNameClusters2 as createNameClusters,
144
+ createNameIndex2 as createNameIndex,
145
+ createNameKinship2 as createNameKinship,
138
146
  createNameMap2 as createNameMap,
139
147
  createNameSet2 as createNameSet,
140
148
  createPersonNameFactory2 as createPersonNameFactory,
@@ -144,6 +152,7 @@ export {
144
152
  foldNameKey2 as foldNameKey,
145
153
  formatPersonDisplay2 as formatPersonDisplay,
146
154
  givenAbbreviates2 as givenAbbreviates,
155
+ givenFamilyTable2 as givenFamilyTable,
147
156
  givenKeyAbbreviates2 as givenKeyAbbreviates,
148
157
  infeasibleClass2 as infeasibleClass,
149
158
  inferNameOrder2 as inferNameOrder,
@@ -167,6 +176,7 @@ export {
167
176
  nameCompare2 as nameCompare,
168
177
  nameFold2 as nameFold,
169
178
  nameFormatOrder2 as nameFormatOrder,
179
+ nameKinship2 as nameKinship,
170
180
  nameMeet2 as nameMeet,
171
181
  nameReadings2 as nameReadings,
172
182
  nameRelation2 as nameRelation,
@@ -204,5 +214,5 @@ export {
204
214
  traditionLookupOf2 as traditionLookupOf
205
215
  };
206
216
 
207
- //# debugId=5FE4459EB5725BDA64756E2164756E21
217
+ //# debugId=6CAF39AD5BAB8B2064756E2164756E21
208
218
  //# sourceMappingURL=index.js.map
@@ -4,6 +4,6 @@
4
4
  "sourcesContent": [
5
5
  ],
6
6
  "mappings": "",
7
- "debugId": "5FE4459EB5725BDA64756E2164756E21",
7
+ "debugId": "6CAF39AD5BAB8B2064756E2164756E21",
8
8
  "names": []
9
9
  }
@@ -11,7 +11,8 @@
11
11
  * A RELATION OF A PAIR AND NEVER A KEY. Nothing here rewrites a given name: a stored key,
12
12
  * a filing key and every rendering keep the print's own spelling. A caller asks whether
13
13
  * TWO given names are one family, at the moment it compares them, and nowhere else, so the
14
- * root never leaves this module: `sameGivenFamily` is the one export, for that reason.
14
+ * root never leaves this module: `sameGivenFamily` and `givenFamilyTable` answer about a
15
+ * pair and neither returns a root, for that reason.
15
16
  *
16
17
  * THE GATE READS LESS THAN THE LANE. `families` is the Python lane's table, first root
17
18
  * wins, and the lane reads every form in it as evidence below an exact match. A GATE that
@@ -29,3 +30,15 @@
29
30
  * tolerance past it, so a caller can tell which one admitted a pair.
30
31
  */
31
32
  export declare function sameGivenFamily(a: string, b: string): boolean;
33
+ /**
34
+ * What the table says about two DIFFERENT given names (NN3, letters only), as a kinship
35
+ * `via`: `given-family` where `sameGivenFamily` admits the pair, `given-family-not-gate`
36
+ * where the two share a root but either is a `notAtGate` form, and `null` where the table
37
+ * does not relate them. Two equal names answer `null`, for `sameGivenFamily`'s reason.
38
+ *
39
+ * THE DEFAULT GIVEN-NAME PROVIDER of `createNameKinship` (`./name-kinship.ts`). It reads the
40
+ * `ROOT` and `NOT_AT_GATE` that `sameGivenFamily` reads, and `sameGivenFamily` is defined as
41
+ * its `given-family` answer, so the gate and the tolerant relation cannot disagree about a
42
+ * pair.
43
+ */
44
+ export declare function givenFamilyTable(a: string, b: string): "given-family" | "given-family-not-gate" | null;
@@ -18,9 +18,10 @@ export { type CommaReading, type NameOrder, type NameSplit, type ResolvedNameOrd
18
18
  export { TEAM_DESIGNATION_WORDS, TEAM_WORD, type TeamSuffixPartition, canonicalTeamName, isCanonicalTeamName, isTeamPrint, partitionTeamSuffix, teamLetter, teamRowDesignation, } from "./team-name.ts";
19
19
  export { foldNameKey, nameFold } from "./name-fold.ts";
20
20
  export { COMPOUND_SURNAMES, isCompoundSurname } from "./compound-surnames.ts";
21
- export { sameGivenFamily } from "./given-diminutives.ts";
22
- export { type ClusterLinkage, type Clusters, type NameMap, type NameSet, createNameClusters, createNameMap, createNameSet, nameCompare, probablySame, } from "./name-compare.ts";
21
+ export { givenFamilyTable, sameGivenFamily } from "./given-diminutives.ts";
22
+ export { type ClusterLinkage, type Clusters, type NameIndex, type NameIndexOptions, type NameMap, type NameMatch, type NameSet, createNameClusters, createNameIndex, createNameMap, createNameSet, nameCompare, probablySame, } from "./name-compare.ts";
23
23
  export { type NameReading, type NameRelation, type NameRelationReason, comparePosition, nameRelation, } from "./name-middle.ts";
24
+ export { type GivenProvider, type GivenVia, KINSHIP_VIAS, type KinshipReason, type KinshipVia, type MiddleVia, type MiddleViasAreNn5Reasons, type NameKinship, type NameKinshipOptions, type NameKinshipRelation, createNameKinship, nameKinship, } from "./name-kinship.ts";
24
25
  export * from "./name-compose.ts";
25
26
  export { INFEASIBLE_CLASSES, type InfeasibleClass, compoundGiven, editDistance, infeasibleClass, middleForm, nearSpelling, } from "./name-infeasible.ts";
26
27
  export { type ListedPerson, isListJoiner, personList } from "./person-list.ts";
@@ -0,0 +1,9 @@
1
+ /**
2
+ * multiset.ts — the one sub-multiset test over surname parts.
3
+ *
4
+ * MOVED HERE FROM `./name-compose.ts` so that `./name-kinship.ts` reads the same test the
5
+ * meet of two surnames reads, without either file importing the other and without the test
6
+ * joining the package's surface (`index.ts` re-exports `name-compose.ts` whole).
7
+ */
8
+ /** Is `outer` a sub-multiset of `inner`? `[Walter] ⊒ [Walter, Oreskarard]`. */
9
+ export declare function subMultiset(outer: readonly string[], inner: readonly string[]): boolean;
@@ -26,6 +26,7 @@
26
26
  * argument at the call site IS the justification, inline and readable, which is
27
27
  * why no catalog of imports is owed.
28
28
  */
29
+ import { type NameKinship, type NameKinshipRelation } from "./name-kinship.ts";
29
30
  import { type Level, type PersonName, type TransitiveLevel } from "./person-name.ts";
30
31
  /**
31
32
  * Order two parsed names at one level.
@@ -124,6 +125,48 @@ export declare function createNameSet(dop: TransitiveLevel): NameSet;
124
125
  * `createNameMap("nn5")` DOES NOT COMPILE — see `createNameSet`.
125
126
  */
126
127
  export declare function createNameMap<V>(dop: TransitiveLevel, entries?: Iterable<readonly [PersonName, V]>): NameMap<V>;
128
+ /** One held entry a tolerant lookup found, with what the relation said about it. */
129
+ export interface NameMatch<V> {
130
+ /** the print the entry is held under — the first print filed into its slot */
131
+ readonly name: PersonName;
132
+ readonly value: V;
133
+ readonly kinship: NameKinship;
134
+ }
135
+ /** A `NameMap` that can also answer which held names are RELATED to a print. */
136
+ export interface NameIndex<V> extends NameMap<V> {
137
+ /**
138
+ * Every held entry the index's relation does not call `different`, `same` before
139
+ * `candidate` and otherwise in insertion order. `[]` for a print that names no person.
140
+ */
141
+ related(name: PersonName): readonly NameMatch<V>[];
142
+ }
143
+ export interface NameIndexOptions<V> {
144
+ /** The tolerant relation `related` filters by. Default `nameKinship`. */
145
+ readonly kinship?: NameKinshipRelation;
146
+ readonly entries?: Iterable<readonly [PersonName, V]>;
147
+ }
148
+ /**
149
+ * A map keyed on parsed names at one declared level, with a TOLERANT LOOKUP beside it.
150
+ *
151
+ * `get`, `has`, `set` and `delete` ARE `createNameMap`'s, unchanged and delegated to: the
152
+ * exact answer at a transitive level is still the slot. `related(name)` is the other
153
+ * question — which held names MAY be this person — and it is answered by a relation, not by
154
+ * a slot: candidates come from a secondary index on the surname key and every surname part
155
+ * (so `Mary Larson` finds `Mary Conley-Larson` and the other way round), and each candidate
156
+ * is kept when the relation does not call it `different`.
157
+ *
158
+ * A SIBLING OF `NameMap` AND NOT A METHOD ON IT, for three reasons. The tolerant answer is
159
+ * NOT TRANSITIVE, and `NameMap`'s contract is an equivalence class; putting a non-transitive
160
+ * lookup on it would blur exactly the boundary the type split above draws. The lookup needs
161
+ * a RELATION, and a relation is chosen by options (its given-name providers), which a map
162
+ * keyed by `dop` alone has no place to hold. And the secondary index costs memory on every
163
+ * `set`, which a caller that only wants the exact map should not pay. A `NameIndex` IS a
164
+ * `NameMap`, so a caller that needs both holds one structure.
165
+ *
166
+ * A print that names no person is held, counted and iterated as `createNameMap` holds it,
167
+ * and is never related to anything, in either direction: `probablySame`'s rule.
168
+ */
169
+ export declare function createNameIndex<V>(dop: TransitiveLevel, options?: NameIndexOptions<V>): NameIndex<V>;
127
170
  /** How a clustering decides whether a name joins an existing cluster. */
128
171
  export type ClusterLinkage = "single" | "complete" | "average";
129
172
  /** A clustering over parsed names. Order-dependent by construction at NN5. */
@@ -0,0 +1,165 @@
1
+ /**
2
+ * name-kinship.ts — THE TYPED, TOLERANT NAME RELATION.
3
+ *
4
+ * `docs/NAME-NORMALIZATION.md` § *Name kinship — the tolerant relation* is the specification.
5
+ *
6
+ * ## WHAT IT ADDS TO NN5
7
+ *
8
+ * `nameRelation` (`./name-middle.ts`) is the strict identity relation: the surname and the
9
+ * first given name must agree at NN3, and only a middle name is tolerated. A different head
10
+ * is rightly `different` there. The other tolerances already exist, one helper each —
11
+ * `givenFamilyTable` / `sameGivenFamily` for a given name, `surnameParts` for a compound
12
+ * surname — and a caller that wanted them had to compose them by hand. This is that
13
+ * composition, done once, and it REPORTS WHICH TOLERANCES A PAIR PASSED THROUGH so that each
14
+ * caller decides how much to accept:
15
+ *
16
+ * Kate Smith / Katherine Smith candidate via [given-family]
17
+ * Mary Larson / Mary Conley-Larson candidate via [surname-part]
18
+ * Kate M Smith / Katherine Conley-Smith
19
+ * candidate via [surname-part, given-family, middle-absent]
20
+ *
21
+ * ## `via` IS A SET, NOT A KIND
22
+ *
23
+ * A pair can pass through several tolerances at once, one per field, and naming the
24
+ * combinations (`both`) grows as the product of the fields. So `via` lists every tolerance
25
+ * used, in the fixed order of `KINSHIP_VIAS`, and is empty for a pair NN3 already agreed on.
26
+ *
27
+ * ## `candidate` IS STILL NOT A MERGE
28
+ *
29
+ * The three values are `NameRelation`'s and mean what they mean there. Every tolerance here
30
+ * is "may be the same person, the rest of the evidence decides". `gateSafe` says something
31
+ * narrower: every tolerance used is one this package's own gate comparators already admit —
32
+ * a middle name `probablySame(…, "nn5")` admits, a given-name family `sameGivenFamily`
33
+ * admits, a compound the print itself carries. A table form that is a name in its own right
34
+ * (`given-family-not-gate`) or a probabilistic source (`given-maybe`) is a lead for the rest
35
+ * of the evidence and never a gate.
36
+ *
37
+ * ## THE GIVEN-NAME TOLERANCE IS INJECTED
38
+ *
39
+ * Which given names are one family is DATA, and the data will move: a probabilistic source
40
+ * of diminutive pairs is expected beside the curated table, and the table may shrink. So the
41
+ * relation asks an ordered list of `GivenProvider`s fixed by `createNameKinship`'s options,
42
+ * and the core knows no table. A new provider is an option; a new kind of answer is one
43
+ * line in `KINSHIP_VIAS`, which also carries its gate-safety, so no branch here changes.
44
+ *
45
+ * ## IT COMPOSES, IT DOES NOT FORK
46
+ *
47
+ * The middle-name and suffix rows are `tailRelation`, the half of `nameRelation` below the
48
+ * head, so `J` against `Jane` is decided in one place. With no tolerance used this answers
49
+ * exactly what `nameRelation` answers (`normalize-name-kinship.spec.ts` pins it), except that
50
+ * it splits `head-differs` into the half that differed.
51
+ */
52
+ import { type NameRelation, type NameRelationReason } from "./name-middle.ts";
53
+ import type { PersonName } from "./person-name.ts";
54
+ /**
55
+ * EVERY TOLERANCE THE RELATION CAN REPORT, IN THE ORDER IT REPORTS THEM, with the field it
56
+ * tolerates and whether a gate may admit a pair through it. The one declaration: the
57
+ * `KinshipVia`, `GivenVia` and `MiddleVia` types are read off it, so adding a kind is a
58
+ * line here and nothing else in the core.
59
+ */
60
+ export declare const KINSHIP_VIAS: Readonly<{
61
+ /** one surname is a whole hyphen- or space-joined part of the other's compound */
62
+ readonly "surname-part": Readonly<{
63
+ field: "surname";
64
+ gateSafe: true;
65
+ }>;
66
+ /** the given-name table relates the two first given names, and the gate admits them */
67
+ readonly "given-family": Readonly<{
68
+ field: "given";
69
+ gateSafe: true;
70
+ }>;
71
+ /** the table relates them through a form that is a name in its own right */
72
+ readonly "given-family-not-gate": Readonly<{
73
+ field: "given";
74
+ gateSafe: false;
75
+ }>;
76
+ /** a probabilistic source says the two MAY be one family; reserved for one */
77
+ readonly "given-maybe": Readonly<{
78
+ field: "given";
79
+ gateSafe: false;
80
+ }>;
81
+ /** NN5's own: one side carries no middle name */
82
+ readonly "middle-absent": Readonly<{
83
+ field: "middle";
84
+ gateSafe: true;
85
+ }>;
86
+ /** NN5's own: an initial against a full middle whose first letter matches */
87
+ readonly "initial-agrees": Readonly<{
88
+ field: "middle";
89
+ gateSafe: true;
90
+ }>;
91
+ }>;
92
+ /** A tolerance a pair passed through. */
93
+ export type KinshipVia = keyof typeof KINSHIP_VIAS;
94
+ type ViaOfField<F extends string> = {
95
+ [K in KinshipVia]: (typeof KINSHIP_VIAS)[K]["field"] extends F ? K : never;
96
+ }[KinshipVia];
97
+ /** The tolerances a `GivenProvider` may answer with. */
98
+ export type GivenVia = ViaOfField<"given">;
99
+ /** The middle-name tolerances: NN5's two `candidate` reasons, by the same names. */
100
+ export type MiddleVia = ViaOfField<"middle">;
101
+ /**
102
+ * Why the relation came out the way it did. NN5's reasons, except that `head-differs` is
103
+ * split into the half that differed, because this relation tolerates each half on its own.
104
+ */
105
+ export type KinshipReason = Exclude<NameRelationReason, "head-differs">
106
+ /** the surnames are neither equal at NN3 nor one a whole part of the other */
107
+ | "surname-differs"
108
+ /** the first given names are neither equal at NN3 nor related by any provider (or one is missing) */
109
+ | "given-differs";
110
+ /** The tolerant relation's answer. */
111
+ export interface NameKinship {
112
+ /** `NameRelation`'s three values, meaning what they mean there: `candidate` is never a merge */
113
+ readonly relation: NameRelation;
114
+ /** every tolerance used, in `KINSHIP_VIAS` order; empty for `same` and for `different` */
115
+ readonly via: readonly KinshipVia[];
116
+ /** the veto for `different`; NN5's middle-name reason otherwise */
117
+ readonly reason: KinshipReason;
118
+ /** true only when the pair is related and every `via` is gate-safe */
119
+ readonly gateSafe: boolean;
120
+ }
121
+ /**
122
+ * Might two DIFFERENT first given names (NN3 fields, so upper case and letters) be one
123
+ * family, and by which tolerance? `null` is "this provider does not relate them".
124
+ *
125
+ * IT IS ASKED WITH `a < b` in code-unit order, once per pair, so a provider holding ordered
126
+ * pairs needs to hold each pair one way only and the relation stays symmetric whatever the
127
+ * provider does.
128
+ */
129
+ export type GivenProvider = (a: string, b: string) => GivenVia | null;
130
+ export interface NameKinshipOptions {
131
+ /**
132
+ * The given-name tolerances, asked in order; THE FIRST NON-NULL ANSWER WINS, so a caller
133
+ * lists its most trusted source first. Default `[givenFamilyTable]`; `[]` tolerates no
134
+ * given name.
135
+ */
136
+ readonly given?: readonly GivenProvider[];
137
+ }
138
+ /** A tolerant relation with its providers fixed. */
139
+ export type NameKinshipRelation = (a: PersonName, b: PersonName) => NameKinship;
140
+ /**
141
+ * Make the tolerant relation over a fixed list of given-name providers.
142
+ *
143
+ * The rule table, in the order it is applied:
144
+ *
145
+ * | the two sides | relation |
146
+ * |---|---|
147
+ * | either names no person | `different`, `not-a-person` |
148
+ * | surnames differ at NN3 and neither is a whole part of the other | `different`, `surname-differs` |
149
+ * | first given names differ at NN3 (or one is missing) and no provider relates them | `different`, `given-differs` |
150
+ * | NN5's suffix and middle rows (`tailRelation`) say `different` | `different`, NN5's reason |
151
+ * | no tolerance used and the middles are equal | `same` |
152
+ * | otherwise | `candidate`, via every tolerance used |
153
+ */
154
+ export declare function createNameKinship(options?: NameKinshipOptions): NameKinshipRelation;
155
+ /** The tolerant relation over the curated given-name table, `givenFamilyTable`. */
156
+ export declare const nameKinship: NameKinshipRelation;
157
+ /**
158
+ * THE MIDDLE VIAS ARE NN5'S CANDIDATE REASONS, ASSERTED IN TYPECHECKED SOURCE. A middle
159
+ * `via` is `tailRelation`'s reason passed through, so the day either list moves without the
160
+ * other, `bun run typecheck` goes red here. Exported for `Nn5IsNotAMemberOfAParse`'s reason.
161
+ */
162
+ type AssertTrue<T extends true> = T;
163
+ /** Every middle `via` is a reason NN5 can give. */
164
+ export type MiddleViasAreNn5Reasons = AssertTrue<MiddleVia extends NameRelationReason ? true : false>;
165
+ export {};
@@ -156,6 +156,17 @@ export declare function comparePosition(x: string, y: string): NameRelationReaso
156
156
  * not this issue's to close.
157
157
  */
158
158
  export declare function nameRelation(a: PersonName, b: PersonName): NameReading;
159
+ /**
160
+ * NN5's rule table BELOW THE HEAD: the suffix row and every middle-name row, for two
161
+ * persons whose surname and first given name the CALLER has already accepted.
162
+ *
163
+ * MOVED OUT OF `nameRelation`, NOT FORKED. `nameRelation` checks the head by equality and
164
+ * hands the rest here; `./name-kinship.ts` accepts the head through its own tolerances
165
+ * (a given-name family, a surname part) and hands the rest here too, so the two relations
166
+ * read one middle-name rule and cannot disagree about `J` and `Jane`. It is not on the
167
+ * package's surface: a caller that has not decided the head has no business asking it.
168
+ */
169
+ export declare function tailRelation(a: PersonName, b: PersonName): NameReading;
159
170
  /**
160
171
  * THE OWNER'S RULE, ASSERTED IN TYPECHECKED SOURCE.
161
172
  *
@@ -448,3 +448,68 @@ tolerating accent, punctuation and order.
448
448
  classifier: its declared-order weights were fitted only on documents that declared that
449
449
  order, and a guess is not a declaration. With no order, or `unknown`, the classifier
450
450
  answers exactly as it does without the argument.
451
+
452
+ ## Name kinship — the tolerant relation
453
+
454
+ `nameRelation` is the strict identity relation: the surname and the first given name MUST
455
+ agree at NN3, and only a middle name is tolerated. `nameKinship(a, b)` is the tolerant
456
+ relation built on it. It reports **which tolerances a pair passed through**, so each caller
457
+ decides how much to accept.
458
+
459
+ ```ts
460
+ interface NameKinship {
461
+ relation: "same" | "candidate" | "different"; // NameRelation's values and meanings
462
+ via: readonly KinshipVia[]; // every tolerance used, KINSHIP_VIAS order
463
+ reason: KinshipReason; // the veto, or NN5's middle-name reason
464
+ gateSafe: boolean; // related, and every via is gate-safe
465
+ }
466
+ ```
467
+
468
+ | via | field | gate-safe | when |
469
+ |---|---|---|---|
470
+ | `surname-part` | surname | yes | one surname is a whole hyphen- or space-joined part of the other's compound |
471
+ | `given-family` | given | yes | a provider says the two first given names are one family, and a gate admits them |
472
+ | `given-family-not-gate` | given | no | the table relates them through a `notAtGate` form |
473
+ | `given-maybe` | given | no | a probabilistic source says they MAY be one family |
474
+ | `middle-absent` | middle | yes | NN5's: one side carries no middle |
475
+ | `initial-agrees` | middle | yes | NN5's: an initial against a full middle that starts with it |
476
+
477
+ - `candidate` is NEVER a merge, as in NN5. Every tolerance means "may be the same person;
478
+ the rest of the evidence decides".
479
+ - `via` is a SET, one entry per tolerance used, in the table's order. It is empty for
480
+ `same` and for `different`. `Kate M Smith` / `Katherine Conley-Smith` is a candidate via
481
+ `surname-part`, `given-family` and `middle-absent`.
482
+ - A surname part is compared WHOLE: `Larson` is a part of `Conley-Larson` and of `Conley
483
+ Larson, Mary`, and not of `Carlson` or `Conley-Fitzlarson`. Two compounds with the same
484
+ number of parts are two surnames (`Smith-Jones` / `Jones-Smith` is `different`).
485
+ - The given-name tolerance is INJECTED: `createNameKinship({ given: [p1, p2, …] })` asks
486
+ each `GivenProvider` in order and the first non-null answer wins. A provider is asked
487
+ once per pair, with the two NN3 first given names in code-unit order, and never with two
488
+ equal names. The default is `[givenFamilyTable]`, which answers from
489
+ `given-diminutives.json`; `sameGivenFamily(a, b)` is exactly
490
+ `givenFamilyTable(a, b) === "given-family"`. `[]` tolerates no given name.
491
+ - A new kind of `via` is one entry in `KINSHIP_VIAS`, which carries its field and its
492
+ gate-safety. The relation's rules do not change.
493
+ - With no tolerance used, the answer is `nameRelation`'s. `head-differs` is split into
494
+ `surname-differs` and `given-differs`, because each half is tolerated on its own. The
495
+ suffix and middle rows are NN5's (`tailRelation`) and still veto.
496
+ - A print that names no person is `different` from everything, itself included.
497
+
498
+ Over the 261 forms of the current table (33,930 distinct pairs), `givenFamilyTable` answers
499
+ `given-family` for 354 pairs, `given-family-not-gate` for 302, and `null` for the rest.
500
+ `sameGivenFamily` answers as it did before the provider existed on every one of them.
501
+
502
+ ### The tolerant lookup
503
+
504
+ `createNameIndex(dop, { kinship?, entries? })` is a `NameMap` whose `get`, `has`, `set` and
505
+ `delete` are `createNameMap`'s, unchanged. It adds `related(name)`: every held entry the
506
+ relation does not call `different`, each with its `NameKinship`, `same` before `candidate`
507
+ and otherwise in insertion order. Candidates come from a secondary index on each entry's
508
+ NN3 surname key and each of its surname parts, so `Mary Larson` finds `Mary Conley-Larson`
509
+ and the other way round. The index does not key on the first initial, because a given-name
510
+ family need not keep one (`Peggy` / `Margaret`).
511
+
512
+ It is a sibling of `NameMap` and not a method on it. The tolerant answer is not
513
+ transitive, and a `NameMap` is an equivalence class. The lookup needs a relation chosen by
514
+ options. And the secondary index costs memory that a caller wanting only the exact map
515
+ should not pay.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@finbheara/names",
3
- "version": "0.12.0",
3
+ "version": "0.13.0",
4
4
  "description": "A curated lexicon of personal, school and place names, the surname-particle vocabulary, a phrase classifier and a person-name normalizer",
5
5
  "keywords": [
6
6
  "names",