@iconicompany/imatchingcore 1.0.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.
- package/.github/workflows/publish-main.yml +36 -0
- package/.github/workflows/publish-release.yml +51 -0
- package/README.md +14 -0
- package/bun.lock +26 -0
- package/data/specializations/specializations.csv +265 -0
- package/package.json +19 -0
- package/src/specializations/specializations-matching-engine.test.ts +140 -0
- package/src/specializations/specializations-matching-engine.ts +138 -0
- package/src/specializations/specializations-matching-factory.ts +268 -0
- package/tsconfig.json +29 -0
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
name: Publish main branch
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches:
|
|
6
|
+
- main
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
permissions:
|
|
10
|
+
id-token: write # Required for OIDC https://docs.npmjs.com/trusted-publishers
|
|
11
|
+
contents: write # нужно для пуша
|
|
12
|
+
|
|
13
|
+
jobs:
|
|
14
|
+
publish-main:
|
|
15
|
+
runs-on: ubuntu-latest
|
|
16
|
+
|
|
17
|
+
steps:
|
|
18
|
+
- uses: actions/checkout@v4
|
|
19
|
+
- uses: actions/setup-node@v4
|
|
20
|
+
with:
|
|
21
|
+
node-version: 20
|
|
22
|
+
registry-url: https://registry.npmjs.org/
|
|
23
|
+
# Ensure npm 11.5.1 or later is installed
|
|
24
|
+
- run: npm install -g npm@latest
|
|
25
|
+
- run: npm ci
|
|
26
|
+
- run: npm run build
|
|
27
|
+
- name: Bump version
|
|
28
|
+
run: |
|
|
29
|
+
git config --global user.name "github-actions"
|
|
30
|
+
git config --global user.email "github-actions@github.com"
|
|
31
|
+
npm version patch
|
|
32
|
+
- name: Publish to NPM
|
|
33
|
+
run: npm publish --access public
|
|
34
|
+
- name: Commit and push
|
|
35
|
+
run: |
|
|
36
|
+
git push --follow-tags
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
name: Publish Release to NPM
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [created]
|
|
6
|
+
|
|
7
|
+
permissions:
|
|
8
|
+
contents: write # нужно для пуша
|
|
9
|
+
|
|
10
|
+
jobs:
|
|
11
|
+
publish-npm:
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
steps:
|
|
14
|
+
- uses: actions/checkout@v4
|
|
15
|
+
with:
|
|
16
|
+
# Checkout the branch where the release was created, not the tag
|
|
17
|
+
ref: ${{ github.event.release.target_commitish }}
|
|
18
|
+
- uses: actions/setup-node@v4
|
|
19
|
+
with:
|
|
20
|
+
node-version: 20
|
|
21
|
+
registry-url: https://registry.npmjs.org/
|
|
22
|
+
- name: Install dependencies
|
|
23
|
+
run: npm ci
|
|
24
|
+
- name: Build
|
|
25
|
+
run: npm run build
|
|
26
|
+
- name: Bump version to release tag
|
|
27
|
+
id: bump_version
|
|
28
|
+
run: |
|
|
29
|
+
git config --global user.name "github-actions"
|
|
30
|
+
git config --global user.email "github-actions@github.com"
|
|
31
|
+
# GITHUB_REF is in format refs/tags/<tag_name>, we want the tag name
|
|
32
|
+
VERSION=${GITHUB_REF#refs/tags/}
|
|
33
|
+
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
|
34
|
+
# Use npm version to update package.json and package-lock.json
|
|
35
|
+
# --no-git-tag-version prevents it from creating a commit or tag
|
|
36
|
+
npm version $VERSION --no-git-tag-version --allow-same-version
|
|
37
|
+
- name: Publish to NPM
|
|
38
|
+
run: npm publish --access public
|
|
39
|
+
env:
|
|
40
|
+
NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
|
|
41
|
+
- name: Commit and push version bump
|
|
42
|
+
run: |
|
|
43
|
+
# Commit the changes made by npm version
|
|
44
|
+
git add package.json package-lock.json
|
|
45
|
+
# Check if there are changes to commit
|
|
46
|
+
if git diff --staged --quiet; then
|
|
47
|
+
echo "Version already up to date."
|
|
48
|
+
else
|
|
49
|
+
git commit -m "chore(release): bump version to ${{ steps.bump_version.outputs.version }}"
|
|
50
|
+
git push
|
|
51
|
+
fi
|
package/README.md
ADDED
package/bun.lock
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"lockfileVersion": 1,
|
|
3
|
+
"configVersion": 1,
|
|
4
|
+
"workspaces": {
|
|
5
|
+
"": {
|
|
6
|
+
"name": "imatchingcore",
|
|
7
|
+
"devDependencies": {
|
|
8
|
+
"@types/bun": "latest",
|
|
9
|
+
},
|
|
10
|
+
"peerDependencies": {
|
|
11
|
+
"typescript": "^5",
|
|
12
|
+
},
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
"packages": {
|
|
16
|
+
"@types/bun": ["@types/bun@1.3.6", "", { "dependencies": { "bun-types": "1.3.6" } }, "sha512-uWCv6FO/8LcpREhenN1d1b6fcspAB+cefwD7uti8C8VffIv0Um08TKMn98FynpTiU38+y2dUO55T11NgDt8VAA=="],
|
|
17
|
+
|
|
18
|
+
"@types/node": ["@types/node@25.0.9", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-/rpCXHlCWeqClNBwUhDcusJxXYDjZTyE8v5oTO7WbL8eij2nKhUeU89/6xgjU7N4/Vh3He0BtyhJdQbDyhiXAw=="],
|
|
19
|
+
|
|
20
|
+
"bun-types": ["bun-types@1.3.6", "", { "dependencies": { "@types/node": "*" } }, "sha512-OlFwHcnNV99r//9v5IIOgQ9Uk37gZqrNMCcqEaExdkVq3Avwqok1bJFmvGMCkCE0FqzdY8VMOZpfpR3lwI+CsQ=="],
|
|
21
|
+
|
|
22
|
+
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
|
|
23
|
+
|
|
24
|
+
"undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
"Название"
|
|
2
|
+
".NET разработчик"
|
|
3
|
+
"1С разработчик"
|
|
4
|
+
"1С эксперт"
|
|
5
|
+
"3D дизайнер"
|
|
6
|
+
"3D моделлер"
|
|
7
|
+
"AI-разработчик"
|
|
8
|
+
"Android разработчик"
|
|
9
|
+
"Angular разработчик"
|
|
10
|
+
"Astra Linux разработчик (языки C)"
|
|
11
|
+
"Atlassian разработчик"
|
|
12
|
+
"Backend разработчик"
|
|
13
|
+
"C разработчик"
|
|
14
|
+
"C# разработчик"
|
|
15
|
+
"C++ разработчик"
|
|
16
|
+
"Data Quality инженер"
|
|
17
|
+
"Data Scientist"
|
|
18
|
+
"Data аналитик"
|
|
19
|
+
"Data инженер"
|
|
20
|
+
"Delphi разработчик"
|
|
21
|
+
"DevOps"
|
|
22
|
+
"DWH разработчик"
|
|
23
|
+
"Email-маркетолог"
|
|
24
|
+
"Embedded Linux разработчик"
|
|
25
|
+
"ETL разработчик"
|
|
26
|
+
"Event-менеджер"
|
|
27
|
+
"Flutter разработчик"
|
|
28
|
+
"Frontend разработчик"
|
|
29
|
+
"Full Stack разработчик"
|
|
30
|
+
"GameDev разработчик"
|
|
31
|
+
"Golang разработчик"
|
|
32
|
+
"GR-менеджер"
|
|
33
|
+
"HR бизнес-партнер"
|
|
34
|
+
"Insight разработчик"
|
|
35
|
+
"IOS разработчик"
|
|
36
|
+
"IT-рекрутер"
|
|
37
|
+
"Java разработчик"
|
|
38
|
+
"JavaScript разработчик"
|
|
39
|
+
"Kotlin разработчик"
|
|
40
|
+
"LLM исследователь"
|
|
41
|
+
"MacOS разработчик"
|
|
42
|
+
"ML разработчик"
|
|
43
|
+
"MLOps"
|
|
44
|
+
"Motion-дизайнер"
|
|
45
|
+
"MS Dynamics разработчик"
|
|
46
|
+
"NestJS разработчик"
|
|
47
|
+
"NodeJS разработчик"
|
|
48
|
+
"Oracle аналитик"
|
|
49
|
+
"Oracle разработчик"
|
|
50
|
+
"Pega разработчик"
|
|
51
|
+
"PHP разработчик"
|
|
52
|
+
"PR-менеджер"
|
|
53
|
+
"Product owner"
|
|
54
|
+
"Python разработчик"
|
|
55
|
+
"QA FullStack"
|
|
56
|
+
"QA авто"
|
|
57
|
+
"QA мобильный"
|
|
58
|
+
"QA нагрузочный"
|
|
59
|
+
"QA ручной"
|
|
60
|
+
"React Native разработчик"
|
|
61
|
+
"React разработчик"
|
|
62
|
+
"RPA разработчик"
|
|
63
|
+
"Ruby разработчик"
|
|
64
|
+
"Rust разработчик"
|
|
65
|
+
"Scala разработчик"
|
|
66
|
+
"Scrum Master"
|
|
67
|
+
"SEO специалист"
|
|
68
|
+
"Siebel разработчик"
|
|
69
|
+
"SMM-менеджер"
|
|
70
|
+
"Software архитектор"
|
|
71
|
+
"Solution архитектор"
|
|
72
|
+
"SRE инженер"
|
|
73
|
+
"Unity разработчик"
|
|
74
|
+
"UX исследователь"
|
|
75
|
+
"UX проектировщик"
|
|
76
|
+
"UX-писатель"
|
|
77
|
+
"UX/UI дизайнер"
|
|
78
|
+
"Vue.js разработчик"
|
|
79
|
+
"Web аналитик"
|
|
80
|
+
"Web дизайнер"
|
|
81
|
+
"Web разработчик"
|
|
82
|
+
"Webtutor разработчик"
|
|
83
|
+
"WMS Аналитик"
|
|
84
|
+
"Xamarin разработчик"
|
|
85
|
+
"Администратор 1С"
|
|
86
|
+
"Администратор Teamcenter"
|
|
87
|
+
"Администратор баз данных"
|
|
88
|
+
"Администратор отдела"
|
|
89
|
+
"Администратор офиса"
|
|
90
|
+
"Администратор проектов"
|
|
91
|
+
"Администратор тестовых сред"
|
|
92
|
+
"Аккаунт менеджер"
|
|
93
|
+
"Актер"
|
|
94
|
+
"Аналитик 1С"
|
|
95
|
+
"Аналитик AI-агентов"
|
|
96
|
+
"Аналитик BI"
|
|
97
|
+
"Аналитик DWH"
|
|
98
|
+
"Аналитик SAP Commerce"
|
|
99
|
+
"Аналитик Битрикс24"
|
|
100
|
+
"Аналитик ЕХД"
|
|
101
|
+
"Аналитик информационной безопасности"
|
|
102
|
+
"Архитектор"
|
|
103
|
+
"Архитектор 1С"
|
|
104
|
+
"Архитектор Camunda"
|
|
105
|
+
"Архитектор SAP TOPO"
|
|
106
|
+
"Архитектор ИИ агентов"
|
|
107
|
+
"Ассистент"
|
|
108
|
+
"Аудиомонтажер"
|
|
109
|
+
"Аудитор"
|
|
110
|
+
"Бизнес аналитик"
|
|
111
|
+
"Бизнес аналитик 1С"
|
|
112
|
+
"Бизнес/системный аналитик"
|
|
113
|
+
"Битрикс разработчик"
|
|
114
|
+
"Битрикс24 разработчик"
|
|
115
|
+
"Бренд-дизайнер"
|
|
116
|
+
"Бренд-менеджер"
|
|
117
|
+
"Бухгалтер"
|
|
118
|
+
"Верстальщик"
|
|
119
|
+
"Видеограф"
|
|
120
|
+
"Видеомонтажер"
|
|
121
|
+
"Графический дизайнер"
|
|
122
|
+
"Дизайнер презентаций"
|
|
123
|
+
"Дизайнер рекламных материалов"
|
|
124
|
+
"Дизайнер упаковки"
|
|
125
|
+
"Диктор"
|
|
126
|
+
"Директор по CRM"
|
|
127
|
+
"Директор по аналитике"
|
|
128
|
+
"Директор по аналитике и инсайтам"
|
|
129
|
+
"Директор по безопасности"
|
|
130
|
+
"Директор по закупкам"
|
|
131
|
+
"Директор по инновациям"
|
|
132
|
+
"Директор по качеству"
|
|
133
|
+
"Директор по клиентскому опыту"
|
|
134
|
+
"Директор по коммуникациям"
|
|
135
|
+
"Директор по логистике"
|
|
136
|
+
"Директор по маркетингу"
|
|
137
|
+
"Директор по омниканальности"
|
|
138
|
+
"Директор по онлайн-маркетингу"
|
|
139
|
+
"Директор по персоналу"
|
|
140
|
+
"Директор по правовым вопросам"
|
|
141
|
+
"Директор по продукту"
|
|
142
|
+
"Директор по развитию"
|
|
143
|
+
"Директор по стратегии"
|
|
144
|
+
"Директор по управлению проектами"
|
|
145
|
+
"Директор по устойчивому развитию"
|
|
146
|
+
"Директор по цифровой трансформации"
|
|
147
|
+
"Директор по цифровым инновациям"
|
|
148
|
+
"Директор по цифровым каналам"
|
|
149
|
+
"Директор по цифровым технологиям"
|
|
150
|
+
"Директор по электронной коммерции"
|
|
151
|
+
"Журналист"
|
|
152
|
+
"Звукорежиссер"
|
|
153
|
+
"Иллюстратор"
|
|
154
|
+
"Инженер NLP/PLP"
|
|
155
|
+
"Инженер БД"
|
|
156
|
+
"Инженер микроконтроллеров"
|
|
157
|
+
"Инженер систем мониторинга"
|
|
158
|
+
"Инженер сопровождения"
|
|
159
|
+
"Инженер-конструктор"
|
|
160
|
+
"Инженер-метролог"
|
|
161
|
+
"ИТ-директор"
|
|
162
|
+
"Кадровый делопроизводитель"
|
|
163
|
+
"Категорийный менеджер"
|
|
164
|
+
"Коммерческий директор"
|
|
165
|
+
"Композитор"
|
|
166
|
+
"Консультант 1С"
|
|
167
|
+
"Консультант Directum RX"
|
|
168
|
+
"Консультант Elma365"
|
|
169
|
+
"Консультант SAP BW/BI"
|
|
170
|
+
"Консультант SAP EWM"
|
|
171
|
+
"Консультант SAP HCM"
|
|
172
|
+
"Консультант SAP MM/SD"
|
|
173
|
+
"Консультант SAP PI"
|
|
174
|
+
"Консультант SAP PM"
|
|
175
|
+
"Консультант SAP PP"
|
|
176
|
+
"Контент-мейкер"
|
|
177
|
+
"Контент-менеджер"
|
|
178
|
+
"Копирайтер"
|
|
179
|
+
"Корпоративный директор"
|
|
180
|
+
"Корпоративный психолог"
|
|
181
|
+
"Корректор"
|
|
182
|
+
"Лидер по инновациям"
|
|
183
|
+
"Логист"
|
|
184
|
+
"Маркетолог"
|
|
185
|
+
"Менеджер маркетплейса"
|
|
186
|
+
"Менеджер по некоммерческим закупкам"
|
|
187
|
+
"Менеджер по продажам"
|
|
188
|
+
"Менеджер по работе с ключевыми клиентами"
|
|
189
|
+
"Менеджер по работе с корпоративными клиентами"
|
|
190
|
+
"Менеджер по транспортной логистике"
|
|
191
|
+
"Менеджер продукта"
|
|
192
|
+
"Музыкальный продюсер"
|
|
193
|
+
"Музыкант"
|
|
194
|
+
"Мультипликатор"
|
|
195
|
+
"Независимый директор"
|
|
196
|
+
"Оператор call-центра"
|
|
197
|
+
"Операционный директор"
|
|
198
|
+
"Переводчик"
|
|
199
|
+
"Подкастер"
|
|
200
|
+
"Продакшн-менеджер"
|
|
201
|
+
"Продуктовый аналитик"
|
|
202
|
+
"Продуктовый дизайнер"
|
|
203
|
+
"Продуктовый маркетолог"
|
|
204
|
+
"Продюсер"
|
|
205
|
+
"Промпт-инженер"
|
|
206
|
+
"Разработчик BI"
|
|
207
|
+
"Разработчик BPMSoft"
|
|
208
|
+
"Разработчик Camunda"
|
|
209
|
+
"Разработчик Directum RX"
|
|
210
|
+
"Разработчик HCM"
|
|
211
|
+
"Разработчик HP IUM"
|
|
212
|
+
"Разработчик Low-code"
|
|
213
|
+
"Разработчик Navision"
|
|
214
|
+
"Разработчик Opentext"
|
|
215
|
+
"Разработчик SAP ABAP"
|
|
216
|
+
"Разработчик SAP BW/BO"
|
|
217
|
+
"Разработчик SAP Commerce"
|
|
218
|
+
"Разработчик SAP HCM"
|
|
219
|
+
"Разработчик SAS"
|
|
220
|
+
"Разработчик Sharepoint"
|
|
221
|
+
"Разработчик SQL"
|
|
222
|
+
"Разработчик T-SQL"
|
|
223
|
+
"Разработчик баз данных"
|
|
224
|
+
"Разработчик ЕХД"
|
|
225
|
+
"Разработчик офисных приложений"
|
|
226
|
+
"Разработчик чат-ботов"
|
|
227
|
+
"Редактор"
|
|
228
|
+
"Режиссер"
|
|
229
|
+
"Рекрутер"
|
|
230
|
+
"Ресечер"
|
|
231
|
+
"Ретушер"
|
|
232
|
+
"Руководитель отдела персонала"
|
|
233
|
+
"Руководитель отдела продаж"
|
|
234
|
+
"Руководитель проекта"
|
|
235
|
+
"Саунд-дизайнер"
|
|
236
|
+
"Секретарь"
|
|
237
|
+
"Сервис менеджер"
|
|
238
|
+
"Системный администратор"
|
|
239
|
+
"Системный аналитик"
|
|
240
|
+
"Системный аналитик 1С"
|
|
241
|
+
"Системный архитектор"
|
|
242
|
+
"Специалист по документообороту"
|
|
243
|
+
"Специалист по защите информации"
|
|
244
|
+
"Специалист по контролю качества"
|
|
245
|
+
"Специалист по лидогенерации"
|
|
246
|
+
"Специалист по математическому моделированию"
|
|
247
|
+
"Специалист по работе с CRM"
|
|
248
|
+
"Специалист по тендерным продажам"
|
|
249
|
+
"Специалист по управленческому учету"
|
|
250
|
+
"Специалист технической поддержки"
|
|
251
|
+
"Сценарист"
|
|
252
|
+
"Таргетолог"
|
|
253
|
+
"Тест-менеджер"
|
|
254
|
+
"Тестировщик 1С"
|
|
255
|
+
"Технический писатель"
|
|
256
|
+
"Технический специалист в фотостудию"
|
|
257
|
+
"Тренер"
|
|
258
|
+
"Финансовый аналитик"
|
|
259
|
+
"Финансовый директор"
|
|
260
|
+
"Финансовый консультант"
|
|
261
|
+
"Фотограф"
|
|
262
|
+
"Художник"
|
|
263
|
+
"Эксперт по внедрению ITSM-процессов"
|
|
264
|
+
"Эксперт по генеративному AI"
|
|
265
|
+
"Юрист"
|
package/package.json
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@iconicompany/imatchingcore",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Matching engine core library",
|
|
5
|
+
"main": "src/specializations/specializations-matching-engine.ts",
|
|
6
|
+
"module": "src/specializations/specializations-matching-engine.ts",
|
|
7
|
+
"types": "src/specializations/specializations-matching-engine.ts",
|
|
8
|
+
"type": "module",
|
|
9
|
+
"scripts": {
|
|
10
|
+
"test": "bun test specializations-matching-engine.test.ts",
|
|
11
|
+
"build": "echo 'No build step required - TypeScript source is published'"
|
|
12
|
+
},
|
|
13
|
+
"devDependencies": {
|
|
14
|
+
"@types/bun": "latest"
|
|
15
|
+
},
|
|
16
|
+
"peerDependencies": {
|
|
17
|
+
"typescript": "^5"
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { expect, test, describe } from "bun:test";
|
|
2
|
+
import { SpecializationsMatchingFactory } from "./specializations-matching-factory";
|
|
3
|
+
|
|
4
|
+
describe("SpecializationsMatchingEngine Benchmark", () => {
|
|
5
|
+
test("should match provided job titles and compare with expected results", async () => {
|
|
6
|
+
const engine = await SpecializationsMatchingFactory.create();
|
|
7
|
+
|
|
8
|
+
const testCases: { input: string; expected: string }[] = [
|
|
9
|
+
{ input: "UX/UI DE-10912 RedLab", expected: "UX/UI дизайнер" },
|
|
10
|
+
{ input: "1С разработчик RedLab 1С 10910", expected: "1С разработчик" },
|
|
11
|
+
{ input: "Ведущий специалист DevOps Senior 🆔DO 10911 RedLab", expected: "DevOps" },
|
|
12
|
+
{ input: "DO 10879 DevOps", expected: "DevOps" },
|
|
13
|
+
{ input: "Инженер DevOps", expected: "DevOps" },
|
|
14
|
+
{ input: "Frontend developer (Senior) 🆔FE-10908", expected: "Frontend разработчик" },
|
|
15
|
+
{ input: "Data Scientist Middle в МТС ДИДЖИТАЛ П2026-53", expected: "Data Scientist" },
|
|
16
|
+
{ input: "QA Функциональное тестирование 🆔Qa-1848", expected: "QA ручной" },
|
|
17
|
+
{ input: "Ведущий Системный Аналитик SA-10907", expected: "Системный аналитик" },
|
|
18
|
+
{ input: "Middle DevOps-инженер на Проект внедрения VK Data Platform", expected: "DevOps" },
|
|
19
|
+
{ input: "Системный аналитик Senior в МосБиржа [П2026-55]", expected: "Системный аналитик" },
|
|
20
|
+
{ input: "Инженер DevOps Middle/Senior [10904]", expected: "DevOps" },
|
|
21
|
+
{ input: "Консультант TM 🆔10899", expected: "" },
|
|
22
|
+
{ input: "Java Backend Developer Middle BE-10906", expected: "Java разработчик" },
|
|
23
|
+
{ input: "Фуллстек аналитик, SA1-SA3", expected: "Системный аналитик" },
|
|
24
|
+
{ input: "Фуллстек аналитик, SA1-SA3 ID 10903", expected: "Системный аналитик" },
|
|
25
|
+
{ input: "Разработчик PHP 🆔 BE-10905", expected: "PHP разработчик" },
|
|
26
|
+
{ input: "Data Engineer (Senior) BD-10905", expected: "Data инженер" },
|
|
27
|
+
{ input: "Аналитик 1С Управленческий учет 1С 10903", expected: "Аналитик 1С" },
|
|
28
|
+
{ input: "DevOps 10902", expected: "DevOps" },
|
|
29
|
+
{ input: "QA HT 🆔Qa-10901", expected: "" },
|
|
30
|
+
{ input: "Инженер DevOps Middle/Senior [10904]", expected: "DevOps" },
|
|
31
|
+
{ input: "Инженер DevOps", expected: "DevOps" },
|
|
32
|
+
{ input: "Консультант TM 10899", expected: "" },
|
|
33
|
+
{ input: "Бизнес аналитик Senior", expected: "Бизнес аналитик" },
|
|
34
|
+
{ input: "Аналитик Senior SA-10894", expected: "Системный аналитик" },
|
|
35
|
+
{ input: "Ведущий разработчик back-end 🆔10896", expected: "Backend разработчик" },
|
|
36
|
+
{ input: "Интеграционный QA /Middle QA-10891", expected: "" },
|
|
37
|
+
{ input: "Аналитик Middle SA-10897", expected: "" },
|
|
38
|
+
{ input: "Бизнес-аналитик Senior в МосБиржа #П2026-54", expected: "Бизнес аналитик" },
|
|
39
|
+
{ input: "Data Scientist Middle в МТС ДИДЖИТАЛ П2026-53", expected: "Data Scientist" },
|
|
40
|
+
{ input: "DevOps Senior в МТС ДИДЖИТАЛ П2026-51", expected: "DevOps" },
|
|
41
|
+
{ input: "Java разработчик/Middle/Middle+ №4295643", expected: "Java разработчик" },
|
|
42
|
+
{ input: "Нагрузочное тестирование Middle в МТС ДИДЖИТАЛ П2026-52", expected: "QA нагрузочный" },
|
|
43
|
+
{ input: "Разработчик Java Middle в МТС ДИДЖИТАЛ", expected: "Java разработчик" },
|
|
44
|
+
{ input: "Аналитик Senior SA-10893", expected: "Системный аналитик" },
|
|
45
|
+
{ input: "React разработчик (Middle) FE-10895", expected: "React разработчик" },
|
|
46
|
+
{ input: "DevOps (телеком) КРОК [150126]", expected: "DevOps" },
|
|
47
|
+
{ input: "Интеграционный QA (телеком) КРОК [140126]", expected: "" },
|
|
48
|
+
{ input: "Коллеги, всем привет, актуальные потребности ITFB на 20 января", expected: "" },
|
|
49
|
+
{ input: "Системный аналитик Senior ЛеманаПРО ITFB [Номер потребности: П2026-47]", expected: "Системный аналитик" },
|
|
50
|
+
{ input: "Системный аналитик Senior в ЛеманаПРО", expected: "Системный аналитик" },
|
|
51
|
+
{ input: "Системный аналитик Senior в ЛеманаПРО П2026-48", expected: "Системный аналитик" },
|
|
52
|
+
{ input: "Системный аналитик Middle в ЛеманаПРО П2026-49", expected: "Системный аналитик" },
|
|
53
|
+
{ input: "Front-end разработчик (Senior) 🆔FE-10892", expected: "Frontend разработчик" },
|
|
54
|
+
{ input: "Дизайнер DE-10890 RedLab", expected: "Продуктовый дизайнер" },
|
|
55
|
+
{ input: "Full‑Stack разработчик Vue.js", expected: "Full Stack разработчик" },
|
|
56
|
+
{ input: ".NET разработчик Middle/Middle+ BE-10889", expected: ".NET разработчик" },
|
|
57
|
+
{ input: "1С-аналитик Senior в МосБиржа П2026-45", expected: "Аналитик 1С" },
|
|
58
|
+
{ input: "Консультант СЭД Middle в X5", expected: "" },
|
|
59
|
+
{ input: "Системный аналитик Senior на проект Банка ID 120126", expected: "Системный аналитик" },
|
|
60
|
+
{ input: "PHP разработчик_Senior BE-10887", expected: "PHP разработчик" },
|
|
61
|
+
{ input: "Инженер NLP/PLP (телеком) [130126]", expected: "Инженер NLP/PLP" },
|
|
62
|
+
{ input: "Консультант SAP TM Senior в X5 П2026-32", expected: "" },
|
|
63
|
+
{ input: "Разработчик ETL/ELT (DWH) BD-10886", expected: "DWH разработчик" },
|
|
64
|
+
{ input: "Дизайнер Senior в X5", expected: "Продуктовый дизайнер" },
|
|
65
|
+
{ input: "Консультант SAP TM Senior в X5 П2026-38", expected: "" },
|
|
66
|
+
{ input: "QA (АТ) Senior Java [QA-10865]", expected: "QA авто" },
|
|
67
|
+
{ input: "QA (АТ) Middle QA-10866", expected: "QA авто" },
|
|
68
|
+
{ input: "ML разработчик BD-10884 RedLab", expected: "ML разработчик" },
|
|
69
|
+
{ input: "ML аналитик BD-10885 RedLab", expected: "ML разработчик" },
|
|
70
|
+
{ input: "Скрам-мастер [10883]", expected: "Scrum Master" },
|
|
71
|
+
{ input: "PHP", expected: "PHP разработчик" },
|
|
72
|
+
{ input: "Разработчик Backend.net [BE-10880]", expected: ".NET разработчик" },
|
|
73
|
+
{ input: "Программист проекта миграции ХД с Oracle на Greenplum (Senior) 🆔BD-10882", expected: "DWH разработчик" },
|
|
74
|
+
{ input: "React разработчик (Middle) FE-10881", expected: "React разработчик" },
|
|
75
|
+
{ input: "DevOps Senior в X5 П2026-42", expected: "DevOps" },
|
|
76
|
+
{ input: "DO 10879 DevOps RedLab", expected: "DevOps" },
|
|
77
|
+
{ input: "Разработчик Java Middle в МосБиржа [Номер потребности: П2026-28]", expected: "Java разработчик" },
|
|
78
|
+
{ input: "Аналитик (БА/СА) middle+/senior", expected: "Бизнес/системный аналитик" },
|
|
79
|
+
{ input: "Консультант (Менеджер инвентаризации РЦ) Разработ Senior в X5 П2026-37", expected: "" },
|
|
80
|
+
{ input: "Консультант СЭД Настройка Middle в X5 [Номер потребности: П2026-39]", expected: "" },
|
|
81
|
+
{ input: "PHP-разработчик Senior в X5 #П2026-40", expected: "PHP разработчик" },
|
|
82
|
+
{ input: "QA Middle+ 🆔1804", expected: "QA ручной" },
|
|
83
|
+
{ input: "ИТ-Лидер команды 10879", expected: "" },
|
|
84
|
+
{ input: "Product Manager 🆔10877", expected: "Product owner" },
|
|
85
|
+
{ input: "Системный Аналитик DWH / Data-инженер от Middle в банк КРОК", expected: "Системный аналитик" },
|
|
86
|
+
{ input: "QA Middle+", expected: "QA ручной" },
|
|
87
|
+
{ input: "Разработчик Java Middle в БКС П2026-33", expected: "Java разработчик" },
|
|
88
|
+
{ input: "Нагрузочное тестирование Senior в X5 ITFB", expected: "QA нагрузочный" },
|
|
89
|
+
{ input: "Java- разработчик 65 apps", expected: "Java разработчик" },
|
|
90
|
+
{ input: "Java- разработчик 65apps [ID: 733]", expected: "Java разработчик" },
|
|
91
|
+
{ input: "Промпт-инженер 🆔10839", expected: "Промпт-инженер" },
|
|
92
|
+
{ input: "Бэкенд-разработчик Middle BE-10878 RedLab", expected: "Backend разработчик" },
|
|
93
|
+
{ input: "Консультант SAP BW Senior в X5 П2026-31", expected: "Консультант SAP BW/BI" },
|
|
94
|
+
{ input: "Frontend Middle в нефтеперерабатывающую компанию", expected: "Frontend разработчик" },
|
|
95
|
+
{ input: "Автотестер Middle", expected: "QA авто" },
|
|
96
|
+
{ input: "1С-Руководитель проекта Senior+ в МосБиржа П2026-27", expected: "Руководитель проекта" },
|
|
97
|
+
{ input: "DevOps Senior", expected: "DevOps" },
|
|
98
|
+
{ input: "Data Engineer Senior в МосБиржа [Номер потребности: П2026-25]", expected: "Data инженер" },
|
|
99
|
+
{ input: "Дизайнер Senior в X5 П2026-29", expected: "Продуктовый дизайнер" },
|
|
100
|
+
{ input: "Middle+ Бизнес аналитик на Проект внедрения КЭДО", expected: "Бизнес аналитик" },
|
|
101
|
+
{ input: "БА", expected: "Бизнес аналитик" },
|
|
102
|
+
{ input: "БА", expected: "Бизнес аналитик" },
|
|
103
|
+
{ input: "1С-Руководитель проекта Senior+ в МосБиржа П2026-14", expected: "Руководитель проекта" },
|
|
104
|
+
{ input: "Data инженер (Senior) 🆔BD-10848", expected: "Data инженер" },
|
|
105
|
+
{ input: "SAP разработчики 10874", expected: "Разработчик SAP ABAP" },
|
|
106
|
+
{ input: "Консультант AirWatch 2 линия [10876]", expected: "" },
|
|
107
|
+
{ input: "Системный аналитик RedLab AN-10867", expected: "Системный аналитик" },
|
|
108
|
+
{ input: "DevOps_Middle+/Senior RedLab [DO 10873]", expected: "DevOps" },
|
|
109
|
+
];
|
|
110
|
+
|
|
111
|
+
console.log(`\nMatching Benchmark Results:\n`);
|
|
112
|
+
console.log(`| Job Name | Expected | New Result (Words) | Score | Match? |`);
|
|
113
|
+
console.log(`| :--- | :--- | :--- | :--- | :--- |`);
|
|
114
|
+
|
|
115
|
+
let passed = 0;
|
|
116
|
+
for (const { input, expected } of testCases) {
|
|
117
|
+
const result = engine.match(input);
|
|
118
|
+
const newResult = result?.specialization || "---";
|
|
119
|
+
const score = result?.score.toFixed(2) || "0.00";
|
|
120
|
+
const isCorrect = expected === "" ? newResult === "---" : newResult === expected;
|
|
121
|
+
|
|
122
|
+
if (isCorrect) passed++;
|
|
123
|
+
|
|
124
|
+
console.log(`| ${input} | ${expected || "---"} | ${newResult} | ${score} | ${isCorrect ? "✅" : "❌"} |`);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
console.log(`\nFinal Score: ${passed}/${testCases.length} (${((passed / testCases.length) * 100).toFixed(2)}%)\n`);
|
|
128
|
+
|
|
129
|
+
const mismatches = testCases
|
|
130
|
+
.map(({ input, expected }) => {
|
|
131
|
+
const result = engine.match(input);
|
|
132
|
+
const got = result?.specialization || "---";
|
|
133
|
+
const target = expected || "---";
|
|
134
|
+
return { input, expected: target, got };
|
|
135
|
+
})
|
|
136
|
+
.filter(m => m.got !== m.expected);
|
|
137
|
+
|
|
138
|
+
expect(mismatches).toEqual([]);
|
|
139
|
+
});
|
|
140
|
+
});
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
export interface WordSynonym {
|
|
2
|
+
src: string;
|
|
3
|
+
dst: string | null;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export interface WordWeight {
|
|
7
|
+
word: string;
|
|
8
|
+
weight: number;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export class SpecializationsMatchingEngine {
|
|
12
|
+
private synonyms: Map<string, string | null>;
|
|
13
|
+
private weights: Map<string, number>;
|
|
14
|
+
private normalizedSpecs: { original: string; words: string[] }[];
|
|
15
|
+
private stopWords: Set<string>;
|
|
16
|
+
|
|
17
|
+
constructor(
|
|
18
|
+
specializationNames: string[],
|
|
19
|
+
synonyms: WordSynonym[],
|
|
20
|
+
weights: WordWeight[],
|
|
21
|
+
stopWords: string[]
|
|
22
|
+
) {
|
|
23
|
+
this.synonyms = new Map();
|
|
24
|
+
for (const s of synonyms) {
|
|
25
|
+
this.synonyms.set(this.replaceHomoglyphs(s.src.toLowerCase()), s.dst ? s.dst.toLowerCase() : null);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// 1. Initialize weights
|
|
29
|
+
this.weights = new Map();
|
|
30
|
+
for (const w of weights) {
|
|
31
|
+
const words = this.normalize(w.word).split(/\s+/).filter(tk => tk.length > 0);
|
|
32
|
+
for (const token of words) {
|
|
33
|
+
this.weights.set(token, w.weight);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// 2. Initialize stopwords
|
|
38
|
+
this.stopWords = new Set(stopWords.map(w => this.replaceHomoglyphs(w.toLowerCase())));
|
|
39
|
+
|
|
40
|
+
// 3. Pre-normalize specializations
|
|
41
|
+
this.normalizedSpecs = specializationNames.map(name => {
|
|
42
|
+
const normalized = this.normalize(name);
|
|
43
|
+
return {
|
|
44
|
+
original: name,
|
|
45
|
+
words: normalized.split(/\s+/).filter(w => !this.stopWords.has(w) && w.length >= 2)
|
|
46
|
+
};
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
public normalize(text: string): string {
|
|
51
|
+
let result = text.toLowerCase();
|
|
52
|
+
|
|
53
|
+
// MUST be before homoglyph replacement
|
|
54
|
+
result = result.replace(/front[-\s]+end/g, 'frontend');
|
|
55
|
+
result = result.replace(/back[-\s]+end/g, 'backend');
|
|
56
|
+
result = result.replace(/full[-\s]+stack/g, 'fullstack');
|
|
57
|
+
result = result.replace(/ux[-\s]*ui/g, 'uxui');
|
|
58
|
+
result = result.replace(/ui[-\s]*ux/g, 'uxui');
|
|
59
|
+
|
|
60
|
+
result = this.replaceHomoglyphs(result);
|
|
61
|
+
result = result.replace(/дwh/g, 'dwh');
|
|
62
|
+
|
|
63
|
+
result = result.replace(/[^a-zа-я0-9]/gi, ' ');
|
|
64
|
+
|
|
65
|
+
const words = result.split(/\s+/).filter(w => w.length > 0);
|
|
66
|
+
|
|
67
|
+
const normalizedWords = words.map(w => {
|
|
68
|
+
let current = w;
|
|
69
|
+
|
|
70
|
+
// Global synonym
|
|
71
|
+
if (this.synonyms && this.synonyms.has(current)) {
|
|
72
|
+
current = this.synonyms.get(current) || '';
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return current;
|
|
76
|
+
}).filter(w => w.length > 0);
|
|
77
|
+
|
|
78
|
+
return normalizedWords.join(' ').trim();
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
private replaceHomoglyphs(text: string): string {
|
|
82
|
+
const map: Record<string, string> = {
|
|
83
|
+
'a': 'а', 'c': 'с', 'e': 'е', 'o': 'о', 'p': 'р', 'x': 'х', 'y': 'у'
|
|
84
|
+
};
|
|
85
|
+
return text.replace(/[aceopxy]/g, m => map[m]!);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
public weightedCoverageRatio(specWords: string[], textWords: string[]): number {
|
|
89
|
+
if (specWords.length === 0 || textWords.length === 0) return 0;
|
|
90
|
+
|
|
91
|
+
let specTotalWeight = 0;
|
|
92
|
+
let textTotalWeight = 0;
|
|
93
|
+
let matchedWeight = 0;
|
|
94
|
+
|
|
95
|
+
const specSet = new Set(specWords);
|
|
96
|
+
const textSet = new Set(textWords);
|
|
97
|
+
|
|
98
|
+
const isMarker = (w: string) => /^\d+$/.test(w) || /^п\d+$/i.test(w) || (w.length > 5 && /\d/.test(w));
|
|
99
|
+
|
|
100
|
+
const getWeight = (word: string) => {
|
|
101
|
+
const lookup = this.replaceHomoglyphs(word.toLowerCase());
|
|
102
|
+
return this.weights.get(lookup) || 1.0;
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
for (const word of specSet) {
|
|
106
|
+
if (this.stopWords.has(word) || isMarker(word) || word.length < 2) continue;
|
|
107
|
+
const weight = getWeight(word);
|
|
108
|
+
specTotalWeight += weight;
|
|
109
|
+
if (textSet.has(word)) matchedWeight += weight;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
for (const word of textSet) {
|
|
113
|
+
if (this.stopWords.has(word) || isMarker(word) || word.length < 2) continue;
|
|
114
|
+
textTotalWeight += getWeight(word);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (specTotalWeight === 0) return 0;
|
|
118
|
+
return matchedWeight / Math.max(specTotalWeight, textTotalWeight);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
public match(text: string): { specialization: string; score: number } | null {
|
|
122
|
+
const normalizedText = this.normalize(text);
|
|
123
|
+
const textWords = normalizedText.split(/\s+/).filter(w => w.length > 0);
|
|
124
|
+
const filteredTextWords = textWords.filter(w => !this.stopWords.has(w) && w.length >= 2);
|
|
125
|
+
|
|
126
|
+
let bestMatch: { specialization: string; score: number } | null = null;
|
|
127
|
+
|
|
128
|
+
for (const spec of this.normalizedSpecs) {
|
|
129
|
+
const score = this.weightedCoverageRatio(spec.words, filteredTextWords);
|
|
130
|
+
|
|
131
|
+
if (score >= 0.25 && (!bestMatch || score > bestMatch.score)) {
|
|
132
|
+
bestMatch = { specialization: spec.original, score };
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return bestMatch;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import { SpecializationsMatchingEngine, type WordSynonym, type WordWeight } from './specializations-matching-engine';
|
|
3
|
+
|
|
4
|
+
export class SpecializationsMatchingFactory {
|
|
5
|
+
public static async create(): Promise<SpecializationsMatchingEngine> {
|
|
6
|
+
const csvPath = path.resolve(process.cwd(), 'data/specializations/specializations.csv');
|
|
7
|
+
const fileContent = await Bun.file(csvPath).text();
|
|
8
|
+
|
|
9
|
+
const specNames = fileContent
|
|
10
|
+
.split('\n')
|
|
11
|
+
.map(line => line.trim())
|
|
12
|
+
.filter(line => line && line !== '"Название"')
|
|
13
|
+
.map(line => line.replace(/^"(.*)"$/, '$1'));
|
|
14
|
+
|
|
15
|
+
// Filtered and refined synonyms
|
|
16
|
+
const synonymsData: WordSynonym[] = [
|
|
17
|
+
{ src: 'dev', dst: 'разработчик' },
|
|
18
|
+
{ src: 'manual', dst: 'ручной' },
|
|
19
|
+
{ src: 'automation', dst: 'авто' },
|
|
20
|
+
{ src: 'director', dst: 'директор' },
|
|
21
|
+
{ src: 'head', dst: 'руководитель' },
|
|
22
|
+
{ src: 'cpo', dst: 'директор' },
|
|
23
|
+
{ src: 'cto', dst: 'директор' },
|
|
24
|
+
{ src: 'cio', dst: 'директор' },
|
|
25
|
+
{ src: 'cdo', dst: 'директор' },
|
|
26
|
+
{ src: 'product', dst: 'продукт' },
|
|
27
|
+
{ src: 'project', dst: 'проект' },
|
|
28
|
+
{ src: 'delivery', dst: 'деливери' },
|
|
29
|
+
{ src: 'teamlead', dst: 'руководитель' },
|
|
30
|
+
{ src: 'team', dst: null },
|
|
31
|
+
{ src: 'senior', dst: null },
|
|
32
|
+
{ src: 'middle', dst: null },
|
|
33
|
+
{ src: 'junior', dst: null },
|
|
34
|
+
{ src: 'staff', dst: null },
|
|
35
|
+
{ src: 'principal', dst: null },
|
|
36
|
+
{ src: 'expert', dst: null },
|
|
37
|
+
{ src: 'specialist', dst: null },
|
|
38
|
+
{ src: 'главный', dst: null },
|
|
39
|
+
{ src: 'ведущий', dst: null },
|
|
40
|
+
{ src: 'старший', dst: null },
|
|
41
|
+
{ src: 'начальник', dst: null },
|
|
42
|
+
{ src: 'заместитель', dst: null },
|
|
43
|
+
{ src: 'зам', dst: null },
|
|
44
|
+
{ src: '1c', dst: '1с' },
|
|
45
|
+
{ src: 'pm', dst: 'проект' },
|
|
46
|
+
{ src: 'hr', dst: 'персонал' },
|
|
47
|
+
{ src: 'recruit', dst: 'рекрутер' },
|
|
48
|
+
{ src: 'legal', dst: 'юрист' },
|
|
49
|
+
{ src: 'lawyer', dst: 'юрист' },
|
|
50
|
+
{ src: 'accounting', dst: 'бухгалтер' },
|
|
51
|
+
{ src: 'finan', dst: 'финанс' },
|
|
52
|
+
{ src: 'wms', dst: 'склад' },
|
|
53
|
+
{ src: 'склад', dst: 'склад' },
|
|
54
|
+
{ src: 'маркетолог', dst: 'маркетолог' },
|
|
55
|
+
{ src: 'performance', dst: 'эффективность' },
|
|
56
|
+
{ src: 'transformation', dst: 'трансформация' },
|
|
57
|
+
{ src: 'digital', dst: 'цифровой' },
|
|
58
|
+
{ src: 'data', dst: 'дата' },
|
|
59
|
+
{ src: 'ai', dst: 'ии' },
|
|
60
|
+
{ src: 'ml', dst: 'ии' },
|
|
61
|
+
{ src: 'vision', dst: 'ии' },
|
|
62
|
+
{ src: 'cv', dst: 'ии' },
|
|
63
|
+
{ src: 'unreal', dst: 'разработчик' },
|
|
64
|
+
{ src: 'unity', dst: 'разработчик' },
|
|
65
|
+
{ src: 'game', dst: 'разработчик' },
|
|
66
|
+
{ src: 'abap', dst: 'abap' },
|
|
67
|
+
{ src: 'basis', dst: 'basis' },
|
|
68
|
+
{ src: 'hcm', dst: 'hcm' },
|
|
69
|
+
{ src: 'ewm', dst: 'ewm' },
|
|
70
|
+
{ src: 'bw', dst: 'bw' },
|
|
71
|
+
{ src: 'mm', dst: 'mm' },
|
|
72
|
+
{ src: 'sd', dst: 'sd' },
|
|
73
|
+
{ src: 'pp', dst: 'pp' },
|
|
74
|
+
{ src: 'pi', dst: 'pi' },
|
|
75
|
+
{ src: 'po', dst: 'po' },
|
|
76
|
+
{ src: 'fi', dst: 'fi' },
|
|
77
|
+
{ src: 'co', dst: 'co' },
|
|
78
|
+
{ src: 'network', dst: 'сетевой' },
|
|
79
|
+
{ src: 'система', dst: 'системный' },
|
|
80
|
+
{ src: 'system', dst: 'системный' },
|
|
81
|
+
{ src: 'administration', dst: 'администратор' },
|
|
82
|
+
{ src: 'administrator', dst: 'администратор' },
|
|
83
|
+
{ src: 'админ', dst: 'администратор' },
|
|
84
|
+
{ src: 'инфраструктура', dst: 'инфраструктура' },
|
|
85
|
+
{ src: 'agile', dst: 'agile' },
|
|
86
|
+
{ src: 'coach', dst: 'коуч' },
|
|
87
|
+
{ src: 'поддержка', dst: 'поддержка' },
|
|
88
|
+
{ src: 'сопровождение', dst: 'сопровождение' },
|
|
89
|
+
{ src: 'tech', dst: 'тех' },
|
|
90
|
+
{ src: 'technical', dst: 'тех' },
|
|
91
|
+
{ src: 'технический', dst: 'тех' },
|
|
92
|
+
{ src: 'информационный', dst: 'инфо' },
|
|
93
|
+
{ src: 'безопасность', dst: 'безопасность' },
|
|
94
|
+
{ src: 'security', dst: 'безопасность' },
|
|
95
|
+
{ src: 'cyber', dst: 'безопасность' },
|
|
96
|
+
{ src: 'cloud', dst: 'облако' },
|
|
97
|
+
{ src: 'sre', dst: 'sre' },
|
|
98
|
+
{ src: 'javascript', dst: 'javascript' },
|
|
99
|
+
{ src: 'js', dst: 'javascript' },
|
|
100
|
+
{ src: 'node', dst: 'nodejs' },
|
|
101
|
+
{ src: 'nodejs', dst: 'nodejs' },
|
|
102
|
+
{ src: 'nest', dst: 'nestjs' },
|
|
103
|
+
{ src: 'nestjs', dst: 'nestjs' },
|
|
104
|
+
{ src: 'ios', dst: 'ios' },
|
|
105
|
+
{ src: 'android', dst: 'android' },
|
|
106
|
+
{ src: 'мобильный', dst: 'мобильный' },
|
|
107
|
+
{ src: 'разработка', dst: 'разработчик' },
|
|
108
|
+
{ src: 'аналитика', dst: 'аналитик' },
|
|
109
|
+
{ src: 'аналитический', dst: 'аналитик' },
|
|
110
|
+
{ src: 'бизнес', dst: 'бизнес' },
|
|
111
|
+
{ src: 'продуктовый', dst: 'продукт' },
|
|
112
|
+
{ src: 'проектов', dst: 'проект' },
|
|
113
|
+
{ src: 'тимлид', dst: 'руководитель' },
|
|
114
|
+
{ src: 'техлид', dst: 'руководитель' },
|
|
115
|
+
{ src: 'автоматизатор', dst: 'авто' },
|
|
116
|
+
{ src: 'автоматизированное', dst: 'авто' },
|
|
117
|
+
{ src: 'dba', dst: 'администратор баз данных' },
|
|
118
|
+
{ src: 'fintech', dst: null },
|
|
119
|
+
{ src: 'retail', dst: null },
|
|
120
|
+
{ src: 'bank', dst: null },
|
|
121
|
+
{ src: 'trading', dst: null },
|
|
122
|
+
{ src: 'crypto', dst: null },
|
|
123
|
+
{ src: 'blockchain', dst: null },
|
|
124
|
+
{ src: 'web3', dst: null },
|
|
125
|
+
{ src: 'llm', dst: 'ии' },
|
|
126
|
+
{ src: 'prompt', dst: 'ии' },
|
|
127
|
+
{ src: 'orchestrator', dst: 'ии' },
|
|
128
|
+
{ src: 'analytics', dst: 'аналитик' },
|
|
129
|
+
{ src: 'scientist', dst: 'аналитик' },
|
|
130
|
+
{ src: 'science', dst: 'аналитик' },
|
|
131
|
+
{ src: 'c#', dst: 'csharp' },
|
|
132
|
+
{ src: 'c++', dst: 'cpp' },
|
|
133
|
+
{ src: 'of', dst: null },
|
|
134
|
+
{ src: 'and', dst: null },
|
|
135
|
+
{ src: 'in', dst: null },
|
|
136
|
+
{ src: 'for', dst: null },
|
|
137
|
+
{ src: 'with', dst: null },
|
|
138
|
+
{ src: 'группы', dst: null },
|
|
139
|
+
{ src: 'команды', dst: null },
|
|
140
|
+
{ src: 'направления', dst: null },
|
|
141
|
+
{ src: 'отдела', dst: null },
|
|
142
|
+
{ src: 'сектора', dst: null },
|
|
143
|
+
{ src: 'управления', dst: null },
|
|
144
|
+
{ src: 'функции', dst: null },
|
|
145
|
+
{ src: 'практики', dst: null },
|
|
146
|
+
{ src: 'центра', dst: null },
|
|
147
|
+
{ src: 'central', dst: null },
|
|
148
|
+
{ src: 'center', dst: null },
|
|
149
|
+
{ src: 'global', dst: null },
|
|
150
|
+
{ src: 'local', dst: null },
|
|
151
|
+
{ src: 'regional', dst: null },
|
|
152
|
+
{ src: 'area', dst: null },
|
|
153
|
+
{ src: 'chief', dst: null },
|
|
154
|
+
{ src: 'officer', dst: null },
|
|
155
|
+
{ src: 'development', dst: null },
|
|
156
|
+
{ src: 'management', dst: null },
|
|
157
|
+
{ src: 'controller', dst: null },
|
|
158
|
+
{ src: 'associate', dst: null },
|
|
159
|
+
{ src: 'assistant', dst: null },
|
|
160
|
+
{ src: 'ассистент', dst: null },
|
|
161
|
+
{ src: 'помощник', dst: null },
|
|
162
|
+
{ src: 'стажер', dst: null },
|
|
163
|
+
{ src: 'intern', dst: null },
|
|
164
|
+
{ src: 'trainee', dst: null },
|
|
165
|
+
{ src: 'разработки', dst: null },
|
|
166
|
+
{ src: 'проектами', dst: null },
|
|
167
|
+
{ src: 'информационных', dst: null },
|
|
168
|
+
{ src: 'технологий', dst: null },
|
|
169
|
+
{ src: 'it', dst: null },
|
|
170
|
+
{ src: 'ит', dst: null },
|
|
171
|
+
|
|
172
|
+
// Local synonyms merged
|
|
173
|
+
{ src: 'uxui', dst: 'дизайнер' },
|
|
174
|
+
{ src: 'ux', dst: 'дизайнер' },
|
|
175
|
+
{ src: 'ui', dst: 'дизайнер' },
|
|
176
|
+
{ src: 'sa', dst: 'системный аналитик' },
|
|
177
|
+
{ src: 'ba', dst: 'бизнес аналитик' },
|
|
178
|
+
{ src: 'са', dst: 'системный аналитик' },
|
|
179
|
+
{ src: 'ба', dst: 'бизнес аналитик' },
|
|
180
|
+
{ src: 'be', dst: 'backend' },
|
|
181
|
+
{ src: 'fe', dst: 'frontend' },
|
|
182
|
+
{ src: 'do', dst: 'devops' },
|
|
183
|
+
{ src: 'qa', dst: 'тестировщик' },
|
|
184
|
+
{ src: 'developer', dst: 'разработчик' },
|
|
185
|
+
{ src: 'engineer', dst: 'инженер' },
|
|
186
|
+
{ src: 'analyst', dst: 'аналитик' },
|
|
187
|
+
{ src: 'scrum', dst: 'скрам' },
|
|
188
|
+
{ src: 'master', dst: 'мастер' },
|
|
189
|
+
{ src: 'backend', dst: 'бэкенд' },
|
|
190
|
+
{ src: 'frontend', dst: 'фронтенд' },
|
|
191
|
+
{ src: 'fullstack', dst: 'фуллстек' },
|
|
192
|
+
{ src: 'mobile', dst: 'мобильный' },
|
|
193
|
+
{ src: 'ios', dst: 'иос' },
|
|
194
|
+
{ src: 'android', dst: 'андроид' },
|
|
195
|
+
{ src: 'scientist', dst: 'сайентист' },
|
|
196
|
+
{ src: 'data', dst: 'дата' },
|
|
197
|
+
{ src: 'architect', dst: 'архитектор' },
|
|
198
|
+
{ src: 'lead', dst: 'лид' },
|
|
199
|
+
{ src: 'manager', dst: 'менеджер' },
|
|
200
|
+
{ src: 'owner', dst: 'овнер' },
|
|
201
|
+
{ src: 'тестирование', dst: 'тестировщик' },
|
|
202
|
+
{ src: 'нагрузочное', dst: 'нагрузочный' },
|
|
203
|
+
{ src: 'функциональное', dst: 'ручной' },
|
|
204
|
+
{ src: 'разработки', dst: 'разработчик' },
|
|
205
|
+
{ src: 'программист', dst: 'разработчик' },
|
|
206
|
+
];
|
|
207
|
+
|
|
208
|
+
const weightsData: WordWeight[] = [
|
|
209
|
+
{ word: 'frontend', weight: 0.5 },
|
|
210
|
+
{ word: 'backend', weight: 0.5 },
|
|
211
|
+
{ word: 'react', weight: 1.2 },
|
|
212
|
+
{ word: 'angular', weight: 1.2 },
|
|
213
|
+
{ word: 'vue', weight: 1.2 },
|
|
214
|
+
{ word: 'qa', weight: 2.0 },
|
|
215
|
+
{ word: 'авто', weight: 1.8 },
|
|
216
|
+
{ word: 'java', weight: 1.2 },
|
|
217
|
+
{ word: 'python', weight: 1.2 },
|
|
218
|
+
{ word: 'javascript', weight: 2.0 },
|
|
219
|
+
{ word: 'csharp', weight: 2.0 },
|
|
220
|
+
{ word: 'cpp', weight: 2.0 },
|
|
221
|
+
{ word: 'php', weight: 1.2 },
|
|
222
|
+
{ word: 'go', weight: 2.0 },
|
|
223
|
+
{ word: 'kotlin', weight: 2.0 },
|
|
224
|
+
{ word: 'swift', weight: 2.0 },
|
|
225
|
+
{ word: 'sql', weight: 2.0 },
|
|
226
|
+
{ word: 'sap', weight: 2.0 },
|
|
227
|
+
{ word: '1с', weight: 1.0 },
|
|
228
|
+
{ word: 'ии', weight: 2.0 },
|
|
229
|
+
{ word: 'data', weight: 1.5 },
|
|
230
|
+
{ word: 'dwh', weight: 1.5 },
|
|
231
|
+
{ word: 'bi', weight: 1.5 },
|
|
232
|
+
{ word: 'developer', weight: 0.3 },
|
|
233
|
+
{ word: 'engineer', weight: 0.3 },
|
|
234
|
+
{ word: 'разработчик', weight: 0.3 },
|
|
235
|
+
{ word: 'инженер', weight: 0.3 },
|
|
236
|
+
{ word: 'менеджер', weight: 0.3 },
|
|
237
|
+
{ word: 'руководитель', weight: 0.8 },
|
|
238
|
+
{ word: 'аналитик', weight: 0.3 },
|
|
239
|
+
{ word: 'дизайнер', weight: 0.3 },
|
|
240
|
+
{ word: 'тестировщик', weight: 0.3 },
|
|
241
|
+
{ word: 'архитектор', weight: 0.3 },
|
|
242
|
+
{ word: 'консультант', weight: 0.2 },
|
|
243
|
+
{ word: 'администратор', weight: 0.3 },
|
|
244
|
+
{ word: 'специалист', weight: 0.1 },
|
|
245
|
+
{ word: 'лид', weight: 0.3 },
|
|
246
|
+
{ word: 'devops', weight: 2.5 },
|
|
247
|
+
{ word: 'scrum', weight: 2.5 },
|
|
248
|
+
{ word: 'jira', weight: 1.5 },
|
|
249
|
+
{ word: 'etl', weight: 1.5 },
|
|
250
|
+
];
|
|
251
|
+
|
|
252
|
+
const stopWords = [
|
|
253
|
+
'в', 'на', 'с', 'к', 'по', 'из', 'от', 'до', 'у', 'о', 'об', 'за', 'над', 'под', 'при', 'для',
|
|
254
|
+
'и', 'а', 'но', 'да', 'или', 'как', 'так', 'что', 'чтобы', 'если', 'хотя', 'не', 'ни', 'же', 'ли',
|
|
255
|
+
'senior', 'middle', 'junior', 'lead', 'tech', 'team', 'lead', 'senior+', 'middle+', 'junior+',
|
|
256
|
+
'🆔', 'redlab', 'mts', 'digital', 'мтс', 'диджитал', 'леманапро', 'x5', 'вк', 'vk', 'крок', 'itfb',
|
|
257
|
+
'the', 'a', 'an', 'of', 'for', 'to', 'in', 'on', 'at', 'by', 'with', 'проект', 'проекта', 'проекте',
|
|
258
|
+
'номер', 'потребности', 'id', 'п2026', '🆔', 'коллеги', 'всем', 'привет', 'актуальные', 'потребности',
|
|
259
|
+
];
|
|
260
|
+
|
|
261
|
+
return new SpecializationsMatchingEngine(
|
|
262
|
+
specNames,
|
|
263
|
+
synonymsData,
|
|
264
|
+
weightsData,
|
|
265
|
+
stopWords
|
|
266
|
+
);
|
|
267
|
+
}
|
|
268
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
// Environment setup & latest features
|
|
4
|
+
"lib": ["ESNext"],
|
|
5
|
+
"target": "ESNext",
|
|
6
|
+
"module": "Preserve",
|
|
7
|
+
"moduleDetection": "force",
|
|
8
|
+
"jsx": "react-jsx",
|
|
9
|
+
"allowJs": true,
|
|
10
|
+
|
|
11
|
+
// Bundler mode
|
|
12
|
+
"moduleResolution": "bundler",
|
|
13
|
+
"allowImportingTsExtensions": true,
|
|
14
|
+
"verbatimModuleSyntax": true,
|
|
15
|
+
"noEmit": true,
|
|
16
|
+
|
|
17
|
+
// Best practices
|
|
18
|
+
"strict": true,
|
|
19
|
+
"skipLibCheck": true,
|
|
20
|
+
"noFallthroughCasesInSwitch": true,
|
|
21
|
+
"noUncheckedIndexedAccess": true,
|
|
22
|
+
"noImplicitOverride": true,
|
|
23
|
+
|
|
24
|
+
// Some stricter flags (disabled by default)
|
|
25
|
+
"noUnusedLocals": false,
|
|
26
|
+
"noUnusedParameters": false,
|
|
27
|
+
"noPropertyAccessFromIndexSignature": false
|
|
28
|
+
}
|
|
29
|
+
}
|