ProductionHealthcareFull-stack web appManaged Postgres9 min read

TB Module

Digital replacement of paper and Excel-based tuberculosis documentation at a primary care clinic.

Quick read

Tuberculosis risk-group registries, fluorography records, and case logs at Ukrainian primary care clinics are kept in Excel and on paper. In one of my clinics this meant 28 risk-group sheets, a fluorography file covering ~1,100 patients, and a paper WHO 4-symptom screening questionnaire. I designed and built a web module that replaces this paper workflow: a structured patient registry with risk groups and TB status, fluorography history with auto-scheduled next-due dates, sputum test tracking, auto-generated referrals, and a sync mechanism with the EMR. Adopted at the clinic where I practice. Built with a free-tier-friendly stack and a clear path to production-grade infrastructure when scaled.

Context

Tuberculosis surveillance at the primary care level in Ukraine requires layered documentation: risk-group registries (medical risk: COPD, HIV, diabetes, etc; social risk: prisoners, migrants, contacts of detected cases), fluorography history per patient with annual scheduling, sputum test results from regional labs (GeneXpert / microscopy / culture / PCR), separate logs for detected and contact cases, and a paper-based WHO 4-symptom screening questionnaire (form Appendix 9) administered at each contact.

In my clinic this stack was:

  • 14 risk-group Excel sheets × 2 clinic locations = 28 sheets
  • 2 detected/contact registries × 2 locations = 4 sheets
  • 1 fluorography file with ~1,100 patients per location
  • ~5 xlsx files total, plus a folder of MoH order PDFs
  • Paper Appendix 9 questionnaires, stored in a binder

Updating risk groups required cross-referencing several files. Generating referrals required hand-writing the WHO 4-symptom form. There was no audit trail. Migration risk on disk was real — these were business-critical files held in Excel.

Solution

A web module that replaces the above workflow with structured records:

  • Patient registry with medical and social risk groups, TB status (none / detected / contact), location, and contact info — imported from EMR declarations via xlsx-diff
  • Fluorography history with auto-scheduled next-due dates and conclusion text storage (e.g., "Висновок: норма")
  • Sputum test tracking (GeneXpert / microscopy / culture / PCR), with the result types regional labs report
  • WHO 4-symptom screening at point-of-care: digital questionnaire, ML-free decision (low_risk / needs_xray / needs_referral), auto-generated PDF referral via window.print()
  • MoH order library linking to source documents
  • EMR data import — operator uploads the EMR declarations export (xlsx), the module computes the diff against current registry, operator confirms
  • EMR integration — a companion script extends the clinical extension to push relevant clinical observations (diagnoses, fluorography results) from the patient view into the module via an authenticated endpoint

Designed for single-practice use; access controlled by a shared practice authentication mechanism. Data handling follows internal clinical workflows established with the clinic management, with sensitive patient information accessed only by authorized clinical staff within the scope of their duties.

TB Module patient registry with risk groups and TB status
Patient registry view with risk groups and TB status.

Impact

The TB Module is too new for adoption metrics — the integrated flow (extension auto-syncing into module) shipped recently. The TLDR-level claim is what the system did at deploy: eliminated the paper journal contour and collapsed 28+ Excel sheets into a single, queryable structure.

A separate observation worth naming: the architectural choices (managed Postgres, serverless functions, transactional email) were deliberately optimized for a small clinical deployment, where infrastructure overhead matters more than maximum theoretical scale. Path to production-grade compliance infrastructure (dedicated tenancy, BAA-eligible providers, formal DPA) is clear when the system scales beyond a single practice.

Adoption metrics will follow as the system accumulates more usage data.

Replacing paper with software is mostly a schema design problem.

Tech stack & architecture

Frontend

React 18 · Vite · TypeScript · TailwindCSS v4

Backend

PostgreSQL (managed) · PostgREST · Object Storage

Hosting

Serverless functions on managed cloud platform

Auth

Practice-wide PIN → bcrypt → JWT in HttpOnly cookie

Data exchange

SheetJS for client-side Excel I/O · Secure sync with EMR via authorized extension

Data

TanStack Query · TanStack Table

The shared-PIN auth is a deliberate simplification for the current single-practice scope: two clinical staff, no patient-facing access. Per-user accounts and audit granularity are part of the production-grade roadmap (see Next steps).

EMR integration. A companion script (~921 LOC) extends the clinical extension to push relevant observations (diagnoses, fluorography results) from the patient view into the TB Module via an authenticated endpoint. Communication uses a shared practice authentication token. Patient identification on the EMR side is mapped to local registry records, with all access scoped to the clinical staff using the system.

What I learned

  • Replacing paper with software is mostly a schema design problem. The hardest part wasn't the UI or the database setup — it was deciding what to model. "Risk group" turns out to be a relationship, not an attribute. "Detected" and "contact" are statuses, not separate registries. Getting the data model right first meant the UI fell out cleanly.
  • Infrastructure economics for a small primary care clinic are forgiving. A practice at the scale of one outpatient clinic fits comfortably within entry-tier allocations of standard managed cloud services. For a clinic considering digitization, this matters more than any feature comparison — the budget barrier to entry is often the actual blocker.
  • Extending an existing tool is a legitimate integration strategy. I could have built a separate sync API and rewritten the data flow. Instead I extended the existing extension's display callback to feed the module passively. Result: ~900 LOC of glue, no changes to the core extension logic, automatic sync of any patient record the doctor opens.
  • A blocking overlay turned out to be the right UX answer for an indicator workflow doctors otherwise skipped. The clinical extension originally required a button click to run the indicator analysis — 15-20 seconds of compute, but worse, a friction pause that broke flow. Doctors quietly stopped using it. When I integrated with the TB Module and made it mandatory to check the last X-ray date, I added a blocking overlay on the patient card: it appears as soon as the card opens, runs the indicator analysis automatically, syncs the X-ray date to the module, then drops away. Counter-intuitively this was better UX than the optional button: removing the choice removed the skip. The lesson generalizes — in clinical workflows, "make it easy to use" is often weaker than "make it the default path."

Next steps

The active priority is extending the same pattern to the other journals that primary care has to maintain: vaccination, HIV, cardiovascular risk, oncology screening. The TB module already proves the architecture works — patient registry + scheduled events + EMR sync is a generalizable shape. Each new module needs its own schema and rules, but the underlying frame transfers.

Equally important is the path to production-grade infrastructure for multi-practice deployment: dedicated tenancy, formal data processing agreements, audit logging, role-based access controls. The current architecture is intentionally a single-clinic MVP; scaling beyond requires compliance investments that are well-understood but not yet implemented.

Read next