12. Saltar a contenido

12. Monitoreo de transacciones

Este microservicio tiene como objeto monitorear transacciones financieras informadas por una aplicación de terceros. El propósito de este servicio es poder detectar transacciones inusuales para, de forma inmediata, alertar a las partes interesadas de la entidad financiera.

El monitoreo se realiza mediante un sistema basado en reglas.

12.1 Reglas de monitoreo de transacciones

Las transacciones que son informadas por la entidad al servicio de monitoreo de Trak.e, son evaluadas mediante ciertas reglas configurables en el servicio, para la detección automática de operaciones inusuales con el fin de generar alertas. Estas alertas son, luego, estudiadas por analistas de la mencionada entidad para un mejor análisis y toma de decisiones.

Particularmente, Trak.e emplea un sistema basado en reglas de desarrollo propio. Un sistema basado en reglas es aquel que utiliza reglas definidas por humanos expertos en cierto dominio para realizar deducciones y efectuar acciones. Son denominados, también, simplemente como sistemas expertos: se los considera una forma de representar el conocimiento de personas expertas dentro de un sistema automático. Las reglas, por lo general, tienen la forma de sentencias if-then; es decir, son de la forma if A then B.

Cada regla activa se ejecuta una vez por cada transacción inyectada. Se permite agregar cualquier cantidad de reglas de monitoreo de legajo, pero a lo sumo 50 pueden estar activas al mismo tiempo.

12.2 Implementación

Las reglas siguen el esquema visto para la matriz de riesgo y el cálculo del perfil transaccional: reglas escritas en lenguaje Python.

12.2.1 Parámetros

Las variables disponibles son:

  • profile: Al evaluar la regla se pone en el contexto de ejecución la variable profile que contiene el legajo que se esta procesando actualmente. Todos los atributos detallados en la sección Legajos se encuentra disponible en este objeto.

  • transaction: Contiene la información de la transacción que se esta procesando actualmente. Todos los atributos informados por la entidad mediante la API de transacciones esta disponible.

  • hist_trxs: Si la regla incluye información sobre la transaccionalidad del legajo, se puede acceder al histórico de la transaccionalidad del legajo con este nombre. La variable es puesta en contexto de ejecución en formato de pandas.DataFrame, donde cada fila del DataFrame representa una transacción y las columnas del DataFrame son los atributos de las transacciones (utilizando guion bajo como separador en caso de ser atributos anidados). Es posible que el historial este vacío (sin filas ni columnas) si el cliente no registra transacciones.

12.2.2 Resultado de la regla

La regla debe definir una variable de nombre SHOULD_RAISE cuyo valor debe ser un valor de verdad que indica si debería generar una nueva alerta. Los valores posibles y su interpretación son:

  • True: Se evaluó la regla y se determinó un comportamiento inusual, por lo que se levantará una alerta en trak.e para que un analista de la entidad gestione y resuelva sobre la anomalía.
  • False: Se evaluó la regla y se determinó que no había comportamientos inusuales.
  • None: No se evaluó la regla. O bien no aplica, o falta algún valor critico para hacerlo.

Nota

Contexto: Además de los valores True, False y None utilizados para interpretar la evaluación de la regla, se devuelve el contexto de la evaluación, particularmente aquellas variables públicas definidas. Esto permite guardar valores calculados durante la ejecución de la regla y que los mismos estén disponibles al momento de ver los resultados.

Nota

Por cuestiones de seguridad, no es posible utilizar el lenguaje Python completo al momento de definir una regla, si no sólo un subconjunto del lenguaje. En particular, no está permitido realizar imports. Sin embargo, para comodidad del programador, se pueden utilizar algunos módulos, clases y funciones incluidas en Python.

Nota

