Make the MongoDB docs better! We value your opinion. Share your feedback for a chance to win $100.
Click here >
Menu Docs
Página inicial do Docs
/

Análise de rede AML com $graphLookup

Casos de uso: Visualização individual

Setores: Serviços financeiros

Produtos: MongoDB Atlas

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.

  • 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 $graphLookup do 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.

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 .

Pipeline de Investigação Agente
clique para ampliar

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}
]
$graphLookup duplo - Descoberta de rede bidirecional
clique para ampliar

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 startWith em relação ao documento de entrada. Os valores de array semeiam a borda simultaneamente (BFS multiraiz).

  • Query: Construa,{ connectToField: { $in: [frontier_values] }} mesclado com qualquer restrictSearchWithMatch filtro. 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 connectFromField valores e envie-os para a próxima borda.

  • Repita: aumente a profundidade. Retorne à etapa 2 até que a borda esvazie ou maxDepth seja 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.

Filtragem durante a passagem versus pós-filtragem
clique para ampliar

figura 3. Filtragem durante a passagem versus pós-filtragem

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"
}
  • Referências de origem/destino aninhadas: A source.entityId target.entityId estrutura e mapeia diretamente $graphLookup connectFromField para connectToField os 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 strength e a confidence campos: 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.

  • active boolean para exclusão reversível: os sistemas AML exigem faixas de auditar . Exclua um relacionamento active: false definindo em vez de remover o documento. Esse sinalizador também serve como um filtro transversal restrictSearchWithMatch em.

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.

1
  • $graphLookup emite uma query { connectToField: { $in: [frontier] } } a cada onde de BFS. Índice connectToField em 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.

2
  • Use o pipeline $graphLookup duplo 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.

3
  • Determine se um cliente de baixo risco se conecta a uma entidade sancionada – e reconstrua a cadeia exata.

  • Use depthField para 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:

4
  • Use o $facet para 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.

5
  • Identifique as entidades que conectam os atores mais suspeitas.

  • O pipeline de centralidade usa $facet para agregar conexões de saída e de entrada separadamente, depois mescla e pontua.

  • O operador $switch atribui 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_entity contribui muito mais para a centralidade ponderada pelo risco do que um link household_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.

6
  • A detecção de comunidade cria um mapa de adjacência por meio de agregação, filtrando em confidence >= 0.7 para 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"}
}}
]
  • $addToSet deduplica conexões automaticamente — duas entidades que compartilham relacionamentos shared_address e business_associate aparecem 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.

  • Usar duplo $graphLookup para descoberta de rede bidirecional: execute duas pesquisas paralelas, direta e inversa, e mescle os resultados com $concatArrays para 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 connectToField e seus filtros transversais: $graphLookup emite uma $match/$in query a cada onde de BFS, portanto, a indexação connectToField de junto com active e confidence evita varreduras de collection em cada salto.

  • Faça a análise de rede do lado do servidor com $facet e a $switch: Execute a distribuição de risco, a detecção de hub e a pontuação de centralidade em subpipelines paralelos e use $switch para 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.

  • Luis Pazmiño

  • Mehar Crewal

  • Andrea Alaman Calderon

Voltar

Armazenar dados do Open Finance

Nesta página