Casos de uso: Visualização individual
Setores: Serviços financeiros
Produtos: MongoDB Atlas
Visão Geral da Solução
Os criminos financeiros operam em redes. Limpeza de capital, anéis de fraude e evasão de autorizações dependem de redes de empresas de shell, directores proxy e caminhos de transação em camadas. As equipes de compliance precisam mapear e analisar a rede de relacionamento em torno de uma entidade suspeita.
As abordagens tradicionais se deparam com dois gargalos:
Queries dispendiosas: os bancos de dados relacionais usam Expressões de Tabela Comum (CTEs) recursivas. Essas queries se tornam caras além de dois ou três saltos.
Ida e volta da rede constantes: o processamento de gráficos do lado do cliente extrai dados para a camada do aplicação . O cliente executa uma nova query para cada salto.
Nesta solução, você:
Crie um mecanismo de análise de rede que use operadores MongoDB para evitar esses gargalos.
Execute grafos multi-op, detecção de caminho mais curto, pontuação de centralidade, detecção de comunidade e propagação de risco diretamente dentro da estrutura de agregação do MongoDB.
Esta solução se concentra no mecanismo de análise de rede e nos operadores MongoDB que o alimentam. Para implementar a arquitetura geral com MongoDB Atlas, FastAPI e Next.js, siga também a Biblioteca deSoluções anterior, Mitigação de Crimes financeiros.
Por que MongoDB em vez de um banco de dados com gráficos dedicados?
Armazene entidades e relacionamentos no MongoDB juntamente com índices de pesquisa, incorporações vetoriais e change streams.
Evite sincronizar dados entre dois sistemas, gerenciar infraestrutura extra e incorrer em latência entre sistemas porque você mantém as cargas de trabalho dos gráficos no MongoDB.
Use o operador
$graphLookupdo MongoDB para executar pesquisas em largura com queries indexadas padrão em cada salto, para que o desempenho dependa dos seus índices em vez de um mecanismo de gráficos separado.Para as traversais de 1a5saltos comuns em pesquisas de compliance, essa abordagem corresponde a bancos de dados de gráficos dedicados enquanto executa query dos mesmos dados que seu aplicação já lê e grava.
Observação
Em pesquisas de compliance, você normalmente rastreia alguns passos a partir de um assunto. As contrapartes imediatas e os intermediários de primeira camada ficam a 1-2 saltos de distância, enquanto as estruturas de shell e os caminhos de movimentação de capital geralmente se enquadram no intervalo de 3a5 saltos. Além disso, os links se tornam menos significativos e mais difíceis de explicar, portanto, a maioria das pesquisas práticas permanece nessa faixa de 1a5saltos.
Arquiteturas de referência
O mecanismo de análise de rede fica entre a camada de serviço FastAPI e MongoDB Atlas e processa operações de gráficos por meio do pipeline de agregação .
figura 1. Pipeline de Investigação Agente
Dual $graphLookup Padrão
As redes de limpeza de ativos envolvem fluxos bidirecionais. Uma entidade pode obter recursos em um relacionamento e recebê-los em outro.
Um único $graphLookup atravessa as bordas em apenas uma direção. Essa solução executa duas pesquisas paralelas - direta e reversa - e mescla os resultados em um gráfico de rede 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. $graphLookup duplo - Descoberta de rede bidirecional
O aplicação recebe o gráfico de rede completo em uma única ida e volta. Ambos os traversais são executados em um pipeline de agregação , o que remove o problema de query N+1 de emitir uma query de acompanhamento separada para cada documento retornado na query inicial.
Como $graphLookup Percorre o gráfico
$graphLookup realiza uma pesquisa em largura (BFS) em ondes discretas:
Semente: Avalie
startWithem relação ao documento de entrada. Os valores de array semeiam a borda simultaneamente (BFS multiraiz).Query: Construa,
{ connectToField: { $in: [frontier_values] }}mesclado com qualquerrestrictSearchWithMatchfiltro. Execute como uma query indexada padrão em relação à coleção.Expandir: para cada documento correspondente que não esteja no conjunto visitado, adicione-o aos resultados, extraia
connectFromFieldvalores e envie-os para a próxima borda.Repita: aumente a profundidade. Retorne à etapa 2 até que a borda esvazie ou
maxDepthseja atingido.Montar: coloque todos os documentos acumulados em uma array sob o campo.
A detecção de ciclo é automática. Um conjunto visitado interno evita loops infinitos em gráficos cíclicos (A → B → C → A), que são comuns em estruturas de limpeza de capital. Cada documento aparece nos resultados exatamente uma vez.
O restrictSearchWithMatch Vantagem
restrictSearchWithMatch empurra critérios de filtro para a própria transversal, não como um pós-filtro. O MongoDB poda os ramos morto durante a travessia em vez de descobrir o gráfico completo e filtrar posteriormente. Para redes grandes, isso pode reduzir o conjunto de trabalho em uma ordem de magnitude.
figura 3. Filtragem durante a passagem versus pós-filtragem
Abordagem do modelo de dados
A solução utiliza duas collections: entities e relationships. Elas seguem um padrão de lista de adjacência. Os metadados de borda ( confiança , provas , status de verificação ) existem como campos de primeira classe no documento de relacionamento .
Esquema de relacionamento
{ "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" }
Principais decisões de design
Referências de origem/destino aninhadas: A
source.entityIdtarget.entityIdestrutura e mapeia diretamente$graphLookupconnectFromFieldparaconnectToFieldos parâmetros e do. Ele também preserva os metadados do tipo de entidade no nível de borda sem exigir uma união.Separar
strengthe aconfidencecampos: A Força captura o quão inerentemente forte é o relacionamento , como um UBO com 90% de propriedade em relação a um associado comercial distante. A confiança captura a confiança no ponto de dados verificado por duas fontes independentes versus inferido a partir de um endereço compartilhado. A propagação do risco usa ambos os valores de forma diferente.activeboolean para exclusão reversível: os sistemas AML exigem faixas de auditar . Exclua um relacionamentoactive: falsedefinindo em vez de remover o documento. Esse sinalizador também serve como um filtro transversalrestrictSearchWithMatchem.
Construir a solução
Clone o repositório e siga as instruções de configuração no GitHub README:
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
As subseções a seguir percorrem as seis operações principais de gráfico implementadas no NetworkRepository.
Criar índices necessários
$graphLookupemite uma query{ connectToField: { $in: [frontier] } }a cada onde de BFS. ÍndiceconnectToFieldem ambas as direções transversais:
db.relationships.createIndex({ "source.entityId": 1, "active": 1, "confidence": -1 }); db.relationships.createIndex({ "target.entityId": 1, "active": 1, "confidence": -1 });
A vantagem do índice é mais forte em 1–3 saltos e diminui à medida que a profundidade aumenta. As passagens de 1para4sapatos nas pesquisas AML se encaixam perfeitamente no ponto ideal.
Construir redes de entidade ($graphLookup duplo)
Use o pipeline
$graphLookupduplo descrito em Arquiteturas de referência para criar redes bidirecionais completas em uma única agregação.O endpoint
/network/{entity_id}expõe esta operação com profundidade configurável, limite de confiança e contagem máxima de nós.
Encontre os caminhos mais curtosdepthField ()
Determine se um cliente de baixo risco se conecta a uma entidade sancionada – e reconstrua a cadeia exata.
Use
depthFieldpara anotar cada relacionamento descoberto com sua distância 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} ]
O resultado identifica a menor profundidade. Um segundo $graphLookup limitado nessa profundidade reconstrói a cadeia de relacionamento completa:
Calcular estatísticas de rede$facet ()
Use o
$facetpara executar cinco análises paralelas no mesmo conjunto de entidades em um único pipeline — distribuição de riscos, detalhamento do tipo de entidade, detecção de hub, pontuação de proeminência e 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}} ] }} ]
Sem o $facet, cada análise exige um pipeline separado. $facet processa todos os subpipelines em paralelo e gera resultados em uma única resposta. Isso é executado em 2a5 milissegundos.
Centralidade de$switch pontuação ( para ponderação específica de domínio)
Identifique as entidades que conectam os atores mais suspeitas.
O pipeline de centralidade usa
$facetpara agregar conexões de saída e de entrada separadamente, depois mescla e pontua.O operador
$switchatribui pesos de risco a cada tipo de relacionamento diretamente dentro da agregação:
"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 }} ]} }
Uma conexão
confirmed_same_entitycontribui muito mais para a centralidade ponderada pelo risco do que um linkhousehold_member.A pontuação composta final combina centralidade de grau normalizada (40), peso médio de confiança (30% e centralidade ponderada de risco (30% ) — todas computadas no lado do servidor.
Detecte comunidades e propague riscos
A detecção de comunidade cria um mapa de adjacência por meio de agregação, filtrando em
confidence >= 0.7para traçar os limites da comunidade em torno de relacionamentos de alta confiança somente:
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"} }} ]
$addToSetdeduplica conexões automaticamente — duas entidades que compartilham relacionamentosshared_addressebusiness_associateaparecem como uma única conexão.
Apropagação do risco aplica um decaimento exponencial ao longo da cadeia de relacionamento depois que $graphLookup descobre a rede:
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 )
A empresa de shell de uma entidade sancionada (tipo de relacionamento de alta confiança e alto risco) recebe quase a pontuação de risco total. A conexão de mídia social de seu contador não recebe quase nada. A travessia é em largura, limitada em profundidade a 3 saltos e para quando a pontuação propagada cai abaixo de um limite configurável.
Principais Aprendizados
Usar duplo
$graphLookuppara descoberta de rede bidirecional: execute duas pesquisas paralelas, direta e inversa, e mescle os resultados com$concatArrayspara capturar a rede completa em uma agregação.Empurre os filtros para a travessia com
restrictSearchWithMatch: podar ramificações mortas durante o BFS em vez de filtrar o grafo completo posteriormente para reduzir o conjunto de trabalho para redes grandes.Crie índices compostos em
connectToFielde seus filtros transversais:$graphLookupemite uma$match/$inquery a cada onde de BFS, portanto, a indexaçãoconnectToFieldde junto comactiveeconfidenceevita varreduras de collection em cada salto.Faça a análise de rede do lado do servidor com
$facete a$switch: Execute a distribuição de risco, a detecção de hub e a pontuação de centralidade em subpipelines paralelos e use$switchpara atribuir pesos de risco específicos do domínio a tipos de relacionamento dentro da agregação.Aplicar decaimento exponencial à propagação de riscos: Combine
$graphLookupa descoberta de rede baseada em com uma fórmula de decaimento por salto que inclua confiança e tipo de relacionamento para que o modelo distinga automaticamente as conexões estruturas de alto risco dos links sociais de baixo risco. Isso corresponde à diretriz para manter os principais aprendizados em um estilo obrigatório consistente.
Autores
Luis Pazmiño
Mehar Crewal
Andrea Alaman Calderon