Módulos, clases y funciones permitidos al momento de definir reglas:

  • Clase Decimal del módulo decimal: Esta clase permite manejar números decimales con precisión fija y arbitraria. Puede consultar la documentación de esta clase aquí.
  • Modulo pandas: Este módulo permite realizar análisis de datos de manera rápida, potente, flexible y simple. En particular es necesario para trabajar con el histórico de las transacciones. Notar que, al igual que en la mayoría de los ejemplos que se encuentran en la página, en las reglas uno se refiere a este modulo utilizando la abreviación pd. La documentación del módulo puede consultarse aquí.
  • Clase datetime del módulo datetime: Esta clase permite representar y trabajar flexiblemente con fechas y horas. Notar que en trak.e todas las fechas y horas se representan en milisegundos desde el Epoch, por lo que luego de trabajar las fechas se deben reconvertir a este formato antes de realizar comparaciones con los atrbiutos del legajo o de las transacciones. La documentación de esta clase puede encontrarse en aquí.
  • Clase timedelta del módulo datetime: Esta clase permite representar y trabajar con intervalos de tiempo. Cómo ejemplo, la diferencia entre dos objetos de tipo datetime sera un objeto de tipo timedelta. La documentación de la clase puede encontrarse aquí.
  • Función strptime: Esta función permite analizar gramaticalmente una fecha y hora en formato texto y devolver el objeto datetime que corresponde. La función recibe dos argumentos; la fecha y hora, y el string que describe el formato. Algunos ejemplos:
    >>> strptime("20-06-2021", "%d-%m-%Y")
    datetime.datetime(2021, 6, 20, 0, 0)
    >>> strptime("20-06-21", "%d-%m-%y")
    datetime.datetime(2021, 6, 20, 0, 0)
    >>> strptime("20-06-21, 20:08", "%d-%m-%y, %H:%M")
    datetime.datetime(2021, 6, 20, 20, 8)
    
  • Módulo json: Este modulo contiene funciones par trabajar cómodamente con objetos JSON. La documentación del módulo puede encontrarse aquí.
  • Módulo math: Contiene varias funciones matemáticas de utilidad. La documentación esta aquí.
  • Además pueden utilizarse algunas funciones de utilidad: max, min, sum, all, any, round, len, isinstance y range.
  • Además pueden utilizarse los constructores estándar str, int, float, list, tuple, dict, set y bool.
  • También se encuentran definidas en el contexto las excepciones estándar IndexError y KeyError, por lo que puede controlarlas en su código.

12.3 Ejemplos

12.3.1 Excede cantidad de transacciones

  • Descripción: Algo muy habitual es evaluar la cantidad de transacciones que realizo un cliente en un determinado período. Una regla básica para evaluar esto es la siguiente:
init = datetime.now().replace(hour=0, minute=0, second=0,
                              microsecond=0) - timedelta(days=30)
init_timestamp = int(init.timestamp()) * 1000
cant_trx = hist_trxs[(hist_trxs["timestamp"] >= init_timestamp) & (
    hist_trxs["side"] == transaction.side)].shape[0]
SHOULD_RAISE =  cant_trx >= 20

12.3.2 Excede monto fijo

  • Descripción:Esta regla evalúa si el total acumulado de transacciones excede un monto fijo predeterminado.
init = datetime.now().replace(hour=0, minute=0, second=0,
                              microsecond=0) - timedelta(days=30)
init_timestamp = int(init.timestamp()) * 1000

total_amount = hist_trxs[
    (hist_trxs["timestamp"] >= init_timestamp) & (
        hist_trxs["side"] == transaction.side)
].amount.sum().item()

SHOULD_RAISE = total_amount + transaction.amount >= 1e7

12.3.3 Excede perfil transaccional

  • Descripción: Esta regla evalúa las transacciones y verifica que, acumulando el monto con el resto de las operaciones del período, el legajo no exceda su perfil transaccional oportunamente asignado.
transactional_profile = profile["transactional_profile_amount"]
if not transactional_profile:
    SHOULD_RAISE = None

transactional_profile_period = 31536000000  # 365*24*60*60*1000

now = transaction["timestamp"]
from_ = now - transactional_profile_period

sum_amount_deposit = sum(hist_trxs[hist_trxs["side"] == "deposit"][hist_trxs["timestamp"] > from_]["amount"])
sum_amount_extraction = sum(hist_trxs[hist_trxs["side"] == "extraction"][hist_trxs["timestamp"] > from_]["amount"])

if transaction.side == "deposit":
    sum_amount_deposit += transaction.amount
elif transaction.side == "extraction":
    sum_amount_extraction += transaction.amount

if sum_amount_deposit + sum_amount_extraction > transactional_profile:
    SHOULD_RAISE = True
else:
    SHOULD_RAISE = False

