Как стать автором
Обновить
Dodo Engineering
О том, как разработчики строят IT в Dodo

Как я создал сервис по контролю качества из таблиц и палок

Время на прочтение 9 мин
Количество просмотров 6.5K
Привет, Хабр! Частенько при размышлении о запуске пилота менеджеры начинают усложнять ситуацию, строят дорожные карты и ждут MVP от разработчиков, вместо того чтобы взять и протестировать идею своими силами. Под катом я хочу поделиться историей создания сервиса по контролю качества на основе Google-форм, ВК и десятков строчек кода, при которой ни один разработчик не пострадал, только 1 маркетолог.



Disclaimer: в этой статье вас жду примеры кода, который вызовет у вас вопросы, сомнения, возможно, даже кровавые слёзы и слепоту. В его оправдание можно сказать лишь одно – этот код работал, как папа Карло, и выполнял своё предназначение на 100%, а некоторые скрипты до сих пор работают.

Первый рейтинг пиццерий в Додо


Один из главных принципов нашей компании — то, что нельзя измерить, не существует. Пицца существует, а значит нужно как-то оценивать её качество.

Всё началось в 2013 году. На тот момент в сети было 7 пиццерий. Тогда в Додо решили запустить проект для контроля качества продукта и стандартов – открытый для всех партнёров рейтинг пиццерий.

Он выглядел так.



Данные брали из обзвона клиентов, отзывов в соц. сетях и немного из Dodo IS (наша информационная система).

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

Второй рейтинг пиццерий с тайными покупателями


Примерно через год мы поняли, что нужно оценивать сам продукт и взяли известную модель – «тайные покупатели». В тот момент мы совершили прорыв – первыми в России решили, что тайными покупателями будут наши же клиенты.

Появление закрытой группы ВК


Изначально мы выяснили, как с тайными покупателями работают в агентствах которые оказывали такие услуги. Оказалось, что они общаются по электронной почте или звонят напрямую, чтобы предложить проверку.

Такой вариант нас сразу не прельстил. Звонить мы точно не собирались, потому что для нас это долго, а для клиента – больно и наравне со спамом. Почту использовали для массовых рассылок, но вскоре отказались и от них.

Мы придумали:

  1. Для общения с тайными агентами и координации проверок использовали обычную страницу во ВК.
  2. Для сбора фото-отчетов сделали группу во ВК с открытой стеной.
  3. А для опросов по обслуживанию мы выбрали Google-формы. А что? Удобно же и отдельный сервис пилить не нужно и на мобилке хорошо отображается.
  4. Все инструкции вели в Google Docs.

Всё на коленке. Всё как полагается стартапу.


Фотография от тайного покупателя. Да, они измеряли линейкой ширину, резали бортик пиццы чтобы показать пропеченность и качество теста.

Ещё больше примеров из фото-отчетов из 2015 года здесь.





Рейтинг постоянно видоизменялся, добавлялись новые критерии оценки. Для примера – это 131 пункт оценки пиццерии Абакан-1.



Весь процессуальный ад заключался в том, что все нарушения в пунктах отмечались вручную, никакой автоматизации. И всё это делал один сотрудник.

Магия Google-таблиц


В 2016 мы узнали (созрели), что человечество давно изобрело функции кроме =SUM. А уж что можно творить с помощью скриптов и функций импорта данных между таблицами… Прям CRM построить.

Например, для оценки каждой пиццерии в таблице был лист, а в нём критерия оценки и баллы. 1 пиццерия = 1 лист. Чем больше пиццерий, тем больше листов. Каждую неделю новая табличка. По нажатию волшебной кнопки шаблонный лист в таблице размножался на 120 листов. Вах, как хорошо!



Этот этап автоматизации «Додо Контроллинг» занял порядка полугода. Мы двигались шаг за шагом: узнавали что-то новое – внедряли, узнавали еще что-то новое – снова внедряли.

Ограничение, как драйвер прогресса


С самого начала мы общались с тайными покупателями от лица персонального аккаунта во ВК. И вот настал тот день, когда мы столкнулись с не решаемыми проблемами личных страниц:

  • есть лимит в 10 000 друзей;
  • есть лимит на отправку сообщений: иногда мы не могли отправлять сообщения, потому что ВК блочил нас с формулировкой «вы отправили слишком много сообщений, приходите завтра».

