feat(rabbits): medical/health record tracking #27
Loading…
Reference in a new issue
No description provided.
Delete branch "feat/health-records"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Closes #22.
Summary
Adds medical/health record tracking per rabbit, per the refined design in #22.
Data model
New
HealthRecordtable:rabbit_idFK (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_recordsis a brand new table, pickedup automatically by the existing
Base.metadata.create_all()on boot.UI
/rabbits/{id}detail page (previously no dedicated rabbit page existed)with stat cards + a Health Records section.
timesheets-on-employee pattern (
_row.html/_edit_row.html/_new_dialog.html).next_due_datevalues render in red.Permissions
permission level).
Audit log
log_action/compute_diffhelpers.rabbit's name (new
health_recordscase in_record_label), same asother linked-record tables.
Out of scope (per issue discussion)
weight_kgis stored now, charting deferred to afuture issue once there's more data logged.
next_due_date— notedas 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 #22The 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.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

2. Rabbit detail page — Health Records section

3. Add Health Record dialog

4. Inline edit row

Note the red "06 Sep 2026" in screenshot 2/4 — that's an overdue
next_due_daterendering correctly.
Bug found + fixed while capturing these
Actually rendering
/rabbits/{id}in a browser (rather than just theSQLite-router smoke test from the PR description) surfaced a real bug:
the route passed a context variable named
today(an ISO date string forthe date-input default), which shadowed the
todayJinja globalregistered in
templates_config.py(datetime.date.today, used by_health_row.htmlto flag overdue records). That collision broke the pagewith
TypeError: 'str' object is not callableany time a rabbit had atleast 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.