@empyria/name 0.1.2 → 0.1.4

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/lib/Name.js CHANGED
@@ -92,6 +92,17 @@ export const TITLE_LIST = [
92
92
  ]
93
93
  export const TITLES = enumise(TITLE_LIST)
94
94
 
95
+ // Honorifics as compact keys (transliterated, alphanumerics only, upper-case) — the form
96
+ // `nameFp` compares each token against. `TITLES` (an enumise() object) can't be used directly:
97
+ // it has no `.has`, its keys carry `_` separators, and its values keep original casing.
98
+ const TITLE_KEYS = new Set(
99
+ TITLE_LIST.map((t) =>
100
+ transliterate(t)
101
+ .toUpperCase()
102
+ .replace(/[^A-Z0-9]/g, ''),
103
+ ),
104
+ )
105
+
95
106
  export function findTitle(_title) {
96
107
  if (!_title) return undefined
97
108
 
@@ -99,4 +110,58 @@ export function findTitle(_title) {
99
110
  return TITLE_LIST.find((t) => transliterate(t).toUpperCase() === title)
100
111
  }
101
112
 
113
+ /**
114
+ * Base fold: transliterate to ASCII, collapse any run of whitespace to a single space, trim.
115
+ * @param {unknown} value
116
+ * @returns {string}
117
+ */
118
+ export function fold(value) {
119
+ return transliterate(String(value ?? ''))
120
+ .replace(/\s+/g, ' ')
121
+ .trim()
122
+ }
123
+
124
+ /**
125
+ * Compact identifier key — transliterated, alphanumerics only, upper-case. Format-agnostic:
126
+ * `idKey('5299 00 T8BM49') === idKey('529900t8bm49')`, `idKey('DE 123.456.789') === 'DE123456789'`.
127
+ * Empty string for empty/nullish input (callers skip those).
128
+ * @param {unknown} value
129
+ * @returns {string}
130
+ */
131
+ export function idKey(value) {
132
+ return fold(value)
133
+ .toUpperCase()
134
+ .replace(/[^A-Z0-9]/g, '')
135
+ }
136
+
137
+ /**
138
+ * Free-text name form for external full-text engines (BAILII, CourtListener, opencaselaw) —
139
+ * transliterated, lower-case, single-spaced, tokens intact so their tokenisers still work.
140
+ * @param {unknown} value
141
+ * @returns {string}
142
+ */
143
+ export function nameText(value) {
144
+ return fold(value).toLowerCase()
145
+ }
146
+
147
+ /**
148
+ * Order-, case-, punctuation-, script-invariant name fingerprint: transliterate, upper-case,
149
+ * split on whitespace, strip non-alphanumerics per token, drop honorifics and 1-char tokens,
150
+ * dedupe, sort. `nameFp('Dr. Steve Charles') === nameFp('charles, steve') === 'CHARLES STEVE'`.
151
+ *
152
+ * Best-effort for organisations — legal-form / "&" vs "and" differences aren't reconciled here
153
+ * (`nameFp('J.P. Morgan & Co') !== nameFp('JPMorgan and Co')`); the `:Ident` exact match and the
154
+ * raw-name text search are the reliable paths for companies.
155
+ * @param {unknown} value
156
+ * @returns {string}
157
+ */
158
+ export function nameFp(value) {
159
+ const tokens = fold(value)
160
+ .toUpperCase()
161
+ .split(' ')
162
+ .map((token) => token.replace(/[^A-Z0-9]/g, ''))
163
+ .filter((token) => token.length > 1 && !TITLE_KEYS.has(token))
164
+ return [...new Set(tokens)].sort().join(' ')
165
+ }
166
+
102
167
  export { transliterate, slugify, TRANS, capitalise, shortify }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@empyria/name",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "Name and title manipulation library for Empyria",
5
5
  "license": "MIT",
6
6
  "author": "Imre Fazekas <imre.fazekas@icloud.com>",
package/test/Name.test.js CHANGED
@@ -3,6 +3,10 @@ import {
3
3
  TITLE_LIST,
4
4
  TITLES,
5
5
  findTitle,
6
+ fold,
7
+ idKey,
8
+ nameFp,
9
+ nameText,
6
10
  TRANS,
7
11
  capitalise,
8
12
  shortify,
@@ -73,3 +77,22 @@ describe('re-exported string helpers (from @empyria/classification)', () => {
73
77
  expect(slugify('Jean Pierre')).toBe('jean-pierre')
74
78
  })
75
79
  })
80
+
81
+ describe('fold / idKey / nameText / nameFp', () => {
82
+ test('idKey is format-agnostic', () => {
83
+ expect(idKey('5299 00 T8BM49')).toBe(idKey('529900t8bm49'))
84
+ expect(idKey('DE 123.456.789')).toBe('DE123456789')
85
+ })
86
+
87
+ test('nameText transliterates + lower-cases, keeps token spacing', () => {
88
+ expect(nameText(' José MÜLLER ')).toBe('jose muller')
89
+ })
90
+
91
+ test('nameFp is order / case / punctuation / honorific / script invariant', () => {
92
+ expect(nameFp('Steve Charles')).toBe(nameFp('Charles Steve'))
93
+ expect(nameFp('Dr. Steve Charles')).toBe(nameFp('charles, steve'))
94
+ expect(nameFp('Professor Ada Lovelace')).toBe(nameFp('lovelace, ada'))
95
+ expect(nameFp('Владимир Путин')).toBe(nameFp('Putin Vladimir'))
96
+ expect(nameFp('Mr.')).toBe('')
97
+ })
98
+ })