CaseStudyForm | |||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Title | Extended Health Clinic | ||||||||||||
DateSubmitted | 25 Sep 2017 | ||||||||||||
CaseStudyType | TeachingCaseStudy | ||||||||||||
OperationsResearchTopics | SimulationModelling | ||||||||||||
ApplicationAreas | Healthcare | ||||||||||||
ProblemDescription |
This case study extends the Simple Health Clinic – Scheduled Appointments model. The extensions are:
| ||||||||||||
ProblemFormulation |
In order to formulate a simulation model we specify the following components:
![]()
![]() | ||||||||||||
ComputationalModel |
Start with the Simple Health Clinic – Scheduled Appointments JaamSim model.
![]() ![]() ![]() 'this.obj.Test = [TestDistribution].Value' } to AttributeAssignmentList.
![]() ![]() ![]() ![]() ![]() ![]() 'this.obj.Test + 1' |
| TriageToDoctor | NextComponent = Doctor, TravelTime = 2 min |
| TriageToTest | NextComponent = NurseTest, TravelTime = 2 min |
| NurseTest | NextComponent = TestToDoctor |
| TestToDoctor | NextComponent = Doctor, TravelTime = 2 min |
Save your simulation.
Now, add Queues for both triage and testing, set these queues as the wait queues for triage and testing. The treatment time is modelled by a triangular distribution with minimum 2 minutes, maximum 15 minutes and mode (most likely) 8 minutes, so add a TriangularDistribution called TriageDistribution with these parameters. The test time is modelled as a constant time of 10 minutes, so add this to the NurseTest's ServiceTime. You need to change the StateAssignment of NurseTriage and NurseTest to reflect these activities too.
| Object | Key Inputs |
| NurseTriage | WaitQueue = TriageQueue, StateAssignment = Triage, ServiceTime = TriageDistribution |
| NurseTest | WaitQueue = TestQueue, StateAssignment = Test, ServiceTime = 10 min |
| TriageDistribution | UnitType = TimeUnit, RandomSeed = 4, MinValue = 2 min, MaxValue = 15 min, Mode = 8 min |
Finally, you should change the StateAssignment of all your queues so that we can track the time spent waiting for various sets in the pathway. You also need to change the DefaultStateList of your patient entities.
| Object | Key Inputs |
| WalkupQueue | StateAssignment = WaitTreat |
| AppointmentQueue | StateAssignment = WaitTreat |
| TriageQueue | StateAssignment = WaitTriage |
| TestQueue | StateAssignment = WaitTest |
| PatientEntity | DefaultStateList = { Arrive WaitTreat WaitTriage WaitTest Treat Triage Test Leave } |
Save your simulation.
At this point you can run your simulation and ensure that your triage/test process does not cause any errors.
However, now that the PatientEntity has new states we need to modify and append our Statistics modules and Simulation output. Change the existing Statistics modules to gather all the time in the system and waiting times and change the outputs generated by the simulation.
| Object | Key Inputs |
| TimeInSystem | SampleValue = 'this.obj.StateTimes("Arrive") + this.obj.StateTimes("WaitTriage") + this.obj.StateTimes("Triage") + this.obj.StateTimes("WaitTest") + this.obj.StateTimes("Test") + this.obj.StateTimes("WaitTreat") + this.obj.StateTimes("Treat") + this.obj.StateTimes("Leave")' |
| WaitingTime | NextComponent = WaitingTriage SampleValue = 'this.obj.StateTimes("WaitTriage") + this.obj.StateTimes("WaitTest") + this.obj.StateTimes("WaitTreat")' |
| TimeInSystem2 | SampleValue = 'this.obj.StateTimes("Arrive") + this.obj.StateTimes("WaitTreat") + this.obj.StateTimes("Treat") + this.obj.StateTimes("Leave")' |
| WaitingTime2 | SampleValue = this.obj.StateTimes("WaitTreat") |
| WaitingTriage (New Statistics component) | UnitType = TimeUnit NextComponent = WaitingTest SampleValue = this.obj.StateTimes("WaitTriage") | | WaitingTest (New Statistics component) | UnitType = TimeUnit NextComponent = WaitingTreat SampleValue = this.obj.StateTimes("WaitTest") | | WaitingTreat (New Statistics component) | UnitType = TimeUnit NextComponent = PatientWalkupSink SampleValue = this.obj.StateTimes("WaitTreat") | | Simulation | UnitTypeList = DimensionlessUnit TimeUnit TimeUnit TimeUnit TimeUnit TimeUnit DimensionlessUnit DimensionlessUnit TimeUnit TimeUnit DimensionlessUnit DimensionlessUnit RunOutputList = { [Simulation].RunIndex(1) } { [WaitingTriage].SampleAverage } { [WaitingTest].SampleAverage } { [WaitingTreat].SampleAverage } { [WaitingTime].SampleAverage } { [TimeInSystem].SampleAverage } { '[TriageQueue].QueueLengthAverage + [TestQueue].QueueLengthAverage + [WalkupQueue].QueueLengthAverage' } { [Doctor].Utilisation } { [WaitingTime2].SampleAverage } { [TimeInSystem2].SampleAverage } { [AppointmentQueue].QueueLengthAverage } { '[Doctor2Appointment].Utilisation + [Doctor2Walkup].Utilisation' } |
Save your simulation.
Run your simulation and use Lab3Analysis.R to look at the results from your simulation with the triage and tests added. The confidence intervals results should look as follows:
| Measure | WalkUp.WaitingTriage | WalkUp.WaitingTest | WalkUp.WaitingTreat | WalkUp.WaitingTime | WalkUp.TimeInSystem | Num.WalkUps.Waiting | Doctor.Utilisation | Appointment.WaitingTime | Appointment.TimeInSystem | Appointment.Queue | Doctor2.Utilisation |
| ConfLower | 0.05326031 | 0.001735700 | 0.1304301 | 0.1865163 | 0.7018218 | 0.5606735 | 0.6132686 | 0.1192807 | 0.4022488 | 0.3524806 | 0.8683465 |
| Mean | 0.05493219 | 0.001873389 | 0.1371880 | 0.1939935 | 0.7099500 | 0.5865928 | 0.6191527 | 0.1215904 | 0.4050244 | 0.3593692 | 0.8709183 |
| ConfUpper | 0.05660406 | 0.002011078 | 0.1439458 | 0.2014707 | 0.7180781 | 0.6125121 | 0.6250368 | 0.1239000 | 0.4078001 | 0.3662577 | 0.8734901 |
By contrast, the final results from SHC with Scheduled Appointments are:
| RunNumber | WalkUp.WaitingTime | WalkUp.TimeInSystem | WalkUp.Queue | Doctor.Utilisation | Appointment.WaitingTime | Appointment.TimeInSystem | Appointment.Queue | Doctor2.Utilisation |
| 4 | 0.1592253 | 0.4426042 | 0.4840191 | 0.6174519 | 0.12180412 | 0.4048019 | 0.3623689 | 0.8726559 |
| RunNumber | Quantity | WalkUp.WaitingTime | WalkUp.TimeInSystem | WalkUp.Queue | Doctor.Utilisation | Appointment.WaitingTime | Appointment.TimeInSystem | Appointment.Queue | Doctor2.Utilisation |
| 4 | .1 | 0.1521349 | 0.4352098 | 0.4599628 | 0.6113834 | 0.11939828 | 0.4019517 | 0.3552248 | 0.8703043 |
| | .2 | 0.1663157 | 0.4499985 | 0.5080754 | 0.6235204 | 0.12420996 | 0.4076520 | 0.369513 | 0.8750076 |
Hence, the walk-up patients are worse off because of the extras steps in their pathways, but the appointment patients are a little better off with the triage and testing process in place. This make sense as the flow of walk-up patients to the doctors has been "stretched" by the triage and testing process, so less walk-up patients will be able to take advantage of any gaps in the appointment schedule and less appointment patients will get delayed by walk-up patients utilising the doctor for appointment patients.
Next, we add some uncertainty to the arrival time of appointment patients by adding a little "noise" to their arrival. Create a NormalDistribution object called AppointmentDistribution with mean 0 minutes, standard deviation 5 minutes and minimum value -15 minutes (this will "truncate" the negative tail of the distribution. Add the value from this distribution to the AppointmentTimes value in the PatientAppointment EntityGenerator.
| Object | Key Inputs |
| AppointmentDistribution | UnitType = TimeUnit RandomSeed = 5 MinValue = -15 min Mean = 0 min StandardDeviation = 5 min | | PatientAppointment | FirstArrivalTime = '[AppointmentTimes].Value + [AppointmentDistribution].Value' InterArrivalTime = '[AppointmentTimes].Value + [AppointmentDistribution].Value' |
Save your simulation.
Re-run your simulation and see what effect the appointment time "noise" has. Here is the doctor utilisation and appointment patients' statistics:
| * Measure* | Doctor.Utilisation | Appointment.WaitingTime | Appointment.TimeInSystem | Appointment.Queue | Doctor2.Utilisation |
| ConfLower | 0.6166581 | 0.1414877 | 0.4241283 | 0.4174881 | 0.8646724 |
| Mean | 0.6225270 | 0.1446245 | 0.4277147 | 0.4270835 | 0.8675022 |
| ConfUpper | 0.6283959 | 0.1477613 | 0.4313011 | 0.4366789 | 0.8703320 |
Finally, we add some non-stationarity to the arrival rate of the walk-up patients. Add a TimeSeries object call WalkupRates to define the changing arrival rates of walk-up patients. Suppose we want an arrival process with exponentially distributed interarrival times with mean 1 hour between midnight and 8 a.m. (8 arrivals expected), mean 15 mins between 8 a.m. and 5 p.m. (36 arrivals expected), and mean 30 mins between 5 p.m. and midnight (14 arrivals expected). We would edit WalkupRates to define the number that arrived over time as follows:
| Object | Key Inputs |
| WalkupRates | UnitType = DimensionlessUnit Value = {0 h 0} {8 h 8} {17 h 44} {24 h 58} CycleTime = 24 h | This means that at 0 hrs, 0 have arrived, at 8 hrs 8 have arrived, at 17 hrs 44 ( 8 + 36) have arrived, and at 24 hrs 58 ( 8 + 36 + 14) have arrived.
Add a NonStatExponentialDistribution called WalkupDistribution to your model that uses WalkupRates to generate arrivals:
| Object | Key Inputs |
| WalkupDistribution | ExpectedArrivals = WalkupRates |
Lastly, use WalkupDistribution to define the first and inter arrivals times in the PatientWalkup EntityGenerator.
| Object | Key Inputs |
| PatientWalkup | FirstArrivalTime = '([Simulation].RunIndex(1) == 1) || ([Simulation].RunIndex(1) == 3) ? 20[min] : [WalkupDistribution].Value' InterArrivalTime = '([Simulation].RunIndex(1) == 1) || ([Simulation].RunIndex(1) == 3) ? 20[min] : [WalkupDistribution].Value' |
Save your simulation.
| ||||||||||||
Results |
As in SHC with Scheduled Appointments, perform 100 runs of this simulation each 168 hours long and with 24 hours warm up to examine the effect of the changes to the health clinic and observe the difference in average time in system and queue length for the walk up and appointment patients, and the difference in utilisation of the walk up only doctor and the appointment and walk up doctor. No changes to the Simulation object’s Key Inputs other than those described earlier in this case study are needed. The Simulation runs are as follows:
| Object | Multiple Runs | Value |
| Simulation | RunIndexDefinitionList | 4 100 |
| | StartingRunNumber | 4-1 |
| | EndingRunNumber | 4-100 |
Run your simulation and modify Lab3Analysis.R to use your new .dat file. Your results should be as below.
WalkUp.WaitingTriage WalkUp.WaitingTest WalkUp.WaitingTreat ConfLower 0.06360199 0.001792959 0.1694404 Mean 0.06682758 0.001953419 0.1824697 ConfUpper 0.07005316 0.002113880 0.1954990 WalkUp.WaitingTime WalkUp.TimeInSystem Num.WalkUps.Waiting ConfLower 0.2362326 0.7518122 0.5720433 Mean 0.2512507 0.7671848 0.6122041 ConfUpper 0.2662688 0.7825573 0.6523649 Doctor.Utilisation Appointment.WaitingTime Appointment.TimeInSystem ConfLower 0.4941766 0.1261258 0.4090825 Mean 0.4993517 0.1292271 0.4126293 ConfUpper 0.5045268 0.1323284 0.4161760 Appointment.Queue Doctor2.Utilisation ConfLower 0.3721434 0.8395105 Mean 0.3817525 0.8425352 ConfUpper 0.3913617 0.8455598
ValueTraceList = { '[TriageQueue].QueueLength + [TestQueue].QueueLength + [WalkupQueue].QueueLength + [AppointmentQueue].QueueLength' } |
Now adapt parse_multiple_runs_log.R to draw the number in the waiting room for the first 10 replications (note that parse_single_run_log.R is also attached for your reference). The plot should look like:
![]() | ||||||||||||
Conclusions | In this case study, you extended the SHC with Scheduled Appointments models to a triage/testing process, "noise" on appointment patient arrivals, and non-stationary arrival rates for walk-up patients. You investigated the effect of the changes as well as plotting the number in the waiting room over time to see the effect of non-stationarity. Given the imbalance between the utilisation of the doctors, how should the two doctors be rostered to ensure their workload is equitable? | ||||||||||||
ExtraForExperts | |||||||||||||
StudentTasks |
I | Attachment | History | Action | Size | Date | Who | Comment |
---|---|---|---|---|---|---|---|
![]() |
Lab3Analysis.R | r4 r3 r2 r1 | manage | 1.7 K | 2017-10-01 - 22:19 | MichaelOSullivan | |
![]() |
nurse.png | r1 | manage | 5.1 K | 2017-09-28 - 02:44 | MichaelOSullivan | |
![]() |
parse_multiple_runs_log.R | r1 | manage | 2.1 K | 2017-09-29 - 02:38 | MichaelOSullivan | |
![]() |
parse_single_run_log.R | r1 | manage | 2.0 K | 2017-09-29 - 02:38 | MichaelOSullivan |