Backend Support for Service Delivery Queues / Workflows

When is the proposed design call?

@aojwang Happening now - Same Zoom meeting as the conference.

1 Like

@slubwama Thanks for your fantastic overview of the module on Friday. And @aojwang, @corneliouzbett, and @jwnasambu thanks for your comments on that call.

I’ve taken a bit of time to discuss some initial ideas with @jdick, as well as to review the Patient Queueing User Guide as well as the designs @slubwama came up with on the Wiki and the original design from 2012.

As @jdick’s initial post notes, there are some ties between this and the clinical workflow. Indeed, @slubwama’s demo showed that the way Uganda EMR is leveraging the patient-queueing module is as a way to track not only the patient’s status in a queue, but the patient’s status within a visit (where has the patient been? where is the patient supposed to be next?).

Starting from this and @burke’s remarks above, I think it’s helpful to separate out where the patient is in a workflow from the queue and instead add queues as something that are derived from a more generic mechanism for tracking the patient’s current state (i.e., where is the patient now?).

To that end, I’m proposing we move the patient queueing module towards using something like this data model (note that the data element names are evocative rather than necessarily recommended names for the table fields):

Patient Queue Design

The idea is that each Visit has one or more Visit States which track the current status of the patient and can be updated as the patient moves through different encounters in the visit or independently of the encounter. Certain Visit States might indicate that the patient is waiting for something. In this case, we create a Queue Entry to capture the fact that the patient is waiting for something.

Patient Visit State

Field Meaning
patient Links to the patient this Visit State tracks
visit Links to the visit the Visit State is a part of
date_started The date and time the patient entered this state
date_ended The date and time the patient exited this state
state A concept capturing what this state is
comment An optional comment about the status
location An optional location field storing where the patient currently is (or at least should be)
entered_in An optional field linking to the encounter that first entered this state. This is to allow the PatientVisitState class to implement the FormRecordable interface from recent versions of OpenMRS

Patient Queue Entry

Field Meaning
patient_visit_state Links to the patient visit state this queue entry derives from
queue_identifier As @slubwama helpfully showed, its useful to have some identifier for a patient in a queue can be tracked that isn’t otherwise a personal identifier for the patient. Sticking the value here allows us to track it on a per-queue basis or copy it across any number of potential queues
priority A concept indicating the patient’s priority in the queue
priority_comment A comment on the priority, if any
concept_waiting_for This shouldn’t be there; ignore it
provider_waiting_for The provider the patient is waiting for, if any
location_waiting_for The location the patient is waiting for, if any

You might notice that in this model the queue itself does not actually exist in the database. This is because queues, at least for these purposes, are ephemeral rather than something durable: the queue is just the query who is waiting for x and the order.

Some sample questions and how to answer them:

  1. How long has patient x been waiting? This should be simple math determined by the time the patient entered the current state and the current time.
  2. Who is waiting for y? Assuming y is a location or provider, this should be a matter of querying the active queue entries for the particular resource in question (location or provider)
  3. Who is next in line for y? This is the main reason for using queues. This should be a matter of the top entry in the active queue sorted according to some rule I’m leaving undefined for the moment.
  4. Who is waiting for any resource (provider or location, etc.)? This is simply a matter of querying all available active queue entries.
  5. What is the status of patient x? This should just be a matter of querying the most recent Patient Visit Status.

There are a few pieces of business logic necessary. The first is to ensure that the previous visit state is always ended when the next state is done. Both the start and end dates for states are stored in one row as this makes the logic to determine “active” state cleaner and it makes retrospective analysis easier (how long does the average patient wait for this set of resources?).

The hard part in this model is determining whether or not a given visit state is a queue state and so a queue entry should be created for it.

Finally, let’s talk through an example visit:

A patient, Mona, comes into the clinic. Mona is seen by the registration desk, where she is checked in. Since this isn’t an emergency, Mona is sent to the triage area to await triage. At this point, Mona’s record will have been created and a visit started. Mona enters the “Waiting for triage” state and a queue entry is created to indicate that Mona is waiting for the triage location.

Mona is seen by a nurse at triage. At this point, Mona stops being in the “Waiting for triage” state and enters into the “With triage” state. The previous state is no longer active. The triage nurse takes Mona’s vitals, asks basic screening questions to determine how urgently Mona should be seen. This information is recorded an Mona is sent to wait for Dr. Ruiru. At this point Mona stops being in the “With triage” state and enters the “Waiting for clinician” state. A new queue entry is created to indicate that Mona is waiting for Dr. Ruiru.

Mona is seen by Dr. Ruiru. At this point, Mona stops being in the “Waiting for clinician state” and enters the “With clinician” state. Dr. Ruiru sends Mona to the lab for tests and then to the pharmacy. At this point, Mona enters the “Waiting for lab” state. A new queue entry is created to indicate that Mona is waiting for the lab.

