Кейс

Бот для комитета подбора арендаторов: правила, уроки из отказов, честные замеры

5 мин чтения
982 слов

Агентство коммерческой недвижимости каждый день проводит «комитет»: брокеры смотрят новые объекты и решают, каким арендаторам их предложить. Раньше это делали вручную в Telegram-чате, а выполнение обещаний сверяли по памяти. С июля 2026 года я веду для агентства бота, который проводит этот комитет вместе с людьми. В бою он с 2 сентября, а с конца октября работает на сервере заказчика.

Ниже — какие решения сработали, какие цифры я считаю честными и что пришлось выбросить.

Задача и ограничения

Комитет устроен так. Сотрудник выкладывает новый объект: помещение, площадь, цена, район. Брокеры подбирают к нему заявки арендаторов из CRM и обещают отправить коммерческое предложение. На следующий день руководитель проверяет, кто отправил, а кто нет.

Что требовалось от бота:

Ограничения: CRM с плохо документированным API, данные заказчика, бюджет на модель — копейки на объект, а не рубли.

Решение 1. Правила до модели и после неё

Первый соблазн — отдать модели объект и всю базу заявок. Это дорого и плохо работает: модель путает районы и забывает про площадь.

Я разделил работу. Детерминированный фильтр из 10 правил отсекает заявки до модели: тип сделки, назначение, площадь, бюджет, район, географические зоны, этаж, вытяжка и ещё несколько условий. Модель получает короткий список и выбирает кандидатов и ниши. Затем тот же список правил проверяет её ответ, поэтому предложить заявку, которую правила запрещают, модель не может.

Детали, которые оказались важными:

Решение 2. Уроки из отказов вместо дообучения

Брокеры отказываются от предложенных заявок и пишут причины: «уже смотрели», «ищут только центр», «нужен первый этаж». Дообучать модель на этом дорого и медленно. Я сделал проще:

Замер. 15 последних объектов, по два прогона на каждый вариант, три варианта:

Вариант Отвергнутые брокерами заявки в выдаче
без отзывов 70%
отзывы без понижения 67%
отзывы с понижением 51%

По объектам понижение оказалось лучше одних отзывов в 10 случаях и хуже в одном (знаковый критерий, p = 0,006). Отдельно проверил на разметке руководителя комитета, что хорошие кандидаты при этом не вытесняются.

Чего замер не показывает: часть отзывов оставлена позже проверяемого объекта, поэтому на старых объектах эффект может быть завышен. Так и написано в отчёте заказчику.

Решение 3. Код добирает то, что модель теряет

Часть решений комитет принимает голосом на встрече: «по заявке такой-то отправляем». Запись встречи бот режет на куски, распознаёт и превращает в черновик назначений для администраторов.

Модель на длинной расшифровке пропускает номера. На одной записи прозвучало 69 номеров заявок: модель нашла 50, ещё 19 добрал код поиском номеров в тексте, потеряно 0. Обратная защита тоже нужна: если в куске записи тишина, код не даёт модели «додумать» решения.

Распознавание речи выбирал сравнением на реальной записи комитета. Сущностей обе системы находили поровну, но у одной не было пунктуации и почти не было латиницы, а латиница встречается в названиях сетей-арендаторов. Выбрал вторую.

Решение 4. Модель — по замеру, провайдер — по доступности

Сравнил все чат-модели Yandex, линейку GigaChat и Gemini на одинаковых объектах. В бою осталась Gemini Flash. Позже доступ к ней перевёл на российского посредника: оплата идёт с юрлица заказчика, запросы уходят из РФ без прокси, а модель и промпт остались прежними.

Переезд на сервер заказчика

До конца октября бот жил на моём сервере. 30 октября перенёс его на российский сервер заказчика: systemd-служба, nginx с HTTPS, внешний сторож с алертами. Простой составил около минуты.

Telegram с российского сервера напрямую не открывается, поэтому бот ходит к нему через SSH-туннель на зарубежный сервер заказчика. В день переезда туннель не поднимался. Оказалось, что адрес зарубежного сервера точечно заблокирован из России: ping проходил, TCP на всех портах молчал, а соседние адреса той же подсети отвечали. Заказчик добавил серверу второй адрес, через него туннель заработал, и переезд прошёл в тот же день.

Что не сработало и чего я не утверждаю

Цифры проекта

Стек: Python 3.12, FastAPI, aiogram 3, SQLAlchemy (async) + SQLite, APScheduler, Gemini Flash через российского посредника, rapidfuzz, ffmpeg, API CRM INTRUM, API 2ГИС, systemd, nginx. Разработка — через Claude Code: спецификация, тикеты, тесты, ревью.