You are on page 1of 20

DynFlow

Cokorda Raka Angga Jananuraga raka.angga@gmail.com | http://jananuraga.blogspot.com

Table of Contents
Introduction................................................................................................................................................1 [YourProjectName]....................................................................................................................................4 [YourProjectName_lib]............................................................................................................................15

Introduction
DynFlow is an Orchestration Designer reusable module that simplifies greatly development of IVR for Avaya Experience Portal platform. It is implemented as a state machine that encapsulates various points of interaction in an IVR flow. Currently, DynFlow supports the following points: Announcement Input collection Menu Blind transfer CTI transfer Custom-logic execution1 Those points are the most-commonly used in any IVR. What makes DynFlow attractive is for typical cases you can almost forget developing IVR in Orchestration Designer; it's all reduced to specifying the points in your IVR as rows in a CSV file (you can achieve almost everything by working with a spreadsheet). You can think of DynFlow as a motor that brings that spreadsheet to motion, transitioning from one point to the next. Here is a screenshot of the spreadsheet of the sample IVR (it is broken into two pieces to fit in the page):

1 For those who are familiar with Orchestration Designer, this would be the Servlet component.

The first row in the spreadsheet is allocated for global commands. In the example, pressing *1 at any point in the flow will cause the flow to transition to node whose ID is 2, and pressing 7*6 will cause transition to node 6. The second row wouldn't be processed by DynFlow, it's there only to ease editing (it contains the labels of the columns). Column TYPE indicates the type of point in the IVR. Valid values are: TYPE A P M B C L Description Announcement Input collection Menu Blind transfer CTI transfer Custom-logic execution

For a typical IVR project developed using DynFlow will need to create three separate projects. But fear not, only one of them (might) involving programming. Those three projects are:

[YourProjectName]_data [YourProjectName] [YourProjectName]_lib

The naming is not a rule, it's just a naming convention that I suggest. The following table briefly describes each one of those projects. Project [YourProjectName]_data Description A static web-application; no servlet, no JSP, no java class. It's only to hold the grammar files (if any) and the audio files used in the IVR. Typical directory structure of this project (assuming the IVR supports both spanish and english): spanish grammars standardaudios customaudios english grammars standardaudios customaudios This web-application needs to be copied under the webapps directory of Tomcat. So, under the webapps of Tomcat, there would be a directory named [YourProjectName]_data. [YourProjectName] An Orchestration Designer Speech Project, a very simple one; it has only one flow file (main.flow) and three elements in that flow file (Start, DynFlow, and Return). The outcome of this project is a WAR file that has to be deployed in the webapps directory of Tomcat. [YourProjectName]_lib A Java library project. This is where the custom logics (and other customization codes that might be required) are located. This is the only project that requires some java programming. The outcome of this project must be packaged as a JAR file that has to be deployed in the lib directory of Tomcat. The [YourProjectName]_data project does not require further explaining. Therefore we go directly to detailed explanation of [YourProjectName] and [YourProjectName]_lib in the next two sections.

[YourProjectName]
In the following screenshot you can see how simple an IVR development turns out to be, using DynFlow. Only three elements. Actually this was the principal motivation behind DynFlow, saving time. From my own experiences, dragging and dropping nodes to a flow file, connecting them, maintaining the layout, specifying properties of the elements, etc., really are a drag, takes too much time. I really don't like the fact that many details about the logic of the flow (such as branching decisions, variable assigments, etc.), are buried under those elements, you can not see them right away (first you need to know the node that contains the logic2, and then double click on it to inspect and modify it). A supposedly visual tool turns out to lower the visibility of an implementation. DynFlow aims to put it to an end, by circumventing that visual tool :). Therefore, DynFlow is designed in such way you wouldn't have to use that visual tool again, ever (except for those three elements). Without further ado, here's the screenshot.

The DynFlow is packaged as Reusable Speech Module (a WAR file) that you can import to Orchestration Designer. It will show up in the palette, under the Modules section, as shown in the following screenshot.

2 Through some guess-works, in the unfortunate cases where documentation about the implementation is poor.

The next (and last thing) you will have to do is specifying value for the nine properties of a DynFlow elements. Double click on the DynFlow element in the flow, and the following properties sheet will show up.

The following table describes those properties in detail. Property Description

