Русский
Русский
English
Статистика
Реклама

Доставка

Скрываем номера курьеров и клиентов с помощью key-value хранилища

17.06.2021 18:06:38 | Автор: admin

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

Каждый сервис использует свои решения для маскировки номеров клиентов и курьеров. В данной статье я расскажу, как сделать это с помощью key-value хранилища в Voximplant.

Как это будет работать

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

У нас будет только один нейтральный номер, на который будут звонить и клиент, и курьер. Номер мы арендуем в панели Voximplant. Затем создадим некую структуру данных, где клиент и курьер будут связаны между собой номером заказа (то есть ключом в терминологии key-value storage).

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

Например, звонок курьера клиенту будет выглядеть следующим образом:

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

Перейдем непосредственно к реализации.

Вам понадобятся

1) Чтобы начать разработку, войдите в свой аккаунт: manage.voximplant.com/auth. В меню слева нажмите Приложения, затем Создать приложение в правом верхнем углу. Дайте ему имя, например, numberMasking и снова кликните Создать.

2) Зайдите в новое приложение, переключитесь на вкладку Сценарии и создайте сценарий, нажав на +. Назовём его kvs-scenario. Здесь мы будем писать код, но об этом чуть позже.

3) Сначала перейдем во вкладку Роутинг и создадим правило для нашего сценария. Маску (регулярное выражение) оставим .* по умолчанию, так правило будет срабатывать для всех номеров.

4) Далее арендуем реальный городской номер. Для этого перейдем в раздел Номера, выберем и оплатим номер. На него будут звонить и клиент, и курьер, и он будет отображаться вместо их настоящих номеров.

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

5) Осталось привязать его к нашему приложению. Заходим в приложение, открываем вкладку Номера Доступные и нажимаем Прикрепить. В открывшемся окне можно также прикрепить наше правило, тогда оно будет автоматически назначено для входящих вызовов, а все остальные правила будут проигнорированы.

6) Далее необходимо верифицировать аккаунт, чтобы использовать этот номер для звонков.

Отлично, структура готова, осталось заполнить key-value хранилище и добавить код в сценарий.

Key-value хранилище

Чтобы сценарий заработал, нужно положить что-то в хранилище. Это можно сделать, воспользовавшись Voximplant Management API. Я буду использовать Python API client, он работает с Python 2.x или 3.x с установленным pip и setuptools> = 18.5.

1) Зайдем в папку проекта и установим SDK, используя pip:

python -m pip install --user voximplant-apiclient

2) Создадим файл с расширением .py и добавим в него код, при выполнении которого данные о заказе попадут в key-value хранилище. Применим метод set_key_value_item:

from voximplant.apiclient import VoximplantAPI, VoximplantExceptionif __name__ == "__main__":    voxapi = VoximplantAPI("credentials.json")        # SetKeyValueItem example.    KEY = 12345    VALUE = '{"courier": "79991111111", "client": "79992222222"}'    APPLICATION_ID = 1    TTL = 864000        try:        res = voxapi.set_key_value_item(KEY,            VALUE,            APPLICATION_ID,            ttl=TTL)        print(res)    except VoximplantException as e:        print("Error: {}".format(e.message))

Файл с необходимыми credentials вы сможете сгенерировать при создании сервисного аккаунта в разделе Служебные аккаунты в настройках панели.

APPLICATION_ID появится в адресной строке при переходе в ваше приложение.

В качестве ключа (KEY) будет использоваться пятизначный номер заказа, а в качестве значений телефонные номера: courier номер курьера, client номер клиента. TTL нам здесь необходимо для указания срока хранения значений.

3) Осталось запустить файл, чтобы сохранить данные заказа:

python3 kvs.py

Если мы больше не захотим, чтобы клиент и курьер беспокоили друг друга, можно будет удалить их данные из хранилища. Информацию о всех доступных методах key-value storage вы найдёте в нашей документации: management API и VoxEngine.

Код сценария

Код, который необходимо вставить в сценарий kvs-scenario, представлен ниже, его можно смело копировать as is:

Полный код сценария
require(Modules.ApplicationStorage);/** * @param {boolean} repeatAskForInput - была ли просьба ввода произнесена повторно * @param longInputTimerId - таймер на отсутствие ввода * @param shortInputTimerId - таймер на срабатывание фразы для связи с оператором * @param {boolean} firstTimeout - индикатор срабатывания первого таймаута * @param {boolean} wrongPhone - индикатор совпадения номера звонящего с номером, полученным из хранилища * @param {boolean} inputRecieved - получен ли ввод от пользователя *  */let repeatAskForInput;let longInputTimerId;let shortInputTimerId;let firstTimeout = true;let wrongPhone;let inputRecieved;const store = {    call: null,    caller: '',    callee: '',    callid: '74990000000',    operator_call: null,    operatorNumber: '',    input: '',    data: {        call_operator: '',        order_number: '',        order_search: '',        phone_search: '',        sub_status: '',        sub_available: '',        need_operator: '',        call_record: ''    }}const phrases = {    start: 'Здр+авствуйтте. Пожалуйста, -- введите пятизначный номер заказa в тт+ооновом режиме.',    repeat: 'Пожалуйста , , - - введите пятизначный номер заказа в т+оновом режиме,, или нажмите решетку для соединения со специалистом',    noInputGoodbye: 'Вы - ничего не выбрали. Вы можете посмотреть номер заказа в смс-сообщении и позвонить нам снова. Всего д+обровоо до свидания.',    connectToOpearator: 'Для соединения со специалистом,, нажмите решетку',    connectingToOpearator: 'Ожидайте, соединяю со специалистом',    operatorUnavailable: 'К сожалению,, все операторы заняты. Пожалуйста,,, перезвоните позднее. Всего д+обровоо до свидания.',    wrongOrder: 'Номер заказа не найден. Посмотрите номер заказа в смс-сообщении и введите его в т+оновом режиме. Или свяжитесь со специалистом,, нажав клавишу решетка.',    wrongOrderGoodbye: 'Вы ничего не выбрали, всего д+обровоо до свидания.',    wrongPhone: 'Номер телефона не найден. Если вы кли+ент, перезвоните с номера, который использовали для оформления заказа. Если вы курьер, перезвоните с номера, который зарегистрирован в нашей системе. Или свяжитесь со специалистом,,- нажав клавишу решетка.',    wrongPhoneGoodbye: 'Вы ничего не выбрали. Всего доброго, до свидания!',    courierIsCalling: `Вам звонит курьер по поводу доставки вашего заказа, - - ${store.data.order_number}`,    clientIsCalling: `Вам звонит клиент по поводу доставки заказа, - - ${store.data.order_number} `,    courierUnavailable: 'Похоже,,, курь+ер недоступен. Пожалуйста,,, перезвоните через п+ару мин+ут. Всего д+обровоо до свидания.',    clientUnavailable: 'Похоже,,, абонент недоступен. Пожалуйста,,, перезвоните через пп+ару мин+ут. Всего д+обровоо до свидания.',    waitForCourier: 'Ожидайте на линии,, - соединяю с курьером.',    waitForClient: 'Ожидайте на линии,, соединяю с клиентом.'}VoxEngine.addEventListener(AppEvents.Started, async e => {    VoxEngine.addEventListener(AppEvents.CallAlerting, callAlertingHandler);})async function callAlertingHandler(e) {    store.call = e.call;    store.caller = e.callerid;    store.call.addEventListener(CallEvents.Connected, callConnectedHandler);    store.call.addEventListener(CallEvents.Disconnected, callDisconnectedHandler);    store.call.answer();}async function callDisconnectedHandler(e) {    await sendResultToDb();    VoxEngine.terminate();}async function callConnectedHandler() {    store.call.handleTones(true);    store.call.addEventListener(CallEvents.RecordStarted, (e) => {        store.data.call_record = e.url;    });    store.call.record();    store.call.addEventListener(CallEvents.ToneReceived, dtmfHandler);    await say(phrases.start);    addInputTimeouts();}function dtmfHandler(e) {    clearInputTimeouts();    store.input += e.tone;    Logger.write('Введена цифра ' + e.tone)    Logger.write('Полный код ' + store.input)    if (e.tone === '#') {        store.data.need_operator = "Да";        store.call.removeEventListener(CallEvents.ToneReceived);        store.call.handleTones(false);        callOperator();        return;    }    if (!wrongPhone) {        if (store.input.length >= 5) {            repeatAskForInput = true;            Logger.write(`Получен код ${store.input}. `);            store.call.handleTones(false);            store.call.removeEventListener(CallEvents.ToneReceived);            handleInput(store.input);            return;        }    }    addInputTimeouts();}function addInputTimeouts() {    clearInputTimeouts();    if (firstTimeout) {        Logger.write('Запущен таймер на срабатывание фразы для связи с оператором');        shortInputTimerId = setTimeout(async () => {            await say(phrases.connectToOpearator);        }, 1500);        firstTimeout = false;    }    longInputTimerId = setTimeout(async () => {        Logger.write('Сработал таймер на отсутствие ввода от пользователя ' + longInputTimerId);        store.call.removeEventListener(CallEvents.ToneReceived);        store.call.handleTones(false);        if (store.input) {            handleInput(store.input);            return;        }        if (!repeatAskForInput) {            Logger.write('Просим пользователя повторно ввести код');            store.call.handleTones(true);            store.call.addEventListener(CallEvents.ToneReceived, dtmfHandler);            await say(phrases.repeat);            addInputTimeouts();            repeatAskForInput = true;        } else {            Logger.write('Код не введен. Завершаем звонок.');            await say(inputRecieved ? phrases.wrongOrderGoodbye : phrases.noInputGoodbye);            store.call.hangup();        }    }, 8000);    Logger.write('Запущен таймер на отсутствие ввода от пользователя ' + longInputTimerId);}function clearInputTimeouts() {    Logger.write(`Очищаем таймер ${longInputTimerId}. `);    if (longInputTimerId) clearTimeout(longInputTimerId);    if (shortInputTimerId) clearTimeout(shortInputTimerId);}async function handleInput() {    store.data.order_number = store.input;    Logger.write('Ищем совпадение в kvs по введенному коду: ' + store.input)    inputRecieved = true;    let kvsAnswer = await ApplicationStorage.get(store.input);    if (kvsAnswer) {        store.data.order_search = 'Заказ найден';        Logger.write('Получили ответ от kvs: ' + kvsAnswer.value)        let { courier, client } = JSON.parse(kvsAnswer.value);        if (store.caller == courier) {            Logger.write('Звонит курьер')            store.callee = client;            store.data.sub_status = 'Курьер';            store.data.phone_search = 'Телефон найден';            callCourierOrClient();        } else if (store.caller == client) {            Logger.write('Звонит клиент')            store.callee = courier;            store.data.sub_status = 'Клиент';            store.data.phone_search = 'Телефон найден';            callCourierOrClient();        } else {            Logger.write('Номер звонящего не совпадает с номерами, полученными из kvs');            wrongPhone = true;            store.data.phone_search = 'Телефон не найден';            store.input = '';            store.call.handleTones(true);            store.call.addEventListener(CallEvents.ToneReceived, dtmfHandler);            await say(phrases.wrongPhone);            addInputTimeouts();        }    } else {        Logger.write('Совпадение в kvs по введенному коду не найдено');        store.data.order_search = 'Заказ не найден';        store.input = '';        store.call.handleTones(true);        store.call.addEventListener(CallEvents.ToneReceived, dtmfHandler);        await say(phrases.wrongOrder);        Logger.write(`Очищаем таймер ${longInputTimerId}. `);        addInputTimeouts();    }}async function callCourierOrClient() {    clearInputTimeouts();    Logger.write('Начинаем звонок курьеру/клиенту');    await say(store.data.sub_status === 'Курьер' ? phrases.waitForClient : phrases.waitForCourier, store.call);    const secondCall = VoxEngine.callPSTN(store.callee, store.callid);    store.call.startPlayback('http://cdn.voximplant.com/toto.mp3');    secondCall.addEventListener(CallEvents.Connected, async () => {        store.data.sub_available = 'Да';        await say(store.data.sub_status === 'Курьер' ? phrases.courierIsCalling : phrases.clientIsCalling, secondCall);        store.call.stopPlayback();        VoxEngine.sendMediaBetween(store.call, secondCall);    });    secondCall.addEventListener(CallEvents.Disconnected, () => {        store.call.hangup();    });    secondCall.addEventListener(CallEvents.Failed, async () => {        store.data.sub_available = 'Нет';        store.call.stopPlayback();        await say(store.data.sub_status === 'Курьер' ? phrases.clientUnavailable : phrases.courierUnavailable, store.call);        store.call.hangup();    });}async function callOperator() {    Logger.write('Начинаем звонок оператору');    await say(phrases.connectingToOpearator, store.call);    store.call.startPlayback('http://cdn.voximplant.com/toto.mp3');    store.operator_call = VoxEngine.callPSTN(store.operatorNumber, store.callid);    store.operator_call.addEventListener(CallEvents.Connected, async () => {        store.data.call_operator = 'Оператор свободен';        VoxEngine.sendMediaBetween(store.call, store.operator_call);    });    store.operator_call.addEventListener(CallEvents.Disconnected, () => {        store.call.hangup();    });    store.operator_call.addEventListener(CallEvents.Failed, async () => {        store.data.call_operator = 'Оператор занят';        await say(phrases.operatorUnavailable, store.call);        store.call.hangup();    });}async function sendResultToDb() {    Logger.write('Данные для отправки в БД');    Logger.write(JSON.stringify(store.data));    const options = new Net.HttpRequestOptions();    options.headers = ['Content-Type: application/json'];    options.method = 'POST';    options.postData = JSON.stringify(store.data);    await Net.httpRequestAsync('https://voximplant.com/', options);}function say(text, call = store.call, lang = VoiceList.Yandex.Neural.ru_RU_alena) {    return new Promise((resolve) => {        call.say(text, lang);        call.addEventListener(CallEvents.PlaybackFinished, function callback(e) {            resolve(call.removeEventListener(CallEvents.PlaybackFinished, callback));        });    });};

