@whiteslove/parsing-lexicon 0.4.3 → 0.5.0

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
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@whiteslove/parsing-lexicon",
3
- "version": "0.4.3",
4
- "description": "Shared deterministic multilingual parsing lexicon for Whiteslove housing and hiring services",
3
+ "version": "0.5.0",
4
+ "description": "Shared deterministic multilingual parsing lexicon for WhitesLove housing and hiring services",
5
5
  "repository": {
6
6
  "type": "git",
7
7
  "url": "git+https://github.com/AmoneMisa/parsing-lexicon.git"
@@ -13,6 +13,14 @@ export interface HousingListingEnrichment {
13
13
  privateYard?: boolean | null;
14
14
  dishwasher?: boolean | null;
15
15
  airConditioner?: boolean | null;
16
+ tv?: boolean | null;
17
+ microwave?: boolean | null;
18
+ oven?: boolean | null;
19
+ bidet?: boolean | null;
20
+ walkInCloset?: boolean | null;
21
+ bathtub?: boolean | null;
22
+ shower?: boolean | null;
23
+ euroLayout?: boolean | null;
16
24
  gas?: boolean | null;
17
25
  newBuilding?: boolean | null;
18
26
  communalSeparated?: boolean | null;
@@ -218,6 +218,14 @@ export function parseHousingListingEnrichment(value, { country = '' } = {}) {
218
218
  privateYard: listingFields.privateYard ?? null,
219
219
  dishwasher: listingFields.dishwasher ?? null,
220
220
  airConditioner: listingFields.airConditioner ?? (AIR_CONDITIONER_RE.test(text) ? true : null),
221
+ tv: listingFields.tv ?? null,
222
+ microwave: listingFields.microwave ?? null,
223
+ oven: listingFields.oven ?? null,
224
+ bidet: listingFields.bidet ?? null,
225
+ walkInCloset: listingFields.walkInCloset ?? null,
226
+ bathtub: listingFields.bathtub ?? null,
227
+ shower: listingFields.shower ?? null,
228
+ euroLayout: listingFields.euroLayout ?? null,
221
229
  gas: listingFields.gas ?? null,
222
230
  newBuilding: listingFields.newBuilding ?? null,
223
231
  communalSeparated: listingFields.communalSeparated ?? null,
@@ -20,6 +20,14 @@ export type HousingListingFields = Readonly<{
20
20
  gazebo: boolean | null;
21
21
  dishwasher: boolean | null;
22
22
  airConditioner: boolean | null;
23
+ tv: boolean | null;
24
+ microwave: boolean | null;
25
+ oven: boolean | null;
26
+ bidet: boolean | null;
27
+ walkInCloset: boolean | null;
28
+ bathtub: boolean | null;
29
+ shower: boolean | null;
30
+ euroLayout: boolean | null;
23
31
  gas: boolean | null;
24
32
  newBuilding: boolean | null;
25
33
  communalSeparated: boolean | null;
@@ -137,6 +137,59 @@ export function parseHousingListingFields(value, { country = '' } = {}) {
137
137
  /кондицион|сплит[- ]?систем|konditsioner|kansaner|kandisaner|klimat|air\s*con|aer\s+condi[țt]ionat/iu,
138
138
  /без\s+(?:кондицион\p{L}*|сплит[- ]?систем\p{L}*)|нет\s+(?:кондицион\p{L}*|сплит[- ]?систем\p{L}*)|(?:кондицион\p{L}*|сплит[- ]?систем\p{L}*)\s+нет|no\s+air\s*con(?:ditioner)?|konditsioner\s+yo['’]?q|кондиционер\s+йўқ/iu,
139
139
  ),
140
+ // "тв"/"tv" are bare 2-letter abbreviations that collide with real words
141
+ // (e.g. "твой" = "your") unless bounded on both sides by a non-letter --
142
+ // the same class of bug as the "пр" abbreviation fixed earlier in
143
+ // compactStreet(); do not relax these boundaries to a bare \b.
144
+ tv: bool(
145
+ text,
146
+ /телевизор|(?:^|[^\p{L}\p{N}_])(?:тв|tv)(?=$|[^\p{L}\p{N}_])|television|televizor/iu,
147
+ /без\s+телевизор\p{L}*|нет\s+телевизор\p{L}*|телевизор\p{L}*\s+нет|no\s+tv|no\s+television/iu,
148
+ ),
149
+ microwave: bool(
150
+ text,
151
+ /микроволнов\p{L}*|(?:^|[^\p{L}\p{N}_])свч(?=$|[^\p{L}\p{N}_])|microwave|mikro(?:to['’]?lqinli|talqinli)\s*pech/iu,
152
+ /без\s+микроволнов\p{L}*|нет\s+микроволнов\p{L}*|микроволнов\p{L}*\s+нет|no\s+microwave/iu,
153
+ ),
154
+ oven: bool(
155
+ text,
156
+ /духовк\p{L}*|духов\p{L}*\s+шкаф\p{L}*|oven|(?:^|[^\p{L}\p{N}_])pech(?=$|[^\p{L}\p{N}_])/iu,
157
+ /без\s+духовк\p{L}*|нет\s+духовк\p{L}*|духовк\p{L}*\s+нет|no\s+oven/iu,
158
+ ),
159
+ bidet: bool(
160
+ text,
161
+ /биде|bidet/iu,
162
+ /без\s+биде|нет\s+биде|биде\s+нет|no\s+bidet/iu,
163
+ ),
164
+ walkInCloset: bool(
165
+ text,
166
+ /гардеробн\p{L}*|walk[- ]?in\s+closet|dressing\s+room|garderob(?:naya)?/iu,
167
+ /без\s+гардеробн\p{L}*|нет\s+гардеробн\p{L}*|гардеробн\p{L}*\s+нет|no\s+walk[- ]?in\s+closet/iu,
168
+ ),
169
+ // "ванна" (the tub fixture) and "ванная" (the bathroom-as-a-room) share a
170
+ // stem, so this only matches the shorter nominative/accusative tub forms
171
+ // with a hard boundary -- it will miss instrumental/genitive tub mentions
172
+ // ("с ванной") since those are spelled identically to the room noun and
173
+ // aren't safely disambiguable by regex alone.
174
+ bathtub: bool(
175
+ text,
176
+ /(?:^|[^\p{L}\p{N}_])(?:ванна|ванну|vanna(?:si)?)(?=$|[^\p{L}\p{N}_])|bathtub/iu,
177
+ /без\s+ванн\p{L}*|нет\s+ванн\p{L}*|ванн\p{L}*\s+нет|no\s+bathtub/iu,
178
+ ),
179
+ shower: bool(
180
+ text,
181
+ /(?:^|[^\p{L}\p{N}_])душ(?=$|[^\p{L}\p{N}_])|душев\p{L}*\s+кабин\p{L}*|shower|dush(?:kabina)?/iu,
182
+ /без\s+душ\p{L}*|нет\s+душ\p{L}*|душ\p{L}*\s+нет|no\s+shower/iu,
183
+ ),
184
+ // "Euro-layout" (евродвушка/евротрёшка/евро-N/европланировка) is a CIS
185
+ // real-estate term for an open-plan flat with the kitchen merged into the
186
+ // living room, as opposed to a conventional flat of the same room count
187
+ // with a separate closed kitchen. Positive-only: there's no common way
188
+ // listings state the absence of this, only its presence.
189
+ euroLayout: bool(
190
+ text,
191
+ /евро[- ]?(?:студи\p{L}*|двушк\p{L}*|трёшк\p{L}*|трешк\p{L}*|четырёшк\p{L}*|четрешк\p{L}*|планировк\p{L}*|[1-9](?!\p{N}))|европланировк\p{L}*|euro[- ]?layout/iu,
192
+ ),
140
193
  gas,
141
194
  newBuilding: bool(text, /новостро|новобуд|новый\s+дом|novast(?:royka|iroyka)|navast(?:royka|iroyka)|new\s*build|newly\s*built|yangi\s+(?:bino|qurilgan|uy)|bloc\s+nou/iu),
142
195
  communalSeparated: parseCommunalSeparated(text, country),
package/src/locations.js CHANGED
@@ -172,11 +172,21 @@ export function matchDictionaryLocation(text, countryCode, city = null) {
172
172
  const country = locationCities(countryCode);
173
173
  const cities = city && country[city] ? [[city, country[city]]] : Object.entries(country);
174
174
  const value = String(text || '');
175
+ let best = null;
175
176
  for (const [cityName, data] of cities) {
176
177
  for (const type of LOCATION_LIST_KEYS) {
177
- const match = (data[type] || []).find((entry) => entry?.re?.test(value));
178
- if (match) return { city: cityName, type, name: match.name, aliases: match.aliases };
178
+ for (const entry of data[type] || []) {
179
+ const match = entry?.re?.exec(value);
180
+ if (!match) continue;
181
+ const start = match.index;
182
+ const end = start + match[0].length;
183
+ const containsBest = best && start <= best.start && end >= best.end && end - start > best.end - best.start;
184
+ if (best && !containsBest) continue;
185
+ best = { city: cityName, type, name: entry.name, aliases: entry.aliases, start, end };
186
+ }
179
187
  }
180
188
  }
181
- return null;
189
+ if (!best) return null;
190
+ const { start: _start, end: _end, ...result } = best;
191
+ return result;
182
192
  }
@@ -210,6 +210,8 @@ export const UZ_LOCATION_EXTENSIONS = Object.freeze({
210
210
  ['Tashkent City', 'Ташкент Сити', 'Toshkent City', 'Tashkent City IBC'],
211
211
  ],
212
212
  streets: [
213
+ { name: 'Gulobod Street', aliases: ['Gulobod ko‘chasi', "Gulobod ko'chasi", 'улица Гулобод', 'ул. Гулобод'], parent: 'Shaykhantahur' },
214
+ { name: 'Sebzor Street', aliases: ['Sebzor ko‘chasi', "Sebzor ko'chasi", 'улица Себзор', 'ул. Себзор'], parent: 'Almazar' },
213
215
  { name: 'Lolazor Street', aliases: ['Lolazor ko‘chasi', "Lolazor ko'chasi", 'улица Лолазор', 'ул. Лолазор'], parent: 'Uchtepa' },
214
216
  { name: 'Shohimardon Street', aliases: ['Shohimardon ko‘chasi', "Shohimardon ko'chasi", 'улица Шохимардон', 'ул. Шохимардон'], parent: 'Yashnobod' },
215
217
  { name: 'Shohimardon Passage 1', aliases: ['Shohimardon 1-tor ko‘chasi', "Shohimardon 1-tor ko'chasi", '1-й проезд Шохимардон', '1 проезд Шохимардон'], parent: 'Yashnobod' },