Skip to content

Commit 98819e2

Browse files
committed
Add Call Center solution
1 parent 7eb6940 commit 98819e2

File tree

3 files changed

+323
-0
lines changed

3 files changed

+323
-0
lines changed

solutions/object_oriented_design/call_center/__init__.py

Whitespace-only changes.
Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
{
2+
"cells": [
3+
{
4+
"cell_type": "markdown",
5+
"metadata": {},
6+
"source": [
7+
"This notebook was prepared by [Donne Martin](https://github.com/donnemartin). Source and license info is on [GitHub](https://github.com/donnemartin/system-design-primer-primer)."
8+
]
9+
},
10+
{
11+
"cell_type": "markdown",
12+
"metadata": {},
13+
"source": [
14+
"# Design a call center"
15+
]
16+
},
17+
{
18+
"cell_type": "markdown",
19+
"metadata": {},
20+
"source": [
21+
"## Constraints and assumptions\n",
22+
"\n",
23+
"* What levels of employees are in the call center?\n",
24+
" * Operator, supervisor, director\n",
25+
"* Can we assume operators always get the initial calls?\n",
26+
" * Yes\n",
27+
"* If there is no free operators or the operator can't handle the call, does the call go to the supervisors?\n",
28+
" * Yes\n",
29+
"* If there is no free supervisors or the supervisor can't handle the call, does the call go to the directors?\n",
30+
" * Yes\n",
31+
"* Can we assume the directors can handle all calls?\n",
32+
" * Yes\n",
33+
"* What happens if nobody can answer the call?\n",
34+
" * It gets queued\n",
35+
"* Do we need to handle 'VIP' calls where we put someone to the front of the line?\n",
36+
" * No\n",
37+
"* Can we assume inputs are valid or do we have to validate them?\n",
38+
" * Assume they're valid"
39+
]
40+
},
41+
{
42+
"cell_type": "markdown",
43+
"metadata": {},
44+
"source": [
45+
"## Solution"
46+
]
47+
},
48+
{
49+
"cell_type": "code",
50+
"execution_count": 1,
51+
"metadata": {
52+
"collapsed": false
53+
},
54+
"outputs": [
55+
{
56+
"name": "stdout",
57+
"output_type": "stream",
58+
"text": [
59+
"Overwriting call_center.py\n"
60+
]
61+
}
62+
],
63+
"source": [
64+
"%%writefile call_center.py\n",
65+
"from abc import ABCMeta, abstractmethod\n",
66+
"from collections import deque\n",
67+
"from enum import Enum\n",
68+
"\n",
69+
"\n",
70+
"class Rank(Enum):\n",
71+
"\n",
72+
" OPERATOR = 0\n",
73+
" SUPERVISOR = 1\n",
74+
" DIRECTOR = 2\n",
75+
"\n",
76+
"\n",
77+
"class Employee(metaclass=ABCMeta):\n",
78+
"\n",
79+
" def __init__(self, employee_id, name, rank, call_center):\n",
80+
" self.employee_id = employee_id\n",
81+
" self.name = name\n",
82+
" self.rank = rank\n",
83+
" self.call = None\n",
84+
" self.call_center = call_center\n",
85+
"\n",
86+
" def take_call(self, call):\n",
87+
" \"\"\"Assume the employee will always successfully take the call.\"\"\"\n",
88+
" self.call = call\n",
89+
" self.call.employee = self\n",
90+
" self.call.state = CallState.IN_PROGRESS\n",
91+
"\n",
92+
" def complete_call(self):\n",
93+
" self.call.state = CallState.COMPLETE\n",
94+
" self.call_center.notify_call_completed(self.call)\n",
95+
"\n",
96+
" @abstractmethod\n",
97+
" def escalate_call(self):\n",
98+
" pass\n",
99+
"\n",
100+
" def _escalate_call(self):\n",
101+
" self.call.state = CallState.READY\n",
102+
" call = self.call\n",
103+
" self.call = None\n",
104+
" self.call_center.notify_call_escalated(call)\n",
105+
"\n",
106+
"\n",
107+
"class Operator(Employee):\n",
108+
"\n",
109+
" def __init__(self, employee_id, name):\n",
110+
" super(Operator, self).__init__(employee_id, name, Rank.OPERATOR)\n",
111+
"\n",
112+
" def escalate_call(self):\n",
113+
" self.call.level = Rank.SUPERVISOR\n",
114+
" self._escalate_call()\n",
115+
"\n",
116+
"\n",
117+
"class Supervisor(Employee):\n",
118+
"\n",
119+
" def __init__(self, employee_id, name):\n",
120+
" super(Operator, self).__init__(employee_id, name, Rank.SUPERVISOR)\n",
121+
"\n",
122+
" def escalate_call(self):\n",
123+
" self.call.level = Rank.DIRECTOR\n",
124+
" self._escalate_call()\n",
125+
"\n",
126+
"\n",
127+
"class Director(Employee):\n",
128+
"\n",
129+
" def __init__(self, employee_id, name):\n",
130+
" super(Operator, self).__init__(employee_id, name, Rank.DIRECTOR)\n",
131+
"\n",
132+
" def escalate_call(self):\n",
133+
" raise NotImplemented('Directors must be able to handle any call')\n",
134+
"\n",
135+
"\n",
136+
"class CallState(Enum):\n",
137+
"\n",
138+
" READY = 0\n",
139+
" IN_PROGRESS = 1\n",
140+
" COMPLETE = 2\n",
141+
"\n",
142+
"\n",
143+
"class Call(object):\n",
144+
"\n",
145+
" def __init__(self, rank):\n",
146+
" self.state = CallState.READY\n",
147+
" self.rank = rank\n",
148+
" self.employee = None\n",
149+
"\n",
150+
"\n",
151+
"class CallCenter(object):\n",
152+
"\n",
153+
" def __init__(self, operators, supervisors, directors):\n",
154+
" self.operators = operators\n",
155+
" self.supervisors = supervisors\n",
156+
" self.directors = directors\n",
157+
" self.queued_calls = deque()\n",
158+
"\n",
159+
" def dispatch_call(self, call):\n",
160+
" if call.rank not in (Rank.OPERATOR, Rank.SUPERVISOR, Rank.DIRECTOR):\n",
161+
" raise ValueError('Invalid call rank: {}'.format(call.rank))\n",
162+
" employee = None\n",
163+
" if call.rank == Rank.OPERATOR:\n",
164+
" employee = self._dispatch_call(call, self.operators)\n",
165+
" if call.rank == Rank.SUPERVISOR or employee is None:\n",
166+
" employee = self._dispatch_call(call, self.supervisors)\n",
167+
" if call.rank == Rank.DIRECTOR or employee is None:\n",
168+
" employee = self._dispatch_call(call, self.directors)\n",
169+
" if employee is None:\n",
170+
" self.queued_calls.append(call)\n",
171+
"\n",
172+
" def _dispatch_call(self, call, employees):\n",
173+
" for employee in employees:\n",
174+
" if employee.call is None:\n",
175+
" employee.take_call(call)\n",
176+
" return employee\n",
177+
" return None\n",
178+
"\n",
179+
" def notify_call_escalated(self, call): # ...\n",
180+
" def notify_call_completed(self, call): # ...\n",
181+
" def dispatch_queued_call_to_newly_freed_employee(self, call, employee): # ..."
182+
]
183+
}
184+
],
185+
"metadata": {
186+
"kernelspec": {
187+
"display_name": "Python 3",
188+
"language": "python",
189+
"name": "python3"
190+
},
191+
"language_info": {
192+
"codemirror_mode": {
193+
"name": "ipython",
194+
"version": 3
195+
},
196+
"file_extension": ".py",
197+
"mimetype": "text/x-python",
198+
"name": "python",
199+
"nbconvert_exporter": "python",
200+
"pygments_lexer": "ipython3",
201+
"version": "3.4.3"
202+
}
203+
},
204+
"nbformat": 4,
205+
"nbformat_minor": 0
206+
}
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
from abc import ABCMeta, abstractmethod
2+
from collections import deque
3+
from enum import Enum
4+
5+
6+
class Rank(Enum):
7+
8+
OPERATOR = 0
9+
SUPERVISOR = 1
10+
DIRECTOR = 2
11+
12+
13+
class Employee(metaclass=ABCMeta):
14+
15+
def __init__(self, employee_id, name, rank, call_center):
16+
self.employee_id = employee_id
17+
self.name = name
18+
self.rank = rank
19+
self.call = None
20+
self.call_center = call_center
21+
22+
def take_call(self, call):
23+
"""Assume the employee will always successfully take the call."""
24+
self.call = call
25+
self.call.employee = self
26+
self.call.state = CallState.IN_PROGRESS
27+
28+
def complete_call(self):
29+
self.call.state = CallState.COMPLETE
30+
self.call_center.notify_call_completed(self.call)
31+
32+
@abstractmethod
33+
def escalate_call(self):
34+
pass
35+
36+
def _escalate_call(self):
37+
self.call.state = CallState.READY
38+
call = self.call
39+
self.call = None
40+
self.call_center.notify_call_escalated(call)
41+
42+
43+
class Operator(Employee):
44+
45+
def __init__(self, employee_id, name):
46+
super(Operator, self).__init__(employee_id, name, Rank.OPERATOR)
47+
48+
def escalate_call(self):
49+
self.call.level = Rank.SUPERVISOR
50+
self._escalate_call()
51+
52+
53+
class Supervisor(Employee):
54+
55+
def __init__(self, employee_id, name):
56+
super(Operator, self).__init__(employee_id, name, Rank.SUPERVISOR)
57+
58+
def escalate_call(self):
59+
self.call.level = Rank.DIRECTOR
60+
self._escalate_call()
61+
62+
63+
class Director(Employee):
64+
65+
def __init__(self, employee_id, name):
66+
super(Operator, self).__init__(employee_id, name, Rank.DIRECTOR)
67+
68+
def escalate_call(self):
69+
raise NotImplemented('Directors must be able to handle any call')
70+
71+
72+
class CallState(Enum):
73+
74+
READY = 0
75+
IN_PROGRESS = 1
76+
COMPLETE = 2
77+
78+
79+
class Call(object):
80+
81+
def __init__(self, rank):
82+
self.state = CallState.READY
83+
self.rank = rank
84+
self.employee = None
85+
86+
87+
class CallCenter(object):
88+
89+
def __init__(self, operators, supervisors, directors):
90+
self.operators = operators
91+
self.supervisors = supervisors
92+
self.directors = directors
93+
self.queued_calls = deque()
94+
95+
def dispatch_call(self, call):
96+
if call.rank not in (Rank.OPERATOR, Rank.SUPERVISOR, Rank.DIRECTOR):
97+
raise ValueError('Invalid call rank: {}'.format(call.rank))
98+
employee = None
99+
if call.rank == Rank.OPERATOR:
100+
employee = self._dispatch_call(call, self.operators)
101+
if call.rank == Rank.SUPERVISOR or employee is None:
102+
employee = self._dispatch_call(call, self.supervisors)
103+
if call.rank == Rank.DIRECTOR or employee is None:
104+
employee = self._dispatch_call(call, self.directors)
105+
if employee is None:
106+
self.queued_calls.append(call)
107+
108+
def _dispatch_call(self, call, employees):
109+
for employee in employees:
110+
if employee.call is None:
111+
employee.take_call(call)
112+
return employee
113+
return None
114+
115+
def notify_call_escalated(self, call): # ...
116+
def notify_call_completed(self, call): # ...
117+
def dispatch_queued_call_to_newly_freed_employee(self, call, employee): # ...

0 commit comments

Comments
 (0)