Backend Support for Service Delivery Queues / Workflows

@burke On the TAC call today, you mentioned an idea of Encounter status. Do you want to expand on that a bit? I wasn’t thinking about encounters per se because service delivery queues are generally a pre-encounter thing, but I’d be interested if there’s some rationale for tracking state within an encounter.

One thing that’s come up a few times is the idea of a patient being able to be in multiple queues at the same time, e.g., waiting for the lab and waiting for the pharmacy at the same time.

This poses some challenges for the proposed model. My thought so far has been that we can track multiple simultaneous visit states to allow for this. I think from a technical perspective this works, but it does add some complexity.

With purely linear workflows, we can always assume that the next workflow step closes out the previous one. With branching workflows, however, this isn’t always the case. For instance, going from triage → waiting for clinician likely closes out the triage state. However, going from waiting on pharmacy and waiting on lab → done with pharmacy shouldn’t also close out the waiting on lab.

Another possibility would be simply to allow multiple “Visit Queue Entries” per visit state with the assumption that there’s only a single active visit state at a time. This simplifies some of the complexity above, but it does mean that we probably need to add start_date and end_date to the “Visit Queue Entry” (since being seen by the pharmacy shouldn’t mean I lose my place in line for the lab).

My preference is to allow for the possibility of simultaneous multiple visit states.

As @ssmusoke mentioned, we need some validation and business logic around things, so some simple validation rules:

  1. A visit state requires an active visit (whether current or retrospective)
  2. A visit state can only start after a visit is started
  3. All pending visit states should end when a visit ends.
  4. A “Patient Queue Entry” requires an active visit state
  5. A “Patient Queue Entry” is no longer valid after it’s visit state has ended (there’s no reason visit states can’t be resumed)

Aligning with FHIR would mean each Encounter has a single status along with an optional history of states). FHIR’s Encounter can represent an OpenMRS Visit or Encounter. So, there could be an overall state for a visit and, if needed, there could be a state for each encounter. I would think most of what has been described (tracking states within a clinical workflow) could be done with a Visit status; however, if we really wanted to track multiple states at once, they could be done at the encounter level. Doing so would require, like we do for visit, that the encounter be created when the status is needed rather than waiting for a form to be completed and completing a form, writing orders, etc. would be added to that encounter.

I wonder if we should use many-to-many tables to relate visit status (or status history) to the queue. The same pattern could be used to link other things (outside visits & encounters) to queues and would open the door to having multiple queues within a visit without having to break alignment with FHIR (i.e., constraining visits to one state at a time within the data model).

image

Also, I would favor focusing the ability to track states & queue entries and layering on logic to, for example, calculate sort order or trigger state changes rather than trying to pre-design every possible need and bake it into the service. For example, the ObsService handles creation, voiding, searching, etc. for obs, but doesn’t presume to know or own all possible ways an observation might need to get created.

I see your point. I think, though, that aligning with FHIR on this point would actually be quite a bit harder from a pragmatic point of view and for—at best—unclear benefits. Here’s the problem as I see it: the natural way to represent a patient waiting for a service in FHIR is to have an Encounter object with a status of “planned” or “arrived”. However, we don’t have the ability to create “planned” encounters (AFAICT). That is, rather than treating the visit_state as 1:1 equivalent with FHIR’s status, I would prefer to treat FHIR’s status as derived from visit_state (this would allow us to represent, for instance, that an encounter was in a waiting state for X period of time once the encounter actually existed—then gradually we could introduce the concept of planned encounters, if necessary).

I fully agree with this, except that I would like to simplify sort order to a single rule (priority DESC, wait_time DESC). Otherwise, we actually will need a queue_definition table to store the rule for sorting adopted by this queue. In this case, I think this fits the idea of minimising the number of elements we need for this design and not adding designs for things for which we don’t currently have a clear use-case.

I’m fine with deferring Encounter status as long as we’re not in a position to create encounters before data are collected (i.e., create an encounter when the patient is initially queued for the encounter and attach form data, obs, orders, etc. to that encounter as they are created).

My main concern is suggesting we would have multiple states for a visit. What do we report as the visit status when a patient is in multiple states at the same time? It makes more sense to me that (aligning with FHIR’s view) that a patient’s visit can only be in a single state at any point in time and the best way to enforce this is with a single visit.status field. Trying to force the capability to have a patient queued for a provider and a lab at the same time into visit status feels like we’re abusing the meaning of visit status. In other words, if we really need to support Mona simultaneously queuing for a provider and the lab at the same time, then her visit status could be “Queued” and we come up with a different workaround for encounter queues (like a pre_encounter.status that could eventually be absorbed into encounter.status when/if we can create encounters before forms are submitted).

That’s fine. We could introduce something like sort_weight when/if we have the requirement for manually controlling order of queues.

Here’s output from an iteration on today’s Platform Team call:

image

