Главная » Хабрахабр » Насколько легко доставить заказ, зная адрес клиента (не очень)

Насколько легко доставить заказ, зная адрес клиента (не очень)

Меня зовут Денис Гирько, я системный архитектор e-commerce платформы в Lamoda. Всем привет! В прошлом году я выступал на конференции DevConf с докладом, которым хочу поделиться с вами.

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

image

Расскажу: О чем пойдет речь?

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

Общая схема доставки заказа Lamoda

Мы доставляем товары уже на следующий день за счет того, что у нас есть собственная служба доставки и десяток сторонних партнеров, услугами которых мы пользуемся. Lamoda — это интернет-магазин, у которого четыре страны доставки: Россия, Украина, Казахстан, Белоруссия. Доставка — крупная часть нашего бизнеса.

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

image

Тогда добавляется следующий шаг — определить, какой службой доставки мы повезем заказ. Что делать, если у нас не одна курьерская служба, а несколько?

image

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

Общая схема усовершенствуется и будет выглядеть так:

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

image

Теперь чуть подробнее об этих шагах.

Спрашиваем у клиента адрес

Как можно его спросить?

  1. Попросить заполнить одно большое поле. Клиент забивает в него свой адрес, с которым далее не нужны никакие хитрые манипуляции. Адрес можно распечатать на листочке, отдать пешему курьеру, который дальше сам во всем разберется.
  2. Второй вариант посложнее. Просим клиента заполнить каждый компонент адреса в своем поле. Здесь уже можно кое-что сделать. Например, город Москва сравнить с заданным перечнем городов. Но сработает это плохо, потому что город Москва можно написать разными способами: “г. Москва”, “город Москва”, “городМосква” без пробела и так далее.
  3. Поэтому есть еще более усовершенствованный вариант. Так как список городов у нас конечный, то можно заранее составить их список и предложить клиенту выбрать нужный. Бонус в том, что каждому элементу такого списка мы можем уже здесь сопоставить какой-то идентификатор. Мы, разработчики, любим работать не со строками, а с идентификаторами, которые можно использовать во всех наших системах как эквивалент выбранного города. У меня на слайде в качестве идентификатора индекс центрального почтового отделения.
    image

Какой службой доставки везем?

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

Я заранее скажу, что Lamoda на старте именно так и работала: результатом выбора клиентом города был индекс, и у нас индексы хранились в базе. У индексов есть свои плюсы и минусы. Как я сказал, индекс – это вещь всем понятная. Почему плюс? Он может получить от курьерской компании города, как-то преобразовать их в индексы и использовать. Любой менеджер, который только пришел работать, знает, что такое индекс. А рядом расположенные населенные пункты могут делить между собой один и тот же индекс. Минус в том, что индекс — это идентификатор почтового отделения «Почты России».

Почему перестало хватать индексов?

Рядом расположена деревня Марусино. Простой пример: Люберцы. Если мы захотели добавить доставку в Люберцы, но не доставлять в Марусино, потому что это, возможно, нам финансово не выгодно, мы бы не смогли это сделать только индексом.
image У Марусино нет своего почтового отделения, их корреспонденция приходит на одно из почтовых отделений Люберец.

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

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

Все просто: попадает точка в полигон — есть доставка, не попадает — нет.
image
Интерфейс создания зоны с помощью полигона Предусмотрели интерфейс в своей программе настройки для логистов, который позволяет поверх карты нарисовать полигон.

В интерфейсе отображается карта, на которой отмечены заказы клиентов. Бонусом того, что мы имеем геокоординаты на каждый заказ, стала возможность усовершенствовать интерфейс, которым пользуются логисты для составления маршрутов для торговых представителей. Далее этот маршрут достается одному торговому представителю, то есть человеку не нужно в течение дня ехать из одного конца города в другой конец, чтобы отвести все свои заказы – они все территориально близки.
image
Интерфейс маршрутизации Логист использует инструмент “лассо”, который объединяет рядом расположенные заказы в один маршрут.

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

