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).
Our goal is to provide an order entry service capable of supporting outpatient orders, focusing initially on medication orders but allowing for other types of orders (tests, referrals, 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.
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.
- activation – an order is "activated" at the point that it can be carried out (filled) – i.e., in a paper-based system, an "activated" order is one that has been written in ink and signed. In many cases, signing & activation occur within in a single step, where, for example, signing an order also makes it immediately available to be carried out. In other cases, signing & activation may occur in discrete steps. For example: in some clinical workflows (especially inpatient settings) certain orders (like admitting, discharge, & transfer orders) may be created & signed prior to being activated. When verbal orders are created, they may be activated before being signed by the ordering provider. "Activated" and "active" have different meanings, see the note below on "Activated vs. Active".
- order number – this is an identifier generated for a given order and shared by all revisions (if any) of that order. The order number is passed to ancillary systems in order for results & events related to the order to be connected to the original order.
- order filter – filters determine which properties (fields) available within a given context. Similar to order validators, filters can be chained together to apply specific business rules based on the current context (user, setting, patient, item being ordered, etc.). For example, if refills are not allowed for opiates then an order filter could remove "refills" from the list of available properties when an opiate is being ordered.
- order group – orders may be placed within groups to assist with subsequent management or reporting of the orders (e.g., a drug regimen of three drugs may be placed within an order group).
- order set – an order set represents a pre-defined list of orders 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 is a document (e.g., XML) describing a single order, which may include choices and/or defaults for the various components of the order – i.e., a pre-defined order. templates allow providers generate structured orders without having to manually complete every component of the order.
- order type – there are pre-defined types of orders (e.g., medications, tests, referrals, activities, diet, 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.
- order version – to keep track of different revisions of the same order
- orderable concepts – 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 an "HIV POSITIVE" or a "YES").
- 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.
- signing – signing of an order represents either documentation of an existing signature or – more commonly – the electronic signature provided by a user
- urgency – specifies when the order should first occur (e.g., stat/immediately, routine, on a specific date, etc.)
There can be many interpretations of "active" orders (e.g., which medications have been ordered, which medications are being filled, which medications are being taken) and these are not always identical lists. There can also be differences based on context – e.g., inpatient medications vs. outpatient medications. Within the order entry API, we are focused on orders, so "active" refers to those things that have been ordered, should have already started, and have not yet ended/expired. Knowledge about which orders are actually being followed (e.g., medication adherence, prescription dispensing data, etc.) are outside of the scope of the first pass at a generic order entry API. For example, medication reconciliation can be a complete project in its own right, taking into consideration adherence, dispensing data, orders from external systems, and orders placed through the order entry API.
"Activated" vs. "Active"
It's possible for an order to be "activated" (available for filling, no longer modifiable) but not yet "active." For example, an order can be signed & activated with a start date two weeks in the future; in this case, the order is "activated" (signed & can no longer be modified, only discontinued & replaced if needed) but may not be considered an "active" order until the start date is reached. We may end up wanting to consider future tests (with a future start date) as "active" (or let implementations decide on which behavior they prefer, if that can be done without complicating the API).
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. The dosing information can be collected in either a structured (discrete values for each component) or unstructured (free text) format. While structured dosing information allows for valuable calculations (e.g., how much medicine the patient will need and/or how much the pharmacy must order to maintain inventory), not all implementations need this level of detail. Some implementations will never need the detailed dosing information and can function fine with free text instructions for all orders, some implementations may wish to pick & choose when they structure information vs. use free text, and other implementation will expect fully structured dosing information for every drug order. Allowing for both structured & unstructured dosing of medication allows OpenMRS to adapt to the needs of the implementation and prevents our API from assuming everyone will order medications with the same level of detail. Allowing for unstructured dosing also provides a mechanism for handling complex dosing instructions that cannot be expressed in simple terms (e.g., take 2 pills in the morning and 1 pill in the evening).
- Allow creation of simple drug orders.
- Revise orders.
- Discontinue orders.
- Allow grouping of orders (to support regimens and/or arbitrary groupings)
- Get orderable concept(s) ± limited by query string
- Get active order(s) by patient ± as of a specific date.
- Get order(s) by patient ± within date range.
- Get order(s) by encounter.
- Get order(s) by concept.
- Get order(s) by ordering user.
- Get order by order number.
- Get order(s) by indication.
- 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
- 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
- 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.
- 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.
- 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.
- orderVersion (int) – a 1-based revision count for tracking revisions within the same order number
- latestVersion (boolean) – use to mark the latest version for any order number (with each new revision, this is set to
false for any existing versions and the new version gets
latestVersion = true. this exists solely to allow the maximum version for any order number to be found quickly
- previousOrderNumber (varchar 50) – 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 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. this property provides a mechanism to link related orders across order numbers.
- patient (FK to patient)
- encounter (FK to encounter)
- orderType (int) – a discriminator that defines the type of order (e.g., drug order vs. test 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
- noncodedName (varchar 255) – for orders where a specific concept does not exist, this property holds the name of the order (e.g.,
concept = OTHER DRUG ORDER #1 and
noncodedName = "Foobaricillin", used when Foobaricillin was just released and hasn't made it into the concept dictionary but still needs to be ordered).
- urgency (varchar 50) – defines the urgency/priority of the order, e.g., STAT, NOW, ROUTINE, ON DATE, BEFORE DATE, AFTER DATE, CONDITIONAL.
- conditionality (varchar 255) – for orders with a CONDITIONAL urgency, this property contains free text describing the condition(s) under which the order should be performed, e.g., "when the patient returns from surgery"
- instructions (text) – free text instructions for the order (e.g., details about a referral, justification for a cardiac stress test, etc.)
- frequency (varchar 50) – described the frequency of repeats for an order (note: eventually, we may want to draw these from a table of possible values)
- indication (a concept) – the reason for the order
- comment (text) – free text comments
- startDate (datetime) – when the order should begin
- autoExpireDate (datetime) – when the order should be discontinued if it hasn't already
- signedBy (user) – user responsible for the order
- dateSigned (datetime) – when order was signed
- activatedBy (user) – user who activate the order so that it could be carried out (may be different from signing user in some cases)
- dateActivated (datetime) -- when order was activated
- filledBy (varchar 255) -- unique reference to the party responsible for filling or carrying out the order, e.g., the lab that reported the result or the pharmacy the filled the prescription (note: we will need a convention for formatting this)
- dateFilled (datetime) – when order was filled
- dateDiscontinued (datetime) – when the order was discontinued
- discontinuedReason (varchar 255) – the reason for discontinuing the order
- creator (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 prescribed
- brandName (varchar 255) -- the brand name, when applicable, of the drug being prescribed
- strength (double) – specifies the strength of a single dose of the medication (e.g., "20" for a 20 milligram pill)
- strengthUnits (varchar 255) – specifies the units of a single dose of the medication (e.g., "milligrams" for a 20 milligram pill) (note: eventually we will probably want to constrain this to values within a separate table)
- dose (double) – specifies the amount for a single dose (e.g., "2" in 2 tablets or "100" in 100 milligrams)
- dosageForm (varchar 50) – specifies the units for a single dose (e.g., "tablets" in 2 tablets)
- route (varchar 50) – the way in which the medication enters the body (e.g., PO for per oral == by mouth)
- unstructuredDosing (varchar 1024) – dosing instructions provided as free text either because fully specifying dose & frequency are not needed by the implementation or the instructions are too complex to fit into the simplified model of dose & frequency properties.
- 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 (varchar 50) – 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)
- additionalInstructions (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 (varchar 50) – 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
- order_id is a unique, internal id
- order_number, order_version, and latest_version indicate the order number (shared with ancillary systems) and revisions to an order before it is finalized
- previous_order_number allows orders to be linked to a previous order – e.g., an order discontinue ampicillin linked to the original ampicillin order (the D/C gets its own order number)
- order_group allows orders to be grouped – e.g., drug regimens
- order_type would be an enumeration of order types – e.g., DRUG, TEST, REFERRAL, DIET, etc.
- order_status hold the state of the order within pre-defined workflows – e.g., SIGNED, PENDING, ACTIVATED, FILLED, etc. (deferred for now)
- date_activated: an order is considered "activated" (order is currently active or has ever been active) if the date_activated is not null
- order_action represents the action being taken on an order – e.g., DISCONTINUE, CARRY-OVER, etc.
- noncoded_name allows for orders to be created for items that are not yet in the dictionary – e.g., OTHER DRUG ORDER #1 with non-coded name "CANE".
- urgency and conditionality describe when an order should be carried out: STAT, ROUTINE, WHEN "patient arrives on the ward."
- filler is an optional URI to a person or process that fulfilled the order – possibly even a pointer to the object/resource that represents the result
- discontinued_reason is optional text that would go on the D/C order (this was a coded answer in previous versions of openmrs)
- date_discontinued is set when an order is explicitly discontinued or auto-expired
- In order to set the date_discontinued when orders automatically expire, we will need mechanism to perform this task in the background in an efficient – yet ideally real-time – manner. For example, registering the order_id with an auto-expriation process whenever auto-expiration is set/changed and/or a cron job to scan for passed auto-expiration dates ± register upcoming auto-expirations.
- strength_units, dosage_form, route, duration_units, quantity_units, and frequency should (at least eventually) be drawn from explicit code sets
- We could use concepts, but this would require mapping or creation of specific concepts before ordering would work. While units, route, and even dosage form might work as concepts, we might want specific tables for frequency to allow for (1) frequency patterns – like "every x hours" – and (2) representing computer-understandable versions.
- We may want to add an optional
specimen_identifier to order_test to accommodate for a specimen identifier sent back from a lab. Alternatively, we may be able to get to this information via
select accession_number from obs where order_id = ?.
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.
OpenMRS already has an early version of an OrderService with basic CRUD support for orders.
Existing OrderService methods
- TODO: should not let you change an Order. can only do an sql insert from here.
- unvoidOrder(Order 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)
- 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)
- 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)
- 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
- 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
- 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(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.
- finds all active orders with this drug/concept and discontinues them
- NEW -- creating a new order
- REVISE -- modifying an existing order (e.g., changing dose of a medication, edits to instructions, etc.)
- CONTINUE -- continue an existing order (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.
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
- Add new objects and attributes to current order and drug order objects
- Determine the feasibility of transitioning from current Orders to new orders
- If doable, add liquibase changesets, etc
- 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
- Modify the Regimens tab to work with new order templating system
- Update the simple order jsp pages to work with the new attributes
- TBD: Write a module with more complex workflow possibilities.
- 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 draft orders & encounters & visits – i.e., unsigned work – within the same tables? Or do we persist these data elsewhere until signed?
- Avoid creating separate table(s) for persisting drafts
- Increases potential in the future to see or react to concurrent drafts
- 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