An open-source hospital transfer center portal built on the Medplum platform.
The Medplum Transfer Center is a hospital transfer center demo app. It provides a portal for managing patient transfers between facilities, including a transfer center dashboard, patient intake, and physician onboarding. It is meant for developers to clone, customize, and run.
- Transfer center dashboard for managing incoming patient transfers
- Patient intake workflow via FHIR Questionnaires
- Physician onboarding for accepting transfer requests
- Hospital location management (buildings, wards, rooms)
- Real-time HL7 ADT message processing via Medplum Agent
- Automated bed assignment workflows
| Directory / File | Purpose |
|---|---|
src/ |
React application source code (pages, components, hooks, utils) |
src/bots/ |
Medplum bots for automating FHIR resource creation and HL7 parsing |
data/core/ |
Project fixture — FHIR bundle data (CodeSystems, ValueSets, Questionnaires) that must be uploaded to Medplum in order to properly run the app |
scripts/ |
Utility scripts for deploying bots and sending ADT messages |
data/core/agent-data.json |
Medplum Agent and Endpoint resource definitions |
The Location FHIR resource is used to model the hierarchy of locations within the data model. We have three levels of nesting in the current model:
- Building
- Level (or ward)
- Room
Each Location has a type (eg. building, level, room) and can be "part of" another Location resource. We use this partOf field to represent that a lower-level location is located within the higher-level location that it is "part of".
For example, the room ACUTE 212 is "part of" the ACUTE level, which is in turn "part of" the hospital building.
This is how that looks hierarchically from the perspective of the FHIR model:
- ACUTE [Location.type=level, Location.partOf=Hospital]
- ACUTE 212
- Location.type=room
- Location.partOf=ACUTE
- Location.alias=212
- ACUTE 213
- Location.type=room
- Location.partOf=ACUTE
- Location.alias=213
- ...other rooms in ACUTE
- ACUTE 212
- 3SURG [Location.type=level, Location.partOf=Hospital]
- 3SURG 307
- Location.type=room
- Location.partOf=3SURG
- Location.alias=307
- 3SURG 308
- Location.type=room
- Location.partOf=3SURG
- Location.alias=308
- ...other rooms in 3SURG
- 3SURG 307
- ...other wards of the hospital
This model allows us to use FHIR search semantics to search for rooms which are "part of" the ACUTE or 3SURG ward, or query for all levels that are of "part of" the Hospital hospital building.
Note that along with each location, we also denote an "alias" which is just the room number. This allows us to search for just the room number more directly while still displaying the full Location.name (eg. 3SURG 307) by default for the user when facilitating things like user type-aheads in inputs or displaying locations in a table cell.
First, fork and clone the repo.
Next, install the app from your terminal:
npm installCopy the example environment file and fill in your Medplum project credentials:
cp .env.example .envWeb app (VITE_ prefix — bundled into the frontend at build time):
| Variable | Description |
|---|---|
VITE_MEDPLUM_PROJECT_ID |
Medplum project ID |
VITE_MEDPLUM_GOOGLE_CLIENT_ID |
Google OAuth client ID for login (optional) |
Bot scripts (used by npm run bots:build / bots:deploy):
| Variable | Description |
|---|---|
MEDPLUM_CLIENT_ID |
Medplum client ID for bot scripts |
MEDPLUM_CLIENT_SECRET |
Medplum client secret for bot scripts |
CI/CD (used during automated deployment):
| Variable | Description |
|---|---|
DEPLOY_MEDPLUM_CLIENT_ID |
Medplum client ID for CI/CD deployment |
DEPLOY_MEDPLUM_CLIENT_SECRET |
Medplum client secret for CI/CD deployment |
Then, run the app:
npm run devThis will host the Vite development server locally, which by default should be hosted on http://localhost:3000/
To build the app, run:
npm run buildTo upsert the core data into the Medplum server, run:
npx medplum post '' "$(cat path/to/bundle.json)"
# Example: npx medplum post '' "$(cat data/core/core-data.json)"The core data for the Transfer Center is stored in the data/core directory. This data is used to populate the Medplum server with the necessary resources for the portal to function. The core data includes the following resources:
| Resource Type | Name |
|---|---|
| CodeSystem | call-dispositions |
| ValueSet | accepting-specialties |
| ValueSet | call-dispositions |
| ValueSet | starting-locations |
| ValueSet | time-sensitive-diagnosis |
| ValueSet | transferring-origins |
| Questionnaire | accepting-physician-intake-questionnaire |
| Questionnaire | create-location-lvl-questionnaire |
| Questionnaire | create-location-ro-questionnaire |
| Questionnaire | patient-bed-assignment-questionnaire |
| Questionnaire | patient-intake-questionnaire |
| Questionnaire | physician-onboarding-questionnaire |
Bots are server-side TypeScript functions that run on the Medplum platform. Each user-facing workflow is driven by a FHIR Questionnaire — when a user submits a form, Medplum fires a FHIR Subscription that invokes the corresponding bot. The ADT processing bot is the exception: it is triggered directly by the Medplum Agent when an HL7 v2 ADT message arrives over MLLP.
| Bot | Trigger |
|---|---|
patient-intake-bot |
Patient transfer form submitted |
accepting-physician-intake-bot |
Accepting physician form submitted |
patient-bed-assignment-bot |
Bed assignment form submitted |
physician-onboarding-bot |
Physician onboarding form submitted |
location-lvl-bot |
Create ward form submitted |
location-room-bot |
Create room form submitted |
adt-processing-bot |
HL7 ADT message received via Medplum Agent |
To build and deploy the bots:
npm run bots:build
npm run bots:deployAfter deploying the bots, you need to create the Medplum Agent and Endpoint resources to enable HL7 message processing:
-
Update
data/core/agent-data.jsonwith your ADT processing bot ID, replacing${YOUR_ADT_PROCESSING_BOT_ID}with the actual Bot ID. -
Upload the agent configuration:
npx medplum post '' "$(cat data/core/agent-data.json)"This creates an Endpoint (HL7 v2 MLLP on port 56000) and an Agent that routes incoming HL7 messages to the ADT processing bot.
The Medplum Agent is required to receive HL7 messages. To run the agent locally, follow the instructions in the Medplum Agent documentation.
Once the agent is running, you can send ADT messages using the provided script to test the sample application:
npm run send-adt <MESSAGE_TYPE> <ROOM_NUMBER>Available message types:
A01- Patient admissionA03- Patient discharge
Example:
npm run send-adt A01 201 # Admit patient to room 201
npm run send-adt A03 201 # Discharge patient from room 201By default, your locally running Transfer Center app points to the hosted Medplum service. To use your own organization's Medplum project, register a new Project on Medplum and configure your environment variables accordingly (see config.ts).
If you are using the Medplum Hosted service, you can log in to your Medplum instance and add the following identifiers to your Project Site Settings:
- Google Client Id
- Google Client Secret
- Recaptcha Site Key
- Recaptcha Secret Key
Contact the Medplum team (support@medplum.com or Discord) with any questions.
Medplum is an open-source, API-first EHR.
Medplum supports self-hosting and provides a hosted service.
- Read our documentation
- Browse our React component library
- Join our Discord