Real math. Real law. Your machine.
The precision of a tax accountant. The patience of a financial advisor. The privacy of a local app. Open source. Free forever.
6 locales · 14 bank formats + any file via LLM · 7 financial domains · Monte Carlo FIRE
Built for Claude Code. Full power runs in Claude Code (CSV import, SQLite, encrypted backup, inbox watcher, weekly digest, live prices). A claude.ai Projects template is included for conversational use, but most features require a local install. See Where this runs for the matrix.
→ Live demo — see a real conversation without installing
You: What's my FIRE confidence level?
FA: Running 10,000 simulations (return 7%±12%, inflation 2%±0.8%)...
Retirement probability: 73% success at 2042 target
┌─────────────────────────────────┐
│ p10 $420k ████ │
│ p25 $580k ██████ │
│ p50 $790k ████████ │ ← median
│ p75 $1.05M ██████████ │
│ p90 $1.38M █████████████ │
└─────────────────────────────────┘
Current: $180k target: $900k
+$200/month raises success to 81%.
That's deterministic math on your numbers — not a model guessing. More examples ↓
git clone --recurse-submodules https://github.com/googlarz/finance-assistant.git
cd finance-assistant && pip install -r requirements.txt
python3 skill.py --demo # seeds sample data → opens a dashboardThen add it as a Claude Code skill and ask "What's my financial health?" — full setup ↓.
Most people have a vague relationship with their finances. A rough sense of what they earn, a guess at what they owe in taxes, a hope that retirement will work out. Every tool that could help wants a monthly subscription and access to your bank account.
Finance Assistant is different. It runs on your machine, stores everything in a local SQLite database, and uses Claude to turn your real numbers into real answers — not estimates, not vibes, not "consult a professional." When you ask about your SE tax, it applies IRS Rev. Proc. 2024-40 to your actual income. When you ask about your UK pension, it pulls HMRC's carry-forward rules for the last three years. When you run a FIRE simulation, it runs 10,000 Monte Carlo paths, not a back-of-napkin multiple.
It works conversationally — no forms, no dashboards to fill in first. You tell it what you earn, what you owe, what you want. It remembers. It alerts you when something needs attention. It hands you a structured brief when you need a real accountant or adviser, and it gets out of the way when you don't.
No subscription. No cloud. No guessing.
| Without Finance Assistant | With Finance Assistant |
|---|---|
| YNAB: $15/month, your data on their servers | Free. SQLite on your machine. No subscription. |
| TurboTax: form wizard, no "what if I go freelance?" | SE tax + QBI §199A calculated in conversation |
| Generic ChatGPT: "I estimate your tax might be around…" | IRS Rev. Proc. 2024-40 applied to your actual income |
| Spreadsheet FIRE model you rebuild every year | Monte Carlo + named scenarios, saved and compared |
| UK pension: 20 min of Googling carry-forward rules | 3-year carry-forward with HMRC PTM057200, in seconds |
| Different tool for each country | DE · UK · US · FR · NL · PL — same commands everywhere |
Any assistant can talk about money. The difference is law-accurate, bracket-correct tax math for six countries, computed by deterministic code (locales/<code>/tax_calculator.py) — not by a model guessing brackets. A general or first-party finance assistant gives you plausible estimates; this applies the actual statute (German EStG §32a, IRS Rev. Proc., HMRC PTM) to your real numbers and shows its work. That per-jurisdiction depth is expensive to build and maintain, which is exactly why a general tool won't — and why this one leans into it. If your country isn't covered, the scaffold generator makes adding it a ~1-evening contribution with source-URL-annotated TODOs.
See the output in 30 seconds — screenshot
python3 skill.py --demo # seed sample data → open ~/.finance/dashboard_demo.html
python3 skill.py --dashboard # generate from your real data → ~/.finance/dashboard.html- What It Does
- Quick Start
- Locales
- How It Works
- Data Storage Layout
- Security & Privacy
- Bank Statement Import
- Module Reference
- Example Conversations
- Running Tests
- Screenshot
Finance Assistant covers the full personal finance lifecycle across 20+ operating modes:
| Mode | What you say | What you get |
|---|---|---|
| Budget Manager | "how am I doing on my budget?" | Variance by category, overspend alerts, pace warnings |
| Transaction Logger | "I spent €42 at REWE" | Logged, auto-categorized, budget actuals updated |
| Savings Planner | "I want to save €10k for a trip" | Timeline projection, monthly contribution needed |
| Investment Tracker | "show my portfolio" | Allocation, total return, XIRR, rebalance suggestions |
| Debt Optimizer | "best way to pay off my debts?" | Avalanche vs snowball comparison, debt-free date, interest saved |
| Tax Module | "what can I deduct?" | Locale-specific deductions (DE/UK/FR/NL/PL/US bundled) |
| Subscription Radar | "what am I subscribed to?" | Detects recurring charges, flags duplicates + still-charging-after-cancel |
| Tax Filing Brief | "prep my taxes for my accountant" | Hand-off doc: computed tax + statutory rules + deduction + document checklist |
| Encrypted Backup | "back up my data" | One file, AES + PBKDF2, to disk or iCloud |
| Audit Trail | "what changed today?" | Append-only log of every mutation |
| Insurance Reviewer | "do I have enough coverage?" | Coverage gap analysis, renewal alerts |
| Net Worth Dashboard | "where do I stand?" | Net worth with 7-domain health score and trend |
| Data Import | "import this statement" | Any format — 14 parsers fast-path, LLM reads the rest → preview → categorize → dedupe → import |
| Scenario Lab | "should I rent or buy?" | Before/after comparison with multi-year projection |
| Monte Carlo | "what's my FIRE confidence?" | 10,000-simulation distribution with p10/p50/p90 outcomes |
| Specialist Handoff | complex case | Structured brief for a Steuerberater or financial adviser |
| Shared Household | "split expenses with my partner" | Shared budget tracking, settlement ledger |
| Month Comparison | "how was March vs February?" | Month-over-month delta by category with trend arrows |
| Scenario Memory | "save this scenario" | Named scenario snapshots you can revisit and compare |
| Session Recall | "what did we discuss last week?" | Session-indexed memory of past financial decisions |
| Milestone Alerts | "alert me when net worth hits €100k" | User-configured threshold triggers surfaced at session start |
| Freelance Analyser | "should I go freelance?" | Break-even rate, net income comparison, billable-days threshold |
Every session start scans your finances and surfaces only what needs attention — ranked by urgency, with stale alerts auto-suppressed (and the footer tells you when it hid something):
- Budget overspend or pacing warnings ("85% of Groceries used at 16% of month")
- Upcoming recurring payments in the next 7 days
- Savings goal deadlines within 45 days
- Tax filing deadlines, incl. US quarterly estimated-tax dates
- Portfolio drift past your target allocation
- Recurring subscriptions — total burden, duplicates, and "still charging after you flagged it to cancel"
- Debt strategy nudges (avalanche could save you €X)
- Monthly FIRE progress bar (
[████████░░░░░░░░░░░░] 42.3% — €317k / €750k)
Finance Assistant is built for Claude Code. A claude.ai Projects template ships alongside it, but the browser sandbox can't reach your files, so most features are unavailable there.
| Feature | Claude Code (full) | claude.ai Projects (limited) |
|---|---|---|
| Conversational budgeting & advice | ✅ | ✅ |
| Tax math (DE/FR/NL/PL/UK/US brackets) | ✅ | ✅ (you supply numbers) |
| Debt avalanche/snowball comparison | ✅ | ✅ (you supply numbers) |
| Net-worth tracking | ✅ | ✅ (you supply numbers) |
| Bank CSV import (14 formats) | ✅ | ❌ no file access |
| SQLite local storage + audit log | ✅ | ❌ no local storage |
| Recurring subscription detection | ✅ | ❌ needs transaction history |
| Live prices (Yahoo + CoinGecko) | ✅ | ❌ no network in this skill's tools |
| FIRE Monte Carlo (10k paths) | ✅ | ❌ needs local compute |
| Inbox watcher (drop CSV → auto-import) | ✅ macOS launchd | ❌ |
| Weekly digest (scheduled OS notification) | ✅ macOS launchd | ❌ |
Encrypted backup (--backup) |
✅ | ❌ |
| Bank sync via GoCardless | ✅ | ❌ |
Translation: If you're on claude.ai web or mobile, you get a knowledgeable finance advisor that reasons over what you tell it in the chat. If you want it to read your bank statements, watch a folder, run scheduled digests, or hold a real database of your transactions, you need Claude Code (free CLI, runs locally).
By default Claude Code sends your prompts and file context to Anthropic's API. The data on disk never leaves your machine, but the conversation does. If you want truly zero-egress operation, route Claude Code through a local model with claude-code-router and point it at Ollama running locally:
# 1. Install Ollama and pull a capable model
ollama pull llama3.3:70b # or qwen2.5-coder:32b, deepseek-r1, etc.
# 2. Install claude-code-router
npm install -g @musistudio/claude-code-router
# 3. Configure it to route to your local Ollama (see router README)Full recipe + accuracy harness: docs/sovereignty.md walks through setup and ships a sovereignty_check.py harness that measures the local model's tax-reasoning accuracy against the deterministic engine on your hardware — so you can see the tradeoff before trusting it, instead of taking this README's word for it.
Honest tradeoff: open local models are meaningfully weaker than Claude on multi-step tax reasoning, bracket math, and "explain why" answers. You will get less precise tax calculations and worse advice quality. Use this mode if data sovereignty matters more than answer quality — e.g. when working with confidential client data or during the EU sovereignty audit at your job. For your own personal finances, the privacy gain over the standard local-first model (your data never leaves your machine; only the conversation does) is usually not worth the quality drop.
About the "Real math. Real law." promise at the top of this README: that guarantee holds on the default Claude path — the tax engines apply real statute and the calculations are bracket-accurate regardless of model, but interpreting your situation correctly (which rule applies, what's deductible) is where a weaker local model degrades. Sovereignty mode explicitly trades away the advice-quality half of that promise. The deterministic math (
locales/<code>/tax_calculator.py, invoked byscripts/tax_engine.py) runs the same either way; the reasoning around it does not. Pick the default path for anything tax-accuracy-critical.
If you use Claude on the web or mobile and don't want to install anything, use the Projects template:
- Create a new Project on claude.ai
- Paste the contents of
projects-template/PROJECT_INSTRUCTIONS.mdinto the Project instructions field - Start chatting — budgeting, debt advice, savings goals, net worth tracking, and tax questions all work conversationally
Limitations: claude.ai runs in a browser and cannot access your computer's filesystem. That means no CSV import, no local SQLite database, no bank sync, no live prices, and no Monte Carlo simulations — those all require files or local storage that the browser can't reach. What works: conversational budgeting, tax questions, debt advice, savings goals, and net worth tracking based on what you tell it. For the full experience, use the Claude Code skill below.
Clone the repo and add it as a skill:
git clone --recurse-submodules https://github.com/googlarz/finance-assistant.git
cd finance-assistant
pip install -r requirements.txtAdd as a skill in ~/.claude/settings.json:
{
"skills": [
{
"name": "finance-assistant",
"path": "/path/to/finance-assistant"
}
]
}Then start a session: What's my financial health?
python3 skill.py --version # finance-assistant 3.9.2
python3 skill.py --doctor # runs health checks on your setupCowork gives you the same agent capabilities as Claude Code in a desktop-friendly interface. For the best experience, set it up as a dedicated project:
1. Create a new project
Open Cowork → Projects → New Project. Name it something like Finance.
2. Add a project instruction In the project's Instructions field, add:
Always load and use the Finance Assistant skill /finance-assistant.
Start every session by running skill.py to load my profile and surface any alerts.
3. Start the project
Open the Finance project and say: What's my financial health?
Claude will load your profile, surface any session alerts (budget warnings, upcoming bills, tax deadlines), and be ready for any finance question.
Tip: Pin the Finance project to your Cowork sidebar so it's one click away at the start of each day.
Tax rules and social contribution logic live in locale plugins — country-specific modules that the skill loads dynamically.
Locales are maintained in a separate git submodule at https://github.com/googlarz/finance-assistant-locales. The --recurse-submodules flag in the clone command above pulls them automatically.
| Locale | Coverage |
|---|---|
de — Germany |
Income tax, Soli, GKV/PKV social contributions, deductions, filing deadlines 2024–2026 |
uk — United Kingdom |
Income tax bands, NI Class 1, personal allowance taper (£100k–£125,140), insurance guidance (income protection, life, critical illness) 2024–2026 |
fr — France |
Quotient familial, décote, IR tranches, CSG/CRDS with assiette réduite (Art. L136-2 CSS) |
nl — Netherlands |
Box 1/2/3, heffingskorting, arbeidskorting (Box 3 Kerstarrest note included) |
pl — Poland |
Polski Ład reform: 12%/32%, 30k PLN free amount, składka zdrowotna |
us — United States |
Federal income tax brackets, standard deduction, self-employment tax + QBI §199A, quarterly estimated-tax deadlines |
DE, FR, NL, PL, and UK are validated against 29 official tax authority test cases (BMF, HMRC, DGFiP, Belastingdienst, KAS) — run python3 -m pytest locales/tests/test_validation.py -v. The US locale ships with unit tests; official IRS reference cases are still being added (contributions welcome).
New locales can be contributed independently to the locales repository without touching the main skill code. See the locales repo for the plugin interface, provenance format, and contribution guide.
┌─────────────────────────────────────────────────────────────────┐
│ Claude Code / Cowork │
│ │
│ You ──► skill.py ──► profile_manager ──► session_alerts │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────┐ │
│ │ 18 Modes │ │
│ │ Budget · Transactions · Goals │ │
│ │ Investments · Debt · Tax · Insurance │ │
│ │ Net Worth · Import · Monte Carlo │ │
│ │ Scenarios · Handoff │ │
│ └──────────────┬───────────────────────────┘ │
│ │ │
│ ┌─────────▼──────────┐ │
│ │ scripts/*.py │ ◄── locale plugins │
│ │ (real math, not │ locales/de · uk · fr │
│ │ hallucination) │ locales/nl · pl · ... │
│ └─────────┬──────────┘ │
│ │ │
│ ┌─────────▼──────────┐ │
│ │ SQLite + .finance/ │ local only, never uploaded│
│ │ profile · budgets │ optional export encryption│
│ │ investments · tax │ chmod 600, git-ignored │
│ │ (12-table WAL DB) │ auto-migrates from JSON │
│ └────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
Every session starts by loading your stored profile with profile_manager.py. All scripts operate on this profile + the .finance/ data directory. Nothing is hardcoded; everything adapts to your locale, currency, and situation.
The insight engine (insight_engine.py) runs after every major data update. It dispatches to domain-specific generators:
budget_insights → savings_insights → investment_insights
→ debt_insights → insurance_insights → tax_insights → net_worth_insights
Each insight carries a 4-level status:
ready— actionable right nowneeds_input— needs one more fact from youneeds_evidence— needs a document or statementdetected— background risk found, FYI
And a confidence label: Definitive | Likely | Debatable | Avoid
Tax rules are country-specific plugins in locales/<country_code>/. Each locale exports a standard interface:
LOCALE_CODE = "de"
SUPPORTED_YEARS = [2024, 2025, 2026]
def get_tax_rules(year) -> dict
def calculate_tax(profile, year) -> dict
def get_filing_deadlines(year) -> list[dict]
def get_social_contributions(gross, year) -> dict
def generate_tax_claims(profile, year) -> list[dict]The German locale is fully bundled via the locales/ submodule. New locales can be scaffolded automatically via locale_loader.py.
All amounts use the Money class (backed by Decimal) to avoid floating-point errors. Exchange rates are cached in .finance/exchange_rates.json with a 24-hour TTL; fallback rates are clearly marked as lower confidence.
All data is project-local in .finance/. No cloud sync, no external APIs, no telemetry.
As of v3.0, the primary store is SQLite (finance.db, WAL mode). JSON files are kept as a human-readable backup and for compatibility; new writes go to both.
.finance/
├── finance.db # SQLite database (12 tables, WAL mode, FK constraints)
│ # tables: profile, accounts, transactions, budget_categories,
│ # goals, holdings, debts, snapshots, recurring_items,
│ # scenarios, thresholds, insurance_policies
├── finance_profile.json # JSON mirror of profile (human-readable backup)
├── accounts/
│ ├── accounts.json # Account registry mirror
│ └── transactions/
│ └── <account>_<year>.json # Transaction log mirror
├── budgets/
│ ├── 2025.json # Annual budget mirror
│ └── 2025-04.json # Monthly budget mirror
├── goals/
│ └── goals.json # Savings goals mirror
├── investments/
│ ├── portfolio.json # Holdings mirror
│ └── snapshots/
│ └── 2025-04-01.json # Point-in-time portfolio snapshots
├── debt/
│ ├── debts.json # Debt registry mirror
│ └── payoff_plans/
│ └── <plan_id>.json # Avalanche/snowball simulation results
├── insurance/
│ └── policies.json # Insurance policies mirror
├── net_worth/
│ └── snapshots/
│ └── 2025-04-01.json # Monthly net worth snapshots
├── taxes/
│ └── de/
│ ├── 2024.json # Tax year data
│ └── 2024-claims.json # Deduction claims for filing
├── imports/
│ └── import_log.json # Import history for deduplication
├── workspace/
│ └── 2025.json # Financial health dashboard
├── subscriptions/
│ └── actions.json # Subscription cancel/keep tracking
├── household/
│ ├── household.json # Members + shared config
│ ├── shared_expenses.json # Split expense ledger
│ └── shared_goals.json # Household goals with per-member contributions
├── telemetry/
│ └── locale_usage.jsonl # Which locales get used (no financial data)
├── exchange_rates.json # Cached FX rates (24h TTL)
├── audit.log # Append-only mutation log (every change)
└── audit/
└── access_log.json # Audit trail of all data access
On first boot after upgrading to v3.0, skill.py automatically migrates all existing JSON data into SQLite using db_migrate.py. The migration is idempotent — safe to re-run, uses INSERT OR IGNORE.
What is never stored:
- Bank login credentials, passwords, PINs, TANs
- Full IBAN or bank account numbers
- Credit card numbers or CVV codes
- Tax IDs, passport numbers, national IDs
- Raw document contents
- Local-only: All data lives in
.finance/on your machine. No network calls for your personal data. No telemetry. No cloud sync. - Structured summaries, not raw data: Transaction amounts and categories are stored, not raw bank statements or login sessions.
- You own the delete button: Every data category can be deleted individually or all at once.
- Encryption at rest: Fernet AES-128-CBC + HMAC-SHA256 — the same authenticated encryption scheme used in production web services.
- Passphrase quality enforced: The system rejects weak passphrases before encrypting (minimum 12 chars, character variety required), because a strong cipher with a weak key is still weak.
- Atomic writes: Encrypted files are written to a
.enc.tmpfile first, then atomically renamed — a power failure or crash cannot leave a half-encrypted, unreadable file. - File permissions:
harden_permissions()sets.finance/to700(owner-only directory) and all files to600(owner-only read/write). Other OS users on the same machine cannot read your data. - Git guard: On first session,
.finance/is automatically added to.gitignoreso financial data cannot be accidentally committed and pushed to a repository. - Audit log: Every significant data access (read, write, encrypt, export, delete) is logged to
audit/access_log.jsonwith a timestamp. - Sanitize before sharing:
sanitize_for_sharing(data)strips all PII (names, employers, payees, addresses) before you share data to get help — financial amounts and structures are preserved.
Key derivation: PBKDF2-HMAC-SHA256
Iterations: 480,000 (NIST 2023 recommendation)
Salt: 16 bytes random per file (unique per encryption)
Cipher: AES-128 in CBC mode (via Fernet)
MAC: HMAC-SHA256 (Fernet built-in; prevents ciphertext tampering)
Encoding: Base64url
Dependency: pip install cryptography
Each file gets its own random salt. Two files encrypted with the same passphrase produce different ciphertexts — you cannot tell if two files contain the same data by comparing them.
The salt is stored alongside the ciphertext (standard practice — it only makes brute-force harder when combined with high iteration counts; it does not weaken the encryption).
Backups can be encrypted before leaving your machine:
# Encrypted backup — safe to store in cloud or email to yourself
export_all_data(passphrase="MyStr0ng!Passphrase")
# Plaintext export — keep offline only
export_all_data()The encrypted export uses the same Fernet key derivation as individual file encryption. The passphrase is never stored anywhere.
from scripts.data_safety import (
get_privacy_summary, # Full security status report
get_data_inventory, # Audit what's stored and where
harden_permissions, # chmod 600/700 on all .finance/ files
check_permissions, # Check for insecure file permissions
ensure_gitignore_protection, # Add .finance/ to .gitignore
encrypt_sensitive_files, # Encrypt profile, accounts, investments, debt
decrypt_sensitive_files, # Decrypt for use
encrypt_file, # Encrypt a single file
decrypt_file, # Decrypt a single file
export_all_data, # Export (plain or encrypted)
import_data, # Import from export file
delete_all_data, # Permanent wipe (requires confirm=True)
delete_category, # Delete one category (requires confirm=True)
sanitize_for_sharing, # Strip PII before sharing for help
get_access_log, # View audit trail
)skill.py (session start)
├── ensure_gitignore_protection() # .finance/ → .gitignore
├── check_permissions() # warn if group/world readable
└── get_profile() # load or start onboarding
└── (new user) show privacy statement
The privacy statement is shown once:
Your data lives only in
.finance/on your machine — nothing is ever uploaded. You can encrypt it, export it, or delete it completely at any time. I never store bank credentials, card numbers, IBANs, or government IDs.
| Threat | Protection |
|---|---|
| Another user on same machine reads your files | harden_permissions() — chmod 600/700 |
Accidental git push of financial data |
ensure_gitignore_protection() — automatic on session start |
| Laptop stolen, unencrypted disk | encrypt_sensitive_files(passphrase) + OS disk encryption (FileVault/LUKS) |
| Weak passphrase undermines AES | _check_passphrase_strength() — enforced before every encrypt call |
| Power failure during encryption corrupts file | Atomic write via .enc.tmp → rename() — POSIX atomic |
| Sharing data for help leaks names/employer | sanitize_for_sharing() — redacts all PII fields |
| Unexpected data access by a process | get_access_log() — timestamped audit trail |
| Cloud backup of export file exposes data | export_all_data(passphrase=...) — Fernet-encrypted export |
- Memory: Decrypted data resides in Python process memory while the skill is running. Python does not securely zero memory on deallocation. This is a fundamental Python limitation.
- OS keychain: Passphrases are not stored in the OS keychain (macOS Keychain, GNOME Keyring). You must provide the passphrase each session when using encrypted files. This is deliberate — no stored secret means no stored secret to steal.
- Disk encryption: If your disk is not encrypted (macOS FileVault, Linux LUKS), Fernet protects against OS-level access control bypass but not against forensic disk reads. Enable full-disk encryption for maximum protection.
- Audit log: The access log itself is protected by
harden_permissions()but is not encrypted by default (it contains timestamps and action types, not financial amounts).
| Format | Banks / Sources |
|---|---|
| CSV (auto-detected by header fingerprint) | 14 formats — 🇩🇪 DKB, ING, Sparkasse, Commerzbank, N26 · 🇺🇸 Chase, Bank of America, Wells Fargo, Capital One · 🌍 Wise, Revolut · 📊 Mint, Monarch, YNAB · plus a generic fallback |
| MT940 | Any German bank (SWIFT standard) |
| OFX / QFX | Most German brokers, international banks |
| Statement parsing for supported layouts | |
| Image (receipt) | Photo → transaction via receipt scanner |
| Anything else | No parser? Claude reads it. Unusual bank, foreign layout, copy-pasted table, scanned PDF, screenshot — the LLM extracts the transactions directly, then they run through the same sanitize → categorize → dedupe → preview pipeline. |
Why "anything else" works: this is an LLM-native product, not a pile of regex parsers. The 14 bundled formats are a fast path; when none match, Finance Assistant doesn't fail — it hands the raw content to Claude (the session you're already in) to extract. Nothing extra leaves your machine, and LLM-extracted rows get no special trust: same deduplication, same CSV-injection sanitization, same confirm-before-commit flow as a built-in parser.
- Detect format — header fingerprinting identifies the bank automatically
- Preserve original — source file is copied to
~/.finance/originals/YYYY-MM-DD_HH-MM-SS_<filename>before any parsing. You always have the raw file, regardless of what happens next. Passkeep_original=Falseto skip. - Parse — extract date, amount, payee, description
- Preview — show first 10 transactions for review
- Confirm — user approves before any data is written
- Auto-categorize — keyword + payee rules assign categories
- Deduplicate — exact-match deduplication against existing transactions
- Update — account balance and budget actuals refreshed
transaction_normalizer.py maps transactions to 30 categories across 8 domains. category_learner.py remembers corrections and applies them to future imports from the same payee — the categorization improves over time.
| Module | Purpose |
|---|---|
skill.py |
Session entry: load profile, run security checks, surface alerts |
finance_storage.py |
Path resolution and JSON persistence |
profile_manager.py |
v2 profile schema, deep-merge updates |
currency.py |
Money dataclass (Decimal), exchange rates with 24h cache |
| Module | Purpose |
|---|---|
account_manager.py |
CRUD for checking/savings/investment/loan accounts |
transaction_logger.py |
Log income/expense with auto-categorization (30 categories) |
recurring_engine.py |
Auto-generate recurring transactions (rent, salary, subscriptions) |
category_learner.py |
Learn from corrections to improve future auto-categorization |
| Module | Purpose |
|---|---|
budget_engine.py |
Create budgets, 50/30/20 auto-distribution, variance analysis |
goal_tracker.py |
Savings goals with completion projections |
| Module | Purpose |
|---|---|
investment_tracker.py |
Portfolio CRUD, allocation, FIRE number, monthly snapshots |
price_sync.py |
Live prices — Yahoo Finance for stocks/ETFs, CoinGecko for crypto (no API key), 6h TTL |
subscription_detector.py |
Detect recurring charges from transaction history (monthly/yearly cadence, duplicates, price changes) |
subscription_actions.py |
Flag → remind → cancel loop; alerts if a flagged sub keeps charging |
investment_returns.py |
TWR, XIRR (Newton's method), per-holding performance |
debt_optimizer.py |
Avalanche/snowball simulation, mortgage optimization, debt-free date |
insurance_analyzer.py |
Policy tracking, coverage gaps, renewal alerts |
net_worth_engine.py |
Aggregate assets + investments − liabilities, JSON snapshots |
| Module | Purpose |
|---|---|
tax_engine.py |
Country-agnostic interface, delegates to locale plugin via importlib |
tax_brief.py |
Accountant/Steuerberater filing brief: computed tax + rules + deduction + doc checklist |
locale_telemetry.py |
Privacy-safe record of which locales get used (locale + operation only) |
locale_registry.py |
Rule provenance (source URL, verification date, confidence) |
locale_loader.py |
Dynamic locale import, on-demand skeleton builder for new countries |
locales/de/ |
German locale: income tax, Soli, social contributions, 2024–2026 |
locales/uk/ |
UK locale: income tax, NI, personal allowance taper £100k–£125,140 |
locales/fr/ |
French locale: quotient familial, décote, CSG/CRDS assiette réduite |
locales/nl/ |
Dutch locale: Box 1/2/3, heffingskorting, arbeidskorting, Box 3 uncertainty |
locales/pl/ |
Polish locale: Polski Ład 12%/32%, 30k PLN free amount, składka zdrowotna |
locales/validation/ |
29 official test cases (BMF, HMRC, DGFiP, Belastingdienst, KAS) — all pass |
| Module | Purpose |
|---|---|
db.py |
12-table SQLite schema, WAL mode, get_conn() context manager |
db_migrate.py |
Idempotent JSON → SQLite migration (INSERT OR IGNORE) |
monte_carlo.py |
10,000-simulation Monte Carlo: FIRE, savings, debt payoff, net worth |
| Module | Purpose |
|---|---|
import_router.py |
Format detection and routing |
csv_importer.py |
14 bank formats (DKB, ING, Sparkasse, Commerzbank, N26, Chase, BofA, Wells Fargo, Capital One, Wise, Revolut, Mint, Monarch, YNAB) + currency-symbol-aware parsing + generic fallback |
mt940_importer.py |
SWIFT MT940 with graceful fallback if library not installed |
ofx_importer.py |
OFX/QFX with normalized date parsing |
transaction_normalizer.py |
Auto-categorize, deduplicate, normalize amounts |
llm_import.py |
LLM-native fallback for any unrecognized format — Claude extracts, same sanitize/normalize/dedupe pipeline |
| Module | Purpose |
|---|---|
insight_engine.py |
Cross-domain insights, 4-status model, sorted by urgency |
scenario_engine.py |
Salary comparison, FIRE projection, rent-vs-buy, debt-vs-invest, mortgage |
workspace_builder.py |
7-domain weighted health score |
output_builder.py |
Structured deliverables assembled into an output suite |
report_renderer.py |
Markdown and HTML reports |
snapshot_scheduler.py |
Monthly auto-snapshots (portfolio and net worth) |
session_alerts.py |
Proactive session nudges (budget, bills, deadlines, FIRE) |
document_sorter.py |
Classify financial documents by type |
adviser_handoff.py |
Structured referral brief for Steuerberater or adviser |
| Module | Purpose |
|---|---|
data_safety.py |
Fernet AES encryption, permissions hardening, git guard, export, delete, sanitize, audit |
audit_log.py |
Append-only mutation log (~/.finance/audit.log) — every change, with rotation |
backup.py |
Encrypted .tar.gz backup/restore (PBKDF2 + Fernet) to disk or iCloud |
sovereignty_check.py |
Measures local-model (Ollama) tax accuracy vs the deterministic engine |
household.py |
Shared household: members, expense splits, settle-up, shared goals |
You: What's my FIRE confidence level?
FA: Running 10,000 simulations (return 7%±12%, inflation 2%±0.8%)...
Retirement probability: 73% success at 2042 target
┌─────────────────────────────────┐
│ p10 $420k ████ │
│ p25 $580k ██████ │
│ p50 $790k ████████ │ ← median
│ p75 $1.05M ██████████ │
│ p90 $1.38M █████████████ │
└─────────────────────────────────┘
Current: $180k target: $900k
Sequence risk: worst 10% of runs hit a bad first decade.
Mitigation: +$200/month raises success to 81%.
You: I make $80k as a W-2 employee. What changes if I go 1099 freelance
at the same $80k?
FA: Same $80k, two tax pictures (2024, single, federal):
As a W-2 employee:
Federal income tax $9,441
Your FICA (SS + Medicare) $6,120 (employer pays another $6,120)
Effective rate 11.8%
As a 1099 freelancer:
Self-employment tax $11,304 (you pay BOTH FICA halves — Schedule SE)
− ½ SE tax deduction −$5,652 (above the line)
− §199A QBI deduction −$11,950 (20% of profit, IRS Rev. Proc. 2024-40)
Taxable income $47,799
Federal income tax $5,569
Total federal + SE $16,872
→ Going freelance at the SAME headline number costs ~$1,300 more in
federal+SE tax, because SE tax replaces the employer's FICA share.
To break even you'd need to bill ~$82k. Want the break-even rate?
You: Best strategy to pay off my 3 loans?
FA: Avalanche (highest rate first):
Credit card $3,200 @ 18.9% → Car loan $8,600 @ 5.9% → Personal loan $13,000 @ 3.4%
Interest saved vs minimum: $2,840 | Debt-free: Nov 2027
Snowball (smallest balance first):
Same order (happens to match) in this case
Interest saved vs minimum: $1,920 | Debt-free: Feb 2028
→ Avalanche saves $920 more and finishes 3 months earlier.
Choose snowball only if you need the win of a quick first payoff.
You: How am I doing on my April budget?
FA: $340 of $400 food budget used (85%) — 28 days into 30-day month. On pace.
Transport: $112 / $150 (75%) — fine.
Eating out: $89 / $80 — OVER by $9 ⚠
Suggestion: $11 left in eating out. One meal left this month.
Adjacent: Groceries has $60 remaining — cooking covers the gap.
The tax math isn't US-only — each locale applies its own statute. Ask the same "what can I deduct?" in Germany and you get German law, not a translation of US rules:
You: What can I deduct this year? (locale: de)
FA: Profile: angestellt, Berlin, homeoffice 3 days/week
✓ Arbeitnehmer-Pauschbetrag: €1,230 (automatic, EStG §9a)
✓ Homeoffice-Pauschale: €6/day × 210 days = €1,260 (at cap)
✓ Pendlerpauschale: €0 (homeoffice replaces commute)
? Gewerkschaftsbeitrag / Fortbildungskosten: enter amounts to refine
Estimated refund above Pauschbetrag: ~€340
UK pulls HMRC pension carry-forward; France applies the quotient familial and décote; the Netherlands handles Box 1/2/3. Six countries, one set of commands.
The locale data lives in a git submodule. If you cloned without --recurse-submodules, run:
git submodule update --init --recursiveThen re-run python3 skill.py --doctor to verify.
See CONTRIBUTING.md for the locale plugin spec — adding a new country is ~7 files.
# Full suite (main + locales + official validation)
python3 -m pytest tests/ locales/tests/ locales/validation/ -q
# 1,208 tests — all modules, all locales, all official tax authority cases
# Main skill only
python3 -m pytest tests/ -v
# Locale tax tests
python3 -m pytest locales/tests/ -v
# Official validation (BMF / HMRC / DGFiP / Belastingdienst / KAS)
python3 -m pytest locales/validation/ -vTests use an isolated .finance/ directory per test via the isolated_finance_dir autouse fixture — they never touch real data.
Key test files:
| File | What it tests |
|---|---|
tests/test_data_safety.py |
Encryption roundtrip, wrong passphrase, unique salts, permissions, git guard, encrypted export, sanitize |
tests/test_session_alerts.py |
Budget warnings, goal deadline alerts, urgency sorting |
tests/test_scenario_engine.py |
FIRE, salary comparison, rent-vs-buy, debt-vs-invest |
tests/test_investment_tracker.py |
FIRE number, portfolio growth projection, snapshots |
tests/test_debt_optimizer.py |
Avalanche vs snowball, interest savings, debt-free date |
tests/test_db.py |
SQLite schema init, CRUD operations, idempotent migration |
tests/test_monte_carlo.py |
All 4 simulators, percentile ordering, probability bounds, seeded reproducibility |
tests/test_recurring_engine.py |
Calendar-aware day clamping (Feb 28/29, Apr 30, Mar 31) |
locales/tests/test_de_tax.py |
German income tax, Soli, social contributions, 2024–2026 |
locales/tests/test_fr_tax.py |
French quotient familial, décote, CSG assiette réduite |
locales/tests/test_validation.py |
Official authority validation runner across all 5 locales |
locales/validation/*/ |
29 cases from BMF, HMRC, DGFiP, Belastingdienst, KAS |
