Your transaction monitoring system flags 10,000 alerts each month. Your team investigates 9,700 false positives and finds 300 genuine risks. That's a 97% noise rate, consuming analyst time that should go toward investigating actual money laundering.
Machine learning can cut that false positive volume by up to 70%, but only if you configure alert scoring correctly. This script provides a framework for prioritizing AML alerts using behavioral risk signals and historical investigation outcomes.
How the Script Works
This alert prioritization script assigns dynamic risk scores to transaction monitoring alerts before they reach your investigation queue. It uses three weighted inputs: transaction pattern deviation, customer risk profile, and historical alert resolution data from your case management system.
The output is a ranked alert queue where high-scoring items represent genuine risk concentrations. Your analysts work the top 30% of alerts first, while low-scoring alerts route to automated dispositioning or periodic sampling reviews.
You'll need basic SQL access to your transaction monitoring database and either Python or your AML platform's native scripting environment. Most enterprise AML systems, including those from ComplyAdvantage and Unit21, support custom scoring logic through APIs or configuration modules.
Prerequisites
Before implementing this script, ensure you have:
- Historical alert data: At least 12 months of closed alerts with disposition codes (true positive, false positive, escalated to SAR).
- Customer risk ratings: Current risk scores from your AML customer identification program (low, medium, high, or numeric scale).
- Transaction metadata access: Fields including transaction amount, counterparty country, payment method, and velocity metrics.
- Approved model governance: Your compliance or model risk team must review any scoring logic affecting regulatory obligations.
If your organization operates under OCC or Federal Reserve supervision, document your scoring methodology in your BSA/AML compliance program. The FFIEC BSA/AML Examination Manual expects you to explain how technology-driven prioritization supports your risk-based approach.
The Alert Prioritization Script
import pandas as pd
from datetime import datetime
def calculate_alert_priority_score(alert_data):
"""
Assigns priority scores to AML transaction monitoring alerts.
Parameters:
alert_data (dict): Alert attributes from monitoring system
Returns:
dict: Original alert data plus priority_score and priority_tier
"""
# Initialize base score
score = 0
# COMPONENT 1: Transaction Pattern Deviation (0-40 points)
# Compare transaction to customer's 90-day baseline
if alert_data['amount_vs_baseline'] > 3.0: # 3x normal volume
score += 40
elif alert_data['amount_vs_baseline'] > 2.0:
score += 25
elif alert_data['amount_vs_baseline'] > 1.5:
score += 10
# Velocity scoring: transactions in 24-hour window
if alert_data['txn_count_24h'] >= 10:
score += 20
elif alert_data['txn_count_24h'] >= 5:
score += 10
# COMPONENT 2: Customer Risk Profile (0-30 points)
customer_risk_map = {
'high': 30,
'medium': 15,
'low': 5
}
score += customer_risk_map.get(alert_data['customer_risk_tier'], 0)
# PEP or [sanctions screening](/glossary/sanctions-screening) match in last 90 days
if alert_data['recent_watchlist_match']:
score += 15
# COMPONENT 3: Historical Pattern Match (0-30 points)
# Compare to alerts that became SARs
if alert_data['matches_sar_pattern']:
score += 30
elif alert_data['matches_escalated_pattern']:
score += 15
# Geographic risk: high-risk jurisdiction per FATF
high_risk_countries = alert_data.get('high_risk_countries', [])
if alert_data['counterparty_country'] in high_risk_countries:
score += 10
# Structuring indicators: amounts just below reporting threshold
if 9000 <= alert_data['transaction_amount'] <= 9999:
score += 15
# Assign priority tier based on total score
if score >= 70:
tier = 'IMMEDIATE'
elif score >= 50:
tier = 'HIGH'
elif score >= 30:
tier = 'MEDIUM'
else:
tier = 'LOW'
alert_data['priority_score'] = score
alert_data['priority_tier'] = tier
alert_data['scored_timestamp'] = datetime.now()
return alert_data
# Example usage with alert queue
def process_alert_queue(alert_queue_df):
"""
Apply scoring to full alert queue and return prioritized list.
"""
scored_alerts = []
for index, alert in alert_queue_df.iterrows():
scored_alert = calculate_alert_priority_score(alert.to_dict())
scored_alerts.append(scored_alert)
# Convert back to DataFrame and sort by score
prioritized_queue = pd.DataFrame(scored_alerts)
prioritized_queue = prioritized_queue.sort_values(
by='priority_score',
ascending=False
)
return prioritized_queue
# Validation: Compare scoring to historical outcomes
def validate_scoring_accuracy(scored_alerts, historical_outcomes):
"""
Calculate precision of high-priority scoring against known outcomes.
"""
high_priority = scored_alerts[scored_alerts['priority_tier'].isin(['HIGH', 'IMMEDIATE'])]
true_positives = len(high_priority[high_priority['alert_id'].isin(
historical_outcomes[historical_outcomes['disposition'] == 'SAR']['alert_id']
)])
precision = true_positives / len(high_priority) if len(high_priority) > 0 else 0
return {
'high_priority_count': len(high_priority),
'true_positives': true_positives,
'precision': precision
}
Customizing the Script
Start with the scoring weights. The script assigns 40 points maximum for transaction deviation, 30 for customer risk, and 30 for historical pattern matching. Adjust these ratios based on what drives true positives in your institution's data.
If you're a digital bank with high transaction velocity, increase the weight on velocity metrics and decrease baseline deviation scoring. If you serve high-net-worth clients where large transactions are normal, focus more on counterparty risk and geographic indicators.
Tune your thresholds using historical data: Run the scoring logic against your last 2,000 closed alerts. Calculate precision (what percentage of high-scored alerts were true positives) and recall (what percentage of true positives scored high). Aim for precision above 15% on high-priority alerts, which represents meaningful noise reduction from a typical 3% baseline hit rate.
Add institution-specific risk indicators: The script includes structuring detection (amounts between $9,000 and $9,999) as an example. Add your own patterns: repeated rounded amounts, beneficiary name mismatches, or transaction timing that matches your SAR filing history.
Connect to your case management system: Most AML platforms expose APIs for alert data. Replace the example DataFrame input with a query to your monitoring system's database. Update alert records with the calculated priority_score so analysts see it in their investigation interface.
Validation Steps
Before deploying this script in production, validate it against three months of historical alerts:
Backtest precision: Score your past alerts and measure how many high-priority items were actual true positives. If precision is below 10%, adjust your weights.
Check for bias: Break down scoring accuracy by customer segment, transaction type, and geographic region. If the script consistently misses risks in one segment, add compensating factors.
Monitor score distribution: Aim for roughly 20-30% of alerts scoring as high or immediate priority. If 60% of alerts score high, your thresholds are too loose and you haven't reduced analyst workload.
Compare to manual triage: Have experienced analysts manually prioritize 100 alerts, then compare their rankings to the script's output. Investigate disagreements to find blind spots in your logic.
Document your validation results and scoring methodology. When examiners review your BSA/AML program, they'll ask how you determined your prioritization approach was effective. Show them the precision metrics and the reduction in time-to-investigation for genuine risks.
This script doesn't replace analyst judgment. It reduces the noise so your team can focus on complex investigations that actually matter. Refine the weights quarterly as your risk profile and customer base evolve.



