Перейти к содержимому
VC
Кейс 23 из 33 · AI / Аналитика продаж

Бот отвечает цифрами по продажам: шесть метрик и состязательная проверка арифметики

Владелец бренда товаров для активного отдыха из США спросил бота в Telegram про продажи по странам за два года и цифр не получил. Теперь бот отвечает живыми числами из двух магазинов на Shopify: страны, годовые окна, топ товаров по деньгам и по штукам, штаты США в разрезе каналов. Вся арифметика идёт через единое ядро метрик. Через час после выпуска четыре независимых агента-проверяющих нашли четыре ошибки — каждая меняла смысл ответа.

Отрасль
Интернет-торговля · два магазина на Shopify
Стек
Python · Shopify Admin API · Telegram · pytest
Сроки
Две итерации за неделю
Итог
2 → 6 метрик, расхождение с отчётом 0
01 · Боль

Живой доступ к платформе был, цифр в ответе не было

Бренд товаров для активного отдыха из США, два магазина на Shopify, владелец, который задаёт вопросы боту в мессенджере между делами. К этому моменту у бота уже была база знаний по переписке команды и тонкий живой слой поверх торговой платформы с двумя метриками: выручка за период и соотношение основных товаров и аксессуаров.

В августе владелец спросил: «сколько продали в 2025 и 2026 годах и в какие страны, кроме США и Канады». Ответ пришёл из базы знаний — текстом, без единой цифры. Проверка показала, что живой доступ к платформе работает: «выручка за 30 дней» отвечалась сразу. Географии в наборе метрик просто не существовало. Корень лежал глубже: отчётный модуль вообще не запрашивал у платформы адрес доставки. Страны в наших данных не было в принципе, хотя API её отдаёт.

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

02 · Решение

Шесть метрик через одно ядро — языковая модель вне контура чисел

Вопрос из мессенджера распознаётся по ключевым словам, попадает в фиксированный набор метрик и считается кодом через единое ядро аналитики — то же, которым собирается официальный отчёт по магазинам. Цифра либо посчитана детерминированно, либо её нет: нераспознанный вопрос уходит в базу знаний, как и раньше.

01
Вопрос

Владелец пишет боту по-русски или по-английски: «топ товаров за 30 дней», «продажи по штатам за 2026»

02
Распознавание намерения

Ключевые слова и явный порядок проверок: штаты раньше стран, топ раньше выручки, годы раньше дней

03
Сборщики

Отдельные модули для стран, топа товаров и штатов в разрезе каналов; окно, статусы и возвраты они берут у ядра

04
Ядро метрик

Единый фильтр считаемого заказа, возвраты построчно, скидки по распределениям, часовой пояс магазина — одна арифметика на бота и отчёт

05
Ответ и журнал спроса

Числа печатаются деньгами и штуками; вопрос, оставшийся без живой цифры, попадает в журнал без имени и идентификатора чата

Почему в контуре чисел нет языковой модели

Ценность всего контура в том, что числам верят. Модель со свободным доступом к API рано или поздно выдаёт правдоподобное число из воздуха, и один такой случай в переписке с владельцем обнуляет доверие ко всем остальным отчётам. Поэтому решение принимает код: ключевые слова на двух языках, фиксированный набор метрик, вызов ядра. На нераспознанный вопрос слой возвращает пустой результат, и бот отвечает из базы знаний. Молчание здесь честнее догадки: полнота осознанно принесена в жертву точности.

Чтобы набор метрик рос по реальному спросу, числовые вопросы без живой цифры складываются в журнал: только текст вопроса после вычистки секретов, без имени, логина и идентификатора чата. Сводка за две недели показывает, чего боту не хватает.

Три вида отказа различаются осознанно

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

География: заказ без адреса остаётся в итоге

Страна берётся из адреса доставки — туда реально везли товар. Заказы без адреса (цифровые товары, самовывоз) входят в общий итог и ни одной стране не приписываются: иначе они молча утекли бы в США и исказили картину. Годы распознаются раньше дней, поэтому «за 2025» даёт календарный год; «кроме США и Канады» вычитает две страны из выдачи. Вопрос владельца теперь отвечается дословно: заказы, выручка и число стран по каждому году, отдельной строкой — заграница. В 2025 году заказы уехали в 21 страну, 19 из них — за пределами США и Канады.

Топ товаров: деньги и штуки печатаются вместе

