Создание системы антифрода в такси с нуля

Добрый день. Меня зовут Никита Башун, работаю дата-аналитиком в группе компаний «Везёт». Мой рассказ будет о том, как мы командой из трёх человек с нуля создавали систему антифрода для сервиса заказа поездок.


image


Введение


Кто раз умеет обмануть, тот много раз еще обманет.
Лопе де Вега

Фрод в нашем случае — это ситуация, когда водитель обманывает компанию. Мошенничество с целью получения денег.


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


Постановка задачи и паттерны поведения


Первоначальная цель — создать MVP, который за минимум времени разработки даст максимальный результат.

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


  • Воровство комиссии. Водитель отменяет заказ (или просит «по-братски» отменить поездку пассажира), потом его выполняет, забирает у клиента наличку. В компанию не приходит ни копейки;
  • Фейковые заказы с целью получения бонусов. Бонусы — это маркетинговые инструменты, стимулирующие водителя брать непопулярные заказы (короткие и дешёвые, например). Водитель делает такой заказ сам себе или другу с левой сим-карты, потом проезжает 200 метров и получает незаслуженное поощрение.
  • Более мелкие паттерны, которые мы объединили в один — «Подозрительные водители»:
    • Водители, у которых отключена комиссия (скажем, менеджер города мог поставить 0% комиссии своему другу);
    • Водители, которые покупают безлимитный тариф и по одному аккаунту работают вдвоём-втроём.

image


Функции в нашей команде распределены так:


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

Сложности


  • Важность снижения как False Positive, так и False Negative ошибок. Человеческим языком:
    • Не хочется обвинять честного водителя, так как он нас покинет (и будет прав, чёрт возьми!);
    • Не хочется оставлять нарушителя безнаказанным.
  • Необходимость ручной проверки и человеческий фактор. На этом пункте мы ещё остановимся подробнее;
  • Водители. Да, сами водители — это сложность для аналитика. Любая задача, связанная с ними, намного тяжелее аналогичной задачи, связанной с пассажирами. Занимались мы как-то предсказанием оттока и тех, и других… Но это уже совсем другая история.

Процесс


image


Основную задачу выполняет SQL-запрос в DWH, который возвращает подозрительные поездки и водителей. Те должны обладать набором заранее рассчитанных признаков. Вот, например, как выглядит фильтрация по паттерну «Бонусы»:


WHERE susp = 1 -- Флаг на подозрительность
  AND finished_orders >= 3 -- Три и более УСПЕШНЫЕ поездки с одним водителем
  AND cancelled >= 3 -- Три и более водителя, с которыми у номера телефона были только ОТМЕНЫ
  AND dist_fin_drivers <= 2 -- Успешные поездки максимум с ДВУМЯ водителями
  AND ok <= 2 -- Не больше 2-х УСПЕШНЫХ поездок с другими водителями

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


Еще пример. В паттерне «Воровство комиссии» ключевую роль играют координаты водителя. Отменил заказ, но отметился на всём его протяжении? Ну-ну. Считаем расстояния, добавляем ещё несколько важных фильтров и выгружаем такие поездки.


image


Далее в работу вступает скрипт на python. Он оборачивает выгрузку в pandas, сохраняет ее в postgres, преобразует в нужный вид и выгружает на проверку в листы Google (о них мы еще поговорим подробнее). Часть скрипта, выгружающая поездки, запускается автоматически дважды в день с помощью Apache Airflow.


Рассмотрим работу с API на примере.
Считываем креды и коннектимся:


credentials = ServiceAccountCredentials.from_json_keyfile_dict(
    config.crd,
    ['https://www.googleapis.com/auth/spreadsheets',
     'https://www.googleapis.com/auth/drive'])

httpAuth = credentials.authorize(httplib2.Http())
service = googleapiclient.discovery.build('sheets', 'v4', http=httpAuth)
sheet = service.spreadsheets()

Добавляем данные на лист:


base_range = f'{city_name}!A{ss_row + 1}:Z{ss_row + reserved_rows}'
sheet.values().append(spreadsheetId=spreadsheetid,
                                 range=base_range,
                                 body={"values": df_pos.values.tolist()},
                                 valueInputOption='RAW').execute()

Забираем резолюции:


range_from_ss = f'{city_name}!A{ss_row}:S{ss_row + reserved_rows}'

data_from_ss = service.spreadsheets().values().get(
            spreadsheetId=spreadsheetid,
            range=range_from_ss).execute().get('values', [])

data_from_ss = pd.DataFrame(data_from_ss)
data_from_ss_cols = ['id', 'Резолюция', 'Комментарий']
data_from_ss = data_from_ss.loc[1:, data_from_ss_cols]

