# Воронка найма: hiring_project

*Для разработчиков. Состояние на 28.09.2026. Переменные, все ручки, порядок выкатки и отката — README и RUNBOOK репозитория `Buharestskie/hiring_project`.*

## Коротко

`hiring_project` — воронка найма менеджеров по продажам («охотников») для самой компании: от входа кандидата на сайт до выхода на обучение. Кандидат входит через Telegram, VK или MAX, заполняет анкету и записывает голосовой ответ. Голос оценивает Gemini, решение принимает РОП прямо в Google Sheet, дальше кандидату приходит сообщение в мессенджер, приглашение на групповое собеседование в Телемосте и ссылка на онлайн-обучение. В amoCRM кандидат попадает только после собеседования.

Сейчас сервис на наших серверах не развёрнут: проект передан новому владельцу и ждёт переезда. Домен `hiring.donula.online`, заложенный в целевую схему, пока не настроен. Внешней пробы в мониторинге нет.

## Путь кандидата

1. **Вход.** Сайт запрашивает одноразовый токен (`/api/auth/token`, живёт 10 минут) и открывает бота в выбранном мессенджере. Кандидат отправляет боту телефон, сайт опрашивает `/api/auth/check` и получает имя и телефон из мессенджера, а не из формы.
2. **Анкета.** `/api/submit` принимает анкету и голос (`audio/webm`, `audio/mp4`, `audio/m4a`, от 1 KiB до 10 MiB). Анкета сначала пишется в журнал на диске, затем строкой в лист «Кандидаты» через Apps Script. Повтор с тем же `request_id` вторую строку не создаёт.
3. **Оценка.** Бэкенд отправляет голос в Gemini (через ProxyAPI): пять критериев от 1 до 5. 20 баллов и выше — PASS, кандидату сразу уходит приглашение на собеседование; 10–19 — GRAY, решает РОП; меньше 10 — FAIL, кандидату уходит отказ. Если модель недоступна, ставится GRAY с пометкой «AI недоступен, слушайте вручную».
4. **Решение РОПа** — в таблице: столбец T («Одобрен» / «Отклонён») и W — итог собеседования («Принят» / «Отклонён» / «Не пришел»). Apps Script складывает изменения в лист «Webhook Outbox» и доставляет их в бэкенд подписанным запросом на `/api/webhook/rop`; бэкенд пишет кандидату.
5. **Собеседование** — ближайший понедельник, среда или пятница не раньше чем через сутки, в 12:00 по Москве, в Телемосте. Напоминания — за 24 часа и за час.
6. **Обучение.** После «Принят» создаются контакт и сделка `Найм: <имя>` в воронке найма amoCRM, кандидат получает персональную ссылку на курс (живёт 90 дней). Сайт обучения отмечает визит и прохождение, сделка двигается только вперёд: принят → зашёл на обучение → готов к ролевке. После курса открывается ссылка в закрытое сообщество.

## Как устроен

- **Бэкенд** — один процесс FastAPI на Python 3.12 (`runtime/backend/`). Состояние — в каталоге `DATA_DIR`: SQLite для переписки, JSON-файлы для соответствия чатов, записей на собеседование и токенов amoCRM, и журнал анкет. Журнал одновременно служит очередью эффектов: после рестарта недоделанное доигрывается.
- **Google Sheet и Apps Script** (`runtime/apps-script/google-apps-script-hiring.js`) — рабочее место РОПа: листы «Кандидаты», «Обучение», «Трафик», «Расписание МР», «Дашборд», «Системное», «Webhook Outbox». Скрипт выкатывается вручную в редакторе таблицы.
- **Мессенджеры** — своя реализация, не общая с `messenger_connector`: Telegram через long polling, VK и MAX через вебхуки.
- **Inbox** (`runtime/sites/inbox`) — общая лента переписки со всеми кандидатами из трёх мессенджеров и ответ из веб-интерфейса.
- **Фоновые циклы внутри процесса:** напоминания, вечерняя сводка РОПу в Telegram-группу в 20:00 и утренняя в 09:00 по Москве.
- **Статика** `runtime/sites/{auth,inbox,training}` отдаётся nginx и обращается к бэкенду по относительным `/api/…` — открывать её нужно с того же адреса, что и бэкенд.

## Где код и как запустить

- Репозиторий `Buharestskie/hiring_project`. Документы о передаче проекта (`STATUS.md`, `TRANSFER.md`, `ROUTE_MAP.md`) описывают старое размещение и устарели.
- Офлайн-тесты (внешняя сеть заблокирована, нужен ещё Node.js для тестов Apps Script): `bash runtime/tests/run_all.sh` после установки `runtime/backend/requirements.lock` в venv.
- Бэкенд локально — `uvicorn main:app` из `runtime/backend` с явными `DATA_DIR`, `DISABLE_SCHEDULED=true` и `TG_POLLING_ENABLED=false`. Без явного `DATA_DIR` запуск упадёт на правах доступа. `/api/submit` без настоящего Apps Script тестовой таблицы отвечает `503`.
- Целевая выкатка — релизными архивами: `scripts/build-release.sh` собирает архив, `scripts/deploy-release.sh` раскладывает его на хост и переключает симлинк, при провале `/health` возвращает предыдущий; `scripts/rollback-release.sh` — откат. systemd-юниты и шаблон nginx — в `infra/`. Когда сервис будет развёрнут, сначала завести внешнюю пробу, потом выкатывать в окне обслуживания.

## На что наступают

- **Только один Telegram-поллер и один планировщик.** Второй процесс на тех же токенах без `DISABLE_SCHEDULED=true` и `TG_POLLING_ENABLED=false` даёт дубли сообщений кандидатам.
- **`DISABLE_SCHEDULED` не выключает эффекты анкеты.** Оценка, сообщение кандидату и уведомление РОПу запускаются прямо из `/api/submit`. Стенд с боевыми токенами пишет живым людям.
- **Утренняя сводка включена по умолчанию.** Если `ENABLE_ROP_MORNING_NOTIFICATION` не задана, она уходит, хотя по решению проекта выключена.
- **Сессии входа живут в памяти.** Рестарт сбрасывает незавершённые входы кандидатов и начатую авторизацию amoCRM. Токен входа удаляется через 10 минут, поэтому напоминания «через 30/60 минут» почти не срабатывают.
- **Строки в «Кандидатах» не удалять и не сортировать.** Строки находятся по скрытому `request_id` в столбце X. Листы «Системное» и «Webhook Outbox» руками не редактируются.
- **Триггер Apps Script должен быть installable** и работает от имени создателя. Потерял аккаунт доступ — решения РОПа копятся в «Webhook Outbox» и не уходят.
- **Поток GRAY с пометкой «AI недоступен»** — почти всегда кончился ключ или баланс ProxyAPI.
- **Сделку в amoCRM бэкенд ищет по названию** `Найм: <имя>` (первый результат) — однофамильцы могут перепутаться. Ошибка создания сделки только логируется.
- **MAX:** `user_id` не равен `chat_id`, соответствие хранится в JSON-файле в `DATA_DIR`. Потеря файла ломает доставку в MAX.
- **Запись голоса во встроенном браузере мессенджера не работает** — сайт просит открыть ссылку в Safari или Chrome.
- **`build-release.sh` не проверяет чистоту дерева** (проверка унаследована из монорепозитория и смотрит не туда): незакоммиченные правки попадут в архив под чужим SHA. Перед сборкой — `git status`.
- **`runtime/backend/Dockerfile`, `backup_sheets.sh` и `legacy/`** — наследие старого размещения, целевая схема их не использует.