feat(rabbits): medical/health record tracking #27

Merged
cpressland merged 2 commits from feat/health-records into main 2026-09-11 01:55:21 +01:00
Collaborator

Closes #22.

Summary

Adds medical/health record tracking per rabbit, per the refined design in #22.

Data model

New HealthRecord table: rabbit_id FK (cascade delete), date, record_type
(fixed enum: Vet Visit / Medication / Vaccination / Weight Check / Nail Clip /
Other), notes, weight_kg, next_due_date, created_at, user_id.

No migration statement needed — health_records is a brand new table, picked
up automatically by the existing Base.metadata.create_all() on boot.

UI

  • New /rabbits/{id} detail page (previously no dedicated rabbit page existed)
    with stat cards + a Health Records section.
  • Inline add/edit/delete via HTMX, mirroring the existing
    timesheets-on-employee pattern (_row.html / _edit_row.html /
    _new_dialog.html).
  • Added a "View" link on the rabbits list row to reach the new page.
  • Overdue next_due_date values render in red.

Permissions

  • Staff and admin can add/edit/delete records (matches existing rabbit-edit
    permission level).
  • Viewer and above can read.
  • Not append-only — records can be corrected/removed like other data in the app.

Audit log

  • create/update/delete on health records are logged via the existing
    log_action/compute_diff helpers.
  • Dashboard activity feed resolves health record entries to the owning
    rabbit's name (new health_records case in _record_label), same as
    other linked-record tables.

Out of scope (per issue discussion)

  • Weight-over-time chart — weight_kg is stored now, charting deferred to a
    future issue once there's more data logged.
  • Feeding the "needs attention" dashboard widget from next_due_date — noted
    as a follow-on dependency on #21, not part of this PR.

Testing

No local Postgres/Docker available in the sandbox this was built in, so I
wrote a throwaway smoke-test script exercising the actual FastAPI router
against an in-memory SQLite engine (schema/ORM layer is portastable) —
covered: detail page render, new-record dialog, create (form POST + HTML
response + DB row), edit dialog, update (form PUT + DB row), audit log
entries for create/update/delete, delete, and cascade-delete-orphan when a
rabbit is removed. All 10 checks passed. The script was for local
verification only and is not included in this PR — happy to re-run it live
against the real Postgres instance before merge if useful.

Please test against the live app and let me know if anything needs
adjusting.

Closes #22. ## Summary Adds medical/health record tracking per rabbit, per the refined design in #22. ## Data model New `HealthRecord` table: `rabbit_id` FK (cascade delete), `date`, `record_type` (fixed enum: Vet Visit / Medication / Vaccination / Weight Check / Nail Clip / Other), `notes`, `weight_kg`, `next_due_date`, `created_at`, `user_id`. No migration statement needed — `health_records` is a brand new table, picked up automatically by the existing `Base.metadata.create_all()` on boot. ## UI - New `/rabbits/{id}` detail page (previously no dedicated rabbit page existed) with stat cards + a Health Records section. - Inline add/edit/delete via HTMX, mirroring the existing timesheets-on-employee pattern (`_row.html` / `_edit_row.html` / `_new_dialog.html`). - Added a "View" link on the rabbits list row to reach the new page. - Overdue `next_due_date` values render in red. ## Permissions - Staff and admin can add/edit/delete records (matches existing rabbit-edit permission level). - Viewer and above can read. - Not append-only — records can be corrected/removed like other data in the app. ## Audit log - create/update/delete on health records are logged via the existing `log_action`/`compute_diff` helpers. - Dashboard activity feed resolves health record entries to the owning rabbit's name (new `health_records` case in `_record_label`), same as other linked-record tables. ## Out of scope (per issue discussion) - Weight-over-time chart — `weight_kg` is stored now, charting deferred to a future issue once there's more data logged. - Feeding the "needs attention" dashboard widget from `next_due_date` — noted as a follow-on dependency on #21, not part of this PR. ## Testing No local Postgres/Docker available in the sandbox this was built in, so I wrote a throwaway smoke-test script exercising the actual FastAPI router against an in-memory SQLite engine (schema/ORM layer is portastable) — covered: detail page render, new-record dialog, create (form POST + HTML response + DB row), edit dialog, update (form PUT + DB row), audit log entries for create/update/delete, delete, and cascade-delete-orphan when a rabbit is removed. All 10 checks passed. The script was for local verification only and is not included in this PR — happy to re-run it live against the real Postgres instance before merge if useful. Please test against the live app and let me know if anything needs adjusting.
Add HealthRecord model + rabbit detail page for logging vet visits,
medications, vaccinations, weight checks, nail clips, and other care
events per rabbit.

