@whiteslove/parsing-lexicon 0.10.0 → 0.10.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/package.json +1 -1
- package/src/geo.js +9 -5
- package/src/geography-detection.js +18 -3
- package/src/hiring-professions.js +19 -3
- package/src/hiring-source-aliases.js +14 -7
- package/src/housing-listing-enrichment.js +11 -4
- package/src/housing-money.js +4 -1
- package/src/housing-structured.js +7 -4
- package/src/kg-location-extensions.js +5 -1
- package/src/kz-address-extensions.js +5 -1
- package/src/kz-almaty-cleaned-residential-extensions.js +5 -1
- package/src/kz-almaty-street-extensions.js +5 -1
- package/src/kz-astana-cleaned-residential-extensions.js +5 -1
- package/src/kz-location-extensions.js +12 -1
- package/src/kz-primary-address-extensions.js +5 -1
- package/src/kz-regional-address-extensions.js +5 -1
- package/src/kz-regional-residential-extensions.js +5 -1
- package/src/kz-residential-extensions.js +5 -1
- package/src/kz-shymkent-cleaned-address-extensions.js +5 -1
- package/src/location-data.js +23 -2
- package/src/locations.js +10 -2
- package/src/money-lexicon.js +1 -1
- package/src/odesa-metropolitan.js +5 -1
- package/src/tashkent-gastronomic-streets.js +5 -1
- package/src/tashkent-housing-geography.js +23 -6
- package/src/tashkent-pois.js +5 -1
- package/src/tashkent-residential-complexes.js +5 -1
- package/src/ua-chernihiv-residential-extensions.js +5 -1
- package/src/ua-residential-extensions.js +5 -1
- package/src/uz-location-extensions.js +12 -1
- package/src/uz-samarkand-context-extensions.js +5 -1
- package/src/uz-tashkent-context-extensions.js +10 -2
package/package.json
CHANGED
package/src/geo.js
CHANGED
|
@@ -19,7 +19,7 @@ export const TASHKENT_DISTRICTS = Object.freeze([
|
|
|
19
19
|
entity('Shaykhantahur', { uzLatn: ['Shayxontohur', 'Shayxontohur tumani', 'Shaykhantahur'], uzCyrl: ['Шайхонтоҳур', 'Шайхонтоҳур тумани', 'Шайхонтохур'], ru: ['Шайхантахур', 'Шайхантахурский район'], en: ['Shaykhantahur', 'Shayxontohur'] }),
|
|
20
20
|
entity('Yunusabad', { uzLatn: ['Yunusobod', 'Yunusobod tumani', 'Yunusabad'], uzCyrl: ['Юнусобод', 'Юнусобод тумани'], ru: ['Юнусабад', 'Юнусабадский район'], en: ['Yunusabad', 'Yunusobod'] }),
|
|
21
21
|
entity('Yakkasaray', { uzLatn: ['Yakkasaroy', 'Yakkasaroy tumani', 'Yakkasaray'], uzCyrl: ['Яккасарой', 'Яккасарой тумани'], ru: ['Яккасарай', 'Яккасарайский район'], en: ['Yakkasaray', 'Yakkasaroy'] }),
|
|
22
|
-
entity('Yangihayot', { uzLatn: ['Yangihayot', 'Yangihayot tumani'], uzCyrl: ['Янгиҳаёт', 'Янгиҳаёт тумани', 'Янгихаёт'], ru: ['Янгихаётский район', 'Янгихаёт'], en: ['Yangihayot'] }),
|
|
22
|
+
entity('Yangihayot', { uzLatn: ['Yangihayot', 'Yangihayot tumani'], uzCyrl: ['Янгиҳаёт', 'Янгиҳаёт тумани', 'Янгихаёт'], ru: ['Янгихаётский район', 'Янгихаёт', 'Янгихаетский район', 'Янгихаетский', 'Янгихает'], en: ['Yangihayot'] }),
|
|
23
23
|
entity('Yashnobod', { uzLatn: ['Yashnobod', 'Yashnobod tumani', 'Yashnabod', 'Yashnabod tumani', 'Yashnobot', 'Yashnobot tumani'], uzCyrl: ['Яшнобод', 'Яшнобод тумани'], ru: ['Яшнабад', 'Яшнабадский район'], en: ['Yashnobod', 'Yashnabad'] }),
|
|
24
24
|
]);
|
|
25
25
|
|
|
@@ -28,17 +28,21 @@ export function canonicalTashkentDistrict(value) {
|
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
function station(name, ru, en, line, aliases = []) {
|
|
31
|
-
const allAliases = [...new Set([name, ru, en, ...aliases])];
|
|
32
|
-
|
|
31
|
+
const allAliases = Object.freeze([...new Set([name, ru, en, ...aliases])]);
|
|
32
|
+
let re = null;
|
|
33
|
+
return Object.freeze({
|
|
33
34
|
canonical: name,
|
|
34
35
|
name,
|
|
35
36
|
type: 'metro',
|
|
36
37
|
country: 'UZ',
|
|
37
38
|
city: 'Tashkent',
|
|
38
39
|
line,
|
|
39
|
-
labels: { ru, en },
|
|
40
|
+
labels: Object.freeze({ ru, en }),
|
|
40
41
|
aliases: allAliases,
|
|
41
|
-
re
|
|
42
|
+
get re() {
|
|
43
|
+
re ||= aliasesToRegex(allAliases);
|
|
44
|
+
return re;
|
|
45
|
+
},
|
|
42
46
|
});
|
|
43
47
|
}
|
|
44
48
|
|
|
@@ -14,10 +14,18 @@ function cityInflectionRegex(aliases) {
|
|
|
14
14
|
|
|
15
15
|
const CITY_MATCHERS = CITIES.map((item) => {
|
|
16
16
|
const aliases = [item.canonical, ...aliasesOf(item)];
|
|
17
|
+
let exact = null;
|
|
18
|
+
let inflected;
|
|
17
19
|
return Object.freeze({
|
|
18
20
|
item,
|
|
19
|
-
exact
|
|
20
|
-
|
|
21
|
+
get exact() {
|
|
22
|
+
exact ||= aliasesToRegex(aliases);
|
|
23
|
+
return exact;
|
|
24
|
+
},
|
|
25
|
+
get inflected() {
|
|
26
|
+
if (inflected === undefined) inflected = cityInflectionRegex(aliases);
|
|
27
|
+
return inflected;
|
|
28
|
+
},
|
|
21
29
|
});
|
|
22
30
|
});
|
|
23
31
|
|
|
@@ -27,7 +35,14 @@ const COUNTRY_MATCHERS = COUNTRIES.map((item) => {
|
|
|
27
35
|
// are still handled by canonicalCountryCode().
|
|
28
36
|
const aliases = [item.canonical, ...aliasesOf(item)]
|
|
29
37
|
.filter((alias) => normalizeForMatch(alias).replace(/\s/g, '').length > 2);
|
|
30
|
-
|
|
38
|
+
let re = null;
|
|
39
|
+
return Object.freeze({
|
|
40
|
+
item,
|
|
41
|
+
get re() {
|
|
42
|
+
re ||= aliasesToRegex(aliases);
|
|
43
|
+
return re;
|
|
44
|
+
},
|
|
45
|
+
});
|
|
31
46
|
});
|
|
32
47
|
|
|
33
48
|
const US_STATE_RE = /\b[A-Z][A-Za-z.'-]+(?:\s+[A-Z][A-Za-z.'-]+){0,3}\s*,?\s+(?:AL|AK|AZ|AR|CA|CO|CT|DE|FL|GA|HI|ID|IL|IN|IA|KS|KY|LA|ME|MD|MA|MI|MN|MS|MO|MT|NE|NV|NH|NJ|NM|NY|NC|ND|OH|OK|OR|PA|RI|SC|SD|TN|TX|UT|VT|VA|WA|WV|WI|WY|DC)\b/;
|
|
@@ -331,6 +331,8 @@ function profession(canonical, group, strongAliases, weakAliases = []) {
|
|
|
331
331
|
const meta = GROUP_META.get(group) || { family: 'other', aliases: [] };
|
|
332
332
|
const strong = [...new Set([...strongAliases, ...(ROMANIAN_PROFESSION_ALIASES[canonical] || [])].filter(Boolean))];
|
|
333
333
|
const weak = [...new Set(weakAliases.filter(Boolean))];
|
|
334
|
+
let strongRe = null;
|
|
335
|
+
let weakRe;
|
|
334
336
|
return Object.freeze({
|
|
335
337
|
id: canonical,
|
|
336
338
|
taxonomyId: `profession.${group}.${canonical}`,
|
|
@@ -340,8 +342,14 @@ function profession(canonical, group, strongAliases, weakAliases = []) {
|
|
|
340
342
|
aliases: Object.freeze(strong),
|
|
341
343
|
strongAliases: Object.freeze(strong),
|
|
342
344
|
weakAliases: Object.freeze(weak),
|
|
343
|
-
strongRe
|
|
344
|
-
|
|
345
|
+
get strongRe() {
|
|
346
|
+
strongRe ||= aliasesToRegex(strong);
|
|
347
|
+
return strongRe;
|
|
348
|
+
},
|
|
349
|
+
get weakRe() {
|
|
350
|
+
if (weakRe === undefined) weakRe = weak.length ? aliasesToRegex(weak) : null;
|
|
351
|
+
return weakRe;
|
|
352
|
+
},
|
|
345
353
|
});
|
|
346
354
|
}
|
|
347
355
|
|
|
@@ -369,7 +377,15 @@ export const SENIORITY_LEVELS = Object.freeze([
|
|
|
369
377
|
{ canonical: 'chief', aliases: ['chief officer','chief executive','chief technology','chief product','chief financial','chief marketing','chief operating'], score: 1 },
|
|
370
378
|
].map((entry) => {
|
|
371
379
|
const aliases = [...new Set([...entry.aliases, ...(ROMANIAN_SENIORITY_ALIASES[entry.canonical] || [])])];
|
|
372
|
-
|
|
380
|
+
let re = null;
|
|
381
|
+
return Object.freeze({
|
|
382
|
+
...entry,
|
|
383
|
+
aliases: Object.freeze(aliases),
|
|
384
|
+
get re() {
|
|
385
|
+
re ||= aliasesToRegex(aliases);
|
|
386
|
+
return re;
|
|
387
|
+
},
|
|
388
|
+
});
|
|
373
389
|
}));
|
|
374
390
|
|
|
375
391
|
export function matchProfessions(value, { limit = 5, allowWeak = true } = {}) {
|
|
@@ -1,13 +1,20 @@
|
|
|
1
1
|
import { aliasesToRegex } from './normalization.js';
|
|
2
2
|
import { matchProfessions } from './hiring-professions.js';
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
4
|
+
function sourceRole(canonical, label, aliases, group = 'other') {
|
|
5
|
+
const all = Object.freeze([...new Set(aliases)]);
|
|
6
|
+
let re = null;
|
|
7
|
+
return Object.freeze({
|
|
8
|
+
canonical,
|
|
9
|
+
label,
|
|
10
|
+
group,
|
|
11
|
+
aliases: all,
|
|
12
|
+
get re() {
|
|
13
|
+
re ||= aliasesToRegex(all);
|
|
14
|
+
return re;
|
|
15
|
+
},
|
|
16
|
+
});
|
|
17
|
+
}
|
|
11
18
|
|
|
12
19
|
/**
|
|
13
20
|
* Source spellings seen in real CV feeds which are too source-specific or
|
|
@@ -32,7 +32,7 @@ const APPLIANCE_PATTERNS = Object.freeze([
|
|
|
32
32
|
|
|
33
33
|
const FIRST_RENT_UZ_RE = /(?:hali\s+hech\s+kim\s+(?:yashamagan|turmagan)|ҳали\s+ҳеч\s+ким\s+(?:яшамаган|турмаган))/iu;
|
|
34
34
|
const LANDLORD_PRESENT_RE = /(?:xozaykali|hojaykali|xo['’]?jaykali|с\s+хозяйк(?:ой|ой\s+в\s+квартире)|хозяйк\p{L}*\s+(?:жив[её]т|прожива\p{L}*)|with\s+(?:the\s+)?(?:landlord|owner)\s+(?:present|living\s+in)|cu\s+proprietar(?:ul)?\s+în\s+cas(?:ă|a)|үй\s*иесі\s+(?:тұрады|бірге\s+тұрады))/iu;
|
|
35
|
-
const STUDENT_RE = /(?:studentlar\s+uchun|talabalar\s+uchun|студент(?:ам|ы|ок|ов)?\s+(?:можно|для)|для\s+студент|students?\s+(?:only|welcome)|for\s+students|pentru\s+studen[țt]i|studen[țt]i(?:lor)?|студенттерге|студенттер\s+үшін|(?:oila|oyla)(?:ga|lar|li)?\s+yoki\s+\d{1,2}\s+ta\s+bola(?:lar)?(?:ga)?\s+(?:ijara(?:ga)?\s+)?(?:beril|topshiril))/iu;
|
|
35
|
+
const STUDENT_RE = /(?:studentlar\s+uchun|talabalar\s+uchun|(?<![\p{L}\p{N}_])(?:student(?:ka)?|talaba)(?:lar)?(?:ga)?\s+(?:qiz|yigit|o['’ʻʼ‘`]?g['’ʻʼ‘`]?il\s+bola|bola)(?:lar)?(?:ga)?(?![\p{L}\p{N}_])|(?<![\p{L}\p{N}_])(?:studentlar|talabalar)ga(?![\p{L}\p{N}_])|студент(?:ам|ы|ок|ов)?\s+(?:можно|для)|для\s+студент|students?\s+(?:only|welcome)|for\s+students|pentru\s+studen[țt]i|studen[țt]i(?:lor)?|студенттерге|студенттер\s+үшін|(?:oila|oyla)(?:ga|lar|li)?\s+yoki\s+\d{1,2}\s+ta\s+bola(?:lar)?(?:ga)?\s+(?:ijara(?:ga)?\s+)?(?:beril|topshiril))/iu;
|
|
36
36
|
const NO_BROKER_RE = /(?:bez\s*makler|maklersiz|vositachisiz|без\s+(?:маклер|посредник|риелтор|риэлтор|комисси)|no\s+(?:broker|agent|commission|agency\s+fee)|f[ăa]r[ăa]\s+(?:comision|agen[țt]ie|intermediari)|делдалсыз|комиссиясыз)/iu;
|
|
37
37
|
const BROKER_RE = /(?:makler|vositachi|макл(?:ер[а-яё]*)?|ри[еэ]лтор[а-яё]*|агентств[а-яё]*|комисси[а-яё]*|broker|realtor|commission|comision(?:ul)?|agen[țt]ie|delda[lл]\p{L}*|делдал\p{L}*)/iu;
|
|
38
38
|
const MEN_RE = /(?:o['’ʻʼ‘`]?g['’ʻʼ‘`]?il\s+bola(?:lar)?(?:ga)?|ogil\s+bola(?:lar)?(?:ga)?|sherik\s+bola|эркак(?:лар)?|erkak(?:lar)?(?:ga)?|только\s+(?:мужчин|парн)|\bmen\s+only\b|b[ăa]rba[țt]i(?:lor)?|b[ăa]ie[țt]i(?:lor)?|жігіттерге|жігіттер(?:ге)?|хлопц(?:ям|і|ів)?|чоловік(?:ам|и)?)/iu;
|
|
@@ -45,6 +45,10 @@ const FAMILY_EXCLUSION_RE = /(?:oila|oyla|семь\p{L}*|family|famil(?:ie|ia)|c
|
|
|
45
45
|
const ROOM_SHARE_RE = /(?:sherik(?:ka|lik|likga)?|шерик(?:ка|лик)?|roommate|flatmate|подселени|койко[-\s]?мест|место\s+в\s+(?:комнат|квартир)|birga\s+yashash(?:ga)?|kvartira(?:ga|da)?[^\r\n.!?]{0,36}(?:\d+|bitta|1)\s*(?:ta\s*)?(?:qiz|ayol)[^\r\n.!?]{0,20}(?:ijarachi\s*)?(?:kerak|kere)|coleg\s+de\s+(?:apartament|camer[ăa])|bed\s+space|бөлмелес(?:\s+керек)?|көрші\s+керек)/iu;
|
|
46
46
|
const AIR_CONDITIONER_RE = /(?:кондицион|air\s*con|konditsioner|kandit(?:s|c)?aner|kanditsaner|кандитсанер)/iu;
|
|
47
47
|
const PER_PERSON_PRICE_RE = /(?:kishi\s+boshiga|киши\s+бошига)\s*(\d{1,3}(?:[\s.,]\d{3})*|\d+(?:[.,]\d+)?)\s*(ming|минг|million|mln|млн)?(?:dan|дан)?/iu;
|
|
48
|
+
// Shared-flat posts price each tenant with the ablative "-dan" ("800 000
|
|
49
|
+
// so'mdan" = "800 000 so'm from each") once they ask for several people.
|
|
50
|
+
const PER_PERSON_SUFFIX_PRICE_RE = /(\d{1,3}(?:[ ]\d{3})+|\d+)\s*(ming|минг|million|mln|млн)?\s*(?:so['’ʻʼ‘`]?m|сўм|сум)(?:dan|дан)(?![\p{L}\p{N}_])/iu;
|
|
51
|
+
const SEVERAL_OCCUPANTS_RE = /(?<![\p{L}\p{N}_])([2-9])\s*ta\s+(?:qiz|yigit|bola|kishi|odam|talaba|student)\p{L}*\s+(?:kerak|kere|olinadi)/iu;
|
|
48
52
|
const WALK_MINUTES_RE = /(?:yayov|piyoda|пешком)\s*(\d{1,2})\s*(?:daqiqa|min(?:ute)?s?|минут)/iu;
|
|
49
53
|
const TRANSIT_ROUTES_RE = /(?:aftobuslar|avtobuslar|автобуслар|автобусы)[^\r\n\d]{0,24}((?:\d{1,4}[\s,;/]*){1,10})/iu;
|
|
50
54
|
const NEARBY_RELATION_TAIL_RE = /(?<!\p{L})(?:рядом\s+(?:с|со)|недалеко\s+от|возле|около|напротив|навпроти|ориентир\s*[:—–-]?|ор[-–—]?р\.?\s*[:—–-]?|near(?:by)?|close\s+to|next\s+to|opposite|behind|in\s+front\s+of|yaqin(?:ida)?|lângă|aproape\s+de|în\s+apropiere\s+de|vizavi\s+de|în\s+spatele|în\s+fața)(?!\p{L})[^.!?\r\n;]*/giu;
|
|
@@ -194,7 +198,9 @@ export function parseHousingAudience(value) {
|
|
|
194
198
|
const students = STUDENT_RE.test(text);
|
|
195
199
|
if (family && students) return deepFreeze({ primary: 'family', alternatives: ['family', 'students'] });
|
|
196
200
|
if (family && women) return deepFreeze({ primary: 'family', alternatives: ['family', 'women'] });
|
|
197
|
-
|
|
201
|
+
// "Student qizlarga" narrows students to girls: the gender is the binding
|
|
202
|
+
// restriction, and studentTarget still reports the student part.
|
|
203
|
+
if (students && !women && !men) return deepFreeze({ primary: 'students', alternatives: ['students'] });
|
|
198
204
|
if (family) return deepFreeze({ primary: 'family', alternatives: ['family'] });
|
|
199
205
|
if (women) return deepFreeze({ primary: 'women', alternatives: ['women'] });
|
|
200
206
|
if (men) return deepFreeze({ primary: 'men', alternatives: ['men'] });
|
|
@@ -222,14 +228,15 @@ export function parseHousingCommission(value) {
|
|
|
222
228
|
|
|
223
229
|
export function parseHousingPerPersonPrice(value, { country = '' } = {}) {
|
|
224
230
|
const text = normalizeUnicode(value ?? '');
|
|
225
|
-
const match = text.match(PER_PERSON_PRICE_RE)
|
|
231
|
+
const match = text.match(PER_PERSON_PRICE_RE)
|
|
232
|
+
|| (SEVERAL_OCCUPANTS_RE.test(text) ? text.match(PER_PERSON_SUFFIX_PRICE_RE) : null);
|
|
226
233
|
if (!match) return null;
|
|
227
234
|
let amount = Number(String(match[1]).replace(/\s+/g, '').replace(',', '.'));
|
|
228
235
|
if (!Number.isFinite(amount) || amount <= 0) return null;
|
|
229
236
|
const scale = String(match[2] || '').toLowerCase();
|
|
230
237
|
if (scale === 'ming' || scale === 'минг') amount *= 1000;
|
|
231
238
|
else if (scale === 'million' || scale === 'mln' || scale === 'млн') amount *= 1_000_000;
|
|
232
|
-
const currency = String(country || '').toUpperCase() === 'UZ' || /(?:ming
|
|
239
|
+
const currency = String(country || '').toUpperCase() === 'UZ' || /(?:ming|минг|so['’ʻʼ‘`]?m|сўм|сум)/iu.test(match[0]) ? 'UZS' : null;
|
|
233
240
|
const approximate = /около|примерно|~|≈/iu.test(text);
|
|
234
241
|
return deepFreeze({ amount, currency, approximate, scope: 'person' });
|
|
235
242
|
}
|
package/src/housing-money.js
CHANGED
|
@@ -602,7 +602,10 @@ export function parseHousingPrice(value, context = '') {
|
|
|
602
602
|
const hasExplicitUzbekThousandScale = country === 'UZ' && /\b(?:ming|минг)\b/iu.test(priceText);
|
|
603
603
|
if (!explicit && !hasExplicitUzbekThousandScale && fallbackCurrency === 'UZS' && price != null) {
|
|
604
604
|
const dailyUzbek = /(?:kunlik|sutkaga|kecha[- ]?kunduz|посуточн|суточн)/i.test(priceText);
|
|
605
|
-
|
|
605
|
+
// A monthly rent of 100,000+ is only plausible in so'm; the USD reading
|
|
606
|
+
// turned "800 000" per person into an 800,000 USD rent.
|
|
607
|
+
const monthlyRent = dealType === 'longRent' && price >= 100_000;
|
|
608
|
+
currency = price >= 1_000_000 || monthlyRent || (dailyUzbek && price >= 10_000) ? 'UZS' : 'USD';
|
|
606
609
|
}
|
|
607
610
|
|
|
608
611
|
return Object.freeze({ amount: price, currency, approximate: price != null && APPROXIMATE_RE.test(priceText) });
|
|
@@ -87,7 +87,9 @@ export function parseHousingRoomCount(value) {
|
|
|
87
87
|
if (total >= 1 && total <= 20) return total;
|
|
88
88
|
}
|
|
89
89
|
for (const [re, rooms] of NUMBER_WORDS) if (re.test(text)) return rooms;
|
|
90
|
-
|
|
90
|
+
// "5-х комн.кв" / "3-ех комнатная": a Russian case ending may sit between
|
|
91
|
+
// the number and the room word, which may itself be abbreviated.
|
|
92
|
+
const numeric = text.match(/(?:^|[^\p{L}\p{N}])(\d{1,2})\s*(?:-\s*(?:х|ти|и|ух|[её]х)\s*)?(?:ta\s*)?(?:(?:-\s*)?комн(?:ат)?\p{L}*|(?:-\s*)?к(?:\.|\b)|(?:-\s*)?xona(?:li|si|lari)?|(?:-\s*)?хона(?:лик|ли|си|лари)?|бөлмелі|rooms?)(?=$|[^\p{L}\p{N}])/iu);
|
|
91
93
|
if (numeric) {
|
|
92
94
|
const rooms = toNumber(numeric[1]);
|
|
93
95
|
if (rooms != null && rooms >= 1 && rooms <= 20) return rooms;
|
|
@@ -127,7 +129,8 @@ export function parseHousingFloor(value) {
|
|
|
127
129
|
}
|
|
128
130
|
}
|
|
129
131
|
|
|
130
|
-
|
|
132
|
+
// "3-й этаж" / "5-м этаже" carry a Russian ordinal ending like Uzbek "-chi".
|
|
133
|
+
const beforeMarker = text.match(/(?:^|[^\d])(\d{1,3})\s*-?\s*(?:(?:chi|чи|й|ий|ый|ой|м|ом|ем)\s*)?(?:этаж(?:да)?|поверх|floor|etaj|qavat(?:i(?:da(?:gi)?)?|da)?|қабат(?:ы(?:нда(?:ғы)?)?|та)?|кават(?:и(?:да(?:ги)?)?|да)?|қават(?:и(?:да(?:ги)?)?|да)?)(?=$|[^\p{L}\p{N}_])/iu);
|
|
131
134
|
let floor = toNumber(beforeMarker?.[1]);
|
|
132
135
|
let totalFloors = null;
|
|
133
136
|
|
|
@@ -140,7 +143,7 @@ export function parseHousingFloor(value) {
|
|
|
140
143
|
if (floor == null && FIRST_FLOOR_WORD_RE.test(text)) floor = 1;
|
|
141
144
|
|
|
142
145
|
if (totalFloors == null) {
|
|
143
|
-
const total = text.match(/(?:дом\s*)?(\d{1,3})\s*(?:[- ]?этаж(?:н\p{L}*|лик)|поверхов\p{L}*|storey|story|floors?\s+total|qavatli|қабатты|каватли|қаватли)/iu);
|
|
146
|
+
const total = text.match(/(?:дом\s*)?(\d{1,3})\s*(?:-\s*(?:ти|х|и|ми)\s*)?(?:[- ]?этаж(?:н\p{L}*|лик)|поверхов\p{L}*|storey|story|floors?\s+total|qavatli|қабатты|каватли|қаватли)/iu);
|
|
144
147
|
totalFloors = toNumber(total?.[1]);
|
|
145
148
|
}
|
|
146
149
|
|
|
@@ -167,7 +170,7 @@ const AREA_UNIT_RE = String.raw`(?:м²|м2|m²|m2|sqm|sq\.?\s*m|mp|кв\.?\s*м
|
|
|
167
170
|
const AREA_UNIT_AFTER_LABEL_RE = String.raw`(?:${AREA_UNIT_RE}|[mм])`;
|
|
168
171
|
|
|
169
172
|
function areaAfterLabel(text, labelRe) {
|
|
170
|
-
const re = new RegExp(`${labelRe.source}\\s*[:=-]?\\s*(\\d{1,4}(?:[.,]\\d{1,2})?)\\s*${AREA_UNIT_AFTER_LABEL_RE}(?=$|[^\\p{L}\\p{N}_])`, 'iu');
|
|
173
|
+
const re = new RegExp(`${labelRe.source}\\s*[:=-]?\\s*(\\d{1,4}(?:[.,]\\d{1,2})?)\\s*-?\\s*${AREA_UNIT_AFTER_LABEL_RE}(?=$|[^\\p{L}\\p{N}_])`, 'iu');
|
|
171
174
|
return toNumber(text.match(re)?.[1]);
|
|
172
175
|
}
|
|
173
176
|
|
|
@@ -2,6 +2,7 @@ import { aliasesToRegex } from './normalization.js';
|
|
|
2
2
|
|
|
3
3
|
function entry(name, aliases = [], meta = {}) {
|
|
4
4
|
const all = Object.freeze([...new Set([name, ...aliases].filter(Boolean))]);
|
|
5
|
+
let re = null;
|
|
5
6
|
return Object.freeze({
|
|
6
7
|
...meta,
|
|
7
8
|
canonical: name,
|
|
@@ -10,7 +11,10 @@ function entry(name, aliases = [], meta = {}) {
|
|
|
10
11
|
entityType: meta.entityType || meta.type,
|
|
11
12
|
country: 'KG',
|
|
12
13
|
aliases: all,
|
|
13
|
-
re
|
|
14
|
+
get re() {
|
|
15
|
+
re ||= aliasesToRegex(all);
|
|
16
|
+
return re;
|
|
17
|
+
},
|
|
14
18
|
});
|
|
15
19
|
}
|
|
16
20
|
|
|
@@ -2,6 +2,7 @@ import { aliasesToRegex } from './normalization.js';
|
|
|
2
2
|
|
|
3
3
|
function entry(name, aliases, entityType) {
|
|
4
4
|
const all = Object.freeze([...new Set([name, ...aliases].filter(Boolean))]);
|
|
5
|
+
let re = null;
|
|
5
6
|
return Object.freeze({
|
|
6
7
|
canonical: name,
|
|
7
8
|
name,
|
|
@@ -9,7 +10,10 @@ function entry(name, aliases, entityType) {
|
|
|
9
10
|
entityType,
|
|
10
11
|
country: 'KZ',
|
|
11
12
|
aliases: all,
|
|
12
|
-
re
|
|
13
|
+
get re() {
|
|
14
|
+
re ||= aliasesToRegex(all);
|
|
15
|
+
return re;
|
|
16
|
+
},
|
|
13
17
|
});
|
|
14
18
|
}
|
|
15
19
|
|
|
@@ -2,6 +2,7 @@ import { aliasesToRegex } from './normalization.js';
|
|
|
2
2
|
|
|
3
3
|
function residential(name, aliases = []) {
|
|
4
4
|
const all = Object.freeze([...new Set([name, ...aliases].filter(Boolean))]);
|
|
5
|
+
let re = null;
|
|
5
6
|
return Object.freeze({
|
|
6
7
|
canonical: name,
|
|
7
8
|
name,
|
|
@@ -9,7 +10,10 @@ function residential(name, aliases = []) {
|
|
|
9
10
|
entityType: 'residential_complex',
|
|
10
11
|
country: 'KZ',
|
|
11
12
|
aliases: all,
|
|
12
|
-
re
|
|
13
|
+
get re() {
|
|
14
|
+
re ||= aliasesToRegex(all);
|
|
15
|
+
return re;
|
|
16
|
+
},
|
|
13
17
|
});
|
|
14
18
|
}
|
|
15
19
|
|
|
@@ -2,6 +2,7 @@ import { aliasesToRegex } from './normalization.js';
|
|
|
2
2
|
|
|
3
3
|
function street(name, aliases = []) {
|
|
4
4
|
const all = Object.freeze([...new Set([name, ...aliases].filter(Boolean))]);
|
|
5
|
+
let re = null;
|
|
5
6
|
return Object.freeze({
|
|
6
7
|
canonical: name,
|
|
7
8
|
name,
|
|
@@ -9,7 +10,10 @@ function street(name, aliases = []) {
|
|
|
9
10
|
entityType: 'street',
|
|
10
11
|
country: 'KZ',
|
|
11
12
|
aliases: all,
|
|
12
|
-
re
|
|
13
|
+
get re() {
|
|
14
|
+
re ||= aliasesToRegex(all);
|
|
15
|
+
return re;
|
|
16
|
+
},
|
|
13
17
|
});
|
|
14
18
|
}
|
|
15
19
|
|
|
@@ -2,6 +2,7 @@ import { aliasesToRegex } from './normalization.js';
|
|
|
2
2
|
|
|
3
3
|
function residential(name, aliases = []) {
|
|
4
4
|
const all = Object.freeze([...new Set([name, ...aliases].filter(Boolean))]);
|
|
5
|
+
let re = null;
|
|
5
6
|
return Object.freeze({
|
|
6
7
|
canonical: name,
|
|
7
8
|
name,
|
|
@@ -9,7 +10,10 @@ function residential(name, aliases = []) {
|
|
|
9
10
|
entityType: 'residential_complex',
|
|
10
11
|
country: 'KZ',
|
|
11
12
|
aliases: all,
|
|
12
|
-
re
|
|
13
|
+
get re() {
|
|
14
|
+
re ||= aliasesToRegex(all);
|
|
15
|
+
return re;
|
|
16
|
+
},
|
|
13
17
|
});
|
|
14
18
|
}
|
|
15
19
|
|
|
@@ -2,7 +2,18 @@ import { aliasesToRegex } from './normalization.js';
|
|
|
2
2
|
|
|
3
3
|
function entry(name, aliases = [], meta = {}) {
|
|
4
4
|
const all = [...new Set([name, ...aliases].filter(Boolean))];
|
|
5
|
-
|
|
5
|
+
let re = null;
|
|
6
|
+
return Object.freeze({
|
|
7
|
+
...meta,
|
|
8
|
+
canonical: name,
|
|
9
|
+
name,
|
|
10
|
+
type: meta.type || meta.entityType,
|
|
11
|
+
aliases: Object.freeze(all),
|
|
12
|
+
get re() {
|
|
13
|
+
re ||= aliasesToRegex(all);
|
|
14
|
+
return re;
|
|
15
|
+
},
|
|
16
|
+
});
|
|
6
17
|
}
|
|
7
18
|
|
|
8
19
|
function entries(rows = [], defaults = {}) {
|
|
@@ -2,6 +2,7 @@ import { aliasesToRegex } from './normalization.js';
|
|
|
2
2
|
|
|
3
3
|
function entry(name, aliases, entityType) {
|
|
4
4
|
const all = Object.freeze([...new Set([name, ...aliases].filter(Boolean))]);
|
|
5
|
+
let re = null;
|
|
5
6
|
return Object.freeze({
|
|
6
7
|
canonical: name,
|
|
7
8
|
name,
|
|
@@ -9,7 +10,10 @@ function entry(name, aliases, entityType) {
|
|
|
9
10
|
entityType,
|
|
10
11
|
country: 'KZ',
|
|
11
12
|
aliases: all,
|
|
12
|
-
re
|
|
13
|
+
get re() {
|
|
14
|
+
re ||= aliasesToRegex(all);
|
|
15
|
+
return re;
|
|
16
|
+
},
|
|
13
17
|
});
|
|
14
18
|
}
|
|
15
19
|
|
|
@@ -2,6 +2,7 @@ import { aliasesToRegex } from './normalization.js';
|
|
|
2
2
|
|
|
3
3
|
function street(name, aliases = []) {
|
|
4
4
|
const all = Object.freeze([...new Set([name, ...aliases].filter(Boolean))]);
|
|
5
|
+
let re = null;
|
|
5
6
|
return Object.freeze({
|
|
6
7
|
canonical: name,
|
|
7
8
|
name,
|
|
@@ -9,7 +10,10 @@ function street(name, aliases = []) {
|
|
|
9
10
|
entityType: 'street',
|
|
10
11
|
country: 'KZ',
|
|
11
12
|
aliases: all,
|
|
12
|
-
re
|
|
13
|
+
get re() {
|
|
14
|
+
re ||= aliasesToRegex(all);
|
|
15
|
+
return re;
|
|
16
|
+
},
|
|
13
17
|
});
|
|
14
18
|
}
|
|
15
19
|
|
|
@@ -2,6 +2,7 @@ import { aliasesToRegex } from './normalization.js';
|
|
|
2
2
|
|
|
3
3
|
function residential(name, aliases = []) {
|
|
4
4
|
const all = Object.freeze([...new Set([name, ...aliases].filter(Boolean))]);
|
|
5
|
+
let re = null;
|
|
5
6
|
return Object.freeze({
|
|
6
7
|
canonical: name,
|
|
7
8
|
name,
|
|
@@ -9,7 +10,10 @@ function residential(name, aliases = []) {
|
|
|
9
10
|
entityType: 'residential_complex',
|
|
10
11
|
country: 'KZ',
|
|
11
12
|
aliases: all,
|
|
12
|
-
re
|
|
13
|
+
get re() {
|
|
14
|
+
re ||= aliasesToRegex(all);
|
|
15
|
+
return re;
|
|
16
|
+
},
|
|
13
17
|
});
|
|
14
18
|
}
|
|
15
19
|
|
|
@@ -2,6 +2,7 @@ import { aliasesToRegex } from './normalization.js';
|
|
|
2
2
|
|
|
3
3
|
function residential(name, aliases = []) {
|
|
4
4
|
const all = Object.freeze([...new Set([name, ...aliases].filter(Boolean))]);
|
|
5
|
+
let re = null;
|
|
5
6
|
return Object.freeze({
|
|
6
7
|
canonical: name,
|
|
7
8
|
name,
|
|
@@ -9,7 +10,10 @@ function residential(name, aliases = []) {
|
|
|
9
10
|
entityType: 'residential_complex',
|
|
10
11
|
country: 'KZ',
|
|
11
12
|
aliases: all,
|
|
12
|
-
re
|
|
13
|
+
get re() {
|
|
14
|
+
re ||= aliasesToRegex(all);
|
|
15
|
+
return re;
|
|
16
|
+
},
|
|
13
17
|
});
|
|
14
18
|
}
|
|
15
19
|
|
|
@@ -2,6 +2,7 @@ import { aliasesToRegex } from './normalization.js';
|
|
|
2
2
|
|
|
3
3
|
function entry(name, aliases = [], entityType) {
|
|
4
4
|
const all = Object.freeze([...new Set([name, ...aliases].filter(Boolean))]);
|
|
5
|
+
let re = null;
|
|
5
6
|
return Object.freeze({
|
|
6
7
|
canonical: name,
|
|
7
8
|
name,
|
|
@@ -9,7 +10,10 @@ function entry(name, aliases = [], entityType) {
|
|
|
9
10
|
entityType,
|
|
10
11
|
country: 'KZ',
|
|
11
12
|
aliases: all,
|
|
12
|
-
re
|
|
13
|
+
get re() {
|
|
14
|
+
re ||= aliasesToRegex(all);
|
|
15
|
+
return re;
|
|
16
|
+
},
|
|
13
17
|
});
|
|
14
18
|
}
|
|
15
19
|
|
package/src/location-data.js
CHANGED
|
@@ -5,14 +5,35 @@ import { UA_REGIONS as UA_REGION_LEXICON } from './geography.js';
|
|
|
5
5
|
function entries(rows) {
|
|
6
6
|
return Object.freeze(rows.map(([name, ...aliases]) => {
|
|
7
7
|
const all = [...new Set([name, ...aliases].filter(Boolean))];
|
|
8
|
-
|
|
8
|
+
let re = null;
|
|
9
|
+
return Object.freeze({
|
|
10
|
+
canonical: name,
|
|
11
|
+
name,
|
|
12
|
+
aliases: Object.freeze(all),
|
|
13
|
+
get re() {
|
|
14
|
+
re ||= aliasesToRegex(all);
|
|
15
|
+
return re;
|
|
16
|
+
},
|
|
17
|
+
});
|
|
9
18
|
}));
|
|
10
19
|
}
|
|
11
20
|
|
|
12
21
|
function lexiconEntries(items) {
|
|
13
22
|
return Object.freeze((items || []).map((item) => {
|
|
14
23
|
const all = [...new Set([item.canonical, ...aliasesOf(item)].filter(Boolean))];
|
|
15
|
-
|
|
24
|
+
let re = null;
|
|
25
|
+
return Object.freeze({
|
|
26
|
+
canonical: item.canonical,
|
|
27
|
+
name: item.canonical,
|
|
28
|
+
type: item.type,
|
|
29
|
+
country: item.country,
|
|
30
|
+
city: item.city,
|
|
31
|
+
aliases: Object.freeze(all),
|
|
32
|
+
get re() {
|
|
33
|
+
re ||= aliasesToRegex(all);
|
|
34
|
+
return re;
|
|
35
|
+
},
|
|
36
|
+
});
|
|
16
37
|
}));
|
|
17
38
|
}
|
|
18
39
|
|
package/src/locations.js
CHANGED
|
@@ -115,6 +115,7 @@ const UZ_BASE_LOCATION_DICTIONARIES = Object.freeze({
|
|
|
115
115
|
|
|
116
116
|
function semanticEntry(entry, name, aliases, entityType) {
|
|
117
117
|
const all = Object.freeze([...new Set([name, ...aliases].filter(Boolean))]);
|
|
118
|
+
let re = null;
|
|
118
119
|
return Object.freeze({
|
|
119
120
|
...entry,
|
|
120
121
|
canonical: name,
|
|
@@ -122,7 +123,10 @@ function semanticEntry(entry, name, aliases, entityType) {
|
|
|
122
123
|
type: entityType,
|
|
123
124
|
entityType,
|
|
124
125
|
aliases: all,
|
|
125
|
-
re
|
|
126
|
+
get re() {
|
|
127
|
+
re ||= aliasesToRegex(all);
|
|
128
|
+
return re;
|
|
129
|
+
},
|
|
126
130
|
});
|
|
127
131
|
}
|
|
128
132
|
|
|
@@ -179,13 +183,17 @@ function normalizeUzSemanticLocations(country) {
|
|
|
179
183
|
'Корасув дахаси',
|
|
180
184
|
'Карасу даха',
|
|
181
185
|
])]);
|
|
186
|
+
let qorasuvRe = null;
|
|
182
187
|
const qorasuvArea = Object.freeze({
|
|
183
188
|
...qorasuv,
|
|
184
189
|
type: 'local_area',
|
|
185
190
|
entityType: 'local_area',
|
|
186
191
|
parent: 'Mirzo Ulugbek',
|
|
187
192
|
aliases: qorasuvAliases,
|
|
188
|
-
re
|
|
193
|
+
get re() {
|
|
194
|
+
qorasuvRe ||= aliasesToRegex(qorasuvAliases);
|
|
195
|
+
return qorasuvRe;
|
|
196
|
+
},
|
|
189
197
|
});
|
|
190
198
|
|
|
191
199
|
normalized = Object.freeze({
|
package/src/money-lexicon.js
CHANGED
|
@@ -5,7 +5,7 @@ const group = (canonical, aliases, extra = {}) => lexiconEntity(canonical, alias
|
|
|
5
5
|
export const CURRENCY_TERMS = Object.freeze([
|
|
6
6
|
group('USD', { ru: ['$', 'usd', 'доллар', 'доллара', 'долларов', 'бакс', 'у.е.', 'у е'], en: ['$', 'usd', 'dollar', 'dollars', 'us dollar'], uk: ['$', 'usd', 'долар', 'долари', 'доларів'], ro: ['$', 'usd', 'dolar', 'dolari'], uzLatn: ['$', 'usd', 'dollar'], uzCyrl: ['$', 'доллар'], kk: ['$', 'usd', 'доллар'] }),
|
|
7
7
|
group('EUR', { ru: ['€', 'eur', 'евро'], en: ['€', 'eur', 'euro', 'euros'], uk: ['€', 'eur', 'євро'], ro: ['€', 'eur', 'euro'], uzLatn: ['€', 'eur', 'yevro'], uzCyrl: ['€', 'евро'], kk: ['€', 'eur', 'еуро'] }),
|
|
8
|
-
group('UZS', { ru: ['сум', 'сумов', 'узс'], en: ['uzs', 'uzbek som'], uk: ['uzs', 'сум'], ro: ['uzs'], uzLatn: ["so'm", 'so‘m', 'soʻm', 'sum', 'uzs'], uzCyrl: ['сўм', 'сум'], kk: ['uzs'] }),
|
|
8
|
+
group('UZS', { ru: ['сум', 'сумов', 'узс'], en: ['uzs', 'uzbek som'], uk: ['uzs', 'сум'], ro: ['uzs'], uzLatn: ["so'm", 'so‘m', 'soʻm', 'sum', 'uzs', "so'mdan", 'so‘mdan', 'soʻmdan', 'somdan', 'sumdan', "so'mgacha", 'so‘mgacha', 'soʻmgacha'], uzCyrl: ['сўм', 'сум', 'сўмдан', 'сумдан', 'сўмгача'], kk: ['uzs'] }),
|
|
9
9
|
group('KZT', { ru: ['₸', 'тенге', 'тг', 'kzt'], en: ['₸', 'kzt', 'tenge'], uk: ['₸', 'тенге', 'kzt'], ro: ['₸', 'kzt', 'tenge'], uzLatn: ['₸', 'kzt', 'tenge'], uzCyrl: ['₸', 'kzt', 'тенге'], kk: ['₸', 'теңге', 'тенге', 'тг', 'kzt'] }),
|
|
10
10
|
group('UAH', { ru: ['грн', 'гривна', 'гривны', 'гривен', '₴', 'uah'], en: ['uah', 'hryvnia', 'hryvnias', '₴'], uk: ['грн', 'гривня', 'гривні', 'гривень', '₴', 'uah'], ro: ['uah', 'hrivne', 'grivne'], uzLatn: ['uah'], uzCyrl: ['uah'], kk: ['uah'] }),
|
|
11
11
|
group('RUB', { ru: ['₽', 'руб', 'руб.', 'рубль', 'рубля', 'рублей', 'rub', 'rur'], en: ['₽', 'rub', 'rur', 'ruble', 'rubles'], uk: ['₽', 'руб', 'рублів', 'rub'], ro: ['₽', 'rub', 'ruble'], uzLatn: ['₽', 'rub', 'rubl'], uzCyrl: ['₽', 'рубль'], kk: ['₽', 'rub', 'рубль'] }),
|
|
@@ -2,12 +2,16 @@ import { aliasesToRegex } from './normalization.js';
|
|
|
2
2
|
|
|
3
3
|
function entity(name, type, aliases = [], options = {}) {
|
|
4
4
|
const all = [...new Set([name, ...aliases].filter(Boolean))];
|
|
5
|
+
let re = null;
|
|
5
6
|
return Object.freeze({
|
|
6
7
|
canonical: name,
|
|
7
8
|
name,
|
|
8
9
|
type,
|
|
9
10
|
aliases: Object.freeze(all),
|
|
10
|
-
re
|
|
11
|
+
get re() {
|
|
12
|
+
re ||= aliasesToRegex(all);
|
|
13
|
+
return re;
|
|
14
|
+
},
|
|
11
15
|
parent: options.parent || null,
|
|
12
16
|
cluster: options.cluster || null,
|
|
13
17
|
contextRequired: Boolean(options.contextRequired),
|
|
@@ -2,6 +2,7 @@ import { aliasesToRegex } from './normalization.js';
|
|
|
2
2
|
|
|
3
3
|
function gastronomicStreet(canonical, aliases = [], district = null) {
|
|
4
4
|
const all = Object.freeze([...new Set([canonical, ...aliases].filter(Boolean))]);
|
|
5
|
+
let re = null;
|
|
5
6
|
return Object.freeze({
|
|
6
7
|
canonical,
|
|
7
8
|
name: canonical,
|
|
@@ -11,7 +12,10 @@ function gastronomicStreet(canonical, aliases = [], district = null) {
|
|
|
11
12
|
city: 'Tashkent',
|
|
12
13
|
district,
|
|
13
14
|
aliases: all,
|
|
14
|
-
re
|
|
15
|
+
get re() {
|
|
16
|
+
re ||= aliasesToRegex(all);
|
|
17
|
+
return re;
|
|
18
|
+
},
|
|
15
19
|
});
|
|
16
20
|
}
|
|
17
21
|
|
|
@@ -3,6 +3,7 @@ import { TASHKENT_DISTRICTS, TASHKENT_METRO } from './geo.js';
|
|
|
3
3
|
|
|
4
4
|
function locationEntry(name, category, aliases = [], options = {}) {
|
|
5
5
|
const all = [...new Set([name, ...aliases].filter(Boolean))];
|
|
6
|
+
let re = null;
|
|
6
7
|
return Object.freeze({
|
|
7
8
|
canonical: name,
|
|
8
9
|
name,
|
|
@@ -10,7 +11,10 @@ function locationEntry(name, category, aliases = [], options = {}) {
|
|
|
10
11
|
country: 'UZ',
|
|
11
12
|
city: 'Tashkent',
|
|
12
13
|
aliases: Object.freeze(all),
|
|
13
|
-
re
|
|
14
|
+
get re() {
|
|
15
|
+
re ||= aliasesToRegex(all);
|
|
16
|
+
return re;
|
|
17
|
+
},
|
|
14
18
|
contextRequired: Boolean(options.contextRequired),
|
|
15
19
|
contextRe: options.context ? new RegExp(options.context, 'iu') : null,
|
|
16
20
|
});
|
|
@@ -18,6 +22,7 @@ function locationEntry(name, category, aliases = [], options = {}) {
|
|
|
18
22
|
|
|
19
23
|
const TASHKENT_HOUSING_DISTRICTS = Object.freeze(TASHKENT_DISTRICTS.map((district) => {
|
|
20
24
|
const aliases = [...new Set([district.canonical, ...aliasesOf(district)].filter(Boolean))];
|
|
25
|
+
let re = null;
|
|
21
26
|
return Object.freeze({
|
|
22
27
|
canonical: district.canonical,
|
|
23
28
|
name: district.canonical,
|
|
@@ -25,7 +30,10 @@ const TASHKENT_HOUSING_DISTRICTS = Object.freeze(TASHKENT_DISTRICTS.map((distric
|
|
|
25
30
|
country: 'UZ',
|
|
26
31
|
city: 'Tashkent',
|
|
27
32
|
aliases: Object.freeze(aliases),
|
|
28
|
-
re
|
|
33
|
+
get re() {
|
|
34
|
+
re ||= aliasesToRegex(aliases);
|
|
35
|
+
return re;
|
|
36
|
+
},
|
|
29
37
|
});
|
|
30
38
|
}));
|
|
31
39
|
|
|
@@ -167,7 +175,7 @@ export const TASHKENT_NUMBERED_AREA_ALIASES = Object.freeze({
|
|
|
167
175
|
Kuylyuk: Object.freeze(['куйлюк', 'куйлик', 'kuylyuk', 'kuyliq', 'qoyliq', 'qo yliq']),
|
|
168
176
|
Sergeli: Object.freeze(['сергели', 'sergeli', 'sergile', 'sergele']),
|
|
169
177
|
Yunusabad: Object.freeze(['юнусабад', 'yunusabad', 'yunusobod']),
|
|
170
|
-
Yangihayot: Object.freeze(['янгихаёт', 'янгихаят', 'yangihayot']),
|
|
178
|
+
Yangihayot: Object.freeze(['янгихаёт', 'янгихает', 'янгихаят', 'yangihayot']),
|
|
171
179
|
});
|
|
172
180
|
|
|
173
181
|
const REVERSE_NUMBERED_ALIASES = Object.freeze({
|
|
@@ -241,15 +249,24 @@ export function hasExplicitTashkentDistrict(value, canonical) {
|
|
|
241
249
|
export function matchTashkentHousingDistrict(value) {
|
|
242
250
|
const text = String(value ?? '');
|
|
243
251
|
if (!text) return null;
|
|
252
|
+
let explicit = null;
|
|
253
|
+
let implicit = null;
|
|
244
254
|
for (const district of TASHKENT_HOUSING_DISTRICTS) {
|
|
245
255
|
const match = text.match(district.re);
|
|
246
256
|
if (!match) continue;
|
|
247
|
-
|
|
257
|
+
// The earliest explicitly marked district wins. In "Янгихаетский район
|
|
258
|
+
// Сергели-1" the "район" also precedes Сергели, but it belongs to the
|
|
259
|
+
// adjective before it; Sergeli-1 is only the quarter name.
|
|
260
|
+
if (hasExplicitDistrictContext(text, match)) {
|
|
261
|
+
if (!explicit || match.index < explicit.index) explicit = { district, index: match.index };
|
|
262
|
+
continue;
|
|
263
|
+
}
|
|
264
|
+
if (implicit) continue;
|
|
248
265
|
if (hasExplicitMetroContext(text, match) || hasExplicitAreaContext(text, match) || hasExplicitMahallaContext(text, match)) continue;
|
|
249
266
|
if (matchTashkentNumberedArea(text, district.name)) continue;
|
|
250
|
-
|
|
267
|
+
implicit = district;
|
|
251
268
|
}
|
|
252
|
-
return
|
|
269
|
+
return explicit?.district || implicit;
|
|
253
270
|
}
|
|
254
271
|
|
|
255
272
|
/**
|
package/src/tashkent-pois.js
CHANGED
|
@@ -2,13 +2,17 @@ import { aliasesToRegex } from './normalization.js';
|
|
|
2
2
|
|
|
3
3
|
function poi(name, category, aliases = [], options = {}) {
|
|
4
4
|
const all = [...new Set([name, ...aliases].filter(Boolean))];
|
|
5
|
+
let re = null;
|
|
5
6
|
return Object.freeze({
|
|
6
7
|
canonical: name,
|
|
7
8
|
name,
|
|
8
9
|
category,
|
|
9
10
|
categories: Object.freeze([...new Set([category, ...(options.categories || [])])]),
|
|
10
11
|
aliases: Object.freeze(all),
|
|
11
|
-
re
|
|
12
|
+
get re() {
|
|
13
|
+
re ||= aliasesToRegex(all);
|
|
14
|
+
return re;
|
|
15
|
+
},
|
|
12
16
|
contextRequired: Boolean(options.contextRequired),
|
|
13
17
|
contextRe: options.context ? new RegExp(options.context, 'iu') : null,
|
|
14
18
|
...(options.parent ? { parent: options.parent } : {}),
|
|
@@ -12,12 +12,16 @@ const AMBIGUOUS_TASHKENT_COMPLEXES = new Set([
|
|
|
12
12
|
|
|
13
13
|
function complex(name, aliases = []) {
|
|
14
14
|
const all = [...new Set([name, ...aliases].filter(Boolean))];
|
|
15
|
+
let re = null;
|
|
15
16
|
return Object.freeze({
|
|
16
17
|
canonical: name,
|
|
17
18
|
name,
|
|
18
19
|
aliases: Object.freeze(all),
|
|
19
20
|
ambiguous: AMBIGUOUS_TASHKENT_COMPLEXES.has(name),
|
|
20
|
-
re
|
|
21
|
+
get re() {
|
|
22
|
+
re ||= aliasesToRegex(all);
|
|
23
|
+
return re;
|
|
24
|
+
},
|
|
21
25
|
});
|
|
22
26
|
}
|
|
23
27
|
|
|
@@ -2,6 +2,7 @@ import { aliasesToRegex } from './normalization.js';
|
|
|
2
2
|
|
|
3
3
|
function residential(name, aliases = []) {
|
|
4
4
|
const all = Object.freeze([...new Set([name, ...aliases].filter(Boolean))]);
|
|
5
|
+
let re = null;
|
|
5
6
|
return Object.freeze({
|
|
6
7
|
canonical: name,
|
|
7
8
|
name,
|
|
@@ -9,7 +10,10 @@ function residential(name, aliases = []) {
|
|
|
9
10
|
entityType: 'residential_complex',
|
|
10
11
|
country: 'UA',
|
|
11
12
|
aliases: all,
|
|
12
|
-
re
|
|
13
|
+
get re() {
|
|
14
|
+
re ||= aliasesToRegex(all);
|
|
15
|
+
return re;
|
|
16
|
+
},
|
|
13
17
|
});
|
|
14
18
|
}
|
|
15
19
|
|
|
@@ -2,6 +2,7 @@ import { aliasesToRegex } from './normalization.js';
|
|
|
2
2
|
|
|
3
3
|
function residential(name, aliases = []) {
|
|
4
4
|
const all = Object.freeze([...new Set([name, ...aliases].filter(Boolean))]);
|
|
5
|
+
let re = null;
|
|
5
6
|
return Object.freeze({
|
|
6
7
|
canonical: name,
|
|
7
8
|
name,
|
|
@@ -9,7 +10,10 @@ function residential(name, aliases = []) {
|
|
|
9
10
|
entityType: 'residential_complex',
|
|
10
11
|
country: 'UA',
|
|
11
12
|
aliases: all,
|
|
12
|
-
re
|
|
13
|
+
get re() {
|
|
14
|
+
re ||= aliasesToRegex(all);
|
|
15
|
+
return re;
|
|
16
|
+
},
|
|
13
17
|
});
|
|
14
18
|
}
|
|
15
19
|
|
|
@@ -2,7 +2,18 @@ import { aliasesToRegex } from './normalization.js';
|
|
|
2
2
|
|
|
3
3
|
function entry(name, aliases = [], meta = {}) {
|
|
4
4
|
const all = [...new Set([name, ...aliases].filter(Boolean))];
|
|
5
|
-
|
|
5
|
+
let re = null;
|
|
6
|
+
return Object.freeze({
|
|
7
|
+
...meta,
|
|
8
|
+
canonical: name,
|
|
9
|
+
name,
|
|
10
|
+
type: meta.type || meta.entityType,
|
|
11
|
+
aliases: Object.freeze(all),
|
|
12
|
+
get re() {
|
|
13
|
+
re ||= aliasesToRegex(all);
|
|
14
|
+
return re;
|
|
15
|
+
},
|
|
16
|
+
});
|
|
6
17
|
}
|
|
7
18
|
|
|
8
19
|
function entries(rows = [], defaults = {}) {
|
|
@@ -2,6 +2,7 @@ import { aliasesToRegex } from './normalization.js';
|
|
|
2
2
|
|
|
3
3
|
function entry(canonical, type, aliases = [], labels = null) {
|
|
4
4
|
const all = Object.freeze([...new Set([canonical, ...aliases].filter(Boolean))]);
|
|
5
|
+
let re = null;
|
|
5
6
|
return Object.freeze({
|
|
6
7
|
canonical,
|
|
7
8
|
name: canonical,
|
|
@@ -11,7 +12,10 @@ function entry(canonical, type, aliases = [], labels = null) {
|
|
|
11
12
|
city: 'Samarkand',
|
|
12
13
|
...(labels ? { labels: Object.freeze(labels) } : {}),
|
|
13
14
|
aliases: all,
|
|
14
|
-
re
|
|
15
|
+
get re() {
|
|
16
|
+
re ||= aliasesToRegex(all);
|
|
17
|
+
return re;
|
|
18
|
+
},
|
|
15
19
|
});
|
|
16
20
|
}
|
|
17
21
|
|
|
@@ -11,6 +11,7 @@ function legacyAreaEntry(district, canonical) {
|
|
|
11
11
|
...(source.aliases || []),
|
|
12
12
|
].filter(Boolean))]);
|
|
13
13
|
|
|
14
|
+
let re = null;
|
|
14
15
|
return Object.freeze({
|
|
15
16
|
canonical: source.name,
|
|
16
17
|
name: source.name,
|
|
@@ -21,12 +22,16 @@ function legacyAreaEntry(district, canonical) {
|
|
|
21
22
|
district,
|
|
22
23
|
parent: district,
|
|
23
24
|
aliases,
|
|
24
|
-
re
|
|
25
|
+
get re() {
|
|
26
|
+
re ||= aliasesToRegex(aliases);
|
|
27
|
+
return re;
|
|
28
|
+
},
|
|
25
29
|
});
|
|
26
30
|
}
|
|
27
31
|
|
|
28
32
|
function residentialEntry(canonical, aliases = []) {
|
|
29
33
|
const all = Object.freeze([...new Set([canonical, ...aliases].filter(Boolean))]);
|
|
34
|
+
let re = null;
|
|
30
35
|
return Object.freeze({
|
|
31
36
|
canonical,
|
|
32
37
|
name: canonical,
|
|
@@ -35,7 +40,10 @@ function residentialEntry(canonical, aliases = []) {
|
|
|
35
40
|
country: 'UZ',
|
|
36
41
|
city: 'Tashkent',
|
|
37
42
|
aliases: all,
|
|
38
|
-
re
|
|
43
|
+
get re() {
|
|
44
|
+
re ||= aliasesToRegex(all);
|
|
45
|
+
return re;
|
|
46
|
+
},
|
|
39
47
|
});
|
|
40
48
|
}
|
|
41
49
|
|