A production-grade AML (Anti-Money Laundering) graph intelligence engine that detects money mule networks in financial transaction data using purely deterministic, explainable graph algorithms — zero machine learning. Deployed on Microsoft Azure App Service with Vercel frontend hosting.
Getting Started · Architecture · Algorithms · API Reference · Deployment
| Pattern | Algorithm | Description |
|---|---|---|
| Circular Fund Routing | Depth-limited DFS | Directed cycles of length 3–5 |
| Smurfing | Sliding-window fan-in/fan-out | ≥10 unique counterparties within 72 h |
| Layered Shell Networks | Shell-DFS | Chains ≥3 through low-activity nodes |
CSV Upload
│
▼
┌───────────────┐
│ Validator │ Schema check, type coercion, timestamp parsing
└──────┬────────┘
│
▼
┌───────────────────┐
│ TransactionGraph │ NetworkX DiGraph + adjacency lookups O(n)
└──────┬────────────┘
│
┌───┴────────────┬──────────────────┐
▼ ▼ ▼
CycleDetector SmurfDetector ShellDetector
(DFS depth 5) (Sliding window) (Shell DFS)
│ │ │
└────────────────┴──────────────────┘
│
▼
FeatureExtractor
(Structural + Flow + Temporal)
│
▼
RiskScorer (Weighted rules)
+ MerchantDampeningRule
│
▼
RingAggregator → JSON Output
│
▼
React + Cytoscape.js Visualisation
|
|
- Python 3.11+
- Node.js 18+
cd backend
pip install -r requirements.txt
# Run development server
uvicorn main:app --reload --port 8000Backend: http://localhost:8000
Swagger UI: http://localhost:8000/docs
cd frontend
npm install
npm run devFrontend: http://localhost:5173
-
Prepare a CSV with the following columns:
transaction_id, sender_id, receiver_id, amount, timestamp -
Generate sample data (optional):
python generate_sample.py # → sample_transactions.csv (500 rows with embedded fraud patterns) -
Upload & Analyse:
- Open the web app → Upload CSV → Click Analyse Transactions
-
Explore Results:
- Red nodes = high risk, purple border = in a ring
- Click any node to see scores and patterns
- Expand fraud ring rows to see member accounts
- Download JSON to export the full detection report
- AI Analyzer for LLM-powered fraud ring explanations
Algorithm: Depth-limited iterative-deepening DFS
Complexity: O(V × d⁵) where d = average out-degree
For each node v:
DFS(v, path=[v], depth_limit=5)
For each successor u of current node:
If u == v AND len(path) ∈ [3,5]:
→ record cycle
Elif u not in path:
→ recurse
Normalise: rotate cycle to lex-smallest prefix → deduplicate
Parameters: min cycle length = 3, max = 5
Algorithm: Two-pointer sliding window
Complexity: O(n log n) — dominated by timestamp sort per account
For each account a:
Sort transactions by timestamp
Two-pointer window of width 72 hours:
Count unique counterparties in window
If count ≥ 10 → SMURFING RING
Thresholds: 72-hour window, ≥10 unique counterparties
Algorithm: Shell-filtered DFS
Complexity: O(V × k × d^k) where k = max_chain_depth (capped at 8)
Precompute shell_accounts = {a : tx_count(a) ≤ 3}
For each non-shell start node s:
DFS through only shell intermediaries
If path length ≥ 3 → record chain
Shell threshold: accounts with
total_tx_count ≤ 3
Fully deterministic weighted scoring — no ML involved.
| Signal | Score |
|---|---|
| Cycle length 3 | +40 |
| Cycle length 4 | +35 |
| Cycle length 5 | +30 |
| Smurfing fan-in hub | +45 |
| Smurfing fan-out hub | +40 |
| Smurfing member | +20 |
| Shell intermediary | +25 |
| Shell final beneficiary | +20 |
| High clustering coefficient (>0.5) | +10 |
| High burst score (>0.7) | +15 |
| High uniformity score (>0.8) | +10 |
| High tx velocity (>10/hr) | +10 |
- Scores capped at 100, floored at 0
- Rounded to 2 decimal places
High-volume legitimate merchants can trigger false positives. The dampening rule fires when:
distributor_flag = True
AND time_span_hours > 720 # active > 30 days
AND amount_variance > median # diverse transaction sizes
AND unique_receivers > 50 # genuinely broad customer base
Effect: raw score ×
0.70(−30% reduction) +"merchant_dampening_applied"pattern flag for audit trail.
| Operation | Complexity |
|---|---|
| Graph construction | O(n) |
| Cycle detection | O(V × d⁵) |
| Smurfing detection | O(n log n) |
| Shell detection | O(V × d⁸) with pruning |
| Feature extraction | O(n + E) |
| Risk scoring | O(A) — A = accounts |
| Total (practical) | ≤ O(n log n) |
Designed to handle 10,000 transactions in ≤ 30 seconds.
| Method | Path | Description |
|---|---|---|
GET |
/health |
Liveness check |
POST |
/analyze |
Full analysis → JSON report |
POST |
/graph-data |
Full analysis + Cytoscape elements |
POST |
/explain-fraud-rings |
AI-powered fraud ring explanations |
Both upload endpoints accept
multipart/form-datawith afilefield.
Example Response
{
"suspicious_accounts": [
{
"account_id": "ACC_F001",
"suspicion_score": 87.50,
"detected_patterns": ["cycle_length_3", "high_velocity"],
"ring_id": "RING_001"
}
],
"fraud_rings": [
{
"ring_id": "RING_001",
"member_accounts": ["ACC_F001", "ACC_F002", "ACC_F003"],
"pattern_type": "cycle",
"risk_score": 87.50
}
],
"summary": {
"total_accounts_analyzed": 500,
"suspicious_accounts_flagged": 15,
"fraud_rings_detected": 4,
"processing_time_seconds": 2.30
}
}See AZURE_DEPLOYMENT_GUIDE.md and QUICK_START_AZURE.md for detailed instructions.
Key environment variables:
| Variable | Description |
|---|---|
GROQ_API_KEY |
Groq API key for AI-powered analysis |
Backend (Web Service)
- Push to GitHub
- New Web Service on render.com
- Root directory:
backend - Build:
pip install -r requirements.txt - Start:
uvicorn main:app --host 0.0.0.0 --port $PORT
Frontend (Vercel — Recommended)
- Go to vercel.com
- Import your GitHub repository
- Root directory:
frontend - Env var:
VITE_API_URL=https://your-backend-url.com - Deploy!
MM_Detection/
├── backend/
│ ├── main.py # FastAPI app + AI endpoint
│ ├── requirements.txt
│ ├── graph/
│ │ └── builder.py # TransactionGraph (DiGraph + lookups)
│ ├── detection/
│ │ ├── cycle_detector.py # DFS cycle detection
│ │ ├── smurf_detector.py # Sliding-window smurfing
│ │ └── shell_detector.py # Layered shell chains
│ ├── scoring/
│ │ ├── feature_extractor.py # Per-account feature computation
│ │ └── risk_scorer.py # Deterministic weighted scoring
│ └── utils/
│ ├── validator.py # CSV schema validation
│ ├── aggregator.py # Ring merging + risk aggregation
│ └── ring_consolidator.py # Jaccard-based ring deduplication
├── frontend/
│ ├── package.json
│ └── src/
│ ├── pages/
│ │ └── Index.tsx # Main dashboard page
│ └── components/
│ ├── Upload # Drag-and-drop CSV upload
│ ├── GraphView # Cytoscape.js graph
│ ├── RingTable # Fraud ring summary
│ └── AIExplanation # AI-powered ring analysis
├── render.yaml # Render deployment config
└── README.md
| Limitation | Details |
|---|---|
| Cycle depth | Capped at 5 hops (configurable via MAX_CYCLE_LEN) |
| Smurfing threshold | 10 unique counterparties (adjust FAN_THRESHOLD in smurf_detector.py) |
| Shell threshold | ≤3 total transactions — may flag genuinely new accounts |
| Graph layout | Cola layout can be slow for >1,000 nodes; falls back to cose |
| Persistence | No result storage; each upload is a fresh analysis session |
| CSV size | Tested up to 10,000 transactions |