Код тщательно прокомментирован, но в некоторые моменты углубимся подробнее.

Вводим номер заказа

Первое, что мы делаем при звонке просим звонящего ввести номер заказа и обрабатываем введенное значение с помощью функции dtmfHandler.

store.input += e.tone;

Если звонящий ввел #, сразу соединяем его с оператором:

if (e.tone === '#') {    store.data.need_operator = "Да";    store.call.removeEventListener(CallEvents.ToneReceived);    store.call.handleTones(false);    callOperator();    return;}

Если он ввел последовательность из 5 цифр, вызываем функцию handleInput:

if (store.input.length >= 5) {    repeatAskForInput = true;    Logger.write('Получен код ${store.input}. ');    store.call.handleTones(false);    store.call.removeEventListener(CallEvents.ToneReceived);    handleInput(store.input);    return;}

Ищем заказ в хранилище

Здесь мы будем сравнивать введенный номер заказа с номером в хранилище, используя метод ApplicationStorage.get(), в качестве ключа используем введенную последовательность:

store.data.order_number = store.input;Logger.write('Ищем совпадение в kvs по введенному коду: ' + store.input)inputRecieved = true;let kvsAnswer = await ApplicationStorage.get(store.input);

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

if (kvsAnswer) {    store.data.order_search = 'Заказ найден';    Logger.write('Получили ответ от kvs: ' + kvsAnswer.value)    let { courier, client } = JSON.parse(kvsAnswer.value);

Теперь осталось разобраться, кому звонить. Если номер звонящего принадлежит курьеру, будем выполнять переадресацию на клиента, если клиенту на курьера. В этом нам поможет функция callCourierOrClient:

if (store.caller == courier) {    Logger.write('Звонит курьер')    store.callee = client;    store.data.sub_status = 'Курьер';    store.data.phone_search = 'Телефон найден';    callCourierOrClient();} else if (store.caller == client) {    Logger.write('Звонит клиент')    store.callee = courier;    store.data.sub_status = 'Клиент';    store.data.phone_search = 'Телефон найден';    callCourierOrClient();}

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

else {    Logger.write('Номер звонящего не совпадает с номерами, полученными из kvs');    wrongPhone = true;    store.data.phone_search = 'Телефон не найден';    store.input = '';    store.call.handleTones(true);    store.call.addEventListener(CallEvents.ToneReceived, dtmfHandler);    await say(phrases.wrongPhone);    addInputTimeouts();}

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

else {    Logger.write('Совпадение в kvs по введенному коду не найдено');    store.data.order_search = 'Заказ не найден';    store.input = '';    store.call.handleTones(true);    store.call.addEventListener(CallEvents.ToneReceived, dtmfHandler);    await say(phrases.wrongOrder);    Logger.write(`Очищаем таймер ${longInputTimerId}. `);    addInputTimeouts();}

Звоним клиенту/курьеру

Переходим непосредственно к звонку клиенту/курьеру, то есть к логике функции callCourierOrClient. Здесь мы сообщим звонящему, что переводим его звонок на курьера/клиента, и включим музыку на ожидание. С помощью метода callPSTN позвоним клиенту или курьеру (в зависимости от того, чей номер был ранее идентифицирован как номер звонящего):

await say(store.data.sub_status === 'Курьер' ? phrases.waitForClient : phrases.waitForCourier, store.call);const secondCall = VoxEngine.callPSTN(store.callee, store.callid);store.call.startPlayback('http://cdn.voximplant.com/toto.mp3');

В этот же момент сообщим второй стороне о том, что звонок касается уточнения информации по заказу:

secondCall.addEventListener(CallEvents.Connected, async () => {    store.data.sub_available = 'Да';    await say(store.data.sub_status === 'Курьер' ? phrases.courierIsCalling : phrases.clientIsCalling, secondCall);    store.call.stopPlayback();    VoxEngine.sendMediaBetween(store.call, secondCall);});

Обработаем событие дисконнекта:

secondCall.addEventListener(CallEvents.Disconnected, () => {    store.call.hangup();});

И оповестим звонящего, если вторая сторона недоступна:

secondCall.addEventListener(CallEvents.Failed, async () => {    store.data.sub_available = 'Нет';    store.call.stopPlayback();    await say(store.data.sub_status === 'Курьер' ? phrases.clientUnavailable : phrases.courierUnavailable, store.call);    store.call.hangup();});

За все фразы, который произносит робот, отвечает функция say, а сами фразы перечислены в ассоциативном массиве phrases. В качестве TTS провайдера мы используем Yandex, голос Alena:

function say(text, call = store.call, lang = VoiceList.Yandex.Neural.ru_RU_alena) {    return new Promise((resolve) => {        call.say(text, lang);        call.addEventListener(CallEvents.PlaybackFinished, function callback(e) {            resolve(call.removeEventListener(CallEvents.PlaybackFinished, callback));        });    });};

Кроме всего прочего, наш сценарий записывает звонки, используя метод record, и показывает, как можно сохранить статистику в базу данных (в нашем коде за это отвечает функция sendResultToDb). Это очень важно для бизнеса, поскольку позволяет анализировать статистику, обеспечивать контроль качества и оперативно решать спорные ситуации, которые могли возникнуть в процессе доставки заказа.

Тестируем

Когда полный код добавлен в сценарий, а данные по заказу в хранилище, смело начинайте тестировать.

Позвоним с телефона клиента или курьера на номер, арендованный в панели. Затем введем номер заказа (в нашем случае это 12345) и будем ждать соединения со второй стороной.

Если все сделано верно, клиент и курьер смогут созваниваться и обсуждать детали заказа, не зная настоящих номеров друг друга, а значит, не нарушая прайваси. Круто, не так ли?) Желаем вам успешной разработки и беспроблемной доставки!

P.S. Также мой коллега недавно рассказал, как обезопасить общение клиента и курьера с помощью Voximplant Kit (наш low-code/no-code продукт). Если эта тема вас заинтересовала, переходите по ссылке :)

Подробнее..

Автоматизация отправки товара в Китае

26.06.2020 14:09:33 | Автор: admin
Я придерживаюсь мысли, что если что-то может быть автоматизировано оно должно быть автоматизировано. В долгосрочной перспективе 9 из 10 действий, подвергшихся автоматизации всегда будут проще и выгоднее.
Ну и так получилось, что я когда-то познакомился с одним мужчиной, который занимается разведением и продажей устриц на юге Китая это крайне популярный бизнес. Подружились мы с ним настолько, что он позвал меня к себе в гости на устричную ферму(ну и похвастаться не без этого). Приехал я к нему и за день оставил без работы двух человек.

