Buenas tardes. Mi nombre es Nikita Bashun, trabajo como analista de datos en el grupo de empresas "Lucky". Mi historia será sobre cómo nosotros, con un equipo de tres personas, creamos desde cero un sistema antifraude para el servicio de reserva de viajes.

Introducción
El que una vez sabe engañar, engañará muchas veces.
Lope de Vega
El fraude en nuestro caso es una situación en la que un conductor engaña a una empresa. Fraude para conseguir dinero.
, , 25, Delphi. . , . , . …
— MVP, .
, :
- . ( «-» ), , . ;
- . — , ( , ). -, 200 .
- , — « »:
- , (, 0% );
- , -.

:
- — , , , ;
- - ( ) — , , ;
- — , , ,
.
- False Positive, False Negative . :
- , ( , !);
- .
- . ;
- . , — . , , , . - , … .

SQL- DWH, . . , , «»:
WHERE susp = 1 --
AND finished_orders >= 3 --
AND cancelled >= 3 -- ,
AND dist_fin_drivers <= 2 --
AND ok <= 2 -- 2-
, .
. « » . , ? -. , .

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 :
- ;
- .
:

( , ).
, :

— .
: , , .
.
, . , , — . «» .

FP- , .
— , , . .
« ». , , :
- ;
- ;
- ;
- — .
.
, . , , , , . - :

, , — .
Google Spreadsheets. . , :
- , «» ;
- API — ;
- ;
- , .
. , — :
- . - , - , - . , . ;
- . , , :
- - , ;
- - ;
- - , ;
- - .
, , . , «».
— , . — , . !
, ?
- . . , «» , . ;
- . , . , .
( ) 35%. — 25%. , — — , . : , , , . .

:
- 15 ;
- 6800 ;
- 500 .
Pero el éxito más importante es la reducción paulatina de los propios casos de sospecha en muchas ciudades. Después de todo, idealmente, no queremos atrapar más , no queremos nada que atrapar .
Conclusión
Intenté describir la funcionalidad y los principios principales del sistema antifraude, así como las dificultades que encontramos. Los planes incluyen: usar ML para optimizar la búsqueda, crear un sistema de monitoreo de sanciones (ahora está en una etapa inicial), mejorar la interfaz para gerentes, crear informes dinámicos, desarrollar nuevos patrones y mucho más.
Después de todo, solo estamos al comienzo del viaje.