Preparing the phone number for dialing

A question came up recently concerning adding the log distance dialing prefix to a phone number. I’ve developed a standard method of setting the outbound phone number. In my application the number to dial as well as the ANI value to use are passed in as part of the URL. I do this mainly because I make over 30,000 calls a day and I didn’t want to slow things down by doing a database lookup to get the phone numbers. I believe that the IVR should be a black box. It makes a call and reports the status – nothing more. No business logic and no database lookups to slow things down or require changes if the business logic changes. But that may be the subject of a future blog so I’ll pass on that for now.

 

 In my applications I set the MakeCall to the value returnd from GetPhoneNUmber() as shown below.

 

            MainMakeCall.CalledDirectoryNumber = GetPhoneNumber();

  

In GetPhoneNumber() I create the number to be dialed by a combination of what I spassed in and the configurations settings found in the web.config. The code below should be fairly easy to follow.

 

            private string GetPhoneNumber()

           {

                 // Get the number from the query string

                string phoneNumber = (string)GetQueryStringValue(“PhoneNumber”, true);

 

                // Use the outside dialing prefix if one is set

                string outsideLinePrefix = “”;

                if (null != ConfigurationSettings.AppSettings[“outsideLinePrefix”])

                {

                     outsideLinePrefix = ConfigurationSettings.AppSettings[“outsideLinePrefix”];

                }

                 // If there is a hard-coded number in the web.config file, use that

                if (null != ConfigurationSettings.AppSettings[“hardCodedNumberToDial”])

                {

                     return(ConfigurationSettings.AppSettings[“hardCodedNumberToDial”]);

                }

 

                // Now get the local area code

                string localAreaCode = “”;

                if (null != ConfigurationSettings.AppSettings[“localAreaCode”])

                {

                     localAreaCode = ConfigurationSettings.AppSettings[“localAreaCode”];

                }

                 // Strip anything other than digits from the phone number

                phoneNumber = new Regex(“[\\D]”).Replace(phoneNumber, “”);

                 // If we have 10 digits

                if (phoneNumber.Length == 10)

                {

                     // Remove the area code, if it’s the same, otherwise add the long-distance prefix

                     if (0 != localAreaCode.Length &&

                           phoneNumber.StartsWith(localAreaCode))

                     {

                           phoneNumber = phoneNumber.Substring(localAreaCode.Length);

                     }

                     else

                     {                   

// Add the long distance dialing prefix

                           phoneNumber = String.Format(“1{0}”, phoneNumber);

                     }

                }

                 // If this is an external number (7 digits or more), add the outside line prefix

                if (phoneNumber.Length >= 7)

                     phoneNumber = String.Format(“{0}{1}”, outsideLinePrefix, phoneNumber);

                 return phoneNumber;

           }

 

 I use the following method for getting parameters off of the URL. If the parameter is required but missing then I throw an exception and the application aborts the call. I’m including this here as it is referenced in the GetPhoneNumber method shown above. If you examine the method above you will see that the number to dial is required – that makes sense huh?

 

           internal string GetQueryStringValue(string queryStringKey, bool isRequired)

           {

                string queryStringValue = Server.UrlDecode(Request.QueryString.Get(queryStringKey));

                if (null == queryStringValue)

                {

                     if (isRequired)

                     {

                           throw new ApplicationException(

                                string.Format(“Query string does not contain a value for required key \”{1}\”.”,

                                queryStringKey));

                     }

                     else

                     {

                           queryStringValue = “”;

                     }

                }

                return queryStringValue;

           }

 

The web.config looks something like what is shown below. My application normally spoofs the ANI as we are making the calls for someone else and the ANI can change with each call. However, there are times when I want to hard code the ANI for testing purposes so I have a hardCodedANI value that I can set to whatever I need. The outsideLinePrefix only has a value when calls need to go through the PBX. For other calls (i.e. production calls) the prefix is not set. And finally I have a localAreaCode that is set to the area code of wherever the app is running so that I can add the ‘1’ dialing prefix if necessary.

 

     <appSettings>

          <add key=”outsideLinePrefix” value=”9″/>

          <!

          <add key=”hardCodedANI” value=”5555555555″/>

          –>

           <add key=”localAreaCode” value=”904”/>             

     </appSettings>

 

