- Web
- Security
FinCrime Graph
An anti-money-laundering investigation workspace: it spots suspicious money movement, maps it as a graph, and takes each case from the first alert to a sealed, auditable conclusion.
- Personal project, full-stack
- Working prototype on synthetic data
- 20 modules, 91 REST endpoints
- Java, Spring Boot, Spring Security, PostgreSQL, Neo4j, Next.js, React, TypeScript

The problem
Financial-crime teams drown in alerts that live in separate tools. Alerts, customer data and case notes sit in different places, so analysts spend their time copying context instead of investigating.
The patterns that matter are structural. Money looping back to where it started, or funds split below reporting limits, are easy to see on a graph and almost invisible in a transaction table.
Regulators and auditors ask who decided what, on which evidence, and whether that evidence was changed afterwards. Spreadsheets and email cannot answer that reliably.
The approach
One workspace for the whole path: signal, alert, case, evidence, finding, decision and outcome. Detection rules raise alerts, a graph database shows how accounts are connected, and customer risk scoring and watchlist screening add context.
An analyst opens a case and attaches evidence. The case then passes through independent review, a maker-checker decision, controlled execution and a sealed outcome, and every step is written to a hash-chained audit ledger.
What it does
- Detection of five typologies: circular flow, structuring, large cross-border transfers, high-risk counterparties and velocity anomalies
- An alert queue with server-side search, severity and status filters, live counts and deep links
- An investigation overview whose panels load independently, so one failing source never blanks the page
- A case workspace with notes, evidence, assignment, a searchable timeline and a PDF case report
- A triage queue that scores and orders open cases and shows who owns them
- Network investigation on a Neo4j graph: money paths, account neighbourhoods and circular structures
- Customer risk scores built from six factors, with the contributing factors shown
- Watchlist screening with fuzzy name matching, aliases and a clear or escalate review step
- A compliance command centre with SLA clocks and breaches
- Chain of custody with SHA-256 integrity hashes, and four-eyes review at every checkpoint
- Governed findings, decisions, execution, outcome attestations and conclusion release
- Detection rule governance with a change history, a hash-chained audit ledger and a data ingestion centre
- A command palette, a URL for every screen, saved views, CSV export and a phone layout
Where the engineering is
Two databases, one source of truth
PostgreSQL is the system of record for customers, accounts, transactions, alerts, cases, evidence and the audit trail. Neo4j holds a projection of accounts and transfers, so path, neighbourhood and cycle questions are one Cypher query instead of many joins.
The graph is rebuilt from PostgreSQL in a single transaction, in batches, so readers never see a half-built graph, and a graph problem can never corrupt the records of a case.
, Open the full-size image (opens in a new tab)Governance as code, not as a policy document
Maker-checker review, role gates, integrity hashes and the audit ledger are enforced by the services themselves, so an analyst cannot skip a step by accident.
The four-eyes rule applies at every checkpoint: the evidence reviewer must differ from the custodian, the finding reviewer from the submitter, the decision reviewer from the proposer and the release reviewer from the preparer. Findings can only rest on evidence that has been verified and accepted.
, Open the full-size image (opens in a new tab)Proving that evidence was not altered
Every item of case evidence has a custody log with SHA-256 integrity hashes, so anyone can check that it is the same as when it was attached.
Security events go into an audit ledger where each entry is chained to the one before, so removing or editing history is detectable. Administrators can search the ledger by date range and verify the chain.
, Open the full-size image (opens in a new tab)Identity from the token, and safe by default
Case notes and audit entries are attributed to the signed-in user taken from the JWT. Request parameters that claim to say who acted are ignored, so nobody can write under another name.
A guard refuses to start the production profile while the demo secret or demo passwords are still in use. Sign-in is rate limited per user name, audit text is clipped to its column width, and free text travels in the request body instead of the URL so it stays out of access logs.
Twenty modules that feel like one product
Grouped navigation, one command palette, shared list tools and a shared design layer keep a wide product coherent. Every screen has its own URL, so refresh, back and forward, and shared links all work.
Overview data loads with Promise.allSettled, every module has its own error boundary, and an expired session returns the user to sign-in with a clear message. One broken dependency costs one panel, not the whole screen.
, Open the full-size image (opens in a new tab)Screens
, Open the full-size image (opens in a new tab)Every alert, searchable and filterable on the server.
, Open the full-size image (opens in a new tab)Notes, evidence, owner and timeline for one case.
, Open the full-size image (opens in a new tab)How money got from one account to another.
, Open the full-size image (opens in a new tab)A prioritised work queue with scores and owners.
, Open the full-size image (opens in a new tab)Explainable customer risk scores.
, Open the full-size image (opens in a new tab)Fuzzy name screening against watchlists.
, Open the full-size image (opens in a new tab)A tamper-evident log for every item of evidence.
, Open the full-size image (opens in a new tab)Detection rules tuned with a full change history.
How it's built
| Layer | Technology |
|---|---|
| Backend | Java 21 and Spring Boot 4.1, a stateless REST API with 91 endpoints across 26 controllers |
| Security | Spring Security with signed JWTs, role-based method security, per-user sign-in lockout and an insecure-defaults guard |
| Database | PostgreSQL 17 as the system of record, with 18 Flyway migrations and 39 tables |
| Graph | Neo4j for the money-flow graph, queried with Cypher |
| Frontend | Next.js 16, React 19, TypeScript and Cytoscape.js for the network graph |
| Testing | 97 automated unit tests, and browser tests against a mock API |
Results
- Runs end to end on synthetic data, from detection to a sealed case outcome.
- 20 modules and 91 REST endpoints across 26 controllers.
- 18 Flyway migrations and 39 tables.
- 97 automated tests.
What I would build next
- A real user store with password hashing, and an httpOnly cookie session instead of a token in session storage.
- Paged analytics lists and a bounded graph query for large data.
- Integration tests against real PostgreSQL and Neo4j containers, and continuous integration.
- Local-time display and user preferences.
What I learned
- Modelling workflow states explicitly makes governance rules easy to test.
- Server-side paging, filtering and counts matter early; in-memory filtering only works while the data is small.
- Accessibility work, such as focus management, combobox semantics and reduced motion, is cheaper when it is part of the first build of a component.
Built with
- Java
- Spring Boot
- Spring Security
- PostgreSQL
- Neo4j
- Next.js
- React
- TypeScript