Data Modeling
Data Modeling in JetGraph
Core Concepts
JetGraph organizes data into two primitives:
- Nodes — entities in your domain (e.g., a user, a card, a merchant, a device)
- Relationships (edges) — directed connections between two nodes (e.g.,
CARD -[:TRANSACTS_AT]-> MERCHANT)
Each node and edge has a type (also called a label) and an optional set of properties (key-value pairs).
Graph Diagram — Fraud Detection Domain
Schema Registration
Before any data can be written, you must declare your node types and edge types. This is done once at startup via the CALL db.* system procedures, then finalized with db.finalizeSchema().
CALL db.resetGraph() when ENABLE_ADMIN_RESET=true is set, or restart the container with a fresh volume.Edge Types, Histograms & Activity Windows
Edge types define both the graph relationship (CARD → MERCHANT) and the feature storage kept for every edge pair.
The storage layout is chosen once, when the edge type is registered. For ML/GNN use cases, the two most important read paths are:
graph.histogram for node-level bucketed counts and graph.edgeState for edge-pair state.
| Concept | Scope | What it stores | Typical use |
|---|---|---|---|
| Compact edge payload | One (src, dst, edge_type) pair |
tx_count, approx_sum, last_seen, activity bitmap, optional 8-bin amount histogram, optional bool flag |
Edge features such as count, amount sum, recency, velocity. |
| Activity bitmap | One edge pair | 21 recent time ticks, 3 bits each. Each tick count saturates at 7. | Fast edge-level velocity windows: last 5 min, 10 min, 1 hour, etc. |
| Node histogram | One (node, edge_type) side |
Two ring buffers: hourly slots and daily slots. Each slot has 8 amount/value buckets. | Node-level behaviour: amount distribution for a card over last 1h, 24h, 7d. |
Registering a transaction edge with full numeric features
To get the full compact payload (numeric bins + approximate sum), register the edge type with bin_boundaries.
The seven boundaries define eight buckets. tracked_property names the numeric value from ingest that is binned and summed,
usually "amount" for payments.
| Registration field | Meaning |
|---|---|
bin_boundaries | Seven numeric thresholds. They create eight buckets: <5, 5–25, 25–50, …, ≥1000. |
tracked_property | The numeric input field that feeds approx_sum and the bucket counters. For payments this is usually amount. |
activity_bitmap.tick_size_secs | The duration of one edge-level activity tick. With 300, [1, 2, 12] means last 5 min, 10 min, and 1 hour. |
node_histogram.hourly_slots | How many hourly histogram slots to keep. 24 keeps 24 hours of hourly detail. |
node_histogram.daily_slots | How many daily histogram slots to keep. 7 keeps 7 days of daily detail. |
Reading node histograms
graph.histogram returns aggregated bucket counts for one node and one edge type. It is node-level:
for CARD → PAYMENT, it counts all PAYMENT edges from that card, not a single merchant edge.
Example output counts = [0, 2, 12, 2, 0, 0, 0, 0] means: 0 events below 5, 2 events in 5–25,
12 events in 25–50, 2 events in 50–100, and none in the higher buckets.
Reading edge state
graph.edgeState reads one edge pair. It is the edge-level complement to graph.histogram.
Use it for per-edge features such as tx_count, approx_sum, last_seen, boolean flags, and activity windows.
If PAYMENT.activity_bitmap.tick_size_secs = 300, then [1, 2, 12] asks for counts over the last
5 minutes, 10 minutes, and 1 hour. The bitmap holds at most 21 ticks, so a 5-minute tick gives about 105 minutes
of edge-level activity history. Longer windows should come from node histograms.
graph.histogram for node features
(count distributions over 1h/24h/7d) with graph.edgeState for edge features
(pair count, amount sum, recency, and short velocity windows).
Data Modeling Best Practices
- Use meaningful, domain-specific labels —
CARD,MERCHANT,DEVICEinstead of generic names - Keep relationship types verb-like and directional —
TRANSACTS_AT,USES_DEVICE,OWNED_BY - Identify entities by a stable external ID (e.g., card PAN hash, merchant ID) not internal surrogate keys
- Model shared attributes as nodes, not properties — a shared device should be a
DEVICEnode that multiple cards point to, not a string property on each card - Avoid deeply nesting all data in properties — if you query by it, it should be a node or relationship