4b0fcb7df0be2c8ca2b6f3cd6011a69578f00a85
Tests / test (push) Successful in 23s
Previously the advisor always analyzed the current calendar month and
compared it against the previous calendar month, regardless of which
range the user had selected on the dashboard. That meant clicking
"Last 90 days" updated the cards but the advice was still scoped to
this month — the two surfaces disagreed on what "now" meant.
The chat payload now carries an optional period { startDate, endDate,
label } that the dashboard derives from its existing range state. When
present, the service uses that window as the current period and the
equal-length window immediately before it as the comparison period
(so "Last 30 days" pairs naturally with the prior 30 days). Period
labels flow into the prompt sections so the model talks about the
window the user is actually looking at. The legacy /advisor/insights
GET endpoint and any caller that omits the period keep the original
this-month / last-month behavior.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
TehRiehlBudget
A self-hosted personal finance application for tracking spending, balances, and net worth across all of your accounts — checking, savings, credit, loans, and investments — with manual transaction entry, receipt uploads, and a conversational AI advisor running entirely on your own hardware.
Deployed at budget.tehriehldeal.com.
Features
- Accounts — track checking, savings, cash, credit cards, loans, stocks, brokerage, and retirement accounts. Drag-to-reorder, per-account history, and a balance integrity check that reverses counter-party transfers when an account is deleted.
- Transactions — record income, expenses, and transfers between accounts. Filter by account, category, type, and date. Attach receipt images. Edit, delete, and bulk-export to CSV.
- Activity history — every create / update / delete is recorded with a snapshot, so deleted transactions and accounts remain auditable for as long as you need them. Filter by entity, action, account, or date range.
- CSV export — pull a date-ranged transaction set out as CSV from the global list or scoped to a single account, with quick-range presets (last 30/60/90 days, this year, all time) plus custom start/end pickers.
- Confirmation guards — every destructive action prompts before running, with light/dark mode parity.
- AI advisor — a "financial buddy" that summarizes your month against last month and answers follow-ups. Runs against a local Ollama model — no transaction data leaves your network.
- Dashboards — net worth and total debt at a glance, spending-by-category pie, monthly cash flow bar chart, and per-account balance history with overlaid valuations.
- Receipts — store receipt images on your own filesystem, never on a third-party blob store.
- Field-level encryption — sensitive columns (account numbers, transaction notes) are AES-256-GCM encrypted at rest.
Tech Stack
- Frontend — React 19 + TypeScript, Vite, TailwindCSS + ShadCN UI, Zustand, Recharts, React Router
- Backend — NestJS (TypeScript), REST, Prisma ORM
- Database — PostgreSQL (Dockerized for local dev)
- Auth — Supabase (email/password + OAuth, JWT-validated on the backend)
- AI — Ollama (self-hosted; defaults to
llama3) - File storage — local filesystem on the self-hosted server
- Package manager — pnpm workspaces
Project Structure
tehriehlbudget/
├── tehriehlbudget-frontend/ # React + Vite app
├── tehriehlbudget-backend/ # NestJS API + Prisma schema
├── docker-compose.yml # Postgres for local dev
├── pnpm-workspace.yaml
└── package.json # Workspace-level scripts
Prerequisites
- Node.js 20+
- pnpm 9+
- Docker (for local Postgres)
- A Supabase project (for auth) — free tier is fine
- An Ollama server reachable from the backend (local or LAN) for the AI advisor
Quick Start
# Clone and install
git clone <your-fork-url>
cd tehriehlbudget
pnpm install
# Start local Postgres
docker-compose up -d
# Configure the backend
cp tehriehlbudget-backend/.env.example tehriehlbudget-backend/.env
# Edit .env to set DATABASE_URL, SUPABASE_*, ENCRYPTION_KEY, OLLAMA_URL, OLLAMA_MODEL
# Apply database migrations
pnpm --filter tehriehlbudget-backend prisma migrate deploy
pnpm --filter tehriehlbudget-backend prisma generate
# Configure the frontend
cp tehriehlbudget-frontend/.env.example tehriehlbudget-frontend/.env
# Edit .env to set VITE_API_URL and VITE_SUPABASE_*
# Run both servers (in separate terminals)
pnpm dev:backend
pnpm dev:frontend
The frontend is served at http://localhost:5173 and the API at http://localhost:3000.
Required environment variables
Backend (tehriehlbudget-backend/.env)
| Variable | Description |
|---|---|
DATABASE_URL |
Postgres connection string |
SUPABASE_URL |
Your Supabase project URL |
SUPABASE_SERVICE_ROLE_KEY |
Service-role key (used to validate JWTs) |
ENCRYPTION_KEY |
32-byte hex key for AES-256-GCM field encryption |
OLLAMA_URL |
URL of your Ollama server (e.g. http://localhost:11434) |
OLLAMA_MODEL |
Ollama model id (e.g. llama3.2:latest) |
Frontend (tehriehlbudget-frontend/.env)
| Variable | Description |
|---|---|
VITE_API_URL |
Backend URL (e.g. http://localhost:3000) |
VITE_SUPABASE_URL |
Supabase project URL |
VITE_SUPABASE_ANON_KEY |
Supabase anon (publishable) key |
Common Commands
# Run dev servers
pnpm dev:frontend
pnpm dev:backend
# Build
pnpm build:frontend
pnpm build:backend
# Tests
pnpm test # both packages
pnpm test:backend # Jest
pnpm test:frontend # Vitest
pnpm test:coverage # both, with coverage
# Lint & format
pnpm lint
pnpm format
# Prisma
pnpm --filter tehriehlbudget-backend prisma migrate dev
pnpm --filter tehriehlbudget-backend prisma studio
Architecture Notes
- Field-level encryption. A NestJS interceptor encrypts sensitive columns on write and decrypts on read using AES-256-GCM. The plaintext only ever exists in memory inside the request handler.
- Receipt storage. Images are stored on the server's local filesystem. The backend issues access-controlled URLs for upload and retrieval — there is no S3 / external blob store.
- Balance integrity on delete. When an account is deleted, related transfers' counter-party balances are reversed inside the same Prisma
$transactionbefore the cascade fires, so a surviving account never reflects a transfer that no longer exists. - Audit log.
ActivityLogrows capture create / update / delete actions for transactions, accounts, and account valuations. For account deletions the per-transaction snapshots are written before the cascade so the trail outlives the data. - AI advisor. The advisor builds a per-request snapshot of standing balances, monthly flow numbers, and top spending categories, strips PII, and sends it to Ollama. The prompt explicitly separates point-in-time balances from monthly flow and instructs the model to never invent equations or derive new figures — your transaction data does not leave your network.
- Auth. Supabase issues JWTs; protected routes on both ends validate them. Sessions are persisted client-side via Supabase's SDK.
License
Languages
TypeScript
97.4%
CSS
1.1%
Dockerfile
0.7%
JavaScript
0.6%
HTML
0.1%