Etc.

This is just intended as a first pass at this, so thoughts, criticisms, etc. are welcome! Also, if I missed something please let me know!

1 Like

Is this in the confines of the Platform Roadmap ? and can it still feature on the platform call on wednesday ?

I’m assuming this would a specific room in the facility i.e consultation room one or pharmacy?

This set of rules can be;

  1. First come first served (FIFO)
  2. Last come first served (LIFO)
  3. Priority selection rule e.g., a male or female patient first or patient with high BP (140/90) comes first

Intriguing, who determines the rules? Which priority comes first? I’m thinking we could leverage sort_weight from the concept class to distinguish which priority takes precedence if it’s possible. Or perhaps adding a new field sort_weight to Patient Queue Entry.

Different implementations should be able to override default rules with their own rules(in other words we should make rules configurable) or @ibacher what are your intentions?

As with patient lists (cohorts) the initial intention is to do this work in a module and eventually migrate it into core.

That’s the intention. It might be nice if we could indicate that someone was waiting on a particular service rather than just a room (this is why my diagram has a concept_waiting_for field), but it seems as if locations are often used as a proxy for this and it simplifies the implementation a bit…

Thanks @corneliouzbett! Those are some great questions!

My initial hope is that we can initially (at least) simplify queues to just priority queues, where order is determined by patient priority and then wait time. There’s some scope for adding alternative queueing strategies at some point, but neither UgandaEMR (from what I understood) or the 3.x designs seem to have a need for a different strategy just yet. This nice property of these priority queues is that as long as the priority for all patients is the same, they are effectively FIFO queues.

My hope was that by storing priority in the concept dictionary, we can (to a certain extent) leave this decision to implementations. That said, it’s probably good to come up with a sort of baseline OpenMRS reference vocabulary for priorities (unless @akanter this is something that belongs in CIEL?).

There is a difficulty around this, which is just that while sort_weight is a property of concept answers and concept set members, it’s not a property of concepts per se. So, we’d either need to extend the concept model by introducing, e.g., ConceptPriority or a more generic ConceptSortable or maybe adding a ConceptAttribute to capture this.

This certainly doesn’t involve messing with the concept model! My preference is to have each priority define its order rather than adding this to the queue model, mostly because this ensures that the user can tell at a glance why the queue is ordered the way it is (otherwise, you might end up in a state where two patients are marked “urgent” but one comes in the queue before the other despite arriving later because the sort_weight property was set to 3 instead of 4).

This is great work, @ibacher. I am wondering how the system is able to reconstruct the queue after a system restart or power outage, if we are not persisting the queue priority anywhere. Is there anywhere else we persist the priority?

Other design questions…

  • Use cases for the shared services i.e. billing where a patient is put in a lab/xray queue but is required to first pay for the tests. Should they automatically be put in the billing queue? Am not so sure

  • How do we design for a mother-baby pair visit such that both are queued and served at the same time? This may not be common but may need some thinking either at the model, logic, or just the UI level

1 Like

Well, the trick is that we are persisting queue priority since the priority in the queue is defined by wait time and priority and both of these are stored in the database. I.e., the queues themselves are durable; there’s just no “queue” database table, unless it becomes necessary.

This seems to me to be very workflow-dependent. However, I think that all of the problems we are trying to solve with queues (e.g., who is next) probably need to be addressed when dealing with billing. This also seems a good case for ensuring that priority is a property of the queue the patient is in rather than the visit (what would it mean to be an “emergency” patient waiting for billing?).

That said, in the vision I’ve outlined above, both Patient Visit State and Patient Queue Entry are intended to be optional entities. No one needs to track “visit state” if it doesn’t serve a business purpose and likewise no patient queue entry needs to be created if there’s no business reason to track that the patient is waiting. So, e.g., if billing queues are handled through a separate billing program, than the patient can just be put into something like a “at billing” state and the EMR doesn’t really need to track anything more granular than that.

That’s a good question that I don’t really have a good off-hand answer to! We’d need to think about this more in a specific context. E.g., are the mother and baby pair going through the same steps of the workflow? If so, it might make sense that only the mother is actually tracked in the queue. Alternatively, we could have two queue entries which share a queue identifier to make things clear that they are at the same stage of the queue (with some modifications to the UI to actually display this identifier). There are probably other options; those are the ones from the top of my head.

Great stuff @ibacher! Looks like we have finally found a good use for Visit: visit states! :slight_smile:

Do we envision even visit states to be brought in as part of a module for the time being?

As a practical matter, yes. I’d think that adding a new domain to core would need to wait for the 2.6.0 release. Plus, it would be nice to be able to leverage this queueing module in pre-2.6.0 versions.

