@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.
Files changed (94) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +137 -137
  3. package/index.d.ts +374 -374
  4. package/package.json +77 -75
  5. package/src/central-asia-locations.js +211 -211
  6. package/src/central-asia.js +233 -233
  7. package/src/contact.d.ts +44 -44
  8. package/src/contact.js +159 -159
  9. package/src/countries.js +175 -175
  10. package/src/country-context.d.ts +10 -10
  11. package/src/country-context.js +22 -22
  12. package/src/currency.d.ts +14 -14
  13. package/src/currency.js +66 -66
  14. package/src/geo.js +169 -169
  15. package/src/geography-central-asia.js +42 -42
  16. package/src/geography-detection.d.ts +14 -14
  17. package/src/geography-detection.js +64 -64
  18. package/src/geography-display.d.ts +13 -13
  19. package/src/geography-display.js +113 -113
  20. package/src/geography.js +210 -210
  21. package/src/hiring-advanced.js +147 -147
  22. package/src/hiring-candidate-fields.d.ts +9 -9
  23. package/src/hiring-candidate-fields.js +92 -92
  24. package/src/hiring-context.d.ts +64 -64
  25. package/src/hiring-context.js +412 -412
  26. package/src/hiring-language-extensions.d.ts +19 -19
  27. package/src/hiring-language-extensions.js +139 -139
  28. package/src/hiring-languages.js +135 -135
  29. package/src/hiring-location-fields.d.ts +3 -3
  30. package/src/hiring-location-fields.js +53 -53
  31. package/src/hiring-professions-ro.js +191 -191
  32. package/src/hiring-professions.js +437 -437
  33. package/src/hiring-requirements.d.ts +16 -16
  34. package/src/hiring-requirements.js +170 -170
  35. package/src/hiring-salary-context.d.ts +27 -27
  36. package/src/hiring-salary-context.js +76 -76
  37. package/src/hiring-semantics.d.ts +29 -29
  38. package/src/hiring-semantics.js +218 -218
  39. package/src/hiring-skills.d.ts +21 -21
  40. package/src/hiring-skills.js +315 -315
  41. package/src/hiring-source-aliases.d.ts +32 -32
  42. package/src/hiring-source-aliases.js +185 -185
  43. package/src/hiring-source-semantics.d.ts +31 -31
  44. package/src/hiring-source-semantics.js +293 -293
  45. package/src/hiring-temporal.d.ts +15 -15
  46. package/src/hiring-temporal.js +67 -67
  47. package/src/hiring-vacancy-fields.d.ts +5 -5
  48. package/src/hiring-vacancy-fields.js +23 -23
  49. package/src/hiring-work-semantics.d.ts +34 -34
  50. package/src/hiring-work-semantics.js +72 -72
  51. package/src/hiring.js +180 -180
  52. package/src/housing-address.d.ts +18 -18
  53. package/src/housing-address.js +217 -210
  54. package/src/housing-commercial.d.ts +1 -0
  55. package/src/housing-commercial.js +9 -0
  56. package/src/housing-context.d.ts +36 -36
  57. package/src/housing-context.js +183 -183
  58. package/src/housing-features.js +30 -30
  59. package/src/housing-intent.d.ts +9 -9
  60. package/src/housing-intent.js +132 -132
  61. package/src/housing-listing-fields.js +131 -131
  62. package/src/housing-money.d.ts +7 -7
  63. package/src/housing-money.js +102 -102
  64. package/src/housing-source-aliases.d.ts +8 -8
  65. package/src/housing-source-aliases.js +51 -51
  66. package/src/housing-structured.d.ts +55 -55
  67. package/src/housing-structured.js +219 -219
  68. package/src/housing.js +200 -200
  69. package/src/index.js +45 -44
  70. package/src/kz-location-extensions.js +307 -307
  71. package/src/landmarks.js +21 -21
  72. package/src/lexicon-core.js +58 -58
  73. package/src/location-merge.js +106 -106
  74. package/src/locations.js +456 -456
  75. package/src/money-core.d.ts +10 -10
  76. package/src/money-core.js +81 -81
  77. package/src/money-lexicon.d.ts +5 -5
  78. package/src/money-lexicon.js +79 -79
  79. package/src/money.js +154 -154
  80. package/src/normalization.js +384 -384
  81. package/src/odesa-metropolitan.js +144 -144
  82. package/src/romania-geography.js +47 -47
  83. package/src/tashkent-colloquial.js +80 -72
  84. package/src/tashkent-housing-geography.d.ts +38 -38
  85. package/src/tashkent-housing-geography.js +211 -211
  86. package/src/tashkent-pois.js +215 -215
  87. package/src/tashkent-residential-complexes.js +272 -272
  88. package/src/ua-location-extensions-major.js +118 -118
  89. package/src/ua-location-extensions-metro.js +9 -9
  90. package/src/ua-location-extensions-regional.js +246 -246
  91. package/src/ua-secondary-cities.d.ts +2 -2
  92. package/src/ua-secondary-cities.js +57 -57
  93. package/src/ukraine.js +75 -75
  94. package/src/uz-location-extensions.js +259 -259