standardAudioPathBase The URL that maps to the directory where standard audios are stored. Standard audios are audio files provided by Orchestration Designer, for standard phrases such as name of months, name of days, numbers, currencies. This is particularly relevant if you don't have a TTS server; you will need those standard audios.

You might have guessed, this URL should map to a that standardaudios directory under that [YourProjectName]_data project. That's correct. Take a good look at the screenshot, you will find that this URL contains '%s'. DynFlow will replace that sequence of characters with the value of language parameter (see below). Suppose the language is spanish, the example URL above (after transformation) would become http://127.0.0.1:8080/SampleIVR_data/spanish/standardaudios/ Standard audios can be easily obtained by creating a dummy speech project on Orchestation Designer, install your language of choices for the project, and export the project as WAR, and you will find those audios under /data folder. Just copy those audios to the standardaudios directory of [YourProjectName]_data project. customAudioPathBase The URL that maps to the directory where custom audios are stored. Those custom audios are audio files specific to your IVR, and you will have to record them yourself / obtain them from your own sources. The same string replacement (for the '%s') will be applied to the value you specify for this property, as in standardAudioPathBase. Suppose in your spreadsheet you specify the audio file of an annoucement (A) is audio_01.wav, the Experience Portal will load the audio to be played out from http://127.0.0.1:8080/SampleIVR_data/spanish/customaudios/audio_01.wav whenever that node is visited. grammarPathBase The URL that maps to the directory where grammar files (grxml) are stored. Those grammar files are required by the input-collection and menu nodes in your IVR (unless you write down the grammar directly in the spreadsheet). Suppose for an input-collection, the grammar file of the node specified in the spreadsheet is credit_card.grxml. Then whenever that node is visited, the Experience Portal will load the grammar from http://127.0.0.1:8080/SampleIVR_data/spanish/grammars/credit_card.grxml language builder The language of call. Currently only two languages are supported: english and spanish. Default value is spanish. The name of the class that extends DefaultIntegrationComponent. You will have to implement this one class if you have customization needs that can not be accomplished through specification in the spreadsheet. For example: Sending pop-up data. Logging the execution of IVR to your own database. Performing queries / transactions to your backend system (databases, webservices, socket, etc). Deciding where to go next in the event of failures. There are two

types of failures that you can provide custom handler for: Failure in input collection (e.g.: the caller doesn't provide valid input after several tries) Failure in CTI transfer (agent does not answer the call). Building custom announcement that requires not-so-simple logic. In the spreadsheet you can concatenate elements of various types to build an announcement. Those types are: literal, user input, sessionvariable value, and audio file. If you need anything more than that you would have to write your custom announcement-builder. Varying the grammar for an input node, based on various conditions. For example: depending on the type of customer, the available options in the menu will be different.

builderParams

The parameters to be fed in to the builder (during initialization). Initialization of the builder takes a map (key of type String, value of type Object). What we put here is the JSON representation of the map. In the case of DefaultIntegrationComponent, it takes a map that contains the following keys: LogicExecutor TrailLogger CollecHandler GrammarSelector PopupDataSender PromptBuilder Valid values for those keys are either 0 or 1. If you specify 0 for TrailLogger for example, than you wouldn't get notifications everytime a node in the IVR is visited (hence you wouldn't be able to log your IVR to your database; maybe you don't need it anyway). In the [YourProjectName]_lib section, each of those (do)er will be explained in detail. Another key required in the map is CSV. The value must be the absolute path to the spreadsheet file (the definition of your IVR's flow). Path must be specified in its UNIX form (slash instead of backslash). In windows C:\myfiles\myivr.csv would have to be written this way: /C:/myfiles/myivr.csv. Example: {"CSV":"/C:/thenewod/apache-tomcat6.0.29/webapps/SampleIVR_data/sample_1.csv","LogicExecutor":1,"TrailL ogger":1,"CollectHandler":1,"GrammarSelector":0,"PopupDataSender":1,"P romptBuilder":1}

startNodeId

The ID of the first node in the spreadsheet to be visited. In the Sample IVR provided it is 3, so the first thing caller will hear is sampledynflow_03.wav.

contextName

The name of the IVR project.