Обычно ВК блочил отправку сообщений в районе 16:00. Это были счастливые часы, когда команда могла отдохнуть.

Постепенно мы упёрлись почти во все ограничения ВК и почти успели разлюбить эту площадку. Но тут произошло чудо, и ВК дал группам возможность подключить виджеты сообщений, чат-ботов и прочие плюшки.

На самом деле такая возможность была и раньше декабря 2016, но с критичным для нас ограничением: группа могла ответить на сообщение только в течение 10 дней с момента отправки последнего сообщения от клиента. Когда они убрали это ограничение, жизнь нашей команды заиграла новыми красками. С тех пор мы начали путь автоматизации с применением API ВК и Google-таблиц.

Страх и ненависть в юзер-сценарии


Изначально чтобы стать тайным покупателем нужно было заполнить форму, потом подать заявку в группу, написать нам кодовое слово и ждать, пока мы ответим. Это ожидание могло длиться до 48 часов. Это было неприлично долго и желание проводить проверки после такой регистрации угасало.

С новым багажом знаний мы приступили к автоматизации регистрации.

У нас было несколько методов API, одна форма, несколько десятков таблиц, лендинг, бот на PHP, скрипты в таблице, которые были написаны самым крутым маркетологом Додо и первым финансовым директором Додо в одном лице, десятки шаблонов сообщений для разных ситуаций. Тогда я даже не догадывался, что это называется юзер-сценарием:

  1. Тайный покупатель заполнял анкету (это устаревшая версия и сейчас она не работает).
  2. Данные с формы попадали сначала в БД, далее уже в таблицу. Бот первым делом стучался в БД для проверки данных об анкете, потому что таймаут ответа таблички в десятки тысяч строк был большим, а БД переваривала это легко).
  3. После заполнения анкеты в форме на экране появлялось сообщение: «Спасибо, осталось вступить в группу (ссылка) и написать кодовое слово «Шерлок»».
  4. Далее тайный покупатель отправлял заявку на вступление в группу и писал кодовое слово «Шерлок». Слово было триггером для запуска скрипта проверки:
    – в БД отправлялся запрос, подходит ли нам агент по возрасту;
    – если да, то человека добавляли в группы, а в таблице напротив анкеты кандидата проставлялся статус «ок/не ок»;
    – ещё данные красились в бирюзовый цвет и это было важно. Так глазами было проще понять, кто нам подходит и с кем можно говорить. Не подходящие анкеты скрипт красил в красный цвет;
    – далее тайного покупателя автоматом принимали в группе, а в чат отправлялось сообщение с дальнейшими шагами и инструкциями.



Такая вот автоматизация регистрации. Теперь вся процедура стала почти простой и понятной.

Далее всё пошло как по маслу. Стало понятно, что мы можем отправлять сообщения через таблицы напрямую в личку тайным агентам через сообщения группы.



И надо-то было всего 46 строчек кода!

function send() {
  var range = SpreadsheetApp.getActiveSpreadsheet().getActiveRange();
  var ss = range.getValues();
  var carray = range.offset(0, -2).getValues();
  var marray = range.offset(0, 1).getValues();
  var iarray = range.offset(0, 0).getValues();
  ss.forEach(function (r, i) {
  var tt = range.getRowIndex();
  var add = tt + i;
    var check = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet().getRange('L' + add).getValue();
    if (check != '') {
      SpreadsheetApp.getActiveSpreadsheet().getActiveSheet().getRange('K' + add).setValue("Повтор").setBackground("#ffff00");
      return;
    }
   var payload = 
   {
     "message" : marray[i][0],
     "user_id" : r[0],
     "access_token" : "тут токен должен быть ваш",
     "v" : "5.74"
   };

   var options = 
   { // опции для http-запроса
     "method" : "post",
     "header" : "Content-type: application/x-www-form-urlencoded",
     "payload" : payload,
     "muteHttpExceptions" : true,
   };
  

  var jsonData = JSON.parse(UrlFetchApp.fetch("https://api.vk.com/method/messages.send", options).getContentText());
    if (jsonData['error'] != undefined) {

      SpreadsheetApp.getActiveSpreadsheet().getActiveSheet().getRange('K' + add).setValue("Ошибочка").setBackground("#ff0000");
    }
  Logger.log(jsonData);
    var log = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Log');
    var int = log.getRange('A1').getValue();
    var d = new Date();
    log.getRange('B' + int).setValue(d);
    log.getRange('C' + int).setValue(r[0] + "");
    log.getRange('A1').setValue(int + 1);
  Utilities.sleep(400);
  });
 }

