@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/housing-address.js
CHANGED
|
@@ -1,210 +1,217 @@
|
|
|
1
|
-
const PHONE_RUN_RE = /\+?\d[\d\s().-]{7,}\d/gu;
|
|
2
|
-
const ADDRESS_LABEL_RE = /(?:адрес|адреса|адресація|адресация|manzil|address|adresă|adresa)\s*[:=\-–—]\s*/iu;
|
|
3
|
-
const PREFIX_STREET_MARKER = String.raw`(?:(?:ул(?:ица)?|вул(?:иця)?|просп(?:ект)?|пр-т|переул(?:ок)?|пров(?:улок)?|проезд|наб(?:ережная)?|шоссе|str(?:ada)?|street|st|avenue|ave|road|rd|көше)\.?)`;
|
|
4
|
-
const POSTFIX_STREET_MARKER = String.raw`(?:ko['’ʼ\u02bc]?cha(?:si)?|кўча(?:си)?|коча(?:си)?|kocha(?:si)?)`;
|
|
5
|
-
const POSTFIX_STREET_TYPE = String.raw`(?:вулиця|улица|провулок|переулок|проспект|бульвар|набережна|набережная|шосе|шоссе|площа|площадь|узвіз|спуск|алея|аллея|дорога|тупик)`;
|
|
6
|
-
const HOUSE_MARKER = String.raw`(?:дом|д\.|будинок|буд\.|house|h\.|uy|уй|nr\.?|no\.?|№)`;
|
|
7
|
-
const BUILDING_MARKER = String.raw`(?:корп(?:ус)?\.?|к\.|строен(?:ие)?|стр\.|будова|секц(?:ия|ія)?|bloc|corp|building|bldg\.?|korpus)`;
|
|
8
|
-
const NUMBER_TOKEN = String.raw`\d{1,5}(?:[-\/]?[\p{L}])?(?:[\/-]\d{1,4}(?:[-\/]?[\p{L}])?)?`;
|
|
9
|
-
const STREET_WORD = String.raw`[\p{L}'’.-]{2,48}`;
|
|
10
|
-
|
|
11
|
-
function clean(value) {
|
|
12
|
-
return String(value ?? '')
|
|
13
|
-
.normalize('NFKC')
|
|
14
|
-
.replace(PHONE_RUN_RE, (segment) => segment.replace(/\D/gu, '').length >= 9 ? ' ' : segment)
|
|
15
|
-
.replace(/[\t\f\v]+/gu, ' ')
|
|
16
|
-
.replace(/\s{2,}/gu, ' ')
|
|
17
|
-
.replace(/^[\s,;:.\-–—]+|[\s,;:.\-–—]+$/gu, '')
|
|
18
|
-
.trim();
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
function escapeRegExp(value) {
|
|
22
|
-
return String(value).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
function compactStreet(value) {
|
|
26
|
-
return clean(value)
|
|
27
|
-
.replace(new RegExp(`^${PREFIX_STREET_MARKER}\\s*`, 'iu'), '')
|
|
28
|
-
.replace(new RegExp(`\\s+${POSTFIX_STREET_MARKER}$`, 'iu'), '')
|
|
29
|
-
.replace(/[\s,;:.\-–—]+$/gu, '')
|
|
30
|
-
.trim() || null;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function normalizeNumber(value) {
|
|
34
|
-
const result = clean(value).replace(/\s+/gu, '');
|
|
35
|
-
return result || null;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function result(address, street = null, houseNumber = null, building = null, confidence = 0) {
|
|
39
|
-
return Object.freeze({
|
|
40
|
-
address: clean(address) || null,
|
|
41
|
-
street: compactStreet(street),
|
|
42
|
-
houseNumber: normalizeNumber(houseNumber),
|
|
43
|
-
building: normalizeNumber(building),
|
|
44
|
-
confidence,
|
|
45
|
-
});
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function splitAddressTail(raw) {
|
|
49
|
-
const text = clean(raw);
|
|
50
|
-
if (!text) return null;
|
|
51
|
-
|
|
52
|
-
const buildingRe = new RegExp(`(?:\\s*[,;]?\\s*${BUILDING_MARKER}\\s*(${NUMBER_TOKEN}))\\s*$`, 'iu');
|
|
53
|
-
const buildingMatch = text.match(buildingRe);
|
|
54
|
-
const building = buildingMatch?.[1] || null;
|
|
55
|
-
const withoutBuilding = buildingMatch ? clean(text.slice(0, buildingMatch.index)) : text;
|
|
56
|
-
|
|
57
|
-
const markedHouse = withoutBuilding.match(new RegExp(`^(.*?)\\s*[,;]?\\s*${HOUSE_MARKER}\\s*(${NUMBER_TOKEN})\\s*$`, 'iu'));
|
|
58
|
-
if (markedHouse && /\p{L}{2,}/u.test(markedHouse[1])) {
|
|
59
|
-
return { street: markedHouse[1], houseNumber: markedHouse[2], building };
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
const trailingHouse = withoutBuilding.match(new RegExp(`^(.*?\\p{L}.*?)\\s+(${NUMBER_TOKEN})\\s*$`, 'iu'));
|
|
63
|
-
if (trailingHouse && /\p{L}{2,}/u.test(trailingHouse[1])) {
|
|
64
|
-
return { street: trailingHouse[1], houseNumber: trailingHouse[2], building };
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
return /\p{L}{2,}/u.test(withoutBuilding)
|
|
68
|
-
? { street: withoutBuilding, houseNumber: null, building }
|
|
69
|
-
: null;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
function postfixTypedStreetAddress(line) {
|
|
73
|
-
const suffix = line.match(new RegExp(
|
|
74
|
-
`(?:^|[^\\p{L}\\p{N}])((?:${STREET_WORD}\\s+){0,4}${STREET_WORD}\\s+${POSTFIX_STREET_TYPE})` +
|
|
75
|
-
`\\s*[,;]?\\s*(${NUMBER_TOKEN})` +
|
|
76
|
-
`(?:\\s*[,;]?\\s*${BUILDING_MARKER}\\s*(${NUMBER_TOKEN}))?` +
|
|
77
|
-
`(?=$|[^\\p{L}\\p{N}])`,
|
|
78
|
-
'iu',
|
|
79
|
-
));
|
|
80
|
-
if (!suffix) return null;
|
|
81
|
-
|
|
82
|
-
const street = suffix[1];
|
|
83
|
-
const houseNumber = suffix[2];
|
|
84
|
-
const building = suffix[3] || null;
|
|
85
|
-
const address = composeHousingAddress({ street, houseNumber, building });
|
|
86
|
-
return result(address, street, houseNumber, building, 1);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
function prefixTypedStreetAddress(line) {
|
|
90
|
-
const prefix = line.match(new RegExp(
|
|
91
|
-
`(?:^|[\\s,;])${PREFIX_STREET_MARKER}\\s+` +
|
|
92
|
-
`((?:${STREET_WORD}\\s+){0,4}${STREET_WORD})` +
|
|
93
|
-
`\\s*[,;]?\\s*(?:${HOUSE_MARKER}\\s*)?(${NUMBER_TOKEN})` +
|
|
94
|
-
`(?:\\s*[,;]?\\s*${BUILDING_MARKER}\\s*(${NUMBER_TOKEN}))?` +
|
|
95
|
-
`(?=$|[^\\p{L}\\p{N}])`,
|
|
96
|
-
'iu',
|
|
97
|
-
));
|
|
98
|
-
if (!prefix) return null;
|
|
99
|
-
|
|
100
|
-
const street = prefix[1];
|
|
101
|
-
const houseNumber = prefix[2];
|
|
102
|
-
const building = prefix[3] || null;
|
|
103
|
-
const address = composeHousingAddress({ street, houseNumber, building });
|
|
104
|
-
return result(address, street, houseNumber, building, 1);
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
function explicitStreetAddress(text) {
|
|
108
|
-
const lines = text
|
|
109
|
-
.split(/[\r\n|]/u)
|
|
110
|
-
.map((part) => clean(part).slice(0, 160))
|
|
111
|
-
.filter(Boolean)
|
|
112
|
-
.slice(0, 12);
|
|
113
|
-
|
|
114
|
-
for (const line of lines) {
|
|
115
|
-
const postfixTyped = postfixTypedStreetAddress(line);
|
|
116
|
-
if (postfixTyped) return postfixTyped;
|
|
117
|
-
|
|
118
|
-
const prefixTyped = prefixTypedStreetAddress(line);
|
|
119
|
-
if (prefixTyped) return prefixTyped;
|
|
120
|
-
|
|
121
|
-
const
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
const
|
|
128
|
-
if (
|
|
129
|
-
const
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
const
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
const
|
|
168
|
-
if (!
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
*
|
|
182
|
-
*
|
|
183
|
-
*
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
1
|
+
const PHONE_RUN_RE = /\+?\d[\d\s().-]{7,}\d/gu;
|
|
2
|
+
const ADDRESS_LABEL_RE = /(?:адрес|адреса|адресація|адресация|manzil|address|adresă|adresa)\s*[:=\-–—]\s*/iu;
|
|
3
|
+
const PREFIX_STREET_MARKER = String.raw`(?:(?:ул(?:ица)?|вул(?:иця)?|просп(?:ект)?|пр-т|переул(?:ок)?|пров(?:улок)?|проезд|наб(?:ережная)?|шоссе|str(?:ada)?|street|st|avenue|ave|road|rd|көше)\.?)`;
|
|
4
|
+
const POSTFIX_STREET_MARKER = String.raw`(?:ko['’ʼ\u02bc]?cha(?:si)?|кўча(?:си)?|коча(?:си)?|kocha(?:si)?)`;
|
|
5
|
+
const POSTFIX_STREET_TYPE = String.raw`(?:вулиця|улица|провулок|переулок|проспект|бульвар|набережна|набережная|шосе|шоссе|площа|площадь|узвіз|спуск|алея|аллея|дорога|тупик)`;
|
|
6
|
+
const HOUSE_MARKER = String.raw`(?:дом|д\.|будинок|буд\.|house|h\.|uy|уй|nr\.?|no\.?|№)`;
|
|
7
|
+
const BUILDING_MARKER = String.raw`(?:корп(?:ус)?\.?|к\.|строен(?:ие)?|стр\.|будова|секц(?:ия|ія)?|bloc|corp|building|bldg\.?|korpus)`;
|
|
8
|
+
const NUMBER_TOKEN = String.raw`\d{1,5}(?:[-\/]?[\p{L}])?(?:[\/-]\d{1,4}(?:[-\/]?[\p{L}])?)?`;
|
|
9
|
+
const STREET_WORD = String.raw`[\p{L}'’.-]{2,48}`;
|
|
10
|
+
|
|
11
|
+
function clean(value) {
|
|
12
|
+
return String(value ?? '')
|
|
13
|
+
.normalize('NFKC')
|
|
14
|
+
.replace(PHONE_RUN_RE, (segment) => segment.replace(/\D/gu, '').length >= 9 ? ' ' : segment)
|
|
15
|
+
.replace(/[\t\f\v]+/gu, ' ')
|
|
16
|
+
.replace(/\s{2,}/gu, ' ')
|
|
17
|
+
.replace(/^[\s,;:.\-–—]+|[\s,;:.\-–—]+$/gu, '')
|
|
18
|
+
.trim();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function escapeRegExp(value) {
|
|
22
|
+
return String(value).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function compactStreet(value) {
|
|
26
|
+
return clean(value)
|
|
27
|
+
.replace(new RegExp(`^${PREFIX_STREET_MARKER}\\s*`, 'iu'), '')
|
|
28
|
+
.replace(new RegExp(`\\s+${POSTFIX_STREET_MARKER}$`, 'iu'), '')
|
|
29
|
+
.replace(/[\s,;:.\-–—]+$/gu, '')
|
|
30
|
+
.trim() || null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function normalizeNumber(value) {
|
|
34
|
+
const result = clean(value).replace(/\s+/gu, '');
|
|
35
|
+
return result || null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function result(address, street = null, houseNumber = null, building = null, confidence = 0) {
|
|
39
|
+
return Object.freeze({
|
|
40
|
+
address: clean(address) || null,
|
|
41
|
+
street: compactStreet(street),
|
|
42
|
+
houseNumber: normalizeNumber(houseNumber),
|
|
43
|
+
building: normalizeNumber(building),
|
|
44
|
+
confidence,
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function splitAddressTail(raw) {
|
|
49
|
+
const text = clean(raw);
|
|
50
|
+
if (!text) return null;
|
|
51
|
+
|
|
52
|
+
const buildingRe = new RegExp(`(?:\\s*[,;]?\\s*${BUILDING_MARKER}\\s*(${NUMBER_TOKEN}))\\s*$`, 'iu');
|
|
53
|
+
const buildingMatch = text.match(buildingRe);
|
|
54
|
+
const building = buildingMatch?.[1] || null;
|
|
55
|
+
const withoutBuilding = buildingMatch ? clean(text.slice(0, buildingMatch.index)) : text;
|
|
56
|
+
|
|
57
|
+
const markedHouse = withoutBuilding.match(new RegExp(`^(.*?)\\s*[,;]?\\s*${HOUSE_MARKER}\\s*(${NUMBER_TOKEN})\\s*$`, 'iu'));
|
|
58
|
+
if (markedHouse && /\p{L}{2,}/u.test(markedHouse[1])) {
|
|
59
|
+
return { street: markedHouse[1], houseNumber: markedHouse[2], building };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const trailingHouse = withoutBuilding.match(new RegExp(`^(.*?\\p{L}.*?)\\s+(${NUMBER_TOKEN})\\s*$`, 'iu'));
|
|
63
|
+
if (trailingHouse && /\p{L}{2,}/u.test(trailingHouse[1])) {
|
|
64
|
+
return { street: trailingHouse[1], houseNumber: trailingHouse[2], building };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return /\p{L}{2,}/u.test(withoutBuilding)
|
|
68
|
+
? { street: withoutBuilding, houseNumber: null, building }
|
|
69
|
+
: null;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function postfixTypedStreetAddress(line) {
|
|
73
|
+
const suffix = line.match(new RegExp(
|
|
74
|
+
`(?:^|[^\\p{L}\\p{N}])((?:${STREET_WORD}\\s+){0,4}${STREET_WORD}\\s+${POSTFIX_STREET_TYPE})` +
|
|
75
|
+
`\\s*[,;]?\\s*(${NUMBER_TOKEN})` +
|
|
76
|
+
`(?:\\s*[,;]?\\s*${BUILDING_MARKER}\\s*(${NUMBER_TOKEN}))?` +
|
|
77
|
+
`(?=$|[^\\p{L}\\p{N}])`,
|
|
78
|
+
'iu',
|
|
79
|
+
));
|
|
80
|
+
if (!suffix) return null;
|
|
81
|
+
|
|
82
|
+
const street = suffix[1];
|
|
83
|
+
const houseNumber = suffix[2];
|
|
84
|
+
const building = suffix[3] || null;
|
|
85
|
+
const address = composeHousingAddress({ street, houseNumber, building });
|
|
86
|
+
return result(address, street, houseNumber, building, 1);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function prefixTypedStreetAddress(line) {
|
|
90
|
+
const prefix = line.match(new RegExp(
|
|
91
|
+
`(?:^|[\\s,;])${PREFIX_STREET_MARKER}\\s+` +
|
|
92
|
+
`((?:${STREET_WORD}\\s+){0,4}${STREET_WORD})` +
|
|
93
|
+
`\\s*[,;]?\\s*(?:${HOUSE_MARKER}\\s*)?(${NUMBER_TOKEN})` +
|
|
94
|
+
`(?:\\s*[,;]?\\s*${BUILDING_MARKER}\\s*(${NUMBER_TOKEN}))?` +
|
|
95
|
+
`(?=$|[^\\p{L}\\p{N}])`,
|
|
96
|
+
'iu',
|
|
97
|
+
));
|
|
98
|
+
if (!prefix) return null;
|
|
99
|
+
|
|
100
|
+
const street = prefix[1];
|
|
101
|
+
const houseNumber = prefix[2];
|
|
102
|
+
const building = prefix[3] || null;
|
|
103
|
+
const address = composeHousingAddress({ street, houseNumber, building });
|
|
104
|
+
return result(address, street, houseNumber, building, 1);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function explicitStreetAddress(text) {
|
|
108
|
+
const lines = text
|
|
109
|
+
.split(/[\r\n|]/u)
|
|
110
|
+
.map((part) => clean(part).slice(0, 160))
|
|
111
|
+
.filter(Boolean)
|
|
112
|
+
.slice(0, 12);
|
|
113
|
+
|
|
114
|
+
for (const line of lines) {
|
|
115
|
+
const postfixTyped = postfixTypedStreetAddress(line);
|
|
116
|
+
if (postfixTyped) return postfixTyped;
|
|
117
|
+
|
|
118
|
+
const prefixTyped = prefixTypedStreetAddress(line);
|
|
119
|
+
if (prefixTyped) return prefixTyped;
|
|
120
|
+
|
|
121
|
+
const boundedPrefix = line.match(new RegExp(
|
|
122
|
+
`(?:^|[\\s,;])${PREFIX_STREET_MARKER}\\s*((?:${STREET_WORD}\\s+){0,3}${STREET_WORD})(?=$|[,;])`,
|
|
123
|
+
'iu',
|
|
124
|
+
));
|
|
125
|
+
if (boundedPrefix) return result(boundedPrefix[0], boundedPrefix[1], null, null, 0.9);
|
|
126
|
+
|
|
127
|
+
const prefix = line.match(new RegExp(`(?:^|[\\s,;])(${PREFIX_STREET_MARKER})\\s+(.+)$`, 'iu'));
|
|
128
|
+
if (prefix) {
|
|
129
|
+
const tail = splitAddressTail(prefix[2]);
|
|
130
|
+
if (tail) return result(line, tail.street, tail.houseNumber, tail.building, tail.houseNumber ? 1 : 0.9);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const postfix = line.match(new RegExp(`^(.+?)\\s+(${POSTFIX_STREET_MARKER})(.*)$`, 'iu'));
|
|
134
|
+
if (postfix) {
|
|
135
|
+
const tailText = clean(`${postfix[1]} ${postfix[3]}`);
|
|
136
|
+
const tail = splitAddressTail(tailText);
|
|
137
|
+
if (tail) return result(line, tail.street, tail.houseNumber, tail.building, tail.houseNumber ? 1 : 0.9);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function knownStreetAddress(text, knownStreet) {
|
|
145
|
+
const street = compactStreet(knownStreet);
|
|
146
|
+
if (!street) return null;
|
|
147
|
+
const streetPattern = street.split(/\s+/u).map(escapeRegExp).join('\\s+');
|
|
148
|
+
const re = new RegExp(
|
|
149
|
+
`(?:^|[^\\p{L}\\p{N}])(?:${PREFIX_STREET_MARKER}\\s+)?(${streetPattern})(?:\\s+${POSTFIX_STREET_MARKER})?` +
|
|
150
|
+
`(?:\\s*[,;]?\\s*(?:${HOUSE_MARKER})?\\s*(${NUMBER_TOKEN}))?` +
|
|
151
|
+
`(?:\\s*[,;]?\\s*${BUILDING_MARKER}\\s*(${NUMBER_TOKEN}))?` +
|
|
152
|
+
`(?=$|[^\\p{L}\\p{N}])`,
|
|
153
|
+
'iu',
|
|
154
|
+
);
|
|
155
|
+
const match = text.match(re);
|
|
156
|
+
if (!match) return null;
|
|
157
|
+
const houseNumber = match[2] || null;
|
|
158
|
+
const building = match[3] || null;
|
|
159
|
+
const address = composeHousingAddress({ street, houseNumber, building });
|
|
160
|
+
return result(address, street, houseNumber, building, houseNumber ? 0.98 : 0.9);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function labelledAddress(text) {
|
|
164
|
+
const label = text.match(ADDRESS_LABEL_RE);
|
|
165
|
+
if (!label) return null;
|
|
166
|
+
const start = (label.index ?? 0) + label[0].length;
|
|
167
|
+
const line = clean(text.slice(start).split(/[\r\n|]/u, 1)[0]).slice(0, 140);
|
|
168
|
+
if (!line) return null;
|
|
169
|
+
return parseHousingAddress(line, { allowBare: true });
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function bareAddress(text) {
|
|
173
|
+
const tail = splitAddressTail(text);
|
|
174
|
+
if (!tail) return null;
|
|
175
|
+
if (/^(?:центр|centre|center)$/iu.test(compactStreet(tail.street) || '')) return null;
|
|
176
|
+
const confidence = tail.houseNumber ? 0.85 : 0.55;
|
|
177
|
+
return result(text, tail.street, tail.houseNumber, tail.building, confidence);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Parse only textual address structure. This module intentionally contains no
|
|
182
|
+
* coordinates and performs no geocoding.
|
|
183
|
+
*
|
|
184
|
+
* `knownStreet` may be supplied when a location dictionary has already
|
|
185
|
+
* canonicalized the street. In that mode the parser only accepts a house/building
|
|
186
|
+
* number immediately adjacent to that exact street mention.
|
|
187
|
+
*
|
|
188
|
+
* `allowBare` should be used only when the input is already known to be an
|
|
189
|
+
* address field (for example a source-provided address), not on arbitrary post
|
|
190
|
+
* prose where prices and phone numbers may look like house numbers.
|
|
191
|
+
*/
|
|
192
|
+
export function parseHousingAddress(value, options = {}) {
|
|
193
|
+
const text = clean(value);
|
|
194
|
+
if (!text) return result(null);
|
|
195
|
+
|
|
196
|
+
const labelled = labelledAddress(text);
|
|
197
|
+
if (labelled) return labelled;
|
|
198
|
+
|
|
199
|
+
if (options.knownStreet) {
|
|
200
|
+
const known = knownStreetAddress(text, options.knownStreet);
|
|
201
|
+
if (known) return known;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const explicit = explicitStreetAddress(text);
|
|
205
|
+
if (explicit) return explicit;
|
|
206
|
+
|
|
207
|
+
if (options.allowBare === true) return bareAddress(text) || result(text);
|
|
208
|
+
return result(null);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
export function composeHousingAddress(parts = {}) {
|
|
212
|
+
const street = compactStreet(parts.street);
|
|
213
|
+
const houseNumber = normalizeNumber(parts.houseNumber);
|
|
214
|
+
const building = normalizeNumber(parts.building);
|
|
215
|
+
if (!street) return null;
|
|
216
|
+
return [street, houseNumber, building ? `корп. ${building}` : null].filter(Boolean).join(' ');
|
|
217
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function looksCommercialHousing(value: unknown): boolean;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// Non-residential/commercial listing signals that should be excluded from
|
|
2
|
+
// residential housing feeds. Kept conservative so nearby amenities (for
|
|
3
|
+
// example "магазин рядом") do not classify an otherwise valid home as commercial.
|
|
4
|
+
const COMMERCIAL_HOUSING_RE =
|
|
5
|
+
/(офис[ _|,/.]|под ?офис|офисн|\boffice\b|\bofis\b|кеңсе|коммерческ|commercial|бизнес[ -]?центр|не\s?жил(?:ое|ой|ый|ых|ым|ого)|помещени[ея]|торгов(?:ое|ая) ?площад|торгов(?:ое|ое помещ)|warehouse|склад(?!н|ыв)|производствен(?:ное|ых) ?помещ|spatiu comercial|(?:închiriez|inchiriez|vând|vand)\s+birou|birou\s+de\s+(?:închiriat|inchiriat|vânzare|vanzare)|\d+\s*sot(?:ix|ka)|\d+\s*сот(?:ок|ка|ки|ых)|yer\s*maydoni|bosh\s*yer|уч[аа]сток\s*земл|servis\s*uchun|kassaprav|avtomoyka|автомойк|car\s?wash|шиномонтаж|салон\s*красот|zallik\s*saloni|beauty\s*salon|beauty[\s-]?кабинет|космет(?:ическ|олог)[а-яё]*\s*кабинет|массажн[а-яё]*\s*кабинет|маникюрн[а-яё]*\s*(?:кабинет|студи)|nail\s*(?:bar|studio|salon)|аренда\s+(?:beauty\s+)?кабинет|парикмахерск|барбершоп|barbershop|аренда\s+рабоч(?:его|ее)\s*мест|рабоч(?:ее|его)\s*мест[оае]\s+(?:мастер|для\s+мастер|под\s+)|оренд[аи]\s+робоч(?:ого|е)\s*місц|аренда\s+гараж|гараж[ае]?\s+(?:аренд|сда[её]тся|ijara)|\bgaraj\b|\bgarage\b|o[\u2018\u2019\u02bb\u02bc'`ʻʼ]?quv\s*xona|o[\u2018\u2019\u02bb\u02bc'`ʻʼ]?quv\s*markaz|учебн(?:ый|ое|ая|ого)\s*(?:класс|помещ|кабинет|центр)|под\s+(?:бар|каф[её]|ресторан|магазин|салон|склад|бизнес|спортпит|спорт\s*пит|аптек|пекарн|пункт|шоурум|showroom|офис)|шоурум|showroom|спортпит|sportpit|\bsklad(?:lar)?\b|\bombor(?:xona)?\b|(?:avto\s*)?ser?vis\s+(?:arenda|ijara|beriladi)|авто\s?сервис\s+(?:аренд|сда[её]тся)|детейлинг|detailing|^\s*парковк|avtoturargoh|sot(?:ish|uv)\w*\s*joy|savdo\s*(?:uchun|joy)|(?:ijara\w*|arenda\w*|sot(?:ish|uv)\w*)\s+do[\u2018\u2019'`ʻʼ]?kon|do[\u2018\u2019'`ʻʼ]?kon\s+(?:ijara\w*|arenda\w*|sot(?:ish|uv)\w*)|(?:аренд|сда[её]т|сдам|прода|продаж)[а-яё]*\s+(?:(?:в|под|аренду|готов[а-яё]*|действующ[а-яё]*|срочно|отдельн[а-яё]*|стоящ[а-яё]*|цел[а-яё]*)\s+){0,3}(?:здани|каф[её])|бутик|[|,/]\s*(?:салон|бутик|аптек)|arenda\w*\s+joy|ijara\w*\s+joy|готов[а-яё]*\s+бизнес|tayyor\s+biznes|бизнес\s+(?:ресторан|каф[её]|магазин)|(?:сда[её]тся|аренда|прода[её]тся)\s+(?:готов[а-яё]*\s+)?(?:ресторан|каф[её]|чайхан|choyxona)|marojni|muzqaymoq)/i;
|
|
6
|
+
|
|
7
|
+
export function looksCommercialHousing(value) {
|
|
8
|
+
return value ? COMMERCIAL_HOUSING_RE.test(String(value)) : false;
|
|
9
|
+
}
|
package/src/housing-context.d.ts
CHANGED
|
@@ -1,36 +1,36 @@
|
|
|
1
|
-
export type HousingPolicy = 'allowed' | 'notAllowed' | 'conditional' | null;
|
|
2
|
-
export type HousingContextResult = Readonly<{
|
|
3
|
-
condition: string | null;
|
|
4
|
-
layouts: readonly string[];
|
|
5
|
-
buildingType: string | null;
|
|
6
|
-
buildingStatus: string | null;
|
|
7
|
-
priceContext: string | null;
|
|
8
|
-
priceModifiers: readonly string[];
|
|
9
|
-
rentDuration: string | null;
|
|
10
|
-
floorConstraints: readonly string[];
|
|
11
|
-
furniture: string | null;
|
|
12
|
-
tenantPolicies: Readonly<{ pets: HousingPolicy; children: HousingPolicy; smoking: HousingPolicy }>;
|
|
13
|
-
documents: readonly string[];
|
|
14
|
-
financing: readonly string[];
|
|
15
|
-
locationRelations: readonly string[];
|
|
16
|
-
availability: string | null;
|
|
17
|
-
listingStatus: string | null;
|
|
18
|
-
}>;
|
|
19
|
-
export const PROPERTY_CONDITION_TERMS: readonly unknown[];
|
|
20
|
-
export const LAYOUT_TERMS: readonly unknown[];
|
|
21
|
-
export const BUILDING_TYPE_TERMS: readonly unknown[];
|
|
22
|
-
export const BUILDING_STATUS_TERMS: readonly unknown[];
|
|
23
|
-
export const PROPERTY_PRICE_CONTEXT: readonly unknown[];
|
|
24
|
-
export const PRICE_MODIFIERS: readonly unknown[];
|
|
25
|
-
export const RENT_DURATION_TERMS: readonly unknown[];
|
|
26
|
-
export const FLOOR_CONSTRAINT_TERMS: readonly unknown[];
|
|
27
|
-
export const FURNITURE_STATE_TERMS: readonly unknown[];
|
|
28
|
-
export const PET_POLICY_TERMS: readonly unknown[];
|
|
29
|
-
export const CHILD_POLICY_TERMS: readonly unknown[];
|
|
30
|
-
export const SMOKING_POLICY_TERMS: readonly unknown[];
|
|
31
|
-
export const DOCUMENT_STATUS_TERMS: readonly unknown[];
|
|
32
|
-
export const FINANCING_TERMS: readonly unknown[];
|
|
33
|
-
export const LOCATION_RELATIONS: readonly unknown[];
|
|
34
|
-
export const AVAILABILITY_TERMS: readonly unknown[];
|
|
35
|
-
export const LISTING_STATUS_TERMS: readonly unknown[];
|
|
36
|
-
export function parseHousingContext(value: unknown): HousingContextResult;
|
|
1
|
+
export type HousingPolicy = 'allowed' | 'notAllowed' | 'conditional' | null;
|
|
2
|
+
export type HousingContextResult = Readonly<{
|
|
3
|
+
condition: string | null;
|
|
4
|
+
layouts: readonly string[];
|
|
5
|
+
buildingType: string | null;
|
|
6
|
+
buildingStatus: string | null;
|
|
7
|
+
priceContext: string | null;
|
|
8
|
+
priceModifiers: readonly string[];
|
|
9
|
+
rentDuration: string | null;
|
|
10
|
+
floorConstraints: readonly string[];
|
|
11
|
+
furniture: string | null;
|
|
12
|
+
tenantPolicies: Readonly<{ pets: HousingPolicy; children: HousingPolicy; smoking: HousingPolicy }>;
|
|
13
|
+
documents: readonly string[];
|
|
14
|
+
financing: readonly string[];
|
|
15
|
+
locationRelations: readonly string[];
|
|
16
|
+
availability: string | null;
|
|
17
|
+
listingStatus: string | null;
|
|
18
|
+
}>;
|
|
19
|
+
export const PROPERTY_CONDITION_TERMS: readonly unknown[];
|
|
20
|
+
export const LAYOUT_TERMS: readonly unknown[];
|
|
21
|
+
export const BUILDING_TYPE_TERMS: readonly unknown[];
|
|
22
|
+
export const BUILDING_STATUS_TERMS: readonly unknown[];
|
|
23
|
+
export const PROPERTY_PRICE_CONTEXT: readonly unknown[];
|
|
24
|
+
export const PRICE_MODIFIERS: readonly unknown[];
|
|
25
|
+
export const RENT_DURATION_TERMS: readonly unknown[];
|
|
26
|
+
export const FLOOR_CONSTRAINT_TERMS: readonly unknown[];
|
|
27
|
+
export const FURNITURE_STATE_TERMS: readonly unknown[];
|
|
28
|
+
export const PET_POLICY_TERMS: readonly unknown[];
|
|
29
|
+
export const CHILD_POLICY_TERMS: readonly unknown[];
|
|
30
|
+
export const SMOKING_POLICY_TERMS: readonly unknown[];
|
|
31
|
+
export const DOCUMENT_STATUS_TERMS: readonly unknown[];
|
|
32
|
+
export const FINANCING_TERMS: readonly unknown[];
|
|
33
|
+
export const LOCATION_RELATIONS: readonly unknown[];
|
|
34
|
+
export const AVAILABILITY_TERMS: readonly unknown[];
|
|
35
|
+
export const LISTING_STATUS_TERMS: readonly unknown[];
|
|
36
|
+
export function parseHousingContext(value: unknown): HousingContextResult;
|