Заносим их в PG:


vls_ss = ','.join([f"""({', '.join([f(d[c]) for c in data_from_ss_cols])}
                    )""" for d in data_from_ss.to_dict('rows')])

sql_update = f"""
    WITH updated as (
        UPDATE fraud_billing
        SET resolution = tb.resolution,
            comment=tb.comment,
            dt = NOW()
        FROM (VALUES {vls_ss}) AS tb(fraud_billing_id, resolution, comment)
        WHERE fraud_billing.fraud_billing_id = CAST(tb.fraud_billing_id AS INTEGER)
            AND ((fraud_billing.resolution IS NULL AND tb.resolution IS NOT NULL)
                OR (fraud_billing.comment IS NULL AND tb.comment IS NOT NULL)
                OR (fraud_billing.comment IS NOT NULL AND tb.comment IS NOT NULL
                   AND fraud_billing.comment <> tb.comment)
                OR (fraud_billing.resolution IS NOT NULL AND tb.resolution IS NOT NULL
                    AND fraud_billing.resolution <> tb.resolution)
               )
        RETURNING {alias_cols_text_with_id}
        )
    INSERT INTO fraud_billing_history ({cols_text_with_id})
    SELECT {cols_text_with_id}
    FROM updated;
"""

crs_postgres.execute(sql_update)
con_postgres.commit()

В самой postgres для каждого паттерна реализовано две таблицы:


  • хранение записей о поездках и водителях;
  • история обновлений.

Логи скрипта:


image


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


Пример того, как выглядит работа менеджера с листом:


image


Иногда по всем признакам сразу видно — водитель фродил.


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


И так для каждого паттерна.


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


image


На картинке справа наша FP-ошибка будет равна нулю, но мы не поймаем многих мошенников.


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


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


  • предупреждают;
  • штрафуют;
  • урезают в правах;
  • блокируют — временно или навсегда.

На данном этапе мы лишь наблюдаем и логируем информацию.


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


image


Как видите, подобрать универсальные правила и для Новосибирска, и для Магнитогорска — нетривиальная задача.


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


  • Менеджеры городов хорошо в них разбираются, так как «эксельку» знают все;
  • У Гугла превосходный API — удобный и лёгкий;
  • Они бесплатны;
  • Они обладают всем необходимым функционалом, в том числе контролем версий.

Самая большая проблема


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


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

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


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

Развитие и первые результаты


Как мы совершенствуем алгоритмы, то есть снижаем ошибки?


  • Невыявленные случаи фрода. Здесь нам помогают кураторы и менеджеры городов. Бывает, что они самостоятельно «ловят» фродовые поездки и водителей, которые пропустили наши скрипты. Мы выясняем причину и дорабатываем код;
  • Ложные обвинения. Таких ошибок гораздо больше, плюс их мы можем посчитать. Здесь помогают комментарии менеджеров к поездкам, которые они признали честными.

В начале нашего пути ошибка по самому масштабному паттерну (воровство комиссии) в среднем составляла около 35%. Сейчас — меньше 25%. В то же время, по другому паттерну — бонусам — удалось не только свести ошибку к нулю, но и в десятки раз уменьшить количество таких случаев. Выдвинем гипотезу: водители поняли, что теперь за подобное наказывают, и решили, что риск не стоит свеч. И придумали другие схемы.


image


За первые месяцы работы удалось достичь следующих результатов:


  • 15 тысяч поездок признаны фродом;
  • 6800 водителей понесли наказание;
  • более 500 тысяч рублей вернулись в компанию только по воровству комиссий.

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


Заключение


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


В конце концов, мы лишь в начале пути.

AdBlock похитил этот баннер, но баннеры не зубы — отрастут

