<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:media="http://search.yahoo.com/mrss/"><channel><title><![CDATA[text analysis - Alexander Development]]></title><description><![CDATA[text analysis - Alexander Development]]></description><link>https://alexanderdevelopment.net/</link><image><url>https://alexanderdevelopment.net/favicon.png</url><title>text analysis - Alexander Development</title><link>https://alexanderdevelopment.net/</link></image><generator>Ghost 1.20</generator><lastBuildDate>Fri, 24 Apr 2026 14:21:24 GMT</lastBuildDate><atom:link href="https://alexanderdevelopment.net/tag/text-analysis/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[Using ML.NET in an OpenFaaS function]]></title><description><![CDATA[<div class="kg-card-markdown"><p>Last week at its annual Build conference, Microsoft announced <a href="https://www.microsoft.com/net/learn/apps/machine-learning-and-ai/ml-dotnet">ML.NET</a>, an &quot;open source and cross-platform machine learning framework&quot; that runs in .NET Core. I took a look at the <a href="https://www.microsoft.com/net/learn/apps/machine-learning-and-ai/ml-dotnet/get-started/windows">getting started</a> samples and realized ML.NET would be a great tool to use in OpenFaas functions.</p>
<p>I</p></div>]]></description><link>https://alexanderdevelopment.net/post/2018/05/17/using-ml-net-in-an-openfaas-function/</link><guid isPermaLink="false">5afe3ef397f5e30001931b56</guid><category><![CDATA[OpenFaaS]]></category><category><![CDATA[serverless]]></category><category><![CDATA[C#]]></category><category><![CDATA[machine learning]]></category><category><![CDATA[text analysis]]></category><dc:creator><![CDATA[Lucas Alexander]]></dc:creator><pubDate>Fri, 18 May 2018 03:20:22 GMT</pubDate><media:content url="https://alexanderdevelopment.net/content/images/2018/05/chrome_2018-05-17_22-16-59.png" medium="image"/><content:encoded><![CDATA[<div class="kg-card-markdown"><img src="https://alexanderdevelopment.net/content/images/2018/05/chrome_2018-05-17_22-16-59.png" alt="Using ML.NET in an OpenFaaS function"><p>Last week at its annual Build conference, Microsoft announced <a href="https://www.microsoft.com/net/learn/apps/machine-learning-and-ai/ml-dotnet">ML.NET</a>, an &quot;open source and cross-platform machine learning framework&quot; that runs in .NET Core. I took a look at the <a href="https://www.microsoft.com/net/learn/apps/machine-learning-and-ai/ml-dotnet/get-started/windows">getting started</a> samples and realized ML.NET would be a great tool to use in OpenFaas functions.</p>
<p>I decided to write a proof-of-concept function based on the ML.NET sentiment <a href="https://docs.microsoft.com/en-us/dotnet/machine-learning/tutorials/sentiment-analysis">analysis sample</a>. Because the function needs a trained model before it can run, you actually need to use a separate application to generate the model and save it as a file. Then you can include the model as part of your function deployment.</p>
<p>Here's a screenshot of my function in action. <img src="https://alexanderdevelopment.net/content/images/2018/05/Postman_2018-05-17_21-42-58.png#img-thumbnail" alt="Using ML.NET in an OpenFaaS function"></p>
<p>You can get the code for my OpenFaas sentiment analysis function <a href="https://github.com/lucasalexander/faas-functions/tree/master/get_sentiment_mlnet">here</a>, and the code for the application that generates the model is available <a href="https://github.com/lucasalexander/mlnet-samples/tree/master/sentiment-analysis">here</a>.</p>
</div>]]></content:encoded></item><item><title><![CDATA[Azure Text Analytics sentiment analysis with North52]]></title><description><![CDATA[<div class="kg-card-markdown"><p>For the last several months I've been working on an enterprise Dynamics CRM project where one of our goals is to minimize the amount of custom code we write by using <a href="http://www.north52.com/business-process-activities/">North52's Business Process Activities</a>. I had not been exposed to North52 before working on this project, but I have</p></div>]]></description><link>https://alexanderdevelopment.net/post/2016/05/17/azure-text-analytics-sentiment-analysis-with-north52/</link><guid isPermaLink="false">5a5837236636a30001b9782d</guid><category><![CDATA[Microsoft Dynamics CRM]]></category><category><![CDATA[text analysis]]></category><category><![CDATA[integration]]></category><category><![CDATA[Azure]]></category><category><![CDATA[analytics]]></category><category><![CDATA[web services]]></category><category><![CDATA[North52]]></category><dc:creator><![CDATA[Lucas Alexander]]></dc:creator><pubDate>Tue, 17 May 2016 13:19:37 GMT</pubDate><media:content url="https://alexanderdevelopment.net/content/images/2016/05/2016-05-17_08-51-04.png" medium="image"/><content:encoded><![CDATA[<div class="kg-card-markdown"><img src="https://alexanderdevelopment.net/content/images/2016/05/2016-05-17_08-51-04.png" alt="Azure Text Analytics sentiment analysis with North52"><p>For the last several months I've been working on an enterprise Dynamics CRM project where one of our goals is to minimize the amount of custom code we write by using <a href="http://www.north52.com/business-process-activities/">North52's Business Process Activities</a>. I had not been exposed to North52 before working on this project, but I have been pleasantly surprised with how much it has allowed our mostly functional resources to achieve without needing technical assistance.</p>
<p>While looking through North52's documentation a while back, I noticed it could be used to <a href="http://support.north52.com/knowledgebase/articles/488697-introduction-to-north52-s-webfusion">call a REST web service</a>. This got me thinking about how I could rework my <a href="https://alexanderdevelopment.net/post/2015/10/12/sentiment-analysis-in-dynamics-crm-using-azure-text-analytics/">Sentiment analysis in Dynamics CRM using Azure Text Analytics</a> sample using North52 instead of a custom workflow activity.</p>
<p>I found I was able to replace the custom workflow activity with a North52 <a href="http://www.north52.com/business-process-activities/process-genie-for-microsoft-dynamics-crm-xrm/">Process Genie</a>. It executes a <a href="https://datamarket.azure.com/dataset/amla/text-analytics">Smart Flow</a> to call the Azure Machine Learning <a href="https://datamarket.azure.com/dataset/amla/text-analytics">Text Analytics API</a> and then return the result to the calling CRM dialog.</p>
<p>Here's the formula I used in my Process Genie:</p>
<pre><code>SmartFlow(
  SetVar('jsoninput', CreateJObject( 
     CreateJProperty('Inputs', 
           CreateJArray(CreateJObject(
                           CreateJProperty('Id', '1'),  
                           CreateJProperty('Text', [account.texttoanalyze]) 
                              )
                        )
                     )
               )
          ),

  CallRestAPI(
      SetRequestBaseURL('https://api.datamarket.azure.com/data.ashx/amla/text-analytics/v1'),
      SetRequestResource('/GetSentimentBatch'),
      SetRequestDetails('POST'),
      SetRequestHeaders(),
      SetRequestParams('RawContentTextJSON',GetVar('jsoninput')),
      SetRequestAuthenticationBasic('AccountKey','YOUR_AZURE_ML_API_KEY_HERE'),
      SetRequestFiles(),
      SetRequestExpected('OK'),
      SetRequestActionPass(SetVar('result', GetVarJsonValue('SentimentBatch{0}.Score'))),
      SetRequestActionFail(SetVar('result', 'ERROR' + GetVarJsonValue('Errors{0}.Message')))
    ), 

   SmartFlowReturn(GetVar('result'))
)
</code></pre>
<p>Here's a screenshot of my CRM dialog:<br>
<img src="https://alexanderdevelopment.net/content/images/2016/05/2016-05-16_19-30-01.png#img-thumbnail" alt="Azure Text Analytics sentiment analysis with North52"></p>
<p>To execute the Process Genie, you use a North52 N52 Process Genie step like this:<br>
<img src="https://alexanderdevelopment.net/content/images/2016/05/2016-05-16_19-32-09.png#img-thumbnail" alt="Azure Text Analytics sentiment analysis with North52"></p>
<p>The Formula ShortCode value is the short code of the North52 Process Genie. The Formula Parameter Xml value contains the text to analyze from the dialog input in the format expected by the formula:</p>
<p><code>&lt;account&gt;&lt;texttoanalyze&gt;{Response Text(Get text)}&lt;/texttoanalyze&gt;&lt;/account&gt;</code></p>
<p>If were going to use this in real solution, I would not hardcore the Azure ML API key directly in the formula. Other than that I think this is a production-ready approach.</p>
<p>What do you think? Would you consider using something like this as an alternative to writing your own custom code?</p>
</div>]]></content:encoded></item><item><title><![CDATA[Webcast: Sentiment Analysis in Microsoft Dynamics CRM using Azure Text Analytics]]></title><description><![CDATA[<div class="kg-card-markdown"><p>On Monday, April 11, at 12 p.m. EDT, I will be presenting a <a href="https://msdynamicsworld.webex.com/msdynamicsworld/onstage/g.php?MTID=e9c658b74d1caa0e4f1063423421cd47c&amp;SourceId=la">webcast</a> at MSDynamicsWorld.com that will show how a custom integration with Microsoft Azure Machine Learning can be used to perform sentiment analysis on any data stored in Dynamics CRM.</p>
<p>Custom sentiment analysis integrations can enable</p></div>]]></description><link>https://alexanderdevelopment.net/post/2016/04/06/webcast-sentiment-analysis-in-microsoft-dynamics-crm-using-azure-text-analytics/</link><guid isPermaLink="false">5a5837236636a30001b97819</guid><category><![CDATA[Microsoft Dynamics CRM]]></category><category><![CDATA[machine learning]]></category><category><![CDATA[Azure]]></category><category><![CDATA[text analysis]]></category><category><![CDATA[demonstrations]]></category><dc:creator><![CDATA[Lucas Alexander]]></dc:creator><pubDate>Wed, 06 Apr 2016 14:37:20 GMT</pubDate><media:content url="https://alexanderdevelopment.net/content/images/2016/04/sentiment-dialog.png" medium="image"/><content:encoded><![CDATA[<div class="kg-card-markdown"><img src="https://alexanderdevelopment.net/content/images/2016/04/sentiment-dialog.png" alt="Webcast: Sentiment Analysis in Microsoft Dynamics CRM using Azure Text Analytics"><p>On Monday, April 11, at 12 p.m. EDT, I will be presenting a <a href="https://msdynamicsworld.webex.com/msdynamicsworld/onstage/g.php?MTID=e9c658b74d1caa0e4f1063423421cd47c&amp;SourceId=la">webcast</a> at MSDynamicsWorld.com that will show how a custom integration with Microsoft Azure Machine Learning can be used to perform sentiment analysis on any data stored in Dynamics CRM.</p>
<p>Custom sentiment analysis integrations can enable a number of interesting processes in Dynamics CRM including:</p>
<ul>
<li>Routing emails to queues based on sentiment</li>
<li>Dynamically load agent scripts</li>
<li>Reporting on interactions</li>
</ul>
<p>This webinar will include an introduction to Azure Machine Learning, an overview of a sample solution and a code deep dive. Programming knowledge will be helpful during the code deep dive, but it is not required for most of the discussion.</p>
<p>You can register for the session <a href="https://msdynamicsworld.webex.com/msdynamicsworld/onstage/g.php?MTID=e9c658b74d1caa0e4f1063423421cd47c&amp;SourceId=la">here</a>.</p>
</div>]]></content:encoded></item><item><title><![CDATA[Sentiment analysis in Dynamics CRM using Azure Text Analytics]]></title><description><![CDATA[<div class="kg-card-markdown"><p>Last year I created a <a href="https://github.com/lucasalexander/CRM-IdolOnDemand-Tools">proof-of-concept solution</a> that showed how to integrate Dynamics CRM with <a href="https://www.havenondemand.com/">HP Haven OnDemand</a> (then called HP IDOL OnDemand) to perform sentiment analysis and index records to support &quot;find similar&quot; queries. While I was working through the <a href="https://challenge.azurecon.com/">AzureCon challenge</a> a few weeks ago, I</p></div>]]></description><link>https://alexanderdevelopment.net/post/2015/10/12/sentiment-analysis-in-dynamics-crm-using-azure-text-analytics/</link><guid isPermaLink="false">5a5837226636a30001b97775</guid><category><![CDATA[Microsoft Dynamics CRM]]></category><category><![CDATA[CRM 2015]]></category><category><![CDATA[text analysis]]></category><category><![CDATA[Azure]]></category><category><![CDATA[analytics]]></category><category><![CDATA[web services]]></category><dc:creator><![CDATA[Lucas Alexander]]></dc:creator><pubDate>Mon, 12 Oct 2015 22:36:23 GMT</pubDate><media:content url="https://alexanderdevelopment.net/content/images/2015/10/dialog-2.png" medium="image"/><content:encoded><![CDATA[<div class="kg-card-markdown"><img src="https://alexanderdevelopment.net/content/images/2015/10/dialog-2.png" alt="Sentiment analysis in Dynamics CRM using Azure Text Analytics"><p>Last year I created a <a href="https://github.com/lucasalexander/CRM-IdolOnDemand-Tools">proof-of-concept solution</a> that showed how to integrate Dynamics CRM with <a href="https://www.havenondemand.com/">HP Haven OnDemand</a> (then called HP IDOL OnDemand) to perform sentiment analysis and index records to support &quot;find similar&quot; queries. While I was working through the <a href="https://challenge.azurecon.com/">AzureCon challenge</a> a few weeks ago, I thought it would be an interesting exercise to update my sentiment analysis code to work with the <a href="https://datamarket.azure.com/dataset/amla/text-analytics">Text Analytics</a> offering from the Microsoft Azure Marketplace.</p>
<h4 id="theapproach">The approach</h4>
<p>As with my Haven OnDemand solution, the approach I'm using with Azure relies on a custom workflow activity that does the following:</p>
<ol>
<li>Parse a supplied text input and strip any HTML tags using a helper function.
</li><li>Create a JSON sentiment analysis request and post it to Azure Text Analytics with an HttpWebRequest.
</li><li>Deserialize the JSON respsonse returned by Azure Text Analytics to a custom class object using a DataContractJsonSerializer.
</li><li>Return the sentiment score to the calling process.
</li></ol>
<p>There are two main differences with the my Azure Text Analytics solution:</p>
<ol>
<li>The Azure service only returns a sentiment score, so this custom workflow activity doesn't return a positive/negative string value.
</li><li>Instead of embedding an access key in the custom workflow activity code, I've made it a parameter, which means you can take the solution straight from GitHub and start using it in your CRM organization as soon as you sign up for the Text Analytics service.
</li></ol>
<h4 id="thesolutioninaction">The solution in action</h4>
<p>Here's a sample dialog I've created to demonstrate the use of the custom workflow activity.<br>
<img src="https://alexanderdevelopment.net/content/images/2015/10/dialog.png#img-thumbnail" alt="Sentiment analysis in Dynamics CRM using Azure Text Analytics"></p>
<p><img src="https://alexanderdevelopment.net/content/images/2015/10/dialog-01-input.png#img-thumbnail" alt="Sentiment analysis in Dynamics CRM using Azure Text Analytics"></p>
<p><img src="https://alexanderdevelopment.net/content/images/2015/10/dialog-02-params.png#img-thumbnail" alt="Sentiment analysis in Dynamics CRM using Azure Text Analytics"></p>
<p><img src="https://alexanderdevelopment.net/content/images/2015/10/dialog-03-output.png#img-thumbnail" alt="Sentiment analysis in Dynamics CRM using Azure Text Analytics"></p>
<p>Here's how the process works for the sample text &quot;I hate you.&quot;</p>
<p><img src="https://alexanderdevelopment.net/content/images/2015/10/hate-01.png#img-thumbnail" alt="Sentiment analysis in Dynamics CRM using Azure Text Analytics"></p>
<p><img src="https://alexanderdevelopment.net/content/images/2015/10/hate-02.png#img-thumbnail" alt="Sentiment analysis in Dynamics CRM using Azure Text Analytics"></p>
<p>Wrapping it up on a more positive note, here's the same dialog with &quot;I love you&quot; instead.</p>
<p><img src="https://alexanderdevelopment.net/content/images/2015/10/love-01.png#img-thumbnail" alt="Sentiment analysis in Dynamics CRM using Azure Text Analytics"></p>
<p><img src="https://alexanderdevelopment.net/content/images/2015/10/love-02.png#img-thumbnail" alt="Sentiment analysis in Dynamics CRM using Azure Text Analytics"></p>
<p>As you can see, the score for &quot;I hate you&quot; is about .06, and the score for &quot;I love you&quot; is about .91, which makes sense as scores closer to one are more positive, and scores closer to zero are more negative.</p>
<h4 id="thecode">The code</h4>
<p>You can download all the custom code and a CRM solution extract from my <a href="https://github.com/lucasalexander/Crm-Sample-Code/tree/master/CrmAzureTextAnalysis">Crm-Sample-Code repository on GitHub</a>. Let me know what you think in the comments!</p>
</div>]]></content:encoded></item><item><title><![CDATA[Using IDOL OnDemand for text analysis in Dynamics CRM - part 3]]></title><description><![CDATA[<div class="kg-card-markdown"><p>In my <a href="https://alexanderdevelopment.net/post/2014/09/24/using-idol-ondemand-for-text-analysis-in-dynamics-crm-part-2/">last post</a> I provided a detailed walkthrough of how to perform sentiment analysis on incoming emails received in Microsoft Dynamics CRM by parsing them with <a href="https://www.idolondemand.com/" target="_blank" rel="nofollow">HP IDOL OnDemand’s</a> sentiment analysis API and then storing the calculated sentiment values on the email records. In today’s post, I</p></div>]]></description><link>https://alexanderdevelopment.net/post/2014/09/28/using-idol-ondemand-for-text-analysis-in-dynamics-crm-part-3/</link><guid isPermaLink="false">5a5837226636a30001b9773a</guid><category><![CDATA[Microsoft Dynamics CRM]]></category><category><![CDATA[CRM 2013]]></category><category><![CDATA[IDOL OnDemand]]></category><category><![CDATA[text analysis]]></category><dc:creator><![CDATA[Lucas Alexander]]></dc:creator><pubDate>Mon, 29 Sep 2014 00:00:00 GMT</pubDate><content:encoded><![CDATA[<div class="kg-card-markdown"><p>In my <a href="https://alexanderdevelopment.net/post/2014/09/24/using-idol-ondemand-for-text-analysis-in-dynamics-crm-part-2/">last post</a> I provided a detailed walkthrough of how to perform sentiment analysis on incoming emails received in Microsoft Dynamics CRM by parsing them with <a href="https://www.idolondemand.com/" target="_blank" rel="nofollow">HP IDOL OnDemand’s</a> sentiment analysis API and then storing the calculated sentiment values on the email records. In today’s post, I will show a similar, but slightly more complex integration that enables &quot;find similar&quot; functionality for emails stored in Dynamics CRM.</p>
<p>Continuing with the scenario from my previous post, let’s assume you manage a Microsoft Dynamics CRM system (online or on-premise) for an organization that provides customer service and support via a variety of channels including phone, email and live chat. In order to help agents resolve inbound email cases more quickly, the operations team has asked you to add an embedded display on the email form that shows conceptually similar email records so the agents can easily view them. Just as before, we’re in luck because IDOL OnDemand has a <a href="https://www.idolondemand.com/developer/apis/findsimilar#overview" target="_blank" rel="nofollow">find similar API</a>.</p>
<h4 id="thesolutionapproach">The solution approach</h4>
<p>Unlike the sentiment analysis solution I discussed previously where we used a workflow to retrieve a sentiment score and write it directly to the email record, the find similar email display will require two different calls to the IDOL OnDemand service because to find similar items, IDOL OnDemand needs to have an index of items to search. We’ll need to populate this index with emails as they’re received, and then we’ll run the find similar search against the index when agents view an email record. Thus, the solution I am showing today has two pieces:</p>
<ol><li>First, there is a custom workflow activity that adds inbound emails to an IDOL OnDemand text index using the <a href="https://www.idolondemand.com/developer/apis/addtotextindex" target="_blank" rel="nofollow">add to text index API</a>.</li><li>Second, there is an HTML web resource that can be embedded on the email form that uses an AJAX call to the find similar API to retrieve similar emails from the index and then display summary data and a hyperlink to the actual CRM record for each result to the end user.</li></ol>
<h4 id="creatingtheindex">Creating the index</h4>
<p>Before we can populate an index with data, we need to have an index that can be populated. Although IDOL OnDemand offers APIs that can be used to manage indexes programmatically, it’s easier in this case to use the index management tools that are available on the <a href="https://www.idolondemand.com/account/account.html" target="_blank" rel="nofollow">main account screen</a>. On that screen, click the &quot;Create, Manage, Index and Search your own Text Indexes&quot; link, then click the &quot;create text index&quot; button on the next page. Give your index a name, and then just accept the default options for each of the following prompts in the wizard.* Remember the name you selected because you’ll need to use it later. My index name is &quot;lucas-test.&quot;</p>
<h4 id="theindexingprocess">The indexing process</h4>
<p>IDOL OnDemand can index JSON documents that contain both standard and custom fields, so we’re going to create a JSON object that contains the email body, the email subject line and the email activity id as the index reference (basically a primary key) field. Because an IDOL OnDemand index has a standard field called title that is, per the <a href="https://www.idolondemand.com/developer/docs/StandardFlavor.html" target="_blank" rel="nofollow">index developer documentation</a>, considered &quot;highly relevant,&quot; we’ll also set that to the value of the email subject. Our logical mappings will look like this:</p>
<ul>
<li>Email "body" (description) -&gt; Index "content"
</li><li>Email "subject" -&gt; Index "title"
</li><li>Email "subject" -&gt; Index "subject"
</li><li>Email "activityid" -&gt; Index "reference"
</li></ul>
<p>You can, of course, index lots of other fields like &quot;to,&quot; &quot;from,&quot; &quot;date,&quot; etc., and the more complete your index, the better your search results will be.</p>
<p>In order to populate the index with the fields above, the custom workflow activity needs input parameters for the email body, subject and activity id. Here's how we define them:<pre><code>[Input(&quot;Content&quot;)]<br>
public InArgument&lt;String&gt; Content { get; set; }<br>
[Input(&quot;Subject&quot;)]<br>
public InArgument&lt;String&gt; Subject { get; set; }<br>
[Input(&quot;Email&quot;)]<br>
[ReferenceTarget(&quot;email&quot;)]<br>
public InArgument&lt;EntityReference&gt; Email { get; set; }</code></pre></p>
<p>We also need to set up three variables to help us call IDOL OnDemand. They are the URL for the find similar API, the API key to authorize our requests and the name of the text index that will hold the email data:<pre><code>//address of the service to which you will post your json messages<br>
private string _webAddress = &quot;<a href="https://api.idolondemand.com/1/api/sync/addtotextindex/v1">https://api.idolondemand.com/1/api/sync/addtotextindex/v1</a>&quot;;<br>
//name of the text index<br>
private string _indexName = &quot;lucas-test&quot;;<br>
//idol ondemand api key<br>
private string _apiKey = &quot;XXXXXXXXXXXXXXXX&quot;;</code></pre></p>
<p>Now we’re ready to index the inbound email using the following steps:</p>
<ol><li>Check whether the email description field contains text.</li><li>If it does, strip HTML tags using a helper function.</li><li>Serialize the index request to a JSON object.</li><li>Pass the index request object to IDOL OnDemand with an HttpWebRequest. (In order to keep this example simple, we’re going to ignore the response from IDOL OnDemand, but you might want to do something with the response if you try this out on your own.)</li></ol>
<p>Here's the code that does all of this:</p>
<pre><code>string inputText = Content.Get(executionContext);
if (inputText != string.Empty)
{
	inputText = HtmlTools.StripHTML(inputText);
	IndexDocument myDoc = new IndexDocument
	{
	 Content = inputText,
	  Reference = (Email.Get(executionContext)).Id.ToString(),
	  Subject = Subject.Get(executionContext),
	  Title = Subject.Get(executionContext)
	};
	DocumentWrapper myWrapper = new DocumentWrapper();
	myWrapper.Document = new List&lt;IndexDocument&gt;();
	myWrapper.Document.Add(myDoc);
	//serialize the myjsonrequest to json
	System.Runtime.Serialization.Json.DataContractJsonSerializer serializer = new System.Runtime.Serialization.Json.DataContractJsonSerializer(myWrapper.GetType());
	MemoryStream ms = new MemoryStream();
	serializer.WriteObject(ms, myWrapper);
	string jsonMsg = Encoding.Default.GetString(ms.ToArray());
	//create the webrequest object and execute it (and post jsonmsg to it)
	HttpWebRequest req = (HttpWebRequest)HttpWebRequest.Create(\_webAddress);
	//set request content type so it is treated as a regular form post
	req.ContentType = "application/x-www-form-urlencoded";
	//set method to post
	req.Method = "POST";
	StringBuilder postData = new StringBuilder();
	//HttpUtility.UrlEncode
	//set the apikey request value
	postData.Append("apikey=" + System.Uri.EscapeDataString(\_apiKey) + "&amp;");
	//postData.Append("apikey=" + \_apiKey + "&amp;");
	//set the json request value
	postData.Append("json=" + jsonMsg + "&amp;");
	//set the index name request value
	postData.Append("index=" + _indexName);
	//create a stream
	byte[] bytes = System.Text.Encoding.ASCII.GetBytes(postData.ToString());
	req.ContentLength = bytes.Length;
	System.IO.Stream os = req.GetRequestStream();
	os.Write(bytes, 0, bytes.Length);
	os.Close();
	//get the response
	System.Net.WebResponse resp = req.GetResponse();
	//deserialize the response to a ResponseBody object
	ResponseBody myResponse = new ResponseBody();
	System.Runtime.Serialization.Json.DataContractJsonSerializer deserializer = new System.Runtime.Serialization.Json.DataContractJsonSerializer(myResponse.GetType());
	myResponse = deserializer.ReadObject(resp.GetResponseStream()) as ResponseBody;
}</code></pre>
<p>The StripHTML function is the same function <a href="http://www.codeproject.com/Articles/11902/Convert-HTML-to-Plain-Text" target="_blank" rel="nofollow">from CodeProject</a> that used in my sentiment analysis post, and the code is included in the download at the end of this post.</p>
<p>Here are the classes used for serializing the JSON request and deserializing the JSON response:<pre><code>//DataContract decoration necessary for serialization/deserialization to work properly<br>
[DataContract]<br>
public class DocumentWrapper<br>
{<br>
//datamember name value indicates name of json field to which data will be serialized/from which data will be deserialized<br>
[DataMember(Name = &quot;document&quot;)]<br>
public List&lt;IndexDocument&gt; Document { get; set; }<br>
}<br>
//DataContract decoration necessary for serialization/deserialization to work properly<br>
[DataContract]<br>
public class IndexDocument<br>
{<br>
[DataMember(Name = &quot;title&quot;)]<br>
public string Title { get; set; }<br>
[DataMember(Name = &quot;reference&quot;)]<br>
public string Reference { get; set; }<br>
[DataMember(Name = &quot;subject&quot;)]<br>
public string Subject { get; set; }<br>
[DataMember(Name = &quot;content&quot;)]<br>
public string Content { get; set; }<br>
}<br>
//DataContract decoration necessary for serialization/deserialization to work properly<br>
[DataContract]<br>
public class ResponseBody<br>
{<br>
[DataMember(Name = &quot;index&quot;)]<br>
public string Index { get; set; }<br>
[DataMember(Name = &quot;references&quot;)]<br>
public List&lt;ResponseReference&gt; References { get; set; }<br>
}<br>
//DataContract decoration necessary for serialization/deserialization to work properly<br>
[DataContract]<br>
public class ResponseReference<br>
{<br>
[DataMember(Name = &quot;reference&quot;)]<br>
public string Reference { get; set; }<br>
[DataMember(Name = &quot;id&quot;)]<br>
public int Id { get; set; }<br>
}</code></pre></p>
<h4 id="theresultsdisplay">The results display</h4>
<p>The code above takes care of getting the email data into our IDOL OnDemand text index, but now we need an easy way to execute the find similar queries from a web resource on the email form. This is actually incredibly easy to do with a little bit of JavaScript and jQuery.</p>
<p>To keep things simple, we’re going to make an assumption that all CRM emails will be in the IDOL OnDemand index by the time an agent opens one, so we can tell IDOL OnDemand to find similar records using the email’s activityid field, which we are populating as the index reference field in the code above. This also means we can embed the web resource on the email form without having to use any custom code because the Dynamics CRM form designer lets us specify that an iframe will be passed the record id when the form loads.</p>
<p>If turned out that we couldn’t count on the emails always being indexed before the agents viewed them, we could search the index using the relevant fields from an email record instead of using an index reference, but this would require some changes to what I’m showing today.</p>
<p>Our web resource will do the following:</p>
<ol><li>Load the jQuery library (in my sample, I’m using the Google CDN).</li><li>Parse the record id from the value supplied in the query string that is passed to the iframe.</li><li>Execute a jQuery "getJSON" call to IDOL OnDemand’s find similar API.</li><li>Loop through the output results and write them to the screen.</li></ol>
<p>Let’s take a look at the code in detail.</p>
<p>First, we load jQuery:</p>
<pre><code>&lt;script src="//ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"&gt;&lt;/script&gt;</code></pre>
<p>Next we need a function that can extract query string values:<pre><code>function getParameterByName(name) {<br>
name = name.replace(/[[]/, &quot;\[&quot;).replace(/[]]/, &quot;\]&quot;);<br>
var regex = new RegExp(&quot;[\?&amp;]&quot; + name + &quot;=([^&amp;#]*)&quot;),<br>
results = regex.exec(location.search);<br>
return results == null ? &quot;&quot; : decodeURIComponent(results[1].replace(/+/g, &quot; &quot;));<br>
}</code></pre></p>
<p>Using the getParameterByName function, we parse the email’s activity id and prepare if for querying IDOL OnDemand like so:<pre><code>var entityId = getParameterByName('id');<br>
//strip brackets because we indexed a string value of the guid without them<br>
entityId = entityId.replace(&quot;{&quot;,&quot;&quot;).replace(&quot;}&quot;,&quot;&quot;);</code></pre></p>
<p>Finally we send the find similar request and process the results:</p>
<pre><code>var indexName = "lucas-test";
var apiKey = "f90f3fc7-0e36-40f0-b508-734f45fe9033";
$.getJSON("<a href="https://api.idolondemand.com/1/api/sync/findsimilar/v1?indexes=&quot;+indexName+&quot;&amp;apikey=&quot;+apiKey+&quot;&amp;" target="_blank" rel="nofollow">https://api.idolondemand.com/1/api/sync/findsimilar/v1?indexes="+indexName+"&amp;apikey="+apiKey+"&amp;</a> summary=context&amp;index\_reference="+entityId,function(data){
	var content = "";
	$(content).text("");
	$.each(data.documents, function(i,document){
		content += '&lt;p&gt;&lt;a href="/main.aspx?etn=email&amp;id={' + document.reference + '}&amp;newWindow=true&amp;pagetype=entityrecord" target="_blank"&gt;' + document.title + '&lt;/a&gt;';
		content += '&lt;br /&gt;Score: ' + document.weight + '';
		content += '&lt;br /&gt;' + document.summary + '&lt;/p&gt;';
	});
	$(content).appendTo("#results");
});</code></pre>
<p>This writes the results to a div element on the page with an id of &quot;results.&quot; You’ll see that each result shows the email subject (stored as &quot;title&quot; in our index) as a hyperlink to the actual email record, a similarity score value (weight) and a summary of the similar text.</p>
<p>So that’s it for getting started with IDOL OnDemand’s find similar API for Microsoft Dynamics CRM data. Full code for this is in my <a href="https://github.com/lucasalexander/CRM-IdolOnDemand-Tools">CRM-IdolOnDemand-Tools repository</a> on GitHub. Does this look like something you could use in your organization? If so, let us know your thoughts in the comments!</p>
<p><em>* If you want to learn more about IDOL OnDemand text indexes, I suggest you look at the text indexes section of the </em><a href="https://www.idolondemand.com/developer/docs/" target="_blank" rel="nofollow"><em>developer documentation</em></a><em>.</em></p>
<p><em>A version of this post was originally published on the HP Enterprise Services Application Services blog.</em></p>
</div>]]></content:encoded></item><item><title><![CDATA[Using IDOL OnDemand for text analysis in Dynamics CRM - part 2]]></title><description><![CDATA[<div class="kg-card-markdown"><p>In my <a href="https://alexanderdevelopment.net/post/2014/09/19/using-idol-ondemand-for-text-analysis-in-dynamics-crm-part-1/">last post</a> I provided an overview of <a target="_blank" href="https://www.idolondemand.com/" rel="nofollow">HP IDOL OnDemand</a> and discussed a couple of things you can do with it to process and analyze data stored in a Microsoft Dynamics CRM instance. Today I'll provide a detailed walkthrough of how perform sentiment analysis on incoming emails using</p></div>]]></description><link>https://alexanderdevelopment.net/post/2014/09/23/using-idol-ondemand-for-text-analysis-in-dynamics-crm-part-2/</link><guid isPermaLink="false">5a5837226636a30001b9773f</guid><category><![CDATA[Microsoft Dynamics CRM]]></category><category><![CDATA[CRM 2013]]></category><category><![CDATA[IDOL OnDemand]]></category><category><![CDATA[text analysis]]></category><dc:creator><![CDATA[Lucas Alexander]]></dc:creator><pubDate>Wed, 24 Sep 2014 00:00:00 GMT</pubDate><content:encoded><![CDATA[<div class="kg-card-markdown"><p>In my <a href="https://alexanderdevelopment.net/post/2014/09/19/using-idol-ondemand-for-text-analysis-in-dynamics-crm-part-1/">last post</a> I provided an overview of <a target="_blank" href="https://www.idolondemand.com/" rel="nofollow">HP IDOL OnDemand</a> and discussed a couple of things you can do with it to process and analyze data stored in a Microsoft Dynamics CRM instance. Today I'll provide a detailed walkthrough of how perform sentiment analysis on incoming emails using IDOL OnDemand. First let's consider a typical scenario for sentiment analysis.</p>
<p>Assume you manage a Microsoft Dynamics CRM system (online or on-premise) for an organization that provides customer service and support via a variety of channels including phone, email and live chat. The business manager has asked you to set up a mechanism that will categorize the sentiment of incoming emails sent to the main &quot;support&quot; address so that they can be assigned to different queues for handling by agents. Fortunately for you, IDOL OnDemand offers a <a target="_blank" href="https://www.idolondemand.com/developer/apis/analyzesentiment" rel="nofollow">sentiment analysis API</a>!</p>
<p>This API parses a body of text and returns a text value for sentiment (positive, neutral or negative) and a decimal sentiment score between 1 and -1 (larger positive scores indicating positivity and larger negative scores indicating negativity)*. Let's look at how we can make use of that API inside Dynamics CRM.</p>
<h4 id="thesolutionapproach">The solution approach</h4>
<p>Communicating with IDOL OnDemand from Dynamics CRM obviously requires custom code, but whether that code is called from a plug-in or workflow doesn't materially affect the code. For the purpose of this example, I'll be showing an approach that uses a custom workflow activity called from an asynchronous workflow. When an email is received, the workflow will pass the text of the email to the custom workflow activity, which will then call out to IDOL OnDemand's sentiment analysis API to retrieve the sentiment and score. The workflow will then write these values to custom fields on the email record.</p>
<h4 id="thecode">The code</h4>
<p>The custom workflow activity needs one input parameter for email text and two output parameters for sentiment and score. Here's how we define them:<pre><code>[Input(&quot;Text Input&quot;)]<br>
public InArgument&lt;String&gt; TextInput { get; set; }<br>
[Output(&quot;Sentiment&quot;)]<br>
public OutArgument&lt;String&gt; Sentiment { get; set; }<br>
[Output(&quot;Score&quot;)]<br>
public OutArgument&lt;Decimal&gt; Score { get; set; }</code></pre></p>
<p>We also need to set up two variables to help us call IDOL OnDemand. The first is the URL for the sentiment analysis API:<pre><code>//address of the service to which you will post your json messages<br>
private string _webAddress = &quot;<a href="https://api.idolondemand.com/1/api/sync/analyzesentiment/v1">https://api.idolondemand.com/1/api/sync/analyzesentiment/v1</a>&quot;;</code></pre></p>
<p>The second is the API key to authorize our requests:<pre><code>//idol ondemand api key<br>
private string _apiKey = &quot;XXXXXXXXXXXXXXXX&quot;;</code></pre></p>
<p>Now that all the inputs and configuration parameters are set, you are ready to process the inbound email. We're going to do the following:<ol><li>Check whether the email description field contains text.</li><li>If it does, strip HTML tags using a helper function.</li><li>Pass the &quot;clean&quot; text to IDOL OnDemand with an HttpWebRequest.</li><li>Deserialize the JSON respsonse returned by IDOL OnDemand to a custom class object (SentimentResponse) using a DataContractJsonSerializer.</li><li>Return the sentiment value and score to the calling workflow.</li></ol></p>
<p>Here's the code that does all of this:<pre><code>//get the text we want to analyze<br>
string inputText = TextInput.Get(executionContext);<br>
if (inputText != string.Empty)<br>
{<br>
//use a helper function to strip out HTML tags<br>
inputText = HtmlTools.StripHTML(inputText);<br>
//create the webrequest object and execute it (and post jsonmsg to it)<br>
HttpWebRequest req = (HttpWebRequest)HttpWebRequest.Create(_webAddress);<br>
//set request content type so it is treated as a regular form post<br>
req.ContentType = &quot;application/x-www-form-urlencoded&quot;;<br>
//set method to post<br>
req.Method = &quot;POST&quot;;<br>
//create a stringbuilder to build our request<br>
StringBuilder postData = new StringBuilder();<br>
//set the apikey request value<br>
postData.Append(&quot;apikey=&quot; + System.Uri.EscapeDataString(_apiKey) + &quot;&amp;&quot;);<br>
//set the text request value<br>
postData.Append(&quot;text=&quot; + System.Uri.EscapeDataString(inputText));<br>
//create a stream<br>
byte[] bytes = System.Text.Encoding.ASCII.GetBytes(postData.ToString());<br>
req.ContentLength = bytes.Length;<br>
System.IO.Stream os = req.GetRequestStream();<br>
os.Write(bytes, 0, bytes.Length);<br>
os.Close();<br>
//get the response<br>
System.Net.WebResponse resp = req.GetResponse();<br>
//deserialize the response to a SentimentResponse object<br>
SentimentResponse myResponse = new SentimentResponse();<br>
System.Runtime.Serialization.Json.DataContractJsonSerializer deserializer = new System.Runtime.Serialization.Json.DataContractJsonSerializer(myResponse.GetType());<br>
myResponse = deserializer.ReadObject(resp.GetResponseStream()) as SentimentResponse;<br>
//set output values from the fields of the deserialzed myjsonresponse object<br>
Score.Set(executionContext, myResponse.Aggregate.Score);<br>
Sentiment.Set(executionContext, myResponse.Aggregate.Sentiment);<br>
}</code></pre></p>
<p>The StripHTML function is taken from a <a target="_blank" href="http://www.codeproject.com/Articles/11902/Convert-HTML-to-Plain-Text" rel="nofollow">sample on CodeProject</a>.</p>
<p>Here are the classes used for deserializing the JSON response:<pre><code>//DataContract decoration necessary for serialization/deserialization to work properly<br>
[DataContract]<br>
public class SentimentResponse<br>
{<br>
//datamember name value indicates name of json field to which data will be serialized/from which data will be deserialized<br>
[DataMember(Name = &quot;positive&quot;)]<br>
public List&lt;SentimentEntity&gt; Positive { get; set; }<br>
[DataMember(Name = &quot;negative&quot;)]<br>
public List&lt;SentimentEntity&gt; Negative { get; set; }<br>
[DataMember(Name = &quot;aggregate&quot;)]<br>
public SentimentAggregate Aggregate { get; set; }<br>
public SentimentResponse() { }<br>
}<br>
//DataContract decoration necessary for serialization/deserialization to work properly<br>
[DataContract]<br>
public class SentimentAggregate<br>
{<br>
[DataMember(Name = &quot;sentiment&quot;)]<br>
public string Sentiment { get; set; }<br>
[DataMember(Name = &quot;score&quot;)]<br>
public decimal Score { get; set; }<br>
}<br>
//DataContract decoration necessary for serialization/deserialization to work properly<br>
[DataContract]<br>
public class SentimentEntity<br>
{<br>
[DataMember(Name = &quot;sentiment&quot;)]<br>
public string Sentiment { get; set; }<br>
[DataMember(Name = &quot;topic&quot;)]<br>
public string Topic { get; set; }<br>
[DataMember(Name = &quot;score&quot;)]<br>
public decimal Score { get; set; }<br>
[DataMember(Name = &quot;original_text&quot;)]<br>
public string OriginalText { get; set; }<br>
[DataMember(Name = &quot;normalized_text&quot;)]<br>
public string NormalizedText { get; set; }<br>
[DataMember(Name = &quot;original_length&quot;)]<br>
public int OriginalLength { get; set; }<br>
[DataMember(Name = &quot;normalized_length&quot;)]<br>
public int NormalizedLength { get; set; }<br>
}</code></pre></p>
<p>Although the scenario for this example mentions email, you'll note that nothing in the code is specific to emails, so you can use this same code without modifications to perform sentiment analysis on any record in your Dynamics CRM system. Full code for this is in my <a href="https://github.com/lucasalexander/CRM-IdolOnDemand-Tools">CRM-IdolOnDemand-Tools repository</a> on GitHub.</p>
<p>That's all for now. Join me next time to see how you can index Dynamics CRM records and execute &quot;find similar&quot; queries using IDOL OnDemand.</p>
<p><em>* Actually the sentiment analysis API also returns separate sentiment and score values for different phrases in the text, but we're going to disregard those here and only work with the overall sentiment and score values.</em></p>
<p><em>A version of this post was originally published on the HP Enterprise Services Application Services blog.</em></p>
</div>]]></content:encoded></item><item><title><![CDATA[Using IDOL OnDemand for text analysis in Dynamics CRM - part 1]]></title><description><![CDATA[<div class="kg-card-markdown"><p>Inside any organization's Microsoft Dynamics CRM system, there is a wealth of raw data that can be turned into actionable intelligence if it can be effectively analyzed. This data can be found in emails, case notes, attachments and other records that are created and stored in the course of day-to-day</p></div>]]></description><link>https://alexanderdevelopment.net/post/2014/09/18/using-idol-ondemand-for-text-analysis-in-dynamics-crm-part-1/</link><guid isPermaLink="false">5a5837226636a30001b97735</guid><category><![CDATA[Microsoft Dynamics CRM]]></category><category><![CDATA[CRM 2013]]></category><category><![CDATA[IDOL OnDemand]]></category><category><![CDATA[text analysis]]></category><dc:creator><![CDATA[Lucas Alexander]]></dc:creator><pubDate>Fri, 19 Sep 2014 00:00:00 GMT</pubDate><content:encoded><![CDATA[<div class="kg-card-markdown"><p>Inside any organization's Microsoft Dynamics CRM system, there is a wealth of raw data that can be turned into actionable intelligence if it can be effectively analyzed. This data can be found in emails, case notes, attachments and other records that are created and stored in the course of day-to-day business. In this three-part series, I will show how you can unlock this data using HP IDOL OnDemand's powerful text analysis capabilities. Specifically I will show how you can perform sentiment analysis on incoming emails and also how you can index Dynamics CRM records so that agents can easily see similar, relevant records. Before we get into the technical details, let's take a closer look at IDOL OnDemand.</p>
<h4 id="whatishpidolondemand">What is HP IDOL OnDemand?</h4>
<p><a target="_blank" href="https://www.idolondemand.com/" rel="nofollow">IDOL OnDemand</a> is a collection of web service APIs that expose a subset of the functionality offered by HP Autonomy. These APIs allow developers to write code that can &quot;understand and engage with human information, including text, images, social media, and app data.&quot; Currently IDOL OnDemand offers more than 20 different APIs including sentiment analysis, image recognition, face detection and text indexing/searching. A full list of IDOL OnDemand APIs is available here: <a target="_blank" href="https://www.idolondemand.com/developer/apis" rel="nofollow">https://www.idolondemand.com/developer/apis</a>. Despite the breadth of functionality available in IDOL OnDemand, there are definite differences between it and the on-premise HP IDOL version. You can read more about those differences <a target="_blank" href="http://www.autonomy.com/technology/idol-on-demand" rel="nofollow">here</a>.</p>
<h4 id="howdoweconnectdynamicscrmandidolondemand">How do we connect Dynamics CRM and IDOL OnDemand?</h4>
<p>Because all IDOL OnDemand APIs are RESTful web services that return responses as JSON-formatted messages, custom workflow activities or plug-ins can communicate with IDOL OnDemand via the System.Net.HttpWebRequest class, but first you need to sign up for an IDOL OnDemand developer account at <a target="_blank" href="https://www.idolondemand.com/signup.html" rel="nofollow">https://www.idolondemand.com/signup.html</a>. Once you sign up for a developer account, you'll be given an API key that grants access to run IDOL OnDemand APIs. I'll be showing detailed code examples in parts two and three of this series, but in the meantime I suggest you go ahead and take a look at the IDOL OnDemand <a target="_blank" href="https://www.idolondemand.com/developer/docs/HowTo_GettingStarted.html" rel="nofollow">getting started documentation</a>.</p>
<p>That's all for today. In my next post, I'll show how to analyze inbound emails for customer sentiment. See you then!</p>
<p><em>A version of this post was originally published on the HP Enterprise Services Application Services blog.</em></p>
</div>]]></content:encoded></item></channel></rss>