И сделали саджест не только для города, но и для улицы, и даже для номера дома. Дело в том, что клиенты часто не следуют сценариям, которые мы для них предусматриваем, поэтому мы обзавелись адресными базами для каждой из 4 стран, в которые доставляем заказы. Чтобы составить список домов, мы распарсили открытые данные openstreetmap.org.
image
Форма оформления заказа предлагает подсказки, чтобы формализовать адресные данные

Адресная база

Откуда мы достали все адресные базы для наших четырёх стран? Чтобы делать саджест по адресной базе, ее нужно у себя хранить. Она довольно полная, хотя и не без огрехов. В России – это ФИАС, адресная база, которая составляется и ведется нашей налоговой службой. С другими странами нам помогли наши партнеры по доставке.
image

Почему в том же виде? У нас есть еще набор PHP-скриптов, которые берут тот формат, с которым нам приходит адресная база, и примерно в том же виде складывает ее в PostgreSQL. Это значит, что если бы мы предусматривали конвертацию, ее нужно было повторять при каждом обновлении.Таким образом, данные попадают в PostgreSQL, а оттуда они конвертируются и складываются в Apache Solr; Solr позволяет быстро по ним искать и делать саджест. Потому что одной из задач является периодическое обновление этих баз из тех же самых источников. Небольшой веб-сервер на PHP умеет строить запросы в Solr, по их результатам клиенту на сайте формируется список для саджеста.

Живой пример

image

image

То есть с тем же набором полей, с теми же типами столбцов и прочим. Мы загружаем данные из первоисточников примерно в том же виде, в котором они к нам поступили. Мы пытались изначально использовать данные именно в таком виде, и чтобы преобразовывать их в те структуры, с которыми можно работать, написали несколько представлений (views). Складываем их, как есть. Поэтому надо было с этим что-то сделать. Так как у нас 4 страны, то все это умножалось на 4, и это было очень сложно и дорого в поддержке.

То есть, как только загрузили сырые данные, с помощью views преобразовываем их в унифицированный формат, с которым дальше настроены все наши остальные преобразования. Первое, от чего мы избавились — это от неструктурированности, а точнее, от специфичной структурированности, на самом раннем этапе. И именно в этот момент мы забываем о той структуре, в которой к нам данные попали, и работаем только с тем, что сами себе придумали.
image Это нас избавило от умножения на 4.

Главное, чтобы формат у этих данных на выходе после преобразования во views был одинаковый.
image Если надо два источника – пожалуйста, загружаем.

Простой пример: в ФИАС-е Чувашская республика называется «Чувашская респ. Еще одним требованием к загруженным адресным базам стало то, что нужно вносить в нее точечные исправления. Ну а мы хотим, чтобы была просто Чувашская республика. — Чувашия». И при этом периодического обновления из источников нам все равно не избежать. Зачем нам это тире?

Вот следующие слои, которые расположены у нас в PostgreSQL.
image

Таблицы слева — это сырые данные, загруженные из первоисточников.

За ними views, которые преобразовывают данные в стандартный формат.

Сюда мы внесли, например, что запись с таким-то идентификатором должна получить вместо “Чувашской респ. Local overridings – это у нас набор таблиц, которые точечно переопределяют какие-то атрибуты у загруженных адресных данных. — Чувашии” наше выбранное имя.

Все это вместе объединяется и фиксируется в материализованном представлении (materialized view). Mapping table – это наше хранилище идентификаторов, которые мы сами назначили тем адресным объектам, которые загрузили – это позволило абстрагироваться нашим системам от источника, от тех идентификаторов, которые используется в источнике, а также спрятать за одним ID не один источник, а даже несколько – я расскажу чуть позже. Таким образом, у нас получается практически эквивалент финальной таблицы, который можно обновить запуском одной SQL-команды REFRESH MATERIALIZED VIEW.

Address objects — сформированная адресная база со всеми исправлениями и дополнениями.

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

Где может пригодиться поиск? Так как у нас теперь есть адресные базы, классно бы по ним не только делать саджест для формы оформления заказа, но и делать поиск. Те же самые зоны доставки, которые мы получаем от курьерских служб, очень часто представлены просто списком городов. Оказывается, много где. А список городов таит в себе те же самые проблемы, как и с вводом пользователя: города могут иметь разночтения, разные названия и прочее.

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