На этом бизнесе две метрики расходятся: аксессуары дают около 22% денег и около 63% штук. Одна колонка подтолкнула бы к ложному выводу по ассортименту, поэтому у каждой позиции печатаются обе доли плюс отдельный блок «много штук, мимо денежного топа». Ключ агрегации — идентификатор товара из каталога: название у одного товара плавает, артикул пуст примерно у 87% строк позиций. Проверка топа переставлена раньше проверки выручки — первое из двух исправлений порядка распознавания.

Штаты США в разрезе каналов продаж

Просьба владельца звучала так: чистые продажи по штатам, общие и по маркетплейсу, за вычетом возвратов и скидок. База «чистых» — позиции заказа после скидок минус возвраты по позициям, без доставки и налогов. Этот же инвариант сверяет ядро, поэтому сумма по штатам обязана сходиться с общим отчётом. Канал продажи в выборке ядра до этого отсутствовал: в поля запроса добавлены источник заказа и теги, версия кэша поднята. Владелец получает CSV и ответ бота, собранные из одних чисел.

Вопрос «по штатам США» содержит слово «США» и без приоритета уезжал в разрез по странам — второе исправление порядка: штаты проверяются раньше географии.

03 · Стек

Арифметика живёт в одном месте, сборщики только агрегируют

Python

Распознавание намерения, сборщики, ядро метрик и доставка ответа — один язык на весь контур

Shopify Admin API

Заказы с позициями, возвратами, адресом доставки, источником и тегами канала; доступ только на чтение

Ядро метрик

Единый фильтр считаемого заказа, возвраты построчно, скидки по распределениям, окно и часовой пояс магазина

Распознавание по ключевым словам

Русский и английский, явный порядок приоритетов: штаты → страны, топ → выручка, годы → дни

Telegram Bot API

Вопрос и ответ в личных сообщениях; тот же бот отвечает по базе знаний

Журнал спроса (JSONL)

Числовые вопросы без живой цифры, текст после вычистки секретов, без личности; сводка за 14 дней

Агенты-проверяющие

Четыре независимых разбора свежего кода через час после выпуска; пять линз перед отчётом по штатам

GraphQL, контрольный запрос

Сверка возвратов с чистым платежом платформы: заграница до цента, итог года в пределах 0,1%

pytest

5 защитных тестов на арифметику, 27 на разрез по штатам, полный прогон — 1312

PythonShopify Admin APIGraphQLTelegram Bot APIpytestJSONLCSV
Масштаб живого слоя

Пять модулей на Python — распознавание намерения, сборщики стран, топа товаров и штатов, журнал спроса — около 1,2 тыс. строк, плюс около 600 строк тестов. Каждый сборщик умещается в 150–300 строк, потому что арифметику он берёт у ядра.

Масштаб проверки

Четыре агента через час после выпуска, пять независимых линз перед отчётом по штатам. Сверка на живом окне в 30 дней: 324 заказа во всех трёх отчётах, расхождение по товарной выручке ноль. Полный прогон тестов — 1312 зелёных.

04 · Результат

Четыре ошибки за час и ноль расхождений после

Метрик с живой цифрой
2 6

страны, годовые окна, топ по деньгам и по штукам, штаты × каналы

Завышение товарной выручки в топе
+8,54% 0,00

сверка на живом окне в 30 дней: 324 заказа во всех трёх отчётах

Бесплатных заказов в счётчиках штатов
264 0

ручная скидка 100% за два года: раздачи отделены от продаж

Через час после выпуска: четыре агента, четыре ошибки

Свежий код разобрали четыре независимых агента-проверяющих. Находки:

  • Топ товаров не вычитал возвраты вовсе — товарная выручка завышена на 8,54%.
  • У сборщика был свой набор статусов оплаты: два статуса ядра молча выпадали. Отсюда 324 заказа в отчёте против 323 у метрик.
  • Агрегация шла по полю типа товара, которого в позициях заказа нет — тип живёт в каталоге. Колонка типа всегда была пустой.
  • География считала отмену авторизации платежа возвратом. Таких сумм на тот момент было ноль, ошибка спала бы до первой отменённой авторизации.

Все четыре — один класс: сборщик считал сам. Теперь вся арифметика — вызовы ядра: единый фильтр считаемого заказа, возвраты построчно, скидки по распределениям. География больше не делает собственный запрос — адрес доставки добавлен в поля ядра, версия ключа кэша поднята, иначе шесть часов читались бы старые данные без этого поля. Сверка после правок на живом окне в 30 дней: 324 заказа в отчёте, географии и топе; расхождение по товарной выручке — 0,00.

