16 500 отзывов в одном рабочем контуре, 9 500 записей в собственном хранилище, четыре XML‑фида, 4 000 строк кода и 130+ страниц документации. Все это работает за 8 500 в месяц.
Вместо введения: что здесь вообще произошло
Задача была очень простая: сделать так, чтобы отзывы гостей ресторанной сети перестали жить в десятке вкладок, кабинетов, выгрузок и ручных таблиц. На первый взгляд — обычная автоматизация. Забрать данные, сложить в одно место, показать человеку. Но мы не ищем легких путей)
Для общепита отзывы — это поток данных о продукте, доставке, работе смен, коммуникации и проблемах, которые начинают повторяться задолго до того, как становятся заметны в финансовых отчетах. Пока этот поток собирается вручную, весь процесс держится на памяти и внимательности одного человека. Вроде работает. До первой пропущенной вкладки, неверного филиала или сломанной строки.
Я маркетолог, хоть и с сильным техническим уклоном, но код до этого проекта не писала. Зато хорошо понимала сам процесс: кто работает с отзывами, зачем они нужны бизнесу, где люди ошибаются и какие данные потом приходится вытаскивать из отчета.
В Review Warehouse я прошла полный цикл AI‑assisted development: от постановки задачи и выбора архитектуры до serverless‑контура, D1-хранилища, XML‑фидов, мониторинга, автоответов и документации для эксплуатации. Код помогал писать ChatGPT. Решения о том, что этот код вообще должен делать, как хранить данные и что считать ошибкой, оставались на мне.
В общем, код здесь был только одним из материалов. Основная работа — разложить живой бизнес‑процесс по полкам и не дать автоматизации бодро делать неправильные вещи.
Как процесс выглядел до проекта
Дано: локальная сеть ресторанов с тремя филиалами и позже еще довесок — кофейня. Отзывы приходили из CRM (сайт и приложение), Яндекс Карт, Google Maps, 2ГИС, VK, Нельзяграм, email и отдельных офлайн‑обращений. Часть жила в кабинетах карт, часть — в CRM, часть — в соцсетях и переписках.
Каждый день менеджер открывал всё это по очереди, искал новые отзывы и сообщения, переносил их в таблицу, указывал филиал, источник, оценку, дату, текст и статус обработки. Потом та же таблица превращалась в ежемесячную аналитику: рейтинги, доля негатива, компенсации, динамика и повторяющиеся косяки.

Проблема была не столько в объеме, сколько в самой механике. Чтобы не потерять один отзыв, нужно помнить, куда зайти, какой раздел открыть, какие фильтры проверить и что уже перенесено. Забыла один источник — потеряла обратную связь. Ошиблась в одной строке — поплыла аналитика.

