Русский
Русский
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 продукт). Если эта тема вас заинтересовала, переходите по ссылке :)

Подробнее..

История одного личного кабинета, который помог нам сделать 15 000 курьеров и сборщиков немного счастливее

13.04.2021 14:10:30 | Автор: admin
Когда мы видим какой-то сервис, мы сразу же спешим оценить, насколько он удобен и полезен. В случае со СберМаркетом клиенту нужны понятный интерфейс, обширный каталог товаров, все подробности по доставке, разные системы оплаты и так далее. Так сервис выполняет свою функцию, а пользователь остается доволен услугой.

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

В сентябре 2020-го мы собрали кроссфункциональную команду из разработчиков, аналитиков, дизайнеров, бухгалтеров и операционного отдела и сосредоточились на невидимой для пользователя стороне СберМаркета: личном кабинете для курьеров и сборщиков заказов. Мы сделали его с нуля за 3 месяца.

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





ИТ-решение вместо амбарной книги



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

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

Что хотят знать партнеры?

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


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

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

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

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

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

Как он выглядит



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

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

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

Что на экране позиций в заказе: карточки продуктов и товаров в сборке или доставке плюс кнопка информации о заказе, инфо о клиенте, вес заказа, временной слот и так далее.



Личный кабинет помог нам на 15% снизить нагрузку на супервайзеров. Теперь все ответы есть в ЛК. С выплатами вознаграждений тоже стало проще: от потоковой системы сверки мы пришли к претензионной системе.

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

Как личный кабинет помогает бизнесу



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

Теперь личный кабинет решает несколько задач сразу:

  • Легализация существующих костылей. Весь нужный функционал теперь в одном месте.
  • Повышение доверия к СберМаркет у партнеров. Когда сервис дает прозрачную аналитику по заказам, люди видят и знают, что никакая часть их работы не потеряется. Им спокойнее они лучше и охотнее работают.
  • Укрепление связи клиент-партнер. В ЛК сборщики видят комментарии от клиентов: Молодцы, все хорошо. Или наоборот: ну вот, привезли не ту шоколадку. Раньше эта информация могла дойти до партнеров только через супервайзера. Теперь сборщики и курьеры видят свои точки роста, могут работать над ошибками или радоваться положительной обратной связи.
  • Прозрачность и наглядность результатов работы. Сколько собрал, сколько заработал, как быстро довез теперь вся детализация доступна в любое время.
  • Повышение мотивации. Партнеры увидели, что их труд приносит кому-то радость, и даже узнавать постоянных клиентов. Так могут дать более хороший и персонализированный сервис.
  • Платформа для экспериментов. Что если мы захотим попробовать выплачивать вознаграждение не каждую неделю, а каждый день? В личном кабинете мы сможем быстро рассказать об изменениях. Раньше любые новости объявлял супервайзер.
  • Нужный функционал для найма гибких партнеров. Сейчас у нас около 15 тысяч сборщиков и курьеров. Большинство работает по фиксированному графику. Но есть и те, кто хотят работать нерегулярно и по паре часов в день. Личный кабинет помогает держать связь с такими партнерами и не терять их из виду, так как они гораздо реже других общаются с супервайзером.
  • Одно окно для любых вопросов. Мы постоянно увеличиваем количество партнеров. Общаться с тысячами людей сложно, поэтому ЛК призван стать удобным единым каналом коммуникации. Там можно узнать, спросить и понять любой вопрос, не нагружая супервайзера.


Как мы нашли все боли



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

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

