Skip to content

dsmagic12/nexusadvisors-kyc

Repository files navigation

Nexus Advisor — KYC Platform

A full-stack prototype investment advisor onboarding platform with AI-powered KYC document ingestion, portfolio suitability analysis, and AML/sanctions screening. Built for demonstration purposes against a company like Osaic.


Table of Contents


Overview

Nexus Advisor is a single-binary fullstack web application: an Express backend serves both the REST API and the Vite-compiled React frontend from the same port. Data is persisted in a local SQLite file (osaic.db). All AI features call the Anthropic API server-side — no credentials are exposed to the browser.

The app ships with five seed clients pre-populated in the database so you can explore every feature immediately after starting the server.


Tech Stack

Layer Technology
Frontend React 18, Vite 7, Tailwind CSS 3, shadcn/ui, TanStack Query v5, Wouter
Backend Express 5, TypeScript, tsx (dev) / esbuild (prod)
Database SQLite via better-sqlite3 + Drizzle ORM
AI Anthropic SDK (claude-sonnet-4-6)
Forms react-hook-form + Zod
Routing Wouter with useHashLocation (hash-based, iframe-safe)

Prerequisites

Node.js

Required: Node.js 20.x

The project pins @types/node to 20.19.27 and uses import.meta.dirname (Node 20.11+). Node 18 will likely work for runtime but may produce TypeScript errors. Node 22+ is untested and may surface ESM/CJS interop issues with better-sqlite3.

node --version   # must be >= 20.11
npm --version    # npm 10+ recommended

nvm users: nvm use 20 before installing.

Python (optional)

Not required at runtime, but node-gyp (a transitive dependency of better-sqlite3) may invoke Python 2/3 during a native compile step on some platforms. If npm install fails with a node-gyp error, install Python 3 and try again.

Native build toolchain

better-sqlite3 ships pre-built binaries for common platforms (Linux x64, macOS arm64/x64, Windows x64). If a pre-built binary is unavailable for your platform, npm install will attempt to compile from source, which requires:

  • macOS: Xcode Command Line Tools (xcode-select --install)
  • Linux: build-essential and python3 (apt install build-essential python3)
  • Windows: windows-build-tools (npm install -g windows-build-tools) or Visual Studio Build Tools

Anthropic API Key

All AI features (document extraction, KYC gap analysis, suitability check, AML screening, chat) require a valid Anthropic API key with access to claude-sonnet-4-6. Without it the server will start but every AI endpoint will return a 500 error.


Environment Variables

Create a .env file in the project root (same level as package.json):

# Required — Anthropic API key for all AI features
ANTHROPIC_API_KEY=sk-ant-...

# Optional — defaults to 'development' if unset
NODE_ENV=development

The server loads .env automatically via the dotenv package. Never commit your .env file.

Note on the Anthropic model: The app calls claude-sonnet-4-6. If that model ID is deprecated or renamed in your account, update the string in server/routes.ts (search for claude-sonnet-4-6; it appears in 5 places).


Installation

# Clone / unzip the source
cd osaic-kyc

# Install all dependencies (runs node-gyp for better-sqlite3 if needed)
npm install

you may get an error on Windows machines

when you try npm run dev if you get an error about missing the module @rollup/rollup-win32-x64-msvc you can resolve that with

npm i @rollup/rollup-win32-x64-msvc

If npm install fails on better-sqlite3 with a native compile error, try:

npm install --ignore-scripts
npm rebuild better-sqlite3

If it still fails, confirm your Node version matches the pre-built binary targets:

node -e "console.log(process.versions)"

Running Locally

npm run dev

This starts a single process that:

  • Runs the Express API on port 5000
  • Serves the Vite dev server (with HMR) proxied through Express

Open http://localhost:5000 in your browser.

The SQLite database file (osaic.db) is created automatically on first start, along with all tables and seed data. You do not need to run any migration commands.

Port conflict: If port 5000 is in use, kill the occupying process or change the port in server/index.ts.


Building for Production

npm run build

This runs script/build.ts which:

  1. Compiles the React frontend with Vite → dist/public/
  2. Bundles the Express backend with esbuild → dist/index.cjs

Start the production server:

NODE_ENV=production ANTHROPIC_API_KEY=sk-ant-... node dist/index.cjs

Or with a .env file present:

npm start

The production server serves the compiled frontend as static files and handles API requests — everything on port 5000, no separate frontend process.


Project Structure

