@whiteslove/parsing-lexicon 0.2.0 → 0.2.2
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/LICENSE +21 -21
- package/README.md +137 -137
- package/index.d.ts +374 -374
- package/package.json +77 -75
- package/src/central-asia-locations.js +211 -211
- package/src/central-asia.js +233 -233
- package/src/contact.d.ts +44 -44
- package/src/contact.js +159 -159
- package/src/countries.js +175 -175
- package/src/country-context.d.ts +10 -10
- package/src/country-context.js +22 -22
- package/src/currency.d.ts +14 -14
- package/src/currency.js +66 -66
- package/src/geo.js +169 -169
- package/src/geography-central-asia.js +42 -42
- package/src/geography-detection.d.ts +14 -14
- package/src/geography-detection.js +64 -64
- package/src/geography-display.d.ts +13 -13
- package/src/geography-display.js +113 -113
- package/src/geography.js +210 -210
- package/src/hiring-advanced.js +147 -147
- package/src/hiring-candidate-fields.d.ts +9 -9
- package/src/hiring-candidate-fields.js +92 -92
- package/src/hiring-context.d.ts +64 -64
- package/src/hiring-context.js +412 -412
- package/src/hiring-language-extensions.d.ts +19 -19
- package/src/hiring-language-extensions.js +139 -139
- package/src/hiring-languages.js +135 -135
- package/src/hiring-location-fields.d.ts +3 -3
- package/src/hiring-location-fields.js +53 -53
- package/src/hiring-professions-ro.js +191 -191
- package/src/hiring-professions.js +437 -437
- package/src/hiring-requirements.d.ts +16 -16
- package/src/hiring-requirements.js +170 -170
- package/src/hiring-salary-context.d.ts +27 -27
- package/src/hiring-salary-context.js +76 -76
- package/src/hiring-semantics.d.ts +29 -29
- package/src/hiring-semantics.js +218 -218
- package/src/hiring-skills.d.ts +21 -21
- package/src/hiring-skills.js +315 -315
- package/src/hiring-source-aliases.d.ts +32 -32
- package/src/hiring-source-aliases.js +185 -185
- package/src/hiring-source-semantics.d.ts +31 -31
- package/src/hiring-source-semantics.js +293 -293
- package/src/hiring-temporal.d.ts +15 -15
- package/src/hiring-temporal.js +67 -67
- package/src/hiring-vacancy-fields.d.ts +5 -5
- package/src/hiring-vacancy-fields.js +23 -23
- package/src/hiring-work-semantics.d.ts +34 -34
- package/src/hiring-work-semantics.js +72 -72
- package/src/hiring.js +180 -180
- package/src/housing-address.d.ts +18 -18
- package/src/housing-address.js +217 -210
- package/src/housing-commercial.d.ts +1 -0
- package/src/housing-commercial.js +9 -0
- package/src/housing-context.d.ts +36 -36
- package/src/housing-context.js +183 -183
- package/src/housing-features.js +30 -30
- package/src/housing-intent.d.ts +9 -9
- package/src/housing-intent.js +132 -132
- package/src/housing-listing-fields.js +131 -131
- package/src/housing-money.d.ts +7 -7
- package/src/housing-money.js +102 -102
- package/src/housing-source-aliases.d.ts +8 -8
- package/src/housing-source-aliases.js +51 -51
- package/src/housing-structured.d.ts +55 -55
- package/src/housing-structured.js +219 -219
- package/src/housing.js +200 -200
- package/src/index.js +45 -44
- package/src/kz-location-extensions.js +307 -307
- package/src/landmarks.js +21 -21
- package/src/lexicon-core.js +58 -58
- package/src/location-merge.js +106 -106
- package/src/locations.js +456 -456
- package/src/money-core.d.ts +10 -10
- package/src/money-core.js +81 -81
- package/src/money-lexicon.d.ts +5 -5
- package/src/money-lexicon.js +79 -79
- package/src/money.js +154 -154
- package/src/normalization.js +384 -384
- package/src/odesa-metropolitan.js +144 -144
- package/src/romania-geography.js +47 -47
- package/src/tashkent-colloquial.js +80 -72
- package/src/tashkent-housing-geography.d.ts +38 -38
- package/src/tashkent-housing-geography.js +211 -211
- package/src/tashkent-pois.js +215 -215
- package/src/tashkent-residential-complexes.js +272 -272
- package/src/ua-location-extensions-major.js +118 -118
- package/src/ua-location-extensions-metro.js +9 -9
- package/src/ua-location-extensions-regional.js +246 -246
- package/src/ua-secondary-cities.d.ts +2 -2
- package/src/ua-secondary-cities.js +57 -57
- package/src/ukraine.js +75 -75
- package/src/uz-location-extensions.js +259 -259
package/src/landmarks.js
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
import { lexiconEntity } from './lexicon-core.js';
|
|
2
|
-
const group = (canonical, aliases) => lexiconEntity(canonical, aliases);
|
|
3
|
-
|
|
4
|
-
export const GENERIC_LANDMARK_TERMS = Object.freeze([
|
|
5
|
-
group('Park', { ru: ['парк'], en: ['park'], uk: ['парк'], ro: ['parc'], uzLatn: ["bog'", 'bog‘', 'bogʻ', 'park'], uzCyrl: ['боғ', 'парк'], kk: ['саябақ', 'парк'] }),
|
|
6
|
-
group('Metro', { ru: ['метро', 'станция метро'], en: ['metro', 'subway station'], uk: ['метро', 'станція метро'], ro: ['metrou', 'stație de metrou'], uzLatn: ['metro', 'metro bekati'], uzCyrl: ['метро', 'метро бекати'], kk: ['метро', 'метро станциясы'] }),
|
|
7
|
-
group('Bus stop', { ru: ['автобусная остановка', 'остановка автобуса', 'конечная автобуса'], en: ['bus stop', 'bus station'], uk: ['автобусна зупинка', 'зупинка автобуса'], ro: ['stație de autobuz', 'statie de autobuz'], uzLatn: ['avtobus bekati', 'avtobus kanichka', 'avtobus kanichkasi', 'avtobus konichka', 'avtobus konichkasi'], uzCyrl: ['автобус бекати'], kk: ['автобус аялдамасы'] }),
|
|
8
|
-
group('Clinic', { ru: ['поликлиника', 'клиника'], en: ['clinic', 'polyclinic'], uk: ['поліклініка', 'клініка'], ro: ['clinică', 'clinica', 'policlinică', 'policlinica'], uzLatn: ['poliklinika', 'poleklinika', 'klinika'], uzCyrl: ['поликлиника', 'клиника'], kk: ['емхана', 'клиника'] }),
|
|
9
|
-
group('Hospital', { ru: ['больница', 'госпиталь'], en: ['hospital'], uk: ['лікарня', 'госпіталь'], ro: ['spital'], uzLatn: ['shifoxona', 'kasalxona'], uzCyrl: ['шифохона', 'касалхона'], kk: ['аурухана'] }),
|
|
10
|
-
group('School', { ru: ['школа', 'школы', 'школу', 'школе'], en: ['school'], uk: ['школа', 'школи', 'школу'], ro: ['școală', 'scoala'], uzLatn: ['maktab'], uzCyrl: ['мактаб'], kk: ['мектеп'] }),
|
|
11
|
-
group('Kindergarten', { ru: ['детский сад', 'садик'], en: ['kindergarten', 'nursery'], uk: ['дитячий садок', 'садочок'], ro: ['grădiniță', 'gradinita'], uzLatn: ["bolalar bog'chasi", 'bolalar bog‘chasi', 'bogcha'], uzCyrl: ['болалар боғчаси', 'боғча'], kk: ['балабақша'] }),
|
|
12
|
-
group('University', { ru: ['университет', 'институт'], en: ['university', 'institute'], uk: ['університет', 'інститут'], ro: ['universitate', 'institut'], uzLatn: ['universitet', 'institut'], uzCyrl: ['университет', 'институт'], kk: ['университет', 'институт'] }),
|
|
13
|
-
group('Shopping center', { ru: ['тц', 'трц', 'торговый центр'], en: ['shopping center', 'shopping centre', 'mall'], uk: ['тц', 'трц', 'торговий центр'], ro: ['centru comercial', 'mall'], uzLatn: ['savdo markazi', 'mall'], uzCyrl: ['савдо маркази'], kk: ['сауда орталығы', 'сауда үйі'] }),
|
|
14
|
-
group('Supermarket', { ru: ['супермаркет', 'гипермаркет'], en: ['supermarket', 'hypermarket'], uk: ['супермаркет', 'гіпермаркет'], ro: ['supermarket', 'hipermarket'], uzLatn: ['supermarket', 'gipermarket'], uzCyrl: ['супермаркет', 'гипермаркет'], kk: ['супермаркет', 'гипермаркет'] }),
|
|
15
|
-
group('Market', { ru: ['рынок', 'базар'], en: ['market', 'bazaar'], uk: ['ринок', 'базар'], ro: ['piață', 'piata', 'bazar'], uzLatn: ['bozor', 'bazar'], uzCyrl: ['бозор', 'базар'], kk: ['базар', 'нарық'] }),
|
|
16
|
-
group('Pharmacy', { ru: ['аптека'], en: ['pharmacy', 'drugstore'], uk: ['аптека'], ro: ['farmacie'], uzLatn: ['dorixona', 'apteka'], uzCyrl: ['дорихона', 'аптека'], kk: ['дәріхана', 'аптека'] }),
|
|
17
|
-
group('Mosque', { ru: ['мечеть'], en: ['mosque'], uk: ['мечеть'], ro: ['moschee'], uzLatn: ['masjid'], uzCyrl: ['масжид'], kk: ['мешіт'] }),
|
|
18
|
-
group('Church', { ru: ['церковь', 'храм'], en: ['church', 'cathedral'], uk: ['церква', 'храм', 'собор'], ro: ['biserică', 'biserica', 'catedrală', 'catedrala'], uzLatn: ['cherkov', 'ibodatxona'], uzCyrl: ['черков'], kk: ['шіркеу', 'собор'] }),
|
|
19
|
-
group('Railway station', { ru: ['вокзал', 'железнодорожный вокзал'], en: ['railway station', 'train station'], uk: ['вокзал', 'залізничний вокзал'], ro: ['gară', 'gara', 'gară feroviară', 'gara feroviara'], uzLatn: ['vokzal', 'temir yol vokzali', "temir yo'l vokzali"], uzCyrl: ['вокзал', 'темир йўл вокзали'], kk: ['теміржол вокзалы', 'вокзал'] }),
|
|
20
|
-
group('Airport', { ru: ['аэропорт'], en: ['airport'], uk: ['аеропорт'], ro: ['aeroport'], uzLatn: ['aeroport'], uzCyrl: ['аэропорт'], kk: ['әуежай', 'аэропорт'] }),
|
|
21
|
-
]);
|
|
1
|
+
import { lexiconEntity } from './lexicon-core.js';
|
|
2
|
+
const group = (canonical, aliases) => lexiconEntity(canonical, aliases);
|
|
3
|
+
|
|
4
|
+
export const GENERIC_LANDMARK_TERMS = Object.freeze([
|
|
5
|
+
group('Park', { ru: ['парк'], en: ['park'], uk: ['парк'], ro: ['parc'], uzLatn: ["bog'", 'bog‘', 'bogʻ', 'park'], uzCyrl: ['боғ', 'парк'], kk: ['саябақ', 'парк'] }),
|
|
6
|
+
group('Metro', { ru: ['метро', 'станция метро'], en: ['metro', 'subway station'], uk: ['метро', 'станція метро'], ro: ['metrou', 'stație de metrou'], uzLatn: ['metro', 'metro bekati'], uzCyrl: ['метро', 'метро бекати'], kk: ['метро', 'метро станциясы'] }),
|
|
7
|
+
group('Bus stop', { ru: ['автобусная остановка', 'остановка автобуса', 'конечная автобуса'], en: ['bus stop', 'bus station'], uk: ['автобусна зупинка', 'зупинка автобуса'], ro: ['stație de autobuz', 'statie de autobuz'], uzLatn: ['avtobus bekati', 'avtobus kanichka', 'avtobus kanichkasi', 'avtobus konichka', 'avtobus konichkasi'], uzCyrl: ['автобус бекати'], kk: ['автобус аялдамасы'] }),
|
|
8
|
+
group('Clinic', { ru: ['поликлиника', 'клиника'], en: ['clinic', 'polyclinic'], uk: ['поліклініка', 'клініка'], ro: ['clinică', 'clinica', 'policlinică', 'policlinica'], uzLatn: ['poliklinika', 'poleklinika', 'klinika'], uzCyrl: ['поликлиника', 'клиника'], kk: ['емхана', 'клиника'] }),
|
|
9
|
+
group('Hospital', { ru: ['больница', 'госпиталь'], en: ['hospital'], uk: ['лікарня', 'госпіталь'], ro: ['spital'], uzLatn: ['shifoxona', 'kasalxona'], uzCyrl: ['шифохона', 'касалхона'], kk: ['аурухана'] }),
|
|
10
|
+
group('School', { ru: ['школа', 'школы', 'школу', 'школе'], en: ['school'], uk: ['школа', 'школи', 'школу'], ro: ['școală', 'scoala'], uzLatn: ['maktab'], uzCyrl: ['мактаб'], kk: ['мектеп'] }),
|
|
11
|
+
group('Kindergarten', { ru: ['детский сад', 'садик'], en: ['kindergarten', 'nursery'], uk: ['дитячий садок', 'садочок'], ro: ['grădiniță', 'gradinita'], uzLatn: ["bolalar bog'chasi", 'bolalar bog‘chasi', 'bogcha'], uzCyrl: ['болалар боғчаси', 'боғча'], kk: ['балабақша'] }),
|
|
12
|
+
group('University', { ru: ['университет', 'институт'], en: ['university', 'institute'], uk: ['університет', 'інститут'], ro: ['universitate', 'institut'], uzLatn: ['universitet', 'institut'], uzCyrl: ['университет', 'институт'], kk: ['университет', 'институт'] }),
|
|
13
|
+
group('Shopping center', { ru: ['тц', 'трц', 'торговый центр'], en: ['shopping center', 'shopping centre', 'mall'], uk: ['тц', 'трц', 'торговий центр'], ro: ['centru comercial', 'mall'], uzLatn: ['savdo markazi', 'mall'], uzCyrl: ['савдо маркази'], kk: ['сауда орталығы', 'сауда үйі'] }),
|
|
14
|
+
group('Supermarket', { ru: ['супермаркет', 'гипермаркет'], en: ['supermarket', 'hypermarket'], uk: ['супермаркет', 'гіпермаркет'], ro: ['supermarket', 'hipermarket'], uzLatn: ['supermarket', 'gipermarket'], uzCyrl: ['супермаркет', 'гипермаркет'], kk: ['супермаркет', 'гипермаркет'] }),
|
|
15
|
+
group('Market', { ru: ['рынок', 'базар'], en: ['market', 'bazaar'], uk: ['ринок', 'базар'], ro: ['piață', 'piata', 'bazar'], uzLatn: ['bozor', 'bazar'], uzCyrl: ['бозор', 'базар'], kk: ['базар', 'нарық'] }),
|
|
16
|
+
group('Pharmacy', { ru: ['аптека'], en: ['pharmacy', 'drugstore'], uk: ['аптека'], ro: ['farmacie'], uzLatn: ['dorixona', 'apteka'], uzCyrl: ['дорихона', 'аптека'], kk: ['дәріхана', 'аптека'] }),
|
|
17
|
+
group('Mosque', { ru: ['мечеть'], en: ['mosque'], uk: ['мечеть'], ro: ['moschee'], uzLatn: ['masjid'], uzCyrl: ['масжид'], kk: ['мешіт'] }),
|
|
18
|
+
group('Church', { ru: ['церковь', 'храм'], en: ['church', 'cathedral'], uk: ['церква', 'храм', 'собор'], ro: ['biserică', 'biserica', 'catedrală', 'catedrala'], uzLatn: ['cherkov', 'ibodatxona'], uzCyrl: ['черков'], kk: ['шіркеу', 'собор'] }),
|
|
19
|
+
group('Railway station', { ru: ['вокзал', 'железнодорожный вокзал'], en: ['railway station', 'train station'], uk: ['вокзал', 'залізничний вокзал'], ro: ['gară', 'gara', 'gară feroviară', 'gara feroviara'], uzLatn: ['vokzal', 'temir yol vokzali', "temir yo'l vokzali"], uzCyrl: ['вокзал', 'темир йўл вокзали'], kk: ['теміржол вокзалы', 'вокзал'] }),
|
|
20
|
+
group('Airport', { ru: ['аэропорт'], en: ['airport'], uk: ['аеропорт'], ro: ['aeroport'], uzLatn: ['aeroport'], uzCyrl: ['аэропорт'], kk: ['әуежай', 'аэропорт'] }),
|
|
21
|
+
]);
|
package/src/lexicon-core.js
CHANGED
|
@@ -1,58 +1,58 @@
|
|
|
1
|
-
export const LEXICON_LANGUAGES = Object.freeze([
|
|
2
|
-
'all',
|
|
3
|
-
'ru',
|
|
4
|
-
'en',
|
|
5
|
-
'uk',
|
|
6
|
-
'ro',
|
|
7
|
-
'uzLatn',
|
|
8
|
-
'uzCyrl',
|
|
9
|
-
'kaaLat',
|
|
10
|
-
'kaaCyrl',
|
|
11
|
-
'kk',
|
|
12
|
-
]);
|
|
13
|
-
|
|
14
|
-
export const COUNTRY_CODES = Object.freeze(['UZ', 'KZ', 'UA', 'RO', 'KG']);
|
|
15
|
-
|
|
16
|
-
export function deepFreeze(value, seen = new WeakSet()) {
|
|
17
|
-
if (value == null || (typeof value !== 'object' && typeof value !== 'function')) return value;
|
|
18
|
-
if (seen.has(value)) return value;
|
|
19
|
-
seen.add(value);
|
|
20
|
-
|
|
21
|
-
// Freezing Map/Set objects does not make their entries immutable, so they are
|
|
22
|
-
// intentionally not used as public mutable lexicon indexes. Freeze ordinary
|
|
23
|
-
// nested objects/arrays/regexes only.
|
|
24
|
-
if (value instanceof Map || value instanceof Set) return value;
|
|
25
|
-
|
|
26
|
-
for (const key of Reflect.ownKeys(value)) {
|
|
27
|
-
const child = value[key];
|
|
28
|
-
if (child && (typeof child === 'object' || typeof child === 'function')) deepFreeze(child, seen);
|
|
29
|
-
}
|
|
30
|
-
return Object.freeze(value);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export function freezeAliases(aliases = {}) {
|
|
34
|
-
if (Array.isArray(aliases)) return Object.freeze([...aliases]);
|
|
35
|
-
return Object.freeze(Object.fromEntries(
|
|
36
|
-
Object.entries(aliases).map(([language, values]) => [
|
|
37
|
-
language,
|
|
38
|
-
Object.freeze(Array.isArray(values) ? [...values] : []),
|
|
39
|
-
]),
|
|
40
|
-
));
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export function lexiconEntity(canonical, aliases = {}, extra = {}) {
|
|
44
|
-
return deepFreeze({
|
|
45
|
-
...extra,
|
|
46
|
-
canonical,
|
|
47
|
-
aliases: freezeAliases(aliases),
|
|
48
|
-
});
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
export function locationEntity(canonical, aliases = [], extra = {}) {
|
|
52
|
-
return deepFreeze({
|
|
53
|
-
...extra,
|
|
54
|
-
canonical,
|
|
55
|
-
name: canonical,
|
|
56
|
-
aliases: Object.freeze([...aliases]),
|
|
57
|
-
});
|
|
58
|
-
}
|
|
1
|
+
export const LEXICON_LANGUAGES = Object.freeze([
|
|
2
|
+
'all',
|
|
3
|
+
'ru',
|
|
4
|
+
'en',
|
|
5
|
+
'uk',
|
|
6
|
+
'ro',
|
|
7
|
+
'uzLatn',
|
|
8
|
+
'uzCyrl',
|
|
9
|
+
'kaaLat',
|
|
10
|
+
'kaaCyrl',
|
|
11
|
+
'kk',
|
|
12
|
+
]);
|
|
13
|
+
|
|
14
|
+
export const COUNTRY_CODES = Object.freeze(['UZ', 'KZ', 'UA', 'RO', 'KG']);
|
|
15
|
+
|
|
16
|
+
export function deepFreeze(value, seen = new WeakSet()) {
|
|
17
|
+
if (value == null || (typeof value !== 'object' && typeof value !== 'function')) return value;
|
|
18
|
+
if (seen.has(value)) return value;
|
|
19
|
+
seen.add(value);
|
|
20
|
+
|
|
21
|
+
// Freezing Map/Set objects does not make their entries immutable, so they are
|
|
22
|
+
// intentionally not used as public mutable lexicon indexes. Freeze ordinary
|
|
23
|
+
// nested objects/arrays/regexes only.
|
|
24
|
+
if (value instanceof Map || value instanceof Set) return value;
|
|
25
|
+
|
|
26
|
+
for (const key of Reflect.ownKeys(value)) {
|
|
27
|
+
const child = value[key];
|
|
28
|
+
if (child && (typeof child === 'object' || typeof child === 'function')) deepFreeze(child, seen);
|
|
29
|
+
}
|
|
30
|
+
return Object.freeze(value);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function freezeAliases(aliases = {}) {
|
|
34
|
+
if (Array.isArray(aliases)) return Object.freeze([...aliases]);
|
|
35
|
+
return Object.freeze(Object.fromEntries(
|
|
36
|
+
Object.entries(aliases).map(([language, values]) => [
|
|
37
|
+
language,
|
|
38
|
+
Object.freeze(Array.isArray(values) ? [...values] : []),
|
|
39
|
+
]),
|
|
40
|
+
));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function lexiconEntity(canonical, aliases = {}, extra = {}) {
|
|
44
|
+
return deepFreeze({
|
|
45
|
+
...extra,
|
|
46
|
+
canonical,
|
|
47
|
+
aliases: freezeAliases(aliases),
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function locationEntity(canonical, aliases = [], extra = {}) {
|
|
52
|
+
return deepFreeze({
|
|
53
|
+
...extra,
|
|
54
|
+
canonical,
|
|
55
|
+
name: canonical,
|
|
56
|
+
aliases: Object.freeze([...aliases]),
|
|
57
|
+
});
|
|
58
|
+
}
|
package/src/location-merge.js
CHANGED
|
@@ -1,106 +1,106 @@
|
|
|
1
|
-
import { aliasesToRegex, normalizeForMatch } from './normalization.js';
|
|
2
|
-
|
|
3
|
-
export const LOCATION_LIST_KEYS = Object.freeze([
|
|
4
|
-
'districts',
|
|
5
|
-
'microdistricts',
|
|
6
|
-
'mahallas',
|
|
7
|
-
'localAreas',
|
|
8
|
-
'suburbs',
|
|
9
|
-
'settlements',
|
|
10
|
-
'metro',
|
|
11
|
-
'residentialComplexes',
|
|
12
|
-
'streets',
|
|
13
|
-
'landmarks',
|
|
14
|
-
'pois',
|
|
15
|
-
'searchClusters',
|
|
16
|
-
]);
|
|
17
|
-
|
|
18
|
-
export function locationEntry(name, ...aliases) {
|
|
19
|
-
const all = [...new Set([name, ...aliases].flat().filter(Boolean))];
|
|
20
|
-
return Object.freeze({ canonical: name, name, aliases: Object.freeze(all), re: aliasesToRegex(all) });
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export function locationEntries(rows = []) {
|
|
24
|
-
return Object.freeze(rows.map(([name, ...aliases]) => locationEntry(name, ...aliases)));
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function mergeEntry(existing, incoming) {
|
|
28
|
-
const aliases = [...new Set([
|
|
29
|
-
...(existing?.aliases || []),
|
|
30
|
-
...(incoming?.aliases || []),
|
|
31
|
-
existing?.name,
|
|
32
|
-
incoming?.name,
|
|
33
|
-
].filter(Boolean))];
|
|
34
|
-
const base = { ...(existing || {}), ...(incoming || {}) };
|
|
35
|
-
return Object.freeze({ ...base, canonical: base.canonical || base.name, type: base.type || base.entityType, aliases: Object.freeze(aliases), re: aliasesToRegex(aliases) });
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function parentKey(entry) {
|
|
39
|
-
return normalizeForMatch(entry?.parent || entry?.district || '');
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export function mergeLocationEntries(...lists) {
|
|
43
|
-
const groups = new Map();
|
|
44
|
-
const order = [];
|
|
45
|
-
|
|
46
|
-
for (const list of lists) {
|
|
47
|
-
for (const entry of list || []) {
|
|
48
|
-
if (!entry?.name) continue;
|
|
49
|
-
const canonical = normalizeForMatch(entry.name);
|
|
50
|
-
if (!groups.has(canonical)) {
|
|
51
|
-
groups.set(canonical, []);
|
|
52
|
-
order.push(canonical);
|
|
53
|
-
}
|
|
54
|
-
groups.get(canonical).push(entry);
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
const result = [];
|
|
59
|
-
for (const canonical of order) {
|
|
60
|
-
const group = groups.get(canonical) || [];
|
|
61
|
-
const scopedParents = [...new Set(group.map(parentKey).filter(Boolean))];
|
|
62
|
-
|
|
63
|
-
if (scopedParents.length <= 1) {
|
|
64
|
-
result.push(group.reduce((merged, entry) => mergeEntry(merged, entry), null));
|
|
65
|
-
continue;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// A market name can legitimately exist under multiple parents inside one
|
|
69
|
-
// city (for example Sairan in adjacent Almaty districts). Keep each scoped
|
|
70
|
-
// entity. Unscoped/base aliases are merged into every scoped variant so
|
|
71
|
-
// older dictionaries enrich rather than erase parent information.
|
|
72
|
-
const unscoped = group.filter((entry) => !parentKey(entry));
|
|
73
|
-
for (const parent of scopedParents) {
|
|
74
|
-
const scoped = group.filter((entry) => parentKey(entry) === parent);
|
|
75
|
-
result.push([...unscoped, ...scoped].reduce((merged, entry) => mergeEntry(merged, entry), null));
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
return Object.freeze(result);
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
export function mergeLocationCityDictionaries(...dictionaries) {
|
|
83
|
-
const result = {};
|
|
84
|
-
for (const key of LOCATION_LIST_KEYS) {
|
|
85
|
-
const lists = dictionaries.map((dictionary) => dictionary?.[key]).filter(Boolean);
|
|
86
|
-
if (lists.length) result[key] = mergeLocationEntries(...lists);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
for (const dictionary of dictionaries) {
|
|
90
|
-
if (!dictionary) continue;
|
|
91
|
-
for (const [key, value] of Object.entries(dictionary)) {
|
|
92
|
-
if (LOCATION_LIST_KEYS.includes(key) || value == null) continue;
|
|
93
|
-
result[key] = value;
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
return Object.freeze(result);
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
export function mergeLocationCountries(...countries) {
|
|
101
|
-
const cityNames = [...new Set(countries.flatMap((country) => Object.keys(country || {})))];
|
|
102
|
-
return Object.freeze(Object.fromEntries(cityNames.map((city) => [
|
|
103
|
-
city,
|
|
104
|
-
mergeLocationCityDictionaries(...countries.map((country) => country?.[city]).filter(Boolean)),
|
|
105
|
-
])));
|
|
106
|
-
}
|
|
1
|
+
import { aliasesToRegex, normalizeForMatch } from './normalization.js';
|
|
2
|
+
|
|
3
|
+
export const LOCATION_LIST_KEYS = Object.freeze([
|
|
4
|
+
'districts',
|
|
5
|
+
'microdistricts',
|
|
6
|
+
'mahallas',
|
|
7
|
+
'localAreas',
|
|
8
|
+
'suburbs',
|
|
9
|
+
'settlements',
|
|
10
|
+
'metro',
|
|
11
|
+
'residentialComplexes',
|
|
12
|
+
'streets',
|
|
13
|
+
'landmarks',
|
|
14
|
+
'pois',
|
|
15
|
+
'searchClusters',
|
|
16
|
+
]);
|
|
17
|
+
|
|
18
|
+
export function locationEntry(name, ...aliases) {
|
|
19
|
+
const all = [...new Set([name, ...aliases].flat().filter(Boolean))];
|
|
20
|
+
return Object.freeze({ canonical: name, name, aliases: Object.freeze(all), re: aliasesToRegex(all) });
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function locationEntries(rows = []) {
|
|
24
|
+
return Object.freeze(rows.map(([name, ...aliases]) => locationEntry(name, ...aliases)));
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function mergeEntry(existing, incoming) {
|
|
28
|
+
const aliases = [...new Set([
|
|
29
|
+
...(existing?.aliases || []),
|
|
30
|
+
...(incoming?.aliases || []),
|
|
31
|
+
existing?.name,
|
|
32
|
+
incoming?.name,
|
|
33
|
+
].filter(Boolean))];
|
|
34
|
+
const base = { ...(existing || {}), ...(incoming || {}) };
|
|
35
|
+
return Object.freeze({ ...base, canonical: base.canonical || base.name, type: base.type || base.entityType, aliases: Object.freeze(aliases), re: aliasesToRegex(aliases) });
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function parentKey(entry) {
|
|
39
|
+
return normalizeForMatch(entry?.parent || entry?.district || '');
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function mergeLocationEntries(...lists) {
|
|
43
|
+
const groups = new Map();
|
|
44
|
+
const order = [];
|
|
45
|
+
|
|
46
|
+
for (const list of lists) {
|
|
47
|
+
for (const entry of list || []) {
|
|
48
|
+
if (!entry?.name) continue;
|
|
49
|
+
const canonical = normalizeForMatch(entry.name);
|
|
50
|
+
if (!groups.has(canonical)) {
|
|
51
|
+
groups.set(canonical, []);
|
|
52
|
+
order.push(canonical);
|
|
53
|
+
}
|
|
54
|
+
groups.get(canonical).push(entry);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const result = [];
|
|
59
|
+
for (const canonical of order) {
|
|
60
|
+
const group = groups.get(canonical) || [];
|
|
61
|
+
const scopedParents = [...new Set(group.map(parentKey).filter(Boolean))];
|
|
62
|
+
|
|
63
|
+
if (scopedParents.length <= 1) {
|
|
64
|
+
result.push(group.reduce((merged, entry) => mergeEntry(merged, entry), null));
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// A market name can legitimately exist under multiple parents inside one
|
|
69
|
+
// city (for example Sairan in adjacent Almaty districts). Keep each scoped
|
|
70
|
+
// entity. Unscoped/base aliases are merged into every scoped variant so
|
|
71
|
+
// older dictionaries enrich rather than erase parent information.
|
|
72
|
+
const unscoped = group.filter((entry) => !parentKey(entry));
|
|
73
|
+
for (const parent of scopedParents) {
|
|
74
|
+
const scoped = group.filter((entry) => parentKey(entry) === parent);
|
|
75
|
+
result.push([...unscoped, ...scoped].reduce((merged, entry) => mergeEntry(merged, entry), null));
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return Object.freeze(result);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function mergeLocationCityDictionaries(...dictionaries) {
|
|
83
|
+
const result = {};
|
|
84
|
+
for (const key of LOCATION_LIST_KEYS) {
|
|
85
|
+
const lists = dictionaries.map((dictionary) => dictionary?.[key]).filter(Boolean);
|
|
86
|
+
if (lists.length) result[key] = mergeLocationEntries(...lists);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
for (const dictionary of dictionaries) {
|
|
90
|
+
if (!dictionary) continue;
|
|
91
|
+
for (const [key, value] of Object.entries(dictionary)) {
|
|
92
|
+
if (LOCATION_LIST_KEYS.includes(key) || value == null) continue;
|
|
93
|
+
result[key] = value;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return Object.freeze(result);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function mergeLocationCountries(...countries) {
|
|
101
|
+
const cityNames = [...new Set(countries.flatMap((country) => Object.keys(country || {})))];
|
|
102
|
+
return Object.freeze(Object.fromEntries(cityNames.map((city) => [
|
|
103
|
+
city,
|
|
104
|
+
mergeLocationCityDictionaries(...countries.map((country) => country?.[city]).filter(Boolean)),
|
|
105
|
+
])));
|
|
106
|
+
}
|