立即体验 Elasticsearch:深入探索 Elasticsearch Labs 代码库 中的示例笔记本,开始 免费的云试用,或者现在就在您的 本地机器上试用 Elastic。
五段 ES|QL 查询能够标记整个投资组合中的洗钱跑分账户,从一个已报告的案例开始追踪下游洗钱网络,并将可疑支付与单独集群中的行为信号关联起来,而无需移动任何数据。本文将介绍我们基于 Elasticsearch 构建的欺诈调查平台的架构、数据模型和 ES|QL 查询。此外,还将探讨分层存储和 Logsdb 索引模式如何将七年监管保留成本降低高达 65%。本文是三部曲的第二部分;第一部分 讨论了金融服务领域的调查差距,第三部分将介绍在此之上构建的 AI 代理。
我们最近在英国客户技术交流会上演示了这个平台。值得强调的关键点是:Elasticsearch、Agent Builder、ES|QL 和 Kibana 是许多金融机构已经在运行的组件,因此这里描述的调查功能可以从现有基础设施中组装起来,而无需购买独立的堆栈。
该平台采用分层架构,数据、智能和展示层之间有清晰的区分。

Elasticsearch 欺诈调查平台架构,包含 Agent Builder、ES|QL 查询、跨集群搜索和 Kibana,横跨数据、智能和展示层
图 1. Elastic 高级架构
欺诈调查数据很少只存在于一个地方。交易记录、客户资料和账户元数据通常位于由支付或核心银行团队管理的银行数据集群中。同时,应用程序遥测数据(移动应用会话日志、网上银行事件、设备指纹和呼叫中心交互记录)通常位于由工程或运营团队管理的可观测性集群中。
跨集群搜索(CCS) 允许每个团队保留其数据的所有权。同时,调查人员可以通过一个单一请求查询这两个集群,而无需将数据迁移到单个集群,也无需单独的聚合层。银行集群保存交易数据,而可观测性集群保存行为信号。调查人员可以看到一个统一的结果集,而两个团队都无需放弃对其数据的控制权。
跨集群搜索 不仅仅是技术上的便利。它解决了我们在第一部分中描述的组织内部障碍——不同团队拥有不同数据、对查询影响生产系统的担忧以及出于充分理由存在的基于角色的访问控制(RBAC)。CCS 在尊重所有这些的同时,仍然允许进行跨领域调查。
智能层汇集了 AI 代理、ES|QL 查询、机器学习作业和工作流自动化。在 Elastic Agent Builder 中,代理将请求路由到正确的技能,每个技能绑定其所需的 ES|QL 工具和工作流;例如,调查、类型学与叙事、图与网络、COP/漏洞和 SAR 创建。第三部分将详细介绍代理和技能架构。在本博客中,我们重点关注它们所依赖的数据和查询基础。
该平台包含一个自定义调查界面,用于与代理服务和 Elasticsearch API 进行通信。但相同的数据在 Kibana 中也可用,供分析师进行即席探索。这种双界面方法是刻意为之的:结构化的调查工作流用于提高速度,而 Kibana 则用于回答工作流未设计解决的问题。
Kibana 仪表板提供运营视图(警报队列、案例量趋势和跑分账户热力图),而 Discover 和控制台中的 ES|QL 则让分析师可以自由运行探索性查询,而无需等待他人构建新的仪表板。
关于可用性的一点说明:此处显示的数据模型和 ES|QL 查询可在任何 Elasticsearch 部署上运行,但此架构中的两项功能具有特定要求:
数据模型是其他一切的基础。欺诈调查是一个跨领域问题,这意味着数据模型必须适应不同的文档类型,同时仍支持快速、灵活的查询。