This is very late and I said I’d have something but I essentially agree with Burke’s diagram above. The only thing that’s a little weird is having something called an encounter_status that may or may not refer to an actual encounter. However, the queues we’ve been talking through using this for are essentially queues waiting on an encounter so it makes sense in that way and, as discussed, we’d like to evolve the data model towards supporting “draft” encounters, i.e., where an encounter is pre-populated with some data before the actual encounter happens, at which point, the encounter_state construct can be migrated into the encounter model.

The one modification I’d suggest is that encounter_state should still reference the visit which the encounter will be part of otherwise, we end up with dangling encounter_states that don’t have any way to be linked to a patient context. So something alone these lines:

image

2 Likes

@slubwama what do you think of the above?

@ibacher @dkayiwa I think the last design from @ibacher is closer to the original intention of the introduction of the queue. I see lots being ignored queue_rooms, location_from, vist_number (queue_number).

1.queue_rooms (sub-locations) are very important. At times patients are sent to rooms without specifying which provider, since providers could easily switch rooms without their specific name tags to the door.

  1. location_from (previous_location). this offers a traceable movement of patients where one can easily draw the pattern of how patients move across the clinic.

  2. vist_number (queue_number). In local clinics where more than 100 patients are attended to in a day, these help to keep order in the clinic. At times paper cards can be issued to patients in reference to which number they hold in the queue and then later called by their queue numbers aloud. This improves the privacy of patients also

Also, take into note that a queue may not necessarily be tied to an encounter since there are some services that could utilize other data models such as orders without necessarily being attached to a specific encounter.

So previous location is captured in the model, but it’s part of the encounter_status_history in the above diagram.

queue_rooms, i.e., where the patient currently is (if I’ve got this right) could be modelled as the location attached to the current encounter_status_history record.

As I think we discussed previously, these could be converted into, e.g., a visit-specific attribute (since they’re data about a visit). That way, the visit_number only needs to live in one place (unless there’s a need for a visit_number per queue?).

Could you give me an example of this? I’d think that when the order gets created it’s generally attached to some sort of encounter right?

@burke can you provide the updated draft model?

End decision in TAC discussion today was; we need this from you. Though not expecting a massive conceptual change. Thought from @ibacher; would “is-active” view on queue get us around the major MVP problems for now?

During today’s Platform Team call, we iterated on the model and decided to simplify the queue service to focus on its primary goals:

  • Created, update, and delete queues
  • Add & remove entries from queues
  • Efficiently answer questions like “what was the average time spent in this queue over the past week? And what was the average number of patients in that queue over the past month?”

The queue entries can be managed in with a queue_entry table like this:

image

We’d include a queue table for metadata (name, description, etc.) and likely one or more tables like queue_stats to help manage queue statistics efficiently.

The triggers to add or remove entries from a queue could be myriad and come from a variety of domains (e.g., registration, encounter state, observation value, modules for specific workflows, etc.).

For the initial use case of encounter-based queues (triage, lab, clinic, etc.), we can create and track encounter state by introducing an encounter_status table with status (Concept) and encounter_id to optionally link to an encounter. Eventually, when we have support for draft encounters in the Platform, this status field could be merged into the encounter table.

image

For now, the implementation of triggers to add/remove entries could be included in the service queue module or provided by other modules. As common patterns evolve (encounter-driven triggers, observation-driven triggers, etc.), the service queue module could provide configurable support for these out of the box.

@corneliouzbett & @ibacher are going to spike on adapting the service queue module this direction and we’ll aim to iterate on code going forward.


Updated 2022-01-14: included some additional attributes based on this comment

After discussion with @jdick, I will amend my previous post with a couple of changes:

  • Include location_waiting_for and provider_waiting_for within queue entries to optionally specify for which provider and/or room the patient is queued. This can be useful in some workflows, can be ignored when not needed, and allows us to build on the existing module.
  • Replicate additional encounter attributes within the encounter_status table as needed to identify to which encounter the status applies (or will apply when the encounter is created). If we had support for draft encounters (the ability to create an encounter row before the actual encounter takes place), we would be adding this status directly to the encounter table. Because we don’t yet support draft encounters and we need to track status before the encounter is completed (and the encounter row created), we are – for now – creating an encounter_status table to hold this information. A status attribute alone is insufficient to know to which encounter the status belongs, so we will need to duplicate additional attributes from encounter into the encounter_status table to allow us to know to which encounter the status applies. These would include at least encounter_datetime, encounter_type, patient_id, and visit_id (note that visit_id will be used to help unambiguously identify the encounter to which the status belongs and is not meant to assign status directly to a visit, which – if needed – we would do introducing either visit.status or, if necessary as a workaround for lack of draft visits, within a new visit_status table).

@burke Re-looking at this: how to we link encounter_status to the corresponding queue_entry? That seems to be missing.

In the earlier design, I proposed a mapping table for this to avoid tightly coupling queues to the type of triggers/statuses that might lead to queue entries. In the case of encounter_status, this would be something like:

image