osaic-kyc/
├── client/                   # React frontend (Vite root)
│   └── src/
│       ├── components/
│       │   ├── ui/           # shadcn/ui primitives
│       │   ├── AppShell.tsx  # Sidebar nav + layout wrapper
│       │   └── StatusBadge.tsx
│       ├── pages/
│       │   ├── Dashboard.tsx
│       │   ├── ClientDirectory.tsx
│       │   ├── ClientDetail.tsx       # KYC status + AML gate
│       │   ├── KycOnboarding.tsx      # 5-step wizard incl. AML
│       │   ├── PortfolioOnboarding.tsx
│       │   ├── DocumentVault.tsx
│       │   └── AmlScreening.tsx       # Full AML screening page
│       ├── lib/
│       │   └── queryClient.ts         # apiRequest + TanStack Query setup
│       ├── hooks/
│       │   └── use-toast.ts
│       ├── App.tsx                    # Wouter router
│       ├── index.css                  # CSS variables + Tailwind directives
│       └── main.tsx
├── server/
│   ├── index.ts              # Express app entry + dev/prod branching
│   ├── routes.ts             # All REST + AI endpoints
│   ├── storage.ts            # SQLite DDL, seed data, all CRUD methods
│   ├── vite.ts               # Vite middleware (dev only)
│   └── static.ts             # Static file serving (prod only)
├── shared/
│   └── schema.ts             # Drizzle schema + Zod insert schemas + types
├── script/
│   └── build.ts              # esbuild + Vite production build script
├── dist/                     # Production build output (git-ignored)
├── osaic.db                  # SQLite database (git-ignored, auto-created)
├── drizzle.config.ts
├── vite.config.ts
├── tailwind.config.ts
├── tsconfig.json
└── package.json

Features

Dashboard

Real-time KYC pipeline stats, AUM summary, recent activity feed, and quick-action shortcuts.

Client Directory

Searchable, filterable table of all clients with KYC and portfolio status badges.

KYC Onboarding Wizard (5 steps)

  1. Personal Info — name, email, DOB, SSN
  2. Address & Financials — address, risk tolerance, income, net worth, investment objective
  3. Documents — upload identity/financial/legal documents with AI field extraction
  4. AML Screening — AI-powered sanctions and compliance check (see below); blocks progression if auto-blocked
  5. Review & Submit — summary with AML verdict inline, creates or updates client record

Client Detail

  • KYC status history and manual status transitions
  • AML screening result panel with compliance override workflow
  • AI KYC gap analysis
  • Linked portfolio summary
  • Document list

Portfolio Onboarding

  • Household and account structure
  • AI suitability check against client's risk profile
  • Holdings entry

Document Vault

  • Cross-client document browser with category filters
  • Per-document AI field extraction

AI Chat Assistant

Context-aware chat embedded in the app shell — can answer questions about any loaded client or document.


AML Screening

The AML layer calls claude-sonnet-4-6 with a structured compliance prompt that evaluates five checks per client:

Check What it looks for
OFAC / SDN US Treasury Specially Designated Nationals list
PEP Status Politically Exposed Persons (Class 1–3)
Adverse Media Criminal charges, fraud, regulatory actions
Sanctions UN, EU, OFAC non-SDN lists
Source of Wealth Unexplained wealth origins or red flags

Risk levels:

Score Level Consequence
0–15 Clear No restrictions
16–35 Low No restrictions
36–60 Medium Advisory review recommended
61–84 High requiresOverride=true — compliance officer must document justification before KYC can be approved
85–100 Blocked autoBlocked=true — KYC approval button is hard-locked; OFAC SDN match requires CCO-level review

Screening results are persisted in the screening_results table and surfaced in:

  • The KYC wizard (step 4)
  • The Client Detail page (AML panel + gated approve button)
  • The standalone AML Screening page (Compliance → AML Screening in the sidebar)

Quick Screen: The AML Screening page includes a standalone name/DOB form for ad-hoc checks without needing a client record.


Known Issues & Dependency Notes

better-sqlite3 — native module, platform-specific binaries

This is the most likely source of installation failures.

  • ARM Linux (e.g., Raspberry Pi, some Docker images): No pre-built binary exists. npm install will attempt a source compile. Ensure build-essential, python3, and a compatible gcc are present.
  • Apple Silicon (M1/M2/M3): Pre-built arm64 binaries are included in recent better-sqlite3 versions. If you see an arch mismatch error (e.g., running an x64 Node binary under Rosetta), switch to an arm64 Node build via nvm install 20 --default.
  • Windows: Requires Visual Studio Build Tools or windows-build-tools. Run PowerShell as Administrator: npm install -g windows-build-tools. Node 20 + better-sqlite3 11.x is tested on Windows x64.
  • Docker/CI: Use node:20-bullseye or node:20-bookworm base images which include the necessary build toolchain. Alpine images require apk add python3 make g++ before npm install.

Vite 7 + @tailwindcss/vite — do not mix Tailwind versions

The project uses Tailwind CSS v3 (tailwindcss: ^3.4.17) with @tailwindcss/vite listed in devDependencies. If you add a package that transitively installs tailwindcss@^4.x, it will conflict with the v3 PostCSS setup. Lock Tailwind to v3 in package.json if this happens:

"overrides": {
  "tailwindcss": "3.4.17"
}

Also: the index.css uses @tailwind base/components/utilities directives — not the Tailwind v4 @import "tailwindcss" syntax. Do not upgrade to v4 without rewriting index.css and tailwind.config.ts.

