MaksForge

Binky

Android app

An Android app for keeping track of a bunny's health.

Live

Due doses are derived, not stored

A medication course carries a start date, an end date, and an optional daily schedule of clock times. The doses due today are computed from that schedule on read; only what actually happened — given or deliberately skipped — is written to the database. Storing future dose rows would mean rewriting or deleting them every time a course is shortened, paused, or rescheduled, and orphaned future doses are a reliable source of wrong reminders.

A dose can also be recorded with no schedule at all, since scheduling is optional per course.

Dose amount is free text; a course’s end date is optional

The dose amount a course prescribes is a free-text field (“0.3 ml”, “¼ tablet”, “2 drops”, “0.2 mg/kg”). Rabbit prescriptions are too heterogeneous for a value + unit enum that would never be complete, and the app never sums, converts or reasons over amounts — so structure would be false precision. This is the mirror image of symptoms (ADR-0010): symptoms are structured because they are counted; dose amounts are free text because they never are. A recorded dose stays minimal — given or skipped, a timestamp, an optional note (“only got half in”) — and does not re-specify the amount.

A course’s end date is nullable, meaning an ongoing course the owner closes when treatment actually stops. Forcing an end date would make owners invent a far-future one, which then derives phantom due doses. An open course simply keeps deriving today’s doses (reminders switchable off, ADR-0003), and “shortening a course drops its future due doses” still holds — closing an open course is setting its end to today.

Derivation looks forward, never back

Deriving is equally cheap in both directions, and that is the trap. A course’s times live in one row each, so changing one changes what every day derives — including days already answered. A fortnight’s 08:00 course moved to 20:00 on day ten re-derives the nine days behind it at a time no dose was ever recorded against: an owner who did everything right is shown nine unanswered slots, plus nine recorded doses matching no slot at all. The edit rewrote history it never touched.

So the derived window is clamped to [max(start, today), end ?: ∞]. Today and forward is derived; the past is what was recorded. A past day lists its dose rows — given, skipped, ad hoc — and never a slot that nothing answered. Giving each time an effectiveFrom would keep the past derivable, and is the wrong trade for one owner and one rabbit: every schedule edit would then have to ask whether it is a correction or a change, and get it right.

A recorded dose is keyed by its slot’s local date and time, never by an instant. Slots resolve wall-clock in the device’s current zone (ADR-0003), so the same 08:00 dose is a different instant in Warsaw and in London; an instant-keyed row would stop matching its own slot the moment the owner travels, and a dose already given would read as unanswered and re-arm its alarm. scheduledOn and scheduledTime are null together for an ad-hoc dose, and SQLite’s NULLs-are-distinct rule leaves those unconstrained under UNIQUE(courseId, scheduledOn, scheduledTime) while still answering each real slot exactly once. An instant is computed only to place an alarm, and never stored.

Consequences

Any “did I miss a dose?” question is answered by comparing the derived schedule against recorded doses for today, not by looking for rows that are absent — and not at all for days already gone. That is ADR-0001’s sentence one domain over: silence means nobody looked, so the app does not draw it as a gap.

A slot stops existing at local midnight. A dose given at 00:30 for the previous evening is recorded ad hoc and back-dated, in the app, deliberately — which is also why a dose notification is given setTimeoutAfter its own day rather than sitting in the shade holding a one-tap answer the app would no longer accept (ADR-0025).

Hi, I'm Maksymilian.

MaksForge is where I build the small tools I wish existed. Each one solves a single problem.