Так как в Китае на сегодняшний день существуют два типа бизнеса обанкротившиеся и онлайн, то продажа товара осуществляется через Taobao. То есть до моего приезда туда весь процесс выглядел так:
1) два человека приходят на работу в 4 часа утра, открывают личный кабинет продавца на Taobao и начинают по одной строчке копировать данные заказа в интерфейс отправки в службе доставки SF. Для этого им надо скопипастить ФИО получателя, адрес и телефон, прокликать все до конца и получить номер заказа.
image
2) в 6-00 к ним приезжает курьер с переносным Bluetooth-термопринтером и запасом ленты. Он категорически не приспособлен для такой работы на то он и переносной. Он отлично заходит, когда нужно забрать заказ у частного лица и распечатать 1-2 транспортные накладные, которые потом приклеят на посылку\конверт, но когда нужно распечатать накопившиеся за день 200-300 заказов это трагедия. Соответственно, на то, чтобы распечатать накладные на каждый заказ и забрать товар, уходит 2-3 часа.
image
3) за то, что курьер приезжает к ним в нерабочее время, они дополнительно платят SF немалые деньги. А принимать курьера в рабочее время они не могут SF обещает Next day delivery по Китаю только в случае, если курьер забрал посылку до 09-00. А устрицы такой товар, что его необходимо доставить за день.
Отправляют их(как и рыбу, мясо и вообще все скоропортящееся), кстати, если кому интересно в пенопластовом контейнере с кульком льда внутри. Даже на теперешней 35-градусной жаре к моменту приезда растаять лед не успевает.
4) после всего этого каждый номер посылки вручную забивается в Taobao, что меняет его статус на в доставке и покупателю можно отследить трекинг.
5) оплата за заказ производится отправителями с личных кошельков, после чего деньги возвращаются им владельцем по авансовым отчетам
Это капитальный треш, от которого встают волосы дыбом. Что было сделано?
1) Регистрируется аккаунт продавца в SF на платформа SF для продавцов. Постоплатная. Все, что нужно загрузить туда бизнес-лицензию, заполнить информацию о компании и адресе отправки и пройти телефонное интервью. После чего платформа раз в месяц формирует счет за услуги и отправляет его владельцу аккаунта
2) регистрируется аккаунт разработчика в SF. На котором подключается API создания заказа и API трекинга заказа. На той же платформе подключается уже готовое решение (помощник владельца магазина), у которого уже есть решения для всех популярных платформ(в том числе, естественно, для Taobao), в которое прописываются креды от аккаунта продавца
image
3) выбирается формат шаблона электронной транспортной накладной + покупается USB-принтер
4) номер аккаунта продавца меняется с песочницы () на реальный (
image
Заказчику предлагается сделать тестовую покупку на Taobao, через 30 секунд заказ уже висит в статусе ожидает отправки в SF, а принтер, урча, печатает накладную, которую нужно просто отлепить от подложки и наклеить на посылку.
Расходы на автоматизацию составили:
1) 600 юаней термопринтер
2) 300 юаней огромный ящик термоленты
3) 50 юаней в месяц за премиум-аккаунт во помощнике владельца магазина
Подробнее..

Как и зачем мы создаём собственную курьерскую платформу. Три истории Яндекс.Маркета

28.04.2021 14:06:12 | Автор: admin
Всем привет, меня зовут Алексей Остриков, я руковожу разработкой в Яндекс.Маркете. Когда-то я много-много писал код, затем полтора года руководил группой бэкенда одного из сервисов Маркета, а сейчас отвечаю за разработку курьерской платформы Маркета.

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


На фото команда курьерской платформы десять месяцев назад. В те времена она помещалась в одной комнате. Сейчас нас стало в 5 раз больше.

Зачем мы всё это делали


Разрабатывая курьерскую платформу, мы хотели прокачать три главные вещи.

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

Второе прозрачность. Когда что-то идёт не так (происходят переносы, срывы сроков), то мы не знаем, почему они произошли. Мы не можем пойти и подсказать: Ребят, давайте делать вот так. Мы и сами не видим, и клиенту не можем показать какие-то дополнительные вещи. Например, что заказ приедет не к восьми, а в интервале 15 минут. А всё потому, что в процессе нет такого уровня прозрачности.

Третье деньги. Когда мы работаем с подрядчиком, есть договор, в котором прописаны суммы. И мы можем менять эти циферки в рамках договора. А когда мы отвечаем за весь процесс от а до я, то можно видеть, какие части системы спроектированы экономически невыгодно. И можно, например, поменять провайдера SMS или формат документооборота. Или можно заметить, что у курьеров слишком большие пробеги. И если строить маршруты кучнее, то в итоге получится развозить больше заказов. Благодаря этому можно ещё и сэкономить деньги доставка станет более эффективной.

Это и были те три цели, которые мы ставили во главу всего.

Как выглядит платформа


Давайте посмотрим, что у нас получилось.



На изображении показана схема процесса. У нас есть большие склады, на которых хранятся сотни тысяч заказов. С каждого склада вечером уезжают фуры, доверху набитые заказами. Там может быть 56 тысяч заказов. Эти фуры едут в здания поменьше, которые называются сортировочные центры. В них за несколько часов большая куча заказов превращается в маленькие кучки для курьеров. И когда с утра приезжают курьеры на машинах, то каждый курьер знает, что ему нужно забрать кучку вот с этим QR-кодом, загрузить себе в машину и поехать развозить.

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


Как это видит курьер

У курьеров есть приложение для Android, написанное на React Native. И в этом приложении они видят весь свой день. Они чётко понимают последовательность: на какой адрес ехать сначала, на какой потом. Когда позвонить клиенту, когда отвезти возвраты в сортировочный центр, как начать день, как закончить. Они всё видят в приложении и практически не задаются лишними вопросами. Мы им очень помогаем. По сути, они просто выполняют задания.



Помимо этого, на платформе есть элементы управления. Это многофункциональная админка, которую мы переиспользовали из другого сервиса Яндекса. В этой админке можно конфигурировать состояние системы. Мы загружаем туда данные о новых курьерах, изменяем интервалы работы. Можем корректировать процесс создания заданий на завтра. Регулируется практически всё, что нужно.

Кстати, про бэкенд. Мы в Маркете очень любим Java, в основном версию 11. И все бэкенд-сервисы, про которые пойдёт речь, написаны на Java.

Архитектура




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

Второй узел это сервис, который коммуницирует с внутренними сервисами Яндекса. Все сервисы это классические RESTful-сервисы со стандартной коммуникацией. Когда вы сделаете заказ на Маркете, через какое-то время к вам прилетит документ в JSON-формате, где будет всё написано: когда доставляем, кому доставляем, в какой интервал. И у нас это состояние сохранится в базу данных. Всё просто.

Помимо этого, второй узел также коммуницирует с другими внутренними сервисами, уже не Маркета, а Яндекса. Например, за уточнением геокоординат мы уходим в геосервис. Чтобы отправить push-уведомление, идём в сервис, который рассылает push и SMS. Для авторизации используем другой сервис. Для расчёта маршрутизации на завтра ещё один сервис. Таким образом осуществляется вся коммуникация с внутренними службами.

Этот узел также является входной точкой, у него есть API, в которую стучится наша админка. У неё есть свой endpoint, который называется, скажем, /partner. И наша админка, всё состояние системы, конфигурируется через коммуникацию с этим сервисом.

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

И в центре всего находится база данных, в которой, собственно, и хранится всё состояние. Все сервисы входят в одну базу данных.



Отказоустойчивость


У Яндекса есть несколько дата-центров, и наш сервис регионально распределен по трём дата-центрам. Как это выглядит.

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

Один из узлов это наш API. Он крутится во всех трёх дата-центрах, потому что поток сообщений больше, чем из внутренних сервисов. Более того, такая схема позволяет вам довольно легко горизонтально масштабироваться.

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

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

Наша архитектура позволяет справляться даже с такими случаями, как отключение одного из дата-центров. Например, мы решили провести учения и отключили дата-центр во Владимире, и наш сервис остаётся на плаву, ничего не меняется. У нас исчезают хосты базы данных, которые лежат там, а сервис остаётся в работе.

Балансер понимает через какое-то время: ага, у меня не осталось ни одного живого хоста в этом дата-центре, и больше не перенаправляет туда трафик.

Все сервисы в Яндексе устроены схожим образом, мы все умеем переживать отказ одного из дата-центров. Как это реализовано, что такое graceful degradation и как сервисы в Яндексе переносят отключение одного из дата-центров, мы уже рассказывали.

Итак, это была архитектура. А теперь начинаются истории.

История первая про Яндекс.Ровер


Недавно у нас была Yet another Conference, там Роверу уделили много внимания. Я продолжу тему.

Как-то раз к нам пришли ребята из команды Яндекс.Ровера и предложили проверить гипотезу, что люди захотят получать заказы таким экстраординарным способом.

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

И мы подумали: а почему бы и нет? Уточнили детали эксперимента: на тот момент нужно было проверить гипотезу, что людям это понравится. И мы решили довезти 50 заказов за полторы недели в очень лайтовом режиме.



Мы придумали максимально простой флоу, когда человеку приходит SMS с предложением нестандартного способа доставки не курьер привезёт, а Ровер. Весь эксперимент проходил во внутреннем дворе Яндекса. Человек выбирал подъезд, к которому Ровер подъедет. Когда робот приезжал открывалась крышка, клиент брал заказ, закрывал крышку, и Ровер уезжал за новым заказом. Всё просто.

Затем мы пришли к команде Ровера, чтобы договориться про API.

В API Ровера есть простые методы: открыть крышку, закрыть крышку, поехать в такую-то точку, получить состояние. Классика. Тоже JSON. Очень просто.

Что ещё очень важно: и такие маленькие истории, и любые большие истории лучше делать через featureflags. Фактически у вас есть рубильник, по которому вы можете в production включить эту историю. Когда она вам больше не нужна, эксперимент завершен успешно или не успешно, либо заметили какие-то баги, вы просто вырубаете её. И вам не нужно делать ещё один деплой новой версии кода на прод. Эта штука здорово облегчает жизнь.

Казалось бы, всё просто и всё должно работать. Там даже две недели нечего разрабатывать, можно за несколько дней сделать. Но посмотрите, где собака зарыта.

