Как мы в СберМаркете боремся с товарами-призраками

    Так могла бы выглядеть наша команда, но мы на удаленке
    Так могла бы выглядеть наша команда, но мы на удаленке

    В офлайне покупатель видит полки магазина и сам может понять, чего нет в наличии, а что можно положить в корзину и купить прямо сейчас. В онлайне«‎глазами»‎ пользователя становится каталог: он всегда должен быть актуальным. 

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

    Из-за отмены всем станет грустно. Есть шанс, что в такой ситуации покупатель и вовсе откажется от сервиса, а компания потеряет потенциальную выручку. Чтобы пользователи не расстраивались и оставались с нами дольше, мы ввели метрику под названием «‎доля ненайденных товаров»‎. Она показывает процент таких продуктов от общего числа заказов. 

    Рассказываем, как мы внедрили алгоритм автоматического отключения таких «‎призраков»‎ и уменьшили долю ненайденных товаров на 25%.

    Как работает СберМаркет

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

    К СберМаркету подключены сотни магазинов по всей России. В каждом — уникальный ассортимент из десятков тысяч товаров. В общем каталоге сервиса больше 16 млн позиций. На таких объемах контролировать актуальность и наличие тяжело.

    Чтобы улучшить сервис, нам предстояло не только сделать эффективный алгоритм, но и внедрить его в кратчайшие сроки. За время пандемии количество заказов выросло в 15 раз — нужно соответствовать.

    Идея алгоритма

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

    Исторически 70% таких артикулов имеют очень высокий показатель ненайденных товаров за последние дни продаж — более 50%. Значит, нужно срочно заблокировать позицию в каталоге, чтобы клиенты не смогли купить то, что мы не можем привезти. 

    Выше представлена динамика неуспешных сборок по товару из категории «‎фрукты»‎ в одном магазине. С 21 июня по 1 июля показатель ненайденных товаров был 100%. Хотя ретейлер передавал нам другие данные. 

    Клиенты могли спокойно покупать эту позицию, а потом получали звонки от сборщиков: «‎такого нет, можем заменить или отменить»‎. Возможно, товар имелся в наличии, но был незрелыми или испорченными.

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

    И начали писать его на Python.

    Как работает алгоритм

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

    2. Из базы данных наших заказов вытаскиваем историю последних сборок. Для каждого ненайденного товара берём историю THRESHOLD_ORDERS.

    3. Если в последних THRESHOLD_ORDERS заказах процент ненайденных товаров более THRESHOLD_CANCELLATION процентов, то эту позицию нужно заблокировать.

    4. Если товар ещё ни разу не блокировался или с момента последней блокировки уже прошло DAYS_NO_CANC дней, то товар блокируется на HIDE_1 дней.

    5. Если с момента последней блокировки товара прошло менее DAYSNOCANC дней, то:

       ~ товар блокируется на HIDE_2 дней, если текущая блокировка ставится второй раз подряд;
      ~ товар блокируется на HIDE_3 дней, если уже блокировался более двух раз подряд.

    Пример параметров алгоритма для магазина:

    'params': {
      'DAYS_NO_CANC': 4,
      'HIDE_1': 2,
      'HIDE_2': 11,
      'HIDE_3': 9,
      'THRESHOLD_CANCELLATION': 0.4,
      'THRESHOLD_ORDERS': 3
      }

    Параметры алгоритма подбирались отдельно для каждого магазина на исторических данных с использованием библиотеки hyperopt на Python. В процессе оптимизации максимизировалась метрика F-мера с beta 0.7.

    Зачем так усложнять

    Почему нельзя просто всегда блокировать товары на N дней? Зачем нам параметры DAYS_NO_CANC, HIDE_1, HIDE_2 и HIDE_3?

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

    Кейс №1: товары нужно блокировать на маленький срок

    У ретейлера A в магазине закончились бананы. Новую поставку смогут выложить в торговый зал через 1-2 дня. Бананы необходимо заблокировать на максимально маленький срок — после этого они точно будут доступны для клиентов и сборщиков.

    Кейс №2: товары нужно блокировать на большой срок

    У ретейлера B случился сбой в поставке яблочного сока. Возможно, новая партия доедет до магазина через 2-3 недели. Нет смысла блокировать товар на маленький срок, так как после такой блокировки клиенты все равно смогут заказать товар, но сборщик не сможет его собрать. 

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

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

    Параметр HIDE_3 нужен для того, чтобы сохранять длительные блокировки. Возможно, сок из второго кейса появится не через 2-3 недели, а через месяц. В этом случае после блокировки на HIDE_2 дней сок заблокируется на HIDE_3 дней. 

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

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

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

    Чего вам не хватает в сервисах доставки продуктов или у нас? Делитесь в комментариях — подумаем над решением.

    СберМаркет
    Кодим будущее доставки товаров

    Комментарии 10

      +2
      Чтобы пользователи не расстраивались и оставались с вами дольше, нужно было заниматься начинкой основного продукта.
        –2
        Мы знаем, что наш продукт сейчас неидеален, поэтому очень активно работаем над расширением команды и новыми функциями. В прошлом году нашим главным фокусом была стабильность — при росте х11 за год мы увеличили ит-ресурсы примерно в 20 раз. В этом году очень хотим сделать всё возможное, чтобы начинка не расстраивала наших клиентов
        +4
        Совсем не понятно каким образом можно было допустить двухнедельное отсутсвие фруктов в сервисе. Неужели не настроены мониторинги и алертинг на такие вещи для быстрого реагирования на подобного рода ситуации?
          –2
          здравствуйте! можете, пожалуйста, написать здесь или в лс, в каком магазине и городе вы столкнулись с данной проблемой?
          Мы доставляем продукты, которые есть на полках магазинов партнеров ритейлеров, поэтому данная проблема могла возникнуть в двух случаях: 1) задержка с поставками каких-то продуктов в конкретный магазин 2) мы не получали информацию об актуальных товаров (стоках) на полках по техническим причинам

          Хотим разобраться
            0
            Выше представлена динамика неуспешных сборок по товару из категории «‎фрукты»‎ в одном магазине. С 21 июня по 1 июля показатель ненайденных товаров был 100%.
              0
              В примере из статьи показано двухнедельное отсутсвие в магазине только одного товара из категории Фрукты (Пример: «Зелёные яблоки поставщика X, sku 12345»)
          +2
          А магазинам вы сигнализируете, что дескать «на эти позиции есть спрос и он не удовлетворен»?
            –2
            Привет! Магазины ритейлеров регулярно получают статистику по ненайденным товарам в торговом зале. И уже с помощью этой информации менеджеры в магазине делают вывод: «на эти позиции есть спрос и он не удовлетворен, необходимо увеличить предложение»
            +2

            Для всех товаров стоит показывать либо точное число в наличии, если возможно, либо хотя бы плашку "много/достаточно/мало" в цветах светофора, а для заканчивающихся товаров с определённого минимума (десяток или несколько штук) стоит писать "осталось столько-то" или хотя бы "осталось мало, скоро закончится". Это предупредит обиды и негатив от возможного отсутствия каких-то позиций уже на этапе сборки.
            Также для закончившихся товаров можно добавить кнопочку для сбора голосов желающих их приобрести, чтобы набирать статистку и завозить наиболее требуемое.

              0
              То что чего мало народ не будет заказывать.
              А вот если на это мало сделать еще скидку…

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

              Самое читаемое