Design a Call Center.
Tags: python, beginners
Published: 2022-10-30 19:42:25

### Instructions
A call center has three levels of employees: respondent, manager, 
and director. An incoming call must be first allocated to a respondent who is free. If the respondent can't handle the call, he or she must escalate the call to a manager. If the manager is not free or not able to handle it, then the call should be escalated to a director. Design the classes and 
data structures for this problem. Implement a method dispatchCall() which assigns a call to the first available employee. 

## Approach
We will follow the approach suggested in Cracking the Coding Interview:

### Step 1: Handle Ambiguity
Here we determine the constraints and assumptions we will use in the design. 

- We assume the responders always get the initial calls and they only escalate to manager if they cannot handle it. The call is escalated to the director if the manager cannot handle it and the director can handle all calls.
- Calls are placed in queue awaiting to be handled.

### Step 2: Define the Core Objects

The main objects are Employee, Call,and CallCenter. 
The Respondent, Manager, and Director classes inherit from the Employee class.
We also include a Rank class to handle the employee's rank, CallState class to determine the state of each call. 

### Step 3: Analyze Relationships 
An employee can only handle one call at a time.

### Step 4: Investigate Actions 
The Employee can receive and end a call.
The Responder and Manager can escalate calls.
The CallCenter dispatches calls to available employees.

## Implementation
Create an enumeration to define the level of each employee.
We create an enumeration where the levels are functionally constant. 
Learn more about enum [here](https://docs.python.org/3/library/enum.html).

```python
from enum import Enum

class Rank(Enum):
    RESPONDER = 0
    MANAGER = 1
    DIRECTOR = 2
```
Create class Employee which is a super class of the Responder, Manager and Director classes. 

We implement the Employee class as an abstract class with all the abstract methods common to all employees.

More about abstract classes in python [here](https://docs.python.org/3/library/abc.html) and [here](https://blog.teclado.com/python-abc-abstract-base-classes/).


> An abstract class inherits from the Abstract Base Class ABC. @abstractmethod is a decorator to define an abstract method.


```python
from abc import ABCMeta, abstractmethod
class Employee(metaclass=ABCMeta):

    def __init__(self, employee_id, name, rank, call_center):
        self.employee_id = employee_id
        self.name = name
        self.rank = rank
        self.call = None
        self.call_center = call_center

    def take_call(self, call):
        """Assume the employee will always successfully take the call."""
        self.call = call
        self.call.employee = self
        self.call.state = CallState.IN_PROGRESS

    def complete_call(self):
        self.call.state = CallState.COMPLETE
        self.call_center.notify_call_completed(self.call)

    @abstractmethod
    def escalate_call(self):
        pass

    def _escalate_call(self):
        self.call.state = CallState.READY
        call = self.call
        self.call = None
        self.call_center.notify_call_escalated(call)
```

Create Responder class inheriting from Employee.
Learn more about inheritance [here](https://www.w3schools.com/python/python_inheritance.asp).

> RANK.RESPONDER returns the value of the responder's level

```python
class Responder(Employee):

    def __init__(self, employee_id, name):
        super(Responder, self).__init__(employee_id, name, Rank.RESPONDER)

    def escalate_call(self):
        self.call.level = Rank.MANAGER
        self._escalate_call()
```

Create the Manager Class inheriting from Employee.

```python
class Manager(Employee):

    def __init__(self, employee_id, name):
        super(Responder, self).__init__(employee_id, name, Rank.MANAGER)

    def escalate_call(self):
        self.call.level = Rank.DIRECTOR
        self._escalate_call()

```
Create class Director.
Director cannot escalate call; therefore, we throw an error.

```python
class Director(Employee):

    def __init__(self, employee_id, name):
        super(Responder, self).__init__(employee_id, name, Rank.DIRECTOR)

    def escalate_call(self):
        raise NotImplementedError('Directors must be able to handle any call')
```
Create an enumeration to define the state of a given call.

```python
class CallState(Enum):

    READY = 0
    IN_PROGRESS = 1
    COMPLETE = 2
```
Create Call class.

```python
class Call(object):

    def __init__(self, rank):
        self.state = CallState.READY
        self.rank = rank
        self.employee = None
```
Create the CallCenter class.
- Attributes: responders, managers, directors and calls.
- methods : dispatch_call, notify when call is escalated,completed,

```python
from collections import deque
class CallCenter(object):

    def __init__(self, responders, managers, directors):
        self.responders = responders
        self.managers = managers
        self.directors = directors
        self.queued_calls = deque()

    def dispatch_call(self, call):
        if call.rank not in (Rank.RESPONDER, Rank.MANAGER, Rank.DIRECTOR):
            raise ValueError('Invalid call rank: {}'.format(call.rank))
        employee = None
        if call.rank == Rank.RESPONDER:
            employee = self._dispatch_call(call, self.responders)
        if call.rank == Rank.MANAGER or employee is None:
            employee = self._dispatch_call(call, self.managers)
        if call.rank == Rank.DIRECTOR or employee is None:
            employee = self._dispatch_call(call, self.directors)
        if employee is None:
            self.queued_calls.append(call)

    def _dispatch_call(self, call, employees):
        for employee in employees:
            if employee.call is None:
                employee.take_call(call)
                return employee
        return None

    def notify_call_escalated(self, call):
        print("Call escalated")

    def notify_call_completed(self, call):
        print("Call completed")

```
You can find the entire code [here](https://github.com/wanguiwaweru/Object-Oriented).
Happy learning and designing !!!

### References

1. Cracking the Coding Interview
2. [System Design Primer](https://github.com/donnemartin/system-design-primer)