Подробнее
Реклама

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

    +1
    Спасибо за статью. Жаль только, что в табличке нет информации по фроду в самом Краснодаре. А как дела у вас обстоят с маленькими станицами и т.п.? Как-то агрегируете их или рассматриваете отдельно (или делаете что-то совсем другое)?
      0

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

      +2
      Признаки определяются статистически на основании исторических данных, а также путём консультаций с опытными управленцами на местах.

      Этр и есть самое интересное, а sql запрос по этим параметрам написать уже дело техники. К сожалению, эту часть вниманием обошли.

        +2

        Согласен, стоило уделить время данному вопросу. Расскажу вот на этом примере:


        Водители, которые покупают безлимитный тариф и по одному аккаунту работают вдвоём-втроём.

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


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

          +3
          А зачем вообще ловить этот случай?
          Я конечно далёк от темы, но мне самым логичным решением кажется изменить сам безлимитный тариф.
          Написать под звёздочкой не более N часов в сутки. Тем более это имеет смысл, чтобы водитель не переутомился.
        –2
        То есть вы работаете еще с тех пор, когда про мобильные приложения никто не слышал, а вопросами антифрода озаботились только в 2020 году? И то, все равно система завязана на людях и работает с довольно большой погрешностью?
          0
          Вопросами антифрода озаботились только в 2020 году

          Вопросами автоматизации поиска — да. К сожалению, на это были причины.


          И то, все равно система завязана на людях и работает с довольно большой погрешностью?

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

          +5
          А считается фродом когда водитель нажимает кнопку «я на месте» (чтобы подобрать пассажира), а сам ещё в двух кварталах о тебя? Насколько я понимаю, в расчёте на оплату ожидания. Тут убытки у компании косвенные — мне меньше хочется пользоваться услугами таких водителей.
            0

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

              +1
              Написал я как-то жалобу в поддержку на водителя остановившегося поперек тротуара при наличии тут же нормального парковочного места и не желавшего переставлять авто, с фотками его парковки и прочим. Так и не дождался ответа. Правда это была компания начинающаяся «Сити» и заканчивающаяся на «мобил», но дела никому нет, пока речь не идёт о деньгах.
              Фотка героя
              image
                +2
                Но вы же сами предоставляете водителю возможность жать эту кнопку заранее.
                У вас есть исторические данные и данные позиционирования. Я думаю, можно настроить антифрод, заложив пределы погрешности позиционирования и маркировать таких недобросовестных водителей.
                У самого часто так бывает, стою на перекрестке, водитель стоит на красном сигнале светафора и якобы уже ожидает меня, потом только через минуту+ подъезжает на точку.
              0
              Лет 5 назад работал в такси порядка 7 месяцев и главный вопрос, который был у меня это как компания защищает водителя от действий клиентов (вызовы нескольких машин, невыходы) и операторов (когда вас просят приехать за 70 км в деревню за «постоянным клиентом», а там никого, оказалось 2 поездки = постоянный). Ну и клиентов от действий водителей (обсчитывают по «допам»), корректируют сумму поездки и т.п.
                0

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

                  +1
                  на практике операторам и менеджерам плевать, т.к. в их понимании это потери только водителя (т.е. если водитель зажал 10 рублей комиссии то это компания потеряла, а если водитель приехал на заказ и его кинули, то компания теряет те же деньги, но из-за клиента, поэтому не страшно). Как итог водители обсчитывают компанию (как только понял что будет межгород — тут же сброс поездки), а некоторые ещё и клиентов. Фактически замкнутый круг который разорвать может только компания (в глобальном понимании), но вот менеджеры на местах не хотят.
                    0

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

                      –1
                      теоретически да, практически такие менеджеры повсеместно, особенно в небольших города. В их понимание работаешь в такси = таксист по жизни, т.е. быдло, подрезаешь на дороге, хамишь и обсчитываешь клиентов. У каждого менеджеры своя трактовка даже на техническое состояние машины, не говоря уже о том, что клиент не вышел.
                +1
                А как у вас дела обстоят с подменой местоположения? Сейчас даже не самые продвинутые владельцы смартфонов на андроиде легко освоили базовые принципы — ловля покемонов тоже помогла в самообразованиии некоторых индивидуумов.
                  0

                  Включенную mock location можно отследить. Если только не кастомная прошивка

                    0
                    Андроидный Mock Location фильтруется элементарно на уровне приложения.
                    Во только есть люди кто задают вопросы вида qna.habr.com/q/241909
                    и получают в том числе ответы. будет даже NMEA поток фальшивый.
                    А кто-то и в приват спрашивает и денег даже иногда предлагают (а когда спросишь зачем — честно признаются что в их отношении дискриминация аэропортом а для ее избежания — такси должно ждать прямо на ВПП а то заказов вкусных не будет).
                    0

                    Думаю, если водитель сильно заморочится, то он может нас обмануть кривым gps-трекингом. Но вот стоит ли оно того?

                      +1
                      Если комиссия приемлемая и справедливая, то может и не стоит. А если больше половины чистой прибыли с заказа — то конечно стоит. Другое дело, что технически грамотные люди долго в такси не задерживаются (наверное).
                        +1

                        Но технически грамотные люди могут предоставлять сервис для задерживающихся в такси за отдельную (но меньше комиссии) плату

                    0
                    Интересно, насколько в финансовом плане оправдывает себя разработка таких систем?
                    Т.е. если за несколько месяцев система принесла 500К в компанию, а компания потратила на разработку этой системы (для примера) 700К (3 месяца, 3 человека по, скажем, 80К зарплаты в месяц каждому)?
                      +2

                      Тут два момента.


                      1. Мы не можем посчитать точно сколько система принесла денег. 500к — это лишь один паттерн за короткий срок. А как посчитать точно, сколько мы сэкономили денег на том, что сейчас практически не случается воровства доплат и "нулевых комиссий"? Я обращал на это внимание:


                        мы не хотим ловить больше, мы хотим, чтобы ловить было нечего.

                      2. Даже если разработка системы стоила бы в 10 раз дороже, чем сумма, которую она вернула в компанию за первые месяцы, — работу стоило бы делать все равно. Фрод — это не только деньги, но и качество услуг, а также имидж компании в целом.


                      0

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


                      Не планируете сделать более прогрессивную морду?

                        0

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

                          0

                          Вроде бы для такой задачи несложно соорудить что-то на knockout.js или вообще взять стандартную админку в Django, нет?

                          0
                          можно использовать playbook
                        +1
                        Мультик «В стране невыученных уроков» смотрели все.

                        Правильно писать с использованием тире или двоеточия:
                        «Казнить нельзя: помиловать.»=«Казнить нельзя, потому что надо помиловать.»
                        «Казнить нельзя — помиловать.»=«Казнить нельзя, а надо бы помиловать.»
                          0

                          Зависит же от контекста. В каких-то случаях больше подойдет запятая. Например:


                          Суд вынес вердикт: казнить, нельзя помиловать.
                          0

                          Согласен. Но я решил быть верным классике!

                          • НЛО прилетело и опубликовало эту надпись здесь
                              0

                              Спасибо!

                              –2
                              Это все конечно хорошо, но общение с кучей водителей показывает, что эти схемы с «только короткие поездки» это единственное, что держит водителей у конкретного агрегатора. Если схемы прикрывают, то они просто меняют компанию на ту, где изобретается новый способ, потому что честно работать невыгодно.
                                +1

                                К счастью, по нашим данным это не так.

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

                                Чиновник сидит где-то далеко в кабинете и крутит циферками в модели. Циферки выдают подчинённые подразделения. Чем более формализовано взаимодействие с ними вне потока циферок, тем приятнее чиновнику. Честные попытки улучшить модель (написать комментарии, например) отвергаются. В результате на местах приходится по своему разумению сопрягать действительность с фантазиями. Например, «почему-то» не применяя санкций к водителям. Издалека может быть не видно, что в каком-то месте разница в сто рублей в день приведёт к тому, что нормальные водители уплывут, а на их место придётся набрать полных прохиндеев (некоторых из которых, вероятно, местный директор знает в лицо), и всё предприятие с бульканьем погрузится в жижу спустя какое-то время — но на это обработчикам циферок будет глубоко пофиг, поскольку их это не коснётся (пример взят из головы, все совпадения случайны).

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

                                  Мы втроём занимаемся антифродом. Наша задача — сводить к нулю мошеннические действия.


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

                                    –2
                                    Конечно. Но вся риторика очищения наших рядов от троцкистско-бухаринской фашистской сволочи… то есть «борьбы с мошенниками» такова, как будто ваша служба уже объемлет весь мир, и вы лично на краю ущелья сражаетесь с Дьяволом, чтобы под корень извести обман. Отсюда и цель «чтобы ловить больше было нечего», недостижимая без выведения новой породы людей (чувствуется, что системе не хватает для эффективной работы возможности поражения в правах или каких-нибудь генетических манипуляций). Всё очень показательно.

                                    Не вы первые, не вы последние: когда-то Французская Республика для абстракции сбора податей от местных различий ввела, в числе прочего, метрическую систему мер, а теперь весь мир пользуется. См. «Благими намерениями государства» (англ. Seeing Like a State) Джеймса Скотта.
                                  +1
                                  Вы проверяли свои результаты способом «тайный покупатель», возможно это даст более точные знания релевантности результатов? Например окажется, что ваша система не учитывает неизвестные вам способы фрода, или что вам добавятся новые условия в подозрении на фрод.
                                    0

                                    В этом нет особого смысла. Региональные менеджеры и директора городов знают свою сферу деятельности на 100%. Ваше предложение было бы прекрасным решением для нового игрока на рынке такси.

                                    0
                                    Почему вы просто модель не сделали? Какой-нибудь бустинг максимизировал бы ваши показатели на текущих фичах и разметке данных.
                                      +1

                                      Этим мы занимаемся сейчас. А на то время приоритетнее были другие элементы системы.

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

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