|
@@ -0,0 +1,206 @@
|
|
|
+{
|
|
|
+ "cells": [
|
|
|
+ {
|
|
|
+ "cell_type": "markdown",
|
|
|
+ "metadata": {},
|
|
|
+ "source": [
|
|
|
+ "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)."
|
|
|
+ ]
|
|
|
+ },
|
|
|
+ {
|
|
|
+ "cell_type": "markdown",
|
|
|
+ "metadata": {},
|
|
|
+ "source": [
|
|
|
+ "# Design a call center"
|
|
|
+ ]
|
|
|
+ },
|
|
|
+ {
|
|
|
+ "cell_type": "markdown",
|
|
|
+ "metadata": {},
|
|
|
+ "source": [
|
|
|
+ "## Constraints and assumptions\n",
|
|
|
+ "\n",
|
|
|
+ "* What levels of employees are in the call center?\n",
|
|
|
+ " * Operator, supervisor, director\n",
|
|
|
+ "* Can we assume operators always get the initial calls?\n",
|
|
|
+ " * Yes\n",
|
|
|
+ "* If there is no free operators or the operator can't handle the call, does the call go to the supervisors?\n",
|
|
|
+ " * Yes\n",
|
|
|
+ "* If there is no free supervisors or the supervisor can't handle the call, does the call go to the directors?\n",
|
|
|
+ " * Yes\n",
|
|
|
+ "* Can we assume the directors can handle all calls?\n",
|
|
|
+ " * Yes\n",
|
|
|
+ "* What happens if nobody can answer the call?\n",
|
|
|
+ " * It gets queued\n",
|
|
|
+ "* Do we need to handle 'VIP' calls where we put someone to the front of the line?\n",
|
|
|
+ " * No\n",
|
|
|
+ "* Can we assume inputs are valid or do we have to validate them?\n",
|
|
|
+ " * Assume they're valid"
|
|
|
+ ]
|
|
|
+ },
|
|
|
+ {
|
|
|
+ "cell_type": "markdown",
|
|
|
+ "metadata": {},
|
|
|
+ "source": [
|
|
|
+ "## Solution"
|
|
|
+ ]
|
|
|
+ },
|
|
|
+ {
|
|
|
+ "cell_type": "code",
|
|
|
+ "execution_count": 1,
|
|
|
+ "metadata": {
|
|
|
+ "collapsed": false
|
|
|
+ },
|
|
|
+ "outputs": [
|
|
|
+ {
|
|
|
+ "name": "stdout",
|
|
|
+ "output_type": "stream",
|
|
|
+ "text": [
|
|
|
+ "Overwriting call_center.py\n"
|
|
|
+ ]
|
|
|
+ }
|
|
|
+ ],
|
|
|
+ "source": [
|
|
|
+ "%%writefile call_center.py\n",
|
|
|
+ "from abc import ABCMeta, abstractmethod\n",
|
|
|
+ "from collections import deque\n",
|
|
|
+ "from enum import Enum\n",
|
|
|
+ "\n",
|
|
|
+ "\n",
|
|
|
+ "class Rank(Enum):\n",
|
|
|
+ "\n",
|
|
|
+ " OPERATOR = 0\n",
|
|
|
+ " SUPERVISOR = 1\n",
|
|
|
+ " DIRECTOR = 2\n",
|
|
|
+ "\n",
|
|
|
+ "\n",
|
|
|
+ "class Employee(metaclass=ABCMeta):\n",
|
|
|
+ "\n",
|
|
|
+ " def __init__(self, employee_id, name, rank, call_center):\n",
|
|
|
+ " self.employee_id = employee_id\n",
|
|
|
+ " self.name = name\n",
|
|
|
+ " self.rank = rank\n",
|
|
|
+ " self.call = None\n",
|
|
|
+ " self.call_center = call_center\n",
|
|
|
+ "\n",
|
|
|
+ " def take_call(self, call):\n",
|
|
|
+ " \"\"\"Assume the employee will always successfully take the call.\"\"\"\n",
|
|
|
+ " self.call = call\n",
|
|
|
+ " self.call.employee = self\n",
|
|
|
+ " self.call.state = CallState.IN_PROGRESS\n",
|
|
|
+ "\n",
|
|
|
+ " def complete_call(self):\n",
|
|
|
+ " self.call.state = CallState.COMPLETE\n",
|
|
|
+ " self.call_center.notify_call_completed(self.call)\n",
|
|
|
+ "\n",
|
|
|
+ " @abstractmethod\n",
|
|
|
+ " def escalate_call(self):\n",
|
|
|
+ " pass\n",
|
|
|
+ "\n",
|
|
|
+ " def _escalate_call(self):\n",
|
|
|
+ " self.call.state = CallState.READY\n",
|
|
|
+ " call = self.call\n",
|
|
|
+ " self.call = None\n",
|
|
|
+ " self.call_center.notify_call_escalated(call)\n",
|
|
|
+ "\n",
|
|
|
+ "\n",
|
|
|
+ "class Operator(Employee):\n",
|
|
|
+ "\n",
|
|
|
+ " def __init__(self, employee_id, name):\n",
|
|
|
+ " super(Operator, self).__init__(employee_id, name, Rank.OPERATOR)\n",
|
|
|
+ "\n",
|
|
|
+ " def escalate_call(self):\n",
|
|
|
+ " self.call.level = Rank.SUPERVISOR\n",
|
|
|
+ " self._escalate_call()\n",
|
|
|
+ "\n",
|
|
|
+ "\n",
|
|
|
+ "class Supervisor(Employee):\n",
|
|
|
+ "\n",
|
|
|
+ " def __init__(self, employee_id, name):\n",
|
|
|
+ " super(Operator, self).__init__(employee_id, name, Rank.SUPERVISOR)\n",
|
|
|
+ "\n",
|
|
|
+ " def escalate_call(self):\n",
|
|
|
+ " self.call.level = Rank.DIRECTOR\n",
|
|
|
+ " self._escalate_call()\n",
|
|
|
+ "\n",
|
|
|
+ "\n",
|
|
|
+ "class Director(Employee):\n",
|
|
|
+ "\n",
|
|
|
+ " def __init__(self, employee_id, name):\n",
|
|
|
+ " super(Operator, self).__init__(employee_id, name, Rank.DIRECTOR)\n",
|
|
|
+ "\n",
|
|
|
+ " def escalate_call(self):\n",
|
|
|
+ " raise NotImplemented('Directors must be able to handle any call')\n",
|
|
|
+ "\n",
|
|
|
+ "\n",
|
|
|
+ "class CallState(Enum):\n",
|
|
|
+ "\n",
|
|
|
+ " READY = 0\n",
|
|
|
+ " IN_PROGRESS = 1\n",
|
|
|
+ " COMPLETE = 2\n",
|
|
|
+ "\n",
|
|
|
+ "\n",
|
|
|
+ "class Call(object):\n",
|
|
|
+ "\n",
|
|
|
+ " def __init__(self, rank):\n",
|
|
|
+ " self.state = CallState.READY\n",
|
|
|
+ " self.rank = rank\n",
|
|
|
+ " self.employee = None\n",
|
|
|
+ "\n",
|
|
|
+ "\n",
|
|
|
+ "class CallCenter(object):\n",
|
|
|
+ "\n",
|
|
|
+ " def __init__(self, operators, supervisors, directors):\n",
|
|
|
+ " self.operators = operators\n",
|
|
|
+ " self.supervisors = supervisors\n",
|
|
|
+ " self.directors = directors\n",
|
|
|
+ " self.queued_calls = deque()\n",
|
|
|
+ "\n",
|
|
|
+ " def dispatch_call(self, call):\n",
|
|
|
+ " if call.rank not in (Rank.OPERATOR, Rank.SUPERVISOR, Rank.DIRECTOR):\n",
|
|
|
+ " raise ValueError('Invalid call rank: {}'.format(call.rank))\n",
|
|
|
+ " employee = None\n",
|
|
|
+ " if call.rank == Rank.OPERATOR:\n",
|
|
|
+ " employee = self._dispatch_call(call, self.operators)\n",
|
|
|
+ " if call.rank == Rank.SUPERVISOR or employee is None:\n",
|
|
|
+ " employee = self._dispatch_call(call, self.supervisors)\n",
|
|
|
+ " if call.rank == Rank.DIRECTOR or employee is None:\n",
|
|
|
+ " employee = self._dispatch_call(call, self.directors)\n",
|
|
|
+ " if employee is None:\n",
|
|
|
+ " self.queued_calls.append(call)\n",
|
|
|
+ "\n",
|
|
|
+ " def _dispatch_call(self, call, employees):\n",
|
|
|
+ " for employee in employees:\n",
|
|
|
+ " if employee.call is None:\n",
|
|
|
+ " employee.take_call(call)\n",
|
|
|
+ " return employee\n",
|
|
|
+ " return None\n",
|
|
|
+ "\n",
|
|
|
+ " def notify_call_escalated(self, call): # ...\n",
|
|
|
+ " def notify_call_completed(self, call): # ...\n",
|
|
|
+ " def dispatch_queued_call_to_newly_freed_employee(self, call, employee): # ..."
|
|
|
+ ]
|
|
|
+ }
|
|
|
+ ],
|
|
|
+ "metadata": {
|
|
|
+ "kernelspec": {
|
|
|
+ "display_name": "Python 3",
|
|
|
+ "language": "python",
|
|
|
+ "name": "python3"
|
|
|
+ },
|
|
|
+ "language_info": {
|
|
|
+ "codemirror_mode": {
|
|
|
+ "name": "ipython",
|
|
|
+ "version": 3
|
|
|
+ },
|
|
|
+ "file_extension": ".py",
|
|
|
+ "mimetype": "text/x-python",
|
|
|
+ "name": "python",
|
|
|
+ "nbconvert_exporter": "python",
|
|
|
+ "pygments_lexer": "ipython3",
|
|
|
+ "version": "3.4.3"
|
|
|
+ }
|
|
|
+ },
|
|
|
+ "nbformat": 4,
|
|
|
+ "nbformat_minor": 0
|
|
|
+}
|