Первый коммит: модульный монолит ядра (.NET 10) и gRPC-сервисы ai/ml/telegram, фронтенд Vue 3/Vite/Tailwind, документация (ТЗ, инструкция пользователя, техдокументация, код-стайл), бэклог, скрипты развёртывания и архив прототипа LeadRadar.
62 KiB
ТЕХНИЧЕСКОЕ ЗАДАНИЕ
Система мониторинга, AI-классификации и управления IT-лидами (LeadRadar) | Спецификация V1.2 (Production)
Архитектура: Self-Hosted / High-Perf
База данных: DuckDB (Embedded OLAP)
AI Engine: DeepSeek v4 Flash
Интерфейс: SPA / Custom Dashboard
1. Введение и назначение системы
LeadRadar — это автономная программная платформа для автоматического перехвата, аналитической классификации, дедупликации и трекинга заявок/лидов в любой сфере (IT и не-IT: вакансии и найм, разовые заказы и услуги, товары, недвижимость и т.д.). Система разворачивается на выделенном сервере и полностью управляется через отзывчивый веб-интерфейс, исключая необходимость взаимодействия через командную строку.
2. Анализ применимости базы данных
Выбранная СУБД (DuckDB) идеально подходит для поставленной задачи, сочетая преимущества встраиваемой архитектуры (отсутствие внешних зависимостей, работа в одном файле) и колоночной аналитики:
- Скорость аналитики: мгновенная фильтрация по десяткам тысяч записей, стеку технологий, временным диапазонам и доскам с векторизованным выполнением запросов.
- Нативная поддержка структурированных данных: списки технологий и параметров хранятся без деградации скорости доступа.
- Полнотекстовый поиск (FTS): встроенное расширение позволяет мгновенно искать по ключевым словам и фразам во всей истории сообщений без использования сторонних поисковых движков.
- Легковесность: потребляет минимум системных ресурсов и не требует администрирования отдельного сервиса СУБД.
3. Выбор оптимального технологического стека
| Уровень | Технология | Обоснование |
|---|---|---|
| Backend Runtime | Python 3.12+ / FastAPI | Асинхронное ядро, минимальные накладные расходы, нативная поддержка реалтайм-событий. |
| Database Core | DuckDB | Встраиваемая колоночная СУБД, быстрые агрегации, векторные выборки, работа в одном файле. |
| Telegram Engine | Telethon (MTProto API) | Поддержка Client API, веб-авторизация, полный доступ к диалогам и истории. |
| AI Classifier | DeepSeek v4 Flash | Высокая точность в IT-терминологии, оптимальная себестоимость анализа, строгая структуризация ответов. |
| Frontend Stack | Vue 3 + Tailwind CSS | Максимальная кастомизация, высокая скорость рендеринга, реактивность интерфейса. |
| Realtime Transport | Server-Sent Events (SSE) | Однонаправленный поток событий, мгновенные пуш-уведомления, автоматическое переподключение. |
4. Архитектура и функциональные требования
4.1. Авторизация в дашборд
- Вход в веб-интерфейс по логину и паролю; учетные данные по умолчанию: admin / admin.
- Смена пароля — через интерфейс настроек.
- Серверная сессия действительна 30 дней (месяц): повторная авторизация в течение этого срока не требуется.
4.2. Авторизация Telegram через Web-интерфейс
- Ключи Telegram API (api_id / api_hash) вводятся один раз в интерфейсе настроек, а не через переменные окружения.
- Ввод номера телефона в модальном окне интерфейса.
- Поддержка ввода кода верификации и облачного пароля (2FA).
- Опциональная генерация QR-кода для быстрой авторизации камерой смартфона.
- Надежное сохранение сессии в файловой системе (без необходимости повторных входов).
- Индикация статуса подключения и возможных ошибок.
4.3. Менеджер каналов и диалогов
- Отдельный экран или боковая панель со списком всех каналов и групп.
- Отображение метаданных: название, аватар, системное имя, тип (чат/канал).
- Быстрый предпросмотр последних сообщений в один клик без перехода в приложение мессенджера.
- Индивидуальный переключатель для каждого источника: режим активного мониторинга или игнорирования.
- Кнопка «Включить все» / «Выключить все» в шапке списка каналов — массовое переключение мониторинга для всех источников разом. При первом включении канала (и при массовом включении) выполняется последовательный разбор последних 10 сообщений с паузами (анти-бан), фоново, без блокировки UI.
- Кнопка «Перечитать» в шапке списка каналов — ручная догонялка: перечитывает последние ~10 сообщений всех включённых каналов в фоне (с паузами анти-бан), даже если канал уже разобран ранее. Повторные карточки не создаются (защита дедупликации по тексту). После этого система реагирует только на новые сообщения в реальном времени; страховочный цикл (~30 с) догоняет потерянные события (рестарт/разрыв соединения).
- Автосинхронизация списка: при входе на вкладку и фоновым циклом актуальный список чатов/каналов сверяется с аккаунтом Telegram — новые появляются, переименованные обновляются, покинутые/удалённые исчезают (мониторинг по ним прекращается).
- Новые чаты: при включённой настройке «новые чаты — сразу в мониторинг» (
autoMonitorNew) любой появившийся чат включается в мониторинг автоматически; при выключенной — появляется отключённым, пользователь включает вручную.- Прочитанность: полученные/перечитанные сообщения сразу помечаются прочитанными в Telegram (read-ack в realtime, при «Перечитать» и ручном предпросмотре) — в других клиентах они не висят «новыми».
- В пункте меню «Каналы» выводится счётчик числа каналов, находящихся в мониторинге.
4.4. Кастомные колонки и стилизация
- По умолчанию колонок в системе нет. Колонки создаются пользователем (кнопка «Новая колонка») либо предлагаются ИИ по результатам анализа «Неразобранного».
- Создание и настройка — единый диалог «Новая колонка / Настройки колонки»: название, описание колонки (для пользователя и подсказки ИИ/ML), цветовой акцент и набор фильтров. Диалог открывается сразу при создании и в любой момент из меню колонки (⋮).
- Колонка — это не отдельный навык, а смысловой набор фильтров (каждый опционален, набор можно менять в любой момент): направление/тема задач, ключевые технологии и стек, ключевые слова, грейд/уровень (junior/middle/senior/lead/…, с распознаванием синонимов: джун/мидл/сеньор/mid и т.п.), бюджетный диапазон с валютой (от–до). Пустые группы не участвуют. Режим комбинирования — «все условия» или «любое из условий» («любое» = хотя бы одна заданная (непустая) группа совпала; пустые группы результат не искажают). Сами правила не раскладывают входящие «словарно» до ИИ (словесный матч не понимает смысл и ловит ложные совпадения из дайджестов и футеров): они служат (1) критериями для ИИ-классификатора, (2) проверкой-страховкой на бэкенде и (3) обоснованием «почему карточка в колонке» (см. п. 5.4). Колонка, у которой заданы активные фильтры, не принимает карточки, не прошедшие её правила, ни от ИИ, ни от ML (страховка на бэкенде) — такая карточка остаётся в «Неразобранном».
- ИИ-предложения (статус
suggested) появляются в списке колонок с пометкой «ИИ» и обоснованием: по какому направлению, стеку, грейду и ключевым словам собрана, сколько похожих карточек в выборке. Пользователь открывает колонку, просматривает карточки и решает: принять (колонка становится обычной — можно переименовать, дополнить описание и поправить фильтры) или отклонить (карточки возвращаются в «Неразобранное»).- Причина попадания в колонку: при совпадении с фильтром у карточки фиксируется, какие именно критерии совпали — группа фильтра (направление/слова/стек/грейд/бюджет) и сами совпавшие термины. Совпадение ищется по всему тексту сообщения, включая списки стека, требований и «будет плюсом» (и наоборот: термин карточки ищется по всем группам фильтра). В подробном виде карточки есть блок «Попала в колонку по фильтру — совпало» с перечнем совпавших критериев. При ручном переносе карточки совпадения пересчитываются для новой колонки.
- Для каждой колонки настраиваются: название, описание, цветовой акцент, ширина, сворачивание в виджет, набор фильтров попадания карточек, индивидуальный системный промпт, видимость полей (бюджет, стек, контакты, источник). Описание и набор фильтров колонки передаются в промпт ИИ-классификатора при выборе колонки.
- Любую колонку можно удалить — находящиеся в ней карточки возвращаются в «Неразобранное» (с пометкой новых).
4.5. Буфер «Неклассифицированное» (Inbox)
- Изолированный раздел для входящих заявок, не подошедших под критерии активных досок.
- Счетчик непрочитанных элементов с визуальным уведомлением.
- Удобный ручной перенос лида на нужную доску в один клик.
- Функция пакетной повторной классификации через нейросеть.
4.6. Настройки ключей и интеграций
- Все API-ключи (Telegram api_id/api_hash, ключи AI-провайдеров) задаются и заменяются через веб-интерфейс.
- Секреты не хранятся в переменных окружения и конфигурационных файлах (по договорённости в env исключения — ключ шифрования БД и креды MinIO, см. п. 8).
- Сохраненные ключи не отображаются в открытом виде: доступны только факт наличия и замена значения.
- Настройка целевой валюты отображения и источника курсов (вкладка «Валюта и курсы»).
- Источник курсов — ЦБ РФ (cbr.ru): официальные курсы к рублю, запрос выполняется 4 раза в сутки (каждые 6 часов); до подключения сервиса используются мок-курсы.
- AI-классификатор возвращает бюджет как число или диапазон с указанием валюты (USD/EUR/RUB/USDT и др.).
- AI-классификатор работает через выбираемого провайдера — активен только один: DeepSeek, OpenAI, OpenRouter, Anthropic Claude или локальный OpenAI-совместимый сервер (Ollama, LM Studio, vLLM и т.п.). Для каждого провайдера настраиваются base URL, модель и API-ключ (локальным ключ не нужен); системный промпт общий.
4.7. Дашборд: рабочие колонки и карточки
- Основной рабочий экран — дашборд из колонок (как пользовательских, так и ИИ-предложений, см. п. 4.4); каждая колонка имеет свой цвет-акцент. По умолчанию колонок нет — создаются пользователем или предлагаются ИИ.
- Служебные колонки: «Неразобранное» (буфер Inbox, см. п. 4.5) и «Корзина» (отложенное удаление карточек).
- Колонки прокручиваются по вертикали независимо; при нехватке ширины область дашборда прокручивается горизонтально.
- Пользователь настраивает рабочее пространство: количество колонок, их ширину и порядок (перетаскиванием), отображение на пол-экрана или весь экран.
- Любую колонку можно свернуть в виджет-счетчик (компактная плашка с числом ожидающих карточек) и развернуть обратно в один клик.
- Карточки интерактивные: быстрые действия без открытия — копирование контакта, перенос в другую колонку, добавление комментария, перемещение в корзину; подробное описание карточки открывается по центру экрана в модальной панели.
- На карточке показывается структурированная суть «О заявке» — не голый текст исходника (он виден только в подробном виде под спойлером). «О заявке» у всех карточек собирается из одинаковых логических блоков единой структуры (Компания → Формат → О задаче → Требования → Будет плюсом → Условия), длина блоков разная; недостающие блоки пропускаются. Как заполнять поля блока задаёт отдельный «Промпт структуры карточки» (см. п. 5.6), а единый вид текста гарантирует серверная сборка из структурированных полей.
- Заголовок карточки — короткий (4–9 слов), без эмодзи/хэштегов и без ссылок (markdown-ссылки и URL вычищаются и при сохранении, и при отображении), визуально обрезается до двух строк.
- Текст в карточках переносится по словам (длинные ссылки/токены не выходят за границы карточки): включён перенос строк и сохранение структуры (список параметров от ИИ не схлопывается).
- На карточке источник (название канала) не показывается — он виден только в подробном описании лида.
- Бюджет может быть числом или диапазоном «от–до» в любой валюте.
- Тип заявки (найм/занятость или разовая сделка/заказ) определяется по контексту ИИ (ML учится этому же); маркерная эвристика без ИИ не является решающей. Подписи типов настраиваются в «Сфере и ключах» (по умолчанию — «вакансия» и «фриланс»); бейдж типа выводится, когда тип подтверждён по контексту.
- Пересчёт сумм в целевую валюту (по умолчанию — рубли) выполняется один раз — в момент поступления заявки, по курсу на тот день; исходная сумма в первоначальной валюте всегда сохраняется и отображается первой (в карточке и в подробном описании).
- Служебная колонка «Архив»: карточки, находящиеся в системе дольше настраиваемого срока (интервал настройки 1–30 дней), автоматически переносятся в архив.
- Отсев устаревших на входе: сообщение, опубликованное раньше срока до архива (например, канал молчал, и «Перечитать» подтянуло старые посты), в систему не попадает вообще — ни карточкой, ни в архив, ни в корзину. Работает, когда автоархив включён: возраст сообщения считается от даты публикации в канале до текущего момента.
- Архив очищается автоматически через 90 дней после помещения, корзина — каждые 7 дней. Возврат карточек из архива и корзины возможен только на канбан (доски / «Неразобранное»), пока карточка не очищена автоматически. Помимо автоправил, корзину и архив можно очистить вручную полностью (безвозвратно, с подтверждением).
- Карточки с основного канбана можно «взять в работу» — они попадают в отдельный дашборд «Выбранные» (см. п. 4.8).
- К карточке можно добавить комментарий (внутренняя заметка), он сохраняется вместе с лидом.
- Действия пользователя (ручной перенос, корзина, возврат, перенос в «Выбранные») фиксируются как обучающие примеры и всегда передаются в ML-модель (обучение идёт постоянно, независимо от того, используется ли ML в пайплайне). Дополнительно ML учится на каждом попадании карточки в колонку по правилам. ИИ-предложения колонок анализируются и учитываются пользователем до превращения в постоянные.
4.8. «Выбранные» — второй дашборд (проектный канбан)
- Отдельный экран для отобранных лидов: свой канбан с перетаскиванием карточек по стадиям.
- Стадии по умолчанию: Запланировано → Отклик → Согласование → В работе → Проверка → Готово, плюс Отложено (пауза) и финальные статусы «Выполнено» и «Отклонено».
- Карточка попадает сюда кнопкой «Взять в работу» из лида на канбане, либо создается вручную кнопкой «Новая карточка» с тем же набором полей — такие карточки помечаются как «локальные» (созданы вручную, без лида-источника).
- Карточка не может находиться одновременно на дашборде и в «Выбранных»: при взятии в работу лид уходит с дашборда безвозвратно (в архиве, корзине и поиске не участвует) — обратно на дашборд он не возвращается.
- У проектной карточки ведется история движения: с момента добавления в «Выбранные» фиксируется каждая смена стадии (статус, дата и время); для локальных карточек первая запись — «Создана локально». История показывается в подробном виде под спойлером «История движения».
- У проектной карточки редактируются: сумма (число или диапазон), валюта, стек, контакты, комментарии; можно прикреплять ссылки и ТЗ.
- К карточке прикрепляются файлы — медиа и документы; система автоматически определяет тип файла (по MIME и расширению). На самой карточке значками показывается количество прикрепленных файлов и ссылок.
- Файлы хранятся в объектном хранилище MinIO (S3-совместимое, креды в env): в БД — метаданные и ключ объекта. Если MinIO не настроен (локальный запуск без docker-compose) — те же ключи сохраняются в локальную папку
data/attachments, API и карточки не меняются. Тип файла определяется автоматически (MIME + расширение): изображение/видео/аудио/архив/документ.- Карточки «Выбранных» не попадают в архив и корзину — у дашборда собственные финальные сущности «Выполнено» и «Отклонено». Стадию «Отклонено» можно очистить полностью вручную (безвозвратно, с подтверждением).
- Стадия «Отложено» поддерживает напоминания: при переносе карточки открывается окно настройки — напомнить через N дней (1–30) или в конкретную дату (календарь) и в какое время; по наступлению срока всплывает оповещение с действиями «Открыть карточку / Позже / Снять».
- Общий переключатель напоминаний вынесен в настройки уведомлений: если он выключен, окно настройки при переносе в «Отложено» не показывается и уже установленные напоминания не срабатывают. Список активных напоминаний виден там же.
- Напоминание автоматически снимается, когда карточка покидает стадию «Отложено».
4.9. Поиск и подключение каналов (Discovery)
- Подвкладка «Поиск» на экране «Каналы» — поиск и подключение новых источников (каналы, группы, форумы), в которых мы ещё не состоим. Система ищет кандидатов, оценивает их (метаданные, язык, содержимое) и показывает человеку список «на рассмотрение».
- Задача поиска — конфиг с описанием цели (что ищем): пользователь описывает цель → система генерирует поисковые ключи ИИ (редактируются перед стартом; запуск возможен только после их подтверждения) → по ключам выполняется каскад фильтров по нарастающей стоимости (при первом «нет» источник пропускается): поиск и дедупликация кандидатов → глобальный фильтр «мы не состоим» → число участников (минимум; 0 = не важно) → язык (
ru/any) → оценка содержимого выборки сообщений. Задач может быть несколько.- Глобальное правило «мы не состоим» — безусловное, для всех задач: источник, в котором мы уже состоим (вступили/мониторим), находящийся в чёрном списке или уже обрабатываемый/вступивший в другой задаче, отбрасывается сразу и на любом этапе (поиск → оценка → вступление) — независимо от запроса, ключей и настроек задачи. Проверка повторяется непосредственно перед вступлением (между оценкой и join'ом источник мог быть добавлен вручную).
- Метки кандидатов — человекочитаемые пометки: «закрытая группа/канал», «форум», «не прочитано», «участники не подтверждены», «язык не подтверждён», «мало сообщений», «есть проходные темы». Метки «не подтверждено» — не ошибка и не пропуск, а сигнал человеку на экране рассмотрения.
- Оценка содержимого: сообщение проходит те же правила, что в основном пайплайне (этап 1 → ML → ИИ), но с профилем задачи (описание + ключи задачи), без создания карточек/очереди/обучения ML; при выключенном ИИ — локальный разбор/ML. Каналы и открытые группы: читается выборка до
sampleSizeпоследних сообщений (по умолчанию 10), доля подходящих ≥ порога (по умолчанию 40%) → «на рассмотрение»; открытая группа без чтения → «на рассмотрение» с меткой.- Оценка по темам (форумы): группа раскладывается по темам (
reply_to_top_id), выборка читается по активным темам и оценивается по темам («тема: подходит X из N»); группа подходит при ≥1 проходной теме, в превью — список тем с пометками проходная/нет (имена тем подставляются сниппетом первого сообщения, если API не отдаёт их без членства). Для оценки нужно ≥3 содержательных сообщений в выборке; если их меньше — кандидат идёт «на рассмотрение» с меткой «мало сообщений». Закрытые группы (история скрыта) — сразу «на рассмотрение» с меткой «закрытая группа/канал».- «На рассмотрение» и действия человека: у кандидата показываются тип, число участников, метки, соответствие «подходит X из N», почему подошло (перечень подходящих сообщений/тем, как блок «попала по фильтру») и превью. Действия: «Вступить и мониторить» — вступление, добавление в список каналов с
monitor=1и догон последних ~10 сообщений; «Отклонить» — источник уходит в чёрный список (исключается из поиска всех задач; снимается вручную); для закрытых групп вместо авто-вступления — кнопка-ссылкаt.me/<username>, факт вступления система замечает при синхронизации диалогов и предлагает добавить источник в мониторинг.- План задач и правило создания: у задачи задаётся план вступлений 1–50; сумма планов всех активных задач (статус не
done/failed) ≤ суточного лимита вступлений (по умолчанию 50) — задача с планом 50 не даёт создать другую, план 25 оставляет не более 25. У активной задачи план нельзя увеличить сверх свободного бюджета.- Авто-вступление и квоты: авто-режим включается на задачу (
autoJoin) — подходящие кандидаты вступают сами, по одному действию, со случайной паузой 50–70 с. Суточный лимит — 50 авто-вступлений, общий на все задачи (считаются только автоматические); ручные вступления — без квот и ограничений. Задача «выполнена» при достижении плана; при упоре в общий суточный бюджет авто-режим продолжает на следующий день (новый суточный бюджет).- Анти-бан (BanGuard): единый менеджер квот и пауз для всех действий поиска и для всех задач; поиск/чтение — мягкие паузы (единицы секунд + джиттер). При
FloodWaitError— пауза по секундам из ответа + запас, авто-вступления останавливаются до следующего дня; есть общий «стоп-кран» — ручная пауза всего discovery. Лимит, интервалы и размеры выборки — настройки в UI.
5. Спецификация пайплайна обработки данных
- Перехват события и очередь: Система фиксирует новые сообщения мониторящихся каналов в реальном времени и кладёт их в очередь обработки (на диске); очередь разбирается фоновым воркером. В момент получения сообщению присваивается строгая метка локального серверного времени и сохраняется идентификатор исходного сообщения (для «открыть исходник»).
- Этап 1 — без ИИ: Отбрасываются короткие неинформативные тексты, сообщения со стоп-фразами и (настраиваемо) резюме соискателей: если включён тумблер «Отсев резюме» (
blockResumes), текст с любым маркером резюме/соискателя (resumeMarkers, список редактируется в «Сфере и ключах») удаляется сразу — до правил колонок и ИИ (раньше резюме могло залететь в колонку по стеку, минуя ИИ-фильтр). Слово «резюме» имеет контекстный guard: если перед ним в тексте есть маркер найма (например, «…вакансия…, присылайте резюме») — это объявление работодателя, оно не блокируется. Тумблер выключен — резюме собираются как обычные лиды (полезно, когда система настроена на поиск сотрудников). Дополнительно фильтр «собирать только найм / только разовые заказы» (wantedType) и опциональные фильтры «не создавать карточку без суммы» (отдельно для найма и разовых заказов:budgetRequiredHire/budgetRequiredOrder) также отрабатывают на этапе 1/без ИИ. Отсев устаревших: если автоархив включён и сообщение опубликовано раньше, чем заarchiveAfterDaysдней до текущего момента, оно удаляется сразу (в БД не попадает — ни карточкой, ни в архив/корзину). Не прошедшее карточкой не становится: отброс фиксируется в мониторинге «Отсев» с причиной и конкретным словом/фразой (см. п. 5.12) и живёт там до автоочистки (3 суток).- Дедупликация: По нормализованному тексту (регистр, спецсимволы) вычисляется идентификатор; повторное сообщение игнорируется.
- Правила колонок — не «словарный» роутер до ИИ: смысловые колонки НЕ назначаются словарным матчем до ML/ИИ — он не понимает смысл и ловит ложные совпадения (дайджест из нескольких ролей, «desktop» в URL/футере и т.п.). Правила (направление, стек, слова, грейд, бюджет; режим «все/любое», исключения) используются как: (1) критерии, передаваемые ИИ-классификатору при выборе колонки; (2) страховка-проверка после выбора ИИ/ML (карточка попадает в колонку с активными правилами только если текст прошёл их); (3) обоснование «почему карточка здесь» (блок «Попала по фильтру — совпало»). Во всех «быстрых» путях (ML без ИИ / ИИ выключен) карточка структурируется локальным разбором без ИИ: заголовок (первая строка без markdown-мусора), стек/грейд/контакты/бюджет по меткам вида «Стек: …» и регулярным выражениям (суммы и валюты), признак вакансии.
- ML-слой (обучаемый, отдельный сервис): Между стоп-листом и ИИ работает локальная ML-модель, обучаемая на реальных действиях пользователя (перенос на доску, корзина, возврат, возврат из отсева) и на решениях ИИ. Если ML уверен — решает сам (спам уходит в отсев, колонка назначается) без обращения к ИИ. ML не назначает колонки, у которых заданы активные правила, и ИИ-предложения — такие колонки наполняются ИИ (с проверкой правил) или ручным выбором пользователя, чтобы нерелевантное не попадало в «отфильтрованные» колонки. Использование ML в пайплайне включается настройкой; обучение идёт всегда. 5.1 Полный выключатель ИИ (
aiEnabled, вкладка настроек «AI-классификатор»): при выключенном ИИ карточки собирает локальный разбор без провайдера, смысловую раскладку по колонкам берёт на себя ML (когда готова). ML в этом режиме обучается только вручную — действиями пользователя: переносы карточек, корзина, возвраты, возврат ошибочного отсева, ручная разметка в «ML-лаборатории». Кнопки «Предложить колонки» и «Переклассифицировать» при выключенном ИИ недоступны (с понятным сообщением). 5.2 Самооценка ML (индикатор на вкладке ИИ): каждое реальное действие пользователя (delta=1.0: перенос на доску, корзина, возврат, ручная разметка) сверяется с текущим предсказанием модели до обучения на нём; если модель уверена — фиксируется «верно/ошибка». В статусе ML отдаётся окно последних 50 подтверждённых решений (верно/всего/точность). На вкладке «AI-классификатор» показывается прогресс проверки, а когда накоплено ≥ 50 решений с точностью ≥ 90% — зелёная подсказка «ML справляется — ИИ можно отключить» с кнопкой выключения ИИ.- ИИ-этап (если ML не уверен или выключен): Сообщение проходит ИИ-фильтр с отдельным промптом (не пропускать простые сообщения, рекламу, скам и прочее; этап отключаемый), затем — ИИ-классификацию. Классификатору передаётся карта только принятых колонок вместе с их критериями (направление, стек, ключевые слова, грейд, бюджет, описание); колонка выбирается по совпадению критериев, а не по названию; если ни одна не подходит —
null(в «Неразобранное»). Даже если ИИ/ML вернули колонку с активными правилами, бэкенд проверяет текст правилами колонки и при несоответствии оставляет карточку в «Неразобранном» (страховка от засорения отфильтрованных колонок). Классификатор возвращает структурированную карточку (заголовок, поля блока «О заявке» — company/format/task/requirements/plus/conditions, стек, бюджет с валютой, контакты, признак вакансии); при сбое ИИ карточка структурируется локальным разбором и не теряется. Ручная переклассификация «Неразобранного» повторяет конвейер ИИ с теми же страховками правил и обучающими сигналами для ML.
6.1 Промпт структуры карточки (cardPrompt): отдельный настраиваемый промпт (вкладка AI-классификатора), который задаёт, какие поля блока «О заявке» и как заполнять (компания, формат, задача, требования, «будет плюсом», условия; для не-IT сфер «стек/требования» = материалы, услуги, навыки, инструменты). Добавляется к промпту классификатора отдельным блоком; текст «О заявке» сервер собирает из этих полей сам — поэтому у всех карточек одинаковая структура и разная длина. Поля заполняются только фактами из сообщения. 6.2 Бюджет карточки (fallback и распознавание): если ИИ не выделил бюджет отдельным полем, но сумма с валютой есть в исходнике или в структурированной «О заявке» (часто уходит в «Условия») — она добирается автоматически (тот же источник, что и фильтр «не создавать без суммы»). Распознаются «2к»/«1.5к»/«$2к», «2000р/₽/руб», диапазоны «от X до Y»/«X–Y», «до X»; названия валют нормализуются (руб/рублей/₽, долларов/$/бакс, евро и т.п. → коды USD/EUR/RUB/…). Если ИИ вернул бюджет строкой с суффиксом («2к») или «р/₽» — значение тоже нормализуется.
- Стек — всегда список строк, а не строка: ответы ИИ нормализуются (строка «Java, Kotlin» превращается в массив), названия сохраняются слитно (
.NET,C#,Node.js), одиночные символы отбрасываются.- Контакты собираются списком и квалифицируются (см. п. 6 UI): телефон, email, @username, LinkedIn, WhatsApp, сайт; боты, каналы/группы и ссылки на посты/вакансии отбрасываются; при отсутствии в ответе ИИ контакты добываются из текста сообщения.
- У каждой карточки из сообщения есть переход к исходному сообщению: в подробном виде — явная кнопка «Открыть исходник» (ссылка на сообщение в Telegram по сохранённым id диалога и сообщения), текст исходника — под спойлером.
- ИИ-предложения колонок: По накопленным карточкам «Неразобранного» (от ~6) ИИ периодически (кулдаун ~20 мин) или по кнопке предлагает 2–4 смысловые колонки с обоснованием и критериями; карточки раскладываются по предложениям, пользователь принимает решение (см. п. 4.4).
- Запись и триггер уведомлений: Сохранение результата в базу (в БД оседает только прошедшее фильтры; исходные сообщения карточек хранятся с идентификатором для «исходника»). Сервер инициирует событие, фронтенд плавно добавляет карточку в верх соответствующей колонки (либо в «Неразобранное»), с визуальной и звуковой индикацией.
- Обучение на действиях пользователя: Действия с карточкой (ручной перенос, корзина, возврат, взятие в работу) всегда записываются в очередь обучения ML и учитываются при последующих обработках — система со временем точнее раскладывает похожие заявки или оставляет их в «Неразобранном».
- Универсальность (не только IT) и настройка «Сфера и ключи»: Система работает для любой сферы — разработка, дизайн, недвижимость, стройка/кровля, услуги и т.п. Распознающие паттерны не зашиты в код: в настройках (вкладка «Сфера и ключи») задаются:
- Описание сферы (
domainDescription) — что для пользователя является заявкой/лидом;- Общие ключевые слова-маркеры (
domainKeywords) — слова/фразы, по которым сообщение опознаётся как заявка; кнопка «Предложить ИИ» анализирует накопленные карточки и предлагает список ключей (пользователь правит и сохраняет);- Маркеры «найма» (
hireMarkers), уровней (levelTerms) и резюме/соискателей (resumeMarkers, тумблерblockResumes) — используются этапом 1 и локальным разбором без ИИ; дефолты покрывают IT-найм, но редактируются под любую сферу и язык.
Промпты ИИ (классификатор, фильтр, предложение колонок/ключей) содержат плейсхолдеры{domain}и{keywords}, которые подставляются из этих настроек при каждом вызове — поэтому фильтрация спама и смысловые поля (заголовок, «о чём заявка», цена, контакты: телефон/@username/email) настраиваются под конкретный бизнес без правки кода.
- Библиотека промптов и «Мои промпты»: В настройках AI есть библиотека готовых промптов (на русском) для разных специальностей, разбитая на категории (IT/разработка, дизайн, недвижимость, стройка/ремонт, бытовые услуги, красота/здоровье, обучение и базовые) с поиском по списку. В «Базовых» есть универсальные варианты поведения: нейтральный, строгий (только явные заявки с деталями), гибкий (ничего не упускать), «только найм (вакансии)» и «только заказы (услуги)». Шаблон можно посмотреть (превью текста), изменить под себя и применить в редактор (активным становится только после кнопки «Сохранить промпт» — текущий сохранённый промпт не меняется автоматически). Любой текст можно сохранить в личный раздел «Мои промпты» с собственным названием и описанием; оттуда промпт можно снова применить или удалить. Кнопка «Базовый промпт» возвращает универсальный дефолт.
- Мониторинг обработки — вкладка «Обработка» (отдельный экран). Прозрачность пайплайна: что сейчас в очереди, что и почему отсеяно.
- Очередь — сырые сообщения из каналов, ожидающие разбора: этап 1 (стоп-лист/длина, без ИИ) и ожидающие ИИ; список живой (обновляется по SSE + поллингом), у каждого сообщения — канал, статус этапа и время в очереди. Экран показывает, что воркер делает прямо сейчас (полезно при «Перечитать»/первичном разборе).
- Отсев — сообщения, не прошедшие любой этап, с пометкой почему: этап и причина (стоп-фраза, резюме соискателя, тип заявки, нет суммы, устарело, ML, ИИ-фильтр/спам, повтор), конкретное слово/фраза стоп-списка (если отсев по нему) и чьё решение — правила (без ИИ) / ML / ИИ / система. Повторное отбрасывание того же сообщения (перечитывание каналов) обновляет запись, а не плодит дубликаты.
- Полнотекстовый поиск по отсеву (FTS + LIKE по тексту, причине, слову и каналу), пагинация «показать ещё».
- Очистка отсева: вручную (кнопка «Очистить», с подтверждением; можно удалить отдельную запись) и автоматически — раз в 3 суток записи старше 3 дней удаляются безвозвратно.
- В боковом меню у пункта «Обработка» показывается только счётчик сообщений в очереди; отсев виден внутри вкладки.
- Возврат из отсева в обработку: у записи отсева есть действие «Вернуть в обработку» (кроме «повторов» — карточка уже существует). Открывается окно с полем «Почему обработано некорректно? (необязательно)». Возвращённое сообщение снова кладётся в очередь с пометкой force: причины отсева для него игнорируются (стоп-лист/резюме/тип/без суммы/устарело, ML и ИИ-отсев «спам») — оно проходит ML/ИИ и создаёт карточку; если ИИ снова скажет «спам», вердикт отменяется (карточка создаётся). ML обучается на действии (снятие веса «спама» за текст), а решение ИИ по возвращённому сообщению снова учит ML. Запись в отсеве не удаляется, а помечается «возвращено» с причиной (аудит); автоочистка через 3 суток действует как обычно.
- Сброс карточек/прогона (admin wipe / clear-cards) очищает и отсев, чтобы повторные тестовые прогоны не смешивались со старой историей.
6. Требования к UI/UX и дизайну
- Цветовая палитра: Глубокие темные оттенки фона с яркими акцентами через пользовательские цвета досок.
- Звуковая обратная связь: Деликатный синтезированный сигнал при поступлении приоритетного лида.
- Анимации: Плавное появление новой карточки, микро-индикаторы пульсации онлайн-статуса системы.
- Удобство отклика: Контакты заказчика выводятся крупно в отдельном блоке с кнопкой быстрого копирования и прямой ссылкой на диалог. Контакты квалифицируются по типу (Telegram/@username, телефон, почта, LinkedIn, WhatsApp, сайт): в подробном виде каждый контакт показан с меткой типа, кнопкой «открыть» (t.me/tel/mailto/ссылка) и копированием. Боты (@…bot), каналы/группы и «постовые» ссылки (teletype, формы, job-агрегаторы) контактами не считаются.
- Навигация: Боковое меню сворачивается в узкую панель иконок и разворачивается обратно.
- Время: Текущее время в 24-часовом формате (часы:минуты, без секунд) отображается в шапке дашборда рядом с днем недели и числом.
- Сортировка: В пределах колонки — безусловная хронологическая иерархия по времени получения (самые свежие заявки всегда сверху).
- Счётчики: бейджи и счётчики (Выбранные, Обработка, Каналы, Архив, Корзина, колонки-доски) отображаются только при значении > 0 — нулевые показатели нигде не выводятся.
- Подтверждения и уведомления — только встроенные окна в стиле приложения: единый модальный диалог подтверждения (очистка корзины/архива/«Отклонено»/отсева, удаление или отклонение колонки, сброс ML) и окна с полями (причина при возврате из отсева). Системные окна браузера (confirm/alert/prompt) не используются.
- Отображение суммы на карточке: одна сумма → «2 500 ₽»; только верхняя граница → «до 3 000 ₽»; диапазон → «2 500–3 000 ₽». «От 0» не выводится: «от 0 до X» отображается как «до X».
7. План этапов разработки (Roadmap)
| Этап | Модуль | Ключевой результат |
|---|---|---|
| Спринт 1 | Ядро, авторизация дашборда и Telegram | Инициализация базы, авторизация в дашборд (admin/admin, сессия 30 дней), ввод Telegram api_id/api_hash, модуль веб-авторизации Telegram, веб-интерфейс каналов с предпросмотром сообщений. |
| Спринт 2 | AI & Маршрутизация | Пайплайн дедупликации, AI-классификатор, распределение на доски и в буфер неклассифицированного. |
| Спринт 3 | UI/UX, Кастомизация | Дашборд-колонки с интерактивными карточками, выбор цветов, настройка полей, комментарии и корзина, звуковые уведомления, виджеты-счетчики, обучение на действиях пользователя. |
| Спринт 4 | Деплой & Полировка | Настройка процессов развертывания системы, стресс-тесты под нагрузкой, финальная оптимизация. |
8. Развертывание (Deployment)
- Запуск системы — через Docker Compose (в том числе локально).
- В переменные окружения выносятся неконфиденциальные параметры: порты, пути к файлу БД и данным, пути хранения сессий. По договорённости в env также лежат ключ шифрования секретов БД (
LEADRADAR_ENCRYPTION_KEY; при локальной разработке без env — файлdata/encryption.key) и креды MinIO (LEADRADAR_MINIO_ENDPOINT/_ACCESS_KEY/_SECRET_KEY/_BUCKET).- Секреты (Telegram api_id/api_hash, ключи AI-провайдеров, пароль дашборда) в env не передаются — задаются через веб-интерфейс (см. п. 4.1 и 4.6) и хранятся в БД в зашифрованном виде.