caso de uso: Vista única
Industrias: Servicios financieros
Productos: MongoDB Atlas
Descripción general de la solución
Los delincuentes financieros operan en redes. El lavado de dinero, las redes de fraude y la evasión de sanciones se basan en intrincadas redes de empresas fantasma, directores testaferros y complejas rutas de transacciones. Los equipos de cumplimiento normativo deben mapear y analizar la red de relaciones que rodea a una entidad sospechosa.
Los enfoques tradicionales se topan con dos obstáculos:
Consultas costosas: Las bases de datos relacionales utilizan expresiones de tabla comunes (CTE) recursivas. Estas consultas se vuelven costosas a partir de dos o tres saltos.
Viajes de ida y vuelta constantes en la red: el procesamiento de gráficos del lado del cliente extrae datos a la capa de aplicación. El cliente ejecuta una nueva consulta por cada salto.
En esta solución, usted:
Construya un motor de análisis de redes que utilice operadores de MongoDB para evitar estos cuellos de botella.
Ejecute recorridos de grafos de múltiples saltos, detección de rutas más cortas, puntuación de centralidad, detección de comunidades y propagación de riesgos directamente dentro del marco de agregación de MongoDB.
Esta solución se centra en el motor de análisis de red y los operadores de MongoDB que lo impulsan. Para implementar la arquitectura general con MongoDB Atlas, FastAPI y Next.js, siga también la Biblioteca de soluciones anterior, Mitigación de delitos financieros.
¿Por qué MongoDB en lugar de una base de datos de grafos dedicada?
Almacena entidades y relaciones en MongoDB junto con índices de búsqueda, incrustaciones vectoriales y flujos de cambios.
Evite sincronizar datos entre dos sistemas, gestionar infraestructura adicional y sufrir latencia entre sistemas debido a que mantiene las cargas de trabajo de gráficos en MongoDB.
Utilice MongoDB
$graphLookupEl operador ejecuta una búsqueda en amplitud con consultas indexadas estándar en cada salto, por lo que el rendimiento depende de los índices en lugar de un motor de gráficos independiente.Para los recorridos de 1a5salto comunes en las investigaciones de cumplimiento, este enfoque coincide con las bases de datos de grafos dedicadas al consultar los mismos datos que su aplicación ya lee y escribe.
Nota
En las investigaciones de cumplimiento normativo, normalmente se rastrean algunos pasos a partir de un sujeto. Las contrapartes inmediatas y los intermediarios de primera capa se encuentran a 1–2 saltos de distancia, mientras que las estructuras fantasma y las rutas de movimiento de dinero suelen estar a 3–5 saltos. Más allá de eso, los vínculos se vuelven menos significativos y más difíciles de explicar, por lo que la mayoría de las investigaciones prácticas se mantienen en este rango de 1–5saltos.
Arquitecturas de Referencia
El motor de análisis de red se sitúa entre la capa de servicio FastAPI y MongoDB Atlas, y procesa las operaciones de grafos a través del proceso de agregación.
Figura 1. Proceso de investigación de agentes
Dual $graphLookup Patrón
Las redes de lavado de dinero implican flujos bidireccionales. Una entidad puede obtener fondos a través de una relación y recibirlos a través de otra.
Un único $graphLookup recorre las aristas en una sola dirección. Esta solución ejecuta dos búsquedas paralelas —hacia adelante y hacia atrás— y combina los resultados en un grafo de red unificado.
pipeline = [ {"$match": {"entityId": center_entity_id}}, # Forward traversal: follow source → target edges {"$graphLookup": { "from": "relationships", "startWith": "$entityId", "connectFromField": "target.entityId", "connectToField": "source.entityId", "as": "forward_relationships", "maxDepth": max_depth - 1, "restrictSearchWithMatch": { "active": True, "confidence": {"$gte": min_confidence} } }}, # Reverse traversal: follow target → source edges {"$graphLookup": { "from": "relationships", "startWith": "$entityId", "connectFromField": "source.entityId", "connectToField": "target.entityId", "as": "reverse_relationships", "maxDepth": max_depth - 1, "restrictSearchWithMatch": { "active": True, "confidence": {"$gte": min_confidence} } }}, # Merge both directions {"$project": { "entityId": 1, "all_relationships": { "$concatArrays": [ "$forward_relationships", "$reverse_relationships" ] } }}, {"$unwind": "$all_relationships"}, {"$replaceRoot": {"newRoot": "$all_relationships"}}, {"$limit": max_relationships} ]
Figura 2. Búsqueda de grafos dual: descubrimiento de red bidireccional
La aplicación recibe el grafo de red completo en un solo viaje de ida y vuelta. Ambos recorridos se ejecutan en una única canalización de agregación, lo que elimina el problema de las consultas N+1 que implica realizar una consulta de seguimiento independiente para cada documento devuelto en la consulta inicial.
Cómo $graphLookup Recorre el grafo
$graphLookup Realiza una búsqueda en amplitud (BFS) en ondas discretas:
Semilla: Evaluar
startWithcontra el documento de entrada. Los valores de la matriz inicializan la frontera simultáneamente (BFS de múltiples raíces).Consulta: Construye,
{ connectToField: { $in: [frontier_values] }}fusionado con cualquierrestrictSearchWithMatchfiltro. Ejecuta como una consulta indexada estándar contra la colección de origen.Expandir: Para cada documento coincidente que no esté en el conjunto visitado, agréguelo a los resultados, extraiga
connectFromFieldlos valores y empújelos a la siguiente frontera.Repetir: Incrementar la profundidad. Volver al paso 2 hasta que la frontera se vacíe o
maxDepthse alcance.Ensamblar: Colocar todos los documentos acumulados en una matriz bajo el campo "as".
La detección de ciclos es automática. Un conjunto interno de elementos visitados evita bucles infinitos en grafos cíclicos (A → B → C → A), comunes en las estructuras de lavado de dinero. Cada documento aparece en los resultados exactamente una vez.
La restrictSearchWithMatch Ventaja
restrictSearchWithMatch Incorpora los criterios de filtrado al recorrido en sí, en lugar de aplicarlos posteriormente. MongoDB elimina las ramas obsoletas durante el recorrido, en vez de explorar el grafo completo y filtrarlo después. En redes grandes, esto puede reducir el conjunto de trabajo en un orden de magnitud.
Figura 3. Filtrado durante el recorrido frente a filtrado posterior.
Enfoque de modelo de datos
La solución utiliza dos colecciones: entities y relationships. Estas siguen un patrón de lista de adyacencia. Los metadatos de los bordes (confianza, evidencia, estado de verificación) se encuentran como campos de primera clase en el documento de relación.
Esquema de relación
{ "relationshipId": "REL_8910", "source": { "entityId": "ENT_123", "entityType": "individual" }, "target": { "entityId": "ENT_456", "entityType": "organization" }, "type": "beneficial_owner_of", "direction": "directed", "strength": 0.85, "confidence": 0.95, "active": true, "verified": true, "evidence": [ { "evidence_type": "corporate_registry", "confidence": 0.95, "source": "Companies House UK" } ], "datasource": "KYC_onboarding" }
Decisiones clave de diseño
Referencias de origen/destino anidadas: La
source.entityIdtarget.entityIdestructura$graphLookupconnectFromFieldy se corresponde directamente conconnectToFieldlos parámetros y de. Además, conserva los metadatos del tipo de entidad a nivel de borde sin necesidad de realizar una unión.Separado
strengthyconfidenceCampos: Lafuerza refleja la solidez intrínseca de la relación, como un beneficiario 90final con una propiedad del 0 % frente a un socio comercial distante. La confianza refleja la fiabilidad de los datos verificados por dos fuentes independientes frente a la inferencia a partir de una dirección compartida. La propagación del riesgo utiliza ambos valores de forma diferente.activeBooleano para eliminación lógica: los sistemas AML requieren registros de auditoría. Elimine una relación estableciendoactive: falseen lugar de eliminar el documento. Este indicador también funciona como filtro de recorridorestrictSearchWithMatchen.
Compilar la solución
Clona el repositorio y sigue las instrucciones de configuración que aparecen en el archivo README de GitHub:
git clone https://github.com/mongodb-industry-solutions/fsi-aml-fraud-detection.git cd fsi-aml-fraud-detection/aml-backend poetry install poetry run uvicorn main:app --host 0.0.0.0 --port 8001 --reload
Las siguientes subsecciones repasan las seis operaciones gráficas principales implementadas en NetworkRepository.
Crear índices necesarios
$graphLookupEmite una consulta{ connectToField: { $in: [frontier] } }en cada onda BFS. ÍndiceconnectToFielden ambas direcciones de recorrido:
db.relationships.createIndex({ "source.entityId": 1, "active": 1, "confidence": -1 }); db.relationships.createIndex({ "target.entityId": 1, "active": 1, "confidence": -1 });
La ventaja del índice es mayor en los saltos 1–3 y disminuye a medida que aumenta la profundidad. Los recorridos de 1a4saltos en las investigaciones AML se sitúan justo en el punto óptimo.
Construir redes de entidades $graphLookup (Dual)
Utilice la canalización dual
$graphLookupdescrita en Arquitecturas de referencia para construir redes bidireccionales completas en una única agregación.El punto final
/network/{entity_id}expone esta operación con profundidad, umbral de confianza y número máximo de nodos configurables.
Encontrar caminos más cortosdepthField ()
Determinar si un cliente de bajo riesgo se conecta con una entidad sancionada y reconstruir la cadena exacta.
Utilice
depthFieldpara anotar cada relación descubierta con su distancia de salto:
pipeline = [ {"$match": {"entityId": source_entity_id}}, {"$graphLookup": { "from": "relationships", "startWith": "$entityId", "connectFromField": "source.entityId", "connectToField": "target.entityId", "as": "forward_paths", "maxDepth": max_depth - 1, "depthField": "depth" }}, {"$graphLookup": { "from": "relationships", "startWith": "$entityId", "connectFromField": "target.entityId", "connectToField": "source.entityId", "as": "reverse_paths", "maxDepth": max_depth - 1, "depthField": "depth" }}, {"$project": { "all_paths": {"$concatArrays": ["$forward_paths", "$reverse_paths"]} }}, {"$unwind": "$all_paths"}, {"$match": {"$or": [ {"all_paths.source.entityId": target_entity_id}, {"all_paths.target.entityId": target_entity_id} ]}}, {"$sort": {"all_paths.depth": 1}}, {"$limit": 1} ]
El resultado identifica la profundidad más corta. Un segundo conjunto acotado $graphLookup a esa profundidad reconstruye la cadena de relaciones completa:
Calcular estadísticas de red$facet ()
Utilice
$facetpara ejecutar cinco análisis paralelos en el mismo conjunto de entidades en una única canalización: distribución de riesgos, desglose del tipo de entidad, detección de nodos centrales, puntuación de prominencia y métricas básicas:
stats_pipeline = [ {"$match": {"entityId": {"$in": network_entity_ids}}}, {"$addFields": { "connection_count": {"$size": {"$ifNull": ["$connected_entities", []]}} }}, {"$facet": { "basic_stats": [{"$group": { "_id": None, "total_nodes": {"$sum": 1}, "avg_risk_score": {"$avg": "$riskAssessment.overall.score"}, "max_risk_score": {"$max": "$riskAssessment.overall.score"} }}], "risk_distribution": [ {"$group": {"_id": "$riskAssessment.overall.level", "count": {"$sum": 1}}}, {"$sort": {"_id": 1}} ], "hub_entities": [ {"$match": {"connection_count": {"$gte": 2}}}, {"$sort": {"connection_count": -1}}, {"$limit": 5}, {"$project": {"entityId": 1, "name": 1, "connection_count": 1}} ] }} ]
Sin $facet, cada análisis requiere una canalización independiente. $facet procesa todas las subcanalizaciones en paralelo y devuelve los resultados en una única respuesta. Esto se ejecuta en 2–5 milisegundos.
Centralidad de$switch puntuación ( para ponderación específica del dominio)
Identificar las entidades que conectan a los actores más sospechosos.
El pipeline de centralidad utiliza
$facetpara agregar las conexiones salientes y entrantes por separado, luego las fusiona y les asigna una puntuación.El operador
$switchasigna ponderaciones de riesgo a cada tipo de relación directamente dentro de la agregación:
"outgoing_risk_weighted": { "$sum": {"$multiply": [ "$confidence", {"$switch": { "branches": [ {"case": {"$in": ["$type", [ "confirmed_same_entity", "business_associate_suspected" ]]}, "then": 0.9}, {"case": {"$in": ["$type", [ "director_of", "ubo_of", "parent_of_subsidiary" ]]}, "then": 0.7}, {"case": {"$in": ["$type", [ "household_member", "professional_colleague_public" ]]}, "then": 0.3} ], "default": 0.5 }} ]} }
Una conexión
confirmed_same_entitycontribuye mucho más a la centralidad ponderada por riesgo que un enlacehousehold_member.La puntuación compuesta final combina la centralidad de grado normalizada (40%), el peso de confianza promedio (30%) y la centralidad ponderada por riesgo (30%), todo calculado en el servidor.
Detectar comunidades y propagar el riesgo
La detección de comunidades construye un mapa de adyacencia mediante agregación, filtrando por
confidence >= 0.7para dibujar límites de comunidad solo alrededor de relaciones de alta confianza:
adjacency_pipeline = [ {"$match": { "$or": [ {"source.entityId": {"$in": entity_ids}}, {"target.entityId": {"$in": entity_ids}} ], "active": True, "confidence": {"$gte": 0.7} }}, {"$group": { "_id": "$source.entityId", "connections": {"$addToSet": "$target.entityId"} }} ]
$addToSetElimina automáticamente las conexiones duplicadas: dos entidades que comparten relacionesshared_addressybusiness_associateaparecen como una sola conexión.
Lapropagación del riesgo aplica una disminución exponencial a través de la cadena de relaciones después de que $graphLookup descubre la red:
propagated_risk = ( parent_entity_risk * propagation_factor # decay per hop (default: 0.5) * relationship_confidence # trust level for this edge * type_risk_weight # domain-specific weight )
La empresa fantasma de una entidad sancionada (un tipo de relación de alta confianza y alto riesgo) recibe casi la puntuación de riesgo completa. La conexión en redes sociales de su contable prácticamente no recibe ninguna puntuación. El recorrido es en amplitud, con una profundidad limitada a 3 saltos, y se detiene cuando la puntuación propagada cae por debajo de un umbral configurable.
Lecciones clave
Utilice doble
$graphLookupPara el descubrimiento de redes bidireccionales: ejecute dos búsquedas paralelas, hacia adelante y hacia atrás, y combine los resultados con$concatArrayspara capturar la red completa en una sola agregación.Empujar filtros en el recorrido con
restrictSearchWithMatch: Podar las ramas muertas durante la búsqueda en amplitud (BFS) en lugar de filtrar el grafo completo posteriormente para reducir el conjunto de trabajo en redes grandes.Crear índices compuestos en
connectToFieldy sus filtros de recorrido:$graphLookupemite una$match/$inconsulta en cada onda BFS, por lo que indexarconnectToFieldjunto conactiveyconfidenceevita escaneos de colección en cada salto.Calcular análisis de red del lado del servidor con
$facety$switch: Ejecutar la distribución de riesgos, la detección de hubs y la puntuación de centralidad en subcanales paralelos, y usar$switchpara asignar ponderaciones de riesgo específicas del dominio a los tipos de relaciones dentro de la agregación.Aplique la disminución exponencial para la propagación del riesgo: combine
$graphLookupel descubrimiento de redes basado en con una fórmula de disminución por salto que incluya la confianza y el tipo de relación, de modo que el modelo distinga automáticamente las conexiones estructurales de alto riesgo de los vínculos sociales de bajo riesgo. Esto coincide con la directriz de mantener los aprendizajes clave en un estilo imperativo coherente.
Autores
Luis Pazmiño
Mehar Grewal
Andrea Alaman Calderon