projects / pennypilot
PennyPilot
Local-first personal finance. Python reads bank-statement PDFs on your machine and serves a small dashboard over localhost.
- Problem
- Bank PDFs are awkward when you want spending, budgets, recurring payments, and what is safe to spend. Those statements should stay on the machine that already has the files.
- Engineering approach
- One Python process imports statements, categorises transactions, and serves the dashboard locally. The browser talks only to that server. State lives in files: CSV, JSON, Excel, and XML, with PDF as the statement input.
- Technologies
- Python 3
- HTML
- CSS
- JavaScript
- pdfplumber
- pandas
- openpyxl
- Verified results
- A local dashboard for overview, transactions, spending, budgets, exports, safe-to-spend, and recurring payments. Parsers cover current-account and credit-card statements.
- Status
- Personal tool; repository on GitHub. Public demo detail stays limited because the app reads local bank statements.
Languages
PennyPilot is written in four languages. Python does the processing. The dashboard is plain HTML, CSS, and JavaScript. Notes live in Markdown (README.md, DESIGN.md, and specs under docs/ and ui/).
- Python 3. The application. It imports bank statements, categorises transactions, calculates budgets and safe-to-spend, and writes exports. The package lives in
src/, withmain.pyas the batch entry point andsrc/ui_server.pyas the local server. - HTML. Two pages:
ui/index.html(overview, transactions, spending, budgets, exports, safe-to-spend) andui/recurring.html. - CSS. One hand-written stylesheet,
ui/styles.css. - JavaScript. Vanilla ES modules:
ui/app.js,ui/recurring.js, andui/common.js. The browser fetches JSON from the local server and renders the dashboard.
Runtime and libraries
Python 3 is the whole backend. main.py runs import and calculations. python3 -m src.ui_server serves the dashboard locally.
Three packages are installed from requirements.txt. Everything else is the standard library: http.server (ThreadingHTTPServer), json, pathlib, re, xml.etree.ElementTree, hashlib, uuid, argparse, shutil, and datetime.
- pdfplumber. Reads text out of bank-statement PDFs.
- pandas. Holds transaction tables and does aggregation, monthly summaries, and exports.
- openpyxl. Writes Excel workbooks, including styled dashboard sheets.
Backend
A single-process Python package under src/. The UI server is a threaded HTTP handler that serves static files and JSON APIs such as /api/dashboard, /api/transactions, and /api/months. On startup it can run a full update: detect the bank, parse the PDF, clean and categorise rows, then write exports.
Statement parsers cover current-account exports and credit cards. Categorisation is rule-based: keywords, payee aliases, a taxonomy, and a local learning file. Budget, recurring payments, safe-to-spend, and credit-card breakdowns are computed in Python and returned as JSON.
Data
There is no SQL database. State lives in files. PDF is the input format for bank statements. XML exports are built with the standard-library XML library.
| Store | What it holds |
|---|---|
CSV (data/) | Transactions, manual entries, overrides, accounts, categories, merchants, monthly summaries, subscriptions. |
JSON (config/ and data/) | Budgets, pay cycle, taxonomy, payee aliases, recurring rules, savings, commitments, merchant learnings, transaction groups. |
Exports (output/) | The same datasets written out as CSV, Excel (.xlsx), and XML. |
Frontend
Vanilla HTML, CSS, and JavaScript in ui/. index.html and recurring.html are the two pages. app.js and recurring.js are ES modules that fetch JSON from the local API and render the dashboard: overview, transactions, spending, budgets, exports, safe-to-spend, and recurring. common.js holds shared helpers for fetch, money formatting, and saved UI state.
styles.css is hand-written CSS. DESIGN.md and the Apple UI rulebook notes record the visual direction; they are design references. The browser only talks to the local server.