If you have any questions or a better way of doing this then don’t hesitate to email me or post comments.

 

New local site for SOA

You may have noticed that I recently added a section in the right navbar for blogs and web sites from my local community. I live in Jacksonville, Fl. and there is an active .Net community here. Since I live and work in with these guys I thought I would start linking to some of them. I realize that GotSpeech (and my blog) have a world wide audience but we have lots of local visitors too. Plus if you are ever visiting the area on business this will keep you informed with what is happening in my area.


In fact, if you ever are in the area give me a shout and we can hook up. I would love to spend some time talking about Speech Server with you or chowing down at one of the local eating/watering holes.


Dennis Bottjer is the current president of the Jacksonville Developers User Group (JAXDUG) and his partner David Strommer is the past president of JAXDUG.


Here’s what Dennis has to say about his latest adventure:


Drowning In Technical Debt focuses on Enterprise Architecture, SOA, and Line of Business Application Development (Smart Clients and Services Layers).  We hope to uncover and share enterprise best practices. 

 

 

My Apologies

GotSPeech.Net was down this morning for several hours due to an outage of some kind at our hosting facility. I still don’t have an explanation from them as to why we were down but I’m pressing them for one.


 


Please be assured that keeping GotSpeech up and available for the needs of our users is our number one priority. If this requires a change in hosting then that is what we will do. We have decided to be proactive and begin a search for new hosting as the outages we are experiencing (though not often) are more than we are willing to tolerate. So far the hosting has come out of my pocket but we do have some backing from some of our sponsors (some of whom have ads on the site). We will need to find a hosting facility that can give us reliable up time and fit within our budget.  


 


I’m sure we will be able to find a provider that can meet our criteria but we want to make sure we get the right one as we don’t want to move the site any more than necessary.


 


Thanks for your understanding and once again I offer my personal apology.

Hosting Multiple Applications


The question came up recently in one of our forums regarding testing new applications on a live server. This got me to thinking about hosting of multiple applications on one server.


Unfortunately with MSS 2004 you can technically only host one application. This is because TAS can only point to one web address for an inbound application and it only looks at one message queue for outbound applications. Fortunately there is a practical way around this restriction for both inbound and outbound and it involves code (which is what we all like to do). I’m going to show you away around this for an inbound call and briefly touch on how to handle the problem with outbound calls and thin I’ll finish with a quick peek at how MSS 2007 handles this.


As I stated for inbound calls, TAS can only point to one web page. Since we can’t change TAS then we have to consider how we can make that web page handle multiple applications. This is easily done if you think of the web page as an application router. Every call into your system will be sent to that page and then it will “route” the call to the proper application.


How does it do that? There are a couple of ways; for example you could ask the user which app they were calling for but that isn’t very elegant. A much better approach is to design your applications so that they answer to different phone numbers. Your router page would then look at the called DNIS and determine which application to run. Technically you only have one application but if you design the router correctly it can send the caller to any number of virtual applications.


This is really a very simple thing to do in your code. I do it in the JavaScript but it can be done in the code-behind. The web page that TAS points to contains nothing but an AnswerCall control with the OnClientConnected set to a JavaScript function called DetectCallDestination which looks like this -



 function DetectCallDestination(obj, callid, networkCallingDevice, networkCalledDevice)
 {


  switch (networkCalledDevice)
  {
      case “4511”: //Main Menu
      case “8600”: //Limited PROD
          LogMessage(“CALL DESTINATION”, “Main Application”); 
          SpeechCommon.Navigate(“MainMenu.aspx”);
          break;
      case “1689”: // 800-123-9543
      case “4689”: 
          LogMessage(“CALL DESTINATION”, “Application 2″); 
          SpeechCommon.Navigate(“App2Menu.aspx”);
          break;
      case “1559”: // 800-999-9290
      case “4659”:
          LogMessage(“CALL DESTINATION”, “Application 3″); 
          SpeechCommon.Navigate(“App3Menu.aspx”);
          break;
      case “1111”: // this is for debugging/testing 
           // it points to whaterver needs testing
          //debugger   
          SpeechCommon.Navigate(“App3SubDialog.aspx”);
          break;
      case “2608”:
      case “3061234”:
      case “9043061234”:
          LogMessage(“CALL DESTINATION”, “App Test”); 
          SpeechCommon.Navigate(“AppTest.aspx”);
          break;
      case “2999”:
      case “3062999”:
      case “9043062999”:
          LogMessage(“CALL DESTINATION”, “System Test”); 
          SpeechCommon.Navigate(“SystemTest.aspx”);
          break;
      default:
          LogMessage(“CALL DESTINATION”, “Main pplication (default)”); 
          SpeechCommon.Navigate(“MainMenu.aspx”);
   }
   return true;
 }


As you can see you can add any number of applications to the router by adding another case statement checking for the correct DNIS. My applications reside behind our PBX so I check for the 4 digit extension code as well as the 7 & 10 digit numbers is some cases as I sometimes can’t control how the DNIS comes in.


I have a default case that sends the caller to the primary application incase a new number gets pointed to my apps by accident. I also have some special cases I handle. For example, the case “1111” is something that I use when debugging an application. Often times I don’t want to navigate down through several menus (that have already been tested) to get to the area I need to test so I use “1111” in TASIM to get me as close to the code I’m testing as possible.


For outbound calls you will need to do something similar except that you will have to pass the application to run as a parameter on the command line. Not hard to do at all so I’ll leave that as a homework assignment for you to play with. If you get stuck on this then email and I’ll help out.


MSS 2007 has a better approach to this when doing SIP. In the MMC You can set the URL and the Message Queue at the application level along with which SIP addresses it answers to. This makes a much neater and a more practical approach.


As with most things in life there is more that one way to do this. If you have addressed this problem differently I would love to hear how you solved the problem.


 



 

My speech enabled radio project and other stuff

As I blogged about previously, I have a new WinRadio WR-G305e. This radio is phenomenal and I love the control that the interface gives me. Reception was lousy at first as the only antenna I had was the test antenna that came with it and it was strung out across the floor. My house is metal framed and reception with an indoor antenna really stinks. I ordered a Par End Fedz from Grove Enterprise and it has made a world of difference. Right now I just run the antenna out a window and stretch it across the front yard but the changes in my reception has been unbelievable. I’ve listened to hams on 80 meters, SW stations from as far away as Maine and the Canadian time signals on CHU. That is a 10kw signal on 7335 kHz originating in Ottawa, Ontario, Canada. This may not seem important to you but it really brings back memories for me.

If you read my previous posts you know know that I intend to speech enable this radio and broadcast the stations to the Internet using streaming audio. This won’t be a quick project as I have to get my antennas mounted up in the air then start experimenting with the API for the WinRadio. With everything I have going on right now it isn’t my highest priority project but rather one that I intend on plugging away at in my spare time. I really don’t have a choice but to follow through with this project as the guys over at Wireless Speech Recognition.. picked up on my blog post and blogged about it. Nothing like a little pressure to keep you honest.

 

Yesterday I took my youngest son (9yrs old) to a local Hamfest put on by the North Florida Amateur Radio Society. A Hamfest is just a big garage sale or flea market for hams. You can find all sorts of new and used equipment along with some great conversations and story telling at these events. I bought my son an Eton FR300 for $20 and it was probably the best money I ever spent. He was so excited the whole time we were there and told me several times that it was the best day of his life and that he wants to go back next year.

Radio $20. BBQ for lunch $15. Time spent with my son – priceless.

One of the hams that was present gave me a National NCX-3 80, 40 and 20 meter SSB transceiver. This radio was built in the early to mid 60s and was a very good receiver when it was new. I’m excited to own this as I do have a fondness for old equipment. In those days radios looked like radios with none of the digital display stuff and cheap plastic you see today. Those old radios also had tubes in them instead of transistors or printed circuits and the warm glow from the tubes was all part of the magic.

