- Coordinated activity visible as connected networks rather than isolated alerts
- Graph features available to existing risk models in real time
- Investigators reviewing whole rings with evidence for every link
- Entity matches explainable and correctable by analysts
Anonymised composite. Client names, identifying details and confidential figures are withheld; public facts are cited in the references.
The situation
A fintech offering accounts, cards and small loans had a mature rules engine and a transaction-level risk model. Both looked at one event or one account at a time. The losses that hurt most came from coordinated activity: clusters of new accounts opened from the same few devices, with addresses written slightly differently, paying out to a shared set of payees. Each account looked unremarkable alone. The pattern only appeared when someone noticed the connections by hand, usually after the money had moved.
The question was how to make those connections visible automatically, fast enough to matter, without replacing the decisioning stack that already worked.
What had to be true
- Resolve before you connect: one entity per real person, device, address and payee.
- Explainable links: every edge in a ring traceable to the evidence behind it.
- Features, not a new decision engine: graph signals feed existing models and rules.
- Fresh enough for onboarding and payouts, the two moments where stopping fraud pays.
- Investigators in the loop, able to confirm, split or merge entities.
Architecture
Confirmed fraud outcomes from closed cases were fed back as events too, so proximity to known fraud stayed current and the risk models had better labels over time.
The graph model was kept deliberately small, a handful of entity and relationship types with clear definitions, following the approach in ontology vs knowledge graph vs semantic layer.
The data flow, end to end
- StreamOnboarding, login and payment events published to Kafka, keyed by source identifiers
- NormaliseStandardise addresses, names, phone numbers and device fingerprints
- ResolveBlock into candidate pairs, score matches, assign stable entity IDs with match evidence
- ConnectUpsert entities and relationships into the graph incrementally
- FeaturiseCompute component size, shared-attribute counts and distance to confirmed fraud
- ActFeatures served to risk scoring; high-risk rings opened as investigator cases
Current entity state was held in compacted topics, where Kafka retains the latest value for each key, so downstream services could rebuild their view without replaying full history4. Streaming patterns are in change data capture and event streams.
The hard part: entity resolution you can defend
Matching everything to everything is quadratic, so resolution started with blocking: cheap keys that limit comparisons to plausible candidates, a well-studied family of techniques1. Matching then used a probabilistic, Fellegi-Sunter style model of the kind implemented in Splink, which scores each comparison field and can be trained without labelled pairs2. What made it defensible:
- Every merge stored the field-level evidence and score, so an investigator could see why two records became one entity.
- Thresholds were set separately for automatic merges and for "possible match" links that investigators saw but models treated with caution.
- Shared infrastructure entities (a popular office address, a carrier-grade NAT IP) were flagged so they did not collapse unrelated customers into giant false rings.
- Analyst merges and splits were written back as overrides that survived reprocessing.
Ring candidates came from connected components over the resolved graph, which groups nodes reachable from one another regardless of edge direction3. More detail in entity resolution for knowledge graphs.
In fraud work a false merge is not a modelling error, it is an accusation. Explainable matches and reversible overrides are what make a graph usable by investigators.
How it ran
- Resolves entities
- Updates graph
- Serves ring features
- Reads features
- Scores event
- Opens case on high risk
- Reviews ring and evidence
- Confirms, splits or merges
- Records outcome
- Entity and relationship types
- Known fraud cases mapped
- Data sources inventoried
- Historical resolution
- Rings found in past cases
- Investigator feedback
- Incremental resolution
- Online features
- Model integration
- Investigator UI
- Override workflow
- Runbooks and hand-off
Key decisions and trade-offs
| Decision | Alternative | Why |
|---|---|---|
| Probabilistic, explainable matching | Exact keys only | Catches variant spellings while keeping evidence for every merge |
| Graph features into existing models | Separate graph-based decision engine | Adds signal without replacing a working decision stack |
| Small, defined graph model | Load every attribute as a node | Fewer false connections and simpler queries |
| Batch proof before streaming | Build real-time first | Validates value on known cases before paying for latency |
| Durable analyst overrides | Recompute from scratch each run | Human corrections are not lost on reprocessing |
What I'd do again, and differently
Again: start with historical confirmed cases and show investigators the rings the graph would have surfaced. Differently: build the shared-infrastructure entity list on day one. It is the main source of false rings, and it is easier to design for than to clean up.
If you are considering a knowledge graph for risk or investigations, the ontology and knowledge graph page describes how I run these engagements, or analyse your challenge and bring the result to a call.
- 1A Survey of Blocking and Filtering Techniques for Entity ResolutionPapadakis et al., arXiv · 2019
- 2Splink: probabilistic record linkage at scaleUK Ministry of Justice (GitHub)
- 3Weakly Connected Components (Graph Data Science)Neo4j documentation
- 4Log compaction (Kafka design)Confluent documentation