gvarnayev commited on
Commit
701866f
1 Parent(s): 7f62523

add base for week 7

Browse files
Files changed (4) hide show
  1. app.py +165 -0
  2. comments.json +1 -0
  3. features.py +49 -0
  4. utils.py +69 -0
app.py ADDED
@@ -0,0 +1,165 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+ from datetime import datetime
3
+ import pandas as pd
4
+ import json
5
+ from pathlib import Path
6
+
7
+ from huggingface_hub import hf_hub_download, HfApi
8
+ import streamlit as st
9
+
10
+ from features import FEATURES
11
+ from utils import check_password, process_olivia_data
12
+
13
+
14
+ REPO_URL = "https://huggingface.co/datasets/trevolution/conversation-analytics-comments"
15
+ api = HfApi()
16
+
17
+
18
+ features_to_show = ['C: Missed Expectation : No Call Back/Follow Up',
19
+ 'C: Missed Expectation - Not Informed',
20
+ 'C: Missed Promises',
21
+ 'C: Repeat Contact - General/Other',
22
+ 'C: Repeat Contact - Previous Calls',
23
+ 'C: Repeat Information',
24
+ 'C: Agent Hanged Up',
25
+ 'C: Disputing Charge / Chargeback',
26
+ 'A: Transfer',
27
+ 'A: Transfer offer',
28
+ 'C: Channel Switch - Website',
29
+ 'C: Objection - Competitor - Switch',
30
+ 'C: Channel Switch - Webchat',
31
+ 'Escalation: External - Attorney General',
32
+ 'Escalation: External - BBB',
33
+ 'Escalation: External - Legal',
34
+ 'Escalation: Internal - Complaint',
35
+ 'Escalation: Internal - Corporate',
36
+ 'Escalation: Internal - Do Not Contact/Remove from list',
37
+ 'Escalation: Internal - Supervisor',
38
+ 'Voucher',
39
+ 'Refund Voucher',
40
+ 'Refund'
41
+ ]
42
+
43
+
44
+ style = (
45
+ 'border: 1px solid #ccc; '
46
+ 'padding: 10px; '
47
+ 'border-radius: 5px; '
48
+ 'max-height: 500px; ' # Set your desired maximum height
49
+ 'overflow: auto;' # Enable vertical scrollbar if content exceeds max height
50
+ )
51
+
52
+
53
+ def get_div(input):
54
+ return f'<div style="{style}"><p>{input}</p></div>'
55
+
56
+
57
+ def main():
58
+
59
+ if not check_password():
60
+ st.stop()
61
+
62
+ comments_path = hf_hub_download(
63
+ repo_id='trevolution/conversation-analytics-comments',
64
+ repo_type='dataset',
65
+ filename='comments_report_7.json',
66
+ token=st.secrets['WRITE_TOKEN'],
67
+ )
68
+ with open(comments_path, 'r') as f:
69
+ comments = json.load(f)
70
+ with open('transcriptions_report_7.json', 'r') as f:
71
+ transcriptions = json.load(f)
72
+ with open('analytics_report_7.json', 'r') as f:
73
+ analytics = json.load(f)
74
+ call_ids = [json.loads(_['metadata'])['call_id'] for _ in transcriptions]
75
+ call_ids = list(sorted(list(set(call_ids))))
76
+
77
+ st.title('Olivia - Agent - Conversation Analytics')
78
+ call_id = st.selectbox(
79
+ 'Call IDs:',
80
+ call_ids,
81
+ format_func=lambda call_id: f'{call_ids.index(call_id) + 1}: {call_id}'
82
+ )
83
+
84
+ if not st.session_state.get('selectbox'):
85
+ st.session_state['selectbox'] = call_id
86
+ else:
87
+ if call_id != st.session_state['selectbox']:
88
+ st.session_state['analyze_button'] = False
89
+ st.session_state['selectbox'] = call_id
90
+
91
+ transcription = [json.loads(_['transcription']) for _ in transcriptions if json.loads(_['metadata'])['call_id'] == call_id][0]
92
+ try:
93
+ analytics = [json.loads(_['analytics']) for _ in analytics if call_id == json.loads(_['metadata'])['call_id']][0]
94
+ analytics = analytics['analytics']
95
+ analytics = [f for f in analytics if f['name'] in features_to_show]
96
+ except:
97
+ analytics = None
98
+
99
+ # st.audio(f'data/{call_id}.ogg', format='audio/ogg')
100
+
101
+ analyze_button = st.button("Get Conversation Analytics")
102
+ if not st.session_state.get('analyze_button'):
103
+ st.session_state['analyze_button'] = analyze_button
104
+
105
+ if st.session_state['selectbox'] and st.session_state['analyze_button']:
106
+ conversation = process_olivia_data(transcription)
107
+ st.text('Conversation (Olivia Speech-to-Text):')
108
+ st.markdown(get_div(conversation['text']), unsafe_allow_html=True)
109
+
110
+ with st.spinner('Loading analytics...'):
111
+ st.text('Analytics')
112
+ readable_analytics = ''
113
+ for i, feature in enumerate(analytics):
114
+ readable_analytics += f"{i+1}. {feature['name']}: {feature['response']}. Quotation: {feature['quotation']}\n\n\n"
115
+ st.markdown(get_div(readable_analytics), unsafe_allow_html=True)
116
+
117
+
118
+ if "saved_comments" not in st.session_state:
119
+ st.session_state['saved_comments'] = ""
120
+
121
+ user_comments = st.text_area(f"Comments on {call_id}", key='user_comments', height=350)
122
+
123
+ def submit():
124
+ st.session_state['saved_comments'] = st.session_state['user_comments']
125
+ st.session_state['user_comments'] = ""
126
+
127
+ button = st.button("Save comments", on_click=submit)
128
+ if button and st.session_state['saved_comments']:
129
+
130
+ if call_id in comments:
131
+ comments[call_id].append(
132
+ {
133
+ 'timestamp': datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
134
+ 'text': st.session_state['saved_comments']
135
+ }
136
+ )
137
+ else:
138
+ comments[call_id] = [
139
+ {
140
+ 'timestamp': datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
141
+ 'text': st.session_state['saved_comments']
142
+ }
143
+ ]
144
+
145
+ api.upload_file(
146
+ path_or_fileobj=json.dumps(comments).encode('utf-8'),
147
+ path_in_repo="comments_report_7.json",
148
+ repo_id="trevolution/conversation-analytics-comments",
149
+ repo_type="dataset",
150
+ token=st.secrets['WRITE_TOKEN'],
151
+ commit_message=f"{call_id}_{datetime.now().strftime('%Y-%m-%d')}"
152
+ )
153
+ st.success("Saved")
154
+
155
+ if comments.get(call_id):
156
+ value = ''
157
+ for comment in comments.get(call_id):
158
+ value += f"{comment['timestamp']}: {comment['text']}\n"
159
+ st.text_area(label='Comments:', value=value, disabled=True, height=350)
160
+ else:
161
+ st.text_area(label='Comments:', value="No comments exist at the moment", disabled=True)
162
+
163
+
164
+ if __name__ == "__main__":
165
+ main()
comments.json ADDED
@@ -0,0 +1 @@
 
 
1
+ {}
features.py ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FEATURES = {
2
+ 'The Agent introduced themselves properly': "Introduction by Agent",
3
+ 'The Agent greeted the Customer': "Greeting",
4
+ 'The Agent mentioned the company name': "Company Name",
5
+ 'The Agent talked about sending Introduction letter to the customer': "Introduction Letter",
6
+ 'The Agent asked the Customer about their Departure and Destination for the Travel Plan': "Departure and Destination",
7
+ 'The Agent asked about Travel Dates from the Customer': "Travel Dates",
8
+ 'The Agent offer car rental services or ask to inquire about car needs from the passenger': "Q: Car Rental",
9
+ 'The Agent offer car rental services or ask to inquire about car needs from the passenger.': "Q: Car Rental",
10
+ 'The Agent offered car rental services or ask to inquire about car needs from the passenger': "Q: Car Rental",
11
+ 'The Agent offered car rental services or asked to inquire about car needs from the passenger': "Q: Car Rental",
12
+ 'The Agent offered car rental services or asked to inquire about car needs from the passenger.': "Q: Car Rental",
13
+ 'The Customer was interested in car rental service': "A: Car Rental",
14
+ 'The Agent offered Hotel Accommodation or services related to Hotel or asked to inquire about Hotel needs from the Customer': "Q: Hotel Accomodation",
15
+ 'The Agent offered Hotel Accommodation or services related to Hotel or asked to enquire about Hotel needs from the Customer': "Q: Hotel Accomodation",
16
+ 'The Customer was interested in Hotel Accommodation service.': "A: Hotel Accomodation",
17
+ 'The Customer complained that they never received a call back or follow up that was promised by the Agent': "Missed Expectation : No Call Back/Follow Up",
18
+ "The Customer complained that the information that they requested wasn't shared as promised.": "Missed Expectation - Not Informed",
19
+ "The Customer complained that the information that they requested wasn't shared as was promised.": "Missed Expectation - Not Informed",
20
+ "The Customer complained that the information they requested wasn't shared as was promised.": "Missed Expectation - Not Informed",
21
+ 'The Customer complained that they did not receive what was promised by the Agent.': "Missed Promises",
22
+ "The Customer complained that they wrote an email to the company regarding their issue and it wasn't resolved.": "Repeat Contact - General/Other",
23
+ "The Customer complained that they called the company regarding their issue and it wasn't resolved": "Repeat Contact - Previous Calls",
24
+ "The Customer complained that they have been repeating the same information to different agents or to the agent that is assisting during the call.": "Repeat Information",
25
+ 'The Customer was interested in Hotel Accommodation service': "A: Hotel Accomodation",
26
+ 'The Customer complained that they never received a call back or follow up that was promised by the Agent.': "Missed Expectation : No Call Back/Follow Up",
27
+ "The Customer complained that the information that they requested wasn't shared as promised": "Missed Expectation - Not Informed",
28
+ "The Customer complained that the information that they requested wasn't shared as was promised": "Missed Expectation - Not Informed",
29
+ "The Customer complained that the information they requested wasn't shared as was promised": "Missed Expectation - Not Informed",
30
+ 'The Customer complained that they did not receive what was promised by the Agent': "Missed Promises",
31
+ "The Customer complained that they wrote an email to the company regarding their issue and it wasn't resolved": "Repeat Contact - General/Other",
32
+ "The Customer complained that they called the company regarding their issue and it wasn't resolved.": "Repeat Contact - Previous Calls",
33
+ "The Customer complained that they have been repeating the same information to different agents or to the agent that is assisting during the call": "Repeat Information",
34
+ "The Agent hung up on the customer or the customer complained about the previous agent hanging up": "Agent Hanged Up",
35
+ "The Customer threatened chargebacks or expressed intent to dispute charges": "Disputing Charge / Chargeback",
36
+ "The Agent advised the Customer that the call will be transferred to a different agent or department or the Customer requested call transfer": "Transfer",
37
+ "The Agent offered to transfer a customer to a different agent or department": "Transfer offer",
38
+ "The Customer complained that they were not able to resolve their issue by using our company's website": "Channel Switch - Website",
39
+ "The Customer stated that they will not use the company's services and will be switch to the competitors or mentioned that the competitors are providing better services": "Objection - Competitor - Switch",
40
+ "The Customer mentioned that they had been using the webchat": "Channel Switch - Webchat",
41
+ "The Customer threatened that they will contact the attorney or the Customer mentioned Attorney General": "Escalation: External - Attorney General",
42
+ "The Customer threatened that they will be writing a BBB complaint": "Escalation: External - BBB",
43
+ "The Customer threatened with legal actions or mentioned legal escalation": "Escalation: External - Legal",
44
+ "The Customer stated that they will not use the company's services and will switch to the competitors or mentioned that the competitors are providing better services": "Objection - Competitor - Switch",
45
+ "The Customer stated that they had a complaint about the company": "Escalation: Internal - Complaint",
46
+ "The Customer wanted to complain at the corporate level or mentioned escalation": "Escalation: Internal - Corporate",
47
+ "The Customer requested for their information to be removed off of the company's call/sell/mailing list": "Escalation: Internal - Do Not Contact/Remove from list",
48
+ "The Customer requested to speak to a manager or a supervisor": "Escalation: Internal - Supervisor"
49
+ }
utils.py ADDED
@@ -0,0 +1,69 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Dict
2
+
3
+ import hmac
4
+ import streamlit as st
5
+
6
+
7
+ def process_olivia_data(transcription: dict) -> str:
8
+ phrases = transcription['phrases']
9
+ conversation = ''
10
+ for phrase in phrases:
11
+ conversation += 'Agent: ' if phrase['channel'] == 0 else 'Customer: '
12
+ parsed_phrase = ''.join(word['text'] for word in phrase['words'])
13
+ conversation += parsed_phrase
14
+ conversation += '\n\n\n'
15
+ return {'text': conversation}
16
+
17
+ def process_tethr_data(data: Dict) -> Dict:
18
+ extracted_words = []
19
+ participants = data.get("participants", [])
20
+ for participant in participants:
21
+ participant_type = 'Agent' if 'Agent' in participant.get("refType", "") else 'Customer'
22
+ utterances = participant.get("utterances", [])
23
+ for utterance in utterances:
24
+ words = utterance.get("words") # by words, not utterances since messed up
25
+ for word in words:
26
+ word_obj = {
27
+ 'participant': participant_type,
28
+ 'word': word.get('content', ''),
29
+ 'start': word.get('startMs', None),
30
+ 'end': word.get('endMs', None)
31
+ }
32
+ extracted_words.append(word_obj)
33
+ extracted_words.sort(key=lambda x: x['start']) # Sort by start time
34
+
35
+ conversation = ''
36
+ participant = None
37
+
38
+ for word in extracted_words:
39
+ if word['participant'] != participant:
40
+ if participant:
41
+ conversation += '\n\n'
42
+ participant = word['participant']
43
+ conversation += f'{participant}: '
44
+ conversation += f"{word['word']} "
45
+ return {'id': data['callId'], 'text': conversation}
46
+
47
+
48
+ def check_password():
49
+ """Returns `True` if the user had the correct password."""
50
+
51
+ def password_entered():
52
+ """Checks whether a password entered by the user is correct."""
53
+ if hmac.compare_digest(st.session_state["password"], st.secrets["PASSWORD"]):
54
+ st.session_state["password_correct"] = True
55
+ del st.session_state["password"] # Don't store the password.
56
+ else:
57
+ st.session_state["password_correct"] = False
58
+
59
+ # Return True if the passward is validated.
60
+ if st.session_state.get("password_correct", False):
61
+ return True
62
+
63
+ # Show input for password.
64
+ st.text_input(
65
+ "Password", type="password", on_change=password_entered, key="password"
66
+ )
67
+ if "password_correct" in st.session_state:
68
+ st.error("Incorrect password")
69
+ return False