Skip to content
AFM Studio
Personal ProjectHealthcareWeb App

DoseRx

Weight-based medication dose calculator covering 92 drugs across four calculator modes, built for bedside clinical use as an offline PWA

All projects4 min read

Role

Developer

Timeframe

Jun – Jul 2026

View live
On this page

The problem

Calculating weight-based medication doses is repetitive, error-prone arithmetic that a doctor redoes for every patient: multiply mg/kg by weight, divide by frequency, convert to mL, check against maximum limits. For pediatric patients, dosing is weight-sensitive and errors are dangerous. The existing tools — institutional paper references, generic phone calculators, spreadsheets — are slow and not built for one-handed mobile use at the bedside.

The primary user is a single doctor working solo on both phone (bedside) and desktop. No multi-user, no auth, no backend required.

The approach

A pure calculation engine as the foundation

The central decision was to keep src/lib/calculate.ts completely pure — no DOM, no React, no side effects. It takes typed inputs, validates them, applies dose caps, and returns a discriminated union: CalcResult | CalcError. UI code is forced to branch on result.valid before touching any numeric field, which structurally eliminates the NaN/Infinity display class of bugs rather than guarding against it with scattered runtime checks. The engine is independently unit-testable and reused across every calculator tab without coupling.

A catalog that outgrew a single file

The drug library grew from 8 seed drugs in the original spec to 92 drugs across 16 therapeutic categories — large enough that the single drugs.ts file was split into seven domain modules (analgesics, anti-infectives, antiparasitics, cardiorespiratory, emergency, gastro-allergy, steroids-nutrition) behind a barrel index.ts. The DrugPreset type separates calculation fields, consumed by calculate.ts, from display-only catalog metadata — dosing source, search aliases, indications, side effects, contraindications, minimum age/weight, and pack sizes — so enriching the catalog for the UI can never accidentally touch the calculation path. A collapsible "Detail Obat" panel surfaces that metadata per drug as a calm reference layer that stays out of the way until tapped.

Indonesian clinical context baked in, not bolted on

After calculating a per-dose amount, the app suggests specific tablet fractions or liquid volumes available in Indonesian pharmacies, snapping to fractions actually practical in local clinical practice (¼, ½, ¾, 1, 1½, 2) rather than a raw decimal. Compounded-powder dosing (Puyer) needed the same discipline at a harder level: sachets require rounding to tablet fractions that are physically cuttable, and a naive round-to-nearest-integer would produce wrong or unphysical quantities. A dedicated suggest.ts module snaps to a predefined SOLID_FRACTIONS array and shows the actual delivered dose next to the target, so the clinician can see any rounding delta rather than trusting a hidden approximation. The Puyer engine runs calculate() per drug across a batch and outputs a combined, printable recipe card with tablet-fraction arithmetic and signa.

A second pure engine for a different problem domain

The IV infusion calculator (calculateInfusion.ts) is a separate pure function for drip rates in mL/hr and drops/min, supporting mcg/kg/min, mcg/kg/hr, mg/kg/hr, and unit/kg/hr dose units. It shares only the input validation pattern with the main engine — the underlying math doesn't overlap with weight-based oral/IV dosing at all.

Letting the doctor override a preset without losing the guardrail

The original plan assumed preset dose values would be used as-is, but in practice a doctor sometimes needs to adjust the dose/kg within a clinically valid range. An amber indicator plus a reset button now appears when a preset's dose is modified — and a follow-up fix was needed after the override wasn't actually being passed into the calculation correctly on the first attempt, evidence this interaction was less trivial than it looked.

Offline PWA with a fast, personal catalog

Deployed as a static site via GitHub Actions to GitHub Pages with PWA support — installable to a phone home screen and fully usable offline once loaded. Recently-used and starred drugs surface at the top of the picker, keyed by a stable drug id and persisted locally, so a 92-drug catalog stays fast to navigate one-handed even as it grows. Calculation history persists across sessions through a typed storage.ts wrapper around localStorage — the only place in the codebase that touches it directly — with an editable free-text note per entry, tucked into a header drawer rather than crowding the calculators themselves.

Outcome

39 commits over roughly three and a half weeks of iteration on top of an original PRD that specified 8 seed drugs and three panels. The shipped app has four calculator modes (Preset, Kustom, Puyer, Infus) plus a history drawer, a 92-drug catalog across 16 categories split into 7 typed modules, a weight-from-age estimator using APLS and Luscombe-Owens pediatric formulas, and a dark-mode pass with a dedicated medical-teal palette. Deployed and live on GitHub Pages via automated CI. The safety disclaimer required by the original product spec — this is a calculation aid, not a clinical decision-support system — stays visible in the app, never buried in a tooltip.

Outcomes

Drugs across 16 categories
92
Dose calculators + history
4
Lines of TypeScript
~5K
Commits over ~3.5 weeks
39

Screenshots

Preset
Preset Detail
Preset Result
Custom
Puyer
Puyer Result

Have a project like this?

If you need a system built with the same care — clear scope, solid execution — let's talk.

Start a project