Перейти к содержанию

Кейс · Веб и SaaS, Автоматизация процессов, LLM и агенты

Социальные Медиа реклама в городских пабликах Иркутска

Сервис рекламного агентства из Иркутска: каталог пабликов с открытыми ценами, бронь даты, согласование с менеджером, оплата, маркировка erid, публикация в назначенный час и закрывающие документы — в одном месте. Работать в нём может и ИИ-агент клиента.

129инструментов для ИИ-агентов клиента — каталог, заказы и документы через MCP
100баллов Lighthouse на телефоне — по производительности, доступности, рекомендациям и SEO
0,7 сдо отрисовки главной на компьютере
1 900+автотестов бэкенда, покрытие кода 94 %

Паспорт проекта

Отрасль
Реклама
Срок
8 недель
Команда
Команда E404 и ИИ-агент

Стек

PythonFastAPIPostgreSQLRedisNext.jsTypeScriptDockerЮKassa
Проект вживую ↗

Задача

Клиент — рекламное агентство из Иркутска. Оно продаёт посты в городских пабликах Иркутска и Братска во ВКонтакте, Telegram и Одноклассниках, делает лендинги-новости, ведёт таргетированные и контекстные кампании, а через ООО «Иркутск Сегодня» — продаёт материалы на новостном сайте IRK.TODAY.

Нужен был не «ещё один сайт», а весь путь заказа в одном месте: посетитель видит площадки, цены и свободные даты без регистрации, бронирует время, загружает пост, менеджер проверяет его по правилам площадки и закону о рекламе, клиент платит — и пост выходит сам, в назначенную минуту, с маркировкой. После — ссылка, статистика и закрывающие документы.

Ограничения задавали закон и устройство самого агентства:

  • Маркировка рекламы. Без erid рекламный пост выходить не может. Маркировка через ОРД VK нужна уже в первой версии: без неё запуска нет.
  • Два продавца. Размещения продаёт самозанятый — без НДС, чек из «Мой налог», лимит дохода 2,4 млн ₽ в год. Материалы IRK.TODAY — ООО, только по счёту. У каждого свои реквизиты, нумерация счетов и актов.
  • Не биржа. Каждый заказ проходит ручную модерацию менеджером до оплаты. Тарифов, подписок и баланса нет — оплата поштучная.
  • Персональные данные. Согласие отдельным документом, политика по фактам обработки, удаление учётной записи по запросу — 152-ФЗ.
  • Общий сервер с соседними проектами: ни один наш шаг не должен ронять чужие сайты.

Что сделали

Витрина с открытыми ценами

На лендинге — паблики с подписчиками, охватом и ценой за 1, 5 и 10 постов. Заказ начинается прямо там: площадка, тираж, дата — цена видна на каждом шаге, а календарь свободных дат открыт без входа. Вопрос можно задать менеджеру в чате на сайте, тоже без регистрации.

Таблица девяти пабликов ВКонтакте, Telegram и Одноклассников с подписчиками, охватом и ценами
Каталог до открытия ВКонтакте: подписчики, охват и цены за 1, 5 и 10 постов
Виджет «Ваш заказ», шаг 3 из 4: сетка свободных дат и времени, итог 1 400 ₽, кнопка «Войти и оформить»
Заказ на лендинге, шаг «Дата»: свободное время по Иркутску и итог 1 400 ₽

Заказ в кабинете за пять шагов

Кадры кабинета дальше сняты на стенде с тестовыми данными: клиенты, заказы, суммы и просмотры выдуманы — продаж через сервис тогда ещё не было.

Вход — по коду на почту, через VK ID или Яндекс ID, без пароля. Дальше — мастер заказа:

  1. Площадки — переезжают с лендинга, смета считается сразу.
  2. Креатив — с предпросмотром поста в нужной сети и пометкой «реклама».
  3. Расписание — по иркутскому времени.
  4. Реквизиты плательщика.
  5. Проверка со сметой — и заказ уходит на согласование.

Время закрепляется за клиентом сразу: черновик держит его сутки, заказ на согласовании — 48 часов.

Заказ в кабинете за пять шагов, запись со стенда
Шаг «Креатив»: текст поста, загруженная картинка и предпросмотр в Telegram с пометкой «реклама»
Шаг «Креатив»: текст, картинка и предпросмотр поста в Telegram
Шаг «Проверка»: публикации с датами, смета 2 550 ₽, плательщик и кнопка «Отправить на согласование»
Шаг «Проверка»: даты, смета с платой за erid и плательщик

Модерация и оплата

Менеджер видит очередь заказов — старые сверху: обычно у них ближе дата публикации. В карточке — креатив по каждому паблику, заказчик и плательщик. При согласовании менеджер выбирает код ККТУ для ОРД, а если платит организация, ей тут же уходит счёт. Клиент платит картой через ЮKassa, переводом по номеру телефона с чеком или по счёту — оплату по счёту менеджер отмечает по выписке.

