Skip to content
AFM Studio
  • Pages

    AFM Studio
  • Work
  • Lab
  • About
  • CV
  • Contact
  • Projects

    AksaraA searchable library for Otorita IKN's knowledge, where the server checks every reader's access across three levels
  • Daily TaqwaTamper-resistant prayer attendance system for ~2,000 OIKN government employees at Masjid Negara IKN
  • Lantara v2A permit engine for Otorita IKN where administrators add new permit types from a screen, without a developer
  • eKiosk IKNTwo control rooms for Nusantara: kiosk content management and live bus tracking with arrival times that grade themselves
  • E-Monev IKNA monitoring platform for Nusantara's 23-year build, tracking every unit's quarterly progress against 24 national KPIs
  • JDIH OIKNOIKN's public legal document portal, with a controlled publishing workflow and AI summaries that never leave the server
  • Jejak PenanamanA check-in app for IKN tree-planting events that proves each participant was on site with a geofenced selfie
  • Dashboard SAKTIA nightly pipeline and dashboard that replaces OIKN's manual budget downloads for all six of its budget units
  • DoseRxA bedside dose calculator for 127 drugs that rounds each dose to tablets and syrups Indonesian pharmacies sell
  • CubiqA speedcubing trainer with competition-grade timing and a solver I wrote for each of the 8 major puzzles
  • ScimotionAn interactive science library of 86 articles, each explained next to animations the reader can control
  • WA Daily Scrum BotA WhatsApp bot that chases missing standup reports for a 14-person team and flags tasks stuck for days

↑ ↓ to move · Enter to open · Esc to close

DoseRx

A bedside dose calculator for 127 drugs that rounds each dose to tablets and syrups Indonesian pharmacies sell

Screenshots
drugs, each with dose range and caps
127
clinical groups organizing the drug catalog
9
smallest tablet fraction the app suggests
¼
On this page

The problem

A doctor works out a weight-based dose for every patient. Multiply the dose per kilogram by the weight, divide by the number of doses, convert to millilitres, and check against the maximum. For children, the dose depends on weight and a mistake can do harm. The usual tools are paper references, a phone calculator or a spreadsheet. None of them is quick to use with one hand at the bedside.

DoseRx is built for one practising doctor who works on a phone at the bedside and on a desktop. It needs no accounts and no server.

What I built

The maths lives in one place, apart from the screens

I kept the dose calculation separate from the interface. It takes typed inputs, checks them, applies the dose caps and returns either a result or an error. A screen has to check which one it got before it shows a number. So a failed calculation can never appear as a dose, and a broken value never reaches the doctor. The same engine serves every calculator and is tested on its own.

Doses snap to what an Indonesian pharmacy sells

After calculating a dose, the app suggests the tablet fraction or syrup volume to give, using preparations sold in Indonesian pharmacies. Tablets snap to fractions a person can cut: ¼, ½, ¾, 1, 1½ and 2. The app shows the dose actually delivered next to the target, so the doctor sees any rounding. The Puyer tab does the same for compounded powders. It calculates each drug in a batch and prints a combined recipe card with tablet counts and signa.

Every drug can carry more than one dose

The catalog first modelled one dose per drug. That broke once diazepam needed one dose by IV for a seizure and a different dose by mouth for anxiety, and once adult dosing sat next to a drug's paediatric numbers. I split "drug" from "dose": each drug now holds one or more regimens, one per route, indication and population, while every existing dose stayed exactly as it was, so past calculations still compute from the same numbers. The catalog has grown alongside this, from the original 8 seed drugs to 127, now organised into 9 clinical groups instead of the earlier 17 flat categories.

Unverified drugs wait behind a review gate

New drugs, routes and doses arrive faster than I can personally re-verify each one against a source. I built a separate review state instead. A new entry starts as a draft with its source cited and stays invisible outside review mode, marked as unverified everywhere it would otherwise show. Only I can move it into the reviewed catalog. As of the most recent commit, the draft list is empty, meaning the current drug list has been through that check.

