Inspiration
Most fraud detection treats every transaction as an isolated row — extract some features, score it, move on. But the fraud that actually costs money at scale (mule networks, layering chains, smurfing rings) isn't visible in any single transaction's features. It's visible in the relationships: shared devices, shared IPs, funds hopping through a chain of intermediary accounts. A row-level model literally cannot see that structure no matter how well-tuned it is.
That gap became concrete the moment we tested against a real Nigerian bank statement instead of synthetic data — the messy realities of real financial data (ambiguous date formats, inconsistent column naming across banks, statements with hundreds of transactions) made it obvious that a genuinely useful fraud system has to be built for the real world from day one, not bolted onto a notebook demo afterward.
What it does
A graph-native, multi-domain fraud detection platform. It builds a heterogeneous transaction graph (accounts, devices, IPs, merchants as connected entities) and runs a stack of models — GraphSAGE, GAT, a Heterogeneous Graph Transformer, R-GCN, hypergraph convolution, XGBoost, and unsupervised anomaly detectors — combined into a calibrated ensemble. The same architecture generalizes across fraud domains: it's validated on both payments fraud and AML/mule-network monitoring using identical model code, with only the data schema changing between them.
On top of the model layer, users can upload their own bank statement (CSV or PDF) and get fresh, unsupervised fraud-pattern analysis — Benford's Law violations, structuring, velocity spikes, recipient concentration — computed from scratch on their own transaction history, with PDF statements parsed via Claude's native document understanding.
How we built it
The core stack is PyTorch + PyTorch Geometric for graph construction and the GNN/HGNN models, XGBoost for the tabular baseline, a conditional WGAN-GP for minority-class augmentation, and an autoencoder + IsolationForest pair for anomaly scoring — all stacked through a calibrated logistic-regression ensemble. Serving is a Flask API; the frontend is React.
The methodological backbone is what we call "feature-camouflage discipline": synthetic fraud rows are deliberately made ~85% statistically indistinguishable from legitimate rows in their own features, so a tabular model can't cheat by learning an obvious leak — any real advantage the graph models show has to come from structure, not feature engineering. To prove the platform actually generalizes rather than just claiming to, we built a domain-agnostic schema (EntityRole / CollusionEdgeSpec / DomainGraphSchema) and proved it reproduces the original hardcoded payments graph builder exactly via an equivalence test, before using it to stand up the AML domain from scratch.
PDF statement extraction uses Claude's native document input with structured outputs (a Pydantic schema Claude fills directly), with a pypdf precheck for corrupt/encrypted files.
Challenges we ran into
The camouflage-discipline bug bit us twice: the first synthetic generator leaked fraud signal into raw features, and XGBoost alone hit near-perfect AUPRC — leaving nothing for the graph models to prove. We caught the same mistake independently when building the second domain (AML), but that time an automated regression test caught it in seconds instead of manual review catching it late.
Separately, the graph transformer models (HGT/R-GCN) initially overfit by memorizing entity embeddings rather than learning structure — held-out performance stayed mediocre while training loss collapsed to near-zero. Fixed with validation-based early stopping.
The most humbling bugs came from testing against a real user's real bank statement: dates like 02-01-2026 (day-first) were silently misparsed as month-first by our date parser — no error, just quietly wrong dates that would have poisoned every timing-based pattern detector — and MoneyIn/MoneyOut column headers (no space) didn't match our alias list, which assumed "money in" with a space. Neither bug would ever have surfaced from clean synthetic test fixtures.
Accomplishments that we're proud of
The Heterogeneous Graph Transformer beat the tabular XGBoost baseline by 115% relative AUPRC on payments and 369% on AML — using the literal same, unmodified model code in both domains. That's the actual evidence the platform generalizes, not just an architecture diagram claiming it does.
We're also proud that the camouflage-discipline methodology turned out to be portable enough to automatically catch the same class of bug in a completely different domain, and that this isn't just a research prototype — it's a full working product: trained models, a serving API, a React frontend, real CSV/PDF statement upload tested end-to-end against an actual user's actual bank statement (bugs found and fixed in the process), backed by 80+ passing automated tests and a full research write-up.
What we learned
Coordinated fraud is fundamentally a graph problem, and the AUPRC gap between XGBoost and HGT is the empirical proof — no amount of per-row feature engineering closes it. But that proof is only trustworthy if the evaluation methodology itself is rigorous: without the camouflage discipline, temporal splitting, and fraud-appropriate metrics (AUPRC, recall@k — not plain accuracy), you get impressive-looking numbers that are secretly just measuring feature leakage.
We also learned that generalization claims need to be proven, not assumed — the equivalence test against the original hardcoded graph builder is what turned "we think this generalizes to other domains" into "we proved it." And synthetic data, no matter how carefully designed, doesn't surface the assumptions that break in production — those only showed up once we tested against one real bank statement.
What's next for End-to-end fraud detection system
Extend the generalized domain schema to the other fraud types it was designed for — insurance claims fraud, marketplace fraud, account-takeover — each following the same "adapter + entity roles + collusion edges" recipe already proven on AML. Move from static-snapshot graph construction toward incremental/streaming graph updates, so the ensemble can score genuinely novel transactions in real time instead of only transactions already present in a trained snapshot. Close the loop on the statement-analysis feature: it's currently deliberately unsupervised (fresh-fit per statement, not routed through the synthetic-trained ensemble) for honesty about what can be claimed on real, unlabeled data — once real labeled fraud data is available, bring it into a supervised feedback loop. Deeper Open Banking integration (Mono, Paystack) for automatic statement ingestion instead of manual upload. Package this as an embeddable fraud-screening API for fintechs and banks in markets where mule-network and structuring fraud is a major, underserved operational cost.
Built With
- axios
- firebase
- gemini
- javascript
- mono
- numpy
- pandas
- paystack
- pypdf
- python
- pytorch
- pytorch-geometric
- react
- scikit-learn
- tailwindcss
- vite
- xgboost
Log in or sign up for Devpost to join the conversation.