Заказ № 3 у менеджера: заказчик, плательщик, креатив с картинкой для каждого паблика
Карточка заказа у менеджера: заказчик, плательщик и креатив
Карточка заказа № 4 «Ждёт оплаты»: кнопки «Оплатить 1 650 ₽» и «Перевести по номеру телефона или карты», срок оплаты по Иркутску
Заказ ждёт оплаты: карта, перевод или счёт

Маркировка и публикация — автоматически

После оплаты креатив регистрируется в ОРД VK и получает erid: один текст — один креатив на все даты и одна плата, 150 ₽. Пост без erid не выходит. В назначенную минуту воркер выпускает пост в нужной сети — у нас адаптеры для ВКонтакте, Telegram, Одноклассников, MAX и сайтов на WordPress, — а через 30 дней снимает его: сам во ВКонтакте и на WordPress, а в остальных сетях — менеджер по списку. Клиент получает ссылку и статистику там, где сеть её отдаёт.

Заказ № 2: посты во ВКонтакте и Одноклассниках с одним erid и строкой «erid 150 ₽ за креатив на 3 публикации», просмотры, лайки, репосты
Посты вышли: у каждого erid агентства, ссылка и статистика. Числа на стенде — тестовые

Отчёты и документы

Аналитика показывает расходы по месяцам и сетям, а для каждого паблика — сколько просмотров дала тысяча рублей. Счета и акты двух продавцов выходят в PDF сами, акт — только на вышедшие публикации, с той же суммой, что уходит в отчёт ОРД.

Экран «Аналитика»: расходы по месяцам и сетям, таблица площадок с просмотрами на 1000 ₽
Аналитика: расходы, просмотры и отдача по площадкам. Числа на стенде — тестовые
Первая страница акта: исполнитель, заказчик, два размещения и регистрация креатива в ОРД, итого 2 550 ₽; ИНН исполнителя закрыт
Акт выходит сам: только на вышедшие публикации, плата за erid — отдельной строкой. Реквизиты тестовые

ИИ-агент в кабинете

Клиент может выпустить токен и подключить Claude, Cursor или другого агента: тот смотрит каталог, заказы и документы, а с полным доступом — оформляет заказы и пишет менеджеру. Агент может не больше, чем заказчик: он вызывает тот же API с правами владельца токена, а платит всегда сам заказчик. Для агента — 129 инструментов; ещё 39 действий, вроде смены почты входа, закрыты для него с записанной причиной.

Карточка «Доступ для агентов (MCP)»: два токена — «Только чтение» и «Полный доступ», форма выпуска нового
Доступ для агентов: токены «Только чтение» и «Полный доступ»
Шаг «Дата» заказа на лендинге на телефоне: сетка дат, выбрана суббота 20:00Зелёная плашка «Оплачено» и список запланированных публикаций на телефонеАналитика на телефоне: карточки с числами и разбивка по сетям
На телефоне: дата на лендинге, оплаченный заказ и аналитика в кабинете

Как устроено

Схема: клиент работает с лендингом и кабинетом, менеджер — с кабинетом, ИИ-агент клиента подключается к API по токену через MCP. Лендинг и кабинет обращаются к API на FastAPI с MCP-сервером; API хранит данные в PostgreSQL и Redis и принимает оплату через ЮKassa. Воркер по расписанию публикует посты в соцсетях, регистрирует креативы в ОРД VK и работает с той же базой.
Как устроен сервис: кто с чем работает и куда уходят данные

Лендинг и кабинет — на Next.js, API — на FastAPI с PostgreSQL и Redis. Всё, что происходит по часам, вынесено в отдельный воркер: 22 цикла публикуют посты, регистрируют креативы, сверяют оплаты и возвраты с ЮKassa, снимают посты через 30 дней, где сеть это позволяет, и шлют письма. MCP-сервер живёт внутри API и вызывает те же маршруты с токеном агента — второй правды о правах нет.

Основу мы взяли из своих прошлых проектов: ядро — из MAX Bot, чат с клиентом — из WPSupport. Донорский код переносили с исправлениями, а правила, за которыми стоят прошлые ошибки, записали в памятку для всех, кто правит код: цену считает сервер, статусы — не строки, а таблицы переходов, схему базы создаёт только Alembic, местное время переводится в UTC ровно в одном месте.

От чего отказались: от тарифов и баланса — это не биржа; от паролей — вход по коду; от веб-аналитики без согласия посетителя — это риск по 152-ФЗ.

Код проверяет конвейер CI из 19 заданий: больше 1 900 тестов бэкенда с покрытием 94 % при пороге 92 %, сборка образов, сквозные сценарии в браузере — с обоими типами продавца и в двух часовых поясах, поиск секретов в истории git.

