@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
package/src/money.js CHANGED
@@ -1,154 +1,154 @@
1
- import { findPhoneLikeSpans } from './contact.js';
2
- import { aliasesOf, findCanonical, normalizeUnicode } from './normalization.js';
3
- import {
4
- CURRENCY_TERMS,
5
- NUMBER_MULTIPLIERS,
6
- SALARY_MODIFIERS,
7
- SALARY_PERIODS,
8
- } from './money-lexicon.js';
9
- import {
10
- MONEY_RANGE_RE,
11
- MONEY_SINGLE_RE,
12
- moneyCurrencyFromText,
13
- parseScaledAmount,
14
- } from './money-core.js';
15
-
16
- export { CURRENCY_TERMS, NUMBER_MULTIPLIERS, SALARY_MODIFIERS, SALARY_PERIODS } from './money-lexicon.js';
17
- export {
18
- MONEY_NUMBER_PATTERN,
19
- MONEY_RANGE_RE,
20
- MONEY_SCALE_PATTERN,
21
- MONEY_SINGLE_RE,
22
- moneyCurrencyFromText,
23
- moneyCurrencyPattern,
24
- moneyScaleMultiplier,
25
- parseNumericAmount,
26
- parseScaledAmount,
27
- } from './money-core.js';
28
-
29
- const CONTACT_MARKER_RE = /(?:телефон|тел\.?|phone|mobile|mob\.?|whatsapp|viber|telegram|контакт|contact|aloqa|murojaat|bog(?:['’ʻʼ‘`])?lanish)\s*[::—-]?\s*$/iu;
30
-
31
- function hasSalaryContext(text) {
32
- return /(?:salary|зарплат|з\s*п\b|оплат|ставк|доход|оклад|компенсац|maosh|oylik|ish\s+haqi|жалақы|айлық|еңбекақы|salariu|оплата)/iu.test(text);
33
- }
34
-
35
- function periodFromText(text) {
36
- return findCanonical(text, SALARY_PERIODS, { partial: true })?.canonical || null;
37
- }
38
-
39
- function hasModifier(text, key) {
40
- return Boolean(findCanonical(text, [SALARY_MODIFIERS[key]], { partial: true }));
41
- }
42
-
43
- function protectedPhoneSpans(text) {
44
- return findPhoneLikeSpans(text).filter((span) => {
45
- const prefix = text.slice(Math.max(0, span.start - 32), span.start);
46
- return CONTACT_MARKER_RE.test(prefix);
47
- });
48
- }
49
-
50
- function overlapsProtectedPhone(start, end, spans) {
51
- return spans.some((span) => start < span.end && end > span.start);
52
- }
53
-
54
- function moneyContextScore(text, start, end, scaled = false) {
55
- const window = text.slice(Math.max(0, start - 36), Math.min(text.length, end + 40));
56
- let score = 0;
57
- if (moneyCurrencyFromText(window)) score += 5;
58
- if (hasSalaryContext(window)) score += 4;
59
- if (periodFromText(window)) score += 2;
60
- if (scaled) score += 1;
61
- return score;
62
- }
63
-
64
- function bestRange(text, protectedSpans) {
65
- const ranges = new RegExp(MONEY_RANGE_RE.source, 'giu');
66
- const candidates = [];
67
- for (const match of text.matchAll(ranges)) {
68
- const start = match.index ?? 0;
69
- const end = start + match[0].length;
70
- if (overlapsProtectedPhone(start, end, protectedSpans)) continue;
71
- const scaled = Boolean(match[2] || match[4]);
72
- const score = moneyContextScore(text, start, end, scaled);
73
- if (score <= 0) continue;
74
- candidates.push({ match, score, start });
75
- }
76
- candidates.sort((a, b) => b.score - a.score || a.start - b.start);
77
- return candidates[0]?.match || null;
78
- }
79
-
80
- export function parseSalary(value) {
81
- const text = normalizeUnicode(value ?? '');
82
- if (!text) return null;
83
-
84
- const currency = moneyCurrencyFromText(text);
85
- const negotiable = hasModifier(text, 'negotiable');
86
- const gross = hasModifier(text, 'gross') ? true : hasModifier(text, 'net') ? false : null;
87
- const period = periodFromText(text);
88
-
89
- const salaryContext = hasSalaryContext(text)
90
- || Boolean(currency)
91
- || NUMBER_MULTIPLIERS.some((entry) => findCanonical(text, [entry], { partial: true }));
92
- if (!salaryContext && !negotiable) return null;
93
-
94
- const protectedSpans = protectedPhoneSpans(text);
95
- const range = bestRange(text, protectedSpans);
96
- let min = null;
97
- let max = null;
98
- const approximate = hasModifier(text, 'approx');
99
-
100
- if (range) {
101
- const firstScale = range[2] || range[4] || null;
102
- const secondScale = range[4] || range[2] || null;
103
- min = parseScaledAmount(range[1], firstScale);
104
- max = parseScaledAmount(range[3], secondScale);
105
- if (min != null && max != null && min > max) [min, max] = [max, min];
106
- } else {
107
- MONEY_SINGLE_RE.lastIndex = 0;
108
- const candidates = [];
109
- for (const match of text.matchAll(MONEY_SINGLE_RE)) {
110
- const start = match.index ?? 0;
111
- const end = start + match[0].length;
112
- if (overlapsProtectedPhone(start, end, protectedSpans)) continue;
113
- const window = text.slice(Math.max(0, start - 24), Math.min(text.length, end + 32));
114
- const scaled = Boolean(match[2]);
115
- const score = moneyContextScore(text, start, end, scaled);
116
- if (score <= 0) continue;
117
- const parsed = parseScaledAmount(match[1], match[2]);
118
- if (parsed == null) continue;
119
- if (!scaled && !moneyCurrencyFromText(window) && parsed >= 1900 && parsed <= 2100 && !hasSalaryContext(window)) continue;
120
- candidates.push({ value: parsed, score, start });
121
- }
122
- candidates.sort((a, b) => b.score - a.score || a.start - b.start);
123
- if (candidates.length) {
124
- const valueAmount = candidates[0].value;
125
- if (hasModifier(text, 'to')) max = valueAmount;
126
- else if (hasModifier(text, 'from') || /\+\s*(?:$|\D)/u.test(text)) min = valueAmount;
127
- else min = max = valueAmount;
128
- }
129
- }
130
-
131
- if (min == null && max == null && !negotiable) return null;
132
- return Object.freeze({
133
- min,
134
- max,
135
- currency,
136
- period,
137
- gross,
138
- negotiable,
139
- approximate,
140
- });
141
- }
142
-
143
- export function salaryCurrency(value) {
144
- return moneyCurrencyFromText(String(value || ''));
145
- }
146
-
147
- export function salaryPeriod(value) {
148
- return periodFromText(String(value || ''));
149
- }
150
-
151
- export function currencyAliases(code) {
152
- const entry = CURRENCY_TERMS.find((item) => item.canonical === code);
153
- return entry ? [entry.canonical, ...aliasesOf(entry)] : [];
154
- }
1
+ import { findPhoneLikeSpans } from './contact.js';
2
+ import { aliasesOf, findCanonical, normalizeUnicode } from './normalization.js';
3
+ import {
4
+ CURRENCY_TERMS,
5
+ NUMBER_MULTIPLIERS,
6
+ SALARY_MODIFIERS,
7
+ SALARY_PERIODS,
8
+ } from './money-lexicon.js';
9
+ import {
10
+ MONEY_RANGE_RE,
11
+ MONEY_SINGLE_RE,
12
+ moneyCurrencyFromText,
13
+ parseScaledAmount,
14
+ } from './money-core.js';
15
+
16
+ export { CURRENCY_TERMS, NUMBER_MULTIPLIERS, SALARY_MODIFIERS, SALARY_PERIODS } from './money-lexicon.js';
17
+ export {
18
+ MONEY_NUMBER_PATTERN,
19
+ MONEY_RANGE_RE,
20
+ MONEY_SCALE_PATTERN,
21
+ MONEY_SINGLE_RE,
22
+ moneyCurrencyFromText,
23
+ moneyCurrencyPattern,
24
+ moneyScaleMultiplier,
25
+ parseNumericAmount,
26
+ parseScaledAmount,
27
+ } from './money-core.js';
28
+
29
+ const CONTACT_MARKER_RE = /(?:телефон|тел\.?|phone|mobile|mob\.?|whatsapp|viber|telegram|контакт|contact|aloqa|murojaat|bog(?:['’ʻʼ‘`])?lanish)\s*[::—-]?\s*$/iu;
30
+
31
+ function hasSalaryContext(text) {
32
+ return /(?:salary|зарплат|з\s*п\b|оплат|ставк|доход|оклад|компенсац|maosh|oylik|ish\s+haqi|жалақы|айлық|еңбекақы|salariu|оплата)/iu.test(text);
33
+ }
34
+
35
+ function periodFromText(text) {
36
+ return findCanonical(text, SALARY_PERIODS, { partial: true })?.canonical || null;
37
+ }
38
+
39
+ function hasModifier(text, key) {
40
+ return Boolean(findCanonical(text, [SALARY_MODIFIERS[key]], { partial: true }));
41
+ }
42
+
43
+ function protectedPhoneSpans(text) {
44
+ return findPhoneLikeSpans(text).filter((span) => {
45
+ const prefix = text.slice(Math.max(0, span.start - 32), span.start);
46
+ return CONTACT_MARKER_RE.test(prefix);
47
+ });
48
+ }
49
+
50
+ function overlapsProtectedPhone(start, end, spans) {
51
+ return spans.some((span) => start < span.end && end > span.start);
52
+ }
53
+
54
+ function moneyContextScore(text, start, end, scaled = false) {
55
+ const window = text.slice(Math.max(0, start - 36), Math.min(text.length, end + 40));
56
+ let score = 0;
57
+ if (moneyCurrencyFromText(window)) score += 5;
58
+ if (hasSalaryContext(window)) score += 4;
59
+ if (periodFromText(window)) score += 2;
60
+ if (scaled) score += 1;
61
+ return score;
62
+ }
63
+
64
+ function bestRange(text, protectedSpans) {
65
+ const ranges = new RegExp(MONEY_RANGE_RE.source, 'giu');
66
+ const candidates = [];
67
+ for (const match of text.matchAll(ranges)) {
68
+ const start = match.index ?? 0;
69
+ const end = start + match[0].length;
70
+ if (overlapsProtectedPhone(start, end, protectedSpans)) continue;
71
+ const scaled = Boolean(match[2] || match[4]);
72
+ const score = moneyContextScore(text, start, end, scaled);
73
+ if (score <= 0) continue;
74
+ candidates.push({ match, score, start });
75
+ }
76
+ candidates.sort((a, b) => b.score - a.score || a.start - b.start);
77
+ return candidates[0]?.match || null;
78
+ }
79
+
80
+ export function parseSalary(value) {
81
+ const text = normalizeUnicode(value ?? '');
82
+ if (!text) return null;
83
+
84
+ const currency = moneyCurrencyFromText(text);
85
+ const negotiable = hasModifier(text, 'negotiable');
86
+ const gross = hasModifier(text, 'gross') ? true : hasModifier(text, 'net') ? false : null;
87
+ const period = periodFromText(text);
88
+
89
+ const salaryContext = hasSalaryContext(text)
90
+ || Boolean(currency)
91
+ || NUMBER_MULTIPLIERS.some((entry) => findCanonical(text, [entry], { partial: true }));
92
+ if (!salaryContext && !negotiable) return null;
93
+
94
+ const protectedSpans = protectedPhoneSpans(text);
95
+ const range = bestRange(text, protectedSpans);
96
+ let min = null;
97
+ let max = null;
98
+ const approximate = hasModifier(text, 'approx');
99
+
100
+ if (range) {
101
+ const firstScale = range[2] || range[4] || null;
102
+ const secondScale = range[4] || range[2] || null;
103
+ min = parseScaledAmount(range[1], firstScale);
104
+ max = parseScaledAmount(range[3], secondScale);
105
+ if (min != null && max != null && min > max) [min, max] = [max, min];
106
+ } else {
107
+ MONEY_SINGLE_RE.lastIndex = 0;
108
+ const candidates = [];
109
+ for (const match of text.matchAll(MONEY_SINGLE_RE)) {
110
+ const start = match.index ?? 0;
111
+ const end = start + match[0].length;
112
+ if (overlapsProtectedPhone(start, end, protectedSpans)) continue;
113
+ const window = text.slice(Math.max(0, start - 24), Math.min(text.length, end + 32));
114
+ const scaled = Boolean(match[2]);
115
+ const score = moneyContextScore(text, start, end, scaled);
116
+ if (score <= 0) continue;
117
+ const parsed = parseScaledAmount(match[1], match[2]);
118
+ if (parsed == null) continue;
119
+ if (!scaled && !moneyCurrencyFromText(window) && parsed >= 1900 && parsed <= 2100 && !hasSalaryContext(window)) continue;
120
+ candidates.push({ value: parsed, score, start });
121
+ }
122
+ candidates.sort((a, b) => b.score - a.score || a.start - b.start);
123
+ if (candidates.length) {
124
+ const valueAmount = candidates[0].value;
125
+ if (hasModifier(text, 'to')) max = valueAmount;
126
+ else if (hasModifier(text, 'from') || /\+\s*(?:$|\D)/u.test(text)) min = valueAmount;
127
+ else min = max = valueAmount;
128
+ }
129
+ }
130
+
131
+ if (min == null && max == null && !negotiable) return null;
132
+ return Object.freeze({
133
+ min,
134
+ max,
135
+ currency,
136
+ period,
137
+ gross,
138
+ negotiable,
139
+ approximate,
140
+ });
141
+ }
142
+
143
+ export function salaryCurrency(value) {
144
+ return moneyCurrencyFromText(String(value || ''));
145
+ }
146
+
147
+ export function salaryPeriod(value) {
148
+ return periodFromText(String(value || ''));
149
+ }
150
+
151
+ export function currencyAliases(code) {
152
+ const entry = CURRENCY_TERMS.find((item) => item.canonical === code);
153
+ return entry ? [entry.canonical, ...aliasesOf(entry)] : [];
154
+ }