Все процессы в основном синхронны. Человек нажимает кнопку, крышка открывается. Человек нажимает кнопку, крышка закрывается. Но один из этих процессов асинхронный. В момент, когда Ровер к вам поедет, нужно, чтобы был какой-то фоновый процесс, который будет отслеживать, что робот вернулся на точку.

И в этот момент мы отправим SMS человеку, например, что Ровер ждёт на месте. Это невозможно сделать синхронно, и нужно как-то решить эту проблему.

Есть много разных подходов. Мы сделали максимально простой вариант.

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



Выглядит это, например, так. Это практически копия кода с production, за исключением вырезанных комментариев. Но есть подвох. Нельзя делать таким образом серьёзные системы. Допустим, мы выкатываем новую версию на бэкенд. Хост перезагружается, состояние теряется, и всё, Ровер уезжает в бесконечность, его больше никто не видит.

Но почему? Если мы знаем, что наша цель доставить 50 заказов за полторы недели, мы сами выбираем время, когда следим за бэкендом. Если что-то пойдёт не так, можно вручную что-то изменить. Для такой задачи это решение более чем достаточное. И в этом кроется мораль первой истории.

Существуют ситуации, для которых нужно делать минимальную версию функционала. Не нужно огород городить и овер-инжинирить. Лучше делать максимально отчуждаемо. Так, чтобы не очень сильно изменять логику внутренних объектов. И избыточная сложность, ненужный технический долг не накапливались.

История вторая про базы данных


Но сначала несколько слов о том, как устроены основные сущности. Есть сервис Яндекс.Маршрутизация, который в конце дня строит маршруты курьерам.

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

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

Теперь давайте посмотрим, как эти сущности можно показать в базе данных. Это делается, например, вот так.



Это очень аккуратная модель данных. Мы не придумали ни одной новой сущности, и сильно не схлопывали уже существующие.

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

Единственное излишество, которое мы сделали в самом начале создания платформы, такое. У нас был один вид заданий на доставку. Но мы поняли, что в будущем появятся другие задания. И мы заложили небольшую архитектурную гибкость: у нас есть задания, и одно из видов заданий доставка заказа.

Затем добавили трекинг и Ровер. Всего лишь по две таблички. В трекинге курьер отправляет свои координаты, мы их фиксируем в отдельной табличке. И есть трекинг заказа со своей моделью состояний, есть дополнительные штуки, типа SMS ушла / не ушла. Не стоит это добавлять прямо в задание. Лучше вынести в отдельную табличку, ведь этот трекинг нужен не для всех типов заданий.



В Ровере его координаты и доставка. У нас доставка Ровером это трекинг как бы для Ровера. Можно его добавить в трекинг заказа, но зачем? Ведь когда мы избавимся от этого эксперимента, когда он будет выключен, эти опции навсегда останутся в сущности трекинга. Там будут null-поля.

Может возникнуть вопрос: а зачем делать табличку с координатами? Один Ровер доставляет пять заказов в день. Не нужно хранить координаты в базе данных, можно просто ходить в API Ровера и получать их на runtime.

Суть в том, что так и было сделано изначально. Этой таблички не было, мы сразу ходили на сервис и всё это брали. Но во время тестов мы увидели, что много людей открывают карту с катящимся Ровером, и нагрузка на этот сервис кратно возрастает. Допустим, семь человек открыли. А там на страничке каждые две секунды Java Script запрашивает координаты. И коллеги нам писали в чат: Откуда такая нагрузка? У вас же там один человек должен кататься.

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

Эту историю можно было сделать с помощью 20 таблиц. Можно было использовать две таблицы: курьер и заказ. Но в первом случае это был бы over-engineering, а во втором случае это было бы слишком сложно поддерживать. Сложная логика, сложно тестировать.

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

Суть истории в том, что есть аспекты, которым лучше уделять дополнительное внимание. Первое особое внимание уделяйте структуре API и БД. Старайтесь замечать, какие у вас сущности есть в реальной жизни. Пытайтесь переложить эту структуру примерно таким же образом в цифровом виде. Старайтесь не сильно её сокращать, не сильно расширять.

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

  1. дойти до всех клиентов, обеспечить обратную совместимость;
  2. выкатить новый API, всех клиентов переключить на новый API;
  3. выпилить старый код о клиентах, выпилить старый код на бэкенде.


Это очень затратно.

Ошибки в коде по сравнению с этим вообще ерунда. Код вы просто переписали, прогнали тесты. Тесты зелёные вы запушили в мастер. А вот API базы данных уделяйте особое внимание.

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



Есть один совет. Когда вы очень быстро разрабатываете что-то, в базе данных возникают неточности, иногда не хватает внешнего ключа или появляется дубль поля. Поэтому иногда, раз в два-три месяца, просто смотрите только на базу. Та же Intellij IDEA умеет генерировать классные схемы. И там это всё видно.

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

История третья про качество


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

Например, у нас есть один процесс, который крайне важен для платформы. Весь день мы набираем заказы на завтрашний день, но вечером срабатывает пометка, что после 22:00 мы не набираем заказы, а до 01:00 готовимся к завтрашнему дню. Потом начинается распределение заказов по сортировочным центрам. Мы идём в Яндекс.Маршрутизацию, она строит маршруты.

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

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

Мы понимали, что если процесс не завершится с десяти вечера до часу ночи, то сортировочные центры даже не будут знать, как им сортировать заказы, на какие кучки. Там будет всё простаивать. Курьеры выйдут позже, и у нас будут провалы по качеству.

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

Расскажу про один из вариантов, как можно настроить такой процесс.

Процесс этот многосоставный. Расчёт маршрутов и их публикацию можно разбить на части и, например, создать очереди. Тогда у нас есть несколько очередей, которые отвечают за законченные участки работы. И есть consumer на этих очередях, которые сидят и ждут сообщения.



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

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

Задача уровня создай маршруты для 200 курьеров, которые развезут несколько тысяч заказов в Москве занимает от получаса до часа. Это действительно очень сложная задача. И ребята из этого сервиса очень крутые, они решают сложнейшую алгоритмическую задачу, которая требует большого количества времени.

В итоге consumer второй очереди будет просто проверять, проверять, проверять. Через какое-то время задача выполнится. Когда задача выполняется, мы получаем ответ в форме нужной структуры завтрашних маршрутов и смен для курьеров.

Мы кладём то, что рассчиталось, в третью очередь. Сообщение исчезает из второй очереди. И просыпается третий consumer, берёт этот контекст с сервиса Яндекс.Маршрутизация и на его основе создаёт state завтрашнего дня. Он создаёт задания курьеров, он создаёт заказы, создаёт смены. Это тоже немалая работа. Он тратит на это какое-то время. И когда всё создано, эта транзакция завершается и задание из очереди удаляется.

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

С такой архитектурой последние месяцы у нас всё идёт довольно гладко, проблем нет. Но раньше был сплошной try-catch. Непонятно, где процесс сбоился, какие статусы поменяли в базе данных и так далее.

Есть вещи, на которых лучше не экономить. Есть вещи, с которыми вы сэкономите себе кучу нервных клеток, если сразу сделаете всё хорошо.

Что мы сделали в офлайне


Я описал большую часть того, что происходит на нашей платформе. Но кое-что осталось за кадром.

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

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

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

Когда у нас есть первая и вторая часть пазла, мы просто можем арендовать какое-то здание, нанять курьеров на машинах. Сказать им, как нажимать, что нажимать, как пикать, какую коробку куда класть, и всё. Благодаря этому мы уже запустились в семи городах, у нас больше десяти сортировочных центров.

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

Итоги


В начале я говорил, зачем мы начинали создавать собственную курьерскую платформу. Теперь расскажу, чего мы достигли. Это невероятно, но при использовании нашей платформы мы смогли приблизиться почти к 100% попадания в интервал. Например, за последнюю неделю качество доставляемости в Москве было порядка 9598%. Это значит, что в 9598% случаев мы не опаздываем. Мы укладываемся в интервал, который выбрал клиент. И мы даже не могли мечтать о такой точности, когда полагались исключительно на внешние службы доставки. Поэтому сейчас мы постепенно распространяем нашу платформу на все регионы. И будем улучшать доставляемость.

Мы получили нереальную прозрачность. Эта прозрачность нужна и нам. У нас всё логируется: все действия, весь процесс выдачи заказа. У нас есть возможность вернуться в историю на пять месяцев и сравнить какую-то метрику с текущей.

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

Вдобавок получилось оптимизировать издержки, потому что у нас есть доступ ко всем элементам цепочки. В итоге сейчас довезти один заказ стоит на четверть дешевле, чем это было раньше, когда мы работали с внешними службами. Да, траты на доставку заказа уменьшились на 25%.

И если обобщать все идеи, о которых шла речь, можно выделить следующее.

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

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

И в таком режиме мы находились около десяти месяцев. У нас были отчётные интервалы, каждые два месяца мы должны были показывать результат. И у нас всё получилось.

В таком режиме, мне кажется, вы не имеете права делать что-то, инвестируя в long-term и не получая ничего на короткой дистанции. Столь сильно закладываться на будущее в таком формате работы нельзя, потому что будущее может просто не наступить.

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

Если говорить коротко, постарайтесь делать максимально просто, но оставляя возможности для расширения.

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

И последнее. Я рассказывал про Ровер, что хорошо подобные процессы делать с помощью featureflags (фиче-флагов). Советую послушать доклад Марии Кузнецовой с митапа по Java. Она рассказывала, как устроены фиче-флаги в нашей системе и мониторинге.
Подробнее..

Доставка, которая доставляет субъективные мысли и выводы простого клиента

17.05.2021 16:18:04 | Автор: admin

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

Конечно, в заголовке я немного слукавила: я не простой клиент, а клиент избалованный автоматизацией и сервисом, а ещё бывший инженер по тестированию, маркетолог и пиарщик. Худший покупатель, в общем. Автоматизацию я полюбила, работая на протяжении 8 лет с RegionSoft CRM, любовь к сервису пришла оттуда же. Интерес к доставкам появился тоже не случайно после того, как компания стала развивать и внедрять сервис для отслеживания удалённых сотрудников на местности RegionSoft GeoMonitor, сразу возникла потребность понять физиологию курьерского дела.

