Local-only Python pipeline that pulls financial data from Akahu (a New Zealand open-banking aggregator), stores it as plain JSON on disk, and provides a CLI for spending analysis, budgeting, investments, net-worth tracking, and HTML reports. Optimised for fast AI-driven iteration — every artefact is hand-readable and git diff-able.
Status: Actively used. All commands are implemented and tested (467 unit tests, all green). See
ROADMAP.mdfor what's next andREVIEW.mdfor a recent code review.
# 1. Install
python3 -m venv .venv
source .venv/bin/activate
pip install -e .
# 2. Configure
cp .env.example .env
# edit .env and fill in AKAHU_USER_ACCESS_TOKEN + AKAHU_APP_ID_TOKEN
# 3. First sync
finance accounts # list connected accounts; copy IDs into config/accounts.json
finance sync --full # pull all available history (~365 days for personal apps)
finance categorize # apply config/category_rules.json → data/derived/
# 4. Iterate on categories
finance spend uncategorised # see what's not matching yet
finance review # interactive merchant review — appends rules
# (optional) finance review --ai # pre-fill suggestions using Claude Haiku
# 5. Analyse
finance spend # current pay cycle (default)
finance spend month # current calendar month
finance spend cycle 2026-04 # named pay cycle
finance spend by-category --from 2026-04-01 --to 2026-04-30
finance spend top-merchants --n 10
finance spend trend --months 6 # per-cycle category trend
finance spend cat "Eating Out" --cycle 2026-04 # drill into a single category
finance spend income # income breakdown
finance budget status # actuals vs config/budgets.json
finance budget status --cycle # same, on pay cycle
# 6. Investments & net worth
finance investments # KiwiSaver + Sharesies + manual
finance net-worth snapshot # record today's net worth
finance net-worth # latest snapshot
finance net-worth trend # over time
# 7. Debt tracking
finance debt # summary: balances, rates, payoff dates
finance debt payoff "Home Loan" # amortisation drilldown
finance debt payoff "Home Loan" --extra 200 # what-if: +$200/mo extra payment
finance debt payoff "Visa" --lump 5000 # what-if: $5,000 lump sum first
# 8. HTML report
finance report # open a self-contained report.html
finance report --cycle 2026-04 # a specific pay cycle
finance report --month 2026-04 # calendar month instead
finance report --output ~/Desktop/april.html # custom output path
finance report --no-open # write without auto-opening
# 8. Savings & goals
finance savings # current-cycle savings summary by bucket
finance savings cycle 2026-04 # specific cycle
finance savings trend # savings rate trend over time
finance goals # progress toward each financial goal
finance forecast # net-worth projection scenarios
# 9. Export / validate
finance export --from 2026-01-01 --out 2026.csv # CSV export
finance validate # check rules against taxonomy| Command | Purpose |
|---|---|
finance sync |
Pull last 90 days of transactions + current balances. Idempotent. Use --full for all history. |
finance accounts |
Show connected accounts with IDs and balances. Use this to seed config/accounts.json. |
finance categorize |
Apply config/category_rules.json (regex) and config/akahu_category_map.json (Akahu fallback) to data/raw/ → data/derived/transactions_categorised.json. |
finance review |
Interactive merchant review — walk through uncategorised merchants ranked by spend, append rules. --ai pre-fills suggestions via Claude Haiku; --auto-apply-high accepts high-confidence ones without prompting. |
finance spend |
Defaults to current pay cycle. Subcommands: month, cycle, by-category, top-merchants, trend, cat, income, uncategorised. |
finance budget status |
Actual spend vs config/budgets.json for the current period. --cycle switches calendar→pay-cycle. |
finance investments |
Portfolio breakdown for KiwiSaver, Sharesies, and manually-tracked investment accounts. Shows breakdown, holdings, contributions/returns since first snapshot. |
finance net-worth |
Latest net-worth snapshot. snapshot (record a new one, prompting for manual balances) and trend (over time) subcommands. |
finance debt |
Debt summary: per-debt balance, rate, minimum payment, paid-this-cycle, projected payoff date. payoff <name> for a full amortisation drilldown with --extra and --lump what-ifs. |
finance report |
Generate a self-contained report.html with headline cards, budget progress bars, debt section, top-category breakdown, and a 6-cycle spend trend chart. Opens in the browser automatically. |
finance savings |
Savings summary by bucket for the current cycle. Subcommands: cycle, trend, bucket, goals, plan, reconcile. |
finance goals |
Progress toward each configured financial goal. set subcommand to add/update goals. |
finance forecast |
Net-worth projection scenarios — base, optimistic, pessimistic — incorporating debt paydown and investment growth. |
finance export |
Export categorised transactions as CSV. Stdout or --out FILE. |
finance validate |
Check config/category_rules.json against the taxonomy in config/categories.json. |
Global flags on every command: --format {table,json,csv}, --quiet, --no-color.
Filter flags on every spend subcommand: --from YYYY-MM-DD, --to YYYY-MM-DD, --owner, --category.
When you spot a misclassification:
finance spend uncategorised— see merchants that don't yet match a rule.- Either edit
config/category_rules.jsondirectly (add a regex rule with the rightcategory), or runfinance reviewfor an interactive walkthrough that writes rules for you. finance categorize— re-applies rules to all data.- Re-run
finance spend ...to verify.
No spreadsheet round-trip, no formula breakage. The whole loop is text and git diff makes every change obvious.
Pay cycles align with when salary actually lands, not a fixed day of month. On first run the pipeline falls back to a 20th-of-month rule, but as soon as you've categorised any transaction as Income > Salary, assign_pay_cycles_from_salary re-stamps every transaction's pay_cycle based on the most recent pay date on or before it. The cycle label is the YYYY-MM of the pay date itself.
Example: paid 2026-04-18 → all transactions from 2026-04-18 up to (but not including) the next pay date belong to cycle 2026-04.
finance-dashboard/
├── pyproject.toml # `pip install -e .` registers the `finance` console script
├── .env # Akahu + (optional) Anthropic tokens — gitignored
├── README.md # this file
├── REVIEW.md # recent code review
├── ROADMAP.md # phased plan: Now / Next / Later
├── config/ # hand-edited JSON
│ ├── accounts.json # account_id → {owner, display_name}
│ ├── categories.json # canonical taxonomy
│ ├── category_rules.json # regex → category, with priority + owner filters
│ ├── akahu_category_map.json# Akahu category → your taxonomy (fallback)
│ ├── budgets.json # per-category budget + default period
│ ├── manual_balances.json # accounts Akahu can't reach (Squirrel, Whai Rawa, ...)
│ ├── manual_contributions.json # per-cycle contribution ledger
│ └── contributions_map.json # transaction-pattern → investment account
├── data/ # generated; gitignored
│ ├── raw/ # source of truth: accounts, transactions/YYYY-MM.json, balances/YYYY-MM-DD.json
│ └── derived/ # rebuilt from raw + config (transactions_categorised.json)
├── finance/ # the package
│ ├── cli/ # CLI package (argparse wiring + thin command shims)
│ │ ├── __init__.py # build_parser(), main(), global flags
│ │ ├── sync.py # sync, accounts
│ │ ├── categorise.py # categorize, review
│ │ ├── spend.py # all spend subcommands
│ │ ├── budget.py # budget status
│ │ ├── investments.py # investments
│ │ ├── net_worth.py # net-worth (snapshot, trend)
│ │ ├── savings.py # savings goals + bucket tracking
│ │ ├── forecast.py # net-worth projection
│ │ ├── goals.py # financial goals
│ │ ├── export.py # export, validate
│ │ └── report.py # report (HTML dashboard)
│ ├── config.py # path constants, env loading, pay-cycle helpers
│ ├── akahu.py # Akahu REST client (retries, pagination, 429 handling)
│ ├── transform.py # pure: Akahu shape → internal shape
│ ├── categorise.py # rules, taxonomy, apply, validate
│ ├── spending.py # filters + rollups + trend matrix
│ ├── budget.py # period progress + status_for_period
│ ├── net_worth.py # snapshot building, totals, trend series, contributions
│ ├── debt.py # pure: amortisation, payoff projections, what-ifs
│ ├── savings.py # savings tracking — goals, buckets, inflow attribution
│ ├── transfers.py # transfer detection + classification rules
│ ├── buckets.py # savings bucket state + allocation helpers
│ ├── forecast.py # net-worth projection scenarios
│ ├── goals.py # financial goal modelling
│ ├── rollover.py # pay-cycle budget rollover logic
│ ├── report_html.py # pure: data dicts → self-contained HTML string
│ ├── store.py # JSON I/O for raw + derived (schema-versioned)
│ ├── review.py # interactive merchant review
│ ├── ai.py # Anthropic API integration (optional, for `--ai`)
│ ├── reporters.py # `rich` table/kv/JSON/CSV rendering
│ └── __main__.py # enables `python -m finance`
├── scripts/ # one-shot migration utilities
└── tests/ # 467 unit tests, all pure + fast (~13s)
The split is pure functions in the core modules (transform, spending, budget, categorise, net_worth, report_html); I/O at the edges (store, akahu, ai); thin CLI shims (cli/, reporters). This is what makes the test suite cheap to maintain.
pytest # 467 unit tests
ruff check . # lint + import order
mypy # strict type-checking on transform, spending, budgetTests cover transform, categorise, spending, budget, review, net_worth, debt, savings, transfers, buckets, forecast, goals, rollover, store (schema versioning), and end-to-end CLI smoke tests. Fixtures live in tests/fixtures/. All three checks run in CI on Python 3.10 and 3.12 via GitHub Actions.
The original version of this project wrote everything to Google Sheets. That made iteration painfully slow — every "what's in the data right now?" question required an API round-trip, and formula tabs drifted silently. JSON files on disk give us:
- Zero round-trip inspection — read a file, see the data.
- Clean
git diffscoped to what actually changed. - Hand-editable config — the iteration loop is "edit JSON, re-run CLI, check diff".
- Snapshot tests that catch regressions in seconds.
If you set ANTHROPIC_API_KEY in .env, finance review --ai will use Claude Haiku to pre-fill category suggestions for each uncategorised merchant. Cost is ~$0.0003 per merchant (a full 100-merchant review is about 3 cents). The default model is claude-haiku-4-5 and can be overridden with ANTHROPIC_MODEL. If the API key isn't set, --ai is silently disabled and review continues in manual mode.