Yesterday wasn’t without it’s sad moments too. I was really touched by some of the older gentlemen that were present. Some of these guys have been in the hobby for over 40 years and there is a lot of history wrapped up in them. It really saddened me to think about what we will lose when then these guys pass on. For some of them their families have no interest or understanding of the hobby and the heritage (plus the small fortune in some cases) that they have wrapped up in their equipment. I may just be sentimental but I hate to see all the history that will slip away when these guys become silent keys.

Two more Speech bloggers

I recently stumbled on to “Speaking Your Language” a new blog by Andrew Morris. Andrew lives in Liverpool, UK and is the managing director of Aeolian a consulting form that specializes in speech recognition. This is the second British venture into speech that I’ve encountered in recent weeks. The other is MUCUGUK that I reported on earlier.

The other blog belongs to Michael Dunn who is a consultant out of Minneapolis that seems to be really interested in speech. Michael recently spoke about Speech Server at an event in Edina, MN but he hasn’t blogged about how that went. I would love to hear the details or see the slides.

It’s really great to see more people blogging about Speech Server.

Andrew and Michael, if there is anything I can do to help you then don’t hesitate to contact me. The “gotspeech guy” is willing to do anything he can to help out.

Rough Month

September was a really rough month for me. You probably noticed that I’ve been kind of quiet for the last month. Well that was mainly because of two things that happened.

I got sick on the 10th with some kind of flue bug that hit me hard. I only missed one day of work but sometimes the only reason that I didn’t come home from work early was because I didn’t feel up to driving. I still haven’t kicked it completely but I’m back to about 98% of my old self. I can’t recall ever being sick for such a long period of time. I kept at it because of the second thing that occurred.

As all of this was going on I had a huge project at work that was coming due. We changed the identity and access methods on our applications so I had to modify the data access routines from an old IVR application that has been in production for 4 years. I won’t name the platform for this IVR system but the application is one huge state machine that I didn’t write. I went away for training on this system 3 years ago and never did any coding with that language until this summer.

I finished the code changes on time and turned it over to QA for testing. They encountered some problems that were data related that we fixed by getting the data cleaned up and in a consistent state. Then QA came up with this weird issue where certain calls worked one way and other calls worked differently.

When an application reacts like this you have to suspect the data. So, I set about trying to determine what was different between the callers. We found two users that I could test with where one worked fine and the other exhibited the problem. After spending some time analyzing the database and the records returned by the different stored procedures I figured out what the difference was. I can’t disclose what it was but I found that the bug only occurred in one really obscure set of circumstances. Armed with that information I turned to the debugger.

As I stated this application was one gigantic state machine containing 11 different modules. Debugging the call turned into a nightmare as I had to document the flow during a test call. Luckily the debugger lets you step through the call and see the contents of the different variables. Unfortunately it doesn’t let you see all of the code as it goes from step to step (think of steps that are dragged onto a giant grid to show the logic flow). I had to remotely connect to the server and every time I needed to know more about one of the steps I had to switch to the development serve so that I could view the actual code. This was slow going and I did a lot of screen prints so that I could document what was happening.

I spent 6 hours in the debugger on one phone call stepping through the logic piece by piece until I finally found the problem. As it turns out the problem wasn’t in the data interface module that I had modified but was in another module. It also turned out to be a logic error on the part of the original programmer and an issue that no one had complained about in the 4 years this app has been in production.

The problem occurred within an if statement that didn’t cover all of the possible conditions. The if was written with the main condition being if var > 1 then do this else do something different. The intent was to determine if the caller had multiple tractors available (the else part) and on the surface it may have made sense to the original programmer. But a problem arose when the caller had only one tractor available. This should have been a case statement with conditions for 0 tractors, 1 tractor and 2 or more tractors available.

This was probably one of the most difficult debugging sessions I’ve ever encountered. I’m pretty good at attacking problems in a logical manner and eliminating things until I track down the bug. However tackling something like this while sick probably didn’t help matters. The stress of all of this may also have contributed to my recovery being slowed.

So, that is why I have been somewhat quiet this last month. I’ve got several things I want to blog about but I’ll spread them out over the next several days.

It sure feels good to be back in the saddle again and I would love to hear from you guys about your worst debugging experience.