В таблицах мы генерировали сам текст сообщений и по id-страницы делали персонализированную рассылку. Также мы перестали копипастить и отправлять сообщения вручную. В пиковые моменты мы могли отправлять до 3000 сообщений за 45 минут при помощи таблиц. Если бы мы делали это руками, то я бы не писал эту статью, а продолжал бы отправлять сообщения.

Далее я привожу пример такой чудо-таблички. Очень старался сделать понятный и функциональный UX/UI. По нажатию на красную кнопку запускалась рассылка сообщений с предложением провести тайную проверку.



Ещё больше фич для автоматизации адовых процессов


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

Немного устав от такой работы поняли, что можно это дело автоматизировать. Я снова обратился к нашему маркетологу, через часика два уже всё было готово и всего-то 47 строчек кода.

function myFunction() {
 var ss = SpreadsheetApp.getActiveSpreadsheet();
 var range = ss.getActiveRange();
 var forms = range.offset(0, 1).getValues();
 var items = range.offset(0, 2).getValues();
 var sources = range.offset(0, 3).getValues();
 var ranges = range.offset(0, 4).getValues();
  forms.forEach(function (r,i) {
  var form = FormApp.openById(forms[i]);
  var values = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(sources[i]).getRange(ranges[i]).getValues();
  var arr = [];
  values.forEach(function (el,ei) {
    if (el[0] != '') {
      arr.push(el[0]);
    }
  });
  var item = form.getItems()[Number(items[i])].asListItem();
  item.setChoiceValues(arr);
  Logger.log(item.getTitle());
  });
 
}

function getData() {
  var ss = SpreadsheetApp.getActiveSpreadsheet();
  var range = ss.getSheetByName("Для форм РФ и ЕС").getRange("A2:C").getValues();
  Logger.log(range);
}

function onOpen() {
  var ui = SpreadsheetApp.getUi();
  // Or DocumentApp or FormApp.
  ui.createMenu('Custom Menu')
      .addItem('Change', 'myFunction')
      .addToUi();
}

function update() {
  var ss = SpreadsheetApp.getActiveSpreadsheet();
  var range = ss.getSheetByName("Автообновление форм").getRange("A2:A");
  range.activate();
  myFunction();
}

function createTimeDrivenTriggers() {
  ScriptApp.newTrigger('update').timeBased().everyDays(1).create();
}

Формы разные, с разными списками пиццерий и расположением списка пиццерий в форме. Где-то нужны зарубежные, где-то из России.



Столбцы B и С указывают скрипту, в какой форме нужно обновить и в каком месте формы отобразить список. А столбцы D и E, с какого листа и диапазона нужно брать список для обновления.

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

Самый сложный кейс про улитку


Однажды мы захотели получать аудиозаписи с проверок доставки.

Откуда брались аудиозаписи: тайные покупатели записывали встречу курьера с клиентом, как аудиосообщение во ВК. Обычно это 10-15 секунд аудиозаписи.

Как мы их доставали: по айдишнику Вк тайного покупателя брали последние 20 сообщений, среди них искали слово «Улика», далее отступали на одно сообщение назад и забирали ссылку на аудиозапись. (Спойлер: не самое удачное слово, часто нам писали слово «Улитка»).

Тут уже по больше строчек кода, готовьтесь.

