/// <summary> Pack Actions to Messages </summary> private static void PackActionsToMessages(List <Task> asyncTransactionsInProcess, DataContext db, Table <ActionSentiment> actionSentimentTable, ActionToAnalyze[] actions) { // Pack Actions into Messages WatsonMessage message = new WatsonMessage(); foreach (ActionToAnalyze actionToAnalyze in actions) { // ActionSentiment already exists or emtpy? bool duplicateActionSentiment = actionSentimentTable.Where(u => u.ActionID == actionToAnalyze.ActionID).Any(); if (duplicateActionSentiment) { WatsonEventLog.WriteEntry("duplicate ActionID in ActionSentiment table " + actionToAnalyze.ActionID, EventLogEntryType.Error); } if (duplicateActionSentiment || (actionToAnalyze.WatsonText().Length == 0)) { actionToAnalyze.DeleteOnSubmit(db); db.SubmitChanges(); continue; } // add action to message? Task task = PackActionToMessage(actionToAnalyze, ref message); if (task != null) { asyncTransactionsInProcess.Add(task); // wait for watson results //break; // uncomment to test with a single HTTP post } } // Send the remainder message if mostly full if (message.UtteranceCount > 40) { SendMessage(ref message); } }
/// <summary> /// Callback on insert of Watson results - using the transaction submitted but NOT committed /// /// For each action, use the most likely sentiment (highest SentimentScore) /// Use Net Promoter score (promoters - detractors), normalized to [0, 1000] where 500 is neutral /// </summary> /// <param name="transaction">data associated with the watson transaction</param> public static void TicketSentimentStrategy(DataContext db, ActionToAnalyze actionToAnalyze, ActionSentimentScore maxScore) { if (actionToAnalyze.IsAgent) { return; } try { // normalize to [0, 1000] double actionScore = Convert.ToDouble(ToneSentiment.ToneSentiments[maxScore.SentimentID].SentimentMultiplier.Value) * Convert.ToDouble(maxScore.SentimentScore); actionScore = 500 * actionScore + 500; // submit TicketSentiment TicketSentiment score = null; CreateTicketSentiment(db, actionToAnalyze, actionScore, out score); score.TicketSentimentScore = (int)Math.Round(score.AverageActionSentiment); score.SetSentimentID(maxScore.SentimentID); db.SubmitChanges(); } catch (Exception e2) { WatsonEventLog.WriteEntry("Exception caught at select from ACtionsToAnalyze or HttpPOST:", e2); Console.WriteLine(e2.ToString()); } }
static void Main(string[] args) { if (args.Contains("RebuildTicketSentimentsTable")) { WatsonEventLog.WriteEntry("RebuildTicketSentimentsTable started..."); RebuildTicketSentimentsTable ticketSentiments = new RebuildTicketSentimentsTable(); // we can recreate TicketSentiments from ActionSentiments ticketSentiments.DoRebuild(); WatsonEventLog.WriteEntry("RebuildTicketSentimentsTable completed."); return; } using (var service = new WatsonToneAnalyzerService()) { if (!Environment.UserInteractive) { ServiceBase.Run(service); // running as a service } else { // run from console service.StartTimer(); Console.WriteLine("Press any key to stop..."); Console.ReadKey(true); service.StopTimer(); } } }
static void PublishActionSentiment(ActionToAnalyze actionToAnalyze) { // Process The ActionToAnalyze results WatsonTransaction transaction = null; // Transaction that can be rolled back try { // 1. Insert ActionSentiment and ActionSentimentScores // 2. run the TicketSentimentStrategy to create TicketSentimentScore // 3. delete the ActionToAnalyze _singleThreadedTransactions.WaitOne(); // connection does not support parallel transactions transaction = new WatsonTransaction(); transaction.RecordWatsonResults(actionToAnalyze); transaction.Commit(); } catch (Exception e2) { if (transaction != null) { transaction.Rollback(); } WatsonEventLog.WriteEntry("Watson analysis failed - system will retry", e2); Console.WriteLine(e2.ToString()); } finally { if (transaction != null) { transaction.Dispose(); } _singleThreadedTransactions.ReleaseMutex(); } }
/// <summary> /// Post to IBM watson with Authorization and JSON formatted utterances to process /// </summary> static async Task HTTP_POST(WatsonMessage message) { if (message.Empty) { return; } //Create Json Readable String with user input: string result = String.Empty; string jsonContent = String.Empty; try { using (HttpClient client = new HttpClient()) { // credentials var Auth = WatsonUsername + ":" + WatsonPassword; var byteArray = Encoding.ASCII.GetBytes(Auth); client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(byteArray)); // JSON content client.DefaultRequestHeaders.Accept.Clear(); client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); // Make Post call and await response jsonContent = message.ToJSON(); using (var response = await client.PostAsJsonAsync(WatsonGatewayUrl, JObject.Parse(jsonContent))) { HttpContent content = response.Content; result = await content.ReadAsStringAsync() ?? " "; UtteranceToneList watsonResponse = JsonConvert.DeserializeObject <UtteranceToneList>(result); // publish results to DB message.PublishWatsonResponse(watsonResponse); _actionsAnalyzed += message.ActionCount; } } } catch (Exception ex) { WatsonEventLog.WriteEntry("********************: Error during watson analysis:", ex); if (!String.IsNullOrEmpty(jsonContent)) { WatsonEventLog.WriteEntry(jsonContent); } if (!String.IsNullOrEmpty(result)) { WatsonEventLog.WriteEntry(result); } Console.WriteLine(ex.ToString()); } }
public void DoRebuild() { try { string connectionString = ConfigurationManager.AppSettings.Get("ConnectionString"); using (SqlConnection connection = new SqlConnection(connectionString)) using (DataContext db = new DataContext(connection)) { db.ObjectTrackingEnabled = false; // read-only linq to sql Initialize(db); // load the highest confidence sentiment for each client action MaxActionSentiment[] actionSentiments = LoadActionSentiments(db); Array.Sort(actionSentiments, (lhs, rhs) => lhs.TicketID.CompareTo(rhs.TicketID)); foreach (MaxActionSentiment actionSentiment in actionSentiments) { // new TicketSentiment? double actionScore = ToTicketScore(actionSentiment.SentimentID, actionSentiment.MaxSentimentScore); if (!_ticketSentiments.ContainsKey(actionSentiment.TicketID)) { _ticketSentiments[actionSentiment.TicketID] = CreateTicketSentiment(actionSentiment, db, actionScore); continue; } // update existing TicketSentiment TicketSentiment sentiment = _ticketSentiments[actionSentiment.TicketID]; int count = sentiment.ActionSentimentCount; sentiment.AverageActionSentiment = (count * sentiment.AverageActionSentiment + actionScore) / (count + 1); sentiment.ActionSentimentCount = count + 1; sentiment.TicketSentimentScore = (int)Math.Round(sentiment.AverageActionSentiment); sentiment.SetSentimentID(actionSentiment.SentimentID); } int insertCount = 0; foreach (KeyValuePair <int, TicketSentiment> pair in _ticketSentiments) { pair.Value.Insert(db); if (++insertCount >= 1000) { Console.Write('.'); insertCount = 0; } } } } catch (Exception e) { WatsonEventLog.WriteEntry("Exception in RebuildTicketSentimentsTable", e); Console.WriteLine(e.ToString()); } }
public void OnTimer(object sender, System.Timers.ElapsedEventArgs args) { // only do the Action table query every 20 minutes to catch what we might have missed? TimeSpan timeSince = DateTime.UtcNow - _lastQueryTime; if (timeSince.TotalMinutes >= WatsonQueryIntervalMinutes) { WatsonEventLog.WriteEntry("Query for ActionsToAnalyze"); ActionsToAnalyzer.FindActionsToAnalyze(); _lastQueryTime = DateTime.UtcNow; WatsonEventLog.WriteEntry("Actions Analyzed " + WatsonAnalyzer.ActionsAnalyzed.ToString()); } WatsonAnalyzer.AnalyzeActions(); if (_timerEnable) { _timer.Start(); } }
/// <summary> /// Get the actions to analyze (dbo.ActionToAnalyze) and post to Watson on the BlueMix account /// </summary> static public void AnalyzeActions() { // without this the HTTP message to Watson returns 405 - failure on send ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls; // keep a handle to all the async transactions we start List <Task> asyncTransactionsInProcess = new List <Task>(); try { //opens a sqlconnection at the specified location string connectionString = ConfigurationManager.AppSettings.Get("ConnectionString"); using (SqlConnection connection = new SqlConnection(connectionString)) using (DataContext db = new DataContext(connection)) { // initialize static data ToneSentiment.Initialize(db); ActionToAnalyze.Initialize(db); // read ActionToAnalyze Table <ActionSentiment> actionSentimentTable = db.GetTable <ActionSentiment>(); Table <ActionToAnalyze> actionToAnalyzeTable = db.GetTable <ActionToAnalyze>(); IQueryable <ActionToAnalyze> actionToAnalyzeQuery = from action in actionToAnalyzeTable select action; ActionToAnalyze[] actions = actionToAnalyzeQuery.ToArray(); // Pack the actions to analyze into watson messages PackActionsToMessages(asyncTransactionsInProcess, db, actionSentimentTable, actions); // ...the remainder can wait to be packed into a call when we have more } } catch (Exception e2) { WatsonEventLog.WriteEntry("Exception in AnalyzeActions", e2); Console.WriteLine(e2.ToString()); } finally { // wait until all the tone chat messages have been received and recorded Task.WaitAll(asyncTransactionsInProcess.ToArray(), 5 * 60 * 1000); // 5 minute timeout just in case... } }
/// <summary> Delete ActionToAnalyze </summary> public void DeleteOnSubmit(DataContext db) { Table <ActionToAnalyze> table = db.GetTable <ActionToAnalyze>(); try { // linq classes have an attach state to the DB table row if (table.GetOriginalEntityState(this) == null) { table.Attach(this); // must be attached to delete } } catch (Exception e2) { WatsonEventLog.WriteEntry("Exception with table.Attach - ", e2); Console.WriteLine(e2.ToString()); } table.DeleteOnSubmit(this); }
public static void FindActionsToAnalyze() { try { string connectionString = ConfigurationManager.AppSettings.Get("ConnectionString"); using (SqlConnection sqlConnection1 = new SqlConnection(connectionString)) using (DataContext db = new DataContext(sqlConnection1)) { var results = db.ExecuteQuery <ActionGetForWatson>("Exec " + "dbo.ActionsGetForWatson"); Table <ActionToAnalyze> table = db.GetTable <ActionToAnalyze>(); foreach (ActionGetForWatson a in results) { ActionToAnalyze actionToAnalyze = new ActionToAnalyze() { ActionID = a.ActionID, TicketID = a.TicketID, UserID = a.UserID, OrganizationID = a.OrganizationID, IsAgent = a.IsAgent == 1, DateCreated = a.DateCreated, ActionDescription = ActionToAnalyze.CleanString(a.ActionDescription), // clean the text of HTML and special characters }; if (!table.Where(u => u.ActionID == actionToAnalyze.ActionID).Any()) { table.InsertOnSubmit(actionToAnalyze); } } db.SubmitChanges(); } } catch (Exception e) { WatsonEventLog.WriteEntry("Exception while reading from action table:", e); Console.WriteLine(e.ToString()); } }
/// <summary> /// Create the ActionSentiment for the ActionID /// </summary> /// <param name="db">context for insert/submit</param> /// <param name="actionToAnalyze">action to analyze</param> /// <returns></returns> static ActionSentiment InsertActionSentiment(DataContext db, ActionToAnalyze actionToAnalyze) { // already exists? Table <ActionSentiment> table = db.GetTable <ActionSentiment>(); if (table.Where(u => u.ActionID == actionToAnalyze.ActionID).Any()) { WatsonEventLog.WriteEntry("duplicate ActionID in ActionSentiment table " + actionToAnalyze.ActionID, EventLogEntryType.Error); } // Insert ActionSentiment sentiment = new ActionSentiment { ActionID = actionToAnalyze.ActionID, TicketID = actionToAnalyze.TicketID, UserID = actionToAnalyze.UserID, OrganizationID = actionToAnalyze.OrganizationID, IsAgent = actionToAnalyze.IsAgent, DateCreated = DateTime.UtcNow }; table.InsertOnSubmit(sentiment); return(sentiment); }