Мы делали два UX-теста перед стартом работы и после выкатки первой версии. Получили несколько инсайтов:

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

    Здесь у нас сильно порвался шаблон. Мы думали, что делаем ЛК ради показа личной статистики (средний балл оценки, вознаграждения, количество заказов), но UX-тесты и опросы показали, что мы ошибались. Поэтому сперва мы начали работать над отображением оценки и комментариев пользователей по каждому заказу. Потом добавили статистику.
  2. Партнеры хотят видеть чаевые и того, кто их оставил.

    После первого MVP мы собрали второй UX-тест, сделали большой опрос и узнали, чего еще не хватает курьерам и сборщикам. Оказалось, что им хочется видеть в личном кабинете в том числе и чаевые: сумму за заказ и адресата. Механика чаевых была реализована на стороннем сервисе. Чтобы посмотреть свой чай, партнеру нужно было каждый раз логиниться в другом месте. Мы быстро добавили эту функцию в ЛК тут даже разработка не потребовалась.
  3. Номер заказа ни о чем не говорит партнерам: нужен список позиций или адреса.

    Окей, все видят отзывы от клиентов, чаевые и свою оценку. А как понять, за какой именно заказ тебя отблагодарили? Оказалось, что сборщики ассоциируют заказ с конкретными позициями: а, это тот, кто купил 100 киндер-сюрпризов!. Для курьеров фактор узнавания адреса. Поэтому мы добавили возможность провалиться в карточку любого заказа, увидеть позиции и другую информацию.


А что теперь



Мы быстро увидели, что 80% партнеров заходят в ЛК несколько раз за смену. Это превзошло наши ожидания. Кабинет работает на геймификацию труда: людям интересно видеть свой прогресс, он их мотивирует.

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

Дальше мы планируем масштабировать ЛК и на другие роли. Например, на партнеров-универсалов: это сборщики, которые сами доставляют заказ.

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

Когда пользуетесь каким-либо сервисом услуг (от такси, клининга, доставки и чего угодно еще) думаете о том, из чего оно состоит внутри?
Подробнее..

Дары небес в Германии разработали дрона, который доставляет 3 посылки одновременно

04.05.2021 14:16:26 | Автор: admin
Источник

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

Модель N 198


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

Основные характеристики БПЛА:

  • 150 км/ч максимальная скорость БПЛА в горизонтальном полете;
  • 6 кг максимальная полезная нагрузка;
  • ~75 км преодолевает дрон на одном заряде аккумулятора при нагрузке в 6 кг;
  • до 109 км пролетает беспилотник, перевозя легкие грузы;
  • 8 роторов для взлета дрона-доставщика;
  • 2 крыла у БПЛА для горизонтального полета;
  • 3 отдельных крепления и лебедки для спуска груза;
  • 10 дронами может одновременно управлять один оператор Wingcopter.

Еще одна особенность дрона-курьера он оснащен датчиками и необходимым ПО, которое помогает облетать препятствия и оставлять посылки в требуемых местах.

Дрон выполнен по схеме смешанного конвертоплана и использует механизм с наклонным ротором.

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

В результате аппарат обладает преимуществами подобных моделей двух типов:

  1. Умеет плавно вертикально взлетать и садиться.
  2. За счет неподвижного крыла может парить в воздухе и быстро передвигаться на большие расстояния.

Чем полезны поворотные роторы? Они реагируют на резкие порывы ветра, адаптируют БПЛА к неблагоприятным погодным условиям.

В случае проблем предусмотрен перехват управления дроном оператором-человеком. Во избежание столкновения с другими аппаратами в воздухе коптер оснащен ресивером ADS-B и транспондером FLARM.

Что еще


Немецкий стартап занимается разработкой и совершенствованием коптеров с 2017 года. По словам генерального директора компании, Тома Плюммера, их следующая миссия создать сеть доставки дронами и развивать логистические магистрали в воздухе. Услуги могут быть использованы в следующих сферах: здравоохранение, e-commerce, доставка продуктов.

Wingcopter открыла прием заявок на первые 100 воздушных курьеров. В дальнейшем они планируют запустить серийное производство.

Сейчас компания получает лицензию от Федерального управления гражданской авиации США на выполнение коммерческих грузоперевозок.

Подробнее..

Категории

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

  • Имя: Макс
    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