@whiteslove/parsing-lexicon 0.7.6 → 0.7.8
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
|
@@ -23,6 +23,7 @@ const ADDRESS_FIELD_STOP_RE = /\s+(?:цена|ціна|нарх(?:и)?|narx|pric
|
|
|
23
23
|
const PROPERTY_AREA_LINE_RE = /(?:^|[^\p{L}\p{N}_])(?:(?:общая|жилая|полезная|кухонная)\s+площадь|площадь\s+(?:квартиры|дома|комнаты))(?=$|[^\p{L}\p{N}_])/iu;
|
|
24
24
|
const NON_ADDRESS_BARE_RE = /^(?:(?:(?:перш(?:ий|ому)|перв(?:ый|ом)|друг(?:ий|ому)|втор(?:ой|ом)|трет(?:ій|ьем|ий)|\d{1,3}(?:-?й)?)\s+(?:поверх|этаж|floor|qavat|қабат))|(?:поверх|этаж|floor|qavat|қабат)(?:\s|$)|(?:район|р-н|рн|мікрорайон|микрорайон|мкр\.?|жк|ж\.к\.|жилой\s+комплекс|житловий\s+комплекс|residential\s+complex)(?:\s|$)|(?:недалеко|поруч|рядом|біля|около|возле)(?=$|[^\p{L}\p{N}_])|(?:зупинка|остановка|станція|станция)(?:\s|$))/iu;
|
|
25
25
|
const DELIMITED_STREET_REJECT_RE = /(?:^|\s)(?:город|місто|city|район|р-н|рн|мікрорайон|микрорайон|мкр|жк|метро|поверх|этаж|floor|qavat|кімнат\p{L}*|комнат\p{L}*|квартира|квартири|квартиры|оренда|аренда|продаж\p{L}*|цена|ціна|площад\p{L}*|площа|зупинка|остановка|ориентир\p{L}*|ор[-–—]?р\.?)(?:\s|$)/iu;
|
|
26
|
+
const LOCATION_RELATION_RE = /(?:yonida|yaqin(?:ida)?|ro['’ʻʼ`]?parasida|near(?:by)?|close\s+to|next\s+to|рядом|возле|около|недалеко|поруч|біля|lângă|aproape)/iu;
|
|
26
27
|
const UNIT_COMPONENT_PATTERNS = Object.freeze([
|
|
27
28
|
String.raw`(?:^|[\s,;])(?:кв\.?|кв-ра)(?!\p{L})\s*(?:№|#)?\s*(${SECONDARY_TOKEN})(?=$|[^\p{L}\p{N}])`,
|
|
28
29
|
String.raw`(?:^|[\s,;])квартира\s*(?:№|#)\s*(${SECONDARY_TOKEN})(?=$|[^\p{L}\p{N}])`,
|
|
@@ -188,9 +189,12 @@ function result(address, street = null, houseNumber = null, building = null, con
|
|
|
188
189
|
const normalizedHouseNumber = normalizeNumber(houseNumber);
|
|
189
190
|
const compactBuilding = normalizedHouseNumber?.match(/^(\d{1,5})(?:к|k)(\d{1,4})$/iu);
|
|
190
191
|
const normalizedBuilding = normalizeNumber(building) || compactBuilding?.[2] || null;
|
|
191
|
-
|
|
192
|
+
// The public address is always a verified street component, optionally
|
|
193
|
+
// followed by its house/building. Never preserve a source label's whole
|
|
194
|
+
// location sentence as an address just because it was parsed on that path.
|
|
195
|
+
const canonicalAddress = normalizedStreet
|
|
192
196
|
? composeHousingAddress({ street: normalizedStreet, houseNumber: compactBuilding ? compactBuilding[1] : normalizedHouseNumber, building: normalizedBuilding })
|
|
193
|
-
:
|
|
197
|
+
: null;
|
|
194
198
|
return Object.freeze({
|
|
195
199
|
address: canonicalAddress,
|
|
196
200
|
street: normalizedStreet,
|
|
@@ -536,7 +540,22 @@ function labelledAddress(text, rawValue) {
|
|
|
536
540
|
|
|
537
541
|
const line = clean(rawLine).slice(0, 140);
|
|
538
542
|
if (!line) return null;
|
|
539
|
-
|
|
543
|
+
const structured = parseHousingAddress(line, { allowDelimitedBare: true });
|
|
544
|
+
if (structured.street || structured.houseNumber) return structured;
|
|
545
|
+
|
|
546
|
+
// A label alone is not evidence that arbitrary prose is a street. Accept
|
|
547
|
+
// an unmarked form only when it is a short street-plus-house expression;
|
|
548
|
+
// landmark lists such as "Yakkasaroy, Magic City yonida" remain geo data.
|
|
549
|
+
const tail = splitAddressTail(line);
|
|
550
|
+
const candidateStreet = compactStreet(tail?.street);
|
|
551
|
+
const safeBare = Boolean(
|
|
552
|
+
tail?.houseNumber
|
|
553
|
+
&& candidateStreet
|
|
554
|
+
&& !LOCATION_RELATION_RE.test(candidateStreet)
|
|
555
|
+
&& candidateStreet.split(/\s+/u).length <= 5
|
|
556
|
+
&& candidateStreet.split(/\s+/u).every((word) => /^[\p{L}'’.-]{2,48}$/u.test(word)),
|
|
557
|
+
);
|
|
558
|
+
return safeBare ? parseHousingAddress(line, { allowBare: true }) : structured;
|
|
540
559
|
}
|
|
541
560
|
|
|
542
561
|
function plausibleDelimitedStreet(value) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type HousingAudience = 'women' | 'men' | 'family';
|
|
1
|
+
export type HousingAudience = 'women' | 'men' | 'family' | 'students';
|
|
2
2
|
export interface HousingPerPersonPrice { amount: number; currency: string | null; approximate: boolean; scope: 'person' }
|
|
3
3
|
export interface HousingCommissionAmount { amount: number; currency: string | null; approximate: boolean }
|
|
4
4
|
export interface HousingListingEnrichment {
|
|
@@ -30,12 +30,12 @@ const APPLIANCE_PATTERNS = Object.freeze([
|
|
|
30
30
|
|
|
31
31
|
const FIRST_RENT_UZ_RE = /(?:hali\s+hech\s+kim\s+(?:yashamagan|turmagan)|ҳали\s+ҳеч\s+ким\s+(?:яшамаган|турмаган))/iu;
|
|
32
32
|
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;
|
|
33
|
-
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
|
|
33
|
+
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;
|
|
34
34
|
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;
|
|
35
35
|
const BROKER_RE = /(?:makler|vositachi|макл(?:ер[а-яё]*)?|ри[еэ]лтор[а-яё]*|агентств[а-яё]*|комисси[а-яё]*|broker|realtor|commission|comision(?:ul)?|agen[țt]ie|delda[lл]\p{L}*|делдал\p{L}*)/iu;
|
|
36
36
|
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;
|
|
37
37
|
const WOMEN_RE = /(?:qiz(?:lar)?(?:ga)?|ayol(?:lar)?(?:ga|ni)?|киз(?:лар)?(?:га)?|аёл(?:лар)?(?:га|ни)?|девушк\p{L}*|женщин\p{L}*|girls?\s+only|women\s+only|fete(?:lor)?|femei(?:lor)?|қыздарға|қыздар(?:ға)?|дівчат(?:ам|а|ок)?|жінк(?:ам|и)?)/iu;
|
|
38
|
-
const FAMILY_RE = /(?:семь\p{L}*|family|oila(?:ga|lar|li)?|oila\s+uchun|оилага|оелага|оилавий|oelaga|famil(?:ie|ia)|cuplu(?:ri)?|отбасына|отбасылы|жанұяға|сім['’ʼ]?[яїі](?:ям|ям[иі])?|сімейн\p{L}*)/iu;
|
|
38
|
+
const FAMILY_RE = /(?:семь\p{L}*|family|oila(?:ga|lar|li)?|oyla(?:ga|lar|li)?|oila\s+uchun|оилага|оелага|оилавий|oelaga|famil(?:ie|ia)|cuplu(?:ri)?|отбасына|отбасылы|жанұяға|сім['’ʼ]?[яїі](?:ям|ям[иі])?|сімейн\p{L}*)/iu;
|
|
39
39
|
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;
|
|
40
40
|
const AIR_CONDITIONER_RE = /(?:кондицион|air\s*con|konditsioner|kandit(?:s|c)?aner|kanditsaner|кандитсанер)/iu;
|
|
41
41
|
const PER_PERSON_PRICE_RE = /(?:kishi\s+boshiga|киши\s+бошига)\s*(\d{1,3}(?:[\s.,]\d{3})*|\d+(?:[.,]\d+)?)\s*(ming|минг|million|mln|млн)?(?:dan|дан)?/iu;
|
|
@@ -138,7 +138,10 @@ export function parseHousingAudience(value) {
|
|
|
138
138
|
const family = FAMILY_RE.test(text);
|
|
139
139
|
const women = WOMEN_RE.test(text);
|
|
140
140
|
const men = MEN_RE.test(text);
|
|
141
|
+
const students = STUDENT_RE.test(text);
|
|
142
|
+
if (family && students) return deepFreeze({ primary: 'family', alternatives: ['family', 'students'] });
|
|
141
143
|
if (family && women) return deepFreeze({ primary: 'family', alternatives: ['family', 'women'] });
|
|
144
|
+
if (students) return deepFreeze({ primary: 'students', alternatives: ['students'] });
|
|
142
145
|
if (family) return deepFreeze({ primary: 'family', alternatives: ['family'] });
|
|
143
146
|
if (women) return deepFreeze({ primary: 'women', alternatives: ['women'] });
|
|
144
147
|
if (men) return deepFreeze({ primary: 'men', alternatives: ['men'] });
|
|
@@ -18,11 +18,16 @@ const TYPE_MARKERS = Object.freeze([
|
|
|
18
18
|
]);
|
|
19
19
|
|
|
20
20
|
const RELATION_RE = /(?<relation>рядом\s+(?:с|со)|возле|около|недалеко\s+от|напротив|за|перед|near(?:by)?|close\s+to|next\s+to|opposite|behind|in\s+front\s+of|yaqin(?:ida)?|yonida|ro['’ʻʼ`]?parasida|орналасқан\s+жерде|поруч|біля|поблизу|lângă|aproape\s+de)\s+(?<target>[^,;.!?\r\n]{2,96})/giu;
|
|
21
|
+
// Uzbek usually places the relation after the landmark: "Magic City yonida"
|
|
22
|
+
// rather than "yonida Magic City". Keep its target span separate so it never
|
|
23
|
+
// leaks into an address/street field.
|
|
24
|
+
const POSTFIX_RELATION_RE = /(?<target>[^,;.!?\r\n]{2,96}?)\s+(?<relation>yonida|yaqin(?:ida)?|ro['’ʻʼ`]?parasida)(?=$|[,;.!?\r\n])/giu;
|
|
21
25
|
const DISTANCE_RE = /(?<amount>\d{1,3}(?:[.,]\d+)?)\s*(?<unit>km|км|min(?:ute)?s?|мин(?:ут(?:ы|а|ах)?)?|дақиқа|daqiqa|метр(?:а|ов)?|m)\s*(?<mode>пешком|пішки|walking?|yayov|piyoda|на\s+машине|by\s+car)?\s*(?:до|от|from|to|до\s+станции)\s+(?<target>[^,;.!?\r\n]{2,96})/giu;
|
|
22
26
|
|
|
23
27
|
function cleanTarget(value) {
|
|
24
28
|
return String(value || '')
|
|
25
29
|
.replace(/\b(?:на\s+машине|пешком|пішки|walking?|piyoda|yayov)\b/giu, ' ')
|
|
30
|
+
.replace(/(?<!\p{L})(\p{L}{3,})(?:ga|qa|ka)(?!\p{L})/giu, '$1')
|
|
26
31
|
.replace(/\s+/g, ' ').trim();
|
|
27
32
|
}
|
|
28
33
|
|
|
@@ -91,7 +96,7 @@ export function extractHousingPoiRelations(value, { country = '', city = '', res
|
|
|
91
96
|
if (!text || !normalizedCountry || typeof resolveGeoCandidates !== 'function') return deepFreeze([]);
|
|
92
97
|
const context = { country: normalizedCountry, city, resolveGeoCandidates };
|
|
93
98
|
const relations = [];
|
|
94
|
-
for (const pattern of [RELATION_RE, DISTANCE_RE]) {
|
|
99
|
+
for (const pattern of [RELATION_RE, POSTFIX_RELATION_RE, DISTANCE_RE]) {
|
|
95
100
|
for (const match of text.matchAll(pattern)) {
|
|
96
101
|
const groups = match.groups || {};
|
|
97
102
|
const target = groups.target || '';
|
package/src/tashkent-pois.js
CHANGED
|
@@ -95,7 +95,7 @@ export const TASHKENT_MALLS = Object.freeze([
|
|
|
95
95
|
poi('Poytaxt Mall', 'mall', ['Poytaxt', 'Пойтахт Молл', 'Пойтахт', 'Poytaxt Shopping Center', 'Poytaxt savdo markazi'], { contextRequired: true, context: MALL_CONTEXT }),
|
|
96
96
|
poi('Alfraganus Mall', 'mall', ['Alfraganus', 'Альфраганус Молл', 'Alfraganus savdo markazi'], { contextRequired: true, context: MALL_CONTEXT }),
|
|
97
97
|
poi('High Town Mall', 'mall', ['HT Mall', 'HT Молл', 'HT savdo markazi']),
|
|
98
|
-
poi('Seoul Mun Mall', 'mall', ['Seoul Mun', 'Сеул Мун Молл', 'Seoul Mun savdo markazi']
|
|
98
|
+
poi('Seoul Mun Mall', 'mall', ['Seoul Mun', 'Seul Mun', 'Seoul Moon', 'Seul Moon', 'Сеул Мун', 'Сеул Мун Молл', 'Seoul Mun savdo markazi']),
|
|
99
99
|
poi('Ecobozor', 'mall', ['Eco Bozor', 'Ekobozor', 'Эко Бозор', 'Экобозор']),
|
|
100
100
|
poi('Chimgan Shopping Center', 'mall', ['Chimgan', 'Chimgan Mall', 'ТРЦ Чимган', 'ТЦ Чимган', 'Chimgan savdo markazi'], { contextRequired: true, context: MALL_CONTEXT }),
|
|
101
101
|
poi('Golden Life', 'mall', ['Golden Life Mall', 'Голден Лайф', 'ТРЦ Golden Life', 'ТЦ Golden Life']),
|
|
@@ -251,4 +251,4 @@ export function matchTashkentPoi(value, category = null) {
|
|
|
251
251
|
}
|
|
252
252
|
matches.sort((a, b) => b.length - a.length || a.start - b.start);
|
|
253
253
|
return matches[0]?.entry ?? null;
|
|
254
|
-
}
|
|
254
|
+
}
|
|
@@ -188,7 +188,6 @@ export const TASHKENT_RESIDENTIAL_COMPLEXES = Object.freeze([
|
|
|
188
188
|
complex('Sayram Avenue'),
|
|
189
189
|
complex('Sayram Plaza'),
|
|
190
190
|
complex('Sayram Tower'),
|
|
191
|
-
complex('Seoul Mun', ['Сеул Мун']),
|
|
192
191
|
complex('Seoul Riverside'),
|
|
193
192
|
complex('Shahriabad'),
|
|
194
193
|
complex('Shohsaroy Towers'),
|