@whiteslove/parsing-lexicon 0.2.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.
Files changed (92) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +137 -0
  3. package/index.d.ts +375 -0
  4. package/package.json +75 -0
  5. package/src/central-asia-locations.js +211 -0
  6. package/src/central-asia.js +233 -0
  7. package/src/contact.d.ts +44 -0
  8. package/src/contact.js +159 -0
  9. package/src/countries.js +175 -0
  10. package/src/country-context.d.ts +10 -0
  11. package/src/country-context.js +22 -0
  12. package/src/currency.d.ts +14 -0
  13. package/src/currency.js +66 -0
  14. package/src/geo.js +169 -0
  15. package/src/geography-central-asia.js +42 -0
  16. package/src/geography-detection.d.ts +14 -0
  17. package/src/geography-detection.js +64 -0
  18. package/src/geography-display.d.ts +13 -0
  19. package/src/geography-display.js +113 -0
  20. package/src/geography.js +210 -0
  21. package/src/hiring-advanced.js +147 -0
  22. package/src/hiring-candidate-fields.d.ts +9 -0
  23. package/src/hiring-candidate-fields.js +92 -0
  24. package/src/hiring-context.d.ts +64 -0
  25. package/src/hiring-context.js +412 -0
  26. package/src/hiring-language-extensions.d.ts +19 -0
  27. package/src/hiring-language-extensions.js +139 -0
  28. package/src/hiring-languages.js +135 -0
  29. package/src/hiring-location-fields.d.ts +3 -0
  30. package/src/hiring-location-fields.js +53 -0
  31. package/src/hiring-professions-ro.js +191 -0
  32. package/src/hiring-professions.js +437 -0
  33. package/src/hiring-requirements.d.ts +16 -0
  34. package/src/hiring-requirements.js +170 -0
  35. package/src/hiring-salary-context.d.ts +27 -0
  36. package/src/hiring-salary-context.js +76 -0
  37. package/src/hiring-semantics.d.ts +29 -0
  38. package/src/hiring-semantics.js +218 -0
  39. package/src/hiring-skills.d.ts +21 -0
  40. package/src/hiring-skills.js +315 -0
  41. package/src/hiring-source-aliases.d.ts +32 -0
  42. package/src/hiring-source-aliases.js +186 -0
  43. package/src/hiring-source-semantics.d.ts +31 -0
  44. package/src/hiring-source-semantics.js +293 -0
  45. package/src/hiring-temporal.d.ts +15 -0
  46. package/src/hiring-temporal.js +67 -0
  47. package/src/hiring-vacancy-fields.d.ts +5 -0
  48. package/src/hiring-vacancy-fields.js +23 -0
  49. package/src/hiring-work-semantics.d.ts +34 -0
  50. package/src/hiring-work-semantics.js +72 -0
  51. package/src/hiring.js +180 -0
  52. package/src/housing-address.d.ts +18 -0
  53. package/src/housing-address.js +210 -0
  54. package/src/housing-context.d.ts +36 -0
  55. package/src/housing-context.js +183 -0
  56. package/src/housing-features.js +30 -0
  57. package/src/housing-intent.d.ts +9 -0
  58. package/src/housing-intent.js +132 -0
  59. package/src/housing-listing-fields.js +131 -0
  60. package/src/housing-money.d.ts +7 -0
  61. package/src/housing-money.js +102 -0
  62. package/src/housing-source-aliases.d.ts +8 -0
  63. package/src/housing-source-aliases.js +51 -0
  64. package/src/housing-structured.d.ts +55 -0
  65. package/src/housing-structured.js +219 -0
  66. package/src/housing.js +200 -0
  67. package/src/index.js +44 -0
  68. package/src/kz-location-extensions.js +307 -0
  69. package/src/landmarks.js +21 -0
  70. package/src/lexicon-core.js +58 -0
  71. package/src/location-merge.js +106 -0
  72. package/src/locations.js +456 -0
  73. package/src/money-core.d.ts +10 -0
  74. package/src/money-core.js +81 -0
  75. package/src/money-lexicon.d.ts +5 -0
  76. package/src/money-lexicon.js +79 -0
  77. package/src/money.js +154 -0
  78. package/src/normalization.js +384 -0
  79. package/src/odesa-metropolitan.js +145 -0
  80. package/src/romania-geography.js +47 -0
  81. package/src/tashkent-colloquial.js +72 -0
  82. package/src/tashkent-housing-geography.d.ts +38 -0
  83. package/src/tashkent-housing-geography.js +211 -0
  84. package/src/tashkent-pois.js +215 -0
  85. package/src/tashkent-residential-complexes.js +272 -0
  86. package/src/ua-location-extensions-major.js +118 -0
  87. package/src/ua-location-extensions-metro.js +9 -0
  88. package/src/ua-location-extensions-regional.js +246 -0
  89. package/src/ua-secondary-cities.d.ts +2 -0
  90. package/src/ua-secondary-cities.js +57 -0
  91. package/src/ukraine.js +75 -0
  92. package/src/uz-location-extensions.js +259 -0