Инженерные решения

Иркутск живёт на восемь часов впереди UTC

Слоты публикаций — местное время Иркутска, UTC+8. Сравнить время слота с utcnow() — значит выпустить пост на восемь часов позже, а взять часы браузера — показать клиенту из Москвы время на пять часов раньше. Такие ошибки трудно заметить: неверная дата выглядит как верная.

Мы оставили одно место, где местное время становится моментом в UTC, и запретили сравнения в обход него:

def to_utc(local_date: date, local_time: time | str) -> datetime:
    """Местные дата и время слота -> момент в UTC.

    `local_time` принимается и как `time`, и как строка «HH:MM» —
    из формы приходит строка, из БД объект.
    """
    if isinstance(local_time, str):
        hh, _, mm = local_time.partition(":")
        local_time = time(int(hh), int(mm or 0))
    return datetime.combine(local_date, local_time, tzinfo=agency_tz()).astimezone(UTC)

А сквозные сценарии прогоняем дважды — в поясе Иркутска и в поясе Москвы. Сначала московский прогон ловил ошибку «месяц по часам браузера» только в те часы, когда месяцы двух городов расходятся, — 60 часов в году. Теперь в сценариях часы браузера остановлены на 00:30 первого числа по Иркутску: в Москве в этот момент ещё прошлый месяц, и ошибка видна при любом прогоне.

Запросы с деньгами — вне транзакции

Возврат через ЮKassa сначала был обычным запросом внутри транзакции с блокировкой заказа. Пока ЮKassa отвечала, заказ был заперт: цикл маркировки ждал его, и выходила взаимная блокировка. А выкладка, перезапустившая сервер в эту минуту, теряла запись о деньгах, которые уже ушли клиенту.

Теперь возврат идёт в три шага:

  1. До запроса сохраняются запись «возврат в работе» и снятие публикаций.
  2. Сам запрос в ЮKassa — вне транзакции и без блокировок.
  3. Исход записывается отдельно, а всё оборванное доводит сверка воркера.

Это правило теперь общее: любой внешний запрос с деньгами — вне транзакции.

Запрос агента не должен съедать память сервера

MCP пускает в кабинет запросы, которые составляет не человек, а агент. Ревью показало: один запрос в 16 МБ из мелких значений JSON добавлял серверу 874 МБ памяти при пределе контейнера в 1 ГБ. Разбор JSON случался раньше всех проверок.

Мы перенесли проверку до разбора: большое тело принимается, только если оно большое из-за файла — одной строки content_base64, — и проходит общую очередь больших запросов.

def _outside_file(body: bytes) -> int:
    """Сколько байт тела — не содержимое файла."""
    at = body.find(_FILE_KEY)
    if at < 0:
        return len(body)
    value = _FILE_VALUE.match(body, at + len(_FILE_KEY))
    return len(body) - (len(value.group(1)) if value else 0)

Результат

Сервис развёрнут и открыт: лендинг — на irk.agency, кабинет — на app.irk.agency; на сервере он с конца сентября 2026 года. У пабликов каталога в сумме больше 430 000 подписчиков; одни и те же люди могут быть подписаны в нескольких сетях. Заказы во ВКонтакте открылись, когда поддержка ВК выдала приложению права на стену и фото.

Боевой запуск ещё впереди: магазин ЮKassa не подключён, и продаж через сервис в октябре 2026 ещё не было. Поэтому цифры в этом кейсе — про сам продукт: скорость, проверки, инструменты для агентов, аудитория пабликов. Цифры по заказам и выручке добавим, когда пойдут продажи.

Открыть irk.agency


Лендинг быстрый — Lighthouse 13.5.0, замер в октябре 2026, по одному прогону:

Замер Телефон Компьютер
Производительность 100 98
Доступность, рекомендации, SEO 100 100
Первая отрисовка (FCP) 1,0 с 0,4 с
Отрисовка главной (LCP) 1,2 с 0,7 с
Сдвиг вёрстки (CLS) 0 0
Отчёт Lighthouse для irk.agency: 100, 100, 100, 100; FCP 1,0 с, LCP 1,2 с, CLS 0
Lighthouse живого лендинга на телефоне: 100, 100, 100, 100

Ещё до боевого запуска код за три дня прошёл семь кругов независимого ревью. Каждую находку сверяли с кодом. У каждой правки — тест и мутация: в код возвращали исправленную ошибку и проверяли, что тест её ловит. В журнале записано больше 700 таких мутаций — и все пойманы. Находок больше 250 — от гонок при оплате до текста подсказки на кнопке.

Комментарии

Писать можно, войдя через VK ID, через Яндекс ID или по коду из письма. У комментария — имя и фото из профиля VK или Яндекса либо имя, которое вы укажете при входе по почте. Как мы обращаемся с данными — в политике конфиденциальности.

Поделиться записью

TGVK