The app began with four tabs: Preset, Custom, Puyer, Infus. That matched the four calculation engines, not how a doctor actually thinks at the bedside, closer to what an emergency needs than which calculator to open. I replaced the tabs with task-based destinations instead: Obat, Darurat, Puyer, Cairan and Riwayat, with one shared patient-weight bar carried across all of them.

Result

DoseRx is live on GitHub Pages and installs to a phone's home screen, working offline once loaded. Recent and starred drugs sit at the top of the picker, keeping the 127-drug catalog fast to search with one hand, and every calculation is saved in the browser with an optional note. The original spec had 8 drugs and three panels, built over about three and a half weeks in June and July 2026. I kept returning to it through September for a second pass that replaced the tab structure with task-based navigation, added the review workflow for unverified drugs, and grew the catalog to its current size. The app states plainly that it is a calculation aid, not clinical decision support.

Under the hoodTechnical detail for engineers
  • React 18 and TypeScript on Vite, with vite-plugin-pwa. Plain CSS. No UI, state or router library; routing is a small custom router rather than react-router.
  • src/lib/calculate.ts has no DOM or React code and returns CalcResult | CalcError. The UI branches on result.valid before reading any number, which removes the NaN and Infinity display bugs by construction. The same pattern covers calculateInfusion.ts, calculateElectrolyte.ts, calculateFluidRate.ts and calculateDextrose.ts. calculate.test.ts in Vitest covers normal doses, daily and single caps, missing concentration and invalid input.
  • calculateInfusion.ts supports mcg/kg/min, mcg/kg/hr, mg/kg/hr and unit/kg/hr, and outputs a pump rate in mL/hr plus macro and micro drip rates. estimateWeight.ts implements the APLS and Luscombe-Owens paediatric weight-from-age formulas, labelled as an estimate wherever it's shown.
  • suggest.ts snaps doses to a SOLID_FRACTIONS array.
  • catalog.ts layers CatalogDrug/Regimen on top of the existing preset and infusion data without rewriting it, so every regimen still holds the original object the tests were written against. The catalog spans 164 dosing regimens across its 127 drugs.
  • New drugs enter drafts.ts with status: 'draft' and a cited source; a clinician republishes one into a domain file with status: 'verified' and a reviewer name to make it live — a code change, not a data toggle.
  • The catalog lives in seven domain modules (analgesics, anti-infectives, antiparasitics, cardiorespiratory, emergency, gastro-allergy, steroids-nutrition) plus requested.ts (the ward-requested list) and drafts.ts, behind a barrel index.ts. The DrugPreset type separates calculation fields from display-only metadata.
  • Page metadata (title, description, OG/Twitter tags, the PWA manifest, sitemap) all generate from one site.ts/routes.ts data source at build time, after the manifest and page description had drifted out of sync once.
  • The build runs a typecheck, a client Vite build, an SSR Vite build of the entry point, then a Node prerender script that expands every route, including one per drug, into static HTML for GitHub Pages.
  • A per-dose / per-day entry toggle is saved and applied across the app. When a preset dose is edited within its valid range, an amber marker and reset button appear.
  • History goes through storage.ts, a typed wrapper and the only code that touches localStorage directly. Custom presets, recents and favourites are also saved locally, keyed by a stable drug id.
  • A dark-mode and accessibility pass added a medical-teal palette, documented as "Ward Day / Ward Night" design tokens.
  • 165 tests across 20 files cover the calculation engines, catalog integrity, search, route matching, pack-dose suggestion, dose-position banding, style tokens, and a smoke test that renders every drug page.
  • GitHub Actions deploys to GitHub Pages. About 15,400 lines of TypeScript, 134 commits between 20 June and 27 September 2026.

Screenshots

Beranda
Obat
Darurat
Racik Puyer
Cairan
Riwayat

Hiring, or building something similar?

I'm open to full-time roles and select freelance work.