The Simio BBQ Smoke Pit is an up-and-coming local restaurant. It features a variety of BBQ smoked meats and classic sides. This makeshift cookout is nestled in an older, renovated building in a bustling downtown block. Due to size constraints, this is a carry-out only establishment. However, as Simio BBQ Smoke Pit continues to grow in popularity, customers are facing longer wait times due to product outages and a constrained labor force. To keep up with demand, the restaurant needs help determining what new policies to enact to resolve their service bottlenecks.
Due to the long smoking time required for the BBQ meats and lengthy cook time for some sides, the restaurant is struggling to keep a reasonable level of cooked food available. Having too little inventory will affect customer satisfaction. If food shortages occur too often, wait times could increase, causing customers to leave and losing potential business for the restaurant. On the other hand, creating excess inventory could accrue extra costs. The meat is expensive and excess uneaten food must be disposed of at the end of the day.
The challenge is to balance staffing and food production to maximize profits and customer satisfaction. The restaurant is looking to investigate customer arrivals and ordering patterns, resource requirements, and food production rates. Simio BBQ Smoke Pit desires the best strategy to replenish each of the different food items on their menu. This replenishment strategy not only affects when and how much food to cook, but also how to allocate the cooked portions between the meal assembly stations and the holding cabinets. Additionally, Simio BBQ would like to know whether improving staffing levels or adding equipment would be worth the investment.
Identify:
Service bottlenecks in the current system
Staffing issues. Is any work shift or service station causing delays? The base model is available at BBQDemo1.zip
For this exercise, look for customized objects in the galleries available to make the scenario as real as possible. Change the current images and layout of the model to represent new or modified processes and items.
This assignment will test your understanding of a process based on a given scenario and data. I will evaluate how you modify the existing scenario to make it more functional. Analyze how to apply techniques we studied in class, like assigning a higher workload to servers, assigning data values to the processing time of entities, and importing data into the model. As well, how you manage workers and orders.
The exercise does not have a unique solution. I’ll evaluate how you interpret the process and suggest changes to it. Be creative and have fun!
library(readxl)
library(dplyr)
library(lubridate)
library(simmer)
library(simmer.plot)
library(ggplot2)
library(lubridate)
We begin with importing customer arrival data and staff schedule.
bbq <- read_excel("/Users/ulianaplotnikova/Desktop/Data 604/Customer_Arrivals.xlsx")
head(bbq)
## # A tibble: 6 × 5
## Time OrderID ItemID MenuItem `Side 1`
## <dttm> <dbl> <dbl> <chr> <chr>
## 1 2024-11-22 10:11:47 1 1 Small Platter - Ribs Green Beans
## 2 2024-11-22 10:11:47 1 2 Sandwich - Pulled Pork Fries
## 3 2024-11-22 10:15:26 2 1 Sandwich - Brisket Fries
## 4 2024-11-22 10:15:26 2 2 Small Platter - Pulled Pork Baked Beans
## 5 2024-11-22 10:19:40 3 1 Sandwich - Pulled Pork Fries
## 6 2024-11-22 10:19:40 3 2 Sandwich - Pulled Pork Fries
staff <- read_excel("/Users/ulianaplotnikova/Desktop/Data 604/Work_Schedule.xlsx")
head(staff)
## # A tibble: 6 × 4
## `Worker Name` Position `Start of Shift` `End of Shift`
## <chr> <chr> <chr> <chr>
## 1 Albert Food Production 9:00am 4:30pm
## 2 Bernise Food Production 9:00am 4:30pm
## 3 Chad Food Production 9:00am 4:30pm
## 4 Duncan FoodProduction 4:30pm 11:30pm
## 5 Emilio FoodProduction 4:30pm 11:30pm
## 6 Felicity FoodProduction 4:30pm 11:30pm
Convert timestamps into dataframe format and calculate interarrival time (determine how frequently customer arrive)
bbq <- bbq %>%
mutate(
time = ymd_hms(`Time`)
) %>%
arrange(time)
cat("\nParsed times (first 5):\n")
##
## Parsed times (first 5):
print(head(bbq$time))
## [1] "2024-11-22 10:11:47 UTC" "2024-11-22 10:11:47 UTC"
## [3] "2024-11-22 10:15:26 UTC" "2024-11-22 10:15:26 UTC"
## [5] "2024-11-22 10:19:40 UTC" "2024-11-22 10:19:40 UTC"
bbq <- bbq %>%
mutate(
iat = c(2, as.numeric(diff(time), units = "mins")) # first arrival: 2 min
)
cat("\nInterarrival summary (minutes):\n")
##
## Interarrival summary (minutes):
print(summary(bbq$iat))
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 0.0000 0.0000 0.0000 0.8112 0.9833 25.9000
The pattern of customer arrivals reflects a bursty arrival process: Minimum arrival time: 0 min - customers arrive back to back Median: 0.81 min - high frequency arrival Maximum: 25 min - occasional arrival, slow periods
Since the Excel file does not contain service times, we create reasonable distributions for: - order taking (cashier) - meat station (BBQ) - sides station
set.seed(123)
bbq <- bbq %>%
mutate(
svc_order = runif(n(), 2, 4), # 2–4 minutes
svc_meat = pmax(rnorm(n(), 8, 2), 3), # Normal(8,2), min 3 minutes
svc_sides = runif(n(), 1, 3) # 1–3 minutes
)
In this step we define helper function to sample service times during simulation.
sample_fun <- function(x) {
force(x)
function() sample(x, size = 1, replace = TRUE)
}
iat_fun <- sample_fun(bbq$iat)
order_fun <- sample_fun(bbq$svc_order)
meat_fun <- sample_fun(bbq$svc_meat)
sides_fun <- sample_fun(bbq$svc_sides)
Identify how many workers are available at each workstation.
num_food <- sum(staff$Position == "Food Production")
num_cust <- sum(staff$Position == "Customer Service")
cat("\nNumber of workers (from schedule):\n")
##
## Number of workers (from schedule):
cat("Food production:", num_food, "\n")
## Food production: 3
cat("Customer service:", num_cust, "\n\n")
## Customer service: 2
Each customer follows the same sequence of steps: 1. Place order at the order station (Customer Service). 2. Receive BBQ meat at the meat station (Food Production). 3. Receive side dishes at the sides station.
cust_traj <- trajectory("customer path") %>%
seize("order", 1) %>%
timeout(order_fun) %>%
release("order", 1) %>%
seize("meat", 1) %>%
timeout(meat_fun) %>%
release("meat", 1) %>%
seize("sides", 1) %>%
timeout(sides_fun) %>%
release("sides", 1)
We simulate a full operating day from 9:00 to 23:00.
sim_time <- 14 * 60
env_base <- simmer("BBQ_base") %>%
add_resource("order", capacity = num_cust) %>%
add_resource("meat", capacity = num_food) %>%
add_resource("sides", capacity = num_food) %>%
add_generator("cust", cust_traj, iat_fun) %>%
run(until = sim_time)
env_base
## simmer environment: BBQ_base | now: 840 | next: 840.745407187726
## { Monitor: in memory }
## { Resource: order | monitored: TRUE | server status: 2(2) | queue status: 385(Inf) }
## { Resource: meat | monitored: TRUE | server status: 3(3) | queue status: 245(Inf) }
## { Resource: sides | monitored: TRUE | server status: 0(3) | queue status: 0(Inf) }
## { Source: cust | monitored: 1 | n_generated: 954 }
res_base <- get_mon_resources(env_base) %>%
group_by(resource) %>%
summarise(
mean_util = mean(server/pmax(capacity,1)),
mean_queue = mean(queue),
max_queue = max(queue),
.groups = "drop"
)
cat("BASE SCENARIO – RESOURCE STATS:\n")
## BASE SCENARIO – RESOURCE STATS:
print(res_base)
## # A tibble: 3 × 4
## resource mean_util mean_queue max_queue
## <chr> <dbl> <dbl> <int>
## 1 meat 0.999 119. 245
## 2 order 1.00 213. 393
## 3 sides 0.335 0 0
Interpretation:
head(get_mon_arrivals(env_base))
## name start_time end_time activity_time finished replication
## 1 cust0 0.5666667 13.52178 12.95511 TRUE 1
## 2 cust1 0.5666667 15.42013 14.85347 TRUE 1
## 3 cust2 0.5666667 16.53766 12.20496 TRUE 1
## 4 cust4 1.2666667 24.05965 12.78500 TRUE 1
## 5 cust5 1.2666667 24.14213 12.91124 TRUE 1
## 6 cust3 0.5666667 24.67668 14.57692 TRUE 1
arr_base_raw<-get_mon_arrivals(env_base)
head(arr_base_raw)
## name start_time end_time activity_time finished replication
## 1 cust0 0.5666667 13.52178 12.95511 TRUE 1
## 2 cust1 0.5666667 15.42013 14.85347 TRUE 1
## 3 cust2 0.5666667 16.53766 12.20496 TRUE 1
## 4 cust4 1.2666667 24.05965 12.78500 TRUE 1
## 5 cust5 1.2666667 24.14213 12.91124 TRUE 1
## 6 cust3 0.5666667 24.67668 14.57692 TRUE 1
arr_base <- arr_base_raw %>%
mutate(
total_time = end_time - start_time,
waiting_time = total_time - activity_time
) %>%
summarise(
mean_wait = mean(waiting_time),
mean_total = mean(total_time)
)
cat("\nBASE SCENARIO – CUSTOMER STATS (minutes):\n")
##
## BASE SCENARIO – CUSTOMER STATS (minutes):
print(arr_base)
## mean_wait mean_total
## 1 295.9712 308.7811
The base scenario leads to very long average waiting and total times, confirming that the system is overloaded under current staffing levels.
In the first improvement scenario, we add one additional Food Production worker at the meat station while keeping the order station unchanged.
env_meat_plus <- simmer("BBQ_meat_plus") %>%
add_resource("order", capacity = num_cust) %>%
add_resource("meat", capacity = num_food + 1) %>% # <-- one extra worker
add_resource("sides", capacity = num_food) %>%
add_generator("cust", cust_traj, iat_fun) %>%
run(until = sim_time)
## Resource statistics for the improvement scenario
res_meat_plus <- get_mon_resources(env_meat_plus) %>%
group_by(resource) %>%
summarise(
mean_util = mean(server / pmax(capacity, 1)),
mean_queue = mean(queue),
max_queue = max(queue),
.groups = "drop"
)
cat("\nEXTRA MEAT WORKER – RESOURCE STATS:\n")
##
## EXTRA MEAT WORKER – RESOURCE STATS:
print(res_meat_plus)
## # A tibble: 3 × 4
## resource mean_util mean_queue max_queue
## <chr> <dbl> <dbl> <int>
## 1 meat 0.998 71.4 139
## 2 order 1.00 300. 593
## 3 sides 0.417 0.0108 1
Adding a worker in Meat Production reduced queue slightly but doesn’t address the overloaded Order station. Wait time is still high.
arr_meat_plus_raw <- get_mon_arrivals(env_meat_plus)
arr_meat_plus <- arr_meat_plus_raw %>%
mutate(
total_time = end_time - start_time,
waiting_time = total_time - activity_time
) %>%
summarise(
mean_wait = mean(waiting_time),
mean_total = mean(total_time)
)
cat("\nEXTRA MEAT WORKER – CUSTOMER STATS (minutes):\n")
##
## EXTRA MEAT WORKER – CUSTOMER STATS (minutes):
print(arr_meat_plus)
## mean_wait mean_total
## 1 280.3354 293.3103
scenario_comp <- bind_rows(
Base = arr_base,
Extra_Meat_Worker = arr_meat_plus,
.id = "Scenario"
)
cat("\nCOMPARISON – MEAN WAIT & TOTAL TIME (minutes):\n")
##
## COMPARISON – MEAN WAIT & TOTAL TIME (minutes):
print(scenario_comp)
## Scenario mean_wait mean_total
## 1 Base 295.9712 308.7811
## 2 Extra_Meat_Worker 280.3354 293.3103
Interpretation
Adding one extra meat worker reduces queues at the meat station, but the order station remains highly utilized and still creates long customer delays. This shows that changing only one bottleneck is not enough.
env_meat_plus2 <- simmer("BBQ_meat_plus") %>%
add_resource("order", capacity = num_cust+1) %>% # <-- one extra order worker
add_resource("meat", capacity = num_food + 2) %>% # <-- extra worker
add_resource("sides", capacity = num_food ) %>%
add_generator("cust", cust_traj, iat_fun) %>%
run(until = sim_time)
## Resource statistics for the improvement scenario
res_meat_plus2 <- get_mon_resources(env_meat_plus2) %>%
group_by(resource) %>%
summarise(
mean_util = mean(server / pmax(capacity, 1)),
mean_queue = mean(queue),
max_queue = max(queue),
.groups = "drop"
)
cat("\nEXTRA MEAT WORKER – RESOURCE STATS:\n")
##
## EXTRA MEAT WORKER – RESOURCE STATS:
print(res_meat_plus2)
## # A tibble: 3 × 4
## resource mean_util mean_queue max_queue
## <chr> <dbl> <dbl> <int>
## 1 meat 0.998 137. 288
## 2 order 0.996 96.1 196
## 3 sides 0.494 0.0280 2
arr_meat_plus_raw <- get_mon_arrivals(env_meat_plus2)
arr_meat_plus2 <- arr_meat_plus_raw %>%
mutate(
total_time = end_time - start_time,
waiting_time = total_time - activity_time
) %>%
summarise(
mean_wait = mean(waiting_time),
mean_total = mean(total_time)
)
cat("\nEXTRA MEAT WORKER – CUSTOMER STATS (minutes):\n")
##
## EXTRA MEAT WORKER – CUSTOMER STATS (minutes):
print(arr_meat_plus)
## mean_wait mean_total
## 1 280.3354 293.3103
scenario_comp <- bind_rows(
Base = arr_base,
Two_Extra_Meat_Worker_and_Extra_Order = arr_meat_plus2,
.id = "Scenario"
)
cat("\nCOMPARISON – MEAN WAIT & TOTAL TIME (minutes):\n")
##
## COMPARISON – MEAN WAIT & TOTAL TIME (minutes):
print(scenario_comp)
## Scenario mean_wait mean_total
## 1 Base 295.9712 308.7811
## 2 Two_Extra_Meat_Worker_and_Extra_Order 195.4306 208.4159
Interpretation
In the final improvement scenario, I increased staffing by adding two additional Meat Production workers and one additional Order Worker.
Under this changes, the mean waiting time decreased from 296 min in thebase scenario to about 194 min, which is approximately 34% of reduction. The mean total time in bthe system also improved dropping from 309 to 207 minutes, which is around 33% of reduction.
In this project, I applied several key concepts from the course:
Importing real data for customer arrivals and staff schedules, rather than using purely theoretical distributions.
Defining interarrival times and service-time distributions to model realistic variability in the system.
Using simmer to build discrete-event simulation
models with resources, queues, and trajectories.
Assigning workloads to different servers and changing capacities to test the effect of staffing policies.
Comparing multiple scenarios based on utilization, queue lengths, and customer time in the system.
These steps mirror the general approach to modeling service systems and evaluating process improvements.
Question I tried to answer:
1. Where are the service bottlenecks in the current system?
The simulation shows that the order and meat stations are the main bottlenecks. They operate at nearly 100% utilization and accumulate long queues, while the sides station remains underutilized.
2. Are staffing or shift issues causing delays?
Yes. With only the current number of workers, the system simply does not have enough capacity at order and meat stations to handle the observed arrival rate. This leads directly to long customer waiting time and long total time in the system.
3. What happens when staffing is changed?
Adding only one meat worker reduces queues at the meat station but leaves the order station overloaded, so the overall improvement in customer time is limited. The second scenario with two extra meat workers and one extra order worker reduces utilization and queues at both key stations, leading to a substantial reduction in mean wait and total time.
4. Is it worth adding staff? From the customer perspective, yes: the coordinated staffing increase improves service levels considerably. However, from a managerial perspective, the restaurant would need to weigh the labor cost of extra workers against the benefit of shorter waits, higher throughput, and better customer satisfaction.