Проверка обычно уезжала на поздний вечер, после закрытия ресторанов. Час в день звучит нестрашно, пока не умножаешь его на каждый рабочий день и не добавляешь ежемесячное сведение отчета. Передать эту работу другому человеку тоже было сложно, несмотря на описанный в обучающих материалах процесс — слишком много нюансов нужно держать в голове.
Что нужно было изменить
Я не формулировала задачу как «сделать парсер отзывов». Нужно было поменять саму модель работы с обратной связью: убрать ежедневную ручную агрегацию, сохранить историю, развести бренды и филиалы, не плодить дубли и оставить менеджеру нормальный рабочий интерфейс:
карты и автоматические источники обновляются без ручного переноса;
ручные источники попадают в систему по простому и проверяемому сценарию;
данные хранятся отдельно от интерфейса, в котором с ними работает менеджер;
ошибки видны человеку, а не тихо лежат где‑то в логах; после сдачи систему можно поддерживать и расширять, не собирая ее заново.
И вот этот фокус позже сэкономил мне прилично лишней разработки. Когда смотришь только на технологии, хочется немедленно написать всё свое. Когда смотришь на работу менеджера, риски и стоимость поддержки, выясняется, что часть задач разумнее отдать готовому сервису.
Первая гипотеза: n8n, API, парсеры и GPT
Первая схема была почти образцовой: n8n как оркестратор, CRM и VK через API, карты через парсеры, Google Sheets как промежуточный слой, GPT — для тональности, тегов, черновиков ответов и аналитики.
С CRM всё выглядело прилично: API, стабильные ID, пагинация. С VK тоже можно было работать через API или контролируемый ручной ввод. А вот карты довольно быстро превратились в отдельную BDSM практику. Где‑то нет удобного публичного API, где‑то данные отдаются кусками, где‑то приходится разбирать network‑запросы, сортировки и HAR‑файлы. Площадка меняет что‑нибудь у себя — парсер идёт полежать.
Постоянно обслуживать парсеры Яндекса, 2ГИС и Google мне показалось, мягко говоря, неудобным. Стала искать отдельный сервис именно для карт, чтобы закрыть этот кусок.
Откуда в проекте появился OK Review
Я перебрала с десяток сервисов управления репутацией. Смотрела прямые интеграции с картами, аналитику, ответы из одного окна, импорт собственных данных и стоимость для нескольких филиалов.
По возможностям мне даже больше понравился Пойнтер. Потом пришло КП — около 15К за один филиал в месяц. Оу, для четырех точек это порядка 60К. На этом наше знакомство закончилось(
У OK Review тариф оказался гуманнее: ~2000 за филиал, итого 8К/месяц за четыре точки. За эти деньги сервис собирает отзывы с карт, даёт единую ленту, аналитику, автоответы и принимает любые внешние источники через XML feed.
И вот тут проект развернулся. Вместо попытки построить собственный комбайн для всего на свете оказалось логичнее оставить карты готовой платформе, а свой слой писать только для того, чего она не умеет.
Рекламы сервиса OK Review здесь нет. В процессе хватало ограничений и косяков: сначала мне неверно объяснили историческую загрузку, по Яндексу обнаружился лимит, синхронизация карт иногда идёт небыстро, а API CRM пришлось самой превращать в XML. Но за вменяемые деньги сервис закрыл самый хрупкий кусок схемы и сильно упростил дальнейшую разработку.
Почему не «просто сервис управления репутацией»
Готовая платформа закрыла карты, единый интерфейс, ответы и базовую аналитику. За бортом остались API CRM, webhook новых отзывов, соцсети, email и исторические дозагрузки.
Главный затык был в форматах. CRM отдавала отзывы через API в JSON, а сервис управления репутацией принимал внешние данные через XML. Между ними требовался слой, который заберёт данные, приведёт поля к одной модели, определит филиал, проверит дубли, сохранит исходное событие и соберёт отдельный фид для каждой точки.
Мне также был нужен собственный контроль данных. Внешняя платформа показывает конечный отзыв, но не объясняет, почему конкретная запись не дошла, где потерялся shop_id и что происходило во время синхронизации. Для этого нужны raw events, журналы запусков и нормальные правила обработки ошибок.
Как устроилась финальная схема
Карты идут в сервис управления репутацией напрямую. Все нестандартные источники проходят через Cloudflare Worker и D1. CRM отдает историю через REST API, а новые отзывы — через webhook. Ручные источники менеджер добавляет в Google Sheets. Worker читает данные, нормализует, пишет в D1 и публикует четыре XML‑фида: три для ресторанов и один для кофейни.
Сервис управления репутацией остается рабочим окном менеджера. D1 хранит правду по кастомному контуру. Google Sheets нужен только как вход для ручных данных. XML работает транспортом между хранилищем и внешней платформой.
Никакого универсального механизма для всех источников здесь нет — и ок. Карты лучше отдать сервису, который уже умеет с ними жить. CRM надёжнее забирать через API и webhook. Ручное обращение проще занести в таблицу. Диагностику и историю лучше держать у себя.
Google Sheets перестал быть отчётом
Раньше в таблице жили вообще все отзывы и вся аналитика. После внедрения она стала input‑layer — контролируемым входом для того, что нельзя подключить напрямую: сообщений и отзывов из соцсетей, email и других ручных обращений.

Карты и CRM туда больше не заносятся. Иначе один отзыв легко появляется дважды: через прямую интеграцию или API и ещё одной ручной строкой. Таблица перестала изображать из себя базу данных, отчёт и рабочий журнал одновременно. Теперь у нее одна задача — аккуратно принять ручной источник.
D1 как Review Warehouse
В Cloudflare D1 лежат финальные отзывы, справочники проектов и филиалов, связи shop_id с точками, raw events, история запусков, ошибки и служебные состояния. Это уже не временная прокладка перед XML, а review warehouse, к которому можно вернуться при любой сверке.
В модели отдельно хранятся source, platform и ingest_method. Благодаря этому видно не только откуда отзыв по смыслу, но и как он попал в систему. Исторический отзыв Яндекса имеет platform = yandex и ingest_method = backfill. Новый отзыв CRM — platform = crm, а способ загрузки может быть api или webhook.
Для редких кривых случаев есть review_exclusions. Запись остаётся в исходных данных и истории, но не уходит в сервис управления репутацией. Так я изолировала 12 старых отзывов из CRM, которые были в API‑выгрузке, однако в саму CRM когда‑то не дошли из‑за сетевого сбоя. Удалять их бессмысленно, тк следующий полный импорт притащит их обратно. Поэтому D1 их хранит, а XML делает вид, что их нет.
Проверка масштабируемости
Сначала система была полностью собрана и запущена для трез ресторанов: одна CRM, три XML‑фида и общий контур ручных источников. Кофейню я подключила уже после этого, и получилась реальная проверка архитектуры на расширение.
D1 специально проектировалась под такие ситуации. Проекты, филиалы, источники, платформы и способы загрузки вынесены в справочники, а связи задаются через project_id, location_id и source mappings. Поэтому для кофейни не пришлось клонировать базу или собирать второй Worker. Я добавила новый проект, локацию, отдельный лист Google Sheets, четвертый XML‑фид и разрезы в dashboard.
Данные двух брендов не смешались, а общая логика нормализации, проверок и мониторинга осталась прежней. Вот это уже нормальная проверка масштабируемости на живом проекте, а не обещание на архитектурной схеме.
Worker: 4 000 строк кода вокруг довольно простой идеи

Cloudflare Worker принимает события, приводит их к общей схеме, проверяет обязательные поля, делает upsert в D1, генерирует XML, запускает синхронизации и собирает dashboard. Код так разросся не из‑за сложности самого импорта. Объем набрался вокруг надежности: валидация, дедупликация, разные источники, диагностика и ремонтные сценарии.
Один из базовых принципов — Worker не гадает. Если CRM прислала отзыв без shop_id, система не назначает филиал «по умолчанию». Событие остается в raw_events, появляется в ошибках и ждет проверки. Неверно распределенный отзыв гораздо хуже временно необработанного.
Мониторинг: status, cron, Telegram и Tail Worker
Черный ящик из системы делать не хотелось. Человекочитаемый dashboard показывает общий статус, текущие ошибки и распределение отзывов. Проблемы с данными для сервиса управления репутацией, XML‑фидами, CRM, Google Sheets и Telegram собираются в одном месте.

Google Sheets синхронизируется раз в три часа. Telegram‑проверка ходит каждые 30 минут и использует облегченный запрос, а не полный тяжелый status. Пока все нормально, бот молчит. Появилась новая сигнатура ошибки — отправляет сообщение. Ошибка исчезла — сообщает о восстановлении.
Для sync_runs добавлена защита от зависаний. Старые started‑записи закрываются автоматически, аварийные запуски получают статус failed, а Брайтсайд и Lazy coffee синхронизируются независимо. Один сломанный лист не должен класть второй проект.
Платформенные ошибки Cloudflare собирает отдельный Tail Worker. Он получает итог выполнения вызова, исключения и системные сбои, пишет их в worker_runtime_events, а dashboard показывает рядом с прикладными ошибками. Это нужно для случаев, когда основной Worker умер настолько быстро, что не успел записать даже собственный catch.
Историческая миграция и backfill
Сначала в сервисе управления репутацией сказали, что в базе будут только отзывы, оставленные после подключения сервиса. Я поплевалась и начала собирать историю сама. Для 2ГИС адаптировала open‑source парсер. Яндекс разбирала через network‑запросы и HAR. Google пришлось выгружать по нескольким сортировкам, а потом чистить дубли.
Когда данные уже были спарсены, поддержка сервиса управления репутацией раздуплилась: историю с карт все‑таки загрузят, но Яндекс отдаёт не больше 600 отзывов. И тут моя возня пригодилась. На одном филиале прямой импорт уперся в лимит, а у меня уже лежала большая выгрузка.
Недостающий хвост подгрузила в Google Sheets и передала через D1 и XML как yandex_backfill. История получилась полнее (14 отзывов из 808 всё же не удалось никак дособрать), а дополнительные записи не смешались с обычной ежедневной загрузкой: у них отдельные source и ingest_method.
Парсинг в итоге стал не основным контуром, а страховкой от ограничений внешнего сервиса. Для постоянного сбора карт он слишком хрупкий. Для разовой сверки и дозагрузки — норм.
XML‑фиды: внешний слой, а не база
Для каждого филиала Worker собирает отдельный XML‑фид. Туда уходят только поля, которые понимает сервис управления репутацией: стабильный ID, дата, оценка, автор, текст и несколько служебных attributes. Проекты, журналы, mappings и исключения остаются внутри D1.
Это удобно и при ремонте. Исключили запись из XML — она исчезла из сервиса управления репутацией после синхронизации, но осталась в D1 вместе с причиной. Пришел неполный payload — raw event тоже сохранен, хотя финального отзыва пока нет.

В документации лежит не только описание архитектуры. Там сохранены актуальные коды основного и Tail Worker, D1 schema и migrations, endpoint‑ы, bindings без секретных значений, контрольные SQL‑запросы, порядок развертывания и отката, repair‑регламенты и раздел по технической поддержке и SLA. Иначе через полгода вся эта красота снова превратится в археологию.
А еще этот файл можно скормить любой нейронке и продолжить разработку — никакого дополнительного контекста не потребуется. Так поддержание и развитие проекта не завязано на исполнителе.
Автоответы: где автоматизация уместна, а где нет
В сервисе управления репутацией настроены сценарии по рейтингу, тональности и наличию текста. Оценка без комментария закрывается безопасным шаблоном. Позитивный отзыв тоже не требует отдельного расследования. Негатив, спорные ситуации и конкретные жалобы остаются человеку.

Здесь граница автоматизации важнее количества сценариев. Плохой автоответ экономит минуту и создает новую репутационную проблему. Поэтому бот берет только низкорисковые случаи. Все, где нужен контекст, проверка заказа или компенсация, идет менеджеру.
Что пошло не так
Если показать только финальную схему, получится подозрительно гладкая история. В реальности заметная часть разработки состояла из «почему тут опять какая‑то хрень» и последующего разбора.
Парсеры собирали не то, не так или не все
2ГИС сначала отдавал пустой файл, потом отзывы расползались по строкам из‑за переносов текста. Яндекс собирал 50 записей, пока не выяснилось, что нужно менять сортировку, чистить network log, прокручивать ленту до конца и работать с HAR. Google щедро смешивал отзывы, ответы владельца и дубли между сортировками. Первый CSV — это не миграция, а сырье для нее. Дальше все равно нужны нормализация, сверка количества, дедупликация, сопоставление филиалов и тестовая загрузка.
Счетчик CRM и API расходился на 12 записей
В D1 оказалось на 12 уникальных отзывов больше, чем показывал интерфейс CRM. Я выгрузила все crm_id и отправила файл поддержке. В логах нашли эти записи: наружу данные ушли, а в CRM когда‑то не записались из‑за старого сетевого бага. Так и появился review_exclusions — входящую историю не трогаем, в рабочую систему лишнее не передаем.
На бесплатном Cloudflare cron уперся в CPU
Две cron‑задачи завершились с exceededCpu на бесплатном лимите 10 мс. Telegram просто перестал присылать уведомления, а мой dashboard в тот момент не умел внятно объяснить, что случилось. После этого я перешла на Workers Paid, облегчила Telegram‑check, добавила heartbeat задач, защиту sync_runs и Tail Worker для системных outcomes Cloudflare.
Код в чате оказался удобен до первого серьезного копирования
Сначала я просила GPT присылать большие куски кода прямо сообщениями. Довольно быстро получила случайные символы, неверные вставки и однажды файл из одного слова. После этого правило стало железным: версии Worker передаются готовым файлом с номером версии. Небольшой диагностический SQL можно оставить в сообщении. Production‑код — нет.
AI может уверенно предложить то, чего нет
В одном варианте кода появилась переменная окружения, которую я никогда не создавала. В другом — fallback «если филиал неизвестен, поставить вариант № 1». Технически все даже запускалось. По смыслу — портило данные. AI не знает бизнес‑процесс лучше человека, который его ведет, и спокойно достраивает потерянный контекст чем‑нибудь правдоподобным.
Контекст перестал помещаться в один чат
Диалог с AI разросся в полноценный рабочий архив. Продолжать разработку только внутри него стало сложно — легко пропустить правило или вернуться к старой версии кода. Контекст пришлось вынести в документацию, архив обсуждения и версионированные файлы. В общем, AI‑разработка довольно быстро потребовала обычного project management. Хотя пожалуй в целом стоило начать с Claude Code или Codex)
Как я управляла AI‑разработкой
Моя роль была не в том, чтобы вручную писать JavaScript. Я управляла циклом: формулировала бизнес‑ограничение, проверяла предложенную логику на реальных данных, фиксировала, где она врет, и решала, что менять дальше:
Описать, что должно измениться в данных или в работе человека.
Получить архитектурный вариант, SQL или код.
Запустить его на тестовом либо контролируемом реальном массиве.
Сверить D1, XML, dashboard, количество строк и внешний интерфейс.
Зафиксировать, что сработало, что сломалось и откуда взялась разница.
Обновить код, документацию и правила эксплуатации.
AI сильно удешевлял одну итерацию. Можно быстро разобрать API, написать миграцию, собрать SQL‑проверку или переписать кусок Worker. Но дешевизна итерации провоцирует навалить сразу десять изменений. Поэтому правило получилось довольно скучное и очень полезное: сначала SELECT, потом UPDATE; сначала проверка, потом изменение; одна версия — один понятный набор правок.
Что получилось