If you don't specify any value for this parameter, the flow definition (the spreadsheet) will be read and the model of the flow will be constructed based on it for each call (the model will be stored in the HTTP Session associated with the call). If you specify a correct value (name of the IVR project), the flow definition will be read only once (on the first call), and the same model will be used as well by subsequent calls (the model is stored in the Servlet Context associated with the IVR project). If you specify incorrect value (inexistent context or name of a different web application), the behavior is unspecified. You have been warned. dynFlowKey This parameter is only relevant when contextName is set. The value must be the name of instance of DynFlow in your IVR project.

The next thing you need to know is the fields in the spreadsheet associated with each type of node. Here they are. Node type -- All -Fields ID TYPE NAME NEXT (with an exception for node type L). DESC A_AUDIO T_VDN T_AUDIO T_AAOUUI T_VDN T_AUDIO T_AAOUUI C_FALLBACK C_RINGMAX P_GRAMMAR P_PROMPT P_NOINPUT P_NOMATCH P_RETRY P_MODAL

A B

P_GRAMMAR P_PROMPT P_NOINPUT P_NOMATCH P_RETRY P_MODAL M and P share the same set of fields, because a menu (M) in basically is a specialized form of an input collection (P).

The following table contains a description of every single field in the CSV. Field name ID Description ID of the node. Obligatory, and you have to ensure uniqueness of this field in your entire spreadsheet. The value can be numeric or text, doesn't matter. I would suggest numeric value starting from 1 though, it's easier. Type of the node. Name of the node. Assign a short sensible value for this field. This is only used for informative purposes. Description about the node. Useful for documentation. ID of the next node. It applies to all types of node except L, because for L you would have to specify the next node from your Java code (your code must return the next node). For node whose type is M, the following special format for the value is accepted: [nodeID_1]:[inputInterpretation_1]|...|[nodeID_N]: [inputInterpretation_N] In the provided sample IVR, node 3 which is a menu has the following value as its next node: 1:select_1|2:select_2|4:tarjeta_5_digits|4:tarjeta_7_digits To understand it you need to know the grammar associated with that menu. In the sample case it is written directly in the spreadsheet (field P_GRAMMAR) as: (1:select_1|2:select_2|3:tarjeta_5_digits| 4:tarjeta_7_digits) By combining those two pieces of information, you get the following reading: If the caller presses 1, the interpretation would be select_1. Consequently, the flow will transition to node whose ID is 1. If the caller presses 2, the interpretation would be select_2. Consequently, the flow will transition to node whose ID is 2. If the caller presses 3, the interpretation would be tarjeta_5_digits. Consequently, the flow will transition to node whose ID is 4. If the caller presses 4, the interpretation would be tarjeta_7_digits. Consequently, the flow will transition to node whose ID is 4.

TYPE NAME DESC NEXT

To specify default option, simply drop the ':' and the adjacent inputInterpretation. Let's suppose the NEXT of a menu is 1:select_1| 2:select_2|4 Notice there's no ':' and interpretation that follows the digit '4' at the end of the string. Assuming the same grammar, you will get the following reading:

If the caller presses 1, the interpretation would be select_1. Consequently, the flow will transition to node whose ID is 1. If the caller presses 2, the interpretation would be select_2. Consequently, the flow will transition to node whose ID is 2. Otherwise transition to node whose ID is 4.

The simplest case would to specify the NEXT just like in any other types of node; a single token, just the node ID. For example: 4 In that case you will get: DESC A_AUDIO No matter what the caller presses, transition to node whose ID is 4.

Description about the node. Audio for an announcement. The value can take any of these forms: Name of the audio file (or relative path to the audio file). The lookup will be performed starting from the URL specified as customAudioPathBase (one of those parameters of DynFlow). Example: sampledynflow_08.wav The text to be played out by the TTS engine. Example: {spanish:este es algo random} . Notice the curly braces that surround the text. Additionaly it is prefixed by the language (spanish) and the ':' character. Here's another example, showing support for multiple languages: {spanish:Este es anuncio 2|english:This is audio 2} . The separator between the text for spanish and english is the '|' character. If the language of the call is spanish, Este es anuncio 2 will be played out. If the language of the call is english Literal, which takes the following format: '[value]:[format]'. Example: '2013-06-03:dmy'. Pay attention to the surrounding quotes. The experience portal will play out the corresponding sequence of audio files (loaded from standardAudioPathBase), depending on the format specified and current language. For instance, if the current language is spanish, the given example will play out as tres de junio de dos mil trece. Here is the list of known formats: int : takes integer as the value. Example: '123:int' in english will play out as one hundred and twenty three. dec : takes a decimal value. Example: '123.05:dec' in english will play out as one hundred and twenty three coma zero five dmy: takes date specified in year-month-date_of_month format. Example: '2013-01-02:dmy' in english will play out as two january of two thousand and thirteen. dm : takes date specified in year-month-date_of_month format, just as in dmy case. However, the year part will not be included in the play out. Example: '2013-01-02:dmy' in english will play out as two january.

