Генерация транзакций для дропов¶
- ноутбуки лучше просматривать на Github pages, т.к. при просмотре прямо в репозитории могут быть проблемы с отображением, например, обрезка вывода с широкими датафреймами. Если в адресной строке браузера есть
iaroslav-dzh.github.io
, то вы уже на Github pages.
Ссылки:
Информация о ноутбуке
- в этом ноутбуке все что относится к генерации одной транзакции:
- генерация части данных транзакций
- сборка базовых классов дропов в один объект. Имеются в виду классы:
- управления счетами
- генерации сумм транзакций
- генерации времени транзакций
- управления поведением дропа
- генерации части данных транзакций
- полная генерация одной транзакции:
- для переводов, снятий
- для покупок
In [1]:
import pandas as pd
import numpy as np
import os
import pyarrow
import yaml
from typing import Union
In [2]:
from data_generator.general_time import *
from data_generator.utils import create_txns_df, load_configs, build_transaction
from data_generator.configs import DropDistributorCfg, DropPurchaserCfg
from data_generator.fraud.drops.build.config import DropConfigBuilder
In [3]:
np.set_printoptions(suppress=True)
pd.set_option('display.max_columns', None)
In [4]:
os.chdir("..")
os.getcwd()
Out[4]:
'C:\\Users\\iaros\\My_documents\\Education\\projects\\fraud_detection_01'
In [5]:
# Базовые конфиги
base_cfg = load_configs("./config/base.yaml")
# Настройки легальных транзакций
legit_cfg = load_configs("./config/legit.yaml")
# Общие настройки фрода
fraud_cfg = load_configs("./config/fraud.yaml")
# Настройки для дроп фрода
drops_cfg = load_configs("./config/drops.yaml")
# Настройки времени
time_cfg = load_configs("./config/time.yaml")
# Пути к файлам
data_paths = base_cfg["data_paths"]
Создание объектов конфиг классов¶
In [6]:
# директорию текущего запуска генератора возьмем из предыдущего ноутбука. Т.к. нужны данные того, что сгенерировано до дроп фрода
# предварительно удалены папки дропов dist_drops и purch_drops т.к. при создании объектов конфиг классов они будут созданы заново
run_dir = './data/generated/history/generation_run_2025-07-25_121029'
In [7]:
drop_cfg_build = DropConfigBuilder(base_cfg=base_cfg, legit_cfg=legit_cfg, fraud_cfg=fraud_cfg, drop_cfg=drops_cfg, \
time_cfg=time_cfg, run_dir=run_dir)
Объект конфиг класса DropDistributorCfg
конфиги для дропов распределителей
In [ ]:
dist_configs = drop_cfg_build.build_dist_cfg()
Объект конфиг класса DropPurchaserCfg
конфиги для дропов покупателей
In [8]:
purch_configs = drop_cfg_build.build_purch_cfg()
1. Класс DropTxnPartData
- генерация части данных транзакции дропов¶
- модуль
data_generator.fraud.txndata
. Ссылка на исходный код в Github
Основной функционал класса
- генерация части данных транзакции: мерчант id, координат, IP-адреса, города, ID устройства, канала транзакции, типа транзакции
- берет данные действительно относящиеся к клиенту в плане: координат, IP-адреса, города, ID устройства
In [27]:
from data_generator.fraud.drops.txndata import DropTxnPartData
# пример будет на конфигах дропа распределителя. Для DropTxnPartData это не будет иметь разницы
# т.к. данные, которые он берет из конфигов, по смыслу одинаковы для обоих типов дропов
part_data = DropTxnPartData(configs=dist_configs)
# DropTxnPartData нуждается в данных клиента в виде namedtuple
part_data.client_info = list(drop_clients.itertuples(name='Row', index=False))[0]
part_data.client_info
Out[27]:
Row(client_id=12092, birth_date=Timestamp('2002-05-09 00:00:00'), sex='male', region='Самарская', city='Самара', lat=53.1951657, lon=50.1067691, city_id=68, home_ip='2.60.20.113')
Демонстрация¶
Реальные данные текущего клиента (дропа)
In [28]:
client_id = part_data.client_info.client_id
drop_data = dist_configs.clients.query("client_id == @client_id")
drop_data
Out[28]:
client_id | birth_date | sex | region | city | lat | lon | city_id | home_ip | |
---|---|---|---|---|---|---|---|---|---|
0 | 12092 | 2002-05-09 | male | Самарская | Самара | 53.195166 | 50.106769 | 68 | 2.60.20.113 |
1. Метод original_purchase
¶
- один из двух основных методов класса
Пример №1
- дроп распределитель
- предыдущей транзакции нет
- метод
check_previous()
решает нужно ли взять частичные данные предыдущей транзакции - частичные данные предыдущей транзакции берутся если
- это дроп распределитель
- есть предыдущая транзакция и это была покупка криптовалюты
- частичные данные предыдущей транзакции берутся, чтобы был одинаковый merchant_id при покупке криптовалюты т.е. при переводе денег на криптобиржу
check_previous()
из переданной предыдущей транзакции узнает какой был канал транз-ции
In [36]:
get_cached = part_data.check_previous(dist=True, last_full=None)
# возвращает merchant_id, trans_lat, trans_lon, trans_ip, trans_city, device_id, channel, txn_type
merchant_id, trans_lat, trans_lon, trans_ip, trans_city, device_id, channel, txn_type = \
part_data.original_purchase(online=True, get_cached=get_cached)
# Соберем полученные данные в датафрейм для удобства демонстрации
txn_data_1 = pd.DataFrame([build_transaction(client_id=0, txn_time=0, txn_unix=0, amount=0, \
txn_type=txn_type, channel=channel, category_name=None, online=True, \
merchant_id=merchant_id, trans_city=trans_city, trans_lat=trans_lat, \
trans_lon=trans_lon, trans_ip=trans_ip, device_id=device_id, account=None, \
is_fraud=True, is_suspicious=False, status=None, rule=None)]) \
.drop(columns=['client_id', 'txn_time', 'unix_time', 'amount', 'category', \
'account', 'is_suspicious', 'status', 'rule'])
txn_data_1
Out[36]:
type | channel | online | merchant_id | trans_city | trans_lat | trans_lon | trans_ip | device_id | is_fraud | |
---|---|---|---|---|---|---|---|---|---|---|
0 | purchase | None | True | 6908 | Самара | 53.195166 | 50.106769 | 2.60.20.113 | 9420 | True |
Пример №2
- дроп распределитель
- есть предыдущая транзакция с каналом "crypto_exchange"
- в примере мы берем как-будто бы данные о последней транзакции, но достаточно передать только канал
- т.к. это дроп распределитель и канал это криптобиржа, то вернутся кэшированные данные предыдущей транзакции
- см. значение вывода выше и сравните с выводом ниже merchant_id повторяется т.к. вероятнее, что биржа будет та же самая
In [37]:
last_full = {"channel":"crypto_exchange"}
get_cached = part_data.check_previous(dist=True, last_full=last_full)
part_data.original_purchase(online=True, get_cached=get_cached)
merchant_id, trans_lat, trans_lon, trans_ip, trans_city, device_id, channel, txn_type = \
part_data.original_purchase(online=True, get_cached=get_cached)
# Соберем полученные данные в датафрейм для удобства демонстрации
txn_data_2 = pd.DataFrame([build_transaction(client_id=0, txn_time=0, txn_unix=0, amount=0, \
txn_type=txn_type, channel=channel, category_name=None, online=True, \
merchant_id=merchant_id, trans_city=trans_city, trans_lat=trans_lat, \
trans_lon=trans_lon, trans_ip=trans_ip, device_id=device_id, account=None, \
is_fraud=True, is_suspicious=False, status=None, rule=None)]) \
.drop(columns=['client_id', 'txn_time', 'unix_time', 'amount', 'category', \
'account', 'is_suspicious', 'status', 'rule'])
# соединим две записи в один датафрейм и увидим одинаковые данные
pd.concat([txn_data_1,txn_data_2], ignore_index=True)
Out[37]:
type | channel | online | merchant_id | trans_city | trans_lat | trans_lon | trans_ip | device_id | is_fraud | |
---|---|---|---|---|---|---|---|---|---|---|
0 | purchase | None | True | 6908 | Самара | 53.195166 | 50.106769 | 2.60.20.113 | 9420 | True |
1 | purchase | None | True | 6908 | Самара | 53.195166 | 50.106769 | 2.60.20.113 | 9420 | True |
2. Метод original_data
¶
- второй из двух основных методов класса
- генерирует частичные данные транз-ции для вх./исх. переводов и для снятий
Пример №1
- входящая транзакция. Флаги
online=True
,receive=True
In [44]:
online = True
merchant_id, trans_lat, trans_lon, trans_ip, trans_city, device_id, channel, txn_type = \
part_data.original_data(online=online, receive=True)
# Соберем полученные данные в датафрейм для удобства демонстрации
txn_data = pd.DataFrame([build_transaction(client_id=0, txn_time=0, txn_unix=0, amount=0, \
txn_type=txn_type, channel=channel, category_name=None, online=online, \
merchant_id=merchant_id, trans_city=trans_city, trans_lat=trans_lat, \
trans_lon=trans_lon, trans_ip=trans_ip, device_id=device_id, account=None, \
is_fraud=True, is_suspicious=False, status=None, rule=None)]) \
.drop(columns=['client_id', 'txn_time', 'unix_time', 'amount', 'category', \
'account', 'is_suspicious', 'status', 'rule'])
txn_data
Out[44]:
type | channel | online | merchant_id | trans_city | trans_lat | trans_lon | trans_ip | device_id | is_fraud | |
---|---|---|---|---|---|---|---|---|---|---|
0 | inbound | transfer | True | NaN | not applicable | NaN | NaN | not applicable | NaN | True |
Пример №2
- исходящая транзакция. Флаги
online=True
,receive=False
In [43]:
online = True
merchant_id, trans_lat, trans_lon, trans_ip, trans_city, device_id, channel, txn_type = \
part_data.original_data(online=online, receive=False)
# Соберем полученные данные в датафрейм для удобства демонстрации
txn_data = pd.DataFrame([build_transaction(client_id=0, txn_time=0, txn_unix=0, amount=0, \
txn_type=txn_type, channel=channel, category_name=None, online=online, \
merchant_id=merchant_id, trans_city=trans_city, trans_lat=trans_lat, \
trans_lon=trans_lon, trans_ip=trans_ip, device_id=device_id, account=None, \
is_fraud=True, is_suspicious=False, status=None, rule=None)]) \
.drop(columns=['client_id', 'txn_time', 'unix_time', 'amount', 'category', \
'account', 'is_suspicious', 'status', 'rule'])
txn_data
Out[43]:
type | channel | online | merchant_id | trans_city | trans_lat | trans_lon | trans_ip | device_id | is_fraud | |
---|---|---|---|---|---|---|---|---|---|---|
0 | outbound | transfer | True | NaN | Самара | 53.195166 | 50.106769 | 2.60.20.113 | 9420 | True |
Пример №3
- снятие. Флаги
online=False
,receive=False
In [42]:
online = False
merchant_id, trans_lat, trans_lon, trans_ip, trans_city, device_id, channel, txn_type = \
part_data.original_data(online=online, receive=False)
# Соберем полученные данные в датафрейм для удобства демонстрации
txn_data = pd.DataFrame([build_transaction(client_id=0, txn_time=0, txn_unix=0, amount=0, \
txn_type=txn_type, channel=channel, category_name=None, online=online, \
merchant_id=merchant_id, trans_city=trans_city, trans_lat=trans_lat, \
trans_lon=trans_lon, trans_ip=trans_ip, device_id=device_id, account=None, \
is_fraud=True, is_suspicious=False, status=None, rule=None)]) \
.drop(columns=['client_id', 'txn_time', 'unix_time', 'amount', 'category', \
'account', 'is_suspicious', 'status', 'rule'])
txn_data
Out[42]:
type | channel | online | merchant_id | trans_city | trans_lat | trans_lon | trans_ip | device_id | is_fraud | |
---|---|---|---|---|---|---|---|---|---|---|
0 | withdrawal | ATM | False | NaN | Самара | 53.195166 | 50.106769 | not applicable | NaN | True |
2. Класс DropBaseClasses
- создание и агрегация основных классов для дропов¶
- модуль
data_generator.fraud.drops.build.builder
. Ссылка на исходный код в Github
Основной функционал
- Создает объекты классов и держит их в своих атрибутах:
DropAccountHandler
- управление счетами транзакцийDropAmountHandler
- управление суммами транзакцийDropTimeHandler
- управление временем транзакций и его генерацияDistBehaviorHandler
илиPurchBehaviorHandler
- управление поведением дропаDropTxnPartData
- генерация частичных данных транзакции
- объект
DropBaseClasses
предназначен для удобной передачи основных дроп классов при создании объектов других более "высокоуровневых" классов, например:CreateDropTxn
- полная генерация одной транзакцииDropLifecycleManager
- управление созданием активности одного дропа от начала до конца
Основная логика
- принимает на вход конфиги дропов определенного типа и информацию о типе дропов в виде строки -
distributor
илиpurchaser
- через метод
build_all()
создает объекты указанных выше классов и записывает их в свои атрибуты - также есть отдельные методы на создание объекта каждого класса по отдельности, но используется только
build_all()
Демонстрация¶
In [9]:
from data_generator.fraud.drops.build.builder import DropBaseClasses
# Создадим объекты под каждый тип дропа
dist_base = DropBaseClasses(configs=dist_configs, drop_type="distributor")
purch_base = DropBaseClasses(configs=purch_configs, drop_type="purchaser")
Пример №1
- создать объекты под дропов распределителей
distributor
- разница будет в объекте класса, который управляет поведением, для распределителей это
DistBehaviorHandler
, а для покупателей этоPurchBehaviorHandler
In [46]:
# создаем все объекты
dist_base.build_all()
In [47]:
# смотрим типы
type(dist_base.behav_hand)
Out[47]:
data_generator.fraud.drops.behavior.DistBehaviorHandler
In [48]:
type(dist_base.amt_hand)
Out[48]:
data_generator.fraud.drops.base.DropAmountHandler
Пример №2
- создать объекты под дропов покупателей
purchaser
In [49]:
purch_base.build_all()
In [50]:
type(purch_base.behav_hand)
Out[50]:
data_generator.fraud.drops.behavior.PurchBehaviorHandler
3. Класс CreateDropTxn
¶
- модуль
data_generator.fraud.drops.txns
. Ссылка на исходный код в Github
Основной функционал
- создание всех типов транзакций для дропов обоих типов
- вх./исх переводы
- снятия
- покупки
- определение достигнут ли абсолютный лимит по вх./исх. транзакциям для дропа
Основная логика
- при создании объекта принимает объект конфиг класса
DropDistributorCfg
илиDropPurchaserCfg
и основные классы в виде объектаDropBaseClasses
- имеет два главных метода для полной генерации транзакций - эти методы принимают некоторые аргументы при их вызове
trf_or_atm()
- вх./исх. переводы либо снятиеpurchase
- покупки товаров либо криптовалюты
In [51]:
class CreateDropTxn:
"""
Создание транзакций дропа под разное поведение.
-----------------
drop_type: str. 'distributor' или 'purchaser'
configs: DropDistributorCfg | DropPurchaserCfg.
Конфиги и данные для создания дроп транзакций.
txn_part_data: DropTxnPartData. Генератор части данных транзакции - мерчант,
гео, ip, девайс и т.п.
amt_hand: DropAmountHandler. Генератор активности дропов: суммы, счета, баланс.
acc_hand: DropAccountHandler. Генератор номеров счетов входящих/исходящих транзакций.
Учет использованных счетов.
time_hand: DropTimeHandler. Управление временем транзакций дропа
behav_hand: DistBehaviorHandler. Управление поведением дропа.
categories: pd.DataFrame. Категории товаров с весами. Для дропов покупателей.
in_txns: int. Количество входящих транзакций.
out_txns: int. Количество исходящих транзакций.
in_lim: int. Лимит входящих транзакций. Транзакции клиента совершенные после
достижения этого лимита отклоняются.
out_lim: int. Лимит исходящих транзакций. Транзакции клиента совершенные
после достижения этого лимита отклоняются.
last_txn: dict. Полные данные последней транзакции. По умолчанию None
"""
def __init__(self, configs: Union[DropDistributorCfg, DropPurchaserCfg], base: DropBaseClasses):
"""
configs: DropDistributorCfg | DropPurchaserCfg.
Конфиги и данные для создания дроп транзакций.
base: DropBaseClasses. Объекты основных классов для дропов.
"""
self.drop_type = base.drop_type
self.configs = configs
self.txn_part_data = base.part_data
self.amt_hand = base.amt_hand
self.acc_hand = base.acc_hand
self.time_hand = base.time_hand
self.behav_hand = base.behav_hand
self.in_txns = 0
self.out_txns = 0
self.in_lim = configs.in_lim
self.out_lim = configs.out_lim
if isinstance(self.configs, DropPurchaserCfg):
self.categories = configs.categories
self.last_txn = None
def category_and_channel(self):
"""
Генерация категории и канала транзакции
---------------
"""
drop_type = self.drop_type
# Перевод на криптобиржу
if drop_type == "distributor":
channel = "crypto_exchange"
category_name = "balance_top_up"
return channel, category_name
assert drop_type == "purchaser", \
f"""'ecom' channel and categories sampling work only for self.drop_type as 'purchaser'.
But {self.drop_type} was passed"""
# Покупка в интернете
channel = "ecom"
category_name = self.categories.category \
.sample(1, weights=self.categories.weight).iat[0]
return channel, category_name
def status_and_rule(self, declined):
"""
Статус транзакции, флаг is_fraud и правило.
Зависит от типа дропа self.drop_type
-----------------
declined: bool. Будет ли текущая транзакция отклонена.
"""
drop_type = self.drop_type
if declined and drop_type == "distributor":
status = "declined"
is_fraud = True
rule = "drop_flow_cashout"
return status, is_fraud, rule
if not declined and drop_type == "distributor":
status = "approved"
is_fraud = False
rule = "not applicable"
return status, is_fraud, rule
if declined and drop_type == "purchaser":
status = "declined"
is_fraud = True
rule = "drop_purchaser"
return status, is_fraud, rule
if not declined and drop_type == "purchaser":
status = "approved"
is_fraud = False
rule = "not applicable"
return status, is_fraud, rule
def trf_or_atm(self, declined, to_drop, receive=False):
"""
Один входящий/исходящий перевод либо одно снятие в банкомате.
---------------------
dist: bool. Тип дропа. True - distributor. False - purchaser.
declined: bool. Будет ли текущая транзакция отклонена.
receive: bool. Входящий перевод или нет.
"""
client_id = self.txn_part_data.client_info.client_id # берем из namedtuple
# Время транзакции. Оно должно быть создано до увеличения счетчика self.in_txns
txn_time, txn_unix = self.time_hand.get_txn_time(receive=receive, in_txns=self.in_txns)
online = self.behav_hand.online
in_chunks = self.behav_hand.in_chunks
# перевод дропу
if receive:
self.in_txns += 1
amount = self.amt_hand.receive(declined=declined)
account = self.acc_hand.account
online = True # Тут отдельно прописываем т.к. это вне сценариев поведения самого дропа
# перевод от дропа
elif not receive and online:
self.out_txns += 1
account = self.acc_hand.get_account(to_drop=to_drop)
# снятие дропом
elif not receive and not online:
account = self.acc_hand.account
self.out_txns += 1
# Генерация части данных транзакции. Здесь прописываются аргументы online и receive
merchant_id, trans_lat, trans_lon, trans_ip, trans_city, device_id, channel, txn_type = \
self.txn_part_data.original_data(online=online, receive=receive)
# Генерация суммы если исходящая транзакция
# т.к. этот метод и для входящих транзакций
# а у входящих транзакций своя генерация суммы
if not receive:
amount = self.amt_hand.one_operation(online=online, declined=declined, in_chunks=in_chunks)
status, is_fraud, rule = self.status_and_rule(declined=declined)
# Статичные характеристики
is_suspicious = False
category_name="not applicable"
# Сборка всех данных в транзакцию и запись как последней транзакции
self.last_txn = build_transaction(client_id=client_id, txn_time=txn_time, txn_unix=txn_unix, amount=amount, \
txn_type=txn_type, channel=channel, category_name=category_name, online=online, \
merchant_id=merchant_id, trans_city=trans_city, trans_lat=trans_lat, \
trans_lon=trans_lon, trans_ip=trans_ip, device_id=device_id, account=account, \
is_fraud=is_fraud, is_suspicious=is_suspicious, status=status, rule=rule)
return self.last_txn
def purchase(self, declined):
"""
Покупка дропом. На данный момент для крипты.
--------------
declined: bool. Будет ли текущая транзакция отклонена.
dist: bool. Это дроп распределитель или покупатель.
У распределителя будет перевод на криптобиржу.
У покупателя - покупка в интернете.
"""
client_id = self.txn_part_data.client_info.client_id # берем из namedtuple
receive = False
# Время транзакции
txn_time, txn_unix = self.time_hand.get_txn_time(receive=receive, in_txns=self.in_txns)
online = self.behav_hand.online
# self.behav_hand.in_chunks_val() вызывается вовне. До захода в цикл while balance > 0
in_chunks = self.behav_hand.in_chunks
self.out_txns += 1
# Брать ли данные последней транзакции. Для случаев когда это дроп распределитель
dist = self.drop_type == "distributor"
get_cached = self.txn_part_data.check_previous(dist=dist, last_full=self.last_txn)
# Генерация части данных транзакции. Здесь прописывается аргумент online
# Вместо channel нижнее подчеркивание т.к. этот метод вернет None для channel
merchant_id, trans_lat, trans_lon, trans_ip, trans_city, device_id, _, txn_type = \
self.txn_part_data.original_purchase(online=online, get_cached=get_cached)
amount = self.amt_hand.one_operation(online=online, declined=declined, in_chunks=in_chunks)
channel, category_name = self.category_and_channel()
status, is_fraud, rule = self.status_and_rule(declined=declined)
# Статичные характеристики
is_suspicious = False
account = np.nan
# Сборка всех данных в транзакцию и запись как послдней транзакции
self.last_txn = build_transaction(client_id=client_id, txn_time=txn_time, txn_unix=txn_unix, amount=amount, \
txn_type=txn_type, channel=channel, category_name=category_name, online=online, \
merchant_id=merchant_id, trans_city=trans_city, trans_lat=trans_lat, \
trans_lon=trans_lon, trans_ip=trans_ip, device_id=device_id, account=account, \
is_fraud=is_fraud, is_suspicious=is_suspicious, status=status, rule=rule)
return self.last_txn
def limit_reached(self):
"""
Проверка достижения лимитов входящих и исходящих транзакций
Сверка с self.in_lim и self.out_lim
------------------------
Вернет True если какой либо лимит достигнут
"""
if self.in_lim <= self.in_txns:
return True
if self.out_lim <= self.out_txns:
return True
return False
def reset_cache(self, only_counters=False):
"""
Сброос кэшированных данных
-------------
only_counters: bool. Если True будут сброшены: self.in_txns, self.out_txns.
Если False то также сбросится информация
о последней транзакции self.last_txn
"""
self.in_txns = 0
self.out_txns = 0
if only_counters:
return
self.last_txn = None
Демонстрация¶
In [10]:
from data_generator.fraud.drops.txns import CreateDropTxn
Функция чисто для демонстрации
- Нужно сбрасывать кэши в объектах классов при каждой демонстрации, чтобы не было старых данных от предыдущих примеров
In [11]:
def reset_caches(cr_drop_txn, behav_hand, amt_hand, time_hand, part_data):
cr_drop_txn.reset_cache()
behav_hand.reset_cache(all=False)
amt_hand.reset_cache(all=True) # batch_txns здесь
time_hand.reset_cache()
part_data.reset_cache()
1. Дропы распределители¶
- разделим демонстрацию на две части т.к. нужно создавать отдельные объекты класса
DropBaseClasses
In [55]:
# Создаем объекты основных классов
dist_base = DropBaseClasses(configs=dist_configs, drop_type="distributor")
dist_base.build_all()
# Вынесем объекты созданных классов в отдельные переменные т.к. иногда нужно будет обращаться к ним
# для полноценной демонстрации
acc_hand = dist_base.acc_hand
amt_hand = dist_base.amt_hand
part_data = dist_base.part_data
time_hand = dist_base.time_hand
behav_hand = dist_base.behav_hand
# объект класса создания транзакций, который мы демонстрируем
create_txn = CreateDropTxn(configs=dist_configs, base=dist_base)
# возьмем одного клиента т.к. его данные нужно передать в объекты некоторых классов
# он и будет дропом
dist_client = list(dist_configs.clients.itertuples(name="Client", index=False))[0]
part_data.client_info = dist_client
acc_hand.client_id = dist_client.client_id
# запишем номер счета текущего клиента в атрибут acc_hand.account т.к. в некоторых транзакциях указывается счет
acc_hand.get_account(own=True)
# проверка данных
part_data.client_info
Out[55]:
Client(client_id=12092, birth_date=Timestamp('2002-05-09 00:00:00'), sex='male', region='Самарская', city='Самара', lat=53.1951657, lon=50.1067691, city_id=68, home_ip='2.60.20.113')
Метод trf_or_atm()
¶
- вх./исх. переводы, снятия
Пример №1
- входящий перевод. Тут логика общая и для распределителей и для покупателей
In [62]:
reset_caches(create_txn, behav_hand, amt_hand, time_hand, part_data)
# создание транзакци
receive_txn = create_txn.trf_or_atm(declined=False, to_drop=False, receive=True)
pd.DataFrame([receive_txn])
Out[62]:
client_id | txn_time | unix_time | amount | type | channel | category | online | merchant_id | trans_city | trans_lat | trans_lon | trans_ip | device_id | account | is_fraud | is_suspicious | status | rule | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 12092 | 2025-01-03 14:55:00 | 1735916100 | 17700.0 | inbound | transfer | not applicable | True | NaN | not applicable | NaN | NaN | not applicable | NaN | 15232 | False | False | approved | not applicable |
Баланс дропа повысился на сумму перевода
In [63]:
amt_hand.balance
Out[63]:
np.float64(17700.0)
Пример №2
- НЕ отклоненный исходящий перевод целиком
- пояснение по проставленным аргументам:
receive=False
- это не вх. транз.to_drop=False
- не пытаемся перевести другому дропуdeclined=False
- транзакция не будет отклонена
In [65]:
# это онлайн транзакция. Для исходящих транзакций/снятий этот флаг берется из атрибута behav_hand.online
behav_hand.online = True
# это транзакция на сумму всего баланса
behav_hand.in_chunks = False
# создаем транзакцию и объединяем записи о входящей созданной ранее транз. и о текущей
whole_out = create_txn.trf_or_atm(receive=False, to_drop=False, declined=False)
pd.concat([pd.DataFrame([receive_txn]), pd.DataFrame([whole_out])], ignore_index=True)
Out[65]:
client_id | txn_time | unix_time | amount | type | channel | category | online | merchant_id | trans_city | trans_lat | trans_lon | trans_ip | device_id | account | is_fraud | is_suspicious | status | rule | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 12092 | 2025-01-03 14:55:00 | 1735916100 | 17700.0 | inbound | transfer | not applicable | True | NaN | not applicable | NaN | NaN | not applicable | NaN | 15232 | False | False | approved | not applicable |
1 | 12092 | 2025-01-03 17:45:00 | 1735926300 | 17700.0 | outbound | transfer | not applicable | True | NaN | Самара | 53.195166 | 50.106769 | 2.60.20.113 | 9420.0 | 23028 | False | False | approved | not applicable |
Баланс дропа обнулен
In [66]:
amt_hand.balance
Out[66]:
np.float64(0.0)
Пример №3
- НЕ отклоненное снятие целиком
In [67]:
reset_caches(create_txn, behav_hand, amt_hand, time_hand, part_data)
# это оффлайн транзакция
behav_hand.online = False
behav_hand.in_chunks = False
receive_txn = create_txn.trf_or_atm(declined=False, to_drop=False, receive=True)
whole_atm = create_txn.trf_or_atm(declined=False, to_drop=False, receive=False)
pd.concat([pd.DataFrame([receive_txn]), pd.DataFrame([whole_atm])], ignore_index=True)
Out[67]:
client_id | txn_time | unix_time | amount | type | channel | category | online | merchant_id | trans_city | trans_lat | trans_lon | trans_ip | device_id | account | is_fraud | is_suspicious | status | rule | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 12092 | 2025-01-03 12:57:00 | 1735909020 | 16000.0 | inbound | transfer | not applicable | True | NaN | not applicable | NaN | NaN | not applicable | NaN | 15232 | False | False | approved | not applicable |
1 | 12092 | 2025-01-03 14:49:00 | 1735915740 | 16000.0 | withdrawal | ATM | not applicable | False | NaN | Самара | 53.195166 | 50.106769 | not applicable | NaN | 15232 | False | False | approved | not applicable |
Пример №4
- НЕ отклоненный исходящий перевод частями
Пояснение
- Тут уже нужно задать сценарий -
split_transfer
и применить методbehav_hand.guide_scenario()
, который контролируетbehav_hand.online
атрибут при генерации каждой транзакции в зависимости от выпавшего сценария.guide_scenario()
внедрялся из-за сценарияatm+transfer
, гдеbehav_hand.online
меняется после первой транзакции т.к. первая это снятие, а следущие это онлайн переводы
In [69]:
reset_caches(create_txn, behav_hand, amt_hand, time_hand, part_data)
# определяем сценарий
behav_hand.scen = "split_transfer"
# перевод частями
behav_hand.in_chunks = True
# входящая транз.
receive_txn = create_txn.trf_or_atm(declined=False, to_drop=False, receive=True)
all_txns = [receive_txn]
# дроп будет делать транзакции пока баланс не будет обнулен
while amt_hand.balance > 0:
behav_hand.guide_scenario()
part_out = create_txn.trf_or_atm(declined=False, to_drop=False, receive=False)
all_txns.append(part_out)
pd.DataFrame(all_txns)
Out[69]:
client_id | txn_time | unix_time | amount | type | channel | category | online | merchant_id | trans_city | trans_lat | trans_lon | trans_ip | device_id | account | is_fraud | is_suspicious | status | rule | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 12092 | 2025-01-04 14:57:00 | 1736002620 | 32700.0 | inbound | transfer | not applicable | True | NaN | not applicable | NaN | NaN | not applicable | NaN | 15232 | False | False | approved | not applicable |
1 | 12092 | 2025-01-04 17:11:00 | 1736010660 | 22000.0 | outbound | transfer | not applicable | True | NaN | Самара | 53.195166 | 50.106769 | 2.60.20.113 | 9420.0 | 17653 | False | False | approved | not applicable |
2 | 12092 | 2025-01-04 18:30:00 | 1736015400 | 9000.0 | outbound | transfer | not applicable | True | NaN | Самара | 53.195166 | 50.106769 | 2.60.20.113 | 9420.0 | 19017 | False | False | approved | not applicable |
3 | 12092 | 2025-01-04 19:38:00 | 1736019480 | 1700.0 | outbound | transfer | not applicable | True | NaN | Самара | 53.195166 | 50.106769 | 2.60.20.113 | 9419.0 | 17224 | False | False | approved | not applicable |
Баланс обнулен
In [70]:
amt_hand.balance
Out[70]:
np.float64(0.0)
Пример №5
- отклоненный перевод
- в данном случае сделаем пример на исходящих переводах
declined=True
- в итоговом датафрейме смотрите на
status
иrule
.drop_flow_cashout
значит что это детект по правилу, что клиент это дроп через которого идут потоки денег, это и есть дроп распределитель - баланс дропа не изменится. На нем будет переведенная ему сумма
In [71]:
reset_caches(create_txn, behav_hand, amt_hand, time_hand, part_data)
behav_hand.scen = "transfer"
behav_hand.in_chunks = False
receive_txn = create_txn.trf_or_atm(declined=False, to_drop=False, receive=True)
all_txns = [receive_txn]
i = 0
while amt_hand.balance > 0 and i < 4:
behav_hand.guide_scenario()
txn_out = create_txn.trf_or_atm(declined=True, to_drop=False, receive=False)
all_txns.append(txn_out)
i += 1
pd.DataFrame(all_txns)
Out[71]:
client_id | txn_time | unix_time | amount | type | channel | category | online | merchant_id | trans_city | trans_lat | trans_lon | trans_ip | device_id | account | is_fraud | is_suspicious | status | rule | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 12092 | 2025-01-07 08:41:00 | 1736239260 | 28300.0 | inbound | transfer | not applicable | True | NaN | not applicable | NaN | NaN | not applicable | NaN | 15232 | False | False | approved | not applicable |
1 | 12092 | 2025-01-07 11:02:00 | 1736247720 | 28300.0 | outbound | transfer | not applicable | True | NaN | Самара | 53.195166 | 50.106769 | 2.60.20.113 | 9420.0 | 23535 | True | False | declined | drop_flow_cashout |
2 | 12092 | 2025-01-07 13:46:00 | 1736257560 | 21300.0 | outbound | transfer | not applicable | True | NaN | Самара | 53.195166 | 50.106769 | 2.60.20.113 | 9419.0 | 24245 | True | False | declined | drop_flow_cashout |
3 | 12092 | 2025-01-07 15:35:00 | 1736264100 | 14300.0 | outbound | transfer | not applicable | True | NaN | Самара | 53.195166 | 50.106769 | 2.60.20.113 | 9419.0 | 16982 | True | False | declined | drop_flow_cashout |
4 | 12092 | 2025-01-07 16:53:00 | 1736268780 | 7300.0 | outbound | transfer | not applicable | True | NaN | Самара | 53.195166 | 50.106769 | 2.60.20.113 | 9419.0 | 20795 | True | False | declined | drop_flow_cashout |
Баланс дропа не изменился. Ему не дали перевести полученные деньги
In [72]:
amt_hand.balance
Out[72]:
np.float64(28300.0)
Метод CreateTxn.purchase()
¶
- напомню, что пока это демонстрация, как это работает у дропов распределителей
Пример
- НЕ отклоненная покупка крипты на все деньги
In [74]:
reset_caches(create_txn, behav_hand, amt_hand, time_hand, part_data)
acc_hand.reset_cache()
# покупка крипты проходит под сценарием transfer или split_transfer
behav_hand.scen = "transfer"
# покупка разом на всю сумму
behav_hand.in_chunks = False
# получение денег
receive_txn = create_txn.trf_or_atm(declined=False, to_drop=False, receive=True)
all_txns = [receive_txn]
behav_hand.guide_scenario()
whole_out = create_txn.purchase(declined=False)
all_txns.append(whole_out)
all_df = pd.DataFrame(all_txns)
all_df
Out[74]:
client_id | txn_time | unix_time | amount | type | channel | category | online | merchant_id | trans_city | trans_lat | trans_lon | trans_ip | device_id | account | is_fraud | is_suspicious | status | rule | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 12092 | 2025-01-09 13:45:00 | 1736430300 | 39700.0 | inbound | transfer | not applicable | True | NaN | not applicable | NaN | NaN | not applicable | NaN | 15232.0 | False | False | approved | not applicable |
1 | 12092 | 2025-01-09 15:17:00 | 1736435820 | 39700.0 | purchase | crypto_exchange | balance_top_up | True | 6846.0 | Самара | 53.195166 | 50.106769 | 2.60.20.113 | 9419.0 | NaN | False | False | approved | not applicable |
Баланс обнулен
In [75]:
amt_hand.balance
Out[75]:
np.float64(0.0)
1. Дроп покупатель¶
- у дропа покупателя будет демонстрация только метода
purchase()
т.к. методtrf_or_atm()
применяется только для входящих транзакций, это было продемонстрировано на примере дропов распределителей - создадим объекты нужных классов заново, но уже под дропов покупателей
In [12]:
# Создаем объекты основных классов. Теперь drop_type="purchaser"
# и purch_configs вместо dist_configs
purch_base = DropBaseClasses(configs=purch_configs, drop_type="purchaser")
purch_base.build_all()
# Вынесем объекты созданных классов в отдельные переменные т.к. иногда нужно будет обращаться к ним
# для полноценной демонстрации
acc_hand = purch_base.acc_hand
amt_hand = purch_base.amt_hand
part_data = purch_base.part_data
time_hand = purch_base.time_hand
behav_hand = purch_base.behav_hand
# объект класса создания транзакций, который мы демонстрируем
create_txn = CreateDropTxn(configs=purch_configs, base=purch_base)
# возьмем одного клиента т.к. его данные нужно передать в объекты некоторых классов
# он и будет дропом
purch_client = list(dist_configs.clients.itertuples(name="Client", index=False))[3]
part_data.client_info = purch_client
acc_hand.client_id = purch_client.client_id
# запишем номер счета текущего клиента в атрибут acc_hand.account т.к. в некоторых транзакциях указывается счет
acc_hand.get_account(own=True)
# проверка данных
part_data.client_info
Out[12]:
Client(client_id=662, birth_date=Timestamp('1970-08-01 00:00:00'), sex='male', region='Ростовская', city='Ростов-на-Дону', lat=47.2224364, lon=39.7187866, city_id=5, home_ip='2.60.2.119')
Метод purchase()
¶
Пример №1
- НЕ отклоненная покупка целиком
In [13]:
reset_caches(create_txn, behav_hand, amt_hand, time_hand, part_data)
all_txns = []
receive_txn = create_txn.trf_or_atm(declined=False, to_drop=False, receive=True)
all_txns.append(receive_txn)
behav_hand.in_chunks = False
while amt_hand.balance > 0:
txn_out = create_txn.purchase(declined=False)
all_txns.append(txn_out)
all_df = pd.DataFrame(all_txns)
all_df
Out[13]:
client_id | txn_time | unix_time | amount | type | channel | category | online | merchant_id | trans_city | trans_lat | trans_lon | trans_ip | device_id | account | is_fraud | is_suspicious | status | rule | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 662 | 2025-01-15 15:08:00 | 1736953680 | 25700.0 | inbound | transfer | not applicable | True | NaN | not applicable | NaN | NaN | not applicable | NaN | 10630.0 | False | False | approved | not applicable |
1 | 662 | 2025-01-15 15:52:00 | 1736956320 | 25700.0 | purchase | ecom | shopping_net | True | 6949.0 | Ростов-на-Дону | 47.222436 | 39.718787 | 2.60.2.119 | 1109.0 | NaN | False | False | approved | not applicable |
Пример №2
- НЕ отклоненная покупка частями
In [14]:
reset_caches(create_txn, behav_hand, amt_hand, time_hand, part_data)
all_txns = []
receive_txn = create_txn.trf_or_atm(declined=False, to_drop=False, receive=True)
all_txns.append(receive_txn)
behav_hand.in_chunks = True
while amt_hand.balance > 0:
txn_out = create_txn.purchase(declined=False)
all_txns.append(txn_out)
all_df = pd.DataFrame(all_txns)
all_df
Out[14]:
client_id | txn_time | unix_time | amount | type | channel | category | online | merchant_id | trans_city | trans_lat | trans_lon | trans_ip | device_id | account | is_fraud | is_suspicious | status | rule | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 662 | 2025-01-02 03:25:00 | 1735788300 | 50400.0 | inbound | transfer | not applicable | True | NaN | not applicable | NaN | NaN | not applicable | NaN | 10630.0 | False | False | approved | not applicable |
1 | 662 | 2025-01-02 04:57:00 | 1735793820 | 25000.0 | purchase | ecom | shopping_net | True | 6875.0 | Ростов-на-Дону | 47.222436 | 39.718787 | 2.60.2.119 | 1109.0 | NaN | False | False | approved | not applicable |
2 | 662 | 2025-01-02 07:38:00 | 1735803480 | 16000.0 | purchase | ecom | shopping_net | True | 6906.0 | Ростов-на-Дону | 47.222436 | 39.718787 | 2.60.2.119 | 1109.0 | NaN | False | False | approved | not applicable |
3 | 662 | 2025-01-02 10:06:00 | 1735812360 | 9400.0 | purchase | ecom | shopping_net | True | 6814.0 | Ростов-на-Дону | 47.222436 | 39.718787 | 2.60.2.119 | 1109.0 | NaN | False | False | approved | not applicable |
Пример №3
- отклоненная покупка
- обратите внимание на
rule
.drop_purchaser
, тут говорит само за себя, это дроп покупатель.
In [17]:
reset_caches(create_txn, behav_hand, amt_hand, time_hand, part_data)
all_txns = []
receive_txn = create_txn.trf_or_atm(declined=False, to_drop=False, receive=True)
all_txns.append(receive_txn)
behav_hand.online = True
behav_hand.in_chunks = False
i = 0
while amt_hand.balance > 0 and i < 4:
txn_out = create_txn.purchase(declined=True)
all_txns.append(txn_out)
i += 1
all_df = pd.DataFrame(all_txns)
all_df
Out[17]:
client_id | txn_time | unix_time | amount | type | channel | category | online | merchant_id | trans_city | trans_lat | trans_lon | trans_ip | device_id | account | is_fraud | is_suspicious | status | rule | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 662 | 2025-01-08 07:26:00 | 1736321160 | 54200.0 | inbound | transfer | not applicable | True | NaN | not applicable | NaN | NaN | not applicable | NaN | 10630.0 | False | False | approved | not applicable |
1 | 662 | 2025-01-08 08:24:00 | 1736324640 | 54200.0 | purchase | ecom | shopping_net | True | 6930.0 | Ростов-на-Дону | 47.222436 | 39.718787 | 2.60.2.119 | 1109.0 | NaN | True | False | declined | drop_purchaser |
2 | 662 | 2025-01-08 10:24:00 | 1736331840 | 40700.0 | purchase | ecom | shopping_net | True | 6879.0 | Ростов-на-Дону | 47.222436 | 39.718787 | 2.60.2.119 | 1109.0 | NaN | True | False | declined | drop_purchaser |
3 | 662 | 2025-01-08 12:11:00 | 1736338260 | 27200.0 | purchase | ecom | shopping_net | True | 6935.0 | Ростов-на-Дону | 47.222436 | 39.718787 | 2.60.2.119 | 1109.0 | NaN | True | False | declined | drop_purchaser |
4 | 662 | 2025-01-08 13:37:00 | 1736343420 | 13700.0 | purchase | ecom | misc_net | True | 6824.0 | Ростов-на-Дону | 47.222436 | 39.718787 | 2.60.2.119 | 1109.0 | NaN | True | False | declined | drop_purchaser |