Journal
InfrastructureLessons

The Architectural Reset: From Power Apps Back to a Custom Build

OpenEMR was already ruled out. Power Apps looked like the answer the board endorsed on June 4. Two weeks of hands-on building found the limit: real offline-first data collection needed a system built for it from the start.

After OpenEMR

OpenEMR was already off the table going into June. The LBF work had made that clear: a generic EMR's form builder and interface were not going to hold up under field conditions, no matter how carefully the forms inside it were built. The open question leaving the leadership meeting was not whether to leave OpenEMR. It was what would replace the volunteer-facing form layer.

Power Apps Looked Like the Answer

The recommendation presented to the board on June 4 was Microsoft Power Apps. The case was strong on paper: everything needed to collect data, store it, and analyze it was already inside the Microsoft 365 licenses MMDM holds. Power Apps on a tablet could give each station exactly the fields it needed. SharePoint would store every submission automatically. Power BI could turn same-day mission data into a same-day report. No server to maintain, no form-builder UI fighting the form itself. Leadership endorsed pursuing it as the next step.

Where It Didn't Hold Up

Building continued after the meeting. A real Power Apps form, not a demo, surfaced the constraint that mattered most: genuine offline-first, real-time data collection is difficult to do well in Power Apps. A mission station needs to capture a patient's vitals the moment they're taken, with no internet, on hardware that may lose power, and have that data still be there — correct and complete — when connectivity returns hours or days later. Power Apps can run offline. Making it run offline *reliably*, at the level a clinical record needs, was going to mean working around the platform rather than with it.

That is the wrong place to spend effort. A system that needs to be offline-first should be built offline-first, not adapted to it after the fact.

The Decision

The replacement is a custom Node.js application with a SQLite database, built and maintained directly. That tradeoff is available specifically because the work can be done in-house: bugs get diagnosed and fixed directly, the form set can match exactly what each station lead asks for, and the offline behavior is the foundation, not a patch.

Microsoft 365 is not leaving the picture. SharePoint remains the archive destination for mission data after each deployment, under the same BAA discussed at the leadership meeting — the compliance groundwork from June 4 still applies. What changed is the point-of-care interface, not where the data ultimately lives.

Architecture Wrong for the Mission Environment

OpenEMR is built for permanent clinics: reliable internet, stable hardware, trained administrative staff. A mission is the opposite of all four — intermittent power, no internet, non-technical volunteers, and a network that exists for one week before it's torn down. Running OpenEMR meant a VirtualBox VM bridged onto mission WiFi, which added its own instability, and per-device certificate trust that added setup friction before a single form was ever opened.

Form Builder Too Slow for Iteration

OpenEMR's Layout-Based Form editor is click-through-the-web-UI, one field at a time. Building five forms took months. Any change to a single field repeats the same process. A system that needs to evolve between missions based on what a clinical lead asks for cannot live at that pace.

UX Wrong for Tablets and Volunteers

OpenEMR's interface was designed for desktop use by trained medical office staff. On a tablet in landscape orientation, operated by a volunteer on their first day, navigation was difficult and errors were easy to make without noticing.

Offline Was an Afterthought

OpenEMR assumes connectivity. Real offline operation required workarounds layered on top of that assumption, and those workarounds added fragility rather than reliability — the same lesson the Power Apps test surfaced again from a different angle.

What Replaced It

The new system is a single Node.js process running on a dedicated Linux machine — currently Kubuntu, moving to a dedicated mini PC for future missions. The database is SQLite: one file. The frontend is React, built once and served as static files by that same process. Tablets join the clinic LAN and reach the app by IP in a browser. No internet connection, no cloud dependency, no virtual machine, no second machine to keep running.

