Architecture
How the system is structured and how its components interact.
Frontend
Built with Next.js (App Router) and Tailwind CSS. Pages are split into a server component that fetches data and a Page client component that handles interactivity. User identity is managed in a React context (UserContext) backed by localStorage — no session tokens or cookies are used.
The header renders two navigation rows: one for all users and a second admin-only row that is conditionally shown based on the current user's role.
API Layer
All mutations and data reads go through Next.js Route Handlers under src/app/api/. Key routes:
PATCH /api/expenses/[id]Updates a single expense field: manager approval, approved-by user, manual approval verdict, final reviewer, or core fields.POST /api/batch-analyzeAccepts an array of expense IDs, fetches each from the database, and dispatches them to the Python compliance agent one at a time. Writes the resulting analysis back to expense_analyses.POST /api/analyze-expenseSingle-expense compliance entrypoint used for ad-hoc analysis. Also handles receipt parsing via a Claude vision call.GET/POST /api/usersLooks up a user by login ID or creates a new account.GET/POST /api/policiesLists policy documents or uploads a new one and triggers re-indexing.
Database
PostgreSQL hosted on Neon (serverless). The schema has four main tables:
usersAccounts with roles: active, inactive, or admin.expensesSubmitted expense reports. Tracks amounts, category, vendor, manager approval, approved-by user, final reviewer, and manual approval decision.expense_analysesOne analysis row per expense. Stores the AI verdict (APPROVED / FLAGGED / DENIED), reasoning, policy citations, confidence score, and the model used.promptsEditable prompt templates. Only one row per named prompt may be active at a time.
The app connects via a tagged-template SQL helper (executeSql) that parameterises all queries.
Compliance Agent
Written in Python (src/scripts/compliance.py). The Next.js API spawns a short-lived Python subprocess per expense and reads the JSON result from stdout.
Update: this has all been transcoded to typescript
- 1RAG retrieval
The expense details are used as a query against a ChromaDB vector store to retrieve the most relevant policy excerpts. Only chunks from the currently active policy version are considered.
- 2Prompt construction
Retrieved excerpts are injected into the active compliance prompt template alongside the expense fields (amount, category, vendor, description, client charge flag, manager approval flag).
- 3Claude analysis
The prompt is sent to Claude (claude-opus-4-8) with adaptive thinking enabled. The model returns a JSON object with verdict, reasoning, policy citations, and a confidence score.
- 4Result storage
The API layer parses the JSON response and writes a row to
expense_analyses. Flagged expenses surface in the Needs Manual Review queue for admin decision.
Policy Indexing
Policy documents are uploaded as plain text, chunked, and embedded into ChromaDB Cloud. Each chunk is stored with a version number so the compliance agent can target only the current policy version during retrieval. Uploading a new document increments the version.