A brief table of contents for easy navigation.
Dojo is a self-hosted personal finance application for a household, designed to replace manual budgeting spreadsheets. It integrates an envelope budgeting system with a primary focus on net worth tracking, forecasting, and portfolio optimization. The goal is to provide a holistic view of financial health by directly linking day-to-day budgeting decisions to their long-term impact on wealth accumulation and preservation.
This section provides the fastest path to exercising the Auditable Ledger MVP.
git clone <repository-url>
cd dojo
direnv allow .The development shell installs Python, DuckDB, uv, and other pinned tooling. If you do not use direnv, run nix develop from the repo root before the commands below and run them directly inside that shell.
python -m dojo.core.migrateThis leaves the ledger completely empty so you can start from a clean slate at any time by deleting data/ledger.duckdb, rerunning migrations, and skipping the next (optional) step.
python -m dojo.core.seedThe seed scripts insert the house_* accounts and baseline categories so the SPA works immediately. Skip this step if you want to create every account/category yourself.
uvicorn dojo.core.app:create_app --factory --reload
# open http://127.0.0.1:8000/ in your browserThe landing page now behaves like a lightweight spreadsheet: the first row is an input line, arrow/tab keys move across cells, Enter submits, and the table below scrolls to show prior transactions.
scripts/run-tests
# gather python + Cypress coverage artifacts (clears coverage/ automatically)
scripts/run-tests --skip-e2e --coverageThe wrapper handles Vitest, pytest, and Cypress orchestration with terse summaries. When you pass --coverage, it collects backend coverage (via pytest-cov) plus frontend coverage (via @bahmutov/cypress-code-coverage) and writes reports into coverage/. Remove the coverage/ directory before manual reruns; the script already does this any time --coverage is supplied.
The Cypress run spins up a dedicated DuckDB database (data/e2e-ledger.duckdb) and launches the FastAPI server automatically via cypress.config.cjs, so no additional setup is required.
- FastAPI Monolith:
dojo.core.app:create_appis the sole entry point. It wires routers from each domain package (budgeting, core, investments, etc.) and builds dependencies throughbuild_containerso every request receives a scoped DuckDB connection and typed services. - DAO Layer: All SQL lives under
src/dojo/sql/**and is loaded through DAO classes (src/dojo/*/dao.py). Services only consume typed dataclasses returned by the DAO methods, which keeps business rules pure Python and ensures the temporal ledger constraints stay centralized. - SPA Composition:
src/dojo/frontend/static/main.jsbootstraps hash-based routing, registers each page module (components/{transactions,accounts,budgets,allocations,transfers}/index.js), and integrates the sharedstore.js. State updates are immutable (store.setState/patchState) and all DOM fetches go throughservices/api.js+services/dom.jshelpers. - Styles & Assets: Styles are broken into global primitives (
styles/base.css,styles/forms.css,styles/layout.css,styles/ledger.css) plus feature-scoped bundles instyles/components/*.cssthat follow the documented BEM conventions.
- What it does today: [List of current features]
- Out of Scope: [List of non-goals]
- Stability: The project is currently in alpha. APIs may change.
- Changelog: All changes are documented in the CHANGELOG.md.
- Operating Systems: Linux, macOS (via Nix)
This project manages system dependencies via Nix and Python dependencies via uv. We use direnv to automate the environment setup.
-
Clone the repository:
git clone <repository-url> cd dojo
-
Allow direnv: When you first
cdinto the directory,direnvwill prompt you for permission.direnv allow .This command triggers the activation of the Nix flake development shell, which in turn sets up the
uvvirtual environment based onpyproject.toml. -
Sync Python Dependencies (if needed): If you modify
pyproject.tomlor need to manually resync, run:uv sync
If you do not enable direnv, start a shell with nix develop from the repository root and run commands directly inside that shell.
nix developDOJO_DB_PATH: Override the DuckDB ledger location (defaults todata/ledger.duckdb). Set this before running migrations or starting the API, e.g.DOJO_DB_PATH=/tmp/ledger.duckdb python -m dojo.core.migrate.- Seed scripts: Run
python -m dojo.core.seed(optionally withDOJO_DB_PATHset) to populate dev/demo data. Skip it or delete the DuckDB file if you want a pristine ledger. dojo_prefixed env vars: All settings inherited fromdojo.core.config.Settingscan be supplied via environment variables (e.g.,DOJO_DB_PATH).- Secrets: The MVP stores only local household data and does not require external credentials yet. Do not commit secrets; once services require them, document the process in this section.
- Transactions (
#/transactions): Use the compact transaction form above the ledger to record inflows or outflows. Pick the account & category, set the type toggle (Outflow/Inflow), enter the memo/amount (always dollars), and submit to post/api/transactions. Click any ledger row (or its status pill) to edit inline—change the amount, account, category, flow, or pending/cleared flag—and save without leaving the table. Hero cards surface spent vs. budgeted month-to-date totals sourced directly from the ledger and allocation APIs so they reconcile with Ready-to-Assign. - Allocations (
#/allocations): Dedicated summary chips show “Inflow (this month)” and “Available to budget,” and the ledger lists every movement between envelopes. The form captures date, optional source category (defaulting to Ready-to-Assign), destination category, memo, and amount before posting to/api/budget/allocations. - Budgets (
#/budgets): The summary bar shows Ready-to-Assign, activity, available amounts, and the active month for quick context. Use “Add category” to open the modal (slug auto-fills from the name) and manage envelopes; the Allocate button routes to the allocations page with the destination pre-selected. - Categorized transfers (
#/transfers): The dedicated Transfers page hosts the dual-leg form. Select distinct source/destination accounts plus a reimbursement category, enter the amount in dollars, and submit to hit/api/transfers. The toast exposes concept + transaction ids so you can cross-check the ledger rows (data-concept-idattributes).
To verify the end-to-end flows manually:
-
Transaction capture & reconciliation:
- Navigate to
#/transactions. - Record a $50.00 outflow for "Dining Out".
- Click the row to edit, change the amount to $60.00 (adding a tip), and toggle status to "Cleared".
- Verify the row updates and the "Spent This Month" card increases.
- Navigate to
-
Allocation logging:
- Navigate to
#/budgetsand click a category row (e.g., "Groceries"). - Use the "Quick Allocations" buttons in the modal to fund the category.
- If you try to allocate more than your "Ready to Assign" balance, observe the error message preventing the action.
- Navigate to
-
Budget hierarchy management:
- Navigate to
#/budgets. - Click "+ Add group" to create a "Subscriptions" group.
- Create a new budget "Netflix" and assign it to "Subscriptions".
- Verify the hierarchy renders correctly and the "Uncategorized" group hides if it becomes empty.
- Navigate to
The MVP focuses on the FastAPI + SPA surface; there is no standalone CLI today. All interactions happen via HTTP:
# Insert a transaction via the API
python - <<'PY'
import requests
payload = {
"transaction_date": "2025-11-11",
"account_id": "house_checking",
"category_id": "groceries",
"amount_minor": -1234,
}
print(requests.post("http://127.0.0.1:8000/api/transactions", json=payload, timeout=10).json())
PYFor detailed architectural background see docs/architecture/overview.md.
- Logging: The FastAPI app logs to STDOUT; DuckDB connections log open/close events via
dojo.core.db. - Troubleshooting:
- Problem:
npx cypress run --e2e --browser <browser> [--headed]stalls because the test server never becomes healthy. Solution: Check the inline server logs printed by Cypress, ensure no other process is usingdata/e2e-ledger.duckdb, delete the file if needed, and rerun sotests.e2e.prepare_dbcan recreate it. - Problem: DuckDB file not found. Solution: Run the migration command (Step 2) or set
DOJO_DB_PATH.
- Problem:
- Performance Tips: Use in-memory DuckDB (
:memory:) for tests/experiments to avoid disk I/O when iterating on SQL. - Updating: After pulling new changes, run
uv sync --extra devto refresh dependencies. - Uninstalling: Remove the repo directory; DuckDB data lives under
data/.
We welcome contributions! Please follow these guidelines.
- Run Tests:
scripts/run-tests scripts/run-tests --skip-e2e --coverage # backend coverage + artifacts under coverage/ - Style Rules: We rely on Ruff for lint+format and Pyright for type checking.
ruff check . pyright - Branch/PR Guidelines: See CONTRIBUTING.md.
- Issue Templates: Use the provided templates for bug reports and feature requests.
- Code of Conduct: See CODE_OF_CONDUCT.md.
- License: This project is licensed under the MIT License.
- Security Policy: For responsible disclosure, please see our SECURITY.md.
- Provenance: [Note on data/models used]
- Telemetry: This project [does/does not] collect telemetry data. [Link to privacy policy if applicable].
- Get Help:
- Open an issue on GitHub.
- [Link to Discord/Slack/Forum]