Technical Differences

  • One machine, no virtualization — native Linux, no Windows host, no VirtualBox layer, no bridged-WiFi instability.
  • SQLite instead of MariaDB — a single file, no database server to configure or keep running, backups are file copies.
  • Built for offline from the start — every dependency installed before the mission, no internet required during operation, hourly backups using SQLite's online backup API that are safe during active writes.
  • systemd auto-start — no manual steps after power-on, restart-on-crash, and reboot-tested: power off, power on, app started, tablets connected, data intact, backups resumed, no keyboard required.
  • Forms co-designed with clinical leads — Dr. Richard Byrd (Medical), Janice Holley (Vision), Cathy Short (Dental), Gloria Tobin (Triage), Suzanne Condron (Pediatrics) — mirroring the paper forms so the transition for providers is recognition, not relearning.
  • Clinical workflow, not just forms — Registration feeds Triage, Triage routes patients, live station counts are visible, and station leads can set their own station to open, at capacity, or closed so Triage routes accordingly instead of sending a patient to wait for care that won't arrive.
  • Role-based access matched to staffing — an admin account for the tech lead, individual accounts for station leads, a shared PIN for volunteers. A tablet's station is its identity; a lead's account is their individual accountability.
  • Auto-lock on inactivity — a configurable timeout, ten minutes by default, with the in-progress form preserved exactly as it was left.

What the System Covers

Registration handles patient lookup by surname or MMDM patient number, prints a slip with the MMDM number, and captures date of birth with auto-formatting and validation.

Triage captures a full vitals panel — blood pressure, pulse, temperature, weight, height, BMI, SpO2, respiratory rate, glucose, and last menstrual period where applicable — along with the chief complaint in the patient's own words, and the routing decision that sends them downstream. Abnormal values are auto-highlighted against a single configurable threshold.

Medical uses a SOAP note structure, a three-state review-of-systems toggle (not assessed / negative / positive), a 96-drug Spanish-language formulary with Costa Rica drug names preserved, and a diagnosis field. Pediatric patients are auto-flagged by age and routed to Medical.

Vision covers the service tracker, Snellen acuity without and with correction (near and far), autorefractor capture using the minus-cylinder convention with axis validation, pinhole acuity, anterior and posterior segment findings, IOP, diagnosis, and an append-only dispensing log for readers, sunglasses, and drops. Inventory consumed is computed from that log rather than tracked with a mutable counter.

Dental captures medical history and pre-medication screening, an anesthesia selector, a Universal Numbering System tooth chart covering both adult (1-32) and primary (A-T) dentition, per-tooth procedure logging, dental prescriptions from the same formulary, hygiene notes, and a derived medication-flag panel that surfaces a patient's relevant medications without re-collecting them.

Reporting shows patients by station, both for the current day and the mission total, and vision inventory consumed — readers by strength, sunglasses by type, drops by type — all computed live from the database with no cached numbers.

Patient management covers demographic corrections, adding or removing services for a patient, and guarded deletion that cascades through dependent records.

Mission Day Flow

A patient walks in and is registered in under a minute, leaving with a printed slip carrying their MMDM patient number. At Triage, vitals are taken, the chief complaint is recorded, and the routing decision is made by the clinical person equipped to make it — downstream queues update in real time, and station leads see their own queue. If Dental fills up for the day, the lead marks the station closed, Triage sees that immediately, and the next patient who might have gone to Dental is redirected instead of left waiting for care that isn't coming. At the end of the day, the reporting screen shows where things stand. At the end of the mission, the database exports to SharePoint for archive and year-over-year analysis.

Old vs. New

  • VirtualBox bridged WiFi (unstable) → native Linux, direct network binding
  • MariaDB server, a separate service to keep running → SQLite, a single file
  • Manual form building, click-through-the-web-UI per field → code-defined forms matching paper, field for field
  • Desktop-designed interface → touch-optimized for tablets in landscape
  • No genuine offline mode → fully offline by design
  • Manual startup after every reboot → systemd auto-start, no manual steps
  • Per-device certificate warnings before a form could even open → consistent LAN access, no certificate friction
  • A generic EMR adapted for any clinic → a system purpose-built for MMDM's actual workflow

Simpler in every dimension that matters for reliability. More precise in every dimension that matters clinically. And the data still lands in the same Microsoft 365 archive the BAA already covers.