first version
This commit is contained in:
@@ -0,0 +1,99 @@
|
||||
# Osiris
|
||||
|
||||
Tracking Osiris the cat: which medication he actually got, and his daily pulse.
|
||||
|
||||
Django 5/6 + django-ninja, SQLite. The Django admin is the web UI for now; the
|
||||
API is there so the Flutter app can be a second client without backend changes.
|
||||
|
||||
## Setup
|
||||
|
||||
```bash
|
||||
uv sync
|
||||
uv run python manage.py migrate
|
||||
uv run python manage.py createsuperuser # the only way to make an account: there is no registration
|
||||
uv run python manage.py runserver
|
||||
```
|
||||
|
||||
Then open <http://127.0.0.1:8000/> — it redirects to the Today page.
|
||||
|
||||
To reach it from your phone on the same network:
|
||||
|
||||
```bash
|
||||
DJANGO_CSRF_TRUSTED_ORIGINS=http://192.168.1.x:8000 uv run python manage.py runserver 0.0.0.0:8000
|
||||
```
|
||||
|
||||
## Using it
|
||||
|
||||
1. **Admin → Medications → Add**: create a medication and give it one row per
|
||||
daily dose. "1/4 each morning" is one row; "1/4 morning and 1/4 evening" is
|
||||
two rows on the same medication.
|
||||
2. **Today page** (`/admin/today/`, linked from every admin page): tick off the
|
||||
doses, type the pulse, hit Save. Use *Prev* to fill in a day you missed.
|
||||
3. The pulse graph on that page covers the last 90 days and shows the trend in
|
||||
bpm/day, so a slow drift is visible rather than just day-to-day noise.
|
||||
|
||||
Sessions last a year, so your phone stays logged in.
|
||||
|
||||
## Data model
|
||||
|
||||
- `Medication` — the drug, e.g. Vetmedin. Deactivate rather than delete when a
|
||||
treatment ends; the history stays intact.
|
||||
- `ScheduledDose` — one recurring dose: medication + time of day + amount.
|
||||
- `DoseLog` — one row per (scheduled dose, day). No row means "not recorded";
|
||||
`taken=False` means "explicitly not given", so an untouched day is
|
||||
distinguishable from a missed dose.
|
||||
- `PulseReading` — one per day, unique on the date.
|
||||
|
||||
## API
|
||||
|
||||
Browsable docs at `/api/docs`, OpenAPI schema at `/api/openapi.json` (feed that
|
||||
to a Dart client generator).
|
||||
|
||||
Session cookies authenticate the admin; the Flutter app uses a bearer token:
|
||||
|
||||
```bash
|
||||
curl -X POST localhost:8000/api/auth/login \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"username": "you", "password": "...", "device_name": "pixel"}'
|
||||
# => {"token": "...", "username": "you"}
|
||||
```
|
||||
|
||||
Send it as `Authorization: Bearer <token>` on every other call. Tokens do not
|
||||
expire; revoke them in Admin → Api tokens.
|
||||
|
||||
| Method | Path | Purpose |
|
||||
| -------- | -------------------------- | ------------------------------------------------ |
|
||||
| `POST` | `/api/auth/login` | username + password → token |
|
||||
| `POST` | `/api/auth/logout` | revoke the token used for the call |
|
||||
| `GET` | `/api/auth/me` | current user |
|
||||
| `GET` | `/api/tracker/medications` | the plan: medications and their scheduled doses |
|
||||
| `GET` | `/api/tracker/day` | `?date=` (default today): checklist + pulse |
|
||||
| `POST` | `/api/tracker/doses` | tick a dose on/off; returns the updated day |
|
||||
| `GET` | `/api/tracker/pulse` | `?days=90`, oldest first — the graph data |
|
||||
| `POST` | `/api/tracker/pulse` | record the pulse; posting twice a day overwrites |
|
||||
| `GET` | `/api/tracker/pulse/stats` | average, range, and `trend_bpm_per_day` |
|
||||
| `DELETE` | `/api/tracker/pulse/{date}`| remove a reading |
|
||||
|
||||
`GET /api/tracker/day` is the one the app's home screen needs:
|
||||
|
||||
```json
|
||||
{
|
||||
"date": "2026-07-14",
|
||||
"doses": [
|
||||
{"id": 2, "medication_name": "Fortekor", "amount": "1/4",
|
||||
"time_of_day": 1, "time_of_day_label": "Morning", "taken": true, "notes": ""},
|
||||
{"id": 3, "medication_name": "Fortekor", "amount": "1/4",
|
||||
"time_of_day": 3, "time_of_day_label": "Evening", "taken": null, "notes": ""}
|
||||
],
|
||||
"pulse": {"date": "2026-07-14", "bpm": 188, "notes": ""}
|
||||
}
|
||||
```
|
||||
|
||||
`taken: null` means not recorded yet, as opposed to `false` for a missed dose.
|
||||
|
||||
## Tests
|
||||
|
||||
```bash
|
||||
uv run python manage.py test
|
||||
uv run ruff check . && uv run ruff format .
|
||||
```
|
||||
Reference in New Issue
Block a user