time24 : takes the time specified in hour:minute:second or hour:minute format. The value for the hour is from 0 to 23. Example '20:10:dmy' will be played out as twenty ten. time12 : takes the time specified in hour:minute:second or hour:minute format. The value for the hour is from 0 to 23, just like in the time24 case. However, the hour will be played out in 12h style. Example: '20:10:dmy' will be played out as eight ten pm. digits : it will spell the string character by character. Example: '12A:digits' will be played out as one two A. Node ID / ValBag key. The format it takes is: ([nodeID_or_ValBagKey]:[format]). Example: (6:digits). Looking back at the sample CSV, node whose ID is 6 is 'Ask card number', an input-collection node. The experience portal will play out the number entered by the caller when that node was active, digit by digit (because the format specified is digits). You might have guessed by now this only makes sense for nodes whose type is either P or M. In case node with corresponding ID is not found, DynFlow will look up for the value in ValBag. Example: (someRandomDateWithNoPurpose:dmy). There is not node with ID someRandomDateWithNoPurpose. Therefore, DynFlow performs a lookup in the ValBag (which basically is a map, stored in HttpSession. So, each call has its own ValBag). You put an entry in the ValBag using the Helper class, demonstrated in this example below: Helper.setValueInBag(mySession, someRandomDateWithNoPurpose, 2013-01-02); You would normally do that from inside a logic-execution method that your provide (corresponding to an L node). For example, in the sample CSV, the valye for someRandomDateWithNoPurpose in ValBag is set during execution of node whose ID is 4. The customlogic code for that node is available inside the executeLogic method of MyIntegrationComponent.java in the SampleIVR_lib project.

Concatenated audio. Each piece in the concatenated audio can be of any type explained above. Example (taken from the sample CSV): {spanish:este es algo random}, (someRandomDateWithNoPurpose:dmy), {spanish:okay},sampledynflow_08.wav,(6:digits) Notice the coma between pieces.

A_AUDIO must not be left blank, unless if a prompt-building logic is provided (explained in the yourProjectName_lib section). The other way to see it: it makes sense to specify its value in CSV only when prompt-building logic is not available. Otherwise, leave this field blank. T_VDN The destination number for transfer. This can be the value written in the CSV; e.g.: 123. It can also be a node ID / ValBag key; e.g.: (6) or (someVdnInTheValBag). Notice the surrounding parentheses in the case of node ID / ValBag key. The announcement to be played out before making the transfer. The same rules for specifying the value of A_AUDIO apply here. The AAI Data (in the case of Blind Transfer) or UUI (in the case of UUI transfer) to be passed to the receiving end of call transfer. Its value can be of any of these formats: Literal. Example 'creditCardNum='. Notice the surrounding quotes. Node ID / ValBag key. Example: (6) or (someAAI). Notice the surrounding parentheses. Concatenation. Example: 'creditCardNum=',(6). Notice the comma between pieces. This example in effect will send the string creditCardNum=123456 (assuming the value entered by the caller in node 6 was 123456).

T_AUDIO T_AAOUUI

This field is optional. Leave blank if you don't pass data in the call transfer, or if you provide a custom-logic code for passing on data to the receiving