Итак, проанализируем службы доставки на примере нескольких крупных провайдеров и кое-кого ещё. Место действия Нижний Новгород.

  • Сбермаркет сервис, который доставляет продукты и товары из Ленты, Метро, Ашана и огромного количества магазинов в Москве, в Нижнем Новгороде только из Ленты, Метро, Ашана, Окей. Вышли на рынок региона довольно тихо, но активно распространялись по сарафанному радио. 5 заказов.

  • Впрок (Перекрёсток) собственная доставка сети магазинов Перекрёсток. На удивление лучше, чем сами магазины (субъективно). У меня Перекрёсток в 400 метрах от дома, но после рекламы в Редакции А. Пивоварова (где ведущий вещал со склада) и скидки на первый заказ странно было не попробовать. Результат: уже 13 заказов.

  • Яндекс.Лавка (+ Яндекс.Доставка) быстрый сервис доставки базовых продуктов и товаров. Не для запасов, а для покупки того, чего прямо сейчас не хватает дома или в холодильнике. 51 заказ.

  • Самокат сервис с даркстором в радиусе 1,5 км. На самом деле, хороший сервис доставки продуктов с нормальным ассортиментом. Удобно, что ассортимент у них и Лавки отличается. С недавнего времени появился Самокат Супермаркет для закупки больших заказов с периодом доставки 1,5 часа. 30 заказов.

  • Ozon Озон есть Озон. За последний год 26 заказов.

  • Wildberries маркетплейс со множеством товаров и довольно странной маркетинговой политикой. 34 заказа за год, но большинство это маски, респираторы и санитайзеры они там появились раньше всех и стремительно дешевели.

  • Курьерские доставки СДЭК, Почта России через них присылают товары различные интернет-магазины, организации и т.д.

  • Яндекс.Еда, Delivery Club сервисы доставки еды.

  • Локальные магазинчики с доставкой (в основном, не еда, а одежда, аксессуары, посуда и т.д.)

  • Аутсайдеры для примера: Ленточка и SPAR Online собственный сервис доставки гипермаркетов Лента (1 отменённый заказ) и доставка SPAR Online (1 заказ).

Фишки доставки

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

  • Главная фишка удобство заказа и оплаты: удобные каталоги, любые формы платежей, у курьеров и чековый терминал, и платёжный терминал, и возможность корректировки стоимости заказа (если, например, в заказе есть весовой товар). Не нужно нервничать и искать наличку или переживать, что ошиблась и не выбрала оплату заказа онлайн курьер спокойно принимает платёж (но есть нюанс если сбоит сотовая связь и Wi-Fi, могут быть проблемы и зависания оплаты, это нужно учитывать, оформляя заказ, например, в дачный посёлок с плохим покрытием).

  • Возможность заказа продуктов в область: в сады, на дачу, частные дома и т.д. Есть ограничение по радиусу и срокам доставки (например, в дальние районы доставка не ежедневная), но в целом для Нижегородской области площадь покрытия услуги даже немного впечатляет.

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

  • У Сбермаркета сборщик на связи с клиентом он звонит не только сообщить, что чего-то нет в наличии и предложить замену, но и сообщает сроки годности и обсуждает состояние фруктов. Кстати, их же сборщики могут по личной просьбе найти на полках и добавить в заказ то, чего не было в каталоге особенно это было актуально для заказа из Метро. При этом он не спешит, уточняет, перезванивает. То ли дело Ленточка единственный раз, когда я с ними связалась, сборщик довольно грубо и прямолинейно сообщил, что у него регламентировано время на работу с каждым клиентом и он не пойдёт заменять товар, а просто не положит его в заказ (к слову, заказ я отменила и больше к ним не вернулась).

Яндекс.Лавка дарксторная доставка, не похожая ни на какую другую. Они унаследовали технологичность Яндекса и подходы Яндекс.Еды (а потом и её курьеров). Лавка пришла в Нижний Новгрод довольно громко: их анонсировал в инстаграме губернатор, о них говорили, да к тому же на старте были скидки 10% на весь ассортимент: например, мороженое было рублей на 15-25 дешевле, чем в Пятёрочке. По субъективным впечатлениям, сервис рос очень быстро.

Это на Новый Год отображалось в приложении Лавки. Кстати, до этой открытки от Яндекса я не задумывалась, что доставка экономит столько времени (а ведь правда), для меня важнее была экономия силЭто на Новый Год отображалось в приложении Лавки. Кстати, до этой открытки от Яндекса я не задумывалась, что доставка экономит столько времени (а ведь правда), для меня важнее была экономия сил
  • Удобное приложение: группы товаров, заказанные ранее товары, интерфейсы оплаты, чат с поддержкой всё в одном окне. Приложение за год значительно изменилось, и очевидно, что этот процесс был не просто игрой в перекладывание ярлычков, а рефакторингом на основе пользовательского опыта. Стало удобнее.

  • Идеальная поддержка: за любой косяк с фото сразу возвращали деньги за продукт и давали купоны на скидку (чаще 10%, за крупный промах (просрочку) был 20%), оперативно реагировали и при упоминании в Facebook. Конечно, это стимулировало ещё больший заказ. Был даже случай, когда из-за недостатка курьеров не могли доставить заказ и отклонили его, так бонусом была вся сумма этого заказа (около 1200 рублей).

  • Скорость доставки WOW (ну, справедливости ради, я живу в центре города и недалеко от их распределительной точки).

  • Вежливые курьеры в СИЗ-ах и гарантированно бесконтактная доставка.

  • Удобная система оплаты чаевых.

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

  • Ах да, кэшбек. Если вы подключены к Яндекс.Плюс, то с заказов в Лавке часто прилетают бонусы, ими же можно и рассчитываться, получив их в других сервисах. Экосистема, короче (кстати, раз зашла о ней речь, скажу, что оплата бонусами в такси не прошла таксист, сказал, это проблема на стороне сервиса, я жаловаться и выяснять поленилась).

  • И о социальном: это офигенно, когда в приложении ты видишь, что еду или продукты принесёт слабослышащий курьер. Такие шаги к инклюзивности и правильной нормальности в обществе очень радуют. Мне попалась такая девушка было безумно мило обменяться с ней жестами и увидеть её реальную радость от того, что она делает. (Кстати, со слабослышащим таксистом я тоже ездила и мы прекрасно обсуждали направления подъезда к дому жестами).

Про Яндекс.Еду я не буду ничего говорить, они в городе давно, я ими пользовалась раз 7-8 за без малого три года, нареканий не было, маршрутизация с проблемами и вопросами чёткая (была проблема с тем, что перепутали заказы и мне вместо моего маленького отдали чужой огромный).

Курьер на ул. Рождественской, весна 2020, Нижний Новгород (с) Из группы Прекрасный НижнийКурьер на ул. Рождественской, весна 2020, Нижний Новгород (с) Из группы Прекрасный Нижний

А вот про Яндекс.Доставку я скажу и здесь, и в минусах, потому что мне она показалась самой нестабильной частью экосистемы. Казалось бы, то же самое такси, с которым всё давно отлажено и ясно, но нет. Пока о плюсах: конечно, это супер удобная экспресс-доставка по городу. Раньше передать подружке букет и подарок, вывезти свои вещи из офиса, перевезти пару кресел к пожилым родственникам или отправить знакомому лекарства было задачей не из простых либо делаешь всё сама, либо пользуешься пакетными решениями доставок, среди которых много неудачных по сервису и качеству (особенно по части сладостей, цветов и сувениров) или цене (грузоперевозки просто новое Эльдорадо). За время самоизоляции сервис выручал и меня, и знакомых относительно меня. Как правило, все курьеры были быстрыми, бережными и даже не ругались, если их просили довезти пакет с чем-то важным всего лишь через 400 метров (был и такой случай, чисто ради сюрприза). Цены при этом таксишные и выше.

Самокат пришёл в город значительно (для масштабов 2020) позже, чем Лавка, и выходил на рынок довольно лениво. Но опять же, я живу недалеко от их распределительного центра и вижу, что розовых колёсных курьеров ощутимо заметно на улицах. Самокат, как и Лавка, предлагает ассортимент продукции под своей маркой (гораздо дороже, но и заметно интереснее) и довольно разнообразен в позициях. Курьеры чуть менее вежливы, чем яндексовые, но и быстрее обычно минут 10-15, редко 30.

Обе доставки относительно давно изменили график возможности совершить заказ до 22:00 и даже 23:00, и это прекрасно во многих ситуациях. Несколько раз буквально выручало.

От еды перейдём к вещам насущным.

Как же похорошел Озон при коронавирусе! Я серьёзно: он ощутимо расширился, стал доставлять товары из-за рубежа, расширил маркетплейсы для отдельных брендов и продавцов. Я видела, сколько проблем OZON огребал из-за этих изменений (чего стоила возможность жаловаться на неадекватные цены на хлоргексидин (400 р. против аптечных 35-45 р.), маски и санитайзеры и разборки с грубящими на жалобы продавцами), но, кажется, это сделало его сильнее, хотя, конечно, товары с неадекватными ценами по-прежнему встречаются. Я с Озоном с 2009 года и совершила там сотни покупок, поэтому пользуюсь и Премиумом, и Ozon Card, бонусная система и скидки по подписке для меня оказались выгодными. Этот маркетинг лично меня втянул и теперь Ozon приоритетный для меня книжный, дачный, ремонтный и проч. (кроме сложной техники). И это тоже показатель: маркетологи сделали лояльным стреляного рекламщика, маркетолога, пиарщика честь им и хвала (только смайлики в рассылках ну очень раздражают).

Запах Озона смешался со знакомым душком глобализации. Местами это страшновато.Запах Озона смешался со знакомым душком глобализации. Местами это страшновато.А это смайлики в письмах. Если что, совершенно не работают очень быстро приглядываются, считайте, вариация баннерной слепоты.А это смайлики в письмах. Если что, совершенно не работают очень быстро приглядываются, считайте, вариация баннерной слепоты.