12.3.4 Cambio repentino de perfil

  • Descripción: Esta regla evalúa las transacciones buscando anomalías en los montos operados por el cliente. Para ello compara el mes actual con su promedio histórico de depósitos y extracciones realizados.

  • Parámetros:

Parámetro Significado Tipo Valor por defecto
side La regla se puede configurar para trabajar sobre los depósitos o sobre las extracciones. Literal "deposit" o "extraction" "deposit"
profile_change_min_seniority Esta regla necesita que el legajo tenga cierta antigüedad, una historia de transaccionalidad en la entidad que permita determinar un promedio histórico confiable. La regla será evaluada sólo para aquellos legajos que hayan sido creados hace cierto tiempo. Entero, representando un tiempo en milisegundos. int(1.814e10) (210 días)
profile_change_lookback_period Este parámetro representa cuánto tiempo consideraremos para definir cuál es el comportamiento promedio. Entero, representando un tiempo en milisegundos. int(1.555e10) (180 días)
profile_change_min_threshold Este parámetro representa un monto mínimo de interés, y sirve para evitar tener un exceso de alertas por montos pequeños de dinero. Si el comportamiento del mes actual es menor a este valor la regla no será evaluada. Flotante, representando un monto de dinero. 350000
profile_change_tolerable_deviation Diccionario que representa, para PF/PJ y segun el nivel de riesgo, cuanto se permite que se desvie la transaccionalidad respecto al comportamiento promedio. Diccionario de diccionario de flotantes, representando un cambio porcentual. * 0.6 para PJ de riesgo bajo
*0.4 para PJ de risgo medio
*0.25 para PJ de riesgo alto
*0.8 para PF de riesgo bajo
*0.6 para PF de riesgo medio
*0.4 para PF de riesgo alto
# allowed sides: "deposit"/"extraction"
side = "deposit"

# profile_change_min_seniority: Profile newer than this will not trigger the
# rule. Defaults to  210 days i.e. 7 months
profile_change_min_seniority: int = int(1.814e10)

# profile_change_lookback_period. How long will the rule look back in order to
# define a usual behavior. Defaults to 180 days, i.e. 6 months
profile_change_lookback_period: int = int(1.555e10)

# Minimum amount of money that will trigger the rule.If current month behavior
# is less than this threshold, the rule is not applied.
profile_change_min_threshold: float = 350000

# threshold for each person type and risk value. These are floating point
# numbers between 0 and 1 representing a percentage that the profile might
# overcome each month.
tolerable_deviation = dict(
    legal_person=dict(
        low=0.6,
        medium=0.4,
        high=0.25,
    ),
    natural_person=dict(
        low=0.8,
        medium=0.6,
        high=0.4,
    ),
)

timestamp = transaction.timestamp
created_at = profile.created_at
person_type = profile.person_type
risk = profile.risk


if not timestamp or not created_at or not person_type or not risk:
    SHOULD_RAISE = None
elif transaction.side != side:
    SHOULD_RAISE = None
elif timestamp - created_at < profile_change_min_seniority:
    # Not enough seniority to consider this profile
    SHOULD_RAISE = None
else:
    trx_now = datetime.fromtimestamp(timestamp // 1000)
    period_end = int(trx_now.replace(day=1, hour=0, minute=0, second=0, microsecond=0).timestamp() * 1000)
    period_init = period_end - profile_change_lookback_period
    one_month = int(2.592e9)

    this_month_behavior = (
        hist_trxs.loc[(hist_trxs["timestamp"] >= period_end) & (hist_trxs["side"] == side)].amount.sum()
        + transaction["amount"]
    )

    if this_month_behavior < profile_change_min_threshold:
        SHOULD_RAISE = None
    else:

        average_behavior = (
            hist_trxs.loc[
                (period_init <= hist_trxs["timestamp"])
                & (hist_trxs["timestamp"] < period_end)
                & (hist_trxs["side"] == side)
            ].amount.sum()
            * one_month
            / profile_change_lookback_period
        )

        deviation = (this_month_behavior - average_behavior) / this_month_behavior
        SHOULD_RAISE = bool(deviation > tolerable_deviation[person_type][risk])
Recurso
transaction_transaction
transaction_rule
transaction_summary

12.4 Documentación API

API