Sending Orders | Medplum
Sending Orders with Medplum-Health Gorilla Integration
This guide explains how laboratory orders work in the Medplum-Health Gorilla labs integration.
The high-level workflow for sending laboratory & imaging orders is:
- Present the provider with the appropriate lab order form (CPOE)
- Create the appropriate FHIR resources based on that information
- Execute the appropriate Medplum Bot to send the order to the performing lab.
Key Concepts
Laboratory ordering involves unique complexities: coordinating between providers, labs, and collection facilities; handling physical specimens; and gathering clinical data. Understanding these core concepts is essential before implementing the technical details.
The following concepts form the foundation of laboratory ordering:
| Concept | Description | Importance |
|---|---|---|
| Order | Request for one or more lab tests, uniquely identified and tracked. | Organizes tests, specimens and results. Maintains chain of custody and enables status tracking. |
| Test | Specific diagnostic procedure with defined requirements for specimen and data collection. | Determines specimen needs, required clinical data, and handling protocols. Impacts turnaround times and costs. |
| Specimen | Physical sample (blood, urine, etc.) for analysis. | Samples are only valid for a certain amount of time after collection. Accurately measuring sample collection time is critical for accurate processing. Samples can either be collected by the performing lab, or by the requesting provider. |
| Ask on Order Entry (AOE) | Required questions for some labs to gather additional clinical context for test processing. | Affects lab processing, result interpretation, and billing. Missing data can cause rejections or delays. |
| Order Splitting | Breaking a single order into multiple independent orders, typically when tests require different specimen types. | Allows labs to process specimens independently and maintain separate workflows for different test types. Prevents delays when specimens are collected at different times. |
Creating an Order Form in React
Medplum provides a "headless UX" approach through specialized React components for building laboratory order forms.
Core Components
The integration centers around two main components:
HealthGorillaLabOrderProvider: Context provider for state managementuseHealthGorillaLabOrder: Hook for accessing state and operations
State Management
The hook manages a comprehensive state object:
type HealthGorillaLabOrderState = {
performingLab: LabOrganization | undefined;
selectedTests: TestCoding[];
testMetadata: Record<string, TestMetadata>;
diagnoses: DiagnosisCodeableConcept[];
billingInformation: BillingInformation;
specimenCollectedDateTime: Date | undefined;
orderNotes: string | undefined;
};
Building an Order Form
You can find an open-source example order form here. Feel free to use this as a starting point.
- Wrap your application:
function OrderPage() {
return (
<HealthGorillaLabOrderProvider>
<OrderForm />
</HealthGorillaLabOrderProvider>
);
}
- Initialize with context:
function OrderForm() {
const [patient, setPatient] = useState<Patient>();
const [requester, setRequester] = useState<Practitioner>();
const labOrderReturn = useHealthGorillaLabOrder({
patient,
requester,
});
}
- Access operations:
const {
state,
searchAvailableTests,
setTests,
setDiagnoses,
updateBillingInformation,
setSpecimenCollectedDateTime,
setOrderNotes,
createOrderBundle,
} = labOrderReturn;
| Command | Purpose |
|---|---|
searchAvailableTests |
Uses the autocomplete bot to fetch available lab tests from Health Gorilla's API based on search string |
searchAvailableLabs |
Uses the autocomplete bot to fetch available diagnostic labs from Health Gorilla's API based on search string |
setTests |
Updates selected tests in order state |
setDiagnoses |
Updates ICD-10 diagnosis codes in order state |
updateBillingInformation |
Updates payment details (patient, insurance, customer account) |
setSpecimenCollectedDateTime |
Sets when specimens were/will be collected |
setOrderNotes |
Adds notes/instructions for the lab |
createOrderBundle |
Creates FHIR resources for the complete order |
setPerformingLab |
Sets which lab will process the tests |
setPerformingLabAccountNumber |
Overrides the practice-level lab account number for the order |
validateOrder |
Checks order for required fields and valid data |
Example
Lab Selection:
<MyAutoComplete
label="Performing Lab"
loadOptions={searchAvailableLabs}
onChange={(e) => {
setPerformingLab(e.value as Organization);
}}
/>
Test Selection:
<MyAutoComplete label="Selected tests" loadOptions={searchAvailableTests} onChange={setTests} />
Diagnosis Code Selection
<ValueSetAutocomplete
label="Diagnoses"
binding="http://hl7.org/fhir/sid/icd-10-cm"
name="diagnoses"
maxValues={10}
onChange={(items) => {
const codeableConcepts = items.map((item) => ({
coding: [item],
})) as DiagnosisCodeableConcept[];
setDiagnoses(codeableConcepts);
}}
/>
Billing Type:
<input
type="radio"
id="billToPatient"
name="billTo"
value="patient"
onChange={(e) => {
updateBillingInformation({ billTo: e.target.value });
}}
/>
<label htmlFor="billToPatient">Patient</label>
<input
type="radio"
id="billToInsurance"
name="billTo"
value="insurance"
onChange={(e) => {
updateBillingInformation({ billTo: e.target.value });
}}
/>
<label htmlFor="billToInsurance">Insurance</label>
Optional Insurance Coverage Selection (if billTo is insurance):
{
patient && state.billingInformation.billTo === 'insurance' && (
<select
name="coverage"
onChange={(e) => {
updateBillingInformation({
patientCoverage: { reference: `Coverage/${e.target.value}` },
});
}}
>
<option value="">Select Insurance Coverage</option>
{coverages.map((coverage) => (
<option key={coverage.id} value={coverage.id}>
{coverage.payor?.[0]?.display || 'Unknown Insurance'}
</option>
))}
</select>
);
}
Optional Specimen Collection Time (if drawing in-house):
<input
type="datetime-local"
onChange={(e) => {
setSpecimenCollectedDateTime(e.target.value ? new Date(e.target.value) : undefined);
}}
/>
Order Creation:
async function handleOrderCreation() {
try {
const { serviceRequest } = await createOrderBundle();
await sendLabOrderToHealthGorilla(medplum, serviceRequest);
} catch (err) {
if (err instanceof LabOrderValidationError) {
// Handle validation errors
}
}
}
Lab Account Numbers
Some labs (notably Labcorp) require account numbers to be included with each order. Account number requirements vary by lab—some require none, some require one, and some require both of the following:
Physician-level account number
Stored as an identifier on the Practitioner resource with type AN and an assigner reference pointing to the performing lab's Organization in Medplum. The send-to-health-gorilla bot automatically reads this identifier and includes it in the order—no form input required.
{
"resourceType": "Practitioner",
"identifier": [
{
"type": {
"coding": [{ "system": "http://terminology.hl7.org/CodeSystem/v2-0203", "code": "AN" }]
},
"value": "[PHYSICIAN_ACCOUNT_NUMBER]",
"assigner": { "reference": "Organization/[PERFORMING_LAB_ORGANIZATION_ID]" }
}
]
}
Practice-level account number
Stored as a nested extension on the Practitioner resource, keyed by lab. Each entry has a lab sub-extension (reference to the performing lab Organization) and a value sub-extension (the account number string). The bot automatically reads it and includes it in the order—no form input required.
{
"resourceType": "Practitioner",
"extension": [
{
"url": "https://medplum.com/integrations/health-gorilla/lab-org-account",
"extension": [
{ "url": "lab", "valueReference": { "reference": "Organization/[PERFORMING_LAB_ORGANIZATION_ID]" } },
{ "url": "value", "valueString": "[PRACTICE_ACCOUNT_NUMBER]" }
]
}
]
}
If a practitioner orders under multiple practice-level accounts with the same lab, you can override the account number at order time using setPerformingLabAccountNumber from the useHealthGorillaLabOrder hook:
setPerformingLabAccountNumber(selectedAccountNumber);
FHIR Data Model
Laboratory orders use a two-level hierarchy of FHIR resources, with a parent order containing multiple individual tests.
Order Structure
The parent order is represented by a ServiceRequest resource with the profile https://medplum.com/profiles/integrations/health-gorilla/StructureDefinition/MedplumHealthGorillaOrder. This order contains high-level information like:
- The ordering provider (
ServiceRequest.requester) - The performing laboratory (
ServiceRequest.performer) - Overall order status (
ServiceRequest.status) - Diagnosis codes (
ServiceRequest.reasonCode) - Minimum Patient demographic information (
ServiceRequest.subject) - Shared documentation (
ServiceRequest.supportingInfo)
Each individual test within the order is represented by its own ServiceRequest resource that:
- Links back to the parent order using
ServiceRequest.basedOn - Contains the specific test code from the performing lab's compendium
- Holds test-specific details, including Ask on Order Entry (AoE) questions.
Diagnosis codes
ICD-10 diagnosis codes belong on the parent lab order as ServiceRequest.reasonCode. Child test ServiceRequest resources do not carry diagnoses; they only hold the lab test code.
When using useHealthGorillaLabOrder, call setDiagnoses (or addDiagnosis / removeDiagnosis) with ICD-10 CodeableConcepts. createOrderBundle passes that state into createLabOrderBundle(), which writes it to the parent order's reasonCode.
If you build the order bundle yourself, set reasonCode on the parent order to ICD-10 CodeableConcepts before calling $health-gorilla-send. The send-to-health-gorilla bot reads reasonCode from the parent order and includes those codes on the outbound Health Gorilla RequestGroup.
Patient requirements
The order's ServiceRequest.subject must reference a Patient that satisfies the Medplum Health Gorilla Patient profile. Before transmitting an order, send-to-health-gorilla validates and syncs that patient to Health Gorilla; missing required demographics cause the send to fail.
| Requirement | FHIR path | Notes |
|---|---|---|
| Birth date | Patient.birthDate |
Required |
| Name | Patient.name |
At least one name with family and at least one given |
| Address | Patient.address |
At least one address with line, city, state, and postalCode. If country is omitted, the send bot defaults it to US |
| Medical record number (MRN) | Patient.identifier |
Exactly one identifier with type code MR |
| Phone | Patient.telecom |
At least one phone with use of home or mobile |
| Sex | Patient.gender or US Core Birth Sex |
Either gender, or the US Core Birth Sex extension |
Example of the required MRN identifier:
{
"type": {
"coding": [
{
"system": "http://terminology.hl7.org/CodeSystem/v2-0203",
"code": "MR"
}
]
},
"system": "https://example.com/mrn",
"value": "123456"
}
Use FHIR profile validation in your project so patients are complete at registration time, rather than discovering gaps only when an order is submitted.
Supporting Resources
Several additional FHIR resources provide important order details:
- Ask on Entry (AoE) Responses: Stored as
QuestionnaireResponseresources referenced by each test'sServiceRequest.supportingInfo. - Specimens: The
Specimenresource tracks specimen details, particularly important for recordingcollectionDatewith in-house collections. - Documents: Two types of
DocumentReferenceresources are linked throughServiceRequest.supportingInfo:- Requisition Form: The official order documentation with the lab's requisition number.
- Specimen Label: PDF with specimen labeling information that can be affixed to sample collection tubes.
Order Lifecycle
Orders progress through several states:
draft: Initial state from the order form. Order has not yet been sent to the Health Gorilla.active: Order has been transmitted to Health Gorilla and the performing lab.completed: Results have been received. Order processing is finished.on-hold: An error occurred during processing. Requires intervention to resolve.revoked: Order has been canceled.
Automation Bots
send-to-health-gorilla Bot
- Syncs patient and practitioner data with Health Gorilla.
- Transmits order with billing information and diagnosis codes.
split-order Bot
- Creates new parent orders for each group when splitting is required.
- Reassigns child test
ServiceRequestresources.
Example payload:
const params = {
resourceType: 'Parameters',
parameter: [
{ name: 'order', valueReference: { reference: 'ServiceRequest/example' } },
{ name: 'groups', valueString: '436|1877;9230;900323' },
],
};