Пять защитных тестов поверх находок

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

Перед отчётом по штатам: ещё четыре находки

Отчёт по штатам владелец попросил вечером и получил в тот же вечер. Перед отправкой цифры прошли проверку пятью независимыми линзами:

  • Черновик и маркетплейс — разные признаки. Формулировка «заказы маркетплейса помечаются черновиками» напрашивалась на фильтр по черновику. Замер за семь недель: в одном магазине из 106 черновиков к маркетплейсу относился 81, во втором — 12 черновиков и ни одного заказа маркетплейса. Фильтр по черновику приписал бы маркетплейсу 25 чужих заказов и выдумал бы 12 там, где его нет. Канал определяется тегом заказа, черновик показывается отдельной контрольной строкой.
  • Раздачи блогерам сидели в продажах. 264 заказа за два года с ручной скидкой 100%: платёж не принимался, статус стоял «оплачен», и фильтр считаемого заказа их пропускал — в счётчик каждого штата и каждой страны. Теперь оплаченные и бесплатные разделены; подарок с нулём с рождения отличается от продажи с нулём после возврата.
  • Возврат живёт в платформе в двух представлениях, ни одно не покрывает всё. Учитывалось одно — годовая выручка завышалась примерно на 2%. Замена на второе чинила США и Канаду и ломала заграницу, где в транзакцию входят доставка и налог. Решение позаказное: есть транзакция — берётся она, нет — позиции. Сверка с контрольным запросом: заграница сошлась до цента за оба года, итог года — в пределах 0,1%.
  • Подпись «полный год» стояла на 7,6 месяцах. Платформа принимает будущую границу окна без ошибки, и рядом с полным прошлым годом рост на треть читался как падение на 17%. Конец текущего года теперь прижимается к сегодняшней дате.

27 новых тестов закрепляют ровно эти находки; полный прогон — 1312 зелёных.

Что изменилось в работе

Вопрос про продажи задаётся боту в мессенджере и возвращается живыми числами из двух магазинов: страны, годы, топ по деньгам и по штукам, штаты и каналы. Отчёт по штатам ушёл владельцу в CSV и в ответе бота из одних и тех же чисел. Вопросы без цифры копятся в журнале, и следующие метрики добавляются по реальному спросу.

05 · Применимость

Где ещё ложится та же методология

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

  • Интернет-магазины на любой платформе — вопрос владельца в мессенджере отвечается цифрой из API, посчитанной тем же кодом, что и официальный отчёт
  • Мультиканальные продажи и маркетплейсы — канал определяется признаком, проверенным замером; черновики и раздачи отделены от продаж
  • AI-помощники с доступом к учётной системе — модель формулирует и ищет по документам, арифметику считает код; нераспознанный вопрос попадает в журнал спроса
  • Сводки для руководителя — возвраты, отмены и скидки вычитаются в одном месте, подпись периода соответствует данным внутри
  • Команды с несколькими отчётами — защитные тесты держат все сборщики на едином ядре, расхождение ловится в CI

Какой канал приносит заказы — вопрос сквозной атрибуции, она живёт вокруг пути клика. Здесь вопрос другой: сколько продали, кому и где, и можно ли этой цифре верить.

Что переиспользуется на следующих проектах
  • Распознавание намерения по ключевым словам с явным порядком приоритетов и тестами на перехват
  • Единое ядро метрик: фильтр считаемого заказа, возвраты, скидки и окно в одном месте; сборщики только агрегируют
  • Защитный тест, который разбором исходного кода запрещает сборщику заводить свой фильтр по статусу
  • Состязательная проверка свежего кода несколькими независимыми агентами до отправки цифр заказчику
  • Журнал вопросов без ответа как план по следующим метрикам
Похожая задача?

Если три отчёта дают три разные выручки и никто не знает, какой верить — это решается

Начинаем с одного ядра метрик и одной сверки с официальным отчётом до цента. Бот с живыми цифрами появляется после того, как арифметика сошлась, и каждая следующая метрика встаёт на то же ядро.

Готовы начать?

Аудит за 9 900 ₽ — с конкретным отчётом и сметой

Расскажу что внедрить в вашем бизнесе в первую очередь, какая будет окупаемость, и нужен ли вообще AI для вашей задачи (иногда — нет).

Или просто напишите свой вопрос — отвечу в течение 2 часов