## Problem Users keep correcting the same transactions over and over. The AI mislabels a merchant (e.g. supermarket → fuel), the user fixes it, and the next near-identical transaction from that merchant gets mislabeled the same way again. Today a correction is logged and the offending ai rule is self-healed, but the system only *forgets* its mistake — it never *remembers* the user's fix. ## Approach A correction now becomes a deterministic, forward-looking `AutomationRule` (new `RuleOrigin::Correction`). The next matching transaction is categorized by that rule **before the model ever runs** (`ApplyAutomationRules` is synchronous and runs ahead of AI categorization), ending the loop. Zero model cost, instant, reuses the existing rule engine. **Matching key** (in order): 1. **Merchant** (`creditor_name`/`debtor_name`, exact `==`) when present — stable even as the description varies. 2. Otherwise the **description's distinctive tokens** (`in` / AND-of-`in`), extracted by the shared `DescriptionTokenizer` (noise tokens dropped by document frequency, language-agnostic), **guarded** against over-broad rules that could silently mis-file en masse. If guarded out → nothing is learned, silently. ## Deliberate decisions (from a design walkthrough) - **Forward-only**: never retroactively re-categorizes existing transactions. - **Learn only from system categorizations** (AI label, ai rule, or a prior correction rule) — never from one-off manual filing, bank categories, or the user's own hand-authored rules. - **A key lives in exactly one correction rule**, so changing your mind moves it to the new category. Correcting a transaction a prior correction rule categorized is also learnable, so correction rules stay fixable in-flow. - Correcting to *uncategorized* learns nothing but still self-heals the ai rule. - **Safety net**: correction rules are visible/editable in `settings/automation-rules` (marked with the AI sparkle, tooltip "Learned from your correction"); the transactions table shows a toast with an instant **Undo**. ## Review pass (two independent agents + live QA) - **HIGH fix** (`67fc4293`): an ai rule could out-rank a freshly learned correction and re-apply the wrong category (when the corrected transaction was a *direct* model label with no rule id). Now every ai rule holding the merchant is swept on correction. Regression test added. - **Refactor** (`ca743fde`): collapsed duplicated clause-append logic; removed a speculative unused enum helper. - **Coverage** (`12ceb0f7`): debtor_name path, single-token description clause, encrypted-description fail-safe. - **Toast conflict fix** (`382c8169`, found in live QA): correcting an AI transaction fired both the new "Learned …" toast and the pre-existing "Transaction categorized → Automatize" prompt, which invited the user to manually create the rule the correction had just created. Made them mutually exclusive. Adds a browser test for the inline-correction flow. - **Settings icon** (`f3f882b6`): correction rules now show the AI sparkle in settings, like ai rules. ## Verified end-to-end (against a running instance) Drove the real UI with a browser: correcting an AI-mislabeled transaction creates the correction rule, shows the "Learned · Undo" toast (no competing Automatize prompt), and Undo deletes the rule while keeping the correction. Confirmed for **merchant** keys and the **description-only** path — including that a later "practically identical" description (different surrounding text, no merchant) is caught by the rule, while a near-miss sharing only one distinctive token is correctly **not** caught. ## Open question for reviewers **`debtor_name` (P2P) as a rule key.** For incoming transfers the merchant key falls back to the sender's name, so correcting one can create a rule keyed on a person's name (useful for recurring transfers from a roommate, but a possible privacy surprise; the name appears in the rule title in settings). This matches the existing tier-2 learner's behaviour. Keep as-is, or restrict correction rules to `creditor_name` only? Happy to change. ## Testing - `tests/Feature/Ai/CategoryOverrideHandlerTest.php`: merchant + description learning, next-transaction match, over-broad rejection, change-of-mind move, correct-to-null self-heal, the HIGH regression, debtor_name, single-token, encrypted fail-safe. - `tests/Browser/CategoryCorrectionLearningTest.php`: inline correction → toast → learned rule → undo. - `automation-rule-title.test.tsx`: the AI sparkle shows for `ai` and `correction`, not `user`. - Full AI suite green (106 tests); transaction/bulk-update suites green (62). Pint + Prettier + ESLint clean. No new dependency, no migration (the `origin` column is a free-text string). The feature is implicitly gated by AI categorization — with no AI categorization there is nothing to correct and nothing is learned. |
||
|---|---|---|
| .agents/skills | ||
| .claude | ||
| .cursor | ||
| .github | ||
| .opencode/skills | ||
| .pi | ||
| app | ||
| bootstrap | ||
| config | ||
| database | ||
| docker | ||
| docs | ||
| experiments | ||
| lang | ||
| public | ||
| resources | ||
| routes | ||
| screenshots | ||
| scripts | ||
| src/lib/crypto | ||
| storage | ||
| templates/coolify | ||
| tests | ||
| .dockerignore | ||
| .editorconfig | ||
| .env.example | ||
| .env.production.example | ||
| .gitattributes | ||
| .gitignore | ||
| .mcp.json | ||
| .php-cs-fixer.dist.php | ||
| .php-version | ||
| .prettierignore | ||
| .prettierrc | ||
| .release-it.json | ||
| AGENTS.md | ||
| CHANGELOG.md | ||
| CLAUDE.md | ||
| Dockerfile | ||
| Dockerfile.production | ||
| LICENSE.md | ||
| LOCALIZATION.md | ||
| ONBOARDING.md | ||
| README.md | ||
| artisan | ||
| autoresearch-dashboard.md | ||
| autoresearch.jsonl | ||
| autoresearch.md | ||
| autoresearch.sh | ||
| boost.json | ||
| bun.lock | ||
| components.json | ||
| compose.yaml | ||
| composer.json | ||
| composer.lock | ||
| docker-compose.production.yml | ||
| eslint.config.js | ||
| falcode.json | ||
| opencode.json | ||
| package-lock.json | ||
| package.json | ||
| phpstan-baseline.neon | ||
| phpstan.neon | ||
| phpunit.xml | ||
| tsconfig.json | ||
| vite.config.ts | ||
| vitest.config.ts | ||
| vitest.setup.ts | ||
| whispermoney | ||
| worktree.sh | ||
README.md
Deutsch | Español | français | 日本語 | 한국어 | Português | Русский | 中文
Whisper Money
The most secure way to understand your finances.
Whisper Money is a privacy-first personal finance application that helps you track, categorize, and understand your spending—all while keeping your financial data encrypted and secure.
🎮 Try the Demo: Experience Whisper Money with our demo account - no registration required!
💬 Join our Community: Whether you're a user looking for help or a developer wanting to contribute, we'd love to have you in our Discord server! Share feedback, ask questions, discuss new features, or just hang out with fellow privacy enthusiasts.
Features
- 🔐 Privacy-first — Your data is never shared with third parties. You own it
- 🏦 Bank account management — Track multiple accounts in one place
- 📊 Transaction categorization — Automatic and manual categorization
- 🤖 Automation rules — Set up rules to auto-categorize transactions
- 📈 Financial insights — Understand your spending patterns
Tech Stack
- Backend: Laravel 12, PHP 8.4
- Frontend: React 19, Inertia.js v2, TypeScript
- Styling: Tailwind CSS v4
- Database: MySQL
- Cache/Queue: Redis
- Testing: Pest v4
Running Locally
Quick Start (Recommended)
The easiest way to get started is using our automated setup script:
bash <(curl -fsSL https://whisper.money/setup.sh)
After installation, just visit https://whisper.money.localhost in your browser.
Manual Setup
If you prefer to set up manually:
- Clone the repository:
git clone https://github.com/whisper-money/whisper-money.git
cd whisper-money
- Run the setup script:
whispermoney install
Available Commands
Important: You must run
whispermoney installbefore using any other command. If you skip the install step, commands likestartwill not work.
Once installed, you can use the whispermoney command for common tasks:
# Start all services
whispermoney start
# Stop all services
whispermoney stop
# Upgrade to latest version
whispermoney upgrade
# Interactive menu
whispermoney
Development Server
For active development with hot reloading:
composer run dev
This will concurrently start:
- PHP development server (via Portless HTTPS proxy)
- Queue worker
- Log viewer (Pail)
- Vite dev server
The application will be available at https://dev.whisper.money.localhost. In git worktrees, the branch name is automatically prepended (e.g. https://fix-ui.dev.whisper.money.localhost).
Running with Docker (Production Image)
For testing the production Docker image locally:
- Copy the production environment file:
cp .env.production.example .env
- Start the services:
docker compose -f docker-compose.production.yml up -d
The application will be available at http://localhost:8080.
To use a different port, set APP_PORT:
APP_PORT=3000 docker compose -f docker-compose.production.yml up -d
Deploying to Coolify
Whisper Money can be easily deployed to Coolify using our Docker Compose template.
Quick Deploy
- In Coolify, create a new resource and select Docker Compose
- Choose Empty Compose File as the source
- Paste the contents from our template: 👉 whisper-money.yaml
- Deploy!
The template includes:
- Whisper Money application container
- MySQL 8.0 database with health checks
- Persistent volumes for data and storage
- Auto-generated database credentials
Required Environment Variables
| Variable | Description |
|---|---|
RESEND_API_KEY |
Email service API key (for password resets, notifications) |
Note:
APP_KEYandAPP_URLare auto-configured. The container generates anAPP_KEYon first startup if not provided.
Optional Environment Variables
| Variable | Default | Description |
|---|---|---|
DRIP_EMAILS_ENABLED |
true |
Enable drip emails (welcome, onboarding, feedback) |
HIDE_AUTH_BUTTONS |
false |
Hide login/register buttons on landing page |
SUBSCRIPTIONS_ENABLED |
false |
Enable Stripe subscriptions |
STRIPE_KEY |
- | Stripe publishable key |
STRIPE_SECRET |
- | Stripe secret key |
STRIPE_WEBHOOK_SECRET |
- | Stripe webhook signing secret |
Star History
License
This work is licensed under a Creative Commons Attribution-NonCommercial 4.0 International License.