Elasticsearch 欺诈调查数据模型,显示跨集群搜索将银行交易索引连接到可观测性集群中的行为遥测数据
图 2. 应用欺诈数据模型 - FCA 沙盒
核心索引保存支付交易记录。每个文档代表一个单一的支付事件,包含参与方、金额、时间和其他元数据字段。
1 // 索引:banking-transactions
2 {
3 "transaction_id": "a76fe1f9-c403-4855-bc61-22b0db37fc29",
4 "timestamp": "2025-01-20T23:47:12.341Z",
5 "account_id": "3f7932e3-9e69-4d69-aef5-41f281b3969b",
6 "amount_gbp": -15000,
7 "currency": "GBP",
8 "is_credit": false,
9 "is_debit": true,
10 "channel": "mobile_app",
11 "transaction_type": "faster_payment",
12 "payment_code": "XF",
13 "purpose_code": "INVESTMENT",
14 "description": "Investment deposit",
15 "counterparty_name": "CryptoTrade Ltd",
16 "counterparty_account": "94738291",
17 "counterparty_sort_code": "20-45-67",
18 "merchant_category": "6211",
19 "geo_location": "POINT (-0.1616 51.5296)",
20 "graph.source_account": "47-83-82_10985194",
21 "graph.source_bank": "Bank4",
22 "graph.dest_account": "20-45-67_94738291",
23 "graph.dest_bank": "Bank2",
24 "graph.payment_flow": "outbound",
25 "graph.amount_band": "large",
26 // ... 其他字段
27 }这种结构有几个值得注意的地方:
purpose_code 和 merchant_category 字段在可用时会携带 ISO 20022 丰富信息,提供规则系统可以与 AI 代理一起使用的信号。is_credit 和 is_debit 标志用于快速过滤。geo_location 允许对交易模式进行空间分析。当 ISO 20022 丰富数据可用时,目的代码、法人实体标识符(Legal Entity Identifiers)和结构化汇款信息提供了额外的信号。Elasticsearch 的无模式特性意味着我们可以将 ISO 8583 传统格式和 ISO 20022 丰富格式都摄入到同一个索引中,而无需僵硬的预设模式。
客户数据通常托管在 CDP(客户数据平台)中,但为了简化架构,我们将其放在同一个集群中。账户级别数据存储在 banking-accounts 索引中,而客户身份和资料数据存储在 banking-people 索引中。这种分离是故意的:需要查询交易模式的调查人员可以在不访问个人身份信息(PII)的情况下进行操作,并且 RBAC 可以将 PII 访问权限限制给授权角色。
1 // 索引:banking-accounts
2 {
3 "account_id": "7179a225-19b5-4b24-9aae-b50ad43cb15c",
4 "account_number": "96435152",
5 "account_type": "savings_account",
6 "account_status": "active",
7 "person_id": "79393ff4-3a32-4bae-9021-4566e6b5008c",
8 "bank_name": "Bank1",
9 "sort_code": "20-30-79",
10 "iban": "GB62BANK20307996435152",
11 "balance_gbp": 39920.61,
12 "currency": "GBP",
13 "opened_date": "2017-04-13T00:00:00.000Z",
14 // ... 其他字段
15 }person_id 字段链接到 banking-people 索引,该索引保存客户身份、地址、信用历史、就业和脆弱性标志(存储在具有自己访问控制的单独索引中)。一个简化示例如下:
1 // 索引:banking-people
2 {
3 "person_id": "4233d7fd-29aa-439d-8cc8-a07f1933128f",
4 "first_name": "Rebecca",
5 "last_name": "Brennan",
6 "date_of_birth": "1979-01-19T00:00:00.000Z",
7 "age": 47,
8 "address_line1": "244 Manor Terrace",
9 "city": "Blackpool",
10 "postcode": "FY1 6AN",
11 "location_geo": "POINT (-3.050282 53.836826)",
12 "credit_history.credit_score": 729,
13 "credit_history.vulnerability_flag": "socially_isolated",
14 "credit_history.total_debt_gbp": 6548.49,
15 "accounts": [
16 {
17 "account_id": "528a...",
18 "account_number": "32540475",
19 "sort_code": "22-79-61",
20 "balance_gbp": 3242.93
21 },
22 {
23 "account_id": "692e...",
24 "account_number": "39261497",
25 "sort_code": "24-53-93",
26 "balance_gbp": 2175.45
27 }
28 ],
29 // ... 其他字段
30 }可观测性集群保存来自银行自己的移动和网上银行应用程序的会话级遥测数据。这不是浏览历史或外部网站数据。它是来自机构自身服务的遥测数据,支付服务提供商可以合法访问这些数据。
1 // 索引:app-telemetry (可观测性集群,通过 CCS 访问)
2 {
3 "session_id": "sess-9f8e7d",
4 "customer_id": "C847392",
5 "timestamp": "2025-01-20T21:32:00Z",
6 "app_platform": "ios",
7 "event_type": "screen_view",
8 "screen_name": "payment_limits",
9 "session_duration_seconds": 8100,
10 "device_fingerprint": "fp-abc123",
11 "ip_address": "82.132.xxx.xxx",
12 "geo_city": "London",
13 // ... 其他字段
14 }会话遥测揭示了仅凭交易数据无法发现的行为模式:客户在进行支付前在应用中花费了多长时间、他们访问了哪些屏幕(尤其是支付限额屏幕)、设备指纹是否与其常用设备匹配以及登录位置是否与其历史记录一致。
客户服务交互以结构化元数据进行索引,从而可以将服务呼叫与后续交易关联起来。
1 // 索引:call-centre-logs
2 {
3 "call_id": "CALL-29481",
4 "customer_id": "C847392",
5 "timestamp": "2025-01-17T14:22:00Z",
6 "duration_seconds": 420,
7 "topic": "payment_limit_increase",
8 "outcome": "limit_raised",
9 // ... 其他字段
10 }ES|QL 是 Elastic 的管道式查询语言,专为大型数据集的探索性分析而设计。在我们的平台中,ES|QL 查询扮演着两个角色:它们是 代理技能 以编程方式调用的工具,并且分析师可以在 Kibana 中直接使用它们进行即席探索。
以下是为平台提供支持的关键查询。
这是平台中最重要的查询。它查看每个账户,计算进出资金量,衡量资金流转速度,并标记那些资金直进直出的账户。
1 FROM banking-transactions
2 | STATS
3 incoming = SUM(CASE(is_credit == true, amount_gbp, 0)),
4 outgoing = SUM(CASE(is_debit == true, ABS(amount_gbp), 0)),
5 transaction_count = COUNT(*),
6 first_txn = MIN(timestamp),
7 last_txn = MAX(timestamp)
8 BY account_id
9 | EVAL
10 turnover_ratio = TO_DOUBLE(outgoing) / GREATEST(TO_DOUBLE(incoming), 1.0),
11 time_span_ms = TO_LONG(last_txn) - TO_LONG(first_txn),
12 days_active = time_span_ms / 86400000.0,
13 txn_frequency = TO_DOUBLE(transaction_count) / GREATEST(days_active, 1.0)
14 | WHERE incoming > 5000
15 AND turnover_ratio > 0.75
16 AND days_active < 180
17 | SORT turnover_ratio DESC, incoming DESC
18 | LIMIT 50示例输出:
1 account_id incoming outgoing txns turnover_ratio days_active
2 20-45-67_94738291 247,000 239,000 31 0.968 42.0
3 30-91-22_50271841 87,000 84,200 18 0.968 27.0
4 04-17-55_61398002 54,000 52,400 13 0.970 22.0
5 11-38-09_77450513 43,000 41,500 11 0.965 18.0
6
7 (50 个结果中的前 4 个)turnover_ratio 接近 1.0 意味着几乎所有进来的资金都立即转出,这是跑分账户的典型特征。days_active 过滤器侧重于最近开立的账户,而入账金额阈值则过滤掉无关数据。结果是一个按可疑程度排名的投资组合中最可疑账户列表。
一旦账户被标记,下一步是了解入账模式。此查询识别所有向指定目的地汇款的账户,并计算每个来源的总风险敞口。
1 FROM banking-transactions
2 | WHERE graph.dest_account == "20-45-67_94738291"
3 AND is_debit == true
4 | STATS
5 total_sent = SUM(ABS(amount_gbp)),
6 tx_count = COUNT(*),
7 first_payment = MIN(timestamp),
8 last_payment = MAX(timestamp)
9 BY graph.source_account, graph.source_bank
10| SORT total_sent DESC示例输出:
1 graph.source_account graph.source_bank total_sent tx_count
2 20-30-79_88142251 Bank1 15,000 1
3 04-22-13_67391044 Bank3 14,200 1
4 11-90-56_20517783 Bank2 12,800 2
5 ... ... ... ...
6
7 23 个源账户 · 总计汇款 £247,000这展示了源账户的完整列表——潜在的受害者。当一个账户在六周内显示 23 个不同的来源,所有这些来源都向同一个目的地汇入大笔资金时,模式就变得清晰了。
跑分网络是分层的。为了追踪资金在主要跑分账户之后流向何处,我们反转查询以查看出账流向:
1 FROM banking-transactions
2| WHERE graph.source_account == "20-45-67_94738291"
3 AND graph.payment_flow == "outbound"
4| STATS
5 forwarded = SUM(ABS(amount_gbp)),
6 tx_count = COUNT(*),
7 first_forward = MIN(timestamp),
8 last_forward = MAX(timestamp)
9 BY graph.dest_account, graph.dest_bank
10 BY graph.dest_account, graph.dest_bank示例输出:
1 graph.dest_account graph.dest_bank forwarded tx_count
2 30-91-22_50271841 Bank4 112,400 8
3 04-17-55_61398002 Bank2 78,100 6
4 11-38-09_77450513 Bank5 48,500 4
5
6 3 个下游账户 · 转账 £239,000代理将这些查询串联起来:分析主要跑分账户的下游账户,然后对它们中的每一个运行相同的跑分账户检测查询,以评估它们是否也属于洗钱网络的一部分。这种递归模式将一个单一的报告案例转化为全面的网络级调查。
为了评估特定交易对于给定客户是否异常,我们计算其历史基线并进行比较:
1 FROM banking-transactions
2| WHERE account_id == "3f7932e3-9e69-4d69-aef5-41f281b3969b"
3 AND is_debit == true
4| STATS
5 avg_amount = AVG(ABS(amount_gbp)),
6 max_amount = MAX(ABS(amount_gbp)),
7 tx_count = COUNT(*),
8 first_txn = MIN(timestamp),
9 last_txn = MAX(timestamp)
10| EVAL
11 flagged_amount = 15000,
12 amount_ratio = ROUND(flagged_amount / GREATEST(avg_amount, 1.0), 1),
13 time_span_ms = TO_LONG(last_txn) - TO_LONG(first_txn),
14 days_active = time_span_ms / 86400000.0示例输出:
1 avg_amount max_amount tx_count flagged_amount amount_ratio days_active
2 1,247.50 4,200.00 87 15,000 12.0 363.8amount_ratio 为 12 意味着被标记的交易是该客户典型支付金额的 12 倍。代理将此信息与时间、渠道数据一起,作为其欺诈可能性评估的输入。
跨集群搜索允许单个 ES|QL 查询从可观测性集群中提取数据。此查询检索客户在被标记交易前几小时的应用程序遥测数据:
1 FROM observability:app-telemetry
2| WHERE account_id == "3f7932e3-9e69-4d69-aef5-41f281b3969b"
3 AND timestamp >= "2025-01-20T19:00:00Z"
4 AND timestamp <= "2025-01-21T00:00:00Z"
5| STATS
6 session_count = COUNT_DISTINCT(session_id),
7 total_duration = SUM(session_duration_seconds),
8 screens_viewed = COUNT(*),
9 limit_views = SUM(CASE(screen_name == "payment_limits", 1, 0))
10 | EVAL hours_active = ROUND(TO_DOUBLE(total_duration) / 3600.0, 1)示例输出:
1 session_count total_duration screens_viewed limit_views hours_active
2 4 7,320 38 7 2.0当这些数据揭示出一位通常在午餐时间进行五分钟银行操作的客户,却在深夜在应用中花费了两小时,访问支付限额屏幕七次,然后进行了一笔异常大额的转账时,情况就变得非常清楚了。这种行为上下文通常是自信进行欺诈评估与模棱两可评估之间的区别。
欺诈调查平台只有在能够处理实际数据量和满足监管保留要求时才有用。
Elasticsearch 的分布式架构可以轻松应对。处理企业客户每天数十亿安全事件的相同基础设施,完全能够处理支付交易量(例如 BBVA 等客户)。
英国金融法规通常要求保留七年的交易数据。这是大量数据。Elastic 的分层存储架构使其在经济上可行:
Logsdb 索引模式通过优化字段的存储和索引方式,将交易数据的存储成本降低高达 65%。对于一个需要大规模保留多年数据的平台来说,这是可负担部署与成本高昂部署之间的关键区别。
构建 Elasticsearch 欺诈调查平台强化了一些值得分享的经验,适用于任何构建类似平台的人。
经验 | 重要性 | 应对方法 |
|---|---|---|
从数据模型开始 | 定义良好的模式为 AI 代理提供了准确解释查询结果的上下文。每个下游查询、技能和仪表板都依赖于它。 | 预先定义明确的映射,例如字段名称、类型和关系。跨索引规范化字段名称,定义清晰的文档边界,并规划跨集群查询模式。 |
行为数据是差异化因素 | 仅凭交易数据是模糊的。一笔 15,000 英镑的陌生账户支付可能是房屋首付、购车款或欺诈。行为上下文(例如,午夜两小时的应用使用、七次访问支付限额屏幕、三天前的呼叫中心请求)是将模糊性转化为确定性的关键。 | 将应用会话遥测数据与交易数据一起摄入,并在调查时通过跨集群搜索进行查询。 |
跨集群搜索使架构更具现实性 | CCS 允许调查人员跨银行和可观测性集群进行查询,同时尊重现有数据所有权边界。没有它,您就需要数据迁移或单独的聚合层。 | 将 CCS 视为结构性要求,而非优化。它使得该架构能够在实际机构中部署。 |
接下来:AI 代理和工作流自动化
在数据模型和查询基础到位后,第三部分将介绍在此架构之上构建的 AI 代理:它们的配置方式、使用的工具、工作流自动化如何处理 SAR 报告生成和账户调查,以及一个演示从警报到合规行动的完整调查流程的实际场景。最终结果是一个统一的、可查询的调查层,由许多机构已在运行的基础设施构建而成。
准备好探索 Elastic 用于欺诈调查了吗?请 联系我们的金融服务团队,讨论您的数据概念验证、对当前欺诈堆栈的架构审查,或关于 Elastic 如何适应您的欺诈路线图的战略讨论。