docs(dev): update Run block, project layout, conventions for logging + /healthz

claude-code 2026-04-19 12:32:57 -06:00
parent dd3ffd3453
commit cbd2e11a64

@ -22,11 +22,21 @@ file elsewhere.
## Run the app
```sh
uv run uvicorn quartermaster.main:app --reload
uv run uvicorn quartermaster.main:app \
--log-config src/quartermaster/logconfig.json \
--reload
```
Open http://127.0.0.1:8000.
The `--log-config` flag emits structured JSON to stdout (Loki-ready
when deployed). Omit it if you'd rather read logs in uvicorn's default
human-readable format during local dev; production must use the config.
Health probe (unauthenticated): `curl http://127.0.0.1:8000/healthz`
returns `{"status":"ok"}` on a healthy DB, 503 with the exception
class name in `detail` otherwise.
## Run tests
```sh
@ -34,7 +44,7 @@ uv run pytest
```
Tests use an in-memory SQLite engine; no separate migration step
needed. Full suite runs in under 4 seconds.
needed. Full suite runs in under 6 seconds.
## Developing against a throwaway DB
@ -62,12 +72,15 @@ src/quartermaster/
main.py FastAPI app factory
routes.py Budget configuration handlers
routes_month.py Monthly view handlers + lifecycle transitions
routes_health.py GET /healthz (separate router, unauthenticated)
service.py Budget service: entries, target, zero-amount, groups
month_service.py Month service: snapshot, deviation, lifecycle, groups
month_service.py Month service: snapshot, deviation, lifecycle, groups, postings
groups.py Group enum, labels, section->group mapping
models.py SQLAlchemy models, Section enum, MonthState enum
db.py Engine, session, PRAGMA foreign_keys=ON
config.py DB URL resolution
logging_config.py Loads LOG_CONFIG from logconfig.json; AccessLogFilter
logconfig.json dictConfig source-of-truth for JSON stdout logs
templates/
base.html Barlow Condensed imports + favicon
index.html Budget config page
@ -100,9 +113,14 @@ tests/
test_groups.py Group mapping + subtotals + default-open
test_notes.py Notes field + snapshot copy
test_month_lifecycle.py Transitions + close gate + edit locking
test_postings.py Posting CRUD + derived applied
test_template_edit_isolation.py Template edit doesn't leak into snapshots
test_backup_script.py Backup script shell behaviour
test_logging.py JSON formatter, access filter, dictConfig, seed events
test_health.py /healthz 200 success + 503 on DB failure
CLAUDE.md DB safety rule, repo-level instructions
docs/
superpowers/ Specs and plans (per-session design notes)
wiki/ Cloned wiki (gitignored)
mockups/ Design history + mockup HTML
```
@ -124,8 +142,10 @@ chore/<issue>-<slug> tooling, deps, docs
Each commit is one logical change. Typical PRs in this repo have 3-6
commits grouped by layer (data model, service, routes, templates,
tests, docs). Commit subjects under 72 chars, body under 80, trailers
include `Refs #<issue>` and the Claude co-author line.
tests, docs), or more for TDD-driven feature work where each task
gets its own failing-test / implementation / commit triple. Commit
subjects use conventional-commits prefixes (`feat:`, `fix:`, `chore:`,
`test:`, `docs:`, `refactor:`), under 72 chars. Body under 80.
### Merge flow
@ -138,6 +158,9 @@ PRs merge via the Forgejo API (or the web UI merge button), NOT
4. Close the tracking issue manually via the API (the `Closes #N`
keyword is unreliable on this Forgejo instance).
Exception: purely local direct-to-main merges (rare) rebase onto
origin/main first to keep linear history.
### Adding a schema change
1. Edit `src/quartermaster/models.py`.
@ -161,6 +184,21 @@ affects the HTML, also:
4. Claude cannot run a real browser from the CLI; the human eyeball is
the final check before merge.
### Adding a structured log event
1. Pick a namespaced module logger if one doesn't exist in the file
you're touching: `logger = logging.getLogger("quartermaster.<module>")`
placed after all imports.
2. Use `logger.info` (or `.warning`/`.error`) with `extra={"event": "...",
...additional_fields}`. The `event` name should be a concise
snake_case verb_noun.
3. Place the log after the commit / refresh in service-layer functions,
before `return`, so it only fires on durable success.
4. Write a test using `caplog` that asserts a record with the expected
`event` attribute is emitted. See `tests/test_logging.py` for the
pattern.
5. Document the new event in the table in [Operations](Operations).
### Design workflow
UI experiments live under `docs/mockups/` as self-contained HTML files