Wildberries это немного другой сегмент покупок, особенно в свете последних расследований про поддельную косметику, накрученные отзывы и т.д. И я уверена, что в понимании их маркетинга я не вхожу в целевой сегмент этого интернет-магазина. Однако пандемия внесла свои коррективы и среди разнообразия этого маркетплейса я нашла и приобрела неплохую одежду, обувь, бытовые мелочи и даже косметику. Это тот случай, когда мне особо не за что ругать магазин, но и поводов для похвалы нет, тем более что я часто вижу жалобы знакомых в лентах социальных сетей. Скажу так: здесь нужно выбирать внимательно и с умом, а главное, не гнаться за бонусами и выкупами, это игра для их сегмента, пусть они и играют.

Кстати, я так ни разу ничего не заказала с Яндекс.Маркета, хотя периодически пользуюсь им для поиска отзывов на товары, сравнений и информации о позициях.

Теперь о локальных магазинчиках с доставкой А о них не будет. О них в недостатках. Увы.

Недостатки доставок

Общие минусы

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

  • Время доставки у всех длинных сервисов очень растяжимое понятие. Интервалы вроде с 10 до 18 это день в привязке к дому. Если бы было отслеживание статуса приближения курьера к дому, было бы идеально: можно было бы сопоставить со своим графиком дел и выхода из дома. Бывает, что не соблюдается даже выбранный интервал (справедливости ради, в основном, из-за пробок, аварий, снегопадов и других форс-мажоров).

Понять и простить!Понять и простить!
  • Курьерам и сборщикам в общем и целом наплевать на сохранность товаров и их вида. И если Впрок и Сбермаркет продумали крутые пластиковые контейнеры, а OZON коробки (много и разных, да ещё с набивкой из бумаги и плёнки с пупырышками), то остальным, включая крупных операторов, до фени, что будет с вашей коробкой или пакетом. Я видела и порванные упаковки, и ужасно грязные, пыльные верхние слои защиты, и разбитые яйца, и т.д.

  • Кстати, о яйцах, а точнее о товарном соседстве. Жёстко его соблюдают только Впрок и Сбермаркет (у них всё по категориям в пакетах, замороженное в термосумках, которые забирают), а у остальных всё зависит от укладчика: бывает очень классно и добросовестно, а бывает яйца, мороженое, хлеб и курица приходят в одном пакете и, если одно яйцо решит разбиться, весь пакет и все продукты будут в нём.

  • Пожалуй, курьеры ни одной из доставок не относятся бережно к лифту. Я обратила на это внимание, так как наша девятиэтажка чувствительна к лифту, он был переустановлен лет 10 назад и уже несколько раз ломался. Я могу понять доставщиков больших объёмов: у них много ящиков, они работают в одиночку и кнопку блокировки дверей нажать некому, но когда я несколько раз видела, как сотрудники одной из доставок ставили свой терморюкзак в двери лифта и так несли заказ в пакете до квартиры, это вызывало недоумение и сожаление. В доме всего 9 этажей, не так долго ждать этого лифта.

  • Недовольство курьеров при отключённом лифте. Я старалась не делать крупные (да и никакие) заказы, когда знала, что лифт не работает. Но за год он ломался и некоторые заказы попадали на такую ситуацию, о которой я не знала. Удивило, что громче всех возмущался представитель курьерской службы, который привёз 3 листочка документов, а самыми позитивными оказались доставщики OZON и Wildberries, хотя одному из них пришлось тащить на 7 этаж 24 кг посылки (чаевые не взял).

  • Непонятные функции чаевых: если у сервисов Яндекса всё чётко и чаевые можно оставить всегда, то у остальных ситуации очень странные. Самокат подключил чаевые спустя время через внешний сервис, курьеры OZON и Wildberries ни за что не берут деньги, а доставщик СДЭК, который кроме прочего ещё и помог с занесением тяжестей к месту их нахождения в квартире, заявил, что его вообще за такое оштрафовать могут и некоторые предлагают, а потом жалуются (жуть же!).

  • Человеческий фактор исключать нельзя. Были неприятнейшие моменты и даже небольшая кража, но они вне тематики Хабра и оценки сервиса, поэтому я опущу этот момент. Однако компании должны помнить, что для обычного покупателя курьер == компания, клиенты не вдаются в подробности и особенности найма и субподряда.

Частные минусы

  • Яндекс.Лавка, на мой взгляд, перегружает курьеров, и я им об этом писала. Изначально, когда Лавка только пришла в город, абсолютно все заказы развозили курьеры на автомобилях. Это было здорово и удобно: с бесплатной доставкой можно было заказать муку, 10 бутылок минералки, гору молочки и т.д. Машина 9 ступенек лифт, а вот и получатель. Когда очередной мой заказ на 20+ кг принёс парень с термосумкой и без транспорта, я, кажется, достала службу поддержки, потому что никакие мои чаевые не компенсируют угрозу здоровью молодого (и, кстати, обычно ещё растущего) организма. Это здорово демотивировало делать объёмные заказы, и я перешла на услуги Впрок, а Лавку оставила для для салата яиц не хватило и прочих срочных мини-заявок. Правда, потом заметила по себе и соседям, что заказы стали справедливо распределять между пешими курьерами, курьерами на мото/вело и на авто, что однозначно логично.

На велосипеде всё равно не ок, нагрузка на спине, но раз мистер Геркулес сам так решил...На велосипеде всё равно не ок, нагрузка на спине, но раз мистер Геркулес сам так решил...
  • Яндекс.Лавка изменяет стоимость доставки в зависимости от погоды и загруженности курьеров (как и в Еде, и в Такси). И если в Еде и Такси это очевидно, то в Лавке всегда становится сюрпризом. То есть базовые условия 0 рублей от 200 рублей заказа, а тут дорожает + порог заказа с бесплатной доставкой сильно растёт (в несколько раз). Это максимально справедливо по отношению к труду курьеров, просто первые разы не хватило возможности предусмотреть такую ситуацию со стороны клиента.

  • Курьер Яндекс.Доставки не хотел подниматься на этаж и даже подъезжать к дому из-за сугробов, грубил и отказывался закрыть заказ со своей стороны (там и так наценка за погоду была нехилая), в то время как его коллега приехал, встал сбоку от дома, дошёл по сугробам до подъезда, идеально выполнил срочный и важный заказ на доставку пакета и получил чаевые. И таких инцидентов и мелочей за год доставок было много: грубость курьеров, раздражение, отсутствие СИЗ Я знаю, что компании практически не могут на это повлиять, но ведь у Сбера и Перекрёстка получилось, значит, должно получаться. Сервисы доставки должны прежде всего остального понимать, что лица курьеров и их отношение это и есть лицо компании в глазах клиента. Никто не станет разбираться, почему компания А предоставила компании Б таких сотрудников просто скажут: Давеча в Яндексе заказывала, так быстро привезли и курьер такой аккуратный, на сумку заказ положил, отошёл. Видите, не Еда, не Лавка, а Яндекс и этот парень лицо Яндекса, Перекрёстка, Озона, Сбера и его партнёров и т.д. Наверное, это самый сложный пласт работы с человеческим фактором.

  • Wildberries приносит заказы по частям, иногда ну очень мелким и в разные дни и ты сильно привязан к дате и времени или вынужден просить курьера приехать позже или в другой день. Я пыталась общаться об этом с компанией, но ответ был так себе: всем удобно, а ты сиди недовольная. В противоположность им OZON даёт возможность изменить дату заказа, выбрать единую дату для частей и т.д. Ну и поддержка Озона (сильно и хорошо роботизированная) работает не в пример лучше несчастной Евы Вайлет в социальных сетях фиолетового конкурента.

Ева местами робот, местами человекЕва местами робот, местами человек
  • Яндекс.Лавка и Самокат выбирают очень странных поставщиков готовой еды, выпечки, кондитерской продукции: например, я как горожанин доверяю лишь одному изготовителю из предложенных. Но вообще интересно узнать долю заказов готовой еды именно в этих сервисах какой смысл, когда есть Яндекс.Еда и Delivery с гарантированно проверенными местами, и это не считая локальные доставки из городских кафе, ресторанов и сетевых общепитов. Единственный заказ двух наименований готовой еды оказался на 100% неудачным и был отправлен в мусор в полном составе. Но это дело вкуса.

Теперь о локальных магазинчиках с доставкой. Я работаю удалённо из дома, долго была в жёсткой самоизоляции и иногда хотелось купить что-то для души: пару статуэток в коллекцию, кое-что из эксклюзивных чаёв и прочих милых мелочей. Тех мелочей, которые небольшие, но довольно дорогие. Было 4 заказа и из них только один не взял деньги за доставку. Но особенно поразил один из них: 2 мелочи стоили 3000 рублей, я точно знаю, что наценка минимум 30%. Доставляли Яндекс.Доставкой и попросили компенсировать что-то около 200 рублей не вопрос, но как же это смазало впечатление! Но всех победил магазин, который потребовал 500 рублей за доставку + 100 рублей, если до подъезда и ещё какие-то условия, если до квартиры (стоимость заказа около 7000 рублей, вес не больше 7 кг) в общем, отказалась от их услуг из-за этого неуёмного коммерческого бюрократизма.

И так сойдёт?

Сейчас мы вышли на улицы и вернулись в оффлайновые магазины, которые обходят доставки по свежести, качеству фруктов и овощей, ассортименту и скидкам. Доставкам нужно развиваться, чтобы быть конкурентоспособными вне уникальных условий пандемии COVID-19. Кто-то уже вернул платность доставки и стимулирует посещать пункты выдачи, кто-то стал ощутимо хуже доставлять, кто-то поднял цены ощутимо выше магазинных. Стабильно хорошо смотрятся длинные доставки, которые привозят запас продуктов на дом или на дачу, но они и изначально были на голову выше. Остальным нужно развиваться и уже видно, что Яндекс.Лавка и Самокат это делают. Самокат работает в формате гипермаркет, Лавка постоянно расширяет ассортимент, Яндекс.Еда доставляет продукты из магазинов (включая локальные) и больших гипермаркетов.

Так каким должна быть доставка, чтобы доставлять во всех смыслах этого слова?

