Part 1: Using Brute Force to Build a Chatbot

In this series, I will write a chatbot that will assist the insured in initiating their claim and interacting with the insured to include as much detail as necessary for the insurance company to successfully and expediently process the claim. This first article will cover the very basics of the concept by writing a program that simply interacts with the user through basic questions and answers. This will illustrate the ultimate goal of the series.

Chatbot

* Image used without permission from https://bit.ly/2IT7nHd

Goals of the series:
  • Build a basic Q&A chatbot in Python
  • Integrate the chatbot with a structured dataset
  • Move the user interface to a web-based application platform
  • Integrate the chatbot with social media platforms, such as Facebook and Twitter
  • Evaluate and introduce an AI engine such as Tensor Flow and the Natural Language toolkit to expand the chatbot's capabilities
  • Use AI to automatically process the claim based on the information provided by the insured
  • Integrate with a payment processing service provider to send a payment to the insured
What is a claim?

The purpose of an insurance policy is to indemnify the insured from loss. Meaning, that when an accident is caused by a covered peril, the insurance company will compensate the insured for those damages up to the policy limits. The paper trail that invokes, pays and follows the insured from accident through indemnification is called a "claim", which is initiated by the insured indicating that the accident occurred and that they are requesting damages according to the policy. The insurance company will review the claim and process a payment so that the insured can complete the necessary repairs.

Getting Started

This article assumes you have a basic working knowledge of Python and have it installed. For this first article, we are only using IDLE and the default Python packages, so if you only have Python installed, you should be good-to-go.

Program Objectives

The overall elements for a general claim are 1) the insured, 2) the date of the accident, 3) details related to the accident and 4) damages. Each of these four elements can include several additional elements. For example, the insured is some identifier that we can use to look-up the policy and determine whether or not there is an effective policy in place. The date of the accident allows us to determine whether or not the accident occurred between the effective dates of the policy. The details of the accident determine whether or not the type of accident is within the list of covered perils (i.e. damages caused by an earthquake or flood may not be covered in a standard homeowner's policy). Finally, the damages assist us in determining the amount of money that the insured will require to repair the damages caused by the accident.

Our program must interact with the user to capture this information in a meaningful way so that by the end of the chat session, the insurance company has ALL of the information required to process the claim.

Step 0: A note on UnitTesting

For any project, we want to start with defining our expectations in how the code should work, then we want to define how the code should respond when it doesn't work. We use the unittest framework to do this. For now, we are writing this first proof-of-concept as a script instead of a callable function, so our unit test won't really test anything, but it's good to get the unit test created even as a placeholder. Note: cb0 is the name of the file that I am using to write this first chatbot.

import unittest
import cb0

class TestChatbot0(unittest.TestCase):
    def test_name(self):
        self.assertEqual(cb0, 'arbitrary')

Step 1: Basic Interaction

Using IDLE, let's start with the basic concept of capturing user information, such as the user's name:

name = input("Hello. May I have your full name (last name, first name)?")
answer = "Thank you " + name.split(",")[1].strip() + ". Would you like to 1) Submit a Claim or 2) Speak with an Agent?"

print(answer)

and the output:

First chatbot run

What are we doing?

In the first part of the code, we are requesting user input, but we are asking for the last name AND the first name of the user so that we can do a policy lookup later. In the second part of the code, we are addressing the user (by first name so that we don't have to mess with title identification), and using the string split and strip functions to create an array of the last name and first name (separated by the comma), using the second variable of the array (the first name) and stripping off the extra white space.

And, when I run the test case, I get the following, same results:

First chatbot run

So now, let's wrap the script in a module that we can call so that we can continue to build out the unit test case (note: I had to change the response a bit to work with the unit testing framework):

def question0(name):
    response = "Thank you " + name.split(",")[1].strip() + "."
    return response

def main():
    name = input("Hello. May I have your full name? (last_name, first_name)")
    print(question0(name))


if __name__ == "__main__":
    # execute only if run as a script
    main()

and the unit test:

import unittest
import cb0

class TestChatbot0(unittest.TestCase):

    def test_name(self):
        self.assertEqual(cb0.question0("Dundas, Rob"), 'Thank you Rob.')

if __name__ == '__main__':
    unittest.main()

Here is the execution of the script:

First chatbot run

and the unit test:

First chatbot run

So far, so good. Now, let's consider some negative conditions to see how the code behaves. What if the user enters First Name first and Last Name last? Well, let's assume that no comma means precisely this, so if there is no comma, we will provide an output that says that the user didn't follow the instructions and we will assume their first name is the first word of the array (separated by a space instead of a comma). In this case, we are splitting the text by a comma into an array, so 'Dundas, Rob' becomes ['Dundas', 'Rob'] and we are retrieving the value in the 1 placeholder to get our first name. If the user enters 'Rob Dundas', our array will look like this ['Rob Dundas', ] with no second value, so when our code retrieves it, it will give an IndexError. Therefore, we can use a try except clause to first try the comma method and then call try a split based on space:

def question0(name):
    try:
        response = "Thank you " + name.split(",")[1].strip() + "."
    except IndexError:
        print("Please follow the instructions provided. We will assume your first\
              name is " + name.split(" ")[0] + " and your last name is " + name.split(" ")[1] + ".")
        response = "Thank you " + name.split(" ")[0].strip() + "."

    return response


def main():
    name = input("Hello. May I have your full name? (last_name, first_name)")
    print(question0(name))


if __name__ == "__main__":
    # execute only if run as a script
    main()

and the unit test:

import unittest
import cb0

class TestChatbot0(unittest.TestCase):

    def test_firstnamelast(self):
        self.assertEqual(cb0.question0("Dundas, Rob"), 'Thank you Rob.')

    def test_firstnamefirst(self):    
        self.assertEqual(cb0.question0("Rob Dundas"), 'Thank you Rob.')


if __name__ == '__main__':
    unittest.main()

Here is the execution of the script now:

First chatbot run

and the unit test:

First chatbot run

At this point, we could blow this out as much as we want and add conditions for testing code and their corresponding unit test cases, but that is outside of the scope of this article.

Step 2: Adding questions and answers by hard-coding a decision tree

For now, we are going to hard-code the questions and responses for the purpose of illustrating the concept. Once we have a handle on where to go, we'll move the hard-coded portions to something more dynamic, but the test cases should stay the same (which is why we write them to begin with!).

We want the first fork of the decision tree to be to simply determine whether the user is really there to submit a claim or some other reason. So, after we capture the user's name, we want to ask whether the user 1) wishes to submit a claim, 2) chat with a customer agent, or 3) another purpose. For now 2 and 3 are simply placeholders for future messaging, so our focus will be on 1 but we will generate responses for all three.

We will define a new method to ask a new question and process the response. The test case will be cover a few more options. This time, let's start with the unit test and write the new method according to our expectations:

import unittest
import cb0

class TestChatbot0(unittest.TestCase):

    def test_firstnamelast(self):
        self.assertEqual(cb0.question0("Dundas, Rob"), "Thank you Rob.")

    def test_firstnamefirst(self):    
        self.assertEqual(cb0.question0("Rob Dundas"), "Thank you Rob.")


class TestChatbot1(unittest.TestCase):

    def test_response1(self):
        self.assertEqual(cb0.question1(1), 'OK. I can help you with that.')
    def test_response2(self):
        self.assertEqual(cb0.question1(2), 'OK. I need to get some more information from you.')
    def test_response3(self):
        self.assertEqual(cb0.question1(3), 'OK. Let me connect you with an agent.')
    def test_response4(self):
        self.assertEqual(cb0.question1(4), 'OK. Please provide more information.')

class TestChatbot1a(unittest.TestCase):

    def test_response1(self):
        self.assertEqual(cb0.question1('start a new claim'), 'OK. I can help you with that.')
    def test_response2(self):
        self.assertEqual(cb0.question1('check on the status'), 'OK. I need to get some more information from you.')
    def test_response3(self):
        self.assertEqual(cb0.question1('speak with a rep'), 'OK. Let me connect you with an agent.')
    def test_response4(self):
        self.assertEqual(cb0.question1('something else'), 'OK. Please provide more information.')

Now, we define the method to process the input. Here, however, we are going to give numeric options so that the user will have to enter 1,2, or 3, but, we want to allow the program to process the input if the user enters something other than those numbers. For example, if they write the option or a portion of the option. Let's use a dictionary to store the responses, and convert the input to the numerical key of the dictionary so that the user's input retrieves the correct dictionary response.

def question1(option):

    responses = {1: 'OK. I can help you with that.',
                 2: 'OK. I need to get some more information from you.',
                 3: 'OK. Let me connect you with an agent.',
                 4: 'OK. Please provide more information.'}


    try:
        response = int(option)
    except ValueError:
        if 'new' in response.lower():
            response = 1
        elif 'status' in response.lower():
            response = 2
        elif 'speak' in response.lower():
            response = 3
        else:
            response = 4

    return responses[response]        

def main():
    name = input("Hello. May I have your full name? (last_name, first_name)")
    print(question0(name))
    q1_response = input("Would you like to 1) start a new claim, \
2) check on the status of an existing claim, \
3) speak with a customer service representative, or \
4) something else?")
    print(question1(q1_response))

And, assuming we wrote the method correctly and are retrieving the correct response, we can run the unit test:

First chatbot run

and running the code:

First chatbot run

Conclusion

Here we have a very basic, hard-coded chatbot that is capable of accepting input and forking to a decision. It is obvious that we can start to build this out step by step by creating methods for each response and forking to additional responses, however, this seems extremely inefficient. Instead, in the next article, we'll look at creating a general method that can loop-up questions and responses based on structured data.