docs(dev): update Run block, project layout, conventions for logging + /healthz
parent
dd3ffd3453
commit
cbd2e11a64
1 changed files with 76 additions and 38 deletions
|
|
@ -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
|
||||
|
||||
|
|
@ -59,30 +69,33 @@ rm -rf /tmp/qm-dev.db /tmp/qm-dev-backups
|
|||
|
||||
```
|
||||
src/quartermaster/
|
||||
main.py FastAPI app factory
|
||||
routes.py Budget configuration handlers
|
||||
routes_month.py Monthly view handlers + lifecycle transitions
|
||||
service.py Budget service: entries, target, zero-amount, groups
|
||||
month_service.py Month service: snapshot, deviation, lifecycle, groups
|
||||
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
|
||||
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, 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
|
||||
month.html Month view page
|
||||
month_create.html Create-flow for a missing month
|
||||
base.html Barlow Condensed imports + favicon
|
||||
index.html Budget config page
|
||||
month.html Month view page
|
||||
month_create.html Create-flow for a missing month
|
||||
partials/
|
||||
section.html Budget section card
|
||||
target_card.html Budget debt target card
|
||||
budget_zero.html Budget Zero Amount widget (with logo)
|
||||
budget_group_totals.html OOB swap for all four budget group totals
|
||||
month_section.html Month section card with inline HTMX edits
|
||||
month_target.html Month debt target card
|
||||
month_zero.html Month Zero Amount widget (with logo)
|
||||
month_group_totals.html OOB swap for all four month group totals
|
||||
month_nav.html prev / title / next / state / lifecycle button
|
||||
section.html Budget section card
|
||||
target_card.html Budget debt target card
|
||||
budget_zero.html Budget Zero Amount widget (with logo)
|
||||
budget_group_totals.html OOB swap for all four budget group totals
|
||||
month_section.html Month section card with inline HTMX edits
|
||||
month_target.html Month debt target card
|
||||
month_zero.html Month Zero Amount widget (with logo)
|
||||
month_group_totals.html OOB swap for all four month group totals
|
||||
month_nav.html prev / title / next / state / lifecycle button
|
||||
static/
|
||||
app.css Full stylesheet (Barlow Condensed + ledger layout)
|
||||
brand/ Optimised logo assets
|
||||
|
|
@ -92,19 +105,24 @@ alembic/
|
|||
scripts/
|
||||
backup-db.sh sqlite3.Connection.backup wrapper
|
||||
tests/
|
||||
test_service.py Budget service
|
||||
test_routes.py Budget routes
|
||||
test_month_service.py Month snapshot + CRUD
|
||||
test_month_routes.py Month routes
|
||||
test_zero_amount.py Zero-amount math + widgets
|
||||
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_backup_script.py Backup script shell behaviour
|
||||
CLAUDE.md DB safety rule, repo-level instructions
|
||||
test_service.py Budget service
|
||||
test_routes.py Budget routes
|
||||
test_month_service.py Month snapshot + CRUD
|
||||
test_month_routes.py Month routes
|
||||
test_zero_amount.py Zero-amount math + widgets
|
||||
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/
|
||||
wiki/ Cloned wiki (gitignored)
|
||||
mockups/ Design history + mockup HTML
|
||||
superpowers/ Specs and plans (per-session design notes)
|
||||
wiki/ Cloned wiki (gitignored)
|
||||
mockups/ Design history + mockup HTML
|
||||
```
|
||||
|
||||
## Conventions
|
||||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in a new issue