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 ## Run the app
```sh ```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. 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 ## Run tests
```sh ```sh
@ -34,7 +44,7 @@ uv run pytest
``` ```
Tests use an in-memory SQLite engine; no separate migration step 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 ## Developing against a throwaway DB
@ -62,12 +72,15 @@ src/quartermaster/
main.py FastAPI app factory main.py FastAPI app factory
routes.py Budget configuration handlers routes.py Budget configuration handlers
routes_month.py Monthly view handlers + lifecycle transitions 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 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 groups.py Group enum, labels, section->group mapping
models.py SQLAlchemy models, Section enum, MonthState enum models.py SQLAlchemy models, Section enum, MonthState enum
db.py Engine, session, PRAGMA foreign_keys=ON db.py Engine, session, PRAGMA foreign_keys=ON
config.py DB URL resolution 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/ templates/
base.html Barlow Condensed imports + favicon base.html Barlow Condensed imports + favicon
index.html Budget config page index.html Budget config page
@ -100,9 +113,14 @@ tests/
test_groups.py Group mapping + subtotals + default-open test_groups.py Group mapping + subtotals + default-open
test_notes.py Notes field + snapshot copy test_notes.py Notes field + snapshot copy
test_month_lifecycle.py Transitions + close gate + edit locking 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_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 CLAUDE.md DB safety rule, repo-level instructions
docs/ docs/
superpowers/ Specs and plans (per-session design notes)
wiki/ Cloned wiki (gitignored) wiki/ Cloned wiki (gitignored)
mockups/ Design history + mockup HTML 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 Each commit is one logical change. Typical PRs in this repo have 3-6
commits grouped by layer (data model, service, routes, templates, commits grouped by layer (data model, service, routes, templates,
tests, docs). Commit subjects under 72 chars, body under 80, trailers tests, docs), or more for TDD-driven feature work where each task
include `Refs #<issue>` and the Claude co-author line. 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 ### 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` 4. Close the tracking issue manually via the API (the `Closes #N`
keyword is unreliable on this Forgejo instance). 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 ### Adding a schema change
1. Edit `src/quartermaster/models.py`. 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 4. Claude cannot run a real browser from the CLI; the human eyeball is
the final check before merge. 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 ### Design workflow
UI experiments live under `docs/mockups/` as self-contained HTML files UI experiments live under `docs/mockups/` as self-contained HTML files