end of call transfer. C_FALLBACK Value can be either true or false. Default value is true (if not specified). This is to tell DynFlow whether to make a non-CTI, blind transfer, in case CTItransfer is failed. The maximum number of ringing before CTI transfer is considered notanswered. The grammar for input-collection or menu node. It accepts value specified in the following formats: Name of the grammar file. Example: creditcard.grxml Digits-input specification. Example: [5-7]. Notice the surrounding square brackets. It says: recognize sequence of digits whose length can be from 5 (min) to 7 (max). Other example: [5]. It says: recognize exactly 5-digits input. Set of choices. Example: (1:check_balance|2:pay_bill|3:transfer| *1:main_menu). Notice the surrounding parentheses and the | character between choices. Inside a choice, the token to the left of the : character is the accepted DTMF / word. The one to the right is the tag associated with that DTMF / word; and that would be the value assigned to the node upon successful input-recognition. The example grammar can be interpreted this way: If the caller presses 1, the value of the corresponding node would be check_balance (the tag). If the caller presses 2, the value of the node would be pay_bill. If the caller presses 3, the value of the node would be transfer. If the caller presses 4, the value of the node would be main_menu. If you want to obtain the keys pressed / words spoken from your custom-logic, get the utterance instead: Helper.getTrail(mySession).getLastPointById(6).getDatum(utter ance); For value of the node (6), you would do it this way: Helper.getTrail(mySession).getLastPointById(6).getDatum(result ); You can also specify digits-input specifications as the choices. I have seen in an IVR, a prompt like this: Press 1 for talking to operator X, press 2 for talking to operator Y, or input your 15 digits credit card

C_RINGMAX P_GRAMMAR

number directly. For this case, the grammar would be: (1:operator_x|2:operator_y|[16]). Notice tag should not be provided for choice in the format of digits-input specification; the sequence of digits entered would be the value of the node. This field can be left blank if you provide a custom-logic for the grammar selection (will be explained in YourProjectName_lib section). P_PROMPT P_NOINPUT The announcement to be played out on an input-collection node. The same rules for specifying the value of A_AUDIO apply here. The announcement to be played out on an input-collection node when noinput is detected. The same rules for specifying the value of A_AUDIO apply here. After P_NOINPUT is played out, DynFlow will play out P_PROMPT again. Example: IVR: Please enter your 16 digits creditcard number. User: <noinput> IVR: I'm sorry, I couldn't hear you. IVR: Please enter your 16 digits creditcard number. This field can be left blank. P_NOMATCH The announcement to be played out on an input-collection node when nomatch is detected. The same rules for specifying the value of P_NOINPUT apply here. The number of retries before DynFlow marks the input-collection / menu a failure (and failure handler your custom logic start kicking in if there is one). Marks the input-collection / menu as modal (global commands will be masked). Value can be either 0 (default) which corresponds to non-modal, or 1 (modal).

P_RETRY

P_MODAL