@@ -1,132 +1,132 @@
1
- import { findCanonical } from './normalization.js';
2
- import { lexiconEntity } from './lexicon-core.js';
3
-
4
- const group = (canonical, aliases, extra = {}) => lexiconEntity(canonical, aliases, extra);
5
- const KK_RENT_OUT_ALIASES = Object.freeze(['жалға беремін', 'жалға беріледі']);
6
- const RO_SALE_OFFER_ALIASES = Object.freeze(['de vânzare', 'de vanzare']);
7
-
8
- export const HOUSING_ACTIONS = Object.freeze([
9
- group('sell', {
10
- ru: ['продам', 'продаю', 'продаётся', 'продается', 'выставил на продажу', 'выставила на продажу'],
11
- en: ['selling', 'for sale', 'selling apartment', 'selling house'],
12
- uk: ['продам', 'продаю', 'продається'],
13
- ro: ['vând', 'vand', ...RO_SALE_OFFER_ALIASES, 'se vinde'],
14
- uzLatn: ['sotaman', 'sotiladi', 'sotuvga qoyildi', "sotuvga qo'yildi"],
15
- uzCyrl: ['сотаман', 'сотилади', 'сотувга қўйилди'],
16
- kk: ['сатамын', 'сатылады', 'сатылымға қойылды'],
17
- }),
18
- group('buy', {
19
- ru: ['куплю', 'хочу купить', 'ищу купить', 'ищу для покупки', 'нужна квартира для покупки'],
20
- en: ['want to buy', 'looking to buy', 'looking for a property to buy', 'need an apartment to buy'],
21
- uk: ['куплю', 'хочу купити', 'шукаю купити', 'шукаю для купівлі'],
22
- ro: ['vreau să cumpăr', 'vreau sa cumpar', 'caut să cumpăr', 'caut sa cumpar'],
23
- uzLatn: ['sotib olaman', 'sotib olish uchun qidiryapman', 'sotib olishga uy qidiryapman'],
24
- uzCyrl: ['сотиб оламан', 'сотиб олиш учун қидиряпман', 'сотиб олишга уй қидиряпман'],
25
- kk: ['сатып аламын', 'сатып алуға іздеймін', 'сатып алуға пәтер іздеймін'],
26
- }),
27
- group('rentOut', {
28
- ru: ['сдам', 'сдаю', 'сдаётся', 'сдается', 'сдаётся в аренду', 'сдается в аренду'],
29
- en: ['for rent', 'renting out', 'available for rent', 'letting'],
30
- uk: ['здам', 'здаю', 'здається', 'здається в оренду'],
31
- ro: ['închiriez', 'inchiriez', 'de închiriat', 'de inchiriat', 'se închiriază', 'se inchiriaza'],
32
- uzLatn: ['ijaraga beraman', 'ijaraga beriladi', 'ijaraga topshiraman'],
33
- uzCyrl: ['ижарага бераман', 'ижарага берилади', 'ижарага топшираман'],
34
- kk: KK_RENT_OUT_ALIASES,
35
- }),
36
- group('rentIn', {
37
- ru: ['сниму', 'хочу снять', 'ищу снять', 'ищу квартиру', 'ищу жильё', 'ищу жилье', 'нужна квартира', 'нужно жильё', 'нужно жилье'],
38
- en: ['looking to rent', 'want to rent', 'need an apartment', 'looking for an apartment', 'looking for housing'],
39
- uk: ['зніму', 'хочу зняти', 'шукаю квартиру', 'шукаю житло', 'потрібна квартира'],
40
- ro: ['vreau să închiriez', 'vreau sa inchiriez', 'caut să închiriez', 'caut sa inchiriez', 'caut apartament'],
41
- uzLatn: ['ijaraga olaman', 'uy qidiryapman', 'kvartira qidiryapman', 'ijara uchun uy qidiryapman'],
42
- uzCyrl: ['ижарага оламан', 'уй қидиряпман', 'квартира қидиряпман', 'ижара учун уй қидиряпман'],
43
- kk: ['жалға аламын', 'пәтер іздеймін', 'үй іздеймін', 'жалға пәтер іздеймін'],
44
- }),
45
- ]);
46
-
47
- export const HOUSING_INTENT = Object.freeze([
48
- group('offer', {
49
- ru: ['продам', 'продаю', 'продаётся', 'продается', 'сдам', 'сдаю', 'сдаётся', 'сдается'],
50
- en: ['for sale', 'selling', 'for rent', 'renting out'],
51
- uk: ['продам', 'продаю', 'продається', 'здам', 'здаю', 'здається'],
52
- ro: ['vând', 'vand', 'de vânzare', 'de vanzare', 'închiriez', 'inchiriez', 'de închiriat', 'de inchiriat'],
53
- uzLatn: ['sotaman', 'sotiladi', 'ijaraga beraman', 'ijaraga beriladi'],
54
- uzCyrl: ['сотаман', 'сотилади', 'ижарага бераман', 'ижарага берилади'],
55
- kk: ['сатамын', 'сатылады', 'жалға беремін', 'жалға беріледі'],
56
- }),
57
- group('wanted', {
58
- ru: ['куплю', 'ищу купить', 'сниму', 'ищу квартиру', 'ищу жильё', 'ищу жилье', 'нужна квартира'],
59
- en: ['looking to buy', 'want to buy', 'looking to rent', 'need an apartment'],
60
- uk: ['куплю', 'шукаю купити', 'зніму', 'шукаю квартиру', 'шукаю житло'],
61
- ro: ['vreau să cumpăr', 'vreau sa cumpar', 'vreau să închiriez', 'vreau sa inchiriez', 'caut apartament'],
62
- uzLatn: ['sotib olaman', 'sotib olish uchun qidiryapman', 'ijaraga olaman', 'uy qidiryapman'],
63
- uzCyrl: ['сотиб оламан', 'сотиб олиш учун қидиряпман', 'ижарага оламан', 'уй қидиряпман'],
64
- kk: ['сатып аламын', 'сатып алуға іздеймін', 'жалға аламын', 'пәтер іздеймін'],
65
- }),
66
- ]);
67
-
68
- // Deal type is transaction mechanics only. Direction is resolved by HOUSING_ACTIONS.
69
- export const HOUSING_DEAL_TYPES = Object.freeze([
70
- group('sale', {
71
- ru: ['продажа', 'продать', 'покупка'],
72
- en: ['sale', 'purchase'],
73
- uk: ['продаж', 'продати', 'купівля'],
74
- ro: ['vânzare', 'vanzare', ...RO_SALE_OFFER_ALIASES, 'cumpărare', 'cumparare'],
75
- uzLatn: ['sotish', 'sotuv', 'sotib olish'],
76
- uzCyrl: ['сотиш', 'сотув', 'сотиб олиш'],
77
- kk: ['сату', 'сатып алу', 'сатылым'],
78
- }),
79
- group('longRent', {
80
- ru: ['аренда', 'долгосрочно', 'долгосрочная аренда', 'помесячно', 'на длительный срок'],
81
- en: ['rent', 'long term rent', 'long-term rent', 'monthly rent'],
82
- uk: ['оренда', 'довгостроково', 'довгострокова оренда', 'помісячно'],
83
- ro: ['închiriere', 'inchiriere', 'chirie', 'pe termen lung', 'lunar'],
84
- uzLatn: ['ijara', 'uzoq muddatga', 'oylik ijara'],
85
- uzCyrl: ['ижара', 'узоқ муддатга', 'ойлик ижара'],
86
- kk: ['жалдау', 'жалға', ...KK_RENT_OUT_ALIASES, 'ұзақ мерзімге', 'айлық жалдау'],
87
- }),
88
- group('shortRent', {
89
- ru: ['посуточно', 'посуточная аренда', 'на сутки', 'на час', 'почасово', 'краткосрочно'],
90
- en: ['daily rent', 'short term', 'short-term rent', 'per day', 'hourly'],
91
- uk: ['подобово', 'погодинно', 'на добу', 'на годину', 'короткострокова оренда'],
92
- ro: ['regim hotelier', 'pe zi', 'zilnic', 'pe noapte', 'închiriere pe termen scurt', 'inchiriere pe termen scurt'],
93
- uzLatn: ['kunlik', 'sutkaga', 'sutkalik', 'soatlik', 'kuniga'],
94
- uzCyrl: ['кунлик', 'суткага', 'суткалик', 'соатлик', 'кунига'],
95
- kk: ['тәулік', 'тәуліктік', 'тәулігіне', 'сағаттық', 'күндік'],
96
- }),
97
- ]);
98
-
99
- export const HOUSING_ACTION_MAP = Object.freeze({
100
- sell: Object.freeze({ listingKind: 'propertyOffer', dealType: 'sale' }),
101
- buy: Object.freeze({ listingKind: 'propertyWanted', dealType: 'sale' }),
102
- rentOut: Object.freeze({ listingKind: 'propertyOffer', dealType: 'longRent' }),
103
- rentIn: Object.freeze({ listingKind: 'propertyWanted', dealType: 'longRent' }),
104
- });
105
-
106
- export function resolveHousingIntent(value) {
107
- const text = String(value || '');
108
- if (!text.trim()) return null;
109
-
110
- const action = findCanonical(text, HOUSING_ACTIONS, { partial: true });
111
- const durationDeal = findCanonical(text, HOUSING_DEAL_TYPES, { partial: true });
112
-
113
- if (action) {
114
- const base = HOUSING_ACTION_MAP[action.canonical];
115
- let dealType = base.dealType;
116
- if (durationDeal?.canonical === 'shortRent' && (action.canonical === 'rentOut' || action.canonical === 'rentIn')) {
117
- dealType = 'shortRent';
118
- }
119
- return Object.freeze({
120
- action: action.canonical,
121
- listingKind: base.listingKind,
122
- dealType,
123
- });
124
- }
125
-
126
- if (!durationDeal) return null;
127
- return Object.freeze({
128
- action: null,
129
- listingKind: null,
130
- dealType: durationDeal.canonical,
131
- });
132
- }
1
+ import { findCanonical } from './normalization.js';
2
+ import { lexiconEntity } from './lexicon-core.js';
3
+
4
+ const group = (canonical, aliases, extra = {}) => lexiconEntity(canonical, aliases, extra);
5
+ const KK_RENT_OUT_ALIASES = Object.freeze(['жалға беремін', 'жалға беріледі']);
6
+ const RO_SALE_OFFER_ALIASES = Object.freeze(['de vânzare', 'de vanzare']);
7
+
8
+ export const HOUSING_ACTIONS = Object.freeze([
9
+ group('sell', {
10
+ ru: ['продам', 'продаю', 'продаётся', 'продается', 'выставил на продажу', 'выставила на продажу'],
11
+ en: ['selling', 'for sale', 'selling apartment', 'selling house'],
12
+ uk: ['продам', 'продаю', 'продається'],
13
+ ro: ['vând', 'vand', ...RO_SALE_OFFER_ALIASES, 'se vinde'],
14
+ uzLatn: ['sotaman', 'sotiladi', 'sotuvga qoyildi', "sotuvga qo'yildi"],
15
+ uzCyrl: ['сотаман', 'сотилади', 'сотувга қўйилди'],
16
+ kk: ['сатамын', 'сатылады', 'сатылымға қойылды'],
17
+ }),
18
+ group('buy', {
19
+ ru: ['куплю', 'хочу купить', 'ищу купить', 'ищу для покупки', 'нужна квартира для покупки'],
20
+ en: ['want to buy', 'looking to buy', 'looking for a property to buy', 'need an apartment to buy'],
21
+ uk: ['куплю', 'хочу купити', 'шукаю купити', 'шукаю для купівлі'],
22
+ ro: ['vreau să cumpăr', 'vreau sa cumpar', 'caut să cumpăr', 'caut sa cumpar'],
23
+ uzLatn: ['sotib olaman', 'sotib olish uchun qidiryapman', 'sotib olishga uy qidiryapman'],
24
+ uzCyrl: ['сотиб оламан', 'сотиб олиш учун қидиряпман', 'сотиб олишга уй қидиряпман'],
25
+ kk: ['сатып аламын', 'сатып алуға іздеймін', 'сатып алуға пәтер іздеймін'],
26
+ }),
27
+ group('rentOut', {
28
+ ru: ['сдам', 'сдаю', 'сдаётся', 'сдается', 'сдаётся в аренду', 'сдается в аренду'],
29
+ en: ['for rent', 'renting out', 'available for rent', 'letting'],
30
+ uk: ['здам', 'здаю', 'здається', 'здається в оренду'],
31
+ ro: ['închiriez', 'inchiriez', 'de închiriat', 'de inchiriat', 'se închiriază', 'se inchiriaza'],
32
+ uzLatn: ['ijaraga beraman', 'ijaraga beriladi', 'ijaraga topshiraman'],
33
+ uzCyrl: ['ижарага бераман', 'ижарага берилади', 'ижарага топшираман'],
34
+ kk: KK_RENT_OUT_ALIASES,
35
+ }),
36
+ group('rentIn', {
37
+ ru: ['сниму', 'хочу снять', 'ищу снять', 'ищу квартиру', 'ищу жильё', 'ищу жилье', 'нужна квартира', 'нужно жильё', 'нужно жилье'],
38
+ en: ['looking to rent', 'want to rent', 'need an apartment', 'looking for an apartment', 'looking for housing'],
39
+ uk: ['зніму', 'хочу зняти', 'шукаю квартиру', 'шукаю житло', 'потрібна квартира'],
40
+ ro: ['vreau să închiriez', 'vreau sa inchiriez', 'caut să închiriez', 'caut sa inchiriez', 'caut apartament'],
41
+ uzLatn: ['ijaraga olaman', 'uy qidiryapman', 'kvartira qidiryapman', 'ijara uchun uy qidiryapman'],
42
+ uzCyrl: ['ижарага оламан', 'уй қидиряпман', 'квартира қидиряпман', 'ижара учун уй қидиряпман'],
43
+ kk: ['жалға аламын', 'пәтер іздеймін', 'үй іздеймін', 'жалға пәтер іздеймін'],
44
+ }),
45
+ ]);
46
+
47
+ export const HOUSING_INTENT = Object.freeze([
48
+ group('offer', {
49
+ ru: ['продам', 'продаю', 'продаётся', 'продается', 'сдам', 'сдаю', 'сдаётся', 'сдается'],
50
+ en: ['for sale', 'selling', 'for rent', 'renting out'],
51
+ uk: ['продам', 'продаю', 'продається', 'здам', 'здаю', 'здається'],
52
+ ro: ['vând', 'vand', 'de vânzare', 'de vanzare', 'închiriez', 'inchiriez', 'de închiriat', 'de inchiriat'],
53
+ uzLatn: ['sotaman', 'sotiladi', 'ijaraga beraman', 'ijaraga beriladi'],
54
+ uzCyrl: ['сотаман', 'сотилади', 'ижарага бераман', 'ижарага берилади'],
55
+ kk: ['сатамын', 'сатылады', 'жалға беремін', 'жалға беріледі'],
56
+ }),
57
+ group('wanted', {
58
+ ru: ['куплю', 'ищу купить', 'сниму', 'ищу квартиру', 'ищу жильё', 'ищу жилье', 'нужна квартира'],
59
+ en: ['looking to buy', 'want to buy', 'looking to rent', 'need an apartment'],
60
+ uk: ['куплю', 'шукаю купити', 'зніму', 'шукаю квартиру', 'шукаю житло'],
61
+ ro: ['vreau să cumpăr', 'vreau sa cumpar', 'vreau să închiriez', 'vreau sa inchiriez', 'caut apartament'],
62
+ uzLatn: ['sotib olaman', 'sotib olish uchun qidiryapman', 'ijaraga olaman', 'uy qidiryapman'],
63
+ uzCyrl: ['сотиб оламан', 'сотиб олиш учун қидиряпман', 'ижарага оламан', 'уй қидиряпман'],
64
+ kk: ['сатып аламын', 'сатып алуға іздеймін', 'жалға аламын', 'пәтер іздеймін'],
65
+ }),
66
+ ]);
67
+
68
+ // Deal type is transaction mechanics only. Direction is resolved by HOUSING_ACTIONS.
69
+ export const HOUSING_DEAL_TYPES = Object.freeze([
70
+ group('sale', {
71
+ ru: ['продажа', 'продать', 'покупка'],
72
+ en: ['sale', 'purchase'],
73
+ uk: ['продаж', 'продати', 'купівля'],
74
+ ro: ['vânzare', 'vanzare', ...RO_SALE_OFFER_ALIASES, 'cumpărare', 'cumparare'],
75
+ uzLatn: ['sotish', 'sotuv', 'sotib olish'],
76
+ uzCyrl: ['сотиш', 'сотув', 'сотиб олиш'],
77
+ kk: ['сату', 'сатып алу', 'сатылым'],
78
+ }),
79
+ group('longRent', {
80
+ ru: ['аренда', 'долгосрочно', 'долгосрочная аренда', 'помесячно', 'на длительный срок'],
81
+ en: ['rent', 'long term rent', 'long-term rent', 'monthly rent'],
82
+ uk: ['оренда', 'довгостроково', 'довгострокова оренда', 'помісячно'],
83
+ ro: ['închiriere', 'inchiriere', 'chirie', 'pe termen lung', 'lunar'],
84
+ uzLatn: ['ijara', 'uzoq muddatga', 'oylik ijara'],
85
+ uzCyrl: ['ижара', 'узоқ муддатга', 'ойлик ижара'],
86
+ kk: ['жалдау', 'жалға', ...KK_RENT_OUT_ALIASES, 'ұзақ мерзімге', 'айлық жалдау'],
87
+ }),
88
+ group('shortRent', {
89
+ ru: ['посуточно', 'посуточная аренда', 'на сутки', 'на час', 'почасово', 'краткосрочно'],
90
+ en: ['daily rent', 'short term', 'short-term rent', 'per day', 'hourly'],
91
+ uk: ['подобово', 'погодинно', 'на добу', 'на годину', 'короткострокова оренда'],
92
+ ro: ['regim hotelier', 'pe zi', 'zilnic', 'pe noapte', 'închiriere pe termen scurt', 'inchiriere pe termen scurt'],
93
+ uzLatn: ['kunlik', 'sutkaga', 'sutkalik', 'soatlik', 'kuniga'],
94
+ uzCyrl: ['кунлик', 'суткага', 'суткалик', 'соатлик', 'кунига'],
95
+ kk: ['тәулік', 'тәуліктік', 'тәулігіне', 'сағаттық', 'күндік'],
96
+ }),
97
+ ]);
98
+
99
+ export const HOUSING_ACTION_MAP = Object.freeze({
100
+ sell: Object.freeze({ listingKind: 'propertyOffer', dealType: 'sale' }),
101
+ buy: Object.freeze({ listingKind: 'propertyWanted', dealType: 'sale' }),
102
+ rentOut: Object.freeze({ listingKind: 'propertyOffer', dealType: 'longRent' }),
103
+ rentIn: Object.freeze({ listingKind: 'propertyWanted', dealType: 'longRent' }),
104
+ });
105
+
106
+ export function resolveHousingIntent(value) {
107
+ const text = String(value || '');
108
+ if (!text.trim()) return null;
109
+
110
+ const action = findCanonical(text, HOUSING_ACTIONS, { partial: true });
111
+ const durationDeal = findCanonical(text, HOUSING_DEAL_TYPES, { partial: true });
112
+
113
+ if (action) {
114
+ const base = HOUSING_ACTION_MAP[action.canonical];
115
+ let dealType = base.dealType;
116
+ if (durationDeal?.canonical === 'shortRent' && (action.canonical === 'rentOut' || action.canonical === 'rentIn')) {
117
+ dealType = 'shortRent';
118
+ }
119
+ return Object.freeze({
120
+ action: action.canonical,
121
+ listingKind: base.listingKind,
122
+ dealType,
123
+ });
124
+ }
125
+
126
+ if (!durationDeal) return null;
127
+ return Object.freeze({
128
+ action: null,
129
+ listingKind: null,
130
+ dealType: durationDeal.canonical,
131
+ });
132
+ }
@@ -1,131 +1,131 @@
1
- import { deepFreeze } from './lexicon-core.js';
2
- import { normalizeUnicode } from './normalization.js';
3
- import { parseHousingContext } from './housing-context.js';
4
- import { parseHousingFeatures } from './housing-features.js';
5
-
6
- const bool = (text, positive, negative = null) => {
7
- if (negative?.test(text)) return false;
8
- return positive.test(text) ? true : null;
9
- };
10
-
11
- const number = (match, min, max) => {
12
- const value = match ? Number(match[1]) : null;
13
- return Number.isFinite(value) && value >= min && value <= max ? value : null;
14
- };
15
-
16
- function parseBedrooms(text) {
17
- return number(text.match(/(\d+)\s*-?\s*(?:bedroom|спальн|спалень|dormitoare|dormitor|yotoq(?:xona)?|жатын)/iu), 1, 20);
18
- }
19
-
20
- function parseBathrooms(text) {
21
- const match = text.match(/(\d)\s*(?:сан\s?уз(?:е)?л\p{L}*|с\/?у(?=$|[^\p{L}\p{N}_])|ванн[аы]|bathrooms?|sanuzel|hammom)/iu)
22
- || text.match(/(?:сан\s?уз(?:е)?л\p{L}*|bathrooms?|sanuzel|hammom)\D{0,4}(\d)/iu);
23
- return number(match, 1, 10);
24
- }
25
-
26
- function parseBuildingYear(text) {
27
- const match = text.match(/(?:год(?:а)?\s+постройк\p{L}*|построен\p{L}*|рік\s+побудов\p{L}*|built|year\s+built|an\s+construc\p{L}*|qurilgan|qurilish\s+yil\p{L}*|жыл(?:ы)?\s+салынған)\D{0,12}(19\d{2}|20[0-3]\d)/iu)
28
- || text.match(/(19\d{2}|20[0-3]\d)\s*(?:г\.?|года|р\.?|рік|йил|жыл|year)(?=$|[^\p{L}\p{N}_])/iu);
29
- if (!match) return null;
30
- const year = Number(match[1]);
31
- return year >= 1900 && year <= new Date().getFullYear() + 3 ? year : null;
32
- }
33
-
34
- function parseMinRentTerm(text) {
35
- const match = text.match(/(?:минимальн\p{L}*\s+(?:срок|термин)\p{L}*(?:\s+аренд\p{L}*)?|минимум|не\s+менее|від\s+|minimum(?:\s+term)?|at\s+least)\s*[:\-]?\s*(\d{1,3})\s*(дн\p{L}*|сут\p{L}*|недел\p{L}*|тижн\p{L}*|месяц\p{L}*|мес\.?|місяц\p{L}*|год\p{L}*|рік|days?|weeks?|months?|years?)/iu);
36
- if (!match) return null;
37
- const value = Number(match[1]);
38
- const unitText = match[2].toLocaleLowerCase();
39
- const unit = /дн|сут|day/u.test(unitText) ? 'day'
40
- : /недел|тижн|week/u.test(unitText) ? 'week'
41
- : /месяц|мес|місяц|month/u.test(unitText) ? 'month'
42
- : 'year';
43
- return value > 0 ? deepFreeze({ value, unit }) : null;
44
- }
45
-
46
- function parseAvailableFrom(text) {
47
- const match = text.match(/(?:доступн\p{L}*\s+с|свободн\p{L}*\s+с|заселени\p{L}*\s+с|заезд\s+с|available\s+from|move[- ]?in\s+from)\s*[:\-]?\s*((?:\d{1,2}[./-]\d{1,2}(?:[./-]\d{2,4})?)|(?:\d{1,2}\s+[\p{L}]{3,12}(?:\s+\d{4})?))/iu);
48
- return match?.[1]?.trim() || null;
49
- }
50
-
51
- function parseUtilitiesAmount(text) {
52
- const match = text.match(/(?:коммунальн\p{L}*|коммуналк\p{L}*|ком\.?\s*услуг\p{L}*|utilities?|bills?)\D{0,24}(?:около|примерно|~|≈)?\s*(\d{2,8}(?:[.,]\d{1,2})?)\s*(₴|грн|uah|\$|usd|€|eur|сум|uzs|тг|kzt|lei|ron|руб|rub)?/iu);
53
- if (!match) return null;
54
- const amount = Number(match[1].replace(',', '.'));
55
- if (!Number.isFinite(amount) || amount <= 0) return null;
56
- const raw = (match[2] || '').toLocaleLowerCase();
57
- const currency = /₴|грн|uah/u.test(raw) ? 'UAH'
58
- : /\$|usd/u.test(raw) ? 'USD'
59
- : /€|eur/u.test(raw) ? 'EUR'
60
- : /сум|uzs/u.test(raw) ? 'UZS'
61
- : /тг|kzt/u.test(raw) ? 'KZT'
62
- : /lei|ron/u.test(raw) ? 'RON'
63
- : /руб|rub/u.test(raw) ? 'RUB' : null;
64
- return deepFreeze({ amount, currency, approximate: /около|примерно|~|≈/iu.test(match[0]) });
65
- }
66
-
67
- function parseCommunalSeparated(text, country) {
68
- if (/(коммунал\p{L}*(?:\s+услуг\p{L}*)?\s*(?:отдельно|сверху|плюс|оплачива\p{L}*\s*отдельно)|свет\s*вода\s*газ\s*отдельно|kommunal\p{L}*\s*(?:alohida|ustiga)|utilities?\s*(?:separate|extra|not included))/iu.test(text)) return true;
69
- if (/(коммунал\p{L}*(?:\s+услуг\p{L}*)?\s*(?:включ|входит|в\s*стоимост)|вс[её]\s*включ|all\s*inclusive|kommunal\p{L}*\s*(?:kiritilgan|ichida)|utilities?\s*included)/iu.test(text)) return false;
70
- return String(country || '').toUpperCase() === 'UA' ? true : null;
71
- }
72
-
73
- function parseDepositRequired(text) {
74
- if (/(?:без\s+(?:залога|депозита)|залог\s+не\s+нужен|депозит\s+не\s+нужен|no\s+deposit)/iu.test(text)) return false;
75
- if (/(?:^|[^\p{L}\p{N}_])(?:залог|депозит|deposit|depozit|garantie|garanție|кепіл)(?=$|[^\p{L}\p{N}_])/iu.test(text)) return true;
76
- return null;
77
- }
78
-
79
- export function parseHousingListingFields(value, { country = '' } = {}) {
80
- const text = normalizeUnicode(value ?? '');
81
- const context = parseHousingContext(text);
82
- const features = parseHousingFeatures(text);
83
- if (!text) return deepFreeze({});
84
-
85
- const gas = bool(text,
86
- /(?:^|[^\p{L}\p{N}_])(?:газ|gaz|gas)(?=$|[^\p{L}\p{N}_])|метан|aragaz|gaz\s+ta['’]?min/iu,
87
- /без\s+газа|нет\s+газа|no\s+gas|gaz\s*yo['’]?q/iu,
88
- );
89
- const elevator = bool(text, /лифт|elevator|\blift\b/iu, /без\s*лифт|no\s*elevator|lift\s*yo['’]?q/iu);
90
- const furnished = context.furniture === 'none' ? false : context.furniture ? true : null;
91
- const childrenAllowed = context.tenantPolicies.children === 'allowed' ? true
92
- : context.tenantPolicies.children === 'notAllowed' ? false : null;
93
- const smokingAllowed = context.tenantPolicies.smoking === 'allowed' ? true
94
- : context.tenantPolicies.smoking === 'notAllowed' ? false : null;
95
-
96
- const firstRent = bool(text,
97
- /первая\s+(?:сдача|аренда)|впервые\s+(?:сда[её]тся|сдается|в\s+аренду)|first\s+(?:rental|letting)|never\s+rented/iu,
98
- );
99
-
100
- return deepFreeze({
101
- bedrooms: parseBedrooms(text),
102
- bathrooms: parseBathrooms(text),
103
- buildingYear: parseBuildingYear(text),
104
- balcony: bool(text, /балкон|лоджи|balkon|ayvon|balcon|loggia|balcony/iu),
105
- terrace: bool(text, /террас|terrace|teras[ăa]?/iu),
106
- privateYard: bool(text, /личн\p{L}*\s+двор|сво[йеё]\s+(?:закрыт\p{L}*\s+)?двор|собственн\p{L}*\s+двор|приватн\p{L}*\s+двір|власн\p{L}*\s+двір|private\s+(?:courtyard|yard)|curte\s+(?:proprie|privat[ăa])|shaxsiy\s+hovli/iu),
107
- courtyard: features.courtyard,
108
- gazebo: features.gazebo,
109
- dishwasher: bool(text, /посудомоечн\p{L}*|посудомойк\p{L}*|dishwasher|mașin[ăa]\s+de\s+spălat\s+vase/iu),
110
- airConditioner: bool(text, /кондицион|сплит[- ]?систем|konditsioner|klimat|air\s*con|aer\s+condi[țt]ionat/iu),
111
- gas,
112
- newBuilding: bool(text, /новостро|новобуд|новый\s+дом|new\s*build|newly\s*built|yangi\s+(?:bino|qurilgan|uy)|bloc\s+nou/iu),
113
- communalSeparated: parseCommunalSeparated(text, country),
114
- parking: bool(text, /паркинг|парков|машино[- ]?мест|parking|avtoturargoh|mashina\s*joyi/iu),
115
- elevator,
116
- heating: bool(text, /отоплени|heating|otoplenie|isitish|markaziy\s*issiq/iu),
117
- hotWater: bool(text, /горяч\p{L}*\s*вод|hot\s*water|issiq\s*suv/iu),
118
- internet: features.internet,
119
- petsAllowed: features.petsAllowed,
120
- childrenAllowed,
121
- smokingAllowed,
122
- negotiable: context.priceModifiers.includes('fixed') ? false
123
- : context.priceModifiers.includes('negotiable') ? true : null,
124
- furnished,
125
- depositRequired: parseDepositRequired(text),
126
- firstRent,
127
- minRentTerm: parseMinRentTerm(text),
128
- availableFrom: parseAvailableFrom(text),
129
- utilitiesAmount: parseUtilitiesAmount(text),
130
- });
131
- }
1
+ import { deepFreeze } from './lexicon-core.js';
2
+ import { normalizeUnicode } from './normalization.js';
3
+ import { parseHousingContext } from './housing-context.js';
4
+ import { parseHousingFeatures } from './housing-features.js';
5
+
6
+ const bool = (text, positive, negative = null) => {
7
+ if (negative?.test(text)) return false;
8
+ return positive.test(text) ? true : null;
9
+ };
10
+
11
+ const number = (match, min, max) => {
12
+ const value = match ? Number(match[1]) : null;
13
+ return Number.isFinite(value) && value >= min && value <= max ? value : null;
14
+ };
15
+
16
+ function parseBedrooms(text) {
17
+ return number(text.match(/(\d+)\s*-?\s*(?:bedroom|спальн|спалень|dormitoare|dormitor|yotoq(?:xona)?|жатын)/iu), 1, 20);
18
+ }
19
+
20
+ function parseBathrooms(text) {
21
+ const match = text.match(/(\d)\s*(?:сан\s?уз(?:е)?л\p{L}*|с\/?у(?=$|[^\p{L}\p{N}_])|ванн[аы]|bathrooms?|sanuzel|hammom)/iu)
22
+ || text.match(/(?:сан\s?уз(?:е)?л\p{L}*|bathrooms?|sanuzel|hammom)\D{0,4}(\d)/iu);
23
+ return number(match, 1, 10);
24
+ }
25
+
26
+ function parseBuildingYear(text) {
27
+ const match = text.match(/(?:год(?:а)?\s+постройк\p{L}*|построен\p{L}*|рік\s+побудов\p{L}*|built|year\s+built|an\s+construc\p{L}*|qurilgan|qurilish\s+yil\p{L}*|жыл(?:ы)?\s+салынған)\D{0,12}(19\d{2}|20[0-3]\d)/iu)
28
+ || text.match(/(19\d{2}|20[0-3]\d)\s*(?:г\.?|года|р\.?|рік|йил|жыл|year)(?=$|[^\p{L}\p{N}_])/iu);
29
+ if (!match) return null;
30
+ const year = Number(match[1]);
31
+ return year >= 1900 && year <= new Date().getFullYear() + 3 ? year : null;
32
+ }
33
+
34
+ function parseMinRentTerm(text) {
35
+ const match = text.match(/(?:минимальн\p{L}*\s+(?:срок|термин)\p{L}*(?:\s+аренд\p{L}*)?|минимум|не\s+менее|від\s+|minimum(?:\s+term)?|at\s+least)\s*[:\-]?\s*(\d{1,3})\s*(дн\p{L}*|сут\p{L}*|недел\p{L}*|тижн\p{L}*|месяц\p{L}*|мес\.?|місяц\p{L}*|год\p{L}*|рік|days?|weeks?|months?|years?)/iu);
36
+ if (!match) return null;
37
+ const value = Number(match[1]);
38
+ const unitText = match[2].toLocaleLowerCase();
39
+ const unit = /дн|сут|day/u.test(unitText) ? 'day'
40
+ : /недел|тижн|week/u.test(unitText) ? 'week'
41
+ : /месяц|мес|місяц|month/u.test(unitText) ? 'month'
42
+ : 'year';
43
+ return value > 0 ? deepFreeze({ value, unit }) : null;
44
+ }
45
+
46
+ function parseAvailableFrom(text) {
47
+ const match = text.match(/(?:доступн\p{L}*\s+с|свободн\p{L}*\s+с|заселени\p{L}*\s+с|заезд\s+с|available\s+from|move[- ]?in\s+from)\s*[:\-]?\s*((?:\d{1,2}[./-]\d{1,2}(?:[./-]\d{2,4})?)|(?:\d{1,2}\s+[\p{L}]{3,12}(?:\s+\d{4})?))/iu);
48
+ return match?.[1]?.trim() || null;
49
+ }
50
+
51
+ function parseUtilitiesAmount(text) {
52
+ const match = text.match(/(?:коммунальн\p{L}*|коммуналк\p{L}*|ком\.?\s*услуг\p{L}*|utilities?|bills?)\D{0,24}(?:около|примерно|~|≈)?\s*(\d{2,8}(?:[.,]\d{1,2})?)\s*(₴|грн|uah|\$|usd|€|eur|сум|uzs|тг|kzt|lei|ron|руб|rub)?/iu);
53
+ if (!match) return null;
54
+ const amount = Number(match[1].replace(',', '.'));
55
+ if (!Number.isFinite(amount) || amount <= 0) return null;
56
+ const raw = (match[2] || '').toLocaleLowerCase();
57
+ const currency = /₴|грн|uah/u.test(raw) ? 'UAH'
58
+ : /\$|usd/u.test(raw) ? 'USD'
59
+ : /€|eur/u.test(raw) ? 'EUR'
60
+ : /сум|uzs/u.test(raw) ? 'UZS'
61
+ : /тг|kzt/u.test(raw) ? 'KZT'
62
+ : /lei|ron/u.test(raw) ? 'RON'
63
+ : /руб|rub/u.test(raw) ? 'RUB' : null;
64
+ return deepFreeze({ amount, currency, approximate: /около|примерно|~|≈/iu.test(match[0]) });
65
+ }
66
+
67
+ function parseCommunalSeparated(text, country) {
68
+ if (/(коммунал\p{L}*(?:\s+услуг\p{L}*)?\s*(?:отдельно|сверху|плюс|оплачива\p{L}*\s*отдельно)|свет\s*вода\s*газ\s*отдельно|kommunal\p{L}*\s*(?:alohida|ustiga)|utilities?\s*(?:separate|extra|not included))/iu.test(text)) return true;
69
+ if (/(коммунал\p{L}*(?:\s+услуг\p{L}*)?\s*(?:включ|входит|в\s*стоимост)|вс[её]\s*включ|all\s*inclusive|kommunal\p{L}*\s*(?:kiritilgan|ichida)|комунал(?:каси)?\s+ичида|коммунал(?:каси)?\s+ичида|utilities?\s*included)/iu.test(text)) return false;
70
+ return String(country || '').toUpperCase() === 'UA' ? true : null;
71
+ }
72
+
73
+ function parseDepositRequired(text) {
74
+ if (/(?:без\s+(?:залога|депозита)|залог\s+не\s+нужен|депозит\s+не\s+нужен|no\s+deposit)/iu.test(text)) return false;
75
+ if (/(?:^|[^\p{L}\p{N}_])(?:залог|депозит|deposit|depozit|garantie|garanție|кепіл)(?=$|[^\p{L}\p{N}_])/iu.test(text)) return true;
76
+ return null;
77
+ }
78
+
79
+ export function parseHousingListingFields(value, { country = '' } = {}) {
80
+ const text = normalizeUnicode(value ?? '');
81
+ const context = parseHousingContext(text);
82
+ const features = parseHousingFeatures(text);
83
+ if (!text) return deepFreeze({});
84
+
85
+ const gas = bool(text,
86
+ /(?:^|[^\p{L}\p{N}_])(?:газ|gaz|gas)(?=$|[^\p{L}\p{N}_])|метан|aragaz|gaz\s+ta['’]?min/iu,
87
+ /без\s+газа|нет\s+газа|no\s+gas|gaz\s*yo['’]?q/iu,
88
+ );
89
+ const elevator = bool(text, /лифт|elevator|\blift\b/iu, /без\s*лифт|no\s*elevator|lift\s*yo['’]?q/iu);
90
+ const furnished = context.furniture === 'none' ? false : context.furniture ? true : null;
91
+ const childrenAllowed = context.tenantPolicies.children === 'allowed' ? true
92
+ : context.tenantPolicies.children === 'notAllowed' ? false : null;
93
+ const smokingAllowed = context.tenantPolicies.smoking === 'allowed' ? true
94
+ : context.tenantPolicies.smoking === 'notAllowed' ? false : null;
95
+
96
+ const firstRent = bool(text,
97
+ /первая\s+(?:сдача|аренда)|впервые\s+(?:сда[её]тся|сдается|в\s+аренду)|first\s+(?:rental|letting)|never\s+rented/iu,
98
+ );
99
+
100
+ return deepFreeze({
101
+ bedrooms: parseBedrooms(text),
102
+ bathrooms: parseBathrooms(text),
103
+ buildingYear: parseBuildingYear(text),
104
+ balcony: bool(text, /балкон|лоджи|balkon|ayvon|balcon|loggia|balcony/iu),
105
+ terrace: bool(text, /террас|terrace|teras[ăa]?/iu),
106
+ privateYard: bool(text, /личн\p{L}*\s+двор|сво[йеё]\s+(?:закрыт\p{L}*\s+)?двор|собственн\p{L}*\s+двор|приватн\p{L}*\s+двір|власн\p{L}*\s+двір|private\s+(?:courtyard|yard)|curte\s+(?:proprie|privat[ăa])|shaxsiy\s+hovli/iu),
107
+ courtyard: features.courtyard,
108
+ gazebo: features.gazebo,
109
+ dishwasher: bool(text, /посудомоечн\p{L}*|посудомойк\p{L}*|dishwasher|mașin[ăa]\s+de\s+spălat\s+vase/iu),
110
+ airConditioner: bool(text, /кондицион|сплит[- ]?систем|konditsioner|kansaner|klimat|air\s*con|aer\s+condi[țt]ionat/iu),
111
+ gas,
112
+ 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),
113
+ communalSeparated: parseCommunalSeparated(text, country),
114
+ parking: bool(text, /паркинг|парков|машино[- ]?мест|parking|avtoturargoh|mashina\s*joyi/iu),
115
+ elevator,
116
+ heating: bool(text, /отоплени|heating|otoplenie|isitish|markaziy\s*issiq/iu),
117
+ hotWater: bool(text, /горяч\p{L}*\s*вод|hot\s*water|issiq\s*suv/iu),
118
+ internet: features.internet,
119
+ petsAllowed: features.petsAllowed,
120
+ childrenAllowed,
121
+ smokingAllowed,
122
+ negotiable: context.priceModifiers.includes('fixed') ? false
123
+ : context.priceModifiers.includes('negotiable') ? true : null,
124
+ furnished,
125
+ depositRequired: parseDepositRequired(text),
126
+ firstRent,
127
+ minRentTerm: parseMinRentTerm(text),
128
+ availableFrom: parseAvailableFrom(text),
129
+ utilitiesAmount: parseUtilitiesAmount(text),
130
+ });
131
+ }
@@ -1,7 +1,7 @@
1
- export type HousingPriceParseResult = Readonly<{
2
- price: number | null;
3
- currency: string;
4
- }>;
5
-
6
- export function parseHousingPrice(value: unknown, fallbackCurrency?: string): HousingPriceParseResult;
7
- export const parsePriceFromText: typeof parseHousingPrice;
1
+ export type HousingPriceParseResult = Readonly<{
2
+ price: number | null;
3
+ currency: string;
4
+ }>;
5
+
6
+ export function parseHousingPrice(value: unknown, fallbackCurrency?: string): HousingPriceParseResult;
7
+ export const parsePriceFromText: typeof parseHousingPrice;