На момент фиксации кейса в сервисе управления репутацией собрано более 16 500 отзывов. Более 9 500 проходят через собственный контур Cloudflare Worker и D1. Система обслуживает четыре XML‑фида, два проекта и несколько способов загрузки. Worker перерос 4 000 строк, документация составила 130+ страниц.
Ежедневная ручная нагрузка менеджера сократилась примерно втрое. Карты переехали в единое окно, Google Sheets остался только для ручных источников, типовые ответы автоматизированы. Спорные случаи по‑прежнему разбирает человек. Состояние интеграции видно в dashboard, ошибки приезжают в Telegram.
Сколько это стоит
Регулярных расходов две штуки. Сервис управления репутацией ~8К/мес. за четыре точки и Cloudflare Workers Paid — $5/мес.
Итого, грубо, 8,5К в месяц. В эту сумму входят сбор отзывов с карт, собственное хранилище, API и webhook CRM, четыре XML‑фида, ручные источники, мониторинг, автоответы и аналитика.
Разработку сюда не считаю: отдельно были ChatGPT Plus и моя работа. Меня интересовала именно цена дальнейшей эксплуатации.
Экономия тут не только в подписке. Система убирает ежедневный обход источников, снижает риск пропусков и сохраняет массив, который потом можно использовать для продуктовой и операционной аналитики.
Кстати, на разработку от идеи до стабильного релиза я потратила 2 недели, больше всего времени съело взаимодействие с поддержкой сервисов.
Что оказалось важнее кода
Сначала процесс, потом автоматизация
Можно взять n8n, Make, Python или Workers. Если не развести источники, роли и правила данных, любой инструмент просто ускорит хаос. Нормальная архитектура появилась только после того, как было сформулировано, что именно должен перестать делать менеджер.
Готовый сервис и свой слой отлично уживаются
Полностью самописная система дала бы больше контроля и сильно больше поддержки. Один сервис управления репутацией не закрыл бы CRM, ручные источники и диагностику. Гибридный вариант оказался дешевле и живучее обоих крайних сценариев.
Ошибка должна орать, а не прятаться
Отзыв без филиала, зависший sync_run, просроченный cron и системный outcome Cloudflare должны появляться там, где их заметит человек. Тихий fallback кажется удобным ровно до первой испорченной аналитики.
Документация — это часть системы
Внутренняя документация хранит архитектуру, код, D1 schema, миграции, регламенты восстановления и раздел «Техническая поддержка и SLA проекта». Там написано, что проверять регулярно, как разбирать инцидент, что приложить к диагностике и в какой момент идти в сервис управления репутацией, CRM или Cloudflare.
AI‑assisted development требует дисциплины
AI ускоряет исследование и код, но не отвечает за production‑данные. Чем больше проект, тем важнее версии, бэкапы, маленькие изменения, контроль контекста и привычка перепроверять результат несколькими способами.
Что в итоге
Все началось с вопроса «как перестать вручную вести таблицу отзывов». Довольно быстро выяснилось, что таблица была только верхним слоем. Под ней жили источники, история, дедупликация, филиалы, ответственность менеджера, диагностика и ограничения внешних сервисов.
В результате получилась нормальная рабочая система малого бизнеса. Не корпоративный монстр и не демо «нейросеть написала код за вечер». У нее есть ограничения, аварийные сценарии, документация, понятная стоимость и вполне измеримая польза в ежедневной работе.
Для меня в этом и состоит смысл AI‑assisted development. Человек без классического технического бэкграунда может собрать сложный прикладной контур, если понимает процесс, умеет проверять данные и не принимает сгенерированный код за готовое решение.