[YourProjectName_lib]
For an IVR project ([YourProjectName]), a corresponding [YourProjectName_lib] is needed. In eclipse, this project is of plain Java Project nature. The outcome of [YourProjectName_lib] is a JAR file (to be deployed lib directory of Tomcat. You only need to create one class that extends a class in the DynFlowLib.jar named DefaultIntegrationComponent. This class has ten methods that you can override, nine of them are optional. The only method you will most likely have to implement is the executeLogic (because any useful IVR will have to interact with some sort of database / backend, and executeLogic is the place you can write those interactions in). Here is the list of those ten methods of DefaultIntegrationComponent:

public List<Map<String, String>> buildAnnouncementPrompt (Announcement announcement, SCESession mySession) public List<Map<String, String>> buildPromptAndCollectInputPrompt (PromptAndCollect promptAndCollect, SCESession mySession) public List<Map<String, String>> buildPromptAndCollectNoinputPrompt (PromptAndCollect promptAndCollect, SCESession mySession) public List<Map<String, String>> buildPromptAndCollectNomatchPrompt (PromptAndCollect promptAndCollect, SCESession mySession) public List<Map<String, String>> buildTransferPrompt (Transfer transfer, SCESession mySession) public Node executeLogic (Logic logic, SCESession mySession) public Node handleCollectFailure (PromptAndCollect promptAndCollect, SCESession mySession) public void logTrail (Trail trail, SCESession mySession) public String selectGrammar (PromptAndCollect promptAndCollect, SCESession mySession) public void sendPopupData (Transfer transfer, SCESession mySession)

This screenshot provides an overview of that project.

The following table provide a description of each one of those methods. Method name buildAnnouncementPrompt Description In this method you custom-build the announcement for a node of type A (announcement). You might want to take this course if specifying announcement in the CSV file is not sufficiently flexible for your particular requirement. This screenshot shows an example of how this is done:

In this single method you provide the logics for customconstruction of announcement for every annoucement nodes in the IVR that requires something beyond CSV. In order to select which logic to execute, you check the ID of the announcement node currently executing by invoking this method: annoucement.getId() See the if clause in the screenshot. If you have custom-prompt logic for multiple annoucement nodes, you would have a series chain of if-then clauses. In order to ease the job of custom-building a prompt, a helper class has been provided, PromptElementBuilder. It has three methods, and they're all that we need: addAudioFilePromptElement : it takes the name of the audio file to be played out. addValuePromptElement : it takes the value to be played out, along with its format. Supported formats have been explained in the [YourProjectName] section in the description of A_AUDIO field. getPromptElements : this method returns a list, final outcome of invocations of the two methods above on the PromptElementBuilder instance. This list is the one that you will have to return from buildAnnouncementPrompt method. buildPromptAndCollectInputPro mpt In this method you custom-build the announcement for a node of type P (input-collection) and M (menu). The same technique explained in buildAnnouncementPrompt applies here.

buildPromptAndCollectNoinputP In this method you custom-build the announcement for a node of

rompt

type P (input-collection) and M (menu) when noinput is detected. The same technique explained in buildAnnouncementPrompt applies here.

buildPromptAndCollectNomatch In this method you custom-build the announcement for a node of Prompt type P (input-collection) and M (menu) when nomatch is detected. The same technique explained in buildAnnouncementPrompt applies here. buildTransferPrompt In this method you custom-build the announcement for a node of type B (non-CTI, blind transfer) and C (CTI transfer). The same technique explained in buildAnnouncementPrompt applies here. Inside this method put the logics for every L nodes in the CSV. Just as in buildAnnouncementPrompt, you select which logic to be processed, based on the ID of the currently executing node, which can be examined this way: logic.getId();

executeLogic

The context (parameters, state) for the execution of your custom logic can be obtained from various sources: HttpSession, by invoking: Helper.getHttpSessionAttribute(mySession, key) ValBag, by invoking: Helper.getValueInBag(mySession, key) Points in the the trail, by invoking: Helper.getTrail(mySession).getLastPointById("6").getDatu m("result"). This example says: get the record of the latest execution of node 6, and read its result field. The value returned from this method is the Node to be executed next. You can obtain the correct instance of that node by invoking this method: Helper.getNodeById(mySession, 6); //goto to node 6 By correct here we mean: the node is registered in the CSV. If you want to instruct DynFlow to exit, simply return an instance

of Return node, this way: return Return.INSTANCE; If you return null from this method, the node ID specified in the CSV will be used instead by DynFlow to decide where to go next. handleCollectFailure In this method you make the decision where to go next in the case of failure during execution of an input-collection / menu node (after max retry has been reached). If you return null from this method, DynFlow will exit. logTrail This method is invoked by DynFlow on every transition from a node to the next. Here basically you can put your listener, whose task is to register history of IVR execution to whatever report database you have in the backend. By comparing the upcoming and the last point in trail, you can decide dynamically for example which report number to be saved in the database. I have seen requirement that says: in the transition from asking credit card number and verification of the number, save a point with ID 983 in the report database. That can be achieved this way (assuming node ID of creditcard inputcollection is 6, and node ID of card verification logic is 8): if (trail.getUpcomingPoint().getId().equals(8) && trail.getLast().getId().equals(6)) { //store a report with point 983 in the database } Or a slightly more complex / dynamic requirement: in the transition from asking credit card number and verification of the number, save a point with ID 983 in the report database when the card number starts with 44, or 984 otherwise. The code would be: if (trail.getUpcomingPoint().getId().equals(8) && trail.getLast().getId().equals(6)) { if (trail.getLast().getDatum(result).startsWith(44)) { //store a report with point 983 in the database } else { //store a report with point 984 in the database } } selectGrammar sendPopupData In this method you can specify which grammar to use for an inputcollection / menu node, depending on context of the execution. In this method you get a chance to make pass on call data to the receiving end, using whatever mechanism is appropriate in your

project, before the call transfer is made.

You might also like