Блог AST-SoftPro
Как одна боль рекрутера превратилась в ATS-систему с ИИ: от ручного подбора к трёхроликовому продукту
Введение
Рынок систем отслеживания кандидатов (ATS) в 2025 году превысил 7,4 млрд долларов, а к 2035 году, по прогнозам, достигнет 15,5 млрд. При этом на практике многие рекрутеры и HR-отделы до сих пор работают без какой-либо структурированной системы — вакансии публикуются вручную, резюме приходят в мессенджеры, сопоставление кандидатов с требованиями происходит в голове, а соискатели вынуждены самостоятельно мониторить новые вакансии и повторно обращаться.
В этой статье рассказана история создания системы ItMatch ATS — от конкретной боли одного IT-рекрутера до многопользовательской платформы с тремя ролями, адаптивным резюме и локальным ИИ. Мы рассмотрим архитектурные решения, выбор моделей, алгоритм матчинга и компромиссы между локальным развёртыванием и SaaS-режимом.
💡 История показывает, что реальные потребности пользователей часто не покрываются готовыми решениями — и именно этот разрыв между «есть» и «нужно» становится точкой входа для разработки.
Отправная точка: ручная работа рекрутера
Всё началось с практической ситуации. IT-рекрутеру было отправлено резюме под конкретную вакансию. После общения с ним картина оказалась типичной для многих специалистов по подбору:
-
Вакансии публикуются на hh.ru и в соцсетях вручную
-
Резюме приходят в личные сообщения, на почту, в мессенджеры
-
Сопоставление кандидата с вакансией происходит визуально — рекрутер читает резюме и сравнивает с требованиями в уме
-
База кандидатов отсутствует — каждое резюме обрабатывается как разовое событие
-
Собеседования организуются через календари и сообщения, без единой воронки
-
Соискатели не получают обратной связи и не видят новые вакансии — приходится мониторить площадки самостоятельно и обращаться повторно
Проблема была двусторонней: рекрутер тратил время на рутинные операции вместо подбора, а соискатели терялись в потоке вакансий без персонализированных рекомендаций.
⚠️ Типичная ошибка при автоматизации HR-процессов — начинать с покупки готовой ATS и пытаться подогнать процессы под систему. Эффективнее сначала описать реальные боли, а затем выбрать или создать инструмент, который решает именно их.
Архитектура системы
Технологический стек
Система построена на проверенном стеке, который позволяет быстро итерировать функциональность:
| Компонент | Технология | Назначение |
|---|---|---|
| Веб-фреймворк | Flask | Маршрутизация, обработка запросов |
| ORM | SQLAlchemy | Работа с базой данных |
| БД | SQLite (с миграциями) | Хранение данных, возможность миграции на PostgreSQL |
| Фронтенд | Jinja2 + Bootstrap 5 | Шаблоны, адаптивный UI |
| ИИ-ядро | OpenAI-compatible API | Парсинг резюме/вакансий, генерация тестов, адаптация резюме |
| Запуск | Docker + Gunicorn | Контейнеризация, продакшн-сервер |
| Безопасность | Flask-WTF, Flask-Limiter | CSRF-защита, rate limiting |
Выбор Flask вместо Django был продиктован гибкостью — модульная структура с blueprint'ами позволяет добавлять функциональность без раздувания проекта. SQLite на старте упрощает разработку и развёртывание, при этом архитектура допускает переход на PostgreSQL через Alembic по мере роста нагрузки.
Модульная структура
Проект разделён на 20 маршрутов (blueprint'ов) и более 40 сервисных модулей:
recruter/ ├── app.py # Точка входа, регистрация blueprint'ов ├── config.py # Конфигурация, загрузка .env ├── extensions.py # Инициализация расширений Flask ├── migrate.py # Миграции схемы БД ├── routes/ # 20 blueprint'ов (HTTP-слой) │ ├── auth.py # Авторизация, роли │ ├── candidates.py # CRUD кандидатов │ ├── vacancies.py # CRUD вакансий │ ├── matching.py # Матчинг │ ├── imports.py # LLM-импорт │ ├── pipeline.py # Канбан-воронки │ ├── assessments.py # Тестирования │ ├── resume_variants.py # Адаптация резюме │ └── ... # interviews, comments, notifications и др. ├── services/ # 40+ сервисных модулей (бизнес-логика) │ ├── llm_importer.py # Парсинг через LLM │ ├── matcher.py # Алгоритм матчинга │ ├── resume_adapter.py # Адаптация резюме │ ├── github_public.py # Анализ GitHub │ ├── notifications.py # Уведомления │ └── ... ├── models/ # Модели SQLAlchemy ├── templates/ # Jinja2-шаблоны └── static/ # CSS, JS, изображения
💡 Разделение на routes (HTTP) и services (бизнес-логика) — минимальная, но эффективная граница. Она позволяет тестировать логику без запуска веб-сервера и упрощает рефакторинг.
ИИ-ядро: парсинг и матчинг
LLM-импорт резюме и вакансий
Одна из ключевых функций системы — автоматический парсинг неструктурированных текстов (резюме, описания вакансий) в структурированные данные через LLM. Вместо регулярных выражений и шаблонного парсинга используется JSON Schema:
# Упрощённая схема для импорта кандидата
schema = {
"name": "string",
"email": "string",
"direction": "backend|frontend|mobile|devops|data|ml|qa",
"level": "junior|middle|senior",
"experience_years": "string",
"skills": ["React", "TypeScript", "Python"],
"work_history": [
{
"company": "string",
"position": "string",
"period_start": "2020-03",
"period_end": "2024-01",
"description": "string",
"achievements": ["string"]
}
]
}
LLM получает текст резюме, промпт с описанием полей и JSON Schema, и возвращает структурированный JSON. Система дополнительно нормализует направление, уровень и опыт на сервере, что снижает зависимость от качества вывода модели.
Важный аспект — поддержка fallback: если модель не поддерживает json_schema (например, старые версии локальных моделей), система автоматически переключается на json_object или необработанный режим.
Алгоритм матчинга
Матчинг кандидатов с вакансиями реализован как детерминированный алгоритм с прозрачным scoring (0–100 баллов):
| Критерий | Макс. баллы | Логика |
|---|---|---|
| Обязательные навыки | 60 | Полный балл, если опыт кандидата ≥ требуемого; половина балла, если навык есть, но опыт меньше |
| Опциональные навыки | 25 | Аналогично обязательным |
| Уровень (junior/middle/senior) | 8 | +8 точное совпадение, +4 смежный уровень |
| Формат работы | 4 | Совпадение remote/office/hybrid |
| Общий опыт | 3 | Опыт кандидата ≥ порогового для уровня вакансии |
Алгоритм также учитывает совместимость направлений: backend-разработчик может подойти на vacancy в direction «fullstack», но не на «data science».
Прозрачность scoring позволяет рекрутеру понять, почему кандидат получил конкретный балл, и принять обоснованное решение. Это особенно важно при работе с локальными моделями, где качество парсинга может варьироваться.
⚠️ Использование LLM для парсинга — это первый этап. Второй этап — валидация и нормализация на сервере. Без серверной валидации ошибки модели (галлюцинации, пропуск полей) попадут в базу данных, и их исправление обойдётся дороже, чем первоначальная проверка.
Эволюция: от рекрутинга к трёхроликовому продукту
Фаза 1: Инструмент для рекрутера
Первая версия решала базовую задачу — база кандидатов, база вакансий, импорт через LLM и матчинг. Рекрутер мог загружать резюме, автоматически получать структурированные данные и видеть, какие кандидаты подходят к открытым вакансиям.
Фаза 2: Воронки и адаптация резюме
Вторая фаза добавила:
-
Канбан-воронки — 8 этапов от «Новый» до «Принят»/«Отказан» с историей переходов
-
Адаптацию резюме — соискатель загружает базовое резюме, а система генерирует адаптированную версию под конкретную вакансию, выделяя релевантные навыки и опыт
-
Собеседования — планирование, фидбек (оценка 1-5 по техническим и soft skills, рекомендация)
-
Комментарии — внутренние заметки к кандидатам и вакансиям
Адаптация резюме стала ключевой функцией для соискателей. Система анализирует требования вакансии и переформулирует опыт кандидата так, чтобы релевантные навыки были на первом плане. Это решает проблему «одного резюме на все вакансии», которая снижает эффективность откликов.
Фаза 3: Три роли и многопользовательский режим
Текущая архитектура поддерживает три персоны:
| Роль | Описание | Ключевые функции |
|---|---|---|
| Рекрутер (агентство) | Подбор персонала на аутсорс | Воронки, матчинг, импорт, оценки, адаптация резюме |
| HR компании (работодатель) | Внутренний подбор | Вакансии, воронки, интервью, аналитика |
| Соискатель | Поиск работы | Адаптация резюме, отслеживание вакансий, тестирования |
Каждая роль видит собственный дашборд и набор функций. Организации (workspaces) обеспечивают изоляцию данных между агентствами, компаниями и личными пространствами соискателей.
Локальные LLM: выбор и тестирование
Оборудование и модели
Система спроектирована так, что ИИ-ядро работает через OpenAI-compatible API, что позволяет подключать как облачные, так и локальные модели. На практике были протестированы следующие конфигурации:
| Модель | Платформа | Наблюдения |
|---|---|---|
| t-lite-it-2.1 | Локальный сервер, RTX 4070 12 ГБ | Основная рабочая модель, стабильный парсинг с JSON Schema |
| Qwen 3.5 9B | Локально | Хорошее качество для размера, но требует строгого промпта для JSON |
| Gemma 4 31B | Локально | Высокое качество, но требует больше VRAM (квантование необходимо) |
| DeepSeek V4 Flash | Локально | Быстрый инференс, подходит для быстрых задач |
RTX 4070 с 12 ГБ VRAM — это минимальная конфигурация для комфортной работы с квантованными моделями среднего размера (7-31B параметров). Формат GGUF через llama.cpp позволяет запускать модели с различными уровнями квантования (Q4_K_M, Q6_K, Q8_0), балансируя между качеством и потреблением памяти.
💡 Квантование моделей — компромисс между качеством и ресурсами. Для задач парсинга резюме (извлечение структурированных данных) разница между Q4_K_M и Q8_0 часто незначительна, но экономия VRAM позволяет работать с более крупными моделями. Для генеративных задач (адаптация резюме, планы развития) качество квантования влияет на читаемость текста сильнее.
Локально vs SaaS
Архитектура системы поддерживает оба режима:
Локальное развёртывание:
-
Полная приватность данных — резюме и вакансии не покидают периметр
-
Нет зависимости от API-лимитов и стоимости токенов
-
Требует GPU-оборудования и настройки инфраструктуры
-
Обновление моделей — ручная замена весов
SaaS-режим (облачные LLM):
-
Не требует GPU-инфраструктуры
-
Доступ к самым актуальным моделям
-
Стоимость зависит от объёма запросов
-
Данные передаются внешнему провайдеру
⚠️ При работе с персональными данными соискателей (резюме, контакты, опыт работы) локальное развёртывание — не просто вопрос стоимости, а требование соответствия законодательству о защите персональных данных. Передача ПД внешним API-провайдерам требует отдельного правового обоснования и согласия субъектов данных.
Воронки подбора и kanban
Система реализует 8-этапную воронку с историей переходов:
Новый → HR-скрининг → Тех. интервью → Финальное интервью → Оффер
↓
Принят / Отказ компании / Отказ кандидата
Каждый переход фиксируется в ApplicationStageHistory с указанием автора, времени и предыдущего этапа. Это позволяет анализировать скорость прохождения воронки, выявлять узкие места и оценивать эффективность рекрутеров.
Практические аспекты развёртывания
Docker-развёртывание
cd /app/Recruter docker compose up --build
Приложение доступно на http://localhost:5000. Для подключения LLM задаётся переменная окружения:
$env:OPENAI_API_KEY="your_api_key" # или для локальной модели: $env:LLM_BASE_URL="http://localhost:8080/v1"
Nginx как reverse proxy
При развёртывании за nginx необходимо учитывать таймауты для LLM-запросов (генерация тестов и планов развития может занимать от нескольких секунд до минут):
proxy_connect_timeout 60s; proxy_send_timeout 3600s; proxy_read_timeout 3600s; send_timeout 3600s;
Без увеличения proxy_read_timeout длинные ответы от LLM обрываются с ошибкой 504 Gateway Time-out, хотя бэкенд ещё обрабатывает запрос.
Безопасность
Система включает несколько уровней защиты:
-
CSRF через Flask-WTF с токеном в заголовке X-CSRFToken для fetch-запросов
-
Rate limiting через Flask-Limiter (в памяти или Redis)
-
Демо-аккаунты с блокировкой записи — публичный просмотр без риска изменения данных
-
Обязательный онбординг перед использованием системы
-
Изоляция организаций — данные разных workspace не пересекаются
⚠️ При развёртывании в продакшене обязательно замените SECRET_KEY на случайную строку, настройте rate limiting на /auth/login и используйте HTTPS. Поверхностное копирование конфигурации из репозитория может привести к уязвимостям — стоит проверить реализацию на соответствие OWASP Top 10.
Перспективы и дальнейшее развитие
Текущие направления развития системы включают:
-
Надёжное хранение файлов — привязка исходных файлов резюме и вакансий к записям в БД с санитизацией путей и защитой от path traversal
-
Документы заказа — модель OrderDocument для отслеживания ТЗ и технических требований к вакансиям
-
Пагинация — переход от .all() к paginate() на списках кандидатов и вакансий
-
Alembic — замена ручной миграции на версионируемую систему для поддержки PostgreSQL
-
Индексы — добавление индексов на часто фильтруемые поля (is_active, stage, scheduled_at) для ускорения запросов при росте данных
Заключение
История ItMatch ATS иллюстрирует типичный путь от узкой проблемы к универсальному продукту. Исходная задача — автоматизация ручного сопоставления резюме с вакансиями — привела к созданию многопользовательской системы с тремя ролями, ИИ-парсингом, адаптивным резюме и канбан-воронками.
Ключевые выводы:
-
Локальные LLM на потребительском оборудовании (RTX 4070 12 ГБ) способны решать задачи парсинга HR-документов с качеством, достаточным для практического использования
-
Архитектура с OpenAI-compatible API обеспечивает гибкость — переключение между локальными и облачными моделями без изменения бизнес-логики
-
Три роли (рекрутер, работодатель, соискатель) расширяют аудиторию и делают систему ценной для всех участников процесса подбора
-
Адаптация резюме — функция, которая закрывает реальную боль соискателей и повышает релевантность откликов
💡 Выбор инструмента — лишь половина задачи. Вторая половина — корректная реализация с учётом безопасности персональных данных, валидации ввода, масштабируемости запросов и соответствия законодательству. Для продакшн-решений в области HR рекомендуется привлечение специалистов с опытом — это снижает риски ошибок безопасности и архитектурных просчётов, которые обнаружатся только при росте нагрузки.
Рынок ATS продолжает расти (прогноз — $15,5 млрд к 2035 году), и ниша систем с локальным ИИ и многопользовательским режимом остаётся открытой. Системы, которые сочетают приватность данных, гибкость развёртывания и практическую ценность для всех участников процесса подбора, будут иметь преимущество в конкурентной среде.
Источники: