Dynamics and Machine Learning: Consuming the predictive web service

By | January 8, 2018

In the previous post, I have stopped short of creating a plugin to consume the predictive web service directly from Dynamics. It still needs to happen to complete the whole exercise,  so.. it’s time to do it.

First of all, when I looked at the Machine Learning Studio today, I could not get to the sample web service code right away. The easiest way I found after some digging around is this:

image

image

image

And, then, I got this:

image

Which is not exactly the same screen I was looking at yesterday.. but, anyway, that should do it.

Unfortunately, it did not take me long to find out that the sample code uses async methods and json, so it would take a bit of an effort to get it all into the plugin. Fortunately, I did not need to do it that way – after all, the same page has a sample of the json, so I ended up re-writing that code using WebClient and pre-formatted json string:

using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Http.Headers;

namespace MachineLearning.Plugins
{
     class PredictiveService
     {

        public static string GetBestAssignee(string customerName, string productName, string[] assignees)
         {
             string bestAssignee = null;
             decimal bestTime = 1000000000;
             foreach(var assignee in assignees)
             {
                 decimal assigneeTime = GetPrediction(customerName, assignee, productName);
                 if (bestTime > assigneeTime)
                 {
                     bestAssignee = assignee;
                     bestTime = assigneeTime;
                 }
             }
             return bestAssignee;
         }

        public static decimal GetPrediction(string customerName, string assigneeName, string productName)
         {
             decimal result = 10000000;

            var requestJson = @”{{
                   “”Inputs””: {{
                     “”input1″”: {{
                       “”ColumnNames””: [
                         “”title””,
                         “”CustomerIdName””,
                         “”ita_FirstAssigneeName””,
                         “”ResolutionTime””,
                         “”ProductIdName””
                       ],
                       “”Values””: [
                         [
                           “”Title””,
                           “”{0}””,
                           “”{1}””,
                           “”0″”,
                           “”{2}””
                         ]
                       ]
                     }}
                   }},
                   “”GlobalParameters””: {{
                     “”Data gateway””: “”””
                   }}
                 }}”;

            requestJson = string.Format(requestJson, customerName, assigneeName, productName);

            const string apiKey = “…”; // Replace this with the API key for the web service
             var authorizationHeader = new AuthenticationHeaderValue(“Bearer”, apiKey);

             var uri = “…”;// Replace this with the URL of the web service
             var cli = new WebClient();
             cli.Headers[HttpRequestHeader.ContentType] = “application/json”;
             cli.Headers.Add(“Authorization”, “Bearer ” + apiKey);
             string response = cli.UploadString(uri, requestJson);
             int i = response.LastIndexOf(“\””);
             if(i > -1)
             {
                 int j = response.LastIndexOf(“\””, i – 1);
                 response = response.Substring(j + 1, i – j – 1);
                 result = decimal.Parse(response);
                 if (result < 0) result = 10000000;
             }
             return result;

         }
     }
}

Tested that in a console application quickly:

image

And went on to writing the plugin. The plugin is supposed to do this:

  • Get all possible assignee names (user names), product name, and client name
  • Pass all that information to the service above
  • Set “First Assignee” field based on the outcome of the GetBestAssignee method call above

Here is the plugin code:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Query;

namespace MachineLearning.Plugins
{
    public class CaseFirstAssigneePlugin : IPlugin
    {

        
        public void Execute(IServiceProvider serviceProvider)
        {
            
            IPluginExecutionContext context = (IPluginExecutionContext)
                serviceProvider.GetService(typeof(IPluginExecutionContext));

            IOrganizationServiceFactory serviceFactory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
            IOrganizationService service = serviceFactory.CreateOrganizationService(context.UserId);

            var postImage = context.PostEntityImages["PostImage"];
            if(postImage.Contains("productid") && postImage.Contains("customerid"))
            {
                string productName = ((EntityReference)postImage["productid"]).Name;
                string customerName = ((EntityReference)postImage["customerid"]).Name;
                QueryExpression qe = new QueryExpression("systemuser");
                qe.ColumnSet = new ColumnSet("fullname", "systeuserid");
                var users = service.RetrieveMultiple(qe);
                Entity bestAssignee = null;
                decimal bestTime = 1000000000;
                foreach (var u in users.Entities)
                {
                        decimal assigneeTime = MachineLearning.Plugins.PredictiveService.GetPrediction(
                            customerName, 
                            (string)u["fullname"], 
                            productName);
                        if (bestTime > assigneeTime)
                        {
                            bestAssignee = u;
                            bestTime = assigneeTime;
                        }
                }
                if (bestAssignee != null)
                {
                    Entity updatedEntity = new Entity(postImage.LogicalName);
                    updatedEntity.Id = postImage.Id;
                    updatedEntity["ita_firstassignee"] = bestAssignee.ToEntityReference();
                    service.Update(updatedEntity);
                }
                
            }
        }
    }
}

And here is how it’s been registered in Dynamics:

image

That should be it.. Time for a quick test?

The plugin is only registered on Create of the case entity (should not be difficult to adjust that, but you’ll need to add some logic to the plugin to decide what to do on Update if a case has already been assigned), so, for the test, I’ll create a new Case, and I’ll set Product and Customer fields. Once I push “save” button, the plugin will kick in, and, once the form reloads, I should see “First Assignee” field populated.. Kind of a lot of writing, but it’s really just this:

Step 1 – Creating a new Case:

image

Step 2 – verifying that “First Assignee” field has been populated:

image

That’s it! I now have a super intelligent case assignment mechanism that is using a predictive web service to choose first assignee according to the best predicted case resolution time.

PS. You realize it’s been just a proof of concept, right?Smile Don’t be too harsh on the results of those predictions.. after all, the model has been trained on 10 completely made up cases to start with.

Leave a Reply

Your email address will not be published. Required fields are marked *