@@ -0,0 +1,135 @@
1
+ import { aliasesOf, aliasesToRegex, findCanonical, normalizeUnicode } from './normalization.js';
2
+ import { lexiconEntity } from './lexicon-core.js';
3
+
4
+ const group = (canonical, aliases, extra = {}) => lexiconEntity(canonical, aliases, extra);
5
+
6
+ export const LANGUAGES = Object.freeze([
7
+ group('en', { ru: ['английский', 'английского', 'английским', 'англ', 'англ.'], en: ['english'], uk: ['англійська', 'англійський', 'англійської'], ro: ['engleză', 'engleza', 'limba engleză'], uzLatn: ['ingliz tili', 'inglizcha'], uzCyrl: ['инглиз тили', 'инглизча'], kk: ['ағылшын тілі', 'ағылшынша'] }, { name: 'English' }),
8
+ group('ru', { ru: ['русский', 'русского', 'русским', 'рус.'], en: ['russian'], uk: ['російська', 'російський'], ro: ['rusă', 'rusa', 'limba rusă'], uzLatn: ['rus tili', 'ruscha'], uzCyrl: ['рус тили', 'русча'], kk: ['орыс тілі', 'орысша'] }, { name: 'Russian' }),
9
+ group('uz', { ru: ['узбекский', 'узбекского'], en: ['uzbek'], uk: ['узбецька', 'узбецький'], ro: ['uzbecă', 'uzbeca'], uzLatn: ["o'zbek tili", 'o‘zbek tili', 'ozbek tili', "o'zbekcha", 'o‘zbekcha'], uzCyrl: ['ўзбек тили', 'ўзбекча'], kk: ['өзбек тілі', 'өзбекше'] }, { name: 'Uzbek' }),
10
+ group('kk', { ru: ['казахский', 'казахского'], en: ['kazakh'], uk: ['казахська', 'казахський'], ro: ['kazahă', 'kazaha'], uzLatn: ['qozoq tili', 'qozoqcha'], uzCyrl: ['қозоқ тили', 'қозоқча'], kk: ['қазақ тілі', 'қазақша'] }, { name: 'Kazakh' }),
11
+ group('uk', { ru: ['украинский', 'украинского'], en: ['ukrainian'], uk: ['українська', 'український', 'українською'], ro: ['ucraineană', 'ucraineana'], uzLatn: ['ukrain tili'], uzCyrl: ['украин тили'], kk: ['украин тілі'] }, { name: 'Ukrainian' }),
12
+ group('tr', { ru: ['турецкий', 'турецкого'], en: ['turkish'], uk: ['турецька', 'турецький'], ro: ['turcă', 'turca'], uzLatn: ['turk tili', 'turkcha'], uzCyrl: ['турк тили'], kk: ['түрік тілі', 'түрікше'] }, { name: 'Turkish' }),
13
+ group('de', { ru: ['немецкий', 'немецкого'], en: ['german', 'deutsch'], uk: ['німецька', 'німецький'], ro: ['germană', 'germana'], uzLatn: ['nemis tili'], uzCyrl: ['немис тили'], kk: ['неміс тілі'] }, { name: 'German' }),
14
+ group('fr', { ru: ['французский', 'французского'], en: ['french', 'français', 'francais'], uk: ['французька', 'французький'], ro: ['franceză', 'franceza'], uzLatn: ['fransuz tili'], uzCyrl: ['француз тили'], kk: ['француз тілі'] }, { name: 'French' }),
15
+ group('es', { ru: ['испанский', 'испанского'], en: ['spanish', 'español', 'espanol'], uk: ['іспанська', 'іспанський'], ro: ['spaniolă', 'spaniola'], uzLatn: ['ispan tili'], uzCyrl: ['испан тили'], kk: ['испан тілі'] }, { name: 'Spanish' }),
16
+ group('zh', { ru: ['китайский', 'китайского', 'мандарин'], en: ['chinese', 'mandarin'], uk: ['китайська', 'китайський'], ro: ['chineză', 'chineza', 'mandarină'], uzLatn: ['xitoy tili', 'xitoycha'], uzCyrl: ['хитой тили'], kk: ['қытай тілі', 'қытайша'] }, { name: 'Chinese' }),
17
+ group('ko', { ru: ['корейский', 'корейского'], en: ['korean'], uk: ['корейська', 'корейський'], ro: ['coreeană', 'coreeana'], uzLatn: ['koreys tili'], uzCyrl: ['корейс тили'], kk: ['корей тілі'] }, { name: 'Korean' }),
18
+ group('ja', { ru: ['японский', 'японского'], en: ['japanese'], uk: ['японська', 'японський'], ro: ['japoneză', 'japoneza'], uzLatn: ['yapon tili'], uzCyrl: ['япон тили'], kk: ['жапон тілі'] }, { name: 'Japanese' }),
19
+ group('ar', { ru: ['арабский', 'арабского'], en: ['arabic'], uk: ['арабська', 'арабський'], ro: ['arabă', 'araba'], uzLatn: ['arab tili'], uzCyrl: ['араб тили'], kk: ['араб тілі'] }, { name: 'Arabic' }),
20
+ group('tg', { ru: ['таджикский', 'таджикского'], en: ['tajik'], uk: ['таджицька'], ro: ['tadjică', 'tadjica'], uzLatn: ['tojik tili', 'tojikcha'], uzCyrl: ['тожик тили'], kk: ['тәжік тілі'] }, { name: 'Tajik' }),
21
+ group('ky', { ru: ['кыргызский', 'киргизский', 'кыргызского'], en: ['kyrgyz', 'kirghiz'], uk: ['киргизька'], ro: ['kârgâză', 'kargaza'], uzLatn: ['qirgiz tili', "qirg'iz tili"], uzCyrl: ['қирғиз тили'], kk: ['қырғыз тілі', 'қырғызша'] }, { name: 'Kyrgyz' }),
22
+ group('ro', { ru: ['румынский', 'румынского'], en: ['romanian'], uk: ['румунська'], ro: ['română', 'romana', 'limba română'], uzLatn: ['rumin tili'], uzCyrl: ['румин тили'], kk: ['румын тілі'] }, { name: 'Romanian' }),
23
+ group('pl', { ru: ['польский', 'польского'], en: ['polish'], uk: ['польська'], ro: ['poloneză', 'poloneza'], uzLatn: ['polyak tili'], uzCyrl: ['поляк тили'], kk: ['поляк тілі'] }, { name: 'Polish' }),
24
+ ]);
25
+
26
+ export const LANGUAGE_LEVELS = Object.freeze([
27
+ group('basic', { ru: ['базовый', 'начальный', 'со словарём', 'со словарем'], en: ['basic', 'beginner'], uk: ['базовий', 'початковий'], ro: ['de bază', 'de baza', 'începător', 'incepator'], uzLatn: ['boshlangich', "boshlang'ich", 'asosiy'], uzCyrl: ['бошланғич', 'асосий'], kk: ['бастапқы', 'негізгі'] }),
28
+ group('elementary', { ru: ['элементарный'], en: ['elementary'], uk: ['елементарний'], ro: ['elementar'], uzLatn: ['elementar'], uzCyrl: ['элементар'], kk: ['элементарлық'] }),
29
+ group('preIntermediate', { ru: ['ниже среднего', 'pre-intermediate'], en: ['pre-intermediate', 'pre intermediate'], uk: ['нижче середнього'], ro: ['pre-intermediar'], uzLatn: ['pre-intermediate'], uzCyrl: ['pre-intermediate'], kk: ['pre-intermediate'] }),
30
+ group('intermediate', { ru: ['средний', 'разговорный', 'разговорный уровень'], en: ['intermediate', 'conversational', 'working knowledge'], uk: ['середній', 'розмовний'], ro: ['intermediar', 'conversațional', 'conversational'], uzLatn: ['orta', "o'rta", 'suhbat darajasi'], uzCyrl: ['ўрта', 'суҳбат даражаси'], kk: ['орта', 'сөйлесу деңгейі'] }),
31
+ group('upperIntermediate', { ru: ['выше среднего', 'upper-intermediate'], en: ['upper-intermediate', 'upper intermediate'], uk: ['вище середнього'], ro: ['upper-intermediate'], uzLatn: ['upper-intermediate'], uzCyrl: ['upper-intermediate'], kk: ['upper-intermediate'] }),
32
+ group('advanced', { ru: ['продвинутый', 'уверенный'], en: ['advanced', 'proficient'], uk: ['просунутий', 'впевнений'], ro: ['avansat', 'competent'], uzLatn: ['yuqori daraja', 'ishonchli'], uzCyrl: ['юқори даража'], kk: ['жоғары деңгей', 'сенімді'] }),
33
+ group('fluent', { ru: ['свободный', 'свободное владение', 'в совершенстве'], en: ['fluent', 'fluency', 'full professional proficiency'], uk: ['вільний', 'вільне володіння', 'досконало'], ro: ['fluent', 'fluență', 'fluenta'], uzLatn: ['erkin', 'erkin gaplashish'], uzCyrl: ['эркин', 'эркин гаплашиш'], kk: ['еркін', 'еркін сөйлейді'] }),
34
+ group('native', { ru: ['родной', 'носитель языка'], en: ['native', 'native speaker', 'mother tongue'], uk: ['рідна', 'носій мови'], ro: ['nativ', 'vorbitor nativ', 'limbă maternă'], uzLatn: ['ona tili', 'native speaker'], uzCyrl: ['она тили'], kk: ['ана тілі', 'тіл иесі'] }),
35
+ ]);
36
+
37
+ export const LANGUAGE_CONTEXT_TERMS = Object.freeze({
38
+ required: group('required', {
39
+ ru: ['требуется знание языка', 'знание языка обязательно', 'обязательное знание', 'необходимо знание', 'необходим язык', 'требуется владение', 'свободное владение обязательно', 'со знанием английского', 'английский обязателен'],
40
+ en: ['language required', 'english required', 'must speak', 'must know', 'fluency required', 'proficiency required', 'required language'],
41
+ uk: ['знання мови обов’язкове', "знання мови обов'язкове", 'потрібне знання мови', 'необхідне знання'],
42
+ ro: ['limba este obligatorie', 'cunoașterea limbii este obligatorie', 'trebuie să vorbească'],
43
+ uzLatn: ['til bilish talab qilinadi', 'tilni bilish shart', 'ingliz tilini bilish kerak', 'tilni yaxshi bilishi kerak'],
44
+ uzCyrl: ['тил билиш талаб қилинади', 'тилни билиш шарт', 'инглиз тилини билиш керак'],
45
+ kk: ['тіл білу талап етіледі', 'тілді білу міндетті', 'ағылшын тілін білу қажет'],
46
+ }),
47
+ preferred: group('preferred', {
48
+ ru: ['желательно знание', 'знание будет плюсом', 'будет преимуществом', 'приветствуется знание'],
49
+ en: ['preferred', 'nice to have', 'language is a plus', 'would be an advantage'],
50
+ uk: ['бажане знання', 'знання буде плюсом', 'буде перевагою'],
51
+ ro: ['cunoașterea constituie un avantaj', 'de preferat să cunoască'],
52
+ uzLatn: ['til bilsa yaxshi', 'til bilishi afzal', 'til plus boladi'],
53
+ uzCyrl: ['тил билса яхши', 'тил билиши афзал'],
54
+ kk: ['тіл білу артықшылық болады', 'тіл білгені дұрыс'],
55
+ }),
56
+ notRequired: group('notRequired', {
57
+ ru: ['язык не требуется', 'знание языка не обязательно', 'без обязательного знания', 'язык не нужен'],
58
+ en: ['language not required', 'english not required', 'language optional', 'no language requirement'],
59
+ uk: ['мова не потрібна', 'знання мови не обов’язкове'],
60
+ ro: ['limba nu este necesară', 'limba nu este obligatorie'],
61
+ uzLatn: ['til shart emas', 'til bilish talab qilinmaydi'],
62
+ uzCyrl: ['тил шарт эмас', 'тил билиш талаб қилинмайди'],
63
+ kk: ['тіл міндетті емес', 'тіл білу талап етілмейді'],
64
+ }),
65
+ candidateHas: group('candidateHas', {
66
+ ru: ['владею языками', 'знаю английский', 'владею английским', 'свободно говорю', 'разговорный английский', 'английский на уровне'],
67
+ en: ['i speak', 'i know', 'fluent in', 'proficient in', 'native speaker', 'working proficiency'],
68
+ uk: ['володію мовами', 'знаю англійську', 'вільно говорю'],
69
+ ro: ['vorbesc', 'cunosc limba', 'fluent în', 'fluent in'],
70
+ uzLatn: ['tillarni bilaman', 'ingliz tilini bilaman', 'erkin gaplashaman'],
71
+ uzCyrl: ['тилларни биламан', 'инглиз тилини биламан', 'эркин гаплашаман'],
72
+ kk: ['тілдерді білемін', 'ағылшын тілін білемін', 'еркін сөйлеймін'],
73
+ }),
74
+ });
75
+
76
+ const CEFR_RE = /(?:^|[^A-Za-z0-9])(A1|A2|B1|B2|C1|C2)(?![A-Za-z0-9])/i;
77
+ const NEGATION_RE = /(?:не\s+(?:требуется|обязател|нужен|нужна)|без\s+обязатель|not\s+required|not\s+mandatory|optional|nu\s+(?:este\s+)?(?:necesar|obligatoriu)|shart\s+emas|talab\s+qilinmaydi|міндетті\s+емес|талап\s+етілмейді)/iu;
78
+ const REQUIRED_RE = /(?:обязател|требуется|необходим|required|must\s+(?:speak|know)|mandatory|потріб|необхід|obligatoriu|trebuie|shart|talab\s+qilinadi|міндетті|талап\s+етіледі|қажет)/iu;
79
+ const PREFERRED_RE = /(?:желатель|плюс|приветств|preferred|nice\s+to\s+have|advantage|бажан|переваг|avantaj|afzal|bolsa\s+yaxshi|артықшылық)/iu;
80
+ const CANDIDATE_RE = /(?:владею|знаю|говорю|мой\s+уровень|i\s+(?:speak|know)|fluent\s+in|proficient\s+in|володію|vorbesc|bilaman|gaplashaman|білемін|сөйлеймін)/iu;
81
+
82
+ function relationFromWindow(window, mode) {
83
+ if (NEGATION_RE.test(window)) return 'notRequired';
84
+ if (PREFERRED_RE.test(window)) return 'preferred';
85
+ if (REQUIRED_RE.test(window)) return 'required';
86
+ if (CANDIDATE_RE.test(window)) return 'candidateHas';
87
+ return mode === 'candidate' ? 'candidateHas' : mode === 'vacancy' ? 'mentioned' : 'mentioned';
88
+ }
89
+
90
+ function levelFromWindow(window) {
91
+ const cefr = window.match(CEFR_RE)?.[1]?.toUpperCase() || null;
92
+ if (cefr) return { level: null, cefr };
93
+ const qualitative = findCanonical(window, LANGUAGE_LEVELS, { partial: true });
94
+ return { level: qualitative?.canonical || null, cefr: null };
95
+ }
96
+
97
+ export function languageByCode(code) {
98
+ return LANGUAGES.find((entry) => entry.canonical === String(code || '').toLowerCase()) || null;
99
+ }
100
+
101
+ export function parseLanguageMentions(value, { mode = null } = {}) {
102
+ const text = normalizeUnicode(value || '');
103
+ if (!text) return [];
104
+ const results = [];
105
+ for (const language of LANGUAGES) {
106
+ const re = aliasesToRegex([language.canonical, language.name, ...aliasesOf(language)], 'giu');
107
+ for (const match of text.matchAll(re)) {
108
+ const index = match.index ?? 0;
109
+ const raw = match[0].trim();
110
+ const start = Math.max(0, index - 90);
111
+ const end = Math.min(text.length, index + match[0].length + 90);
112
+ const window = text.slice(start, end);
113
+ const levels = levelFromWindow(window);
114
+ results.push(Object.freeze({
115
+ language: language.canonical,
116
+ name: language.name,
117
+ relation: relationFromWindow(window, mode),
118
+ level: levels.level,
119
+ cefr: levels.cefr,
120
+ matched: raw,
121
+ index,
122
+ }));
123
+ }
124
+ }
125
+ results.sort((a, b) => a.index - b.index);
126
+ const deduped = [];
127
+ const seen = new Set();
128
+ for (const result of results) {
129
+ const key = `${result.language}:${result.relation}:${result.cefr || result.level || ''}`;
130
+ if (seen.has(key)) continue;
131
+ seen.add(key);
132
+ deduped.push(result);
133
+ }
134
+ return deduped;
135
+ }
@@ -0,0 +1,3 @@
1
+ export function detectHiringLocationName(value: unknown, country: unknown): string | null;
2
+ export function isHiringNonCityLocation(value: unknown): boolean;
3
+ export function isHiringRemoteLocationScope(value: unknown): boolean;
@@ -0,0 +1,53 @@
1
+ import { detectCityFromText } from './geography-detection.js';
2
+
3
+ const B = '(?<![\\p{L}\\p{N}])';
4
+ const E = '(?![\\p{L}\\p{N}])';
5
+ const re = (pattern) => new RegExp(`${B}(?:${pattern})(?:\\p{Script=Cyrillic}{1,3})?${E}`, 'iu');
6
+
7
+ const NON_CITY_LOCATION_RE = /^(?:europe|europa|європа|европа|штати|states|worldwide|global|anywhere|emea|apac)$/iu;
8
+ const REMOTE_LOCATION_SCOPE_RE = /^(?:worldwide|global|work\s+from\s+anywhere|anywhere(?:\s+in\b.*)?)$/iu;
9
+
10
+ export function isHiringNonCityLocation(value) {
11
+ return NON_CITY_LOCATION_RE.test(String(value || '').trim());
12
+ }
13
+
14
+ export function isHiringRemoteLocationScope(value) {
15
+ return REMOTE_LOCATION_SCOPE_RE.test(String(value || '').trim());
16
+ }
17
+
18
+ // Public Uzbek CV boards sometimes expose only a region. Keep the historic
19
+ // Personal-Site canonical names as a stable consumer contract while matching
20
+ // multilingual source spellings centrally.
21
+ const UZ_HIRING_REGIONS = Object.freeze([
22
+ ['Tashkent Region', re('ташкент(?:ская)?\\s+(?:обл(?:асть)?\\.?|region)|toshkent\\s+viloyati|tashkent\\s+region')],
23
+ ['Karakalpakstan', re("каракалпакстан|qoraqalpog(?:'|’)iston|karakalpakstan")],
24
+ ['Kashkadarya', re('кашкадар\\p{L}*|qashqadaryo|kashkadarya')],
25
+ ['Surkhandarya', re('сурхандар\\p{L}*|surxondaryo|surkhandarya')],
26
+ ['Jizzakh', re('джизак\\p{L}*|jizzax|jizzakh')],
27
+ ['Syrdarya', re('сырдар\\p{L}*|sirdaryo|syrdarya')],
28
+ ['Khorezm', re('хорезм\\p{L}*|xorazm|khorezm')],
29
+ ]);
30
+
31
+ // Keep legacy consumer canonicals and inflected aliases where the broader
32
+ // geography catalog intentionally uses a different modern spelling.
33
+ const HIRING_CITY_OVERRIDES = Object.freeze({
34
+ UZ: Object.freeze([
35
+ ['Navoi', re('навои|navoi')],
36
+ ]),
37
+ UA: Object.freeze([
38
+ ['Odesa', re('одесс|одес|odesa|odessa')],
39
+ ]),
40
+ });
41
+
42
+ export function detectHiringLocationName(value, country) {
43
+ const text = String(value || '');
44
+ const code = String(country || '').toUpperCase();
45
+ if (!text || !code) return null;
46
+ if (code === 'UZ') {
47
+ const region = UZ_HIRING_REGIONS.find(([, matcher]) => matcher.test(text));
48
+ if (region) return region[0];
49
+ }
50
+ const override = HIRING_CITY_OVERRIDES[code]?.find(([, matcher]) => matcher.test(text));
51
+ if (override) return override[0];
52
+ return detectCityFromText(text, code)?.canonical || null;
53
+ }
@@ -0,0 +1,191 @@
1
+ export const ROMANIAN_GROUP_ALIASES = Object.freeze({
2
+ software_development: ['dezvoltare software', 'programare', 'dezvoltare aplicații', 'dezvoltare aplicatii'],
3
+ quality_assurance: ['asigurarea calității', 'asigurarea calitatii', 'testare software'],
4
+ infrastructure: ['infrastructură', 'infrastructura', 'administrare sisteme'],
5
+ data: ['date', 'analiză de date', 'analiza de date', 'analitică', 'analitica'],
6
+ information_security: ['securitatea informației', 'securitatea informatiei', 'securitate cibernetică', 'securitate cibernetica'],
7
+ design: ['design', 'proiectare'],
8
+ product: ['produs', 'management de produs'],
9
+ project_management: ['management de proiect', 'management proiecte'],
10
+ sales: ['vânzări', 'vanzari'],
11
+ marketing: ['marketing'],
12
+ finance: ['finanțe', 'finante', 'contabilitate'],
13
+ human_resources: ['resurse umane', 'recrutare'],
14
+ customer_support: ['suport clienți', 'suport clienti', 'relații clienți', 'relatii clienti'],
15
+ administration: ['administrație', 'administratie', 'birou'],
16
+ logistics: ['logistică', 'logistica', 'depozit'],
17
+ retail: ['retail', 'comerț cu amănuntul', 'comert cu amanuntul'],
18
+ hospitality: ['ospitalitate', 'horeca', 'restaurant', 'hotel'],
19
+ medicine: ['medicină', 'medicina', 'sănătate', 'sanatate'],
20
+ education: ['educație', 'educatie', 'învățământ', 'invatamant'],
21
+ construction: ['construcții', 'constructii'],
22
+ manufacturing: ['producție', 'productie', 'industrie'],
23
+ transport: ['transport', 'șoferi', 'soferi'],
24
+ security: ['securitate', 'pază', 'paza'],
25
+ legal: ['juridic', 'drept'],
26
+ media_content: ['media', 'conținut', 'continut'],
27
+ beauty_wellness: ['frumusețe', 'frumusete', 'wellness'],
28
+ agriculture: ['agricultură', 'agricultura'],
29
+ });
30
+
31
+ export const ROMANIAN_PROFESSION_ALIASES = Object.freeze({
32
+ software_developer: ['dezvoltator software', 'inginer software', 'programator'],
33
+ frontend_developer: ['dezvoltator frontend', 'programator frontend', 'inginer frontend'],
34
+ backend_developer: ['dezvoltator backend', 'programator backend', 'inginer backend'],
35
+ fullstack_developer: ['dezvoltator full stack', 'dezvoltator fullstack', 'programator full stack'],
36
+ mobile_developer: ['dezvoltator aplicații mobile', 'dezvoltator aplicatii mobile', 'programator mobile'],
37
+ ios_developer: ['dezvoltator ios', 'programator ios'],
38
+ android_developer: ['dezvoltator android', 'programator android'],
39
+ flutter_developer: ['dezvoltator flutter'],
40
+ react_native_developer: ['dezvoltator react native'],
41
+ game_developer: ['dezvoltator jocuri', 'programator jocuri'],
42
+ embedded_developer: ['dezvoltator embedded', 'inginer sisteme embedded'],
43
+ solutions_architect: ['arhitect soluții', 'arhitect solutii'],
44
+ software_architect: ['arhitect software'],
45
+ integration_engineer: ['inginer integrare', 'inginer de integrare'],
46
+ qa_engineer: ['inginer qa', 'inginer testare', 'tester software'],
47
+ manual_qa_engineer: ['tester manual', 'inginer testare manuală', 'inginer testare manuala'],
48
+ qa_automation_engineer: ['inginer automatizare teste', 'tester automatizare'],
49
+ qa_lead: ['lider qa', 'coordonator qa'],
50
+ devops_engineer: ['inginer devops'],
51
+ sre_engineer: ['inginer sre', 'inginer fiabilitate site'],
52
+ cloud_engineer: ['inginer cloud'],
53
+ system_administrator: ['administrator de sistem', 'administrator sisteme'],
54
+ network_engineer: ['inginer rețea', 'inginer retea', 'administrator rețea', 'administrator retea'],
55
+ database_administrator: ['administrator baze de date', 'administrator bază de date'],
56
+ data_analyst: ['analist de date', 'analist date'],
57
+ business_analyst: ['analist de business', 'analist business'],
58
+ system_analyst: ['analist de sistem', 'analist sistem'],
59
+ data_engineer: ['inginer de date', 'inginer date'],
60
+ data_scientist: ['cercetător de date', 'cercetator de date', 'specialist data science'],
61
+ ml_engineer: ['inginer machine learning', 'inginer învățare automată', 'inginer invatare automata'],
62
+ ai_engineer: ['inginer inteligență artificială', 'inginer inteligenta artificiala'],
63
+ bi_analyst: ['analist bi', 'analist business intelligence'],
64
+ security_engineer: ['inginer securitate', 'inginer securitate cibernetică', 'inginer securitate cibernetica'],
65
+ security_analyst: ['analist securitate', 'analist securitate cibernetică', 'analist soc'],
66
+ penetration_tester: ['tester de penetrare', 'specialist testare penetrare'],
67
+ product_designer: ['designer de produs', 'designer produs'],
68
+ ui_designer: ['designer ui', 'designer interfață', 'designer interfata'],
69
+ ux_designer: ['designer ux', 'designer experiență utilizator', 'designer experienta utilizator'],
70
+ ui_ux_designer: ['designer ui ux', 'designer ui/ux'],
71
+ graphic_designer: ['designer grafic'],
72
+ web_designer: ['designer web'],
73
+ interior_designer: ['designer de interior', 'designer interior'],
74
+ product_manager: ['manager de produs', 'manager produs'],
75
+ product_owner: ['proprietar de produs', 'product owner'],
76
+ product_analyst: ['analist de produs', 'analist produs'],
77
+ project_manager: ['manager de proiect', 'manager proiect'],
78
+ program_manager: ['manager de program', 'manager program'],
79
+ project_coordinator: ['coordonator de proiect', 'coordonator proiect'],
80
+ sales_manager: ['manager de vânzări', 'manager vanzari'],
81
+ sales_representative: ['reprezentant de vânzări', 'reprezentant vanzari', 'agent de vânzări', 'agent vanzari'],
82
+ account_manager: ['manager de cont', 'account manager', 'manager clienți', 'manager clienti'],
83
+ key_account_manager: ['manager clienți cheie', 'manager clienti cheie'],
84
+ business_development_manager: ['manager dezvoltare afaceri', 'manager de dezvoltare a afacerii'],
85
+ sales_director: ['director de vânzări', 'director vanzari'],
86
+ real_estate_agent: ['agent imobiliar', 'consultant imobiliar'],
87
+ insurance_agent: ['agent de asigurări', 'agent de asigurari'],
88
+ marketing_manager: ['manager marketing'],
89
+ digital_marketing_manager: ['manager marketing digital'],
90
+ social_media_manager: ['manager social media', 'administrator rețele sociale', 'administrator retele sociale'],
91
+ seo_specialist: ['specialist seo'],
92
+ content_marketing_manager: ['manager marketing de conținut', 'manager marketing continut'],
93
+ accountant: ['contabil'],
94
+ chief_accountant: ['contabil șef', 'contabil sef'],
95
+ financial_analyst: ['analist financiar'],
96
+ finance_manager: ['manager financiar'],
97
+ controller: ['controlor financiar', 'controller financiar'],
98
+ hr_manager: ['manager resurse umane', 'manager hr'],
99
+ recruiter: ['recrutor', 'specialist recrutare'],
100
+ talent_acquisition_specialist: ['specialist achiziție talente', 'specialist achizitie talente'],
101
+ hr_business_partner: ['partener de afaceri hr', 'hr business partner'],
102
+ customer_support_specialist: ['specialist suport clienți', 'specialist suport clienti'],
103
+ call_center_operator: ['operator call center', 'operator centru de apel'],
104
+ customer_success_manager: ['manager succes clienți', 'manager succes clienti'],
105
+ office_manager: ['manager de birou', 'office manager'],
106
+ administrative_assistant: ['asistent administrativ'],
107
+ executive_assistant: ['asistent executiv'],
108
+ secretary: ['secretar', 'secretară', 'secretara'],
109
+ logistics_manager: ['manager logistică', 'manager logistica'],
110
+ logistics_specialist: ['specialist logistică', 'specialist logistica'],
111
+ warehouse_worker: ['lucrător depozit', 'lucrator depozit', 'muncitor depozit'],
112
+ warehouse_manager: ['manager depozit', 'șef depozit', 'sef depozit'],
113
+ store_manager: ['manager magazin', 'șef magazin', 'sef magazin'],
114
+ cashier: ['casier', 'casieră', 'casiera'],
115
+ sales_assistant: ['consultant vânzări', 'consultant vanzari', 'asistent vânzări', 'asistent vanzari'],
116
+ merchandiser: ['merchandiser', 'mercantizor'],
117
+ waiter: ['ospătar', 'ospatar'],
118
+ bartender: ['barman'],
119
+ barista: ['barista'],
120
+ cook: ['bucătar', 'bucatar'],
121
+ chef: ['bucătar șef', 'bucatar sef', 'chef bucătar'],
122
+ receptionist: ['recepționer', 'receptioner', 'recepționeră', 'receptionera'],
123
+ hotel_manager: ['manager hotel'],
124
+ doctor: ['medic', 'doctor'],
125
+ nurse: ['asistent medical', 'asistentă medicală', 'asistenta medicala'],
126
+ pharmacist: ['farmacist', 'farmacistă', 'farmacista'],
127
+ dentist: ['medic dentist', 'stomatolog'],
128
+ psychologist: ['psiholog'],
129
+ teacher: ['profesor', 'profesoară', 'profesoara'],
130
+ tutor: ['meditator', 'tutore'],
131
+ kindergarten_teacher: ['educatoare', 'educator grădiniță', 'educator gradinita'],
132
+ civil_engineer: ['inginer constructor', 'inginer civil'],
133
+ architect: ['arhitect'],
134
+ construction_manager: ['manager construcții', 'manager constructii'],
135
+ site_manager: ['șef de șantier', 'sef de santier'],
136
+ electrician: ['electrician'],
137
+ plumber: ['instalator'],
138
+ welder: ['sudor'],
139
+ carpenter: ['tâmplar', 'tamplar'],
140
+ painter: ['zugrav', 'vopsitor'],
141
+ crane_operator: ['macaragiu', 'operator macara'],
142
+ production_worker: ['muncitor producție', 'muncitor productie', 'operator producție'],
143
+ machine_operator: ['operator utilaj', 'operator mașină', 'operator masina'],
144
+ cnc_operator: ['operator cnc'],
145
+ mechanical_engineer: ['inginer mecanic'],
146
+ electrical_engineer: ['inginer electrician', 'inginer electric'],
147
+ maintenance_technician: ['tehnician mentenanță', 'tehnician mentenanta'],
148
+ driver: ['șofer', 'sofer'],
149
+ truck_driver: ['șofer camion', 'sofer camion', 'șofer tir', 'sofer tir'],
150
+ taxi_driver: ['șofer taxi', 'sofer taxi', 'taximetrist'],
151
+ bus_driver: ['șofer autobuz', 'sofer autobuz'],
152
+ courier: ['curier'],
153
+ forklift_operator: ['stivuitorist', 'operator stivuitor'],
154
+ auto_mechanic: ['mecanic auto'],
155
+ security_guard: ['agent de securitate', 'paznic', 'agent pază', 'agent paza'],
156
+ lawyer: ['avocat', 'jurist'],
157
+ legal_counsel: ['consilier juridic'],
158
+ paralegal: ['asistent juridic'],
159
+ compliance_specialist: ['specialist conformitate', 'ofițer conformitate', 'ofiter conformitate'],
160
+ copywriter: ['copywriter', 'redactor publicitar'],
161
+ content_manager: ['manager de conținut', 'manager continut'],
162
+ editor: ['redactor', 'editor'],
163
+ journalist: ['jurnalist'],
164
+ translator: ['traducător', 'traducator', 'interpret'],
165
+ video_editor: ['editor video', 'montaj video'],
166
+ photographer: ['fotograf'],
167
+ hairdresser: ['coafor', 'coafeză', 'coafeza'],
168
+ barber: ['frizer', 'barber'],
169
+ manicurist: ['manichiuristă', 'manichiurista', 'tehnician unghii'],
170
+ cosmetologist: ['cosmetician', 'cosmeticiană'],
171
+ massage_therapist: ['maseur', 'maseuză', 'maseuza'],
172
+ fitness_trainer: ['antrenor fitness', 'antrenor personal'],
173
+ farmer: ['fermier'],
174
+ agronomist: ['agronom'],
175
+ tractor_driver: ['tractorist'],
176
+ farm_worker: ['muncitor agricol', 'lucrător agricol', 'lucrator agricol'],
177
+ });
178
+
179
+ export const ROMANIAN_SENIORITY_ALIASES = Object.freeze({
180
+ intern: ['stagiar', 'internship', 'practicant'],
181
+ junior: ['junior', 'începător', 'incepator'],
182
+ middle: ['nivel mediu', 'intermediar', 'mid-level'],
183
+ senior: ['senior', 'specialist senior', 'experimentat'],
184
+ staff: ['staff engineer', 'inginer staff'],
185
+ principal: ['principal', 'inginer principal'],
186
+ lead: ['lead', 'lider tehnic', 'lider de echipă', 'lider de echipa'],
187
+ head: ['șef departament', 'sef departament', 'conducător departament', 'conducator departament'],
188
+ director: ['director'],
189
+ vp: ['vicepreședinte', 'vicepresedinte'],
190
+ chief: ['director executiv', 'director tehnic', 'director financiar', 'director operațional', 'director operational'],
191
+ });