image

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

Мы классифицировали эти проблемы.

Например, такие распространенные синонимы, как Чувашия и Чувашская Республика. 1) Равнозначные названия одних и тех же объектов.

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

Часто ошибаются в статусе населенных пунктов. 3) Много и ошибок. Там деревня, здесь село или здесь село, там хутор.

4) Транслитерированные иностранные слова на русский, часто одно и то же название транслитерируют по-разному.

Правильно писать «Город Москва, Зеленоград». 5) Много ошибок в иерархии: Зеленоград по привычке относится к Московской области, хотя формально и в ФИАСе он числится за Москвой.

Как мы придумали с этим бороться?

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

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

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

Исправляем опечатки и исправляем раскладку.

Они имеют значение при поиске, но не участвуют в выдаче результата поиска. Наконец, мы придумали фантомных родителей – это родители, назначенные объектам. Теперь можно искать “Московская область, Зеленоград” и найти нужный нам объект, но в выдаче он все равно будет правильным “Москва, Зеленоград”. Например, для Зеленограда мы добавили Московскую область.

image

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

И где мы нашли такому поиску применение?

  • Мы еще раз прогоняем адрес, введенный клиентом, через такой поиск. Если он не использовал наши подсказки на странице оформления заказа, то у нас есть еще один шанс превратить строчки, которые он ввел, в идентификаторы. У нас получается формализованный адрес.
  • Мы прогоняем через этот поиск все, что нам присылают курьерские службы — распознаем те города, которые они нам передают. Это позволило нам буквально по 10 штук в день запускать, это актуально для B2B — Lamoda предоставляет свою доставку сторонним компаниям, поэтому там очень много новых курьерских служб подключается в единицу времени.
  • Это позволило нам “нанизать” на наши идентификаторы в адресных базах разные полезные сведения. Например, мы загрузили себе часовые пояса, IP-адреса, чтобы искать города по IP-адресам клиентов.
  • У нас появилась возможность за одним нашим идентификатором скрыть объединенную из двух источников адресную базу. То есть, позволило избежать дубликатов и сопоставить одинаковые адресные объекты в обеих базах.

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

То есть наши идентификаторы – индексы, о минусах которых мы знаем. Во-первых, Lamoda работает на индексах. Плюс в том, что проверка попадания города в территорию настолько же простая, как с индексами. Уже почти все наши системы перешли на новый API, они оперируют не индексами, а теми самыми идентификаторами, которые мы сами назначили нашим адресным объектам. Однако нет минуса в том, что за одним ID могут крыться несколько населенных пунктов.

В остальных случаях индексы были вытеснены нашими внутренними адресными идентификаторами. Дополнение: Прошло время с момента моего выступления, и теперь я рад поправить себя: индексы у нас сейчас остались только в кейсе, когда курьерка нам передает свою территорию в виде их списка, например «Почта России».

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

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

Переписали на Lua код, который обращался в Solr. Мы избавляемся от РНР в цепочке поиска по адресным данным. 95% ответов нашего сервиса поиска укладывается в 10 миллисекунд, что нас более, чем устраивает. Заменили nginx на Openresty, теперь все очень быстро и выдерживает большую нагрузку.

image

Но с тех пор Lamoda приняла в качестве одного из языков программирования для нагруженного бекенда Golang, который обладает теми же качествами. Дополнение: Использование Openresty и Lua, которые привлекли своей производительностью — это был своего рода эксперимент, который себя оправдал: сервис работает быстро, стабилен под нагрузкой и легко поддерживается. Если бы решение о разработке сервиса принималось сейчас, мы бы отдали предпочтение ему.

Вывод

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


Оставить комментарий

Ваш email нигде не будет показан
Обязательные для заполнения поля помечены *

*

x

Ещё Hi-Tech Интересное!

Слушаем SID-музыку через OPL3 на современных ПК

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

Пользователь в Docker

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