Real-time risk scoring with JetGraph
Score every event against committed graph history, then write the outcome back. JetGraph supplies graph signals; your application owns the model.
The scoring loop
Event → Query historical graph → Generate signals (velocity, novelty, context, cardinality) → Score (your rules or model) → Record event → Propagate risk (optional FlagNode)
Querying before insert avoids reading your own uncommitted write and keeps novelty honest. Recording declines is required: otherwise velocity under-counts attackers you already blocked.
Signals the engine can produce in this loop
| Signal | API / idea | Role in a score |
|---|---|---|
| Velocity | GetVelocityCount | Burst / session volume |
| Novelty | edge existence / compact pair store | First-time counterparties |
| Neighbor risk | node context after flags | Contagion from known-bad nodes |
| Neighborhood | GetNeighbors / Cypher MATCH | Shared devices, IPs, merchants |
| Cardinality | HyperLogLog GetNeighborCount | Fan-out of an identifier |
| Windows | QueryEdgeWindow | Amount or count in a time range |
Architecture relative to a feature store
A feature store is typically tabular, batch- or stream-updated, and keyed by entity ID. JetGraph is a live graph: features that are relationships stay relationships. Many stacks will still have a feature store for non-graph features (credit bureau, device ML). JetGraph replaces the “join five tables plus a nightly graph job” part of authorize-time scoring. Comparison: vs feature stores.
Technical sketch (Rust-shaped)
// Phase 1 — historical graph
let v1h = features.get_velocity_count(card, "TRANSACTS_AT", 3600).await?;
let is_new = !graph.edge_exists(card, merchant, "TRANSACTS_AT").await?;
let ctx = features.get_fraud_context(card).await?;
// Phase 2 — your score
let mut risk = 0.0;
if is_new { risk += 0.15; }
if v1h > 30 { risk += 0.25; }
risk += 0.5 * ctx.max_neighbor_fraud_score;
// Phase 3 — always insert; flag on decline
graph.create_edge(card, merchant, "TRANSACTS_AT", decision).await?;
if risk > 0.7 { features.flag_node(card, 0.85, "auto_decline").await?; }
Runnable-oriented versions live in code examples and Rust client docs. Cypher can fetch related context; feature APIs remain the O(1) path for velocity and contagion.