Rule-based fraud scoring with 5 behavioural indicators — powered by Python & pandas.
- Problem Statement
- Project Structure
- Dataset
- Detection Logic
- Risk Scoring
- Quick Start
- Results
- Future Improvements
- License
SIM-box fraud (also called bypass fraud or interconnect bypass fraud) is one of the costliest threats facing telecommunications operators worldwide, accounting for billions of dollars in lost revenue annually.
A SIM-box is a device loaded with dozens — sometimes hundreds — of local SIM cards. International VoIP calls are routed into the device, which then re-originates them as local calls, bypassing legitimate international termination fees. From the network's perspective these calls appear to originate from ordinary mobile subscribers.
| Challenge | Description |
|---|---|
| Volume | Millions of CDRs generated every hour |
| Camouflage | SIM-box lines mimic real subscriber behaviour |
| Velocity | Fraud patterns evolve faster than manual rule updates |
This project provides an automated, rule-based detection pipeline that scores each caller across five behavioural indicators derived from CDR data alone — no signalling data or proprietary intelligence required.
simbox-fraud-detection/
│
├── data/
│ └── telecom_cdr.csv ← CDR dataset (real or synthetic)
│
├── notebooks/
│ └── exploration.ipynb ← EDA & visualisations
│
├── src/
│ ├── fraud_detection.py ← Main detection script
│ └── generate_sample_data.py ← Synthetic CDR generator (for testing)
│
├── outputs/
│ ├── suspicious_numbers.csv ← Final scored output
│ └── fig_*.png ← Charts produced by the notebook
│
├── README.md
└── requirements.txt
| Column | Type | Description |
|---|---|---|
caller |
string | Calling party number (MSISDN) |
destination |
string | Called party number |
timestamp |
datetime | Call start time (YYYY-MM-DD HH:MM:SS) |
caller_location |
string | Originating cell/region |
destination_location |
string | Terminating cell/region |
duration |
integer | Call duration in seconds |
caller,destination,timestamp,caller_location,destination_location,duration
9991111111,8881111111,2025-01-01 10:00:00,Delhi,Mumbai,45
9991111111,8882222222,2025-01-01 10:00:01,Delhi,Chennai,30
9991111111,8883333333,2025-01-01 10:00:02,Delhi,Kolkata,15
Run src/generate_sample_data.py from the project root to produce a realistic 9 000-row
test dataset containing 150 normal callers and 20 simulated SIM-box lines:
python src/generate_sample_data.pyThe pipeline computes five independent indicators per caller.
Each indicator fires (1 point) when it exceeds a configurable threshold.
unique_destinations = COUNT(DISTINCT destination) per caller
A genuine subscriber typically calls a limited, stable set of contacts.
A SIM-box auto-dials thousands of different numbers — destination diversity
is therefore one of the strongest single indicators.
Default threshold: ≥ 50 distinct destination numbers
short_call_ratio = COUNT(duration < 10 s) / total_calls
SIM-box equipment often makes rapid "probing" calls — a sub-10-second burst
used to verify that a destination number is live before billing.
A normal subscriber rarely has more than a few accidental short calls.
Default threshold: ≥ 70 % of calls shorter than 10 seconds
total_calls = COUNT(*) per caller in observation period
Human callers are physically limited in how many calls they can make. A SIM-box operating at capacity can generate hundreds of calls per hour.
Default threshold: ≥ 100 total outgoing calls
active_hours = COUNT(DISTINCT HOUR(timestamp)) per caller
People sleep. Machines don't.
A line active across 18 or more distinct hours of the day is almost certainly
running automated routing software rather than a human user.
Default threshold: ≥ 18 distinct hours active
time_gap = timestamp[i] - timestamp[i-1] (seconds)
loc_switch = destination_location[i] != destination_location[i-1]
flag = (time_gap <= 2) AND loc_switchThis is the most discriminative single indicator.
No human can make two consecutive calls to geographically different locations
within 2 seconds — the propagation delay alone exceeds this.
A SIM-box with multiple cards simultaneously routing calls will always produce
these physically impossible patterns.
Default threshold: ≥ 1 such event
| Score | Interpretation |
|---|---|
| 0 | No indicators triggered — normal caller |
| 1 | Mild anomaly — worth monitoring |
| 2–3 | Suspicious — flag for investigation |
| 4–5 | High confidence SIM-box — escalate |
All thresholds are configurable global variables at the top of
src/fraud_detection.py:
THRESHOLD_UNIQUE_DESTINATIONS = 50
THRESHOLD_SHORT_CALL_RATIO = 0.70
THRESHOLD_CALL_VOLUME = 100
THRESHOLD_ACTIVE_HOURS = 18
THRESHOLD_LOCATION_SWITCHES = 1
RISK_THRESHOLD = 2 # minimum score to flaggit clone https://github.com/<your-username>/simbox-fraud-detection.git
cd simbox-fraud-detection
pip install -r requirements.txtEither place your own telecom_cdr.csv in data/, or generate synthetic data:
python src/generate_sample_data.pypython src/fraud_detection.pyOutput:
────────────────────────────────────────────────────────────
SIM-BOX FRAUD DETECTION – SUMMARY
────────────────────────────────────────────────────────────
Total callers analysed : 170
Suspicious callers : 20 (11.8 %)
Risk threshold used : ≥ 2 / 5
────────────────────────────────────────────────────────────
Results are saved to outputs/suspicious_numbers.csv.
jupyter notebook notebooks/exploration.ipynb| caller | total_calls | unique_destinations | avg_duration | short_call_ratio | first_call | last_call | active_hours | location_switches | risk_score |
|---|---|---|---|---|---|---|---|---|---|
| 9900000001 | 334 | 319 | 8.46 | 0.87 | 2025-01-01 00:00:02 | 2025-01-01 00:26:41 | 1 | 212 | 4 |
| 9900000002 | 393 | 383 | 9.26 | 0.85 | 2025-01-01 00:00:03 | 2025-01-01 00:31:18 | 1 | 255 | 4 |
| Metric | Value |
|---|---|
| Total callers | 170 |
| Flagged as suspicious | 20 (11.8 %) |
| True SIM-box callers in dataset | 20 |
| Precision | 100 % |
| Recall | 100 % |
Note: Real-world performance will vary depending on traffic mix and threshold tuning. Use the notebook's threshold-sensitivity chart to calibrate for your network.
- Location switches alone perfectly separate SIM-box from normal callers in this dataset. Normal callers have 0 location switches; SIM-box callers average 150–250.
- Short-call ratio shows a clear bimodal distribution: normal callers cluster near 0; SIM-box callers cluster near 0.85–0.95.
- Active hours is the most ambiguous indicator: both classes can have 24/7 activity if the observation window spans multiple days.
(Replace this section with screenshots from your own notebook runs.)
Replace or augment the rule-based system with a supervised classifier (Random Forest, Gradient Boosting) trained on labelled CDR data. The five engineered features serve directly as input to any sklearn estimator.
A commented-out isolation_forest_detection() function is already included in
src/fraud_detection.py. Uncomment it to run unsupervised anomaly detection
alongside the rule-based method and compare results — useful when labelled
training data is unavailable.
Model callers and destinations as nodes in a directed graph (using NetworkX or Neo4j). SIM-boxes form characteristic "hub" patterns — high out-degree nodes with many weakly connected destinations — that are invisible in tabular CDR data but trivially detectable with graph centrality metrics.
Integrate the detection pipeline with Apache Kafka (streaming CDRs) and serve results through a Grafana or Streamlit dashboard. Risk scores would update in near real-time, enabling operators to block suspicious lines before significant revenue is lost.
Extend the ML section with additional unsupervised models:
- DBSCAN for density-based clustering of calling patterns
- Autoencoder (PyTorch/TensorFlow) for reconstruction-error based anomaly detection
- One-Class SVM for boundary-based novelty detection
MIT © 2025. Free to use, modify, and distribute with attribution.