Express 5 — breaking changes vs Express 4

The project uses express@^5.0.1. Express 5 changes error-handler signatures and route parameter handling. If you add middleware written for Express 4 (e.g., many community packages), watch for async error propagation differences — Express 5 automatically catches rejected promises in route handlers, so you may not need try/catch in every route.

uuid@^14.0.0 — ESM-only

uuid v14 is ESM-only. The backend is compiled to CJS by esbuild at build time, which handles the interop automatically. If you add scripts that import uuid directly outside of the esbuild pipeline (e.g., a plain node script), use a dynamic import() or switch to crypto.randomUUID() (available natively in Node 15.6+).

@tanstack/react-query v5 — object-only API

TanStack Query v5 dropped the positional argument form. Every useQuery and useMutation call must use the object form:

// ✅ v5
useQuery({ queryKey: ['/api/clients'], queryFn: ... })

// ❌ v4 (will throw at runtime)
useQuery(['/api/clients'], ...)

If you copy query patterns from older tutorials or Stack Overflow, update them.

wouter — hash routing only

The frontend uses useHashLocation from wouter/use-hash-location. All routes are hash-based (/#/clients, /#/screening). This is intentional — the app is served from a static S3 prefix where path-based routing would 404 on deep-link refreshes.

Do not use href="#section" anchor links for in-page scroll navigation — Wouter intercepts the hash change and treats it as a route. Use onClick + scrollIntoView() instead.

Windows — NODE_ENV inline assignment

The npm run dev and npm start scripts use cross-env to set NODE_ENV cross-platform. This is already wired in package.json — no extra steps needed. If you cloned an older copy of this repo that predates this fix (scripts looked like NODE_ENV=development tsx ...), you will get:

'NODE_ENV' is not recognized as an internal or external command

Fix it by running npm install (which picks up cross-env) and confirming the scripts in package.json are prefixed with cross-env.

localStorage / sessionStorage / cookies — not available

The template explicitly avoids browser storage APIs because the original deployment target is a sandboxed iframe environment where they are blocked. All transient state uses React state; all persistent state goes through the Express API to SQLite. Do not add localStorage calls — they will silently fail or throw in sandboxed contexts.

Anthropic SDK ^0.90.0

The SDK uses streaming-capable message creation. The app uses the non-streaming messages.create() call. If you want to add streaming responses (e.g., for the chat endpoint), use the stream() helper or set stream: true and handle the SSE response. Be aware that Express 5 + SSE requires manually setting res.flushHeaders() and managing connection teardown.

drizzle-kit db:push — targets data.db, not osaic.db

drizzle.config.ts points to ./data.db (the template default), but the runtime storage code in server/storage.ts uses new Database("osaic.db"). If you run npm run db:push it will create a separate data.db file that the running server will never read. The app does not need db:push — tables are created via raw sqlite.exec() DDL in storage.ts on startup. Only use drizzle-kit if you want to inspect the schema with Drizzle Studio; point it at osaic.db first:

// drizzle.config.ts
dbCredentials: { url: "./osaic.db" }

Database

SQLite file: osaic.db in the project root.

Tables (all created automatically on first server start):

Table Description
clients Core client profiles, KYC status, financial info
documents Uploaded documents with AI-extracted fields (stored as JSON text)
portfolios Portfolio accounts with holdings (stored as JSON text)
chat_messages Chat history, optionally scoped to a clientId
activity_log Audit trail of advisor actions
screening_results AML/sanctions screening records with per-check JSON results

Seed data (5 clients) is inserted on first start only — it is skipped if clients already exist, so re-starts are safe.

To reset the database entirely:

rm osaic.db
npm run dev   # re-creates and re-seeds

Deployment Notes

The production build produces two artifacts:

  • dist/public/ — compiled static frontend (HTML, JS, CSS)
  • dist/index.cjs — bundled Express server

These must be deployed together. The frontend's apiRequest helper rewrites API URLs using a __PORT_5000__ token at deploy time (used by the original Perplexity Computer hosting environment). In a standard deployment (e.g., a VPS or container), this token will not be substituted and API calls will use relative paths (/api/...), which is correct behavior when the Express server also serves the frontend.

Recommended self-hosted setup:

[ nginx / reverse proxy ]  →  port 443 (HTTPS)
         ↓
[ node dist/index.cjs ]    →  port 5000 (internal)
         ↓
[ osaic.db ]               →  local file, same directory

Set ANTHROPIC_API_KEY in your process environment or a .env file co-located with dist/index.cjs. The dotenv package is bundled and will load it automatically.

The SQLite database file path is relative to the current working directory at server start. Run the server from the directory containing osaic.db, or set an absolute path in server/storage.ts:

// server/storage.ts line 15 — change to absolute path for robustness
const sqlite = new Database("/var/data/osaic.db");

About

a sample customer onboarding and kyc app for a fictional financial advisor firm

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages