@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
package/src/hiring.js ADDED
@@ -0,0 +1,180 @@
1
+ import { lexiconEntity } from './lexicon-core.js';
2
+ const group = (canonical, aliases, extra = {}) => lexiconEntity(canonical, aliases, extra);
3
+
4
+ export const HIRING_INTENT = Object.freeze({
5
+ candidate: group('candidate', {
6
+ ru: ['ищу работу', 'ищу вакансию', 'резюме', 'рассматриваю работу', 'нужна работа'],
7
+ en: ['looking for a job', 'seeking work', 'resume', 'cv', 'open to work'],
8
+ uk: ['шукаю роботу', 'шукаю вакансію', 'резюме', 'розглядаю роботу', 'потрібна робота'],
9
+ ro: ['caut de lucru', 'caut un loc de muncă', 'caut un loc de munca', 'cv', 'curriculum vitae', 'în căutarea unui loc de muncă', 'in cautarea unui loc de munca'],
10
+ uzLatn: ['ish qidiryapman', 'ish qidiraman', 'menga ish kerak', 'ish joyi kerak', 'rezyume', 'ishga kirmoqchiman'],
11
+ uzCyrl: ['иш қидиряпман', 'иш қидираман', 'менга иш керак', 'иш жойи керак', 'резюме', 'ишга кирмоқчиман'],
12
+ kk: ['жұмыс іздеймін', 'жұмыс керек', 'түйіндеме', 'жұмыс қарастырып жүрмін', 'жұмысқа орналасқым келеді'],
13
+ }),
14
+ employer: group('employer', {
15
+ ru: ['ищем сотрудника', 'требуется сотрудник', 'требуется', 'вакансия', 'набираем', 'ищем специалиста'],
16
+ en: ['hiring', 'vacancy', 'job opening', 'we are looking for', 'seeking a candidate'],
17
+ uk: ['шукаємо працівника', 'потрібен працівник', 'потрібна', 'вакансія', 'набираємо', 'шукаємо спеціаліста'],
18
+ ro: ['angajăm', 'angajam', 'post vacant', 'loc de muncă vacant', 'loc de munca vacant', 'căutăm coleg', 'cautam coleg', 'căutăm candidat'],
19
+ uzLatn: ['xodim kerak', 'ishchi kerak', 'ishga taklif qilamiz', 'vakansiya', 'mutaxassis kerak'],
20
+ uzCyrl: ['ходим керак', 'ишчи керак', 'ишга таклиф қиламиз', 'вакансия', 'мутахассис керак'],
21
+ kk: ['қызметкер керек', 'жұмысшы керек', 'бос жұмыс орны', 'вакансия', 'маман қажет'],
22
+ }),
23
+ });
24
+
25
+ export const CANDIDATE_FIELD_TERMS = Object.freeze({
26
+ name: group('name', { ru: ['имя', 'фио'], en: ['name', 'full name'], uk: ['ім’я', "ім'я", 'піб'], ro: ['nume', 'nume complet'], uzLatn: ['ism', 'ismi', 'fio'], uzCyrl: ['исм', 'исми', 'фио'], kk: ['аты', 'аты-жөні', 'аты жөні'] }),
27
+ surname: group('surname', { ru: ['фамилия'], en: ['surname', 'last name'], uk: ['прізвище'], ro: ['nume de familie', 'prenume și nume'], uzLatn: ['familiya', 'familya'], uzCyrl: ['фамилия'], kk: ['тегі'] }),
28
+ age: group('age', { ru: ['возраст', 'лет'], en: ['age', 'years old'], uk: ['вік', 'років'], ro: ['vârstă', 'varsta', 'ani'], uzLatn: ['yosh', 'yoshi'], uzCyrl: ['ёш', 'ёши'], kk: ['жасы', 'жас'] }),
29
+ birthYear: group('birthYear', { ru: ['год рождения', 'дата рождения'], en: ['birth year', 'date of birth'], uk: ['рік народження', 'дата народження'], ro: ['anul nașterii', 'anul nasterii', 'data nașterii', 'data nasterii'], uzLatn: ["tug'ilgan yili", 'tug‘ilgan yili', 'tugʻilgan yili'], uzCyrl: ['туғилган йили'], kk: ['туған жылы', 'туған күні'] }),
30
+ profession: group('profession', { ru: ['должность', 'профессия', 'желаемая должность'], en: ['position', 'profession', 'desired role'], uk: ['посада', 'професія', 'бажана посада'], ro: ['poziție', 'pozitie', 'profesie', 'post dorit'], uzLatn: ['kasb', 'kasbi', 'qidirayotgan kasb', 'lavozim'], uzCyrl: ['касб', 'касби', 'қидираётган касб', 'лавозим'], kk: ['мамандық', 'лауазым', 'қалаған лауазым'] }),
31
+ experience: group('experience', { ru: ['опыт', 'стаж', 'опыт работы'], en: ['experience', 'work experience'], uk: ['досвід', 'стаж', 'досвід роботи'], ro: ['experiență', 'experienta', 'experiență de muncă', 'vechime'], uzLatn: ['tajriba', 'staj', 'ish tajribasi'], uzCyrl: ['тажриба', 'стаж', 'иш тажрибаси'], kk: ['тәжірибе', 'еңбек өтілі', 'жұмыс тәжірибесі'] }),
32
+ skills: group('skills', { ru: ['навыки', 'умения', 'технологии', 'стек'], en: ['skills', 'competencies', 'technologies', 'tech stack'], uk: ['навички', 'вміння', 'технології', 'стек'], ro: ['competențe', 'competente', 'abilități', 'abilitati', 'tehnologii'], uzLatn: ["ko'nikmalar", 'ko‘nikmalar', 'texnologiyalar'], uzCyrl: ['кўникмалар', 'технологиялар'], kk: ['дағдылар', 'құзыреттер', 'технологиялар'] }),
33
+ education: group('education', { ru: ['образование'], en: ['education'], uk: ['освіта'], ro: ['studii', 'educație', 'educatie'], uzLatn: ["ta'lim", 'ta‘lim', 'taʻlim', "ma'lumoti"], uzCyrl: ['таълим', 'маълумоти'], kk: ['білімі', 'білім'] }),
34
+ languages: group('languages', { ru: ['языки', 'знание языков'], en: ['languages', 'language skills'], uk: ['мови', 'знання мов'], ro: ['limbi', 'limbi cunoscute', 'cunoștințe lingvistice'], uzLatn: ['tillar', 'til bilishi'], uzCyrl: ['тиллар', 'тил билиши'], kk: ['тілдер', 'тіл білуі'] }),
35
+ salary: group('salary', { ru: ['зарплата', 'зп', 'ожидания по зарплате'], en: ['salary', 'compensation', 'expected salary'], uk: ['зарплата', 'зп', 'очікувана зарплата'], ro: ['salariu', 'salariul dorit', 'așteptări salariale', 'asteptari salariale'], uzLatn: ['maosh', 'oylik', 'ish haqi'], uzCyrl: ['маош', 'ойлик', 'иш ҳақи'], kk: ['жалақы', 'айлық', 'еңбекақы'] }),
36
+ address: group('address', { ru: ['адрес', 'место проживания'], en: ['address', 'location'], uk: ['адреса', 'місце проживання'], ro: ['adresă', 'adresa', 'domiciliu', 'locație'], uzLatn: ['manzil', 'yashash manzili'], uzCyrl: ['манзил', 'яшаш манзили'], kk: ['мекенжай', 'тұрғылықты жері'] }),
37
+ city: group('city', { ru: ['город'], en: ['city'], uk: ['місто'], ro: ['oraș', 'oras'], uzLatn: ['shahar', 'shahri'], uzCyrl: ['шаҳар', 'шаҳри'], kk: ['қала'] }),
38
+ district: group('district', { ru: ['район'], en: ['district'], uk: ['район'], ro: ['sector', 'cartier'], uzLatn: ['tuman', 'tumani'], uzCyrl: ['туман', 'тумани'], kk: ['аудан'] }),
39
+ contact: group('contact', { ru: ['контакты', 'телефон', 'связь'], en: ['contact', 'phone', 'contact details'], uk: ['контакти', 'телефон', 'зв’язок'], ro: ['contact', 'telefon', 'date de contact'], uzLatn: ['aloqa', 'telefon', 'boglanish'], uzCyrl: ['алоқа', 'телефон', 'боғланиш'], kk: ['байланыс', 'телефон'] }),
40
+ employmentType: group('employmentType', { ru: ['занятость', 'тип занятости'], en: ['employment type', 'job type'], uk: ['зайнятість', 'тип зайнятості'], ro: ['tip angajare', 'tip de muncă', 'tip de munca'], uzLatn: ['bandlik turi', 'ish turi'], uzCyrl: ['бандлик тури', 'иш тури'], kk: ['жұмыс түрі', 'жұмыспен қамту түрі'] }),
41
+ workMode: group('workMode', { ru: ['формат работы', 'режим работы'], en: ['work mode', 'workplace type'], uk: ['формат роботи', 'режим роботи'], ro: ['mod de lucru', 'format de lucru'], uzLatn: ['ish formati', 'ish rejimi'], uzCyrl: ['иш формати', 'иш режими'], kk: ['жұмыс форматы', 'жұмыс режимі'] }),
42
+ schedule: group('schedule', { ru: ['график', 'график работы'], en: ['schedule', 'work schedule'], uk: ['графік', 'графік роботи'], ro: ['program', 'program de lucru'], uzLatn: ['grafik', 'ish grafigi'], uzCyrl: ['график', 'иш графиги'], kk: ['жұмыс кестесі', 'кесте'] }),
43
+ relocation: group('relocation', { ru: ['релокация', 'готов к переезду', 'переезд'], en: ['relocation', 'willing to relocate'], uk: ['релокація', 'готовий до переїзду', 'переїзд'], ro: ['relocare', 'dispus să se relocheze', 'dispus sa se relocheze'], uzLatn: ['kochib otishga tayyor', "ko'chib o'tishga tayyor"], uzCyrl: ['кўчиб ўтишга тайёр'], kk: ['көшуге дайын', 'релокация'] }),
44
+ citizenship: group('citizenship', { ru: ['гражданство'], en: ['citizenship', 'nationality'], uk: ['громадянство'], ro: ['cetățenie', 'cetatenie', 'naționalitate', 'nationalitate'], uzLatn: ['fuqarolik'], uzCyrl: ['фуқаролик'], kk: ['азаматтық'] }),
45
+ driversLicense: group('driversLicense', { ru: ['водительские права', 'права категории'], en: ["driver's license", 'driving licence', 'driving license'], uk: ['водійські права', 'права категорії'], ro: ['permis de conducere', 'categoria permisului'], uzLatn: ['haydovchilik guvohnomasi', 'prava kategoriya'], uzCyrl: ['ҳайдовчилик гувоҳномаси'], kk: ['жүргізуші куәлігі'] }),
46
+ });
47
+
48
+ export const JOB_FIELD_TERMS = Object.freeze({
49
+ title: group('title', { ru: ['вакансия', 'должность'], en: ['job title', 'position', 'role'], uk: ['вакансія', 'посада'], ro: ['post', 'poziție', 'pozitie', 'funcție', 'functie'], uzLatn: ['vakansiya', 'lavozim'], uzCyrl: ['вакансия', 'лавозим'], kk: ['бос жұмыс орны', 'лауазым'] }),
50
+ responsibilities: group('responsibilities', { ru: ['обязанности', 'задачи'], en: ['responsibilities', 'duties', 'what you will do'], uk: ['обов’язки', "обов'язки", 'завдання'], ro: ['responsabilități', 'responsabilitati', 'atribuții', 'atributii'], uzLatn: ['vazifalar', 'majburiyatlar'], uzCyrl: ['вазифалар', 'мажбуриятлар'], kk: ['міндеттер', 'тапсырмалар'] }),
51
+ requirements: group('requirements', { ru: ['требования', 'кандидат должен'], en: ['requirements', 'qualifications', 'you should have'], uk: ['вимоги', 'кандидат повинен'], ro: ['cerințe', 'cerinte', 'calificări', 'calificari'], uzLatn: ['talablar', 'nomzoddan talab'], uzCyrl: ['талаблар', 'номзоддан талаб'], kk: ['талаптар', 'біліктілік'] }),
52
+ skills: CANDIDATE_FIELD_TERMS.skills,
53
+ salary: CANDIDATE_FIELD_TERMS.salary,
54
+ benefits: group('benefits', { ru: ['условия', 'бонусы', 'льготы', 'бенефиты'], en: ['benefits', 'perks', 'what we offer'], uk: ['умови', 'бонуси', 'переваги'], ro: ['beneficii', 'ce oferim', 'avantaje'], uzLatn: ['sharoitlar', 'imtiyozlar', 'biz taklif qilamiz'], uzCyrl: ['шароитлар', 'имтиёзлар', 'биз таклиф қиламиз'], kk: ['шарттар', 'жеңілдіктер', 'біз ұсынамыз'] }),
55
+ probation: group('probation', { ru: ['испытательный срок'], en: ['probation', 'probation period'], uk: ['випробувальний термін', 'випробувальний строк'], ro: ['perioadă de probă', 'perioada de proba'], uzLatn: ['sinov muddati'], uzCyrl: ['синов муддати'], kk: ['сынақ мерзімі'] }),
56
+ experience: CANDIDATE_FIELD_TERMS.experience,
57
+ employmentType: CANDIDATE_FIELD_TERMS.employmentType,
58
+ workMode: CANDIDATE_FIELD_TERMS.workMode,
59
+ schedule: CANDIDATE_FIELD_TERMS.schedule,
60
+ location: CANDIDATE_FIELD_TERMS.address,
61
+ });
62
+
63
+ export const EMPLOYMENT_TYPES = Object.freeze([
64
+ group('fullTime', {
65
+ ru: ['полная занятость', 'полный день', 'полный рабочий день'], en: ['full time', 'full-time', 'full time employment'], uk: ['повна зайнятість', 'повний день', 'повний робочий день'], ro: ['normă întreagă', 'norma intreaga', 'full-time', 'program complet'],
66
+ uzLatn: ["to'liq bandlik", 'to‘liq bandlik', "to'liq kun", 'toliq ish kuni'], uzCyrl: ['тўлиқ бандлик', 'тўлиқ кун', 'тўлиқ иш куни'], kk: ['толық жұмыс күні', 'толық жұмыспен қамту'],
67
+ }),
68
+ group('partTime', {
69
+ ru: ['частичная занятость', 'неполная занятость', 'неполный день', 'неполный рабочий день', 'подработка'], en: ['part time', 'part-time', 'part time employment'], uk: ['часткова зайнятість', 'неповний день', 'підробіток'], ro: ['part-time', 'jumătate de normă', 'jumatate de norma', 'program parțial', 'program partial'],
70
+ uzLatn: ['yarim stavka', 'qisman bandlik', "qo'shimcha ish"], uzCyrl: ['ярим ставка', 'қисман бандлик', 'қўшимча иш'], kk: ['жартылай жұмыс', 'толық емес жұмыс күні', 'қосымша жұмыс'],
71
+ }),
72
+ group('contract', {
73
+ ru: ['контракт', 'по договору', 'договорная работа'], en: ['contract', 'contractor', 'fixed contract'], uk: ['контракт', 'за договором', 'договірна робота'], ro: ['contract', 'pe contract', 'contract determinat'],
74
+ uzLatn: ['shartnoma', 'shartnoma asosida'], uzCyrl: ['шартнома', 'шартнома асосида'], kk: ['келісімшарт', 'келісімшарт бойынша'],
75
+ }),
76
+ group('project', {
77
+ ru: ['проектная работа', 'на проект', 'проектная занятость'], en: ['project work', 'project-based', 'project based'], uk: ['проєктна робота', 'проектна робота', 'на проєкт'], ro: ['muncă pe proiect', 'munca pe proiect', 'bazat pe proiect'],
78
+ uzLatn: ['loyiha asosida', 'loyiha ishi'], uzCyrl: ['лойиҳа асосида', 'лойиҳа иши'], kk: ['жобалық жұмыс', 'жоба бойынша'],
79
+ }),
80
+ group('freelance', {
81
+ ru: ['фриланс', 'фрилансер'], en: ['freelance', 'freelancer'], uk: ['фриланс', 'фрилансер'], ro: ['freelance', 'freelancer'], uzLatn: ['frilans', 'freelancer'], uzCyrl: ['фриланс'], kk: ['фриланс', 'фрилансер'],
82
+ }),
83
+ group('temporary', {
84
+ ru: ['временная работа', 'временная занятость'], en: ['temporary', 'temporary work', 'temp job'], uk: ['тимчасова робота', 'тимчасова зайнятість'], ro: ['muncă temporară', 'munca temporara', 'temporar'], uzLatn: ['vaqtinchalik ish'], uzCyrl: ['вақтинчалик иш'], kk: ['уақытша жұмыс'],
85
+ }),
86
+ group('internship', {
87
+ ru: ['стажировка', 'интернатура'], en: ['internship', 'intern', 'traineeship'], uk: ['стажування', 'інтернатура'], ro: ['stagiu', 'internship', 'practică', 'practica'], uzLatn: ['stajirovka', 'amaliyot'], uzCyrl: ['стажировка', 'амалиёт'], kk: ['тағылымдама', 'тәжірибеден өту'],
88
+ }),
89
+ group('volunteer', {
90
+ ru: ['волонтёрство', 'волонтерство', 'волонтёр', 'волонтер'], en: ['volunteer', 'volunteering', 'voluntary work'], uk: ['волонтерство', 'волонтер', 'волонтерська робота'], ro: ['voluntariat', 'voluntar', 'muncă voluntară', 'munca voluntara'], uzLatn: ['volontyorlik', 'ko‘ngilli ish', "ko'ngilli ish"], uzCyrl: ['волонтёрлик', 'кўнгилли иш'], kk: ['еріктілік', 'ерікті жұмыс'],
91
+ }),
92
+ group('seasonal', {
93
+ ru: ['сезонная работа'], en: ['seasonal', 'seasonal work'], uk: ['сезонна робота'], ro: ['muncă sezonieră', 'munca sezoniera'], uzLatn: ['mavsumiy ish'], uzCyrl: ['мавсумий иш'], kk: ['маусымдық жұмыс'],
94
+ }),
95
+ ]);
96
+
97
+ export const WORK_MODES = Object.freeze([
98
+ group('remote', { ru: ['удалённо', 'удаленно', 'удалённая работа', 'дистанционно'], en: ['remote', 'work from home', 'wfh'], uk: ['віддалено', 'дистанційно', 'віддалена робота'], ro: ['remote', 'de acasă', 'de acasa', 'muncă la distanță', 'munca la distanta'], uzLatn: ['masofadan', 'masofaviy', 'uydan ishlash'], uzCyrl: ['масофадан', 'масофавий', 'уйдан ишлаш'], kk: ['қашықтан', 'қашықтан жұмыс', 'үйден жұмыс'] }),
99
+ group('hybrid', { ru: ['гибрид', 'гибридный формат'], en: ['hybrid', 'hybrid work'], uk: ['гібрид', 'гібридний формат'], ro: ['hibrid', 'mod hibrid'], uzLatn: ['gibrid'], uzCyrl: ['гибрид'], kk: ['гибрид', 'аралас формат'] }),
100
+ group('onsite', { ru: ['офис', 'в офисе', 'на месте', 'очно'], en: ['onsite', 'on-site', 'office', 'in person'], uk: ['офіс', 'в офісі', 'на місці'], ro: ['la birou', 'on-site', 'la sediu', 'fizic'], uzLatn: ['ofisda', 'joyida'], uzCyrl: ['офисда', 'жойида'], kk: ['кеңседе', 'офисте', 'орнында'] }),
101
+ ]);
102
+
103
+ export const SCHEDULE_TERMS = Object.freeze([
104
+ group('fiveTwo', { ru: ['5/2', '5 на 2', 'пятидневка'], en: ['5/2', 'five days a week', 'monday to friday'], uk: ['5/2', 'п’ятиденка', "п'ятиденка"], ro: ['5/2', 'luni-vineri', 'luni până vineri'], uzLatn: ['5/2', 'besh kunlik'], uzCyrl: ['5/2', 'беш кунлик'], kk: ['5/2', 'бес күндік'] }),
105
+ group('twoTwo', { ru: ['2/2', 'два через два'], en: ['2/2', 'two on two off'], uk: ['2/2', 'два через два'], ro: ['2/2', 'două zile cu două libere', 'doua zile cu doua libere'], uzLatn: ['2/2', 'ikki kun ikki kun'], uzCyrl: ['2/2', 'икки кун икки кун'], kk: ['2/2', 'екі күн жұмыс екі күн демалыс'] }),
106
+ group('shift', { ru: ['сменный график', 'по сменам', 'смены'], en: ['shift work', 'shifts', 'rotating shifts'], uk: ['змінний графік', 'позмінно', 'зміни'], ro: ['în ture', 'in ture', 'program în schimburi', 'program in schimburi'], uzLatn: ['smenali grafik', 'smena'], uzCyrl: ['сменали график', 'смена'], kk: ['ауысымдық кесте', 'ауысым'] }),
107
+ group('flexible', { ru: ['гибкий график', 'свободный график'], en: ['flexible schedule', 'flexible hours'], uk: ['гнучкий графік', 'вільний графік'], ro: ['program flexibil', 'ore flexibile'], uzLatn: ['moslashuvchan grafik', 'erkin grafik'], uzCyrl: ['мослашувчан график', 'эркин график'], kk: ['икемді кесте', 'еркін кесте'] }),
108
+ group('day', { ru: ['дневная смена', 'дневной график'], en: ['day shift'], uk: ['денна зміна'], ro: ['tură de zi', 'tura de zi'], uzLatn: ['kunduzgi smena'], uzCyrl: ['кундузги смена'], kk: ['күндізгі ауысым'] }),
109
+ group('night', { ru: ['ночная смена', 'ночной график'], en: ['night shift'], uk: ['нічна зміна'], ro: ['tură de noapte', 'tura de noapte'], uzLatn: ['tungi smena'], uzCyrl: ['тунги смена'], kk: ['түнгі ауысым'] }),
110
+ group('rotational', { ru: ['вахта', 'вахтовый метод'], en: ['rotational work', 'rotation', 'fly-in fly-out'], uk: ['вахта', 'вахтовий метод'], ro: ['muncă în rotație', 'munca in rotatie'], uzLatn: ['vaxta', 'vaxta usuli'], uzCyrl: ['вахта', 'вахта усули'], kk: ['вахта', 'вахталық әдіс'] }),
111
+ ]);
112
+
113
+ export const PROBATION_TERMS = Object.freeze({
114
+ probation: group('probation', { ru: ['испытательный срок', 'испыталка'], en: ['probation', 'probation period', 'trial period'], uk: ['випробувальний термін', 'випробувальний строк'], ro: ['perioadă de probă', 'perioada de proba'], uzLatn: ['sinov muddati'], uzCyrl: ['синов муддати'], kk: ['сынақ мерзімі'] }),
115
+ noProbation: group('noProbation', { ru: ['без испытательного срока'], en: ['no probation', 'no trial period'], uk: ['без випробувального терміну'], ro: ['fără perioadă de probă', 'fara perioada de proba'], uzLatn: ['sinov muddatizsiz', 'sinov muddati yoq'], uzCyrl: ['синов муддатисиз', 'синов муддати йўқ'], kk: ['сынақ мерзімінсіз'] }),
116
+ paid: group('paidProbation', { ru: ['оплачиваемый испытательный срок', 'испытательный срок оплачивается'], en: ['paid probation', 'paid trial period'], uk: ['оплачуваний випробувальний термін'], ro: ['perioadă de probă plătită', 'perioada de proba platita'], uzLatn: ['sinov muddati tolanadi', "sinov muddati to'lanadi"], uzCyrl: ['синов муддати тўланади'], kk: ['ақылы сынақ мерзімі'] }),
117
+ unpaid: group('unpaidProbation', { ru: ['неоплачиваемый испытательный срок'], en: ['unpaid probation', 'unpaid trial period'], uk: ['неоплачуваний випробувальний термін'], ro: ['perioadă de probă neplătită', 'perioada de proba neplatita'], uzLatn: ['sinov muddati tolanmaydi', "sinov muddati to'lanmaydi"], uzCyrl: ['синов муддати тўланмайди'], kk: ['ақысыз сынақ мерзімі'] }),
118
+ });
119
+
120
+ export const EXPERIENCE_REQUIREMENTS = Object.freeze({
121
+ noExperience: group('noExperience', { ru: ['без опыта', 'опыт не требуется'], en: ['no experience', 'experience not required', 'entry level'], uk: ['без досвіду', 'досвід не потрібен'], ro: ['fără experiență', 'fara experienta', 'experiența nu este necesară', 'experienta nu este necesara'], uzLatn: ['tajribasiz', 'tajriba shart emas'], uzCyrl: ['тажрибасиз', 'тажриба шарт эмас'], kk: ['тәжірибесіз', 'тәжірибе қажет емес'] }),
122
+ required: group('experienceRequired', { ru: ['опыт обязателен', 'требуется опыт'], en: ['experience required', 'must have experience'], uk: ['досвід обов’язковий', "досвід обов'язковий", 'потрібен досвід'], ro: ['experiență necesară', 'experienta necesara', 'experiență obligatorie'], uzLatn: ['tajriba talab qilinadi', 'tajriba kerak'], uzCyrl: ['тажриба талаб қилинади', 'тажриба керак'], kk: ['тәжірибе қажет', 'тәжірибе міндетті'] }),
123
+ });
124
+
125
+ /** Field/requirement vocabulary only. Canonical technology names remain owned by the consumer skill catalog. */
126
+ export const SKILL_FIELD_TERMS = Object.freeze({
127
+ technical: group('technicalSkills', { ru: ['технические навыки', 'hard skills'], en: ['technical skills', 'hard skills', 'tech stack'], uk: ['технічні навички', 'hard skills'], ro: ['competențe tehnice', 'competente tehnice', 'hard skills'], uzLatn: ['texnik konikmalar', "texnik ko'nikmalar", 'texnologiyalar'], uzCyrl: ['техник кўникмалар', 'технологиялар'], kk: ['техникалық дағдылар', 'технологиялар'] }),
128
+ soft: group('softSkills', { ru: ['гибкие навыки', 'soft skills', 'личные качества'], en: ['soft skills', 'personal qualities'], uk: ['м’які навички', "м'які навички", 'особисті якості'], ro: ['abilități soft', 'abilitati soft', 'calități personale', 'calitati personale'], uzLatn: ['shaxsiy fazilatlar', 'yumshoq konikmalar'], uzCyrl: ['шахсий фазилатлар', 'юмшоқ кўникмалар'], kk: ['жұмсақ дағдылар', 'жеке қасиеттер'] }),
129
+ language: CANDIDATE_FIELD_TERMS.languages,
130
+ tools: group('tools', { ru: ['инструменты', 'программы'], en: ['tools', 'software'], uk: ['інструменти', 'програми'], ro: ['instrumente', 'programe', 'software'], uzLatn: ['vositalar', 'dasturlar'], uzCyrl: ['воситалар', 'дастурлар'], kk: ['құралдар', 'бағдарламалар'] }),
131
+ });
132
+
133
+ export const BENEFIT_TERMS = Object.freeze({
134
+ meals: group('meals', { ru: ['питание', 'обеды', 'бесплатное питание'], en: ['meals', 'lunch', 'free meals'], uk: ['харчування', 'обіди', 'безкоштовне харчування'], ro: ['masă', 'masa', 'prânz', 'pranz', 'tichete de masă', 'bonuri de masă'], uzLatn: ['ovqat', 'tushlik', 'bepul ovqat'], uzCyrl: ['овқат', 'тушлик', 'бепул овқат'], kk: ['тамақ', 'түскі ас', 'тегін тамақ'] }),
135
+ transportation: group('transportation', { ru: ['развозка', 'служебный транспорт'], en: ['transportation', 'company transport', 'shuttle'], uk: ['розвозка', 'службовий транспорт'], ro: ['transport asigurat', 'transport de serviciu'], uzLatn: ['razvozka', 'xizmat transporti'], uzCyrl: ['развозка', 'хизмат транспорти'], kk: ['қызметтік көлік', 'тасымал'] }),
136
+ accommodation: group('accommodation', { ru: ['проживание', 'жильё предоставляется', 'жилье предоставляется'], en: ['accommodation', 'housing provided'], uk: ['проживання', 'житло надається'], ro: ['cazare', 'cazare asigurată', 'cazare asigurata'], uzLatn: ['yotoq joy', 'turar joy beriladi'], uzCyrl: ['ётоқ жой', 'турар жой берилади'], kk: ['тұратын орын', 'баспана беріледі'] }),
137
+ insurance: group('insurance', { ru: ['медицинская страховка', 'дмс'], en: ['health insurance', 'medical insurance'], uk: ['медичне страхування'], ro: ['asigurare medicală', 'asigurare medicala'], uzLatn: ['tibbiy sugurta', "tibbiy sug'urta"], uzCyrl: ['тиббий суғурта'], kk: ['медициналық сақтандыру'] }),
138
+ training: group('training', { ru: ['обучение за счет компании', 'обучение предоставляется'], en: ['training provided', 'company-paid training'], uk: ['навчання за рахунок компанії'], ro: ['training oferit', 'cursuri plătite de companie', 'cursuri platite de companie'], uzLatn: ['oqitish', "o'qitish", 'kompaniya hisobidan talim'], uzCyrl: ['ўқитиш', 'компания ҳисобидан таълим'], kk: ['оқыту', 'компания есебінен оқу'] }),
139
+ bonus: group('bonus', { ru: ['бонус', 'премия', 'kpi бонус'], en: ['bonus', 'performance bonus'], uk: ['бонус', 'премія'], ro: ['bonus', 'primă', 'prima'], uzLatn: ['bonus', 'mukofot'], uzCyrl: ['бонус', 'мукофот'], kk: ['бонус', 'сыйақы'] }),
140
+ equipment: group('equipment', { ru: ['техника предоставляется', 'ноутбук предоставляется', 'оборудование предоставляется'], en: ['equipment provided', 'laptop provided'], uk: ['техніка надається', 'ноутбук надається'], ro: ['echipament oferit', 'laptop oferit'], uzLatn: ['texnika beriladi', 'noutbuk beriladi'], uzCyrl: ['техника берилади', 'ноутбук берилади'], kk: ['техника беріледі', 'ноутбук беріледі'] }),
141
+ });
142
+
143
+ export const SENIORITY_TERMS = Object.freeze([
144
+ group('intern', { ru: ['стажёр', 'стажер', 'интерн'], en: ['intern', 'trainee'], uk: ['стажер', 'інтерн'], ro: ['intern', 'stagiar'], uzLatn: ['stajyor', 'amaliyotchi'], uzCyrl: ['стажёр', 'амалиётчи'], kk: ['тағылымгер', 'стажер'] }),
145
+ group('junior', { ru: ['джун', 'джуниор', 'младший'], en: ['junior', 'jr'], uk: ['джуніор', 'молодший'], ro: ['junior', 'începător', 'incepator'], uzLatn: ['junior', 'boshlangich'], uzCyrl: ['жуниор', 'бошланғич'], kk: ['junior', 'бастапқы деңгей'] }),
146
+ group('middle', { ru: ['мидл', 'средний'], en: ['middle', 'mid-level', 'mid level'], uk: ['мідл', 'середній рівень'], ro: ['middle', 'mid-level', 'nivel mediu'], uzLatn: ['middle', "o'rta daraja"], uzCyrl: ['мидл', 'ўрта даража'], kk: ['middle', 'орта деңгей'] }),
147
+ group('senior', { ru: ['сеньор', 'синьор', 'старший', 'ведущий'], en: ['senior', 'sr'], uk: ['сеньйор', 'старший', 'провідний'], ro: ['senior'], uzLatn: ['senior', 'katta mutaxassis'], uzCyrl: ['сениор', 'катта мутахассис'], kk: ['senior', 'аға'] }),
148
+ group('lead', { ru: ['лид', 'тимлид', 'руководитель'], en: ['lead', 'team lead', 'tech lead'], uk: ['лід', 'тімлід', 'керівник'], ro: ['lead', 'team lead', 'lider de echipă', 'lider de echipa'], uzLatn: ['lead', 'team lead', 'yetakchi'], uzCyrl: ['лид', 'етакчи'], kk: ['lead', 'жетекші'] }),
149
+ ]);
150
+
151
+ export const PROFESSION_TERMS = Object.freeze([
152
+ group('software_developer', { ru: ['разработчик', 'программист'], en: ['software developer', 'software engineer', 'developer', 'programmer'], uk: ['розробник', 'програміст'], ro: ['dezvoltator software', 'programator', 'inginer software'], uzLatn: ['dasturchi', 'programmist'], uzCyrl: ['дастурчи', 'программист'], kk: ['бағдарламашы', 'әзірлеуші'] }),
153
+ group('frontend_developer', { ru: ['фронтенд разработчик', 'frontend разработчик', 'фронтендер'], en: ['frontend developer', 'front-end developer', 'frontend engineer'], uk: ['фронтенд розробник', 'frontend розробник'], ro: ['frontend developer', 'dezvoltator frontend'], uzLatn: ['frontend dasturchi'], uzCyrl: ['фронтенд дастурчи'], kk: ['frontend әзірлеуші', 'frontend бағдарламашы'] }),
154
+ group('backend_developer', { ru: ['бэкенд разработчик', 'backend разработчик'], en: ['backend developer', 'back-end developer', 'backend engineer'], uk: ['бекенд розробник', 'backend розробник'], ro: ['backend developer', 'dezvoltator backend'], uzLatn: ['backend dasturchi'], uzCyrl: ['бэкенд дастурчи'], kk: ['backend әзірлеуші'] }),
155
+ group('fullstack_developer', { ru: ['фулстек разработчик', 'fullstack разработчик'], en: ['fullstack developer', 'full-stack developer'], uk: ['фулстек розробник', 'fullstack розробник'], ro: ['fullstack developer', 'dezvoltator full-stack'], uzLatn: ['fullstack dasturchi'], uzCyrl: ['фулстек дастурчи'], kk: ['fullstack әзірлеуші'] }),
156
+ group('qa_engineer', { ru: ['тестировщик', 'qa инженер', 'qa'], en: ['qa engineer', 'qa tester', 'software tester'], uk: ['тестувальник', 'qa інженер'], ro: ['inginer qa', 'tester software', 'qa tester'], uzLatn: ['testchi', 'qa muhandis'], uzCyrl: ['тестчи', 'qa муҳандис'], kk: ['тестілеуші', 'qa инженері'] }),
157
+ group('designer', { ru: ['дизайнер', 'ui ux дизайнер', 'ux ui дизайнер'], en: ['designer', 'ui designer', 'ux designer', 'product designer'], uk: ['дизайнер', 'ui ux дизайнер'], ro: ['designer', 'designer ui', 'designer ux'], uzLatn: ['dizayner'], uzCyrl: ['дизайнер'], kk: ['дизайнер'] }),
158
+ group('product_manager', { ru: ['продакт менеджер', 'продуктовый менеджер'], en: ['product manager', 'product owner'], uk: ['продакт менеджер', 'продуктовий менеджер'], ro: ['product manager', 'manager de produs'], uzLatn: ['product menejer', 'mahsulot menejeri'], uzCyrl: ['продакт менежер', 'маҳсулот менежери'], kk: ['өнім менеджері', 'product manager'] }),
159
+ group('project_manager', { ru: ['проектный менеджер', 'менеджер проектов'], en: ['project manager', 'pm'], uk: ['проєктний менеджер', 'менеджер проєктів'], ro: ['project manager', 'manager de proiect'], uzLatn: ['loyiha menejeri'], uzCyrl: ['лойиҳа менежери'], kk: ['жоба менеджері'] }),
160
+ group('sales_manager', { ru: ['менеджер по продажам', 'продажник'], en: ['sales manager', 'sales representative'], uk: ['менеджер з продажу', 'менеджер з продажів'], ro: ['manager vânzări', 'manager vanzari', 'reprezentant vânzări'], uzLatn: ['sotuv menejeri', 'savdo menejeri'], uzCyrl: ['сотув менежери', 'савдо менежери'], kk: ['сату менеджері', 'сауда менеджері'] }),
161
+ group('accountant', { ru: ['бухгалтер'], en: ['accountant'], uk: ['бухгалтер'], ro: ['contabil'], uzLatn: ['buxgalter'], uzCyrl: ['бухгалтер'], kk: ['бухгалтер', 'есепші'] }),
162
+ group('cashier', { ru: ['кассир'], en: ['cashier'], uk: ['касир'], ro: ['casier'], uzLatn: ['kassir'], uzCyrl: ['кассир'], kk: ['кассир'] }),
163
+ group('seller', { ru: ['продавец', 'продавец консультант'], en: ['seller', 'sales assistant', 'shop assistant'], uk: ['продавець', 'продавець-консультант'], ro: ['vânzător', 'vanzator', 'consilier vânzări'], uzLatn: ['sotuvchi', 'sotuvchi maslahatchi'], uzCyrl: ['сотувчи', 'сотувчи маслаҳатчи'], kk: ['сатушы', 'сатушы кеңесші'] }),
164
+ group('driver', { ru: ['водитель', 'шофёр', 'шофер'], en: ['driver'], uk: ['водій'], ro: ['șofer', 'sofer', 'conducător auto'], uzLatn: ['haydovchi'], uzCyrl: ['ҳайдовчи'], kk: ['жүргізуші'] }),
165
+ group('courier', { ru: ['курьер'], en: ['courier', 'delivery driver'], uk: ['кур’єр', "кур'єр"], ro: ['curier'], uzLatn: ['kuryer'], uzCyrl: ['курьер'], kk: ['курьер', 'жеткізуші'] }),
166
+ group('cook', { ru: ['повар', 'кулинар'], en: ['cook', 'chef'], uk: ['кухар', 'шеф-кухар'], ro: ['bucătar', 'bucatar', 'chef'], uzLatn: ['oshpaz'], uzCyrl: ['ошпаз'], kk: ['аспаз'] }),
167
+ group('cleaner', { ru: ['уборщик', 'уборщица', 'клинер'], en: ['cleaner'], uk: ['прибиральник', 'прибиральниця', 'клінер'], ro: ['personal curățenie', 'personal curatenie', 'menajeră', 'menajera'], uzLatn: ['farrosh', 'tozalovchi'], uzCyrl: ['фаррош', 'тозаловчи'], kk: ['тазалықшы', 'еден жуушы'] }),
168
+ group('security_guard', { ru: ['охранник', 'сторож'], en: ['security guard', 'guard'], uk: ['охоронець', 'сторож'], ro: ['agent de pază', 'agent de paza', 'paznic'], uzLatn: ['qorovul', 'qo‘riqchi', "qo'riqchi"], uzCyrl: ['қоровул', 'қўриқчи'], kk: ['күзетші'] }),
169
+ group('welder', { ru: ['сварщик'], en: ['welder'], uk: ['зварювальник'], ro: ['sudor'], uzLatn: ['payvandchi'], uzCyrl: ['пайвандчи'], kk: ['дәнекерлеуші'] }),
170
+ group('teacher', { ru: ['учитель', 'преподаватель'], en: ['teacher', 'tutor', 'instructor'], uk: ['вчитель', 'викладач'], ro: ['profesor', 'învățător', 'invatator'], uzLatn: ["o'qituvchi", 'o‘qituvchi'], uzCyrl: ['ўқитувчи'], kk: ['мұғалім', 'оқытушы'] }),
171
+ group('doctor', { ru: ['врач', 'доктор'], en: ['doctor', 'physician'], uk: ['лікар', 'доктор'], ro: ['medic', 'doctor'], uzLatn: ['shifokor'], uzCyrl: ['шифокор'], kk: ['дәрігер'] }),
172
+ group('nurse', { ru: ['медсестра', 'медбрат'], en: ['nurse'], uk: ['медсестра', 'медбрат', 'медична сестра'], ro: ['asistent medical', 'asistentă medicală', 'asistenta medicala'], uzLatn: ['hamshira'], uzCyrl: ['ҳамшира'], kk: ['медбике', 'мейіргер'] }),
173
+ group('administrator', { ru: ['администратор'], en: ['administrator', 'admin'], uk: ['адміністратор'], ro: ['administrator'], uzLatn: ['administrator'], uzCyrl: ['администратор'], kk: ['әкімші', 'администратор'] }),
174
+ group('operator', { ru: ['оператор', 'оператор колл центра'], en: ['operator', 'call center operator'], uk: ['оператор', 'оператор кол-центру'], ro: ['operator', 'operator call center'], uzLatn: ['operator', 'call markaz operatori'], uzCyrl: ['оператор', 'колл марказ оператори'], kk: ['оператор', 'колл-орталық операторы'] }),
175
+ ]);
176
+
177
+ export const PERSON_LINEAGE_TERMS = Object.freeze({
178
+ female: ['qizi', 'қизи', 'кизи', 'қызы'],
179
+ male: ["o'g'li", 'o‘g‘li', 'oʻgʻli', 'ўғли', 'угли', 'оғли', 'огли', 'ұлы'],
180
+ });
@@ -0,0 +1,18 @@
1
+ export type HousingAddressParts = Readonly<{
2
+ address: string | null;
3
+ street: string | null;
4
+ houseNumber: string | null;
5
+ building: string | null;
6
+ confidence: number;
7
+ }>;
8
+
9
+ export function parseHousingAddress(
10
+ value: unknown,
11
+ options?: Readonly<{ allowBare?: boolean; knownStreet?: string | null }>,
12
+ ): HousingAddressParts;
13
+
14
+ export function composeHousingAddress(parts?: Readonly<{
15
+ street?: unknown;
16
+ houseNumber?: unknown;
17
+ building?: unknown;
18
+ }>): string | null;
@@ -0,0 +1,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 prefix = line.match(new RegExp(`(?:^|[\\s,;])(${PREFIX_STREET_MARKER})\\s+(.+)$`, 'iu'));
122
+ if (prefix) {
123
+ const tail = splitAddressTail(prefix[2]);
124
+ if (tail) return result(line, tail.street, tail.houseNumber, tail.building, tail.houseNumber ? 1 : 0.9);
125
+ }
126
+
127
+ const postfix = line.match(new RegExp(`^(.+?)\\s+(${POSTFIX_STREET_MARKER})(.*)$`, 'iu'));
128
+ if (postfix) {
129
+ const tailText = clean(`${postfix[1]} ${postfix[3]}`);
130
+ const tail = splitAddressTail(tailText);
131
+ if (tail) return result(line, tail.street, tail.houseNumber, tail.building, tail.houseNumber ? 1 : 0.9);
132
+ }
133
+ }
134
+
135
+ return null;
136
+ }
137
+
138
+ function knownStreetAddress(text, knownStreet) {
139
+ const street = compactStreet(knownStreet);
140
+ if (!street) return null;
141
+ const streetPattern = street.split(/\s+/u).map(escapeRegExp).join('\\s+');
142
+ const re = new RegExp(
143
+ `(?:^|[^\\p{L}\\p{N}])(?:${PREFIX_STREET_MARKER}\\s+)?(${streetPattern})(?:\\s+${POSTFIX_STREET_MARKER})?` +
144
+ `(?:\\s*[,;]?\\s*(?:${HOUSE_MARKER})?\\s*(${NUMBER_TOKEN}))?` +
145
+ `(?:\\s*[,;]?\\s*${BUILDING_MARKER}\\s*(${NUMBER_TOKEN}))?` +
146
+ `(?=$|[^\\p{L}\\p{N}])`,
147
+ 'iu',
148
+ );
149
+ const match = text.match(re);
150
+ if (!match) return null;
151
+ const houseNumber = match[2] || null;
152
+ const building = match[3] || null;
153
+ const address = composeHousingAddress({ street, houseNumber, building });
154
+ return result(address, street, houseNumber, building, houseNumber ? 0.98 : 0.9);
155
+ }
156
+
157
+ function labelledAddress(text) {
158
+ const label = text.match(ADDRESS_LABEL_RE);
159
+ if (!label) return null;
160
+ const start = (label.index ?? 0) + label[0].length;
161
+ const line = clean(text.slice(start).split(/[\r\n|]/u, 1)[0]).slice(0, 140);
162
+ if (!line) return null;
163
+ return parseHousingAddress(line, { allowBare: true });
164
+ }
165
+
166
+ function bareAddress(text) {
167
+ const tail = splitAddressTail(text);
168
+ if (!tail) return null;
169
+ const confidence = tail.houseNumber ? 0.85 : 0.55;
170
+ return result(text, tail.street, tail.houseNumber, tail.building, confidence);
171
+ }
172
+
173
+ /**
174
+ * Parse only textual address structure. This module intentionally contains no
175
+ * coordinates and performs no geocoding.
176
+ *
177
+ * `knownStreet` may be supplied when a location dictionary has already
178
+ * canonicalized the street. In that mode the parser only accepts a house/building
179
+ * number immediately adjacent to that exact street mention.
180
+ *
181
+ * `allowBare` should be used only when the input is already known to be an
182
+ * address field (for example a source-provided address), not on arbitrary post
183
+ * prose where prices and phone numbers may look like house numbers.
184
+ */
185
+ export function parseHousingAddress(value, options = {}) {
186
+ const text = clean(value);
187
+ if (!text) return result(null);
188
+
189
+ const labelled = labelledAddress(text);
190
+ if (labelled) return labelled;
191
+
192
+ if (options.knownStreet) {
193
+ const known = knownStreetAddress(text, options.knownStreet);
194
+ if (known) return known;
195
+ }
196
+
197
+ const explicit = explicitStreetAddress(text);
198
+ if (explicit) return explicit;
199
+
200
+ if (options.allowBare === true) return bareAddress(text) || result(text);
201
+ return result(null);
202
+ }
203
+
204
+ export function composeHousingAddress(parts = {}) {
205
+ const street = compactStreet(parts.street);
206
+ const houseNumber = normalizeNumber(parts.houseNumber);
207
+ const building = normalizeNumber(parts.building);
208
+ if (!street) return null;
209
+ return [street, houseNumber, building ? `корп. ${building}` : null].filter(Boolean).join(' ');
210
+ }
@@ -0,0 +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;