function getLastAudio() {
  var ss = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
  var token = '';
  var range = ss.getActiveRange().getValues();
  var tt = ss.getActiveRange().getRowIndex();
  range.forEach(function (r,i) {
    var ri = tt + i;
   var cuid = r[0];
    var ci = 0;
  var payload1 = 
   {
     "q" : 'улика',
     'peer_id' : r[0],
     "access_token" : token,
     "v" : "5.73",
   };

   var options1 = 
   { // опции для http-запроса
     "method" : "post",
     "header" : "Content-type: application/x-www-form-urlencoded",
     "payload" : payload1,
     "muteHttpExceptions" : true
   };
  
  var jsonData = JSON.parse(UrlFetchApp.fetch("https://api.vk.com/method/messages.search", options1).getContentText());
    Logger.log(jsonData);
    if (jsonData['response']['items'] == undefined) {
      ss.getRange("B" + ri).setValue('Сообщений нет').setBackground('#f00');
      return;
    }
    var cmid = jsonData['response']['items'][0]['id'];
    var date = new Date((jsonData['response']['items'][0]['date']*1000));
    var fdate = Utilities.formatDate(date, "GMT+3", "dd-MM-yyyy HH:mm:ss");
    ss.getRange("B" + ri).setValue(fdate);
          var payload2 = 
   {
     "user_id" : cuid,
     "count" : '20',
     "access_token" : token,
     "v" : "5.73",
   };

   var options2 = 
   { // опции для http-запроса
     "method" : "post",
     "header" : "Content-type: application/x-www-form-urlencoded",
     "payload" : payload2,
     "muteHttpExceptions" : true
   };
  
  var jsonData2 = JSON.parse(UrlFetchApp.fetch("https://api.vk.com/method/messages.getHistory", options2).getContentText());
    Logger.log(jsonData2);
      jsonData2['response']['items'].forEach(function (r,i) {
        if (r['id'] == cmid) {
          ci = i + 1;
          Logger.log(jsonData2['response']['items'][ci]);
          if (jsonData2['response']['items'][ci] == undefined) {
            ss.getRange("C" + ri).setValue("Ошибка поиска");
          }
          else {
            if (jsonData2['response']['items'][ci] != undefined && jsonData2['response']['items'][ci]['attachments'] != undefined && jsonData2['response']['items'][ci]['attachments'][0]['doc'] != undefined && jsonData2['response']['items'][ci]['attachments'][0]['doc']['url'] != undefined) {
              ss.getRange("C" + ri).setValue(jsonData2['response']['items'][ci]['attachments'][0]['doc']['url']);
            }
            else {
              ss.getRange("C" + ri).setValue(jsonData2['response']['items'][ci]['body']);
            }
          }
        }
      });
  });
}

Google-таблицы не резиновые, а я – так себе кодер


Так всё крутилось-вертелось, пока мы не разрослись до 60 тысяч тайных покупателей к началу 2018 года.

Так вот, мы уже уперлись в ограничения таблиц. Тогда одна таблица могла содержать не более 2 млн ячеек, а мы использовали 1,6 млн ячеек в одной из самых загруженных табличек отдела, из них в 135 тысячах ячеек были всякие функции таблиц.

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

Тут стало понятно, что я плохой кодер, система не держит нагрузку и надо меня заменить.

И тут пришёл настоящий разработчик…


Но это уже совсем другая история и о ней напишем позже. Надеюсь, что эти примеры помогут менеджерам в своих проектах, где много адовых процессов. Процессы можно и нужно автоматизировать, в этом могут помочь как таблицы, так и zapier (его мы тоже использовали), так и инхаусные решения если есть ресурсы, у нас их не было в начале.

Если у вас в компании есть человек, который знает что-то о скриптах, то он вам подходит для организации адовых процессов в табличках. Если вы сами хотите что-то подобное изучить, в Telegram есть отличный канал для этого, из которого я брал инфу о функциях и скриптах.

Ну и мастхев для всех менеджеров – знание таблиц каждый день экономит время при работе с данными и позволяет мыслить и понимать хотя бы немного, что там делает разработчик с данными и что с данными вообще можно делать.
Теги:
Хабы:
+30
Комментарии 2
Комментарии Комментарии 2

Публикации

Информация

Сайт
dodo.dev
Дата регистрации
Дата основания
Численность
201–500 человек
Местоположение
Россия