Идеальная доставка с точки зрения клиента

  • Приложение должно быть удобным, отзывчивым, с однозначно воспринимаемым интерфейсом. Акции, бонусы, последние заказы, адреса, способы оплаты и контакты поддержки должны быть очевидны и находиться на ожидаемых пользователем местах. В приложении должно быть удобное отслеживание заказа (по возможности) пользователь должен знать более точное время прибытия курьера (как у Деливери, Яндекс.Еды, Самоката и Лавки). Дополнительной интересной возможностью могут быть оценки и отзывы на товар (лучше всего реализовано у Впрок) это позволяет покупателю быть информированным, а самому маркетплейсу управлять качеством, ассортиментом и поставщиками.

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

  • Поддержка пользователей обязана быть простой, доступной и очень быстрой. У большинства участников обзора поддержка сочетает в себе роботизацию и участие живого оператора. А вот позвонить и поговорить голосом гораздо проще с небольшими местными компаниями они отвечают сразу и по делу, крупные маркетплейсы гоняют или по людям, или по IVR (который, к тому же служит в первую очередь коммерции, а не помощи клиенту). В принципе (кроме Wildberries) из всех обращений, что случались, всё заканчивалось хорошо и в пользу клиента. Самые приятные механики взаимодействия у Ozon и у сервисов Яндекса. А вот с Самокатом из приложения пообщаться не получалось приходилось переключаться на мессенджер (а потом ещё запоминать, в каком это из 5 мессенджеров я общалась с Самокатом, чтобы обратиться вновь).

  • Курьеры это довольно болезненная часть работы доставок, с ними случается всякое (хотя уверена, что у них тоже сотни историй про особых клиентов). Чаще всего это быстрые, обходительные люди, которые безупречно выполняют работу, но, увы, 2-3 косяка могут испортить отношение к компании. Наверное, где-то в идеальном мире для курьеров проходит качественное обучение, их учат правилам обращения с людьми, лифтами, звонками, ручками дверей, домофонами и т.д. Но вообще это мелочи кроме пары вопиющих случаев на грани правонарушения претензий к этим ребятам нет, они своеобразные герои пандемии. Из реальных пожеланий: возможность отслеживать реальное перемещение курьера и понимать, почему в приложении он будет через 6 или 12 минут, а в реальности уже звонит в дверь.

  • Ассортимент самая сложная история. Необходимо, чтобы он изменялся в зависимости от спроса, от отзывов покупателей, чтобы все продукты были с нормативным сроком годности. Обязательно должно быть полное описание продукта, вся информация о нём. Благодаря этому формируется доверие со стороны клиента, он не боится заказывать не только товары длительного хранения, но и молочку, фрукты, рыбу и т.д. Если говорить откровенно, то абсолютно никаких проблем со свежестью и качеством не было только у Впрок и Сбермаркета вероятно, это объясняется возможностями складского хранения и оптимальными условиями транспортировки заказа у гигантов. Таких оговорок быть не должно: если доставка работает с продуктами, фруктами, овощами, цветами и проч., разочарований и претензий у клиента быть не должно.

  • Программы лояльности факультативная функциональность. Здесь должно работать простое правило: либо программа есть и она рабочая и понятная (бонусы, кэшбек, скидки, условия бесплатной доставки и т.д.), либо её просто нет и покупатель вас выбирает просто за отличную работу сервиса. Третьего не дано.

Лучше, если промокоды, купоны и скидки будут применяться в корзине автоматически и точно будет видно, на какие позиции скидка распространяется, а на какие нет. Такая функциональность при всей очевидности и простоте реализована далеко не у всех.

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

Перечитала статью и вижу, что прямо благостно написала о сервисах Яндекса, Сбермаркете, Впрок и OZON. Конечно, отчасти это привычка (в случае Яндекса и Озона), отчасти работа в техносфере я доверяю тем, кого хорошо знаю как человек из ИТ. Но в этой вроде бы субъективной оценке есть и объективные причины.

У этих приложений я наблюдаю постоянную эволюцию: они меняются и становятся удобнее. Это значит, что за проектированием UI/UX стоят исследования и аналитика поведения пользователей.

  • Приложения не глючат, не вылетают, обеспечивают взаимодействие со всеми функциями в одном интерфейсе (кроме Озона, у него есть ещё отдельное небольшое приложение Ozon Card).

  • У приложения Впрок даже сторонние библиотеки со ссылками на GitHub указаны. Приложение выглядит действительно продуманно и профессионально.

  • У Впрок, Озона, Сбермаркета кроме мобильных приложений отличные web-сайты, где можно собрать заказ в удобном для себя режиме, не тыкая в случайные позиции и вдумчиво перебирая корзину. Поскольку я почти 16 часов в день нахожусь у десктопа, для меня это важная функциональность.

  • В приложениях много продуманных мелочей: от цветовых решений до умной подборки ассортимента.

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

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

Ну а если вы только создаёте свою службу доставки, то у нас в РегионСофт есть RegionSoft CRM с возможностью управления складом, ассортиментом, дисконтными программами и т.д. и облачный сервис GeoMonitor, который позволяет отслеживать перемещение объектов (курьеров и транспорта) на местности. В этой сфере автоматизация многое определяет.

Подробнее..

Геопространственное моделирование с применением методов машинного обучения

18.06.2021 18:20:53 | Автор: admin


Всем привет! Меня зовут Константин Измайлов, я руководитель направления Data Science в Delivery Club. Мы работаем над многочисленными интересными и сложными задачами: от формирования классических аналитических отчетов до построения рекомендательных моделей в ленте приложения.

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

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

Статья написана по мотивам выступления с Евгением Макиным на конференции Highload++ Весна 2021. Для тех, кто любит видео, ищите его в конце статьи.

Бизнес-модель работы Delivery Club


Бизнес-модель Delivery Club состоит из двух частей:

  • ДДК (доставка Деливери Клаб): мы передаем заказ в ресторан и доставляем его клиенту, то есть ресторану остается только приготовить заказ к определенному времени, когда придет курьер.
  • МП (маркетплейс): мы передаем заказ в ресторан, а он своими силами доставляет заказ в пределах своей согласованной зоны.

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

При этом построить зону доставки это полбеды. Ведь география ДДК огромна и охватывает более 170 городов России с тысячами ресторанов в каждом из них, и у каждого ресторана свои индивидуальные особенности, которые необходимо учитывать при построении эффективного сервиса доставки.

Рисуем зону доставки ресторана


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



Как процесс выглядел раньше


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

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



Стоит упомянуть и про SLA (Service Level Agreement соглашение о максимальной длительности отрисовки зоны доставки для одного партнера): онбординг партнера или подготовка его зоны для внедрения в нашу платформу составляли порядка 40 минут для одного заведения. Представьте, что к вам подключилась городская сеть с сотней ресторанов, а если это ещё и жаркий сезон, например, после проведения рекламной акции Вот наглядное доказательство неэффективности ручной отрисовки:

$T = R * SLA = 100 * 40\ минут =\ \sim 67\ часов\ ручной\ работы$


где $T$ время, которое будет затрачено на отрисовку всех зон доставки партнера,
$R$ количество ресторанов,
$SLA$ время на отрисовку одной зоны.

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


  • непостоянство результата;
  • непрозрачная логика;
  • субъективизм в принятии решений о форме зоны и ее характеристиках;
  • однообразный подход без учета индивидуальных особенностей партнера;
  • крайне низкая скорость построения зон;
  • ручной труд менеджеров;
  • отсутствие гибкости в управлении характеристиками зон доставки (зона представляла собой единую нераздельную сущность, внутри которой нельзя было установить разную стоимость доставки в зависимости от расстояния между клиентом и рестораном).

Baseline


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

При этом оставались недостатки:

  • артефакты в зонах доставки (стандартный случай с переходом через реку);
  • единообразный подход к партнерам (не учитываются индивидуальные KPI партнеров).

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



Пробовали алгоритмы на основе Convex Hull и Alpha Shape, с их помощью можно было бы устранить артефакты. Например, используя границы водных массивов как опорные точки для построения форм удавалось обходить реки. Однако всё равно отсутствовал индивидуальный подход к партнерам.

Преимущества технологии H3


Мы стремились прийти к универсальному решению. Каких-то простых и популярных подходов найти не удалось, поэтому мы сосредоточились на разработке собственного алгоритма. А за основу взяли технологию H3 компании Uber.


Источник: eng.uber.com/h3

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

Также стоит отметить, что:

  1. Существует хорошая библиотека для работы с H3, которую и выбрала наша команда в качестве основного инструмента. Библиотека поддерживает многие языки программирования (Python, Go и другие), в которых уже реализованы основные функции для работы с гексагонами.
  2. Наша реляционная аналитическая база Postgres поддерживает работу с нативными функциями H3.
  3. При использовании гексагональной сетки благодаря ряду алгоритмов, работающих с индексами, можно очень быстро получить точную информацию о признаках в соседних ячейках, например, определить вхождение точки в гексагон.
  4. Преимуществом гексагонов является возможность хранить информацию о признаках не только в конкретных точках, но и в целых областях.

Алгоритм построения зон доставки


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


  2. Убираем точки-выбросы. Анализируем все постройки и сразу отбрасываем те, которые нас не интересуют. Например, какие-то мелкие нежилые объекты. Далее с помощью DBSCAN формируем кластеры точек и отбрасываем те, которые для нас не являются важными: например, если кластер находится далеко от ресторана или нам экономически невыгодно доставлять туда.


  3. Далее на основе очищенного набора точек применяем триангуляцию Делоне.


  4. Создаем сетку гексагонов H3.


  5. Определяем, к какому треугольнику принадлежит каждый гексагон H3, и для этого гексагона определяем расстояние от ресторана с помощью аппроксимации по трем вершинам треугольника (кто хочет чуть больше разобраться в геоматематике и понять рациональность использования тех или иных геоформ, вот ссылка на оригинал статьи Uber).


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



    В текущей версии алгоритма мы используем следующие функции ошибок:

    • минимизацию времени доставки при фиксированном покрытии;
    • максимизацию охвата пользователей при фиксированном времени доставки.

    Пример функции ошибки для минимизации времени доставки:

    ${L_{min}}_{time}\;=\;min(\sum_{i=1}^n\;({t_{rest}}_i)/n),$


    где $L_{min_ {time}}$ функция ошибки минимизации времени доставки с фиксированным покрытием,
    $t_{rest_ {i}}$ время от ресторана i до клиента,
    $n$ количество клиентов в зоне доставки.

  7. Далее строим временной градиент в получившихся зонах (с очищенными выбросами) и с заранее определенными интервалами (например, по 10-минутным отрезкам пешего пути).



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

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

Внедрение


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

Здесь мы столкнулись с тем, что очень трудно было менять сложившиеся бизнес-процессы, которые были налажены годами. Операторы очень не хотели передавать свои KPI машине.

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

