API Support for Order Entry (Design Page)

This work was completed and released as Platform 1.10.0

 

Background

Order entry is an important part of an electronic medical record system.  To date, OpenMRS has provided minimal support for orders within the database, since our initial focus was on around data (observation) gathering and reporting.  As OpenMRS implementations have grown, more and more have found the need for support of orders within the system.  Our goal is to create enterprise-quality support for order-entry within OpenMRS.  We begin with the underlying API (programmers interface) and with a focus on building a foundation upon which modules can build/innovate/discover various ordering paradigms.

Scope

Orders are requests made by providers for actions to be carried out, including scripts to be given to the patient, tests to be run on the patient, procedures to be performed on the patient, doctors the patient should go see, requests to discontinue something previously ordered, etc.  While it's helpful for the system to know when/if these requests have been carried out, it is not the job of the EMR's order service to handle all of the actions needs to fulfill the request.  In fact, in many cases the actions taken to fulfill these requests (orders) will be managed by applications external to the EMR (e.g., prescriptions dispensed by a pharmacy system interacting with an inventory system, labs performed within a laboratory system to fulfill the requested tests, etc.).  The primary job of the orders service is to manage the metadata needed to help generate the orders, record the orders, and maintain the list of active orders.

Our goal is to provide an order entry service capable of supporting simple outpatient orders, focusing initially on medication orders and tests but allowing for other types (referrals, nursing, etc.).  While starting with outpatient orders, we want a design that will be able to gracefully grow into support for inpatient orders as well as supporting concurrent inpatient and outpatient orders (i.e., support for orders stored on a single server for the same patient across more than one care settings).

We are taking a pragmatic approach, realizing that our design will not cover every possible attribute or need, but will cover at least 80% of the use cases & needs within resource-constrained environments, focusing on those items that implementations need most now.  These include:

  • Ability to define which concepts are orderable (what items in the dictionary represent potential orders)
  • Ability to pre-define sets of orders (order templates) – e.g., to support ordering of pre-defined regimens.
  • Support basic medication orders and simple test orders.
  • Allow for modules to introduce new types of orders.
  • Answer the question: what are the active orders for this patient?

Glossary

  • active orders – all orders for a patient that have been signed/activated and have not been discontinued or expired. Note that orders that are post-dated (have a start date in the future) are still considered "active", since the request is active even if the future action to fulfill the request is not yet.  See Active Orders (Design Page).
  • complex dosing – this is the opposite of "structured dosing."  For medications, complex dosing is when the dosing instructions cannot fit neatly within the structured dosing fields and therefore must be stored as free text.
  • instruction template -- a pre-defined set of instructions for a specific order, used to present choices for common instructions and/or indication-specific instructions for providers as an alternative to writing the instructions manually each time.
  • order – a request from a provider to do something for/to a patient
  • order action – the action being taken on an orderable ("new" may be implied; other actions would include "discontinue", "revise", "continue" to continue without generating a new prescription, "renew" to continue by generating a new prescription, "supplement" to perform a one-time request that should not affect a standing order for the same orderable).
  • order group – use to record the source of orders that are placed as groups of orders (i.e., chosen from an order set, such as a drug regimen) to assist with subsequent management or reporting of the orders (e.g., a drug regimen of three drugs chosen by the provider as a group instead of ordered individually would be recorded as three orders within an order group pointing to the regimen order set as the source).
  • order number -- this is a unique identifier generated for an order and passed to ancillary systems so that results & events related to the order to be connected to the original order (i.e., HL7's placer number).  Order numbers must be unique across all orders generated by the system, but the algorithm or format of the number could be implementation-specific.
  • order set – an order set represents a pre-defined list of order templates to be used in certain situations (e.g., pre-defined drug regimens, a set of common orders for treating viral gastroenteritis, etc.)
  • order template – an order template represents a single, pre-defined order, which may include choices and/or defaults for the various components of the order.  Templates allow providers to generate structured orders quickly by picking a template from a list or changing only one or two components without having to manually complete every component of the order.
  • order type – defines the category of the the order (e.g., drug, test, referral, activity, diet, nursing, call order, letter, handout, etc.)
  • order validator – used to check the validity of an order given the current ordering context (setting, user, item being ordered, etc.); multiple order validators may be chained together so that each validator can focus on a specific business rule.
  • orderable concepts – all orders are concept-based, meaning that all orders are based on a concept within the system's concept dictionary.  Only certain concepts are valid for defining an order (e.g., while you might want to order a "CD4 COUNT", you should not be able to order the concepts "HIV POSITIVE" or "YES").
  • orderer -- the person (provider) responsible for the order.  There must always be someone accountable for the order (generally, the person making the request who may not always be the same as the person entering the order into the system)
  • orders – our "order" is modeled much like the HL7 order event.  Each order represents a clinical provider's intent for something to happen for/to a patient, including drug prescriptions, tests, referrals, etc.
  • PRN – used in drug prescriptions, derived from the latin "pro re nata", and stands for "as needed."  For example, a pain medication may be administered "prn pain" or a sleeping medication administered "prn insomnia."  In the API and data model, we use the phrase "as needed" to help developers understand the meaning.
  • structured dosing – for drug orders, instructions that are codified within discrete fields (dose, dose unit, route, frequency, duration, duration units, PRN condition, and administration instructions)
  • urgency – when and with what urgency an order should be carried out (e.g., "STAT" to drop everything and do immediately, "routine", "scheduled" on a certain date or at a certain time, "conditional" to perform when certain conditions are met

Structured Dosing vs. Unstructured Dosing

Drug orders include dosing information that explains how much, in what manner, and in what frequency the medication should be administered.  Specifically, the attributes of a medication order that define the structured instructions are:

Simple Structured Dosing

The structure for simple dosing (e.g., 1 tab orally once daily) is reflected in the data model and expected to be filled when the dosing type is "SIMPLE".

  • Dose – (required) number of tab, milligrams, etc. to be given
  • Dose units – (required) unit of the dose (tabs, milligrams, etc.) for a single dose
  • Route – (required) how the dose is to be given (orally, intravenous, etc.)
  • Frequency – (required) how often the dose is given
  • Duration – (optional) how long the medication should be given (number)
  • Duration units – (optional) the units for how long the medication should be given (minute(s), hour(s), day(s), week(s), month(s), ...)
  • As needed – (defaults to false) true if the medication is PRN (taken only when needed)
  • As needed condition – (optional) the condition when the med should be dosed (e.g., "pain" when something is dosed only when pain occurs)
  • Administration instructions – (optional) free text instructions on how to administer the medication (e.g., "take with a full glass of water")
Free Text Dosing

In some cases, the provider may need to provide very complicated dosing instructions that cannot fit into any of the existing structured forms.  In that case, the dosing type is "FREE TEXT" and the free text dosing instructions are placed within the orders.instructions field.

In the future, we may add additional dosing types to capture structured dosing for infusions, titration, varying dose, alternate day dosing, etc. In these cases, the codified, structured dosing information will be stored in a computer-parseable format within the order_drug.dosing_instructions field.

 

Requirements

First Priority

  • Get orderable concept(s) ± limited by query string
  • Allow creation of simple drug and test orders.
  • Revise orders.
  • Discontinue orders.
  • Get order(s) by patient ± within date range.
  • Get order(s) by encounter.
  • Get order(s) by concept.
  • Get order(s) by ordering provider.

Second Priority

  • Order attributes
  • Order groups and order sets
  • Allow grouping of orders (to support regimens and/or arbitrary groupings)
  • Get active order(s) by patient ± as of a specific date.
  • Get order by order number.
  • Get order(s) by group
  • Get all order set(s), order set(s) by name, or order set(s) by concept.
  • Proof of concept of a module adding its own new type of order, with proper inheritance

Subsequently

  • Get order(s) by indication.
  • Advanced order set definitions (e.g., exclusion criteria)
  • Ability to filter orders by care setting – e.g., get all inpatient orders vs. all outpatient orders.
  • Support for order alerts (drug-drug interaction, drug-diagnosis interaction, allergy, etc.)
  • Support for additional order types (diet orders, referrals, activities, etc.)
  • HL7 support (inbound and outbound orders)
  • Order tagging
  • Mapping to standardized drug terminologies
  • Support for multiple dosing clauses for drug orders (e.g., with AND/OR/THEN conjunctions like the NHS Dose Syntax).
  • ... and much more.

Design

  • The base "order" object represents the foundation shared by all orders.  Some types of orders – e.g., medications and tests – extend the base orders object to add type-specific attributes.
    * Despite the convention of singular table names in OpenMRS (e.g., person, patient), we use the plural form for the orders table, since "order" is an SQL keyword (just as we've done with the users table).

Orders Data Model

The orders tables define the base order and those types of orders that extend the base: drug orders and test orders.

Orders
  • orderId (int 11) – primary key
  • orderNumber (varchar 50) -- a unique identifier shared by all versions of a given order. this is the identifier sent to external systems when referring to this order.
  • previousOrder (FK to orders) – when a new order is created from an existing, previously activated order (e.g., a discontinuation or change in dose), this property links back to the order that was cloned. note that once an order has been sign & activated, it cannot be revised as the same order, so any edits to the instructions/dosing or an order to discontinue an existing order gets a new order number.
  • patient (FK to patient)
  • encounter (FK to encounter)
  • orderType (varchar 255) – a discriminator that defines the type of order (e.g., drug order vs. test order vs. lab order vs. serology order)
  • orderAction (varchar 50) – a selection from an enumeration of possible actions (e.g., NEW, REVISE, DISCONTINUE, CONTINUE, etc.).  See Order Actions section below.
  • concept (FK to orderable concept) – if we are ordering AMPICILLIN, this points to the ampicillin concept
  • timing (varchar 50) – defines the urgency/priority of the order or when the order should occur, e.g., STAT, NOW, ROUTINE.  Eventually we could support ON DATE, BEFORE DATE, AFTER DATE, CONDITIONAL if we added an urgency_condition attribute.
  • timing_date (datetime) – when the timing is scheduled (e.g., ON DATE, BEFORE DATE, AFTER DATE), then this specifies the date
  • timing_condition (varchar 1024) – if the timing is CONDITIONAL, this contains a free text description of when the order should be carried out. (deferred for now, using instructions for specifying condition if timing is CONDITIONAL, which means that conditionally timed drug orders will need to use unstructured dosing for now)
  • instructions (text) – free text instructions for the order (e.g., details about a referral, justification for a cardiac stress test, etc.)
  • comments_to_fulfiller (text) – free text comments directed to the person fulfilling the request
  • startDate (datetime) – when the order should begin
  • autoExpireDate (datetime) – when the order should be discontinued if it hasn't already
  • discontinued (boolean)
  • dateDiscontinued (datetime) – when the order was discontinued
  • discontinuedReason (FK to concept) – the reason for discontinuing the order
  • discontinuedReasonNoncoded (varchar 1024) – free text reason for discontinuing the order
  • orderer (FK to provider) – provider responsible for making/signing the request
  • creator (FK to user)
  • dateCreated (datetime)
  • voided (boolean)
  • voidedBy (user)
  • dateVoided (datetime)
  • uuid (varchar 38)
DrugOrder extends Order
  • order (super.order_id)
  • drug (FK to drug) – the specific drug formulation prescribed
  • structuredDosing(boolean) – true if dosing instructions provided within the codified dosing fields; otherwise, orders.instructions is presumed to contain the free text dosing instructions.
  • dose (double) – specifies the amount for a single dose (e.g., "2" in 2 tablets or "100" in 100 milligrams)
  • doseUnits(FK to concept) – specifies the units for a single dose (e.g., "tablets" in 2 tablets)
  • route(FK to concept) – the way in which the medication enters the body (e.g., PO for per oral == by mouth)
  • frequency(FK to order frequency) – the frequency in which the drug is to be dosed (e.g., twice daily).
  • duration (double) – specifies the value for the prescription's duration (e.g., "14" in 14 days), may be null when duration duration value does not apply (e.g., indefinite prescriptions or one-time-only prescriptions)
  • durationUnits(FK to concept) – specifies the units for the prescription's duration (e.g., the "days" in 14 days)
  • asNeeded(boolean) – true for "PRN" prescriptions, i.e., medications that are taken "as needed"
  • asNeededCondition(varchar 255) – for PRN prescriptions, specifies when the medication should be taken (e.g., "cough" for a cough medication or "insomnia" for a sleeping aid)
  • administrationInstructions(varchar 1024) – additional information, usually for the patient, e.g., "take while sitting" or "take with a full glass of water"
  • quantity(double) – amount to be dispensed (e.g., "100" in 100 pills)
  • quantityUnits(FK to concept) – specifies the dispensing units (e.g., "pills" in 100 pills). (note: we will eventually want a separate table to constrain these values)
  • numRefills(int) – the number of times the medication can be refilled
TestOrder extends Order
  • order (super.order_id)
  • specimenSource (FK to concept) – specifies the source used for the specimen (blood, catheter, urine, CSF, etc.)
  • laterality (varchar 50) – specifies left, right, or bilateral
  • clinicalHistory (text) – provides free text clinical history relevant to the person interpreting the test (e.g., "chronic cough and weight loss in smoker" when ordering a chest x-ray)
  • frequency (FK to order frequency) – for repeated tests, specifies the frequency that the test should be repeated
  • numberRepeats (int) – for repeating tests, how many times the test should be repeated (if null, presumes indefinite until discontinued)

Order Sets Data Model

Order sets are used to pre-define sets of orders in order to make the ordering process easier – i.e., pick from a list instead of having to manually enter orders for common orders or groups of orders.  Order sets can contain 0-to-n members; each member can be a reference to an orderable concept, an order template (pre-defined order), or another order set.

  • order_set_id and order_set_member_id are unique internal ids
  • order_set_status indicates the state of the order set – e.g., DRAFT, READY FOR REVIEW, PUBLISHED.
  • operator represents how members of the set can be selected: ANY, ONE, or ALL: ANY allows for multiple selection, ONE forces single selection amongst members, and ALL means that you must either order all members or none.
  • name is used for administration of order sets & would be a fully specified name to support organization/searching among order sets.  Name is optional, since there can be anonymous order sets used for grouping of members within another order set.

OrderService API

OpenMRS already has an early version of an OrderService with basic CRUD support for orders.

Existing OrderService methods
  • saveOrder(Order)
    • TODO: should not let you change an Order.  can only do an sql insert from here.
  • voidOrder(Order)
  • unvoidOrder(Order order)
  • purgeOrder(Order)
  • discontinueOrder(Order order, Concept discontinueReason, Date discontinueDate)
    • TODO: creates a new order with a new order number that is a "discontinue" order.  previous_order_number on this should point to old Order
    • TODO: also finds old Order and marks it as discontinued with the given parameters
  • undiscontinueOrder(Order order)
    • TODO:
  • createOrdersAndEncounter(Patient p, Collection<Order> orders)
    • TODO: activates the orders given
  • getOrder(Integer orderId)
  • getOrderByUuid(String uuid)
  • getOrder(Integer orderId, Class<Ord> orderClassType)
  • getOrders(Class<Ord> orderClassType, List<Patient> patients, List<Concept> concepts, ORDER_STATUS status, List<User> orderers, List<Encounter> encounters, List<OrderType> orderTypes)
  • getOrdersByUser(User user)
  • getOrdersByPatient(Patient patient)
  • getStandardRegimens()
  • getDrugOrdersByPatient(Patient patient, ORDER_STATUS orderStatus)
  • getDrugOrdersByPatient(Patient patient, ORDER_STATUS orderStatus, boolean includeVoided)
  • getDrugOrdersByPatient(Patient patient)
  • getOrdersByEncounter(Encounter encounter)
Suggested new OrderService methods
  • saveOrder(...all required params...no Order object...)
    • used for inserts mainly
    • if called with a previously saved order, creates a new one with the new fields, doens't change the old one, and links the two orders using previous_order_number
    • (similar to saveObs())
  • modifyOrder(Order old, Order new)
    • (implement this or no?)
  • saveActivatedOrder(___, Provider/User?) ?? (or name this documentOrder() or saveDocumentOrder() or saveHistoricalOrder())
    • calls saveOrder, signOrder, activateOrder
  • signOrder(Order, Provider)
    • Can we discern if the Provider is a Nurse vs MD here, or do we need multiple signOrder methods?
    • means setting date signed and signed by
  • activateOrder(Order)
  • fillOrder(Order, User)
    • calls following method with a urn
  • fillOrder(Order, String) // if filler is not a user
    • means it was completed
    • if the order is not signed yet, throw an exception
  • getActiveOrders(Patient patient, Class orderClass, CareSetting careSetting)
    • TODO: Should get Orders that are not yet stopped nor auto expired.
  • getActiveDrugs(Patient patient)
    • TODO: Should get active DrugOrders that are not yet stopped nor auto expired.
  • Order getOrderByOrderNumber(String orderNumber)
    • Gets the 'latest_version=true' order with this orderNumber
  • List<Order> getOrderHistoryByOrderNumber(String orderNumber)  // or by Order object?
    • returns all orders, even ones discontinued, even old versions of the order
  • List<Order> getOrderHistoryByConcept(Patient patient, Concept concept)
    • similar to previous, gets all Order objects that use this Concept
    • create and use an OrderHistory object for this?  Would keep the sequence/history of the Orders and convenience methods for how to look at them.
  • discontinueOrder(Concept)
    • finds all active orders with this drug/concept and discontinues them

Order Actions
  • NEW -- creating a new order
  • REVISE -- modifying an existing order (e.g., changing dose of a medication, edits to instructions, etc.)
  • RENEW -- Renew an order(should this only apply to auto expired orders?) (e.g., providing another prescription for a chronic medication when refills run out)
  • DISCONTINUE -- stopping an order

This list could be extended to include HOLD, to indicate an order that should be held for one or more doses; however, we have seen too many medical errors from mistakes involving "HOLD" orders and would prefer not to promote the practice (i.e., it's safer to discontinue the order and then rewrite it when it should be resumed).

Orders can be discontinued whether or not they are active.  This is necessary, because we cannot assume that the computer will always know about all active prescriptions.  For example, if a patient shows up to clinic already taking a prescription that is not in the computer records and you want them to stop, it's helpful to be able to explicitly write a DISCONTINUE order for that medication even though it's not an active order.  The workaround of writing for the medication and then immediately discontinuing is not viable, since it could imply that were ordering the medication for the patient at some point.

Drug Reference Mappings (Proposed by Wyclif)

Like Concepts, Drugs need to have looser mappings to other terminologies, see diagram below.

API methods for managing drug reference mappings (To be added to ConceptService)
public Drug getDrugByMapping(String code, ConceptSource conceptSource, Boolean includeRetired)
public List<Drug> getDrugsByMapping(String code, ConceptSource conceptSource, Boolean includeRetired)

 

 

TODO (and notes)
  • Rename "Regimens" tab to "Drug Orders"
  • "standard drug regimens" replaced by order sets and and templates in them
  • When initially creating orders via a saveEncounter call, the orders should be in draft state.  A second call to sign all the orders is needed
  • order_number, order_version, latest version are internally maintained, not to be entered by a user
  • Don't link to the admin page edit screen from the dashboard.  add a small popup that only lets the user edit certain properties
  • On dashboard, can change the dosage to "change" an order. (aka orders are linked via discontinue and new order)
    • aka, should not be able to change an activated order
  • Make saveOrder smarter so that we can link/dc/etc orders if there are other ones that are related?
  • Mutability needs to be researched, may be done for API 2.0, but not here
    • Order object properties should be mostly immutable. 
    • Encounter.orders should not contain mutable Order objects

Interested Parties

Assigned Developers

Work Items

  1. Add new objects and attributes to current order and drug order objects
  2. Determine the feasibility of transitioning from current Orders to new orders
    1. If doable, add liquibase changesets, etc
    2. If impossible, investigate writing a one-time-use script or module to help implementations with the upgrade.  PIH has a lot of experience with orders, get feedback from them
  3. Modify the Regimens tab to work with new order templating system
  4. Update the simple order jsp pages to work with the new attributes
  5. TBD: Write a module with more complex workflow possibilities. 

Other Questions

  • How do we identify orderable concepts?  By class(es)?
  • Do we treat the orders table as a transactional table (essentially write-only) vs. creating separate table(s) for storing revision history?
  • Can we support draftorders & encounters & visits – i.e., unsigned work – within the same tables?  Or do we persist these data elsewhere until signed?
    • PROS:
      • Avoid creating separate table(s) for persisting drafts
      • Increases potential in the future to see or react to concurrent drafts
    • CONS:
      • Drafts must be filtered by API
      • Old drafts will need to be cleaned
  • We'll need to agree on order workflows – i.e., what are the valid states of an order.
  • Our first pass at medication orders assumes a single clause for structured dosing.  Eventually, we may want to support multiple clauses linked with AND/OR/THEN to allow for structured representation of more complex medication orders.
    • Support for multiple clause structured dosing could be supported within the data model by adding preceding_order_drug_id (to link to preceding clause) and conjunction (to represent join as AND vs. OR vs. THEN).  More complete representation of structured dosing clauses would probably require moving structured dose into a separate order_drug_dosing table.

See Also

Associated Tickets

type key summary assignee reporter priority status resolution created updated due

Unable to locate Jira server for this macro. It may be due to Application Link configuration.

Notes

...