-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtech_support_sim.py
More file actions
202 lines (169 loc) · 9.3 KB
/
Copy pathtech_support_sim.py
File metadata and controls
202 lines (169 loc) · 9.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
import simpy
import random
import numpy as np
from collections import defaultdict
import argparse
import sys
import matplotlib.pyplot as plt
class TechSupportQueue:
def __init__(self, env, num_agents, avg_call_duration=10, call_duration_std=3):
self.env = env
self.agents = simpy.PriorityResource(env, num_agents)
self.avg_call_duration = avg_call_duration
self.call_duration_std = call_duration_std
# Metrics tracking
self.customers_served = 0
self.total_wait_time = 0
self.total_service_time = 0
self.wait_times = []
self.service_times = []
self.queue_lengths = []
self.abandonments = 0
def serve_customer(self, customer_id, priority=0, patience=30):
"""Process a customer call"""
arrival_time = self.env.now
print(f"Customer {customer_id} calls at time {arrival_time:.1f}")
# Track queue length
queue_length = len(self.agents.queue) + len(self.agents.users)
self.queue_lengths.append((self.env.now, queue_length))
# Customer tries to get an agent or abandons if wait is too long
with self.agents.request(priority=priority) as request:
# Race between getting service and running out of patience
patience_timeout = self.env.timeout(patience)
result = yield request | patience_timeout
if request in result:
# Customer got an agent
wait_time = self.env.now - arrival_time
self.wait_times.append(wait_time)
self.total_wait_time += wait_time
print(f" Customer {customer_id} connected to agent at time {self.env.now:.1f} (waited {wait_time:.1f} min)")
# Service time (normally distributed)
service_time = max(1, np.random.normal(self.avg_call_duration, self.call_duration_std))
self.service_times.append(service_time)
self.total_service_time += service_time
yield self.env.timeout(service_time)
print(f" Customer {customer_id} call completed at time {self.env.now:.1f} (service time: {service_time:.1f} min)")
self.customers_served += 1
else:
# Customer abandoned (ran out of patience)
print(f" Customer {customer_id} abandoned at time {self.env.now:.1f} (waited {patience:.1f} min)")
self.abandonments += 1
def customer_generator(env, support_queue, arrival_rate=0.5):
"""Generate customers calling the support line"""
customer_id = 0
while True:
# Time until next customer (exponential distribution)
inter_arrival_time = random.expovariate(arrival_rate)
yield env.timeout(inter_arrival_time)
customer_id += 1
# Customer characteristics
priority = 0 # Could be 1 for premium customers
patience = random.uniform(15, 45) # How long they'll wait (minutes)
# Start serving the customer
env.process(support_queue.serve_customer(customer_id, priority, patience))
def priority_customer_generator(env, support_queue, arrival_rate=0.1):
"""Generate priority customers (premium support)"""
customer_id = 1000 # Start with high numbers to distinguish
while True:
inter_arrival_time = random.expovariate(arrival_rate)
yield env.timeout(inter_arrival_time)
customer_id += 1
priority = 1 # Higher priority
patience = random.uniform(20, 60) # Premium customers wait longer
print(f"PRIORITY Customer {customer_id} calls")
env.process(support_queue.serve_customer(customer_id, priority, patience))
def queue_monitor(env, support_queue, interval=5):
"""Monitor and log queue statistics periodically"""
while True:
yield env.timeout(interval)
queue_length = len(support_queue.agents.queue)
busy_agents = len(support_queue.agents.users)
print(f"[{env.now:.1f}] Queue: {queue_length}, Busy agents: {busy_agents}")
def run_simulation(sim_duration=480, num_agents=5, arrival_rate=0.8, avg_call_duration=8):
"""Run the tech support simulation"""
print(f"=== Tech Support Queue Simulation ===")
print(f"Simulation duration: {sim_duration} minutes ({sim_duration/60:.1f} hours)")
print(f"Number of agents: {num_agents}")
print(f"Customer arrival rate: {arrival_rate:.2f} customers/minute")
print(f"Average call duration: {avg_call_duration} minutes")
print()
# Create simulation environment
env = simpy.Environment()
# Create support queue
support_queue = TechSupportQueue(env, num_agents, avg_call_duration)
# Start processes
env.process(customer_generator(env, support_queue, arrival_rate))
env.process(priority_customer_generator(env, support_queue, 0.1))
env.process(queue_monitor(env, support_queue, 30))
# Run simulation
env.run(until=sim_duration)
# Calculate and display results
print(f"\n=== Simulation Results ===")
print(f"Total customers served: {support_queue.customers_served}")
print(f"Total abandonments: {support_queue.abandonments}")
if support_queue.customers_served > 0:
avg_wait = support_queue.total_wait_time / support_queue.customers_served
avg_service = support_queue.total_service_time / support_queue.customers_served
print(f"Average wait time: {avg_wait:.2f} minutes")
print(f"Average service time: {avg_service:.2f} minutes")
if support_queue.wait_times:
print(f"Max wait time: {max(support_queue.wait_times):.2f} minutes")
print(f"95th percentile wait time: {np.percentile(support_queue.wait_times, 95):.2f} minutes")
total_customers = support_queue.customers_served + support_queue.abandonments
if total_customers > 0:
abandonment_rate = support_queue.abandonments / total_customers * 100
print(f"Abandonment rate: {abandonment_rate:.1f}%")
# Agent utilization
total_service_time = sum(support_queue.service_times) if support_queue.service_times else 0
total_agent_capacity = num_agents * sim_duration
utilization = total_service_time / total_agent_capacity * 100
print(f"Agent utilization: {utilization:.1f}%")
return support_queue
# Example usage and scenarios
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Tech Support Queue Simulation")
parser.add_argument('--sim_duration', type=int, default=None, help='Simulation duration in minutes')
parser.add_argument('--num_agents', type=int, default=None, help='Number of support agents')
parser.add_argument('--arrival_rate', type=float, default=None, help='Customer arrival rate (customers per minute)')
parser.add_argument('--avg_call_duration', type=float, default=None, help='Average call duration (minutes)')
parser.add_argument('--plot', action='store_true', help='Show queue length chart after simulation')
parser.add_argument('--save_plot', type=str, default=None, help='Save queue length chart to a PNG file (provide filename)')
args = parser.parse_args()
# If any simulation parameter is provided, run a single scenario with those parameters
if any([args.sim_duration, args.num_agents, args.arrival_rate, args.avg_call_duration]):
sim_duration = args.sim_duration if args.sim_duration is not None else 480
num_agents = args.num_agents if args.num_agents is not None else 5
arrival_rate = args.arrival_rate if args.arrival_rate is not None else 0.8
avg_call_duration = args.avg_call_duration if args.avg_call_duration is not None else 8
print("SCENARIO: Custom Parameters from Command Line")
support_queue = run_simulation(sim_duration=sim_duration, num_agents=num_agents, arrival_rate=arrival_rate, avg_call_duration=avg_call_duration)
if args.plot or args.save_plot:
if support_queue.queue_lengths:
times, lengths = zip(*support_queue.queue_lengths)
plt.figure(figsize=(10, 5))
plt.step(times, lengths, where='post')
plt.xlabel('Time (minutes)')
plt.ylabel('Queue Length')
plt.title('Queue Length Over Time')
plt.grid(True)
plt.tight_layout()
if args.save_plot:
plt.savefig(args.save_plot)
print(f"Queue length chart saved to {args.save_plot}")
if args.plot:
plt.show()
plt.close()
else:
print("No queue data to plot.")
else:
# Scenario 1: Normal operations
print("SCENARIO 1: Normal Operations")
run_simulation(sim_duration=480, num_agents=5, arrival_rate=0.6, avg_call_duration=8)
print("\n" + "="*60 + "\n")
# Scenario 2: High traffic period
print("SCENARIO 2: High Traffic Period")
run_simulation(sim_duration=240, num_agents=5, arrival_rate=1.2, avg_call_duration=8)
print("\n" + "="*60 + "\n")
# Scenario 3: Understaffed
print("SCENARIO 3: Understaffed")
run_simulation(sim_duration=240, num_agents=3, arrival_rate=0.8, avg_call_duration=10)