Но тут пришел COVID-19

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

Всё это отчасти помогло нам убедить партнёров активнее тестировать и переходить на алгоритм автоматического построения зон доставки. В итоге, за первую неделю после начала пандемии и введения ограничения для ресторанов порядка 50% наших партнеров перешло на новые зоны доставки, которые уже были построены автоматически, а в течение следующих 3 недель уже почти не осталось партнеров с зонами, построенными вручную.

Оценка


После решения всех горящих проблем нам нужно было немного отдышаться и понять, что мы вообще наделали. Для этого воспользовались A/B-тестом, а точнее его вариацией switch-back. Мы сравнивали зоны ресторанов с одинаковыми входными параметрами, оценивали GMV и время доставки, где в качестве контроля у нас были простые автоматически отрисованные зоны в виде окружностей и прямоугольников, либо зоны, отрисованные операторами вручную.



В итоге для разных алгоритмов мы получили прирост GMV в тестовых алгоритмических зонах при одинаковом времени доставки, либо уменьшение времени доставки в случае с алгоритмом с фиксированным клиентским покрытием.

А время, затрачиваемое на построение зон для партнера из примера выше, теперь выглядит более оптимистично:

$T = 100 * 3,6\ секунды =\ \sim 6\ минут$


Ускорение в 670 раз!

Текущая ситуация и планы


Сервис работает в production. Зоны автоматически строятся по кнопке. Появился более гибкий инструмент для работы со стоимостью доставки для клиентов в зависимости от их удаленности от ресторана. 99,9% ресторанов (изредка ещё нужно ручное вмешательство) перешли на алгоритмические зоны, а наш алгоритм поспособствовал переходу бэкенда на H3.

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

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

Теперь мы планируем начать работать с зонами динамически, автоматически изменяя их в реальном времени в зависимости от внешних обстоятельств (например, погоды), оптимизировать скорость работы, внедрить новые оптимизации.

Всем спасибо!

Подробнее..

Всего 38 заказов в интернете оплачивают заранее. У курьеров выкупают реже

05.02.2021 12:22:10 | Автор: admin

Покупатели стали делать заказы чаще, но оплачивать их не научились. С 2019 года процент предоплаченные заказов застрял где-то у отметки в 38%. То есть больше половины посылок клиенты готовы оплатить только при получении, если вообще готовы их получить. Недавно две крупные компании - PIM Solutions и Data Insight (одна агрегирует логистические возможности, другая - занимается крупными исследованиями) опубликовали большие отчеты о рынке российской логистики в 2020 году. Из него и стало ясно, что далеко не все готовы выкупать свои посылки после заказа. Что только не влияет на этот факт: категория товара, регион проживания покупателя и даже курьеры. Мы просмотрели все графики по 4 раза и готовы поделиться краткой сводкой. Спойлер: продавцам интернет-магазинов отчёт реально будет полезен.

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

Россияне не любят платить заранее

Те самые 38% из первого абзаца - доля предоплаченных заказов. Этот показатель почти не меняется уже несколько лет. Да, в Москве он подрос на пару процентов, но это лишь исключение из правил - сами понимаете, пандемия. Но даже среди 38% покупателей, которые оплачивают заказы сразу, есть те, кто не забирают свои посылки и возвращают деньги назад. Для e-commerce это звучит печально.

Данные отчетов компании Data InsightДанные отчетов компании Data Insight

Курьерам не доверяют, либо не могут доверить деньги

Продавцы, наверное, часто видят сны в которых идеальные покупатели выкупают 100% заказов и никогда не пишут гневных отзывов. В нашем неидеальном мире эти значения чуть выше 90%. Что приятно, в 2020 году этот показатель реально вырос. И тут важное открытие: доля выкупа заказов из ПВЗ выше, чем у курьеров.

Это значит, что в 2020 году покупатели чаще приходили за заказами в пункты выдачи и постаматы и реже встречали курьера у двери. Речь именно о получении заказа (не важно оплачен он заранее или нет). Пока показатели выкупа в ПВЗ в 2020 году подрастали с 91,03% до 92,87%, выкуп заказов у курьеров ни разу не превысил 90%. По факту этот параметр стабилен и не сильно меняется в последние годы.

 Данные отчета компании PIM Solutions Данные отчета компании PIM Solutions

Дойти до двери проще, чем стоять у двери

Подумайте пару секунд и согласитесь: часто получать посылки удобнее именно в ПВЗ. Вспомните: курьерская доставка неизбежно привязывает вас к месту и времени. Ждать никому не хочется.Это первая причина по которой графики выше выглядят так, а не иначе.

Вторая причина - доступность. Пункты выдачи и постаматы расположены в шаговой доступности. Да, пару лет назад было далеко не так. Сегодня все иначе: ПВЗ есть за каждым углом и даже за дверью любимого продуктового магазина.

Что получает покупатель? Он точно знает график работы пункта или постамата, может спланировать свое время и место получения посылки - причем все это он делает во время оформления заказа.

А еще доставка до ПВЗ - более экономичный способ доставки. Очевидно, что делая заказ в интернет-магазине и следя за тем, как меняется итоговая цена при выборе способа доставки, покупатель часто делает выбор в пользу экономии.

Есть и более явные причины. Из-за пандемии число отправлений выросло в разы, а многим ПВЗ пришлось сократить время хранения заказов. Иначе хранить было бы негде. Это, безусловно, стимулировало покупателей быстрее решать: бежать за посылкой или оказаться в числе 10%, которые бросают свои заказы и обрекают их на страшные муки возвратной логистики.

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

Эти бесконечные графики демонстрируют и еще одну зависимость. Процент выкупа заказов также зависит от категории товара. Импульсные покупки забирают реже, товары первой необходимости - чаще. Судя по графику, ребенок вполне может обойтись без новой игрушки, а вот кошка без пакета свежего корма - нет. Про детей мы готовы поспорить - просто попробуйте отказать малышу в новой машинке. Но по данным Pim, товары для животных в 2020 выкупили 91,4% покупателей, детские товары - только 82,7%

 Данные отчета компании PIM Solutions Данные отчета компании PIM Solutions

Есть категория в которой все гораздо сложнее. Одежду в 2020 выкупили только в 78,5% случаев. При этом аналитики признают, что даже эти значения завышены, потому что частично выкупленные заказы тоже легли в основу этой цифры.

И, да, у курьеров, независимо от категории товара, заказы выкупают реже. Тут тоже есть свой антирейтинг: например, товары для спорта при доставке до двери выкупали реже всего. Может передумывали и делали выбор в пользу горячей пиццы?

 Данные отчета компании PIM Solutions Данные отчета компании PIM Solutions

Покупатель всегда прав, только не все его слушают

Логично предположить, что наличие курьерской доставки, выбора пункта выдачи или постамата - современный стандарт для интернет-магазинов. Это, правда, верный шаг на пути к клиенту - помочь ему с выбором или дать иллюзию выбора. Посмотрите, маркетплейсы строят свою работу именно по этому принципу. Однако, в России далеко не все по стандарту.

По данным Data Insigh, сегодня 97% всех магазинов предлагают клиентам услугу доставки до двери, а доставку до ПВЗ - лишь 94% больших магазинов и 90% всех остальных. С доставкой до постаматов предриниматели, и вовсе, не разобрались. Она подключена только у 64% крупнейших магазинов и у 24% остальных. То есть большая магазинов использует курьеров как основной канал доставки в то время, когда покупатели старательно уклоняются от встречи с посыльными.

Данные отчета Data InsightДанные отчета Data Insight

А может продавать только в Москве?

Можно. В Москве и еще паре регионов России. Срез на графике - направления доставки, который наглядно показывает, что в каждом регионе свои значения выкупа и объемы отправлений. На картах отмечено куда направляются заказы чаще и жители каких городов их выкупают.

Данные отчета Pim SolutionsДанные отчета Pim SolutionsДанные отчета Pim SolutionsДанные отчета Pim Solutions

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

Как стать котиком для покупателей?

Кажется нет ничего лучше, чем взять данные аналитики и объединить с вашими цифрами. Например, мы знаем, что товары с доставкой до пунктов выдачи выкупают чаще. Мы помним, что в категории Товары для животных доля выкупа - предел собачьей мечты. Если собрать статистику продаж по товарам в этой категории, найти топовые или, наоборот, почти не представленные на российских маркетплейсах; после отгружать их с доставкой в пункты выдачи, то и вероятность роста выручки высока.

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

С помощью сервиса аналитикиSellerFoxмы посмотрели как обстоят дела на маркетплейсах в категории "Товары для животных" в январе 2020 года. Данные рассмотрели на примере Wildberries.

Данные сервиса аналитики SellerFoxДанные сервиса аналитики SellerFox

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

Судя по следующему графику, кошек балуют чаще. Выручка и количество проданных товаров в этой подкатегории занимает половину пушистых витрин на Wildberries. Вдумайтесь, на них продавцам удалось заработать в январе 359 836 488 рублей

Данные сервиса аналитики SellerFoxДанные сервиса аналитики SellerFox

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

Данные сервиса аналитики SellerFoxДанные сервиса аналитики SellerFox

Если понимаете, что конкурировать с продавцами кормов сложно, смотрите дальше - в списке топовых товаров можно найти антицарапки и одноразовые пеленки. Возможно, стоит поискать дешевые аналоги этих позиций и расширить ассортимент маркетплейса?

Данные сервиса аналитики SellerFoxДанные сервиса аналитики SellerFox

Теперь строим гипотезу.

У нас с вами есть данные о хорошем спросе на некий noname-товар. Мы знаем, что покупатели охотнее выкупают заказы из ПВЗ и очень любят выбирать в качестве способа доставки постаматы. Еще мы видели на графиках, что в Нижегородской области отличные показатели выкупа и именно сюда доставляется огромный объем посылок.

Теперь продумываем логистику и продвижение. На какие склады маркетплейсов стоит отгрузиться, чтобы оказаться рядом с нашими гипотетическими "выкупателями"? Какой рекламный канал выбрать? Возможно контекстную рекламу стоит настроить с упором на географию этих пользователей? Можно построить массу теорий и провести сотню тестов. Если решитесь - расскажите, помогли ли вам данные исследований. А вдруг статистика не врет?

Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru