AMLルールエンジンを作る
目標
疑わしい取引の検知ルール5つをSQLとPythonで実装して、顧客別のアラートスコアを出し、最もスコアが高い顧客の疑わしい取引報告の草案を、事実だけで作成します。
なぜ重要なのか
マネーロンダリング防止(AML)は、銀行ITが最もよく出会う規制業務です。法は、銀行に2つの報告義務を課します。1日に一定金額以上の現金が出入りすれば、それ自体で報告する高額現金取引報告(CTR)と、金額と無関係に、マネーロンダリングが疑われれば報告する疑わしい取引の報告(STR)です。CTRは機械的で、STRは判断です。その判断を助けるのが、ルールエンジンです。
ルールエンジンは、華やかなモデルではありません。「1日の現金の合計が基準を超えているか」、「基準のすぐ下の金額で何回も分割したか」、「高リスク国と取引したか」、「顧客確認が古くなっていないか」、「長く休んでいた口座が突然大金を動かしていないか」のようなルールの集まりです。ルール1つは誤検知が多いですが、1人に複数のルールが重なれば、信号になります。そのため、ルールごとに重みを付けて、合算します。
この仕事には、2つの制約がついてきます。報告書には事実だけを書きます。取引番号、金額、日付、引っかかったルール。「怪しい」は結論ではなく、根拠です。そして、報告したという事実を顧客に知らせません。これは法が定めた守秘義務で、システムは、その事実がチャネル画面や顧客通知に漏れないように設計されていなければなりません。
このラボのルール(基準日2026-08-31)
- R1(高額現金): 同じ顧客が同じ日に同じ方向(入金
CASH_DEPまたは出金CASH_WDR)で動かした現金の合計が、10,000,000ウォン以上。重み1。 - R2(分割の疑い): 1人の顧客の現金取引のうち、8,000,000ウォン以上10,000,000ウォン未満のものが3件以上。重み3。
- R3(高リスク国):
cp_countryがhigh_risk_country表にある振込。重み2。 - R4(顧客確認の経過):
risk_gradeがHIGHか、pepが1なのに、kyc_dateから365日以上が経過。重み1。 - R5(休眠後の活動): 8月の最初の取引日が
last_activityから180日以上経っていて、その最初の取引の金額が5,000,000ウォン以上。重み2。 - スコア = 引っかかったルールの重みの合計。ルール名は、
R1+R3のように、番号順に+でつなぎます。
ステップ
- 右の例にあるジェネレーターを
/root/bank/make_aml.pyとして保存し、python3 make_aml.pyで実行してください。aml.dbに、顧客20人、取引124件、高リスク国3つが入ります。 - R1の結果を
/root/bank/ctr.csvにcust_id,biz_date,direction,cash_totalのヘッダーで書いてください。directionはDEPまたはWDRです。 - R2の結果を
/root/bank/structuring.csvにcust_id,txn_count,cash_total,first_date,last_dateのヘッダーで書いてください。 - R3の結果を
/root/bank/highrisk.csvにtxn_id,cust_id,type,cp_country,amountのヘッダーで書いてください。 - R4の結果を
/root/bank/kyc_due.csvにcust_id,risk_grade,pep,kyc_date,days_sinceのヘッダーで、R5の結果を/root/bank/dormant.csvにcust_id,last_activity,first_txn_date,dormant_days,amountのヘッダーで書いてください。 - ルールに1つでも引っかかった顧客を、
/root/bank/alerts.csvにcust_id,rules,scoreのヘッダーで、スコアの降順(同じならcust_idの昇順)で書いてください。 - スコアが最も高い顧客の報告草案を、
/root/bank/str_<cust_id>.jsonに書いてください。キーは、cust_id、rules(一覧)、score、txn_ids(その顧客がルールに引っかかった取引番号の一覧)、total_amount(その取引の金額の合計)、narrative(100字以上、事実だけ)、prepared_by(人の名前ではなく役割)、customer_notified(必ずfalse)です。 /root/bank/aml_report.mdに、## Detection rules and results(検知ルールと結果)、## Reporting targets(報告対象)、## False positives and limitations(誤検知と限界)、## Confidentiality and personal information(守秘と個人情報)の4つの節で報告書を書いてください。
参考
substr(ts, 1, 10)で日付だけを切り出せば、日単位の集計になります。julianday('2026-08-31') - julianday(kyc_date)が経過日数です。- ルールごとにSQL 1つで十分です。ステップ6は、5つの結果を合わせる作業なので、Pythonが楽です。
- よくある間違い1: R1で入金と出金を合わせて数えてしまうことです。方向別に別々に数えます。
- よくある間違い2: R5の最初の取引を、8月全体の最大金額にしてしまうことです。8月の最初の取引です。
- よくある間違い3: 報告書に顧客の実名や口座番号を書いてしまうことです。
name_maskedすら必要ありません。顧客番号で十分です。
顧客・取引データを作る
右の例にあるジェネレーターを/root/bank/make_aml.pyとして保存し、python3 make_aml.pyで実行してください。aml.dbに、顧客20人、取引124件、高リスク国3つが入ります。
ラボの案内にあるジェネレーターを/root/bank/make_aml.pyとして保存し、python3で実行してください。手で直すと、後のステップの数字がすべてずれます。
R1の高額現金を1日の合計で数える
R1の結果を/root/bank/ctr.csvにcust_id,biz_date,direction,cash_totalのヘッダーで書いてください。directionはDEPまたはWDRです。
現金の種別だけを選んで、cust_id・substr(ts,1,10)・方向でGROUP BYし、HAVING SUM(amount) >= 10000000をかけてください。入金と出金は別々に合計し、合計がちょうど1,000万ウォンの日も含めます。1件ではなく1日の合計です。
R2の分割の疑いを基準のすぐ下で数える
R2の結果を/root/bank/structuring.csvにcust_id,txn_count,cash_total,first_date,last_dateのヘッダーで書いてください。
現金の種別で、amountが8,000,000以上10,000,000未満の案件をcust_idでまとめ、COUNT(*) >= 3の人だけを残してください。日付はMIN/MAXで取ります。
R3の高リスク国を一覧と突き合わせる
R3の結果を/root/bank/highrisk.csvにtxn_id,cust_id,type,cp_country,amountのヘッダーで書いてください。
txnとhigh_risk_countryをcp_countryでINNER JOINすれば終わりです。金額条件はありません。AE宛に出ていった2,000万ウォンは、引っかかってはいけません。
R4・R5の顧客確認の経過と休眠後の活動
R4の結果を/root/bank/kyc_due.csvにcust_id,risk_grade,pep,kyc_date,days_sinceのヘッダーで、R5の結果を/root/bank/dormant.csvにcust_id,last_activity,first_txn_date,dormant_days,amountのヘッダーで書いてください。
R4はcustomerだけでできます: (risk_grade='HIGH' OR pep=1) AND julianday('2026-08-31')-julianday(kyc_date) >= 365。R5は、顧客ごとのMIN(ts)を先に求めて(CTE)、その取引を再び結合してください。長く休んでいても少額の人は、引っかかってはいけません。
ルールを合わせてアラートスコアを出す
ルールに1つでも引っかかった顧客を、/root/bank/alerts.csvにcust_id,rules,scoreのヘッダーで、スコアの降順(同じならcust_idの昇順)で書いてください。
ステップ2から5までのCSV5つから、cust_idを集めて、顧客ごとのルールの集合を作り、重み(R1 1・R2 3・R3 2・R4 1・R5 2)を足してください。rulesは番号順に+でつなぎ、ソートはスコアの降順・cust_idの昇順です。
疑わしい取引報告の草案を事実だけで書く
スコアが最も高い顧客の報告草案を、/root/bank/str_<cust_id>.jsonに書いてください。キーは、cust_id、rules(一覧)、score、txn_ids(その顧客がルールに引っかかった取引番号の一覧)、total_amount(その取引の金額の合計)、narrative(100字以上、事実だけ)、prepared_by(人の名前ではなく役割)、customer_notified(必ずfalse)です。
alerts.csvの最初の行が対象です。その顧客が引っかかったルールごとに、該当する取引(R1はその日の現金、R2は800万から1,000万の現金、R3は高リスク国の取引、R5は8月の最初の取引)を集めて、重複を除いてソートしてください。ルールに引っかからなかった取引は入れません。json.dumpで書き、customer_notifiedはPythonのFalseです。
AML点検報告書を書く
/root/bank/aml_report.mdに、## Detection rules and results(検知ルールと結果)、## Reporting targets(報告対象)、## False positives and limitations(誤検知と限界)、## Confidentiality and personal information(守秘と個人情報)の4つの節で報告書を書いてください。
taskの節の見出し4つ(バッククォートの中の英語の見出し、または括弧の中の日本語の見出しや、同じ意味の韓国語・中国語の見出し)を、それぞれ## で始まる1行で書いてください。検知ルールの節にはアラート顧客の数を「N人」のように、報告対象の節には最高スコアの顧客番号を、守秘の節には報告した事実を顧客に知らせないという文(no tipping-off。例: the customer is not notified)を入れてください。実名は書かないでください。