@whiteslove/parsing-lexicon 0.10.5 → 0.10.6
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
CHANGED
package/src/housing-address.js
CHANGED
|
@@ -28,6 +28,8 @@ const POSTFIX_STREET_MARKER = `(?:${combinedMarkerPattern(STREET_POSTFIX_MARKERS
|
|
|
28
28
|
const POSTFIX_STREET_TYPE = `(?:${combinedMarkerPattern(STREET_TYPE_MARKERS)})`;
|
|
29
29
|
const HOUSE_MARKER = `(?:${combinedMarkerPattern(HOUSE_MARKERS)})`;
|
|
30
30
|
const BUILDING_MARKER = `(?:${combinedMarkerPattern(BUILDING_MARKERS)})`;
|
|
31
|
+
// Uzbek/Kazakh put the house word after the number: "12-uy", "45 uy".
|
|
32
|
+
const HOUSE_NUMBER_SUFFIX = String.raw`(?:\s*-?\s*(?:uy|уй|үй)(?![\p{L}\p{N}]))?`;
|
|
31
33
|
const NUMBER_TOKEN = String.raw`\d{1,5}(?:[-\/]?[\p{L}]\d{0,4})?(?:[\/-]\d{1,4}(?:[-\/]?[\p{L}]\d{0,4})?){0,2}`;
|
|
32
34
|
const STREET_WORD = String.raw`[\p{L}'’.-]{2,48}`;
|
|
33
35
|
// Common post-Soviet street names lead with a bare numeral ("8 Марта",
|
|
@@ -338,12 +340,31 @@ function attachCatalogReferences(parsed, options) {
|
|
|
338
340
|
return Object.keys(geoEntities).length ? Object.freeze({ ...parsed, geoEntities }) : parsed;
|
|
339
341
|
}
|
|
340
342
|
|
|
343
|
+
// "Sergeli 5 104": numbered massif followed by the house number.
|
|
344
|
+
function tashkentAreaThenHouseAddress(text) {
|
|
345
|
+
const re = /(?:^|[^\p{L}\p{N}_])([\p{L}'’ʻʼ-]+)\s+(\d{1,2})\s*[,;]?\s+(?:(?:dom|дом|uy|уй)\s*)?(\d{1,4})([\p{L}])?(?=$|[^\p{L}\p{N}_/])/giu;
|
|
346
|
+
for (const match of text.matchAll(re)) {
|
|
347
|
+
const quarterNumber = Number(match[2]);
|
|
348
|
+
const district = Object.keys(TASHKENT_NUMBERED_AREA_ALIASES).find((canonical) => (
|
|
349
|
+
matchTashkentNumberedArea(`${match[1]} ${quarterNumber}`, canonical)?.number === quarterNumber
|
|
350
|
+
));
|
|
351
|
+
if (!district) continue;
|
|
352
|
+
return Object.freeze({
|
|
353
|
+
...result(null, null, `${Number(match[3])}${match[4] ? match[4].toUpperCase() : ''}`, null,
|
|
354
|
+
scoreAddressConfidence(text, { source: 'structured', hasHouse: true })),
|
|
355
|
+
district,
|
|
356
|
+
quarter: Object.freeze({ number: quarterNumber, suffix: matchTashkentNumberedArea(`${match[1]} ${quarterNumber}`, district)?.suffix || '' }),
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
return null;
|
|
360
|
+
}
|
|
361
|
+
|
|
341
362
|
function tashkentMassifHouseAddress(value) {
|
|
342
363
|
const text = String(value ?? '');
|
|
343
364
|
const match = text.match(
|
|
344
365
|
/(?:^|[^\p{L}\p{N}_])(\d{1,2})\s+(?:mavze(?:si)?|мавзе(?:си)?)\s+(\d{1,5})\s*([\p{L}])?\s*(?:dom|дом|uy|уй)(?=$|[^\p{L}\p{N}_])/iu,
|
|
345
366
|
);
|
|
346
|
-
if (!match) return
|
|
367
|
+
if (!match) return tashkentAreaThenHouseAddress(text);
|
|
347
368
|
|
|
348
369
|
const quarterNumber = Number(match[1]);
|
|
349
370
|
const district = Object.keys(TASHKENT_NUMBERED_AREA_ALIASES).find((canonical) => {
|
|
@@ -371,7 +392,7 @@ function tashkentMassifHouseAddress(value) {
|
|
|
371
392
|
}
|
|
372
393
|
|
|
373
394
|
function splitAddressTail(raw) {
|
|
374
|
-
const text = clean(raw);
|
|
395
|
+
const text = clean(raw).replace(new RegExp(`(${NUMBER_TOKEN})${HOUSE_NUMBER_SUFFIX}(?=\\s*[,;]|\\s*$)`, 'iu'), '$1');
|
|
375
396
|
if (!text) return null;
|
|
376
397
|
|
|
377
398
|
const buildingRe = new RegExp(`(?:\\s*[,;]?\\s*${BUILDING_MARKER}\\s*(${NUMBER_TOKEN}))\\s*$`, 'iu');
|
|
@@ -400,7 +421,7 @@ function splitAddressTail(raw) {
|
|
|
400
421
|
function postfixTypedStreetAddress(line) {
|
|
401
422
|
const suffix = line.match(new RegExp(
|
|
402
423
|
`(?:^|[^\\p{L}\\p{N}])((?:${LEADING_STREET_NUMERAL}\\s+)?(?:${STREET_WORD}\\s+){0,4}${STREET_WORD}\\s+${POSTFIX_STREET_TYPE})` +
|
|
403
|
-
`\\s*[,;]?\\s*(${NUMBER_TOKEN})` +
|
|
424
|
+
`\\s*[,;]?\\s*(${NUMBER_TOKEN})${HOUSE_NUMBER_SUFFIX}` +
|
|
404
425
|
`(?:\\s*[,;]?\\s*${BUILDING_MARKER}\\s*(${NUMBER_TOKEN}))?` +
|
|
405
426
|
`(?=$|[^\\p{L}\\p{N}])`,
|
|
406
427
|
'iu',
|
|
@@ -424,7 +445,7 @@ function prefixTypedStreetAddress(line) {
|
|
|
424
445
|
const prefix = line.match(new RegExp(
|
|
425
446
|
`(?:^|[\\s,;])${PREFIX_STREET_MARKER}\\s+` +
|
|
426
447
|
`((?:${LEADING_STREET_NUMERAL}\\s+)?(?:${STREET_WORD}\\s+){0,4}${STREET_WORD})` +
|
|
427
|
-
`\\s*[,;]?\\s*(?:${HOUSE_MARKER}\\s*)?(${NUMBER_TOKEN})` +
|
|
448
|
+
`\\s*[,;]?\\s*(?:${HOUSE_MARKER}\\s*)?(${NUMBER_TOKEN})${HOUSE_NUMBER_SUFFIX}` +
|
|
428
449
|
`(?:\\s*[,;]?\\s*${BUILDING_MARKER}\\s*(${NUMBER_TOKEN}))?` +
|
|
429
450
|
`(?=$|[^\\p{L}\\p{N}])`,
|
|
430
451
|
'iu',
|
|
@@ -529,7 +550,9 @@ function collectExplicitStreetCandidates(text) {
|
|
|
529
550
|
|
|
530
551
|
const postfix = line.match(new RegExp(`^(.+?)\\s+(${POSTFIX_STREET_MARKER})(.*)$`, 'iu'));
|
|
531
552
|
if (postfix) {
|
|
532
|
-
|
|
553
|
+
// Whatever follows the first comma ("..., Sergeli") is a district or
|
|
554
|
+
// landmark, not part of the street/house.
|
|
555
|
+
const tailText = clean(`${postfix[1]} ${postfix[3].split(/[,;]/u, 1)[0]}`);
|
|
533
556
|
const tail = splitAddressTail(tailText);
|
|
534
557
|
if (tail) {
|
|
535
558
|
add(result(
|
|
@@ -209,7 +209,9 @@ export function parseHousingAudience(value) {
|
|
|
209
209
|
if (family && students) return deepFreeze({ primary: 'family', alternatives: ['family', 'students'] });
|
|
210
210
|
if (family && women) return deepFreeze({ primary: 'family', alternatives: ['family', 'women'] });
|
|
211
211
|
// "Student qizlarga" narrows students to girls: the gender is the binding
|
|
212
|
-
// restriction,
|
|
212
|
+
// primary restriction, but the student audience stays in the alternatives.
|
|
213
|
+
if (students && women && !men) return deepFreeze({ primary: 'women', alternatives: ['women', 'students'] });
|
|
214
|
+
if (students && men && !women) return deepFreeze({ primary: 'men', alternatives: ['men', 'students'] });
|
|
213
215
|
if (students && !women && !men) return deepFreeze({ primary: 'students', alternatives: ['students'] });
|
|
214
216
|
if (family) return deepFreeze({ primary: 'family', alternatives: ['family'] });
|
|
215
217
|
if (women) return deepFreeze({ primary: 'women', alternatives: ['women'] });
|
package/src/housing-money.js
CHANGED
|
@@ -634,8 +634,9 @@ export function parseHousingPrice(value, context = '') {
|
|
|
634
634
|
if (!explicit && !hasExplicitUzbekThousandScale && fallbackCurrency === 'UZS' && price != null) {
|
|
635
635
|
const dailyUzbek = /(?:kunlik|sutkaga|kecha[- ]?kunduz|посуточн|суточн)/i.test(priceText);
|
|
636
636
|
// A monthly rent of 100,000+ is only plausible in so'm; the USD reading
|
|
637
|
-
// turned "800 000" per person into an 800,000 USD rent.
|
|
638
|
-
|
|
637
|
+
// turned "800 000" per person into an 800,000 USD rent. When the deal type
|
|
638
|
+
// is unknown, 100,000+ is still so'm: only a known sale may be dollars.
|
|
639
|
+
const monthlyRent = dealType !== 'sale' && price >= 100_000;
|
|
639
640
|
currency = price >= 1_000_000 || monthlyRent || (dailyUzbek && price >= 10_000) ? 'UZS' : 'USD';
|
|
640
641
|
} else if (!explicit && currency === fallbackCurrency) {
|
|
641
642
|
currency = lowLocalAmountCurrency(price, currency, dealType, priceText);
|
|
@@ -342,7 +342,8 @@ function landmarkIdentity(text, match) {
|
|
|
342
342
|
if (numbered) {
|
|
343
343
|
const localStart = before.length - numbered[0].length;
|
|
344
344
|
const previous = localStart > 0 ? before[localStart - 1] : '';
|
|
345
|
-
|
|
345
|
+
// "24/7 korzinka" is an opening-hours phrase, not branch number 7.
|
|
346
|
+
if (!previous || !/[\p{L}\p{N}_/]/u.test(previous)) {
|
|
346
347
|
number = Number(numbered[1]);
|
|
347
348
|
rawStart = match.start - numbered[0].length;
|
|
348
349
|
}
|