Is the critical decision point to accept the idea that queues are not a thing as such but are in fact inferred from queue priority entries?

Thanks Ian, this is great work!

Small nits/questions

  1. For Patient Queue Entry, I think patient_state should be patient_visit_state (I know these aren’t official names but just want to clearly distinguish from the program workflow patient state).

  2. There was a previous requirement from OHRI to make it the case that queues for certain visit types prevent multiple entry (i.e. a patient can be only in one queue at any given time). The previous suggestion had been to achieve this at the business logic layer. Do you think think that is still the best place? Should we have Patient Queue Entry Attribute Types to be able to support additional logic?

  3. The FHIR spec often includes a section of “what this is for/not for”. I think we should take the time to explain why we are not using the cohort data module to support. I see parallels between cohort / visit state and cohort member /queue entry. I’m not suggesting that we should use cohorts for this purpose but I think it’s a fair question for someone to ask (especially given that OHRI as already proceeded down this route).

  4. @aojwang brings up good points about billing. To me though, the state of billing is a workflow step. Underlying this question is a more general one of creating conditions before a patient can enter a certain state as well as conditions which enable that patient to leave a current state. My hope is that we start laying down the building blocks that a “Rules Engine” could make use of to determine whether a state transition is possible. That (a Rules Engine) would be the next step and perhaps a target for 2022 for OpenMRS.

Agreed. I’ll update the post to reflect this.

Probably not clear from the diagram, but actually the proposal is to only have up to one Patient Queue Entry per Patient Visit State and (ideally) only one active Patient Visit State per visit at any given time, which means that, by design, a patient should only ever be in a single queue at a time (I guess you could have more than one active visit at a time…).

Sure. There are a few considerations for why I’m not looking at the cohort model per se.

First, a cohort is fundamentally a collection of cohort members. Some cohorts may have a defined order, but that’s not a guaranteed property of cohorts. Patient queues, on the other hand, are primarily an ordered collection of patients.

Second, time is a factor in the ordering of the patient queue. In general, the rule for a patient queue is first come, first served, though patients at a higher priority take precedence over those with a lower priority. Importantly, this means that the queue is not a simple FIFO structure. Implementing this on top of the cohort model would likely be less than ideal. Either we implement things in the easiest way, where the cohort is actually backed by an ordered collection. Doing so means that for every patient in a non-default priority added to the queue, the order for the whole cohort needs to be recalculated. Alternatively, we could implement something that does this order recalculation when the cohort is loaded. Even here, though, we’ll still require the same number of database writes to implement this; they’ll simply be deferred to a point where we can batch them.

Yeah, I suppose it is. The advantage of having something like a Patient Queue object is that we can then attach something like a Patient Queue Definition or Patient Queue Type or whatever that could be used to determine things like the queueing strategy used or could be used to represent queues where we aren’t waiting for a particular resource (or at least a particular resource OpenMRS knows about).

The other critical decision point is whether visit states make sense and whether we’re happy to have queues derived from visit states (so the one thing this model forces people to adopt is that there is at least a visit state every time a patient is in a queue).

Finally, is this creating too much noise in the database schema when we could just, e.g., use a cohort with a cohort type of queue?

I really like this proposal @ibacher and am excited to see it move forward. I think this can have a ton of utility across the board for a lot of different use cases. I think I’d certainly favor this over an approach based on cohorts.

With the queue’s priority being a concept, I’m imagining that concept attributes will be used to define each priority in the details:

  • priority level attribute (to know that ‘urgent’ is higher than ‘routine’);
  • priority colour code attribute (to know that ‘urgent’ is red and that ‘routine’ is yellow);
  • etc.

Is that the idea?

I know that this goes into the details already but we don’t want to have to overconfigure the frontend, it would be good that it could infer what to do by default just from the priorities metadata.

1 Like

Yeah, the idea behind using the concept dictionary to store priorities is that we could easily store:

  • The ordering of different priorities (likely as concept attributes) and other information we might need
  • Translations of different priority levels
  • Different priority hierarchies for different contexts. For instance, if you use the SATS triage scale, the priorities are naturally expressed as Red → Orange → Yellow → Green → Blue, but the SATS levels only really makes sense in an emergency triage context.

@ibacher been away with limited internet access. I really like what I see you describe in this post. I also like the scenarios.

1 Like

Priority hierarchies based on different contexts would work best. I.e from triage ■■■ the priority based on the collected vitals. The priority flag be persisted across all service point queues. For non-severe cases, FIFO can be applied. Referrals from other service points can be flagged in a different color on the queue, for instance, patients from the lab can be flagged in yellow to indicate they have lab results uploaded and ready for the final review and diagnosis.

@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.