When an encounter status triggers the creation of a queue entry, that status and the resulting queue entry can be linked through a mapping table like this. Other triggers could follow the same pattern. Code & queries aware of encounter status can use the mapping table while the queue service can focus on the queue tables.

@here. Bringing this back on the top. Are we still pursuing this project? UgandaEMR are starting to look into adopting openmrs 3.x. The Service Queues are key for us to maintain the POC workflows for our implementation. Is there a team looking into this already? What is the schedule for service queues implementation in 3.x

Hi @ibacher , @slubwama , @jecihjoy , @corneliouzbett ,

I recently started looking over the queue module to better understand it’s capabilities and design, and was happy to see that the README of that module links back to this Talk thread, which is super helpful background. I was hoping we could add to the thread and provide more information as to how the module has since evolved, since I have not yet been able to locate any tickets in JIRA that might better describe the changes/commits that have been made and the rationale for them.

Would you be able to describe, either here or in the README of the module, a bit more about what each of the domain entities is meant to represent and how they are intended to be used? Some questions:

  • What is VisitQueueEntry? Is this intended to allow specifying that certain QueueEntry objects are related to a specific Visit? Is there a reason why this was implemented as a separate entity rather than just having an optional visit property on QueueEntry? Are more properties expected here? When would one use a VisitQueueEntry in addition to a QueueEntry - is this the normal case?

  • Can you explain the recent addition of QueueRoom and RoomProviderMap to the model? How is this intended to be used? Is RoomProviderMap really intended to be data vs metadata? It seems more metadata-ish, especially since all of it’s properties are metadata.

  • How many Queues are you expecting a typical implementation to have? Is the idea that there would typically be one Queue in a given facility, and that a patient would be in only one state (non-overlapping startedAt and endedAt dates) at a given time on that Queue? Or would there be one Queue per service (Triage Queue, Consultation Queue, Lab Queue, Pharmacy Queue, etc) and we need to manage multiple queues per patient as they move around the hospital? Given that Queue has a non-null Service and Location associated with it, what level of granularity are these typically expected to have?

  • Is the intention that patients would have Queue Entries, even when they are not actually “Waiting” for something? For example, if they move from “Waiting for Triage” to “In Triage”, the “Waiting for Triage” queue entry would end, but would they then start a new Queue Entry of “In Triage”? I’m guessing the answer will be “It’s configurable - it can do either”, but I’m interested in understanding better how it is currently being used and what the design intention is.

  • I’d be interested in understanding more about the specific Hibernate Annotations on the two List properties on the Queue object and discussing the implications of the Where constraints on each. It is not typical that we would have accessors for data on a metadata domain object. These seem like convenience methods, but are things that would typically only be possible via calling the Service, particularly since permissions to get metadata like Queue might be much different from permissions to get data like QueueEntry.

  • Can you describe how the 3 global properties listed in the documentation - queue.statusConceptSetName, queue.priorityConceptSetName, and queue.serviceConceptSetName are used? How do these relate to the fact that there can be more than one Queue defined, that each Queue has an independent service concept associated with it, and presumably each Queue could have different priorities and statuses associated with it.

Happy to discuss further in other threads or in other more appropriate forums, but wanted to get it started here as it relates directly to the conversation above.

Thanks! Mike

@mogoodrich FYI

Thank you for your thoughtful questions and insights regarding the Queue module’s functionality and design. I appreciate your dedication to understanding the intricacies of the module. I’ll do my best to address your queries comprehensively;

Yes, you’re correct. The VisitQueueEntry is intended to associate specific QueueEntry objects with a particular visit.

The decision to implement it separately was driven by the need to keep QueueEntry more lightweight and focused on the core queue-related attributes. VisitQueueEntry can be seen as an extension to capture additional context related to patient visits. It’s used when you want to track a patient’s journey through the various stages of their visit explicitly. e.g. You are able to get queue entries associated with a specific visit. I guess you can achieve the same If a visit is added to the QueueEntry model as an optional property :thinking:

I would also want to understand more about this addition @PalladiumKenya @jecihjoy @aojwang

The latter, one Queue per service. The number of queues will depend on the facility’s operational model.

Your intuition is correct. The design of queue entries allows for flexibility. Let’s hear from @PalladiumKenya on how they are utilizing this.

All the 3 GPs are designed to allow customization of statuses, priorities, and service concepts i.e. allow you to configure what services(/service points) are being offered in your implementation, or priorities tuned to your implementation. With the current design, I think it’s safe to assume (No. of service concepts configured = No. of queues). Statuses and priorities are configured for all queues, so the same for all queues, at least that’s what I know. We can always evolve/enhance the module to suit our needs

~ Bett

2 Likes

Hi Mark. The two additions are meant to document the physical service points and the providers who are in those points at a particular point. The idea is to make it easy to identify for instance the various points where triage service is offered i.e. Triage room 1, triage room 2, etc. Do you have any design proposals?

@jecihjoy @minimalist worked on the additions and they can add more details.

1 Like