- New HealthRecord table: date, fixed record_type enum, notes,
  weight_kg, next_due_date, created_at, user_id. Cascade-deletes with
  its rabbit.
- New /rabbits/{id} detail page with a Health Records section
  (reverse-chronological, inline add/edit/delete via HTMX, mirroring
  the existing timesheets-on-employee pattern).
- Staff and admin can add/edit/delete records; viewer+ can read,
  matching existing rabbit-edit permission level. Not append-only —
  mistakes can be corrected.
- Audit log entries for health record create/update/delete, with
  dashboard labels resolving to the owning rabbit's name.
- 'View' link added to the rabbits list row to reach the new detail
  page.
- Weight is stored as its own column now; a weight-over-time chart is
  intentionally out of scope (future enhancement).
- No new migration statement needed: health_records is a brand new
  table, picked up by the existing Base.metadata.create_all() on boot.

Closes #22
The rabbit detail and new-record-dialog routes passed a context var named
'today' (an ISO date string, for the date input's default value). That
name collides with the 'today' global registered in templates_config.py
(datetime.date.today, used by _health_row.html to flag overdue
next_due_date values), so calling today() inside the row template raised
'str object is not callable' and broke /rabbits/{id} entirely whenever a
health record was present.

Caught this by actually rendering the page in a browser rather than just
unit-testing the router in isolation. Renamed the route-supplied value to
today_iso to remove the collision.
Author
Collaborator

Screenshots

Rendered the branch locally (SQLite demo instance, seeded with a sample
rabbit + 4 health records) and drove it with a real headless Chrome to
capture the actual pages — not mockups.

1. Rabbits list — new "View" link
rabbits list

2. Rabbit detail page — Health Records section
rabbit detail

3. Add Health Record dialog
add dialog

4. Inline edit row
edit row

Note the red "06 Sep 2026" in screenshot 2/4 — that's an overdue next_due_date
rendering correctly.

Bug found + fixed while capturing these

Actually rendering /rabbits/{id} in a browser (rather than just the
SQLite-router smoke test from the PR description) surfaced a real bug:
the route passed a context variable named today (an ISO date string for
the date-input default), which shadowed the today Jinja global
registered in templates_config.py (datetime.date.today, used by
_health_row.html to flag overdue records). That collision broke the page
with TypeError: 'str' object is not callable any time a rabbit had at
least one health record.

Fixed in the latest commit by renaming the route-supplied value to
today_iso. Screenshots above are from the branch after this fix —
confirmed working end-to-end.

## Screenshots Rendered the branch locally (SQLite demo instance, seeded with a sample rabbit + 4 health records) and drove it with a real headless Chrome to capture the actual pages — not mockups. **1. Rabbits list — new "View" link** ![rabbits list](https://git.cpressland.io/attachments/715476da-8f3a-4051-a6c0-ccd6ede0ba04) **2. Rabbit detail page — Health Records section** ![rabbit detail](https://git.cpressland.io/attachments/e0cdde90-17e9-45ed-b9c5-96f9384abcbe) **3. Add Health Record dialog** ![add dialog](https://git.cpressland.io/attachments/3b1706fe-f6a9-4b0a-a8bc-7f0b876d1096) **4. Inline edit row** ![edit row](https://git.cpressland.io/attachments/5e9bf8c0-cfce-4a18-b8d2-f4692b9a421d) Note the red "06 Sep 2026" in screenshot 2/4 — that's an overdue `next_due_date` rendering correctly. ## Bug found + fixed while capturing these Actually rendering `/rabbits/{id}` in a browser (rather than just the SQLite-router smoke test from the PR description) surfaced a real bug: the route passed a context variable named `today` (an ISO date string for the date-input default), which shadowed the `today` Jinja **global** registered in `templates_config.py` (`datetime.date.today`, used by `_health_row.html` to flag overdue records). That collision broke the page with `TypeError: 'str' object is not callable` any time a rabbit had at least one health record. Fixed in the latest commit by renaming the route-supplied value to `today_iso`. Screenshots above are from the branch *after* this fix — confirmed working end-to-end.
cpressland deleted branch feat/health-records 2026-09-11 01:55:21 +01:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
cpressland/rabbitdb!27
No description provided.