Engineering
A simple way to make complex phone trees... with a state machine
What is a phone tree?

If you have ever called a company and heard “Press 1 for new customers, press 2 for existing customers…” or something to that extent, then you have encountered an IVR - Interactive Voice Response. In its most basic form, an IVR tree will look something like this:

And when you try to code this up, it looks pretty straightforward
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>title</title>
    <link rel="stylesheet" href="style.css">
    <script src="script.js"></script>
  </head>
  <body>
<digit = request.form.["Digit"]

if digit == "1":
	redirect_to_sales()
if digit == "2":
	redirect_to_cs()
if digit == "3":
	collect_voicemail()>
  </body>
</html>
More options - more problems

Things start to get hairy when business needs get more complicated. First, we want to add another layer for “Sales” - for corporate and individual customers.

Then for customer support, we want the caller to say their name before connecting to the agent for easier identification. For out-of-hours support, we want to ask customers to leave a voicemail so that agents can call back the next day.

We also want to send customers an SMS to point them to the complaints form if they are calling to file a complaint. But only if they call from a mobile phone!

Oh, and we want to play a message to international customers warning them about the phone charges.

And that’s just the start! You know the Head of Customer Engagement is up for a raise this year, so they’re full of ideas. WhatsApp messages? Voice bots? Nothing’s off the table.
Pretty soon, you're neck deep in the nested 'if' statements. And then, the company decides they need a web interface for this, so anyone could change the config without taking away engineering time.

That's where you know you need to come up with a better solution. Ideally, something that shows off how clever you are. Well, I've got just the thing for you.

Deterministic Finite Automation

DFA is one of the foundational concepts of the automata theory. A lot of stuff we know and love is built on top of the State Machine - including Turing machines and regular expressions.

However, for this blog post, we’re not going to go deep into theory. Here, we will be using DFA as an abstraction, a design pattern, in hopes that it will help us write better, more easily extendable code.

So what is a Deterministic Finite Automaton?

DFA (or a Finite State Machine) is an automata theory concept describing a machine with a finite number of states and a finite set of transitions.

What makes it deterministic is that given a state and a transition, you can always deterministically tell what the next state is.
Here S0 and S1 are states and 0 and 1 are inputs. Given a state and an input you can always tell what the next state of the machine would be.

So how does it help with our phone support problem?
How to describe an arbitrary IVR tree as a DFA

We can represent the IVR tree as a state machine.

Our IVR system is in a particular state at any given point - playing a message, sending a text, or hanging up.

Every time something happens in the system - an inbound call, a customer pressing a digit on the dial pad, a lookup function telling us whether the caller is on a mobile or the landline - it’s an input.

All we need to do is to define what should happen in every state and which input leads to the next state.

Let’s start with the simple IVR we saw earlier - a customer calls, presses “1”, “2”, or “3”, and gets redirected to the correct department.
Now to the most important part - how do we represent this in code?

We will define a dictionary where keys are states and values are structures containing:

  • what should happen in the state;
  • transitions from this state to the next.

For the simple IVR, it will look like this:
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>title</title>
    <link rel="stylesheet" href="style.css">
    <script src="script.js"></script>
  </head>
  <body>
    <states = {
	"start": {
		"transitions": {
			"incoming_call": "greeting"
		}
	},
	
	"greeting": {
		"action": "play_greeting_message",
		"transitions": {
			"pressed_1": "call_sales",
			"pressed_2": "call_cs",
			"pressed_3": "call_complaints"
		}
	},

	
	// These three states have no transitions - they are terminal states
	"call_sales": {
		"action": "dial_sales"
	},

	"call_cs": {
		"action": "dial_cs"
	},

	"call_complaints": {
		"action": "dial_complaints"
	}
}>
  </body>
</html>
Now to determine what needs to happen at any given time, we just need to keep track of the previous step and the transition that happened after.
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>title</title>
    <link rel="stylesheet" href="style.css">
    <script src="script.js"></script>
  </head>
  <body>
    <previous_state = request.form.get("previous_state", "")
transition = request.form.get("transition", ||)

state = None
# The very first state will not have a previous state
if not previous_state:
	state = "start"
else:
	# If a state does not have transitions it's a terminal state
	if not states[previous_state].get("transitions", {}):
		return

	state = states[previous_state]["transitions"][transition]

action = states[state]["action"]

# Process the action here>
  </body>
</html>
As the IVR tree grows more complex, it’s a matter of adding steps, transitions and action processors - like sending an SMS or capturing a voicemail - to our config dictionary. The processing code stays essentially the same.

And just like that, business requirements have been defeated by computer science ????

October 04, 2021
Irina Bednova
CTO