Skip to content

Commit 77f2b2f

Browse files
CS-1625 POC
1 parent 427cb6d commit 77f2b2f

File tree

7 files changed

+1122
-1
lines changed

7 files changed

+1122
-1
lines changed

events/chat-sentiment.json

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
{
2+
"Records": [
3+
{
4+
"eventVersion": "2.1",
5+
"eventSource": "aws:s3",
6+
"awsRegion": "us-east-1",
7+
"eventTime": "2020-10-26T18:25:55.516Z",
8+
"eventName": "ObjectCreated:Put",
9+
"userIdentity": {
10+
"principalId": "AWS:AIDA5CCDZW4MRSCLDY7UR"
11+
},
12+
"requestParameters": {
13+
"sourceIPAddress": "68.73.117.169"
14+
},
15+
"responseElements": {
16+
"x-amz-request-id": "6392D2FE18F780C1",
17+
"x-amz-id-2": "lJ45peuTi9s5Ffx7K6uljzZjySzgtGK38tKxiInIIL3QgumvIgEhFgdbwpbkGuGHEarfh8r8blscrtzc0hpoiwCPcVWJituMA+jgbaXS+rw="
18+
},
19+
"s3": {
20+
"s3SchemaVersion": "1.0",
21+
"configurationId": "transcriptNotifyEvent",
22+
"bucket": {
23+
"name": "connect-6a8d34621f87",
24+
"ownerIdentity": {
25+
"principalId": "AW0EZPYKZ1NJM"
26+
},
27+
"arn": "arn:aws:s3:::connect-6a8d34621f87"
28+
},
29+
"object": {
30+
"key": "connect/jwelborn-sugar/ChatTranscripts/2021/04/01/cs-1625.json",
31+
"size": 34442,
32+
"eTag": "8f3fe4ffaa7410d8a315901cd3295339",
33+
"sequencer": "005F9714B51413EDF5"
34+
}
35+
}
36+
}
37+
]
38+
}

src/handlers/chat-sentiment.js

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/*
2+
* Your installation or use of this SugarCRM file is subject to the applicable
3+
* terms available at
4+
* http://support.sugarcrm.com/Resources/Master_Subscription_Agreements/.
5+
* If you do not agree to all of the applicable terms or do not have the
6+
* authority to bind the entity as an authorized representative, then do not
7+
* install or use this SugarCRM file.
8+
*
9+
* Copyright (C) SugarCRM Inc. All rights reserved.
10+
*/
11+
12+
/**
13+
* Utils
14+
*/
15+
const chatUtils = require('../utils/sugar/chat-transcript-utils');
16+
const comprehendUtils = require('../utils/aws/comprehend-utils');
17+
const loggerUtils = require('../utils/logger-utils');
18+
const s3Utils = require('../utils/aws/s3-utils');
19+
const utils = require('../utils/utils');
20+
21+
/**
22+
* Function to update a call with transcript once the transcript is
23+
* uploaded to S3.
24+
*
25+
* @param {Object} event S3 Event that invokes this function
26+
*/
27+
const handler = async (event) => {
28+
// Log S3 Event to cloudwatch for debugging.
29+
loggerUtils.logS3Event(event);
30+
31+
// Fetch and process transcript from s3
32+
let transcript = await s3Utils.getJsonFromS3Event(event);
33+
console.log('Fetched Chat transcript: \n', transcript);
34+
35+
let batchTranscript = utils.batchTrancsriptByParticipant(transcript);
36+
console.log('Batch Transcript: \n' + JSON.stringify(batchTranscript, null, 2));
37+
let batchSentiment = await comprehendUtils.getBatchTranscriptByParticipant(batchTranscript);
38+
console.log('Batch Sentiment: \n' + JSON.stringify(batchSentiment, null, 2));
39+
40+
let stringTranscript = utils.stringTranscriptByParticipant(transcript);
41+
console.log('String Transcript: \n' + JSON.stringify(stringTranscript, null, 2));
42+
let stringSentiment = await comprehendUtils.getStringTranscriptByParticipant(stringTranscript);
43+
console.log('String Sentiment: \n' + JSON.stringify(stringSentiment, null, 2));
44+
45+
let annotatedTranscript = chatUtils.createAnnotatedTrancsript(transcript, batchSentiment, stringSentiment);
46+
console.log('Annotated Transcript: \n' + JSON.stringify(transcript, null, 2));
47+
48+
return annotatedTranscript;
49+
};
50+
51+
exports.handler = handler;

src/utils/aws/comprehend-utils.js

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/*
2+
* Your installation or use of this SugarCRM file is subject to the applicable
3+
* terms available at
4+
* http://support.sugarcrm.com/Resources/Master_Subscription_Agreements/.
5+
* If you do not agree to all of the applicable terms or do not have the
6+
* authority to bind the entity as an authorized representative, then do not
7+
* install or use this SugarCRM file.
8+
*
9+
* Copyright (C) SugarCRM Inc. All rights reserved.
10+
*/
11+
const AWS = require('aws-sdk');
12+
13+
/**
14+
* Call comprehend batchDetectSentiment API to get an array of sentiments per utterance grouped by conversation participant
15+
*
16+
* @param {Object} groupedTranscript
17+
* @returns {Object} Sentiments grouped by participant, with a sentiment score for each utterance in the transcript
18+
*/
19+
async function getBatchTranscriptByParticipant(groupedTranscript) {
20+
const comprehend = new AWS.Comprehend();
21+
let groupedSentiment = {};
22+
for (const [key, value] of Object.entries(groupedTranscript)) {
23+
let params = {
24+
LanguageCode: 'en',
25+
TextList: value
26+
};
27+
groupedSentiment[key] = await comprehend.batchDetectSentiment(params, function(err, data) {
28+
if (err) {
29+
console.log(err);
30+
} else {
31+
return data;
32+
}
33+
}).promise();
34+
}
35+
return groupedSentiment;
36+
}
37+
38+
/**
39+
* Call comprehend detectSentiment API to get a single sentiment for each conversation participant
40+
*
41+
* @param {Object} groupedTranscript
42+
* @returns {Object} Sentiments grouped by participant, with a single sentiment score per participant
43+
*/
44+
async function getStringTranscriptByParticipant(groupedTranscript) {
45+
const comprehend = new AWS.Comprehend();
46+
let groupedSentiment = {};
47+
for (const [key, value] of Object.entries(groupedTranscript)) {
48+
let params = {
49+
LanguageCode: 'en',
50+
Text: value
51+
};
52+
groupedSentiment[key] = await comprehend.detectSentiment(params, (err, data) => {
53+
if (err) {
54+
console.log(err);
55+
} else {
56+
return data;
57+
}
58+
}).promise();
59+
}
60+
return groupedSentiment;
61+
}
62+
63+
module.exports = { getBatchTranscriptByParticipant, getStringTranscriptByParticipant };
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/*
2+
* Your installation or use of this SugarCRM file is subject to the applicable
3+
* terms available at
4+
* http://support.sugarcrm.com/Resources/Master_Subscription_Agreements/.
5+
* If you do not agree to all of the applicable terms or do not have the
6+
* authority to bind the entity as an authorized representative, then do not
7+
* install or use this SugarCRM file.
8+
*
9+
* Copyright (C) SugarCRM Inc. All rights reserved.
10+
*/
11+
/**
12+
* Combine raw transcript with the return from batchSentiment API and getSentiment APIs into one
13+
* annotated transcript with overall scores for each participant and individual message scores.
14+
*
15+
* @param {Object} transcript raw Connect Chat Transcript
16+
* @param {*} sentimentScores Sentiment scores for each chat participane keyed by participant name
17+
*/
18+
function createAnnotatedTrancsript(transcript, batchSentiment, stringSentiment) {
19+
for (const [participant, sentiment] of Object.entries(stringSentiment)) {
20+
transcript[`${participant}_SENTIMENT`] = sentiment;
21+
}
22+
23+
let participantIndices = {};
24+
Object.keys(batchSentiment).forEach(key => {
25+
participantIndices[key] = 0;
26+
});
27+
28+
let messages = transcript.Transcript;
29+
30+
messages = messages.map(message => {
31+
if (message.ContentType !== 'text/plain') {
32+
return message;
33+
}
34+
let role = message.ParticipantRole;
35+
let index = participantIndices[role]++;
36+
let messageSentiment = batchSentiment[role].ResultList[index];
37+
message['Sentiment'] = messageSentiment;
38+
return message;
39+
});
40+
transcript.Transcript = messages;
41+
42+
return transcript;
43+
}
44+
45+
module.exports = {
46+
createAnnotatedTrancsript
47+
};

src/utils/utils.js

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,47 @@ function processTranscript(transcript) {
2323
return processedTranscript.trim();
2424
}
2525

26-
module.exports = { processTranscript };
26+
/**
27+
* Process raw transcript to group messages by Participant
28+
*
29+
* @param {Object} transcript JSON representing a chat transcript
30+
* @returns {Object} mapping of conversation participant to an array of utterances
31+
*/
32+
function batchTrancsriptByParticipant(transcript) {
33+
let groupedMessages = {};
34+
transcript.Transcript.forEach((statement) => {
35+
if (!('Content' in statement)) {
36+
return;
37+
};
38+
let role = statement.ParticipantRole;
39+
if (!(role in groupedMessages)) {
40+
groupedMessages[role] = [];
41+
}
42+
groupedMessages[role].push(statement.Content);
43+
});
44+
return groupedMessages;
45+
}
46+
47+
/**
48+
* Process raw transcript to group messages by participant, and combine messages into a single
49+
* string.
50+
*
51+
* @param {Object} transcript JSON representing a chat transcript
52+
* @returns {Object} mapping of conversation participant to string with combined utterances
53+
*/
54+
function stringTranscriptByParticipant(transcript) {
55+
let groupedMessages = {};
56+
transcript.Transcript.forEach((statement) => {
57+
if (!('Content' in statement)) {
58+
return;
59+
};
60+
let role = statement.ParticipantRole;
61+
if (!(role in groupedMessages)) {
62+
groupedMessages[role] = '';
63+
}
64+
groupedMessages[role] = `${groupedMessages[role]} ${statement.Content}`.trim();
65+
});
66+
return groupedMessages;
67+
}
68+
69+
module.exports = { processTranscript, batchTrancsriptByParticipant, stringTranscriptByParticipant };

template.yml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,21 @@ Resources:
238238
region: !Ref AWS::Region
239239
secretManagerArn: !Ref SugarSecrets
240240
sugarUrl: !Ref SugarURL
241+
242+
GetChatSentimentFunction:
243+
Type: 'AWS::Serverless::Function'
244+
Properties:
245+
Description: AWS Lambda Function to get chat sentiments
246+
Handler: 'src/handlers/chat-sentiment.handler'
247+
Runtime: 'nodejs12.x'
248+
MemorySize: 128
249+
Role: !GetAtt s3CloudwatchLambdaExecutionRole.Arn
250+
Timeout: 60
251+
Environment:
252+
Variables:
253+
region: !Ref AWS::Region
254+
secretManagerArn: !Ref SugarSecrets
255+
sugarUrl: !Ref SugarURL
241256

242257
CallRecordingFunction:
243258
Condition: ShouldInstallCallRecordingFunction
@@ -295,6 +310,10 @@ Resources:
295310
- "lambda.amazonaws.com"
296311
Action:
297312
- "sts:AssumeRole"
313+
- Effect: "Allow"
314+
Action:
315+
- "comprehend:BatchDetectSentiment"
316+
Resource: "*"
298317
Path: "/"
299318
ManagedPolicyArns:
300319
- "arn:aws:iam::aws:policy/AmazonS3FullAccess"

0 commit comments

Comments
 (0)