A graph-powered console that surfaces hidden collusion in GST procurement data: employees quietly approving invoices for vendors they're secretly tied to, and clusters of "different" vendors that all funnel money into one bank account. Built on CognoDB (managed graph database, openCypher over Bolt) with the official Neo4j driver.
A non-technical reviewer opens the app, sees a ranked list of alerts in plain English, clicks one, and watches the collusion loop light up in the network.
Live demo:
<your-vercel-url>Screen recording:<your-loom-or-video-link>
The whole product is one question: given an invoice, who is secretly connected to whom, and how? That question is about paths and cycles, not rows.
The core fraud signal is a loop:
Employee ──APPROVED──▶ Invoice ◀──SUBMITTED── Vendor
▲ │
└──────────── shares bank account ─────────────┘
An employee approves an invoice for a vendor who shares a bank account (or address, or phone) with that same employee. Detecting it means walking a variable-length path that returns to where it started.
-
In SQL, this is a recursive self-join across five tables (
employees,approvals,invoices,vendors,bank_accounts) with a join condition that ties the two ends of the chain back together. Adding "…or a shared address, or a shared phone, up to N hops apart" multiplies the join permutations and the query becomes unreadable and slow. -
In Cypher, it's one pattern that reads like the fraud itself:
MATCH (e:Employee)-[:APPROVED]->(i:Invoice)<-[:SUBMITTED]-(v:Vendor) MATCH (v)-[:HAS_ACCOUNT|LOCATED_AT|HAS_PHONE]->(s)<-[:HAS_ACCOUNT|LIVES_AT|HAS_PHONE]-(e) RETURN e, v, s, collect(i)
The relationships are first-class, so traversals are index-free adjacency hops rather than joins. Shell-cluster detection (many vendors → one account) and shortest-path explanations ("how is this employee connected to this vendor?") fall out of the same model for free. That's a graph database genuinely earning its place, not a relational schema in disguise.
Six labeled node types and typed relationships:
graph LR
E[Employee]
V[Vendor]
I[Invoice]
B[BankAccount]
A[Address]
P[Phone]
E -- APPROVED --> I
V -- SUBMITTED --> I
E -- HAS_ACCOUNT --> B
V -- HAS_ACCOUNT --> B
E -- LIVES_AT --> A
V -- LOCATED_AT --> A
E -- HAS_PHONE --> P
V -- HAS_PHONE --> P
| Node | Key properties |
|---|---|
Employee |
id, name, department, role |
Vendor |
id, name, gstin, registeredDate |
Invoice |
id, number, amount, date, status |
BankAccount |
id, accountNumber, ifsc |
Address |
id, line, city, pincode |
Phone |
id, number |
The fraud signals are shared identity nodes: when an Employee and a Vendor point
at the same BankAccount / Address / Phone, that shared node is the smoking
gun. The seed data is mostly clean, with 3 shared-identity rings and 1 shell
cluster deliberately planted so detection has something to find.
All queries are parameterised through the official driver (see lib/queries.ts).
None are string-concatenated.
- Shared-identity collusion (multi-hop cycle, SQL-awkward) — the loop above.
Returns each
(employee, vendor, shared-node)triple plus the invoices and total exposure. - Shell-vendor cluster — one bank account, many vendors:
MATCH (b:BankAccount)<-[:HAS_ACCOUNT]-(v:Vendor) WITH b, collect(DISTINCT v) AS vendors WHERE size(vendors) > 1 RETURN b, vendors
- Vendor risk scoring — a single query aggregates each vendor's invoice volume, whether it shares identity with an approver, and whether it shares an account with other vendors; the score is derived from those graph signals.
- Vendor neighbourhood (variable-length traversal) —
(v)-[*1..2]-(m)expands a vendor's two-hop world for the drill-down view.
app/
page.tsx Console: alerts, watchlist, interactive graph
layout.tsx Fonts + shell
components/
GraphView.tsx Force-directed canvas (react-force-graph-2d)
ui.tsx Risk meter, legend, loading / empty / error states
graph-style.ts Node colours + sizes
api/
health/route.ts DB liveness probe → drives the Live/Offline badge
rings/route.ts Detected collusion rings + shell clusters
graph/route.ts Overview graph (union of all ring subgraphs)
vendors/route.ts Ranked vendor watchlist
vendor/[id]/route.ts Two-hop neighbourhood for drill-down
lib/
neo4j.ts Driver singleton (serverless-safe) + run() helper
queries.ts All Cypher + result mappers
types.ts Shared types
scripts/
seed.ts Generates realistic data + planted fraud, loads via UNWIND
Notes worth calling out:
- Serverless-safe driver. The Neo4j driver is cached on
globalThiswith a small connection pool (lib/neo4j.ts), so Vercel's many function instances don't exhaust the free tier's 200-connection ceiling. - Secrets from the environment only. The URI and password are read from
NEO4J_*env vars and never committed..env.exampleis the template;.env.localis git-ignored. - Graceful degradation. Every API route returns a
503with a helpful hint when CognoDB is unreachable; the UI shows an Offline badge and a retryable error state instead of a blank screen. - Server-only DB access.
neo4j-driverruns only in Node route handlers; nothing touches the database from the browser.
- Sign up at https://console.cognodb.com/signup (free tier, no credit card).
- Create a free c0 instance and pick a region — it provisions in under a minute.
- Copy the connection URI (
bolt+s://<instance-id>.databases.cognodb.cloud) and the generated password for usercognodb. The password is shown once — copy it now.
git clone <your-repo-url>
cd gst-fraud-graph
npm install
cp .env.example .env.local
# edit .env.local with your CognoDB URI, username (cognodb), and passwordnpm run seed # loads ~45 vendors, 18 employees, ~250 invoices + planted fraud
npm run dev # http://localhost:3000.env.local:
NEO4J_URI=bolt+s://<instance-id>.databases.cognodb.cloud
NEO4J_USERNAME=cognodb
NEO4J_PASSWORD=<your-password>
- Push to GitHub and import the repo in Vercel.
- Add
NEO4J_URI,NEO4J_USERNAME,NEO4J_PASSWORDas environment variables. - Deploy. Run
npm run seedlocally once against the same instance so the hosted app has data.
| Overview — map of suspicious activity | Ring selected — collusion loop traced |
|---|---|
docs/overview.png |
docs/ring.png |
(Add screenshots after your first run.)
Next.js 14 (App Router, TypeScript) · CognoDB via neo4j-driver (Bolt) ·
Tailwind CSS · react-force-graph-2d.