Backend Support for Service Delivery Queues / Workflows

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

We currently use the module to support the below patient flow:

  1. One is waiting for a service i.e. triage if they are added to a triage queue
  2. When a provider picks a patient from a queue, the status changes from waiting for triage to calling. We have an interface where this is visualized and can be projected on a screen
  3. If the called patient is available/present, he/she is served by the provider else they are marked to be a ‘no show’ and pushed back to the same queue but with a ‘no show’ flag
  4. The process ends with a patient transferred to the next service point or exited from the queue i.e. checked out

I hope this helps. Thanks

These are actually all questions we didn’t want to provide hard answers to, with the idea that they’d be answered by whatever application is consuming the queue. The queue structure by itself is intended to cover both one queue per facility or one queue per service or even one queue per room, depending on how specific an implementation or application wants to be. That said, both service and location were intended to be optional rather than required fields.

That was the intention.

That wasn’t the intention with the design, i.e., the original intention was that patients only have an active queue entry when they are actually waiting, but I think queues have sort of morphed into what I originally intended the PatientVisitState to be.

Thanks @corneliouzbett , @aojwang , and @ibacher for these insights, it is very helpful. From what I gather, it sounds like this is being used very successfully in Uganda, Kenya and maybe elsewhere, so I am very interested to learn more about those experiences.

What I’m trying to uncover is the degree to which the current module will meet our needs at PIH around hospital flow, triage, check-in, and queuing, and patient census, both related to outpatient and inpatient care.

I do have some additional feedback / suggestions for specific parts of the module, but I’ll save those for later. One thing I will say is that it is a bit difficult to follow changes, or to weigh in on designs, without having a JIRA project to watch and reference. Would this be something we could try to adopt?

There is one point @ibacher made that I’d like to follow-up on:

I went back and re-read your proposal around PatientVisitState and PatientQueueEntry @ibacher , and found that it very closely matches a design that I had also been working up. I think it’s worth building on this and seeing where it leads, whether to an evolution of the queue module or other alternative/complementary modules.

Related to your proposal, I’m also interested in thinking through how we model “services” and “space”.

  • What is a “Service”? Is this best just modeled as a Concept? What is it’s relationship to Location?
  • Is it proper to model beds, rooms, wards, units, departments as Locations (in a hierarchy)?
  • If so, could bed management be fully supported by “PatientVisitState” or an object that references it?

I’d be interested in discussion on an upcoming TAC. @grace / @burke

1 Like

All fair questions and something we should probably work-through. Here’s some preliminary thoughts:

A “Service” is intended to be flexible; we want to cover both the case where the user is waiting for, say “Radiology” or “Room 12” or “Dr. Chakrabarty”. It’s named “Service” because this is a concept in the appointments module (i.e., you can schedule an appointment with a service), but represented as a concept because we didn’t want to pre-specify the specificity of “service”.

There’s no specific, defined relationship between a “service” and a “location”, but the previous iteration of queues had been tied to locations and we wanted to make an easy path for this.

Initially, I thought this was the case, but I’ve somewhat changed my mind. More specifically, it may be appropriate to use “location” for the more “fixed” parts of things (“wards”, “units”, and “departments”) are likely to not change in any important way in the middle of operations, but below that level, things get dicey. Obviously, rooms as physical locations might be representable as a location, however, they have a lot in common with beds and I don’t think beds are well-managed by locations.

Specifically, when managing beds in a clinical setting, we need a fair bit of flexibility to handle a number of things:

  • Though a clinic might have X number of beds, they can only utilise beds that they have appropriate staffing for, thus we need a way of indicating (in real-time) which beds are in use, available, and unavailable.
  • In some circumstances, a clinic might have temporary beds added, e.g., in an emergency situation, sometimes cots and other things are repurposed as “beds”. These should be trackable in the system.
  • Beds can be moved between different locations and this should be tracked (that is, the number of beds per room, for instance, might be variable).
  • Similar to adding and removing beds, certain rooms might be in use, available, or unavailable, depending on needs, staffing levels, etc.

All of this implies that to properly implement a bed management system, we need something that is added to and updated by users during normal system operations. And this suggests to me that locations are maybe not the best suited for this, since we don’t normally have locations being created, marked as available or unavailable live. There’s no technical reason that we couldn’t, it just makes sense to ask whether we want those sorts of things to be tracked in the location table or a separate table for that. And, because of the required flexibility, I’m leaning towards having a separate domain for this, maybe with a hierarchy like:

Location → Room / Clinical Sub-division* → Bed

With the latter two being handled by separate domain object(s).

* Clinical Sub-Division is intended to capture that there might be other arrangements for how beds are organised logically, if not physically. Specifically, I’m thinking about systems that use a concept of “pods” that have an assigned team of nurses to cover a subset of beds / rooms in a larger organisation like a unit or department.

We definitely need some way of tracking which bed a patient is in for IPD and ED at least, and I think having a way of tracking the history is important. So I think we’re going to wind up with some domain object to track that.

Right. Recognizing that this idea of “service” is starting to independently proliferate, I’m wondering if we want to define a shared standard that these independent modules (and future core features) can utilize. It seems that we can either say that “service” is just a Concept, and the way to share the idea of services between modules is linking Concepts and possibly sets of Concepts. Or if there is a need to create a formal HealthCareService domain object similar to this in FHIR, etc. Probably the most straightforward path is to use Concept in the short term for this, and then provide a path where this can be linked (or migrated) to a HealthCareService by concept in the future?

This is a fair point, and something that would be good to discuss. The best argument to use Locations is that there is so much built up to support Locations already. We have hierarchies of Locations (unit → ward → bed), we have attributes of Locations (bed number, bed type, in_service), we have tags of Locations (Pod A), etc. And most of our clinical data can reference a location, whether in core or in a module.