Tracking User Paths in an IVR with Neo4j

I started my software development career writing applications for a Call Center at a small bank in Florida. I remember the bank had purchased whatever the “Cadillac” of Interactive Voice Response (IVR) systems was then for some crazy amount of money. Today you can build an IVR overnight using Twilio.


When you sign up with Twilio, you get to choose your phone number (more or less). For example, I picked +1 (636) 451-7411, which spells out +1 (neo) 4j1-7411. If you were to call this number right now (assuming I have not run out of Twilio credits) you’ll connect to my IVR.

A few years ago I built one for a property management firm, I’ll use that as an example.

  response = do |r|
    r.Say "Thank you for calling the Neo4j I V R demo.", :voice => "alice"
    r.Gather :action => "/mainmenu", :numDigits => 1 do
      r.Say "For quality assurance purposes this call may be recorded", :voice => "alice"
      r.Say "For rent payment options and billing questions, press 1", :voice => "alice"
      r.Say "To submit a maintenance request, press 2", :voice => "alice"
      r.Say "For all other tenant related questions press 3", :voice => "alice"
      r.Say "To repeat this message press 9", :voice => "alice"
    r.Say "Sorry, I didn\"t get your response.", :voice => "alice"
    r.Play "" + "hello.mp3"
    r.Redirect "/", :method => "GET"

The code above generates an XML file which Twilio interprets. If you press “2” to submit a maintenance request you’ll get another menu generated by this code:

post "/maintenance" do
  add_event(request, "/maintenance")
  response = do |r|
    r.Gather :action => "/maintenance-input", :numDigits => 1 do
      r.Say "if your heat is out, press 1 ", :voice => "alice"
      r.Say "if there is a problem with your plumbing, press 2", :voice => "alice"
      r.Say "for all other maintenance questions, press 3", :voice => "alice"
      r.Say "to return to the previous menu, press 0", :voice => "alice"
      r.Say "to repeat this message press 9", :voice => "alice"
    r.Say "Sorry, I didn\"t get your response.", :voice => "alice"
    r.Play "" + "hello.mp3"
    r.Redirect "/maintenance"

IVR users will navigate through this menu and report the issues they are having or frantically start pushing buttons until they get to talk to a human being. The longer it takes them to get where they need to go the angrier they get. If only there was a way to track paths through the system… oh right… Neo4j.

The astute observer may have noticed an “add_event” method call in the code above, let’s see what that’s doing:

def add_event(request, url)
    $neo.commit_transaction(["MATCH (old:Event {session: {session}})<-[:PREV*0..]-(latest)
      WHERE NOT(latest<-[:PREV]-())
      WITH latest
      LIMIT 1
      CREATE (latest)<-[:PREV]-(e:Event {session: {session}, url: {url}})
      WITH e
      MATCH (u:User {number: {number}}), (p:Page {url: {url}})
      CREATE (u)-[:DIALED]->(e)-[:ON]->(p)
      RETURN e", 
    {:number => request["From"],
     :url => url,
     :session => request["CallSid"]}

It’s telling Neo4j that a new event has occurred and that it should add it to the chain of events of this user and session. We look for the last event of this session, and create a new “Previous” relationship with the new event that points to the user and page it occurred on. As users call in, our graph gets more and more interesting and pretty soon you can start visualizing the calls to make some sense of them.


We’ll use the D3 javascript library and borrow the Sankey plugin for our visualization.


The Cypher query that generates that visualization starts from the root and goes out up to 3 hops away, counts up the paths and returns them to us. A little ruby magic turns that cypher into JSON that D3.js can understand and that’s all there is to it.

MATCH path=(p:Page {url:'/'})<-[:ON]-()-[:PREV*0..3]-()
RETURN EXTRACT(v in NODES(path)[1..LENGTH(path)+1] | v.url), count(path)
ORDER BY count(path) DESC

We can see on the image above that most people call into make a payment, and few call in to complain about plumbing. It’s one of those things that can’t really wait until next week, and most people tend to take care of it on their own.

We’ll deploy on Heroku


using the GrapheneDB plugin.

I’ve created a Twilio App that points to heroku:

…and set my number to point to the App:


Now, I don’t have a staff to take your call, and it sure isn’t going to be redirected to my cell phone, so instead I’ve decided the end of the IVR menu tree will play little sound bites from a certain “cancelled too soon sci-fi series” plus an easter egg or two thrown in there. Having problems with your heat? Maybe you should call it in: +1 (neo) 4j1-7411.

The full source code is available on github as usual. I did want to send a thank you to Nick Dingwall who did a great series on this for web traffic on his blog.

Tagged , , , , ,

3 thoughts on “Tracking User Paths in an IVR with Neo4j

  1. Ryan says:

    Any plans to do a post on building a rails app with neo4j.rb?

  2. […] example: this post on Tracking User Posts is directly applicable to an adjacent issue that Matrix Solutions has with answering questions for […]

  3. […] Tracking User Paths in an IVR with Neo4j — Max De Marzi […]

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: