113 lines
3.5 KiB
Markdown
113 lines
3.5 KiB
Markdown
# quartermaster
|
|
|
|
Household budget tracker. FastAPI + HTMX frontend, SQLite backend.
|
|
|
|
## Pages
|
|
|
|
* `/` budget configuration. One section per category (Incomes, Fixed Amount
|
|
Bills, Debt Minimums, Primary Debt Target, Food and Essentials,
|
|
Subscriptions, Other). Every section accepts name + amount entries and
|
|
shows a running total. The Primary Debt Target is a pointer to a Debt
|
|
Minimums row.
|
|
* `/month/YYYY-MM` monthly view. Snapshots the budget at creation time and
|
|
tracks an `applied` amount per entry alongside the planned amount. Each
|
|
row is annotated when its name or planned value has been edited away
|
|
from the snapshot, or when the row was added after creation. Per-month
|
|
debt target is independent of the budget's target after snapshot.
|
|
|
|
Navigate between months with the prev / next buttons or the dropdown
|
|
picker. A "This month" link on `/` jumps to the current `YYYY-MM`; if it
|
|
has not been created yet, you land on the create flow.
|
|
|
|
## Requirements
|
|
|
|
* Python 3.12+
|
|
* [uv](https://github.com/astral-sh/uv) for dependency management
|
|
|
|
## Setup
|
|
|
|
```sh
|
|
uv sync
|
|
uv run alembic upgrade head
|
|
```
|
|
|
|
The SQLite file lives at `./quartermaster.db` by default. Override with the
|
|
`QUARTERMASTER_DB_URL` environment variable (any SQLAlchemy URL).
|
|
|
|
## Run
|
|
|
|
```sh
|
|
uv run uvicorn quartermaster.main:app \
|
|
--log-config src/quartermaster/logconfig.json \
|
|
--reload
|
|
```
|
|
|
|
Open http://127.0.0.1:8000.
|
|
|
|
## Logs
|
|
|
|
Logs are JSON on stdout. Each line has `level` and `event` as queryable
|
|
Loki labels (indexed by Promtail on the deploy host), plus arbitrary
|
|
extra fields in the JSON body.
|
|
|
|
Example LogQL queries in Grafana Explore (Loki data source):
|
|
|
|
```
|
|
{container="quartermaster"} | json
|
|
{container="quartermaster", event="http_request", status=~"5.."}
|
|
{container="quartermaster", event="month_closed"} | json | line_format "{{.path}} {{.message}}"
|
|
```
|
|
|
|
HTTP access logs appear as `event="http_request"` with `method`, `path`,
|
|
`status`, and `client_ip` extras. Application events (`month_created`,
|
|
`month_closed`, `template_entry_updated`, `posting_added`,
|
|
`posting_deleted`) fire at the matching mutation sites.
|
|
|
|
## Tests
|
|
|
|
```sh
|
|
uv run pytest
|
|
```
|
|
|
|
Tests run against an in-memory SQLite database; no migration step needed.
|
|
|
|
## Backups
|
|
|
|
The SQLite data file is precious and gitignored. Before any schema
|
|
change or destructive operation, back it up:
|
|
|
|
```sh
|
|
./scripts/backup-db.sh <reason>
|
|
```
|
|
|
|
Backups land next to the database in `./backups/` by default
|
|
(`QUARTERMASTER_BACKUP_DIR=/some/path` to override) as
|
|
`quartermaster-YYYYMMDD-HHMMSS-{slug}.db` using SQLite's online backup
|
|
API (safe even while the app is writing). Alembic invokes the script
|
|
automatically before every migration via `alembic/env.py`; retention
|
|
is forever for now. To restore, stop the app, copy the chosen backup
|
|
over `quartermaster.db`, and restart.
|
|
|
|
## Project Layout
|
|
|
|
```
|
|
src/quartermaster/
|
|
main.py FastAPI app factory
|
|
routes.py Budget configuration HTTP handlers
|
|
routes_month.py Monthly view HTTP handlers
|
|
service.py Budget queries, totals, target logic
|
|
month_service.py Snapshot, deviation, per-month CRUD
|
|
models.py SQLAlchemy models and Section enum
|
|
db.py Engine, session, PRAGMA foreign_keys=ON
|
|
config.py DB URL resolution
|
|
templates/ Jinja2 templates (base, index, month, partials)
|
|
static/ CSS
|
|
alembic/ Migrations
|
|
tests/ pytest suite
|
|
```
|
|
|
|
## Deferred
|
|
|
|
A transaction log that rolls up into `applied` on a per-entry per-month
|
|
basis is deferred. Once implemented it may replace the hand-edited applied
|
|
field.
|