tango-app-api-audio-analytics 1.0.30 → 1.0.32
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/COHORT_API_EXAMPLES.sh +235 -235
- package/package.json +2 -2
- package/src/controllers/audioAnalytics.controller.js +12 -10
- package/src/controllers/conversationAnalytics.controller.js +75 -0
- package/src/dtos/audioAnalytics.dtos.js +13 -0
- package/src/routes/audioAnalytics.routes.js +3 -2
- package/src/services/conversation.service.js +49 -0
package/COHORT_API_EXAMPLES.sh
CHANGED
|
@@ -1,235 +1,235 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
|
|
3
|
-
# Cohort API Test Examples
|
|
4
|
-
# This file contains curl commands to test all cohort API endpoints
|
|
5
|
-
|
|
6
|
-
BASE_URL="https://testtangoretail-api.tangoeye.ai/v3/audio-analitics"
|
|
7
|
-
COHORT_ID="cohort_photochromatic_001"
|
|
8
|
-
CLIENT_ID="11"
|
|
9
|
-
|
|
10
|
-
# ==================== Helper Function ====================
|
|
11
|
-
print_section() {
|
|
12
|
-
echo ""
|
|
13
|
-
echo "=================================="
|
|
14
|
-
echo "$1"
|
|
15
|
-
echo "=================================="
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
# ==================== 1. CREATE COHORT ====================
|
|
19
|
-
print_section "1. CREATE COHORT"
|
|
20
|
-
|
|
21
|
-
curl -X POST "$BASE_URL/cohorts" \
|
|
22
|
-
-H "Content-Type: application/json" \
|
|
23
|
-
-d '{
|
|
24
|
-
"clientId": "11",
|
|
25
|
-
"cohortId": "cohort_photochromatic_001",
|
|
26
|
-
"cohortName": "Photochromatic",
|
|
27
|
-
"cohortDescription": "A strict evaluation of the photochromatic sales journey, focusing on pitch quality, technical accuracy, and customer sentiment",
|
|
28
|
-
"metrics": [
|
|
29
|
-
{
|
|
30
|
-
"metricId": "pitch_quality_001",
|
|
31
|
-
"metricName": "Standard Pitch Quality",
|
|
32
|
-
"metricDescription": "Identify which specific value propositions were explicitly used by the staff",
|
|
33
|
-
"hasContext": true,
|
|
34
|
-
"isNumneric": false,
|
|
35
|
-
"contexts": [
|
|
36
|
-
{
|
|
37
|
-
"contextName": "ONE_GLASS_ALL_NEEDS",
|
|
38
|
-
"priority": 2,
|
|
39
|
-
"description": "Convenience of one pair for all lighting"
|
|
40
|
-
},
|
|
41
|
-
{
|
|
42
|
-
"contextName": "TRAVEL_CONVENIENCE",
|
|
43
|
-
"priority": 2,
|
|
44
|
-
"description": "Specific benefits for bikers/commuters"
|
|
45
|
-
},
|
|
46
|
-
{
|
|
47
|
-
"contextName": "LIGHT_ADAPTATION",
|
|
48
|
-
"priority": 2,
|
|
49
|
-
"description": "Technical explanation of UV reaction"
|
|
50
|
-
},
|
|
51
|
-
{
|
|
52
|
-
"contextName": "REDUCES_EYE_STRAIN",
|
|
53
|
-
"priority": 2,
|
|
54
|
-
"description": "Glare and fatigue protection"
|
|
55
|
-
}
|
|
56
|
-
]
|
|
57
|
-
},
|
|
58
|
-
{
|
|
59
|
-
"metricId": "technical_depth_score_001",
|
|
60
|
-
"metricName": "Technical Depth Score",
|
|
61
|
-
"metricDescription": "A numeric evaluation of the staff'\''s product knowledge during the pitch",
|
|
62
|
-
"hasContext": true,
|
|
63
|
-
"isNumneric": true,
|
|
64
|
-
"contexts": [
|
|
65
|
-
{
|
|
66
|
-
"minValue": 0,
|
|
67
|
-
"maxValue": 100
|
|
68
|
-
}
|
|
69
|
-
]
|
|
70
|
-
},
|
|
71
|
-
{
|
|
72
|
-
"metricId": "customer_sentiment_001",
|
|
73
|
-
"metricName": "Initial Customer Sentiment",
|
|
74
|
-
"metricDescription": "The immediate reaction of the customer when the lens was introduced",
|
|
75
|
-
"hasContext": true,
|
|
76
|
-
"isNumneric": false,
|
|
77
|
-
"contexts": [
|
|
78
|
-
{
|
|
79
|
-
"contextName": "POSITIVE",
|
|
80
|
-
"priority": 2,
|
|
81
|
-
"description": "High interest or curiosity"
|
|
82
|
-
},
|
|
83
|
-
{
|
|
84
|
-
"contextName": "NEUTRAL",
|
|
85
|
-
"priority": 1,
|
|
86
|
-
"description": "Acknowledgment or price-focused only"
|
|
87
|
-
},
|
|
88
|
-
{
|
|
89
|
-
"contextName": "NEGATIVE",
|
|
90
|
-
"priority": 0,
|
|
91
|
-
"description": "Dismissive or disinterested"
|
|
92
|
-
}
|
|
93
|
-
]
|
|
94
|
-
},
|
|
95
|
-
{
|
|
96
|
-
"metricId": "staff_responsiveness_001",
|
|
97
|
-
"metricName": "Staff Responsiveness",
|
|
98
|
-
"metricDescription": "How effectively were customer doubts addressed?",
|
|
99
|
-
"hasContext": true,
|
|
100
|
-
"isNumneric": false,
|
|
101
|
-
"contexts": [
|
|
102
|
-
{
|
|
103
|
-
"contextName": "FULLY_RESPONSIVE",
|
|
104
|
-
"priority": 2,
|
|
105
|
-
"description": "All doubts cleared with evidence"
|
|
106
|
-
},
|
|
107
|
-
{
|
|
108
|
-
"contextName": "PARTIALLY_RESPONSIVE",
|
|
109
|
-
"priority": 1,
|
|
110
|
-
"description": "Some doubts addressed, others ignored"
|
|
111
|
-
},
|
|
112
|
-
{
|
|
113
|
-
"contextName": "UNRESPONSIVE",
|
|
114
|
-
"priority": 0,
|
|
115
|
-
"description": "Customer doubts were dismissed"
|
|
116
|
-
}
|
|
117
|
-
]
|
|
118
|
-
},
|
|
119
|
-
{
|
|
120
|
-
"metricId": "sales_outcome_001",
|
|
121
|
-
"metricName": "Final Sales Outcome",
|
|
122
|
-
"metricDescription": "The final commitment level reached at the end of the conversation",
|
|
123
|
-
"hasContext": true,
|
|
124
|
-
"isNumneric": false,
|
|
125
|
-
"contexts": [
|
|
126
|
-
{
|
|
127
|
-
"contextName": "BOUGHT_PHOTOCHROMATIC",
|
|
128
|
-
"priority": 2,
|
|
129
|
-
"description": "Customer agreed to the upgrade"
|
|
130
|
-
},
|
|
131
|
-
{
|
|
132
|
-
"contextName": "BOUGHT_SOMETHING_ELSE",
|
|
133
|
-
"priority": 0,
|
|
134
|
-
"description": "Bought standard lenses/frames only"
|
|
135
|
-
},
|
|
136
|
-
{
|
|
137
|
-
"contextName": "NO_SALE",
|
|
138
|
-
"priority": 0,
|
|
139
|
-
"description": "No purchase commitment"
|
|
140
|
-
}
|
|
141
|
-
]
|
|
142
|
-
},
|
|
143
|
-
{
|
|
144
|
-
"metricId": "pitch_timing_summary_001",
|
|
145
|
-
"metricName": "Pitch Timing & Context",
|
|
146
|
-
"metricDescription": "Narrative summary of when and how the pitch was introduced",
|
|
147
|
-
"hasContext": false,
|
|
148
|
-
"isNumneric": false
|
|
149
|
-
}
|
|
150
|
-
]
|
|
151
|
-
}'
|
|
152
|
-
|
|
153
|
-
# ==================== 2. GET COHORT BY ID ====================
|
|
154
|
-
print_section "2. GET COHORT BY ID"
|
|
155
|
-
|
|
156
|
-
curl -X GET "$BASE_URL/cohorts/$COHORT_ID" \
|
|
157
|
-
-H "Content-Type: application/json"
|
|
158
|
-
|
|
159
|
-
# ==================== 3. GET COHORTS BY CLIENT ====================
|
|
160
|
-
print_section "3. GET COHORTS BY CLIENT"
|
|
161
|
-
|
|
162
|
-
curl -X GET "$BASE_URL/cohorts/client/$CLIENT_ID?limit=10&offset=0" \
|
|
163
|
-
-H "Content-Type: application/json"
|
|
164
|
-
|
|
165
|
-
# ==================== 4. SEARCH COHORTS ====================
|
|
166
|
-
print_section "4. SEARCH COHORTS"
|
|
167
|
-
|
|
168
|
-
curl -X POST "$BASE_URL/cohorts/search" \
|
|
169
|
-
-H "Content-Type: application/json" \
|
|
170
|
-
-d '{
|
|
171
|
-
"clientId": "11",
|
|
172
|
-
"cohortName": "Photochrom",
|
|
173
|
-
"limit": 10,
|
|
174
|
-
"offset": 0
|
|
175
|
-
}'
|
|
176
|
-
|
|
177
|
-
# ==================== 5. UPDATE COHORT ====================
|
|
178
|
-
print_section "5. UPDATE COHORT"
|
|
179
|
-
|
|
180
|
-
# Note: Replace DOCUMENT_ID with actual ID returned from create
|
|
181
|
-
DOCUMENT_ID="550e8400-e29b-41d4-a716-446655440000"
|
|
182
|
-
|
|
183
|
-
curl -X PUT "$BASE_URL/cohorts/$DOCUMENT_ID" \
|
|
184
|
-
-H "Content-Type: application/json" \
|
|
185
|
-
-d '{
|
|
186
|
-
"cohortName": "Updated Photochromatic",
|
|
187
|
-
"cohortDescription": "Updated description for photochromatic cohort"
|
|
188
|
-
}'
|
|
189
|
-
|
|
190
|
-
# ==================== 6. GET COHORT ANALYTICS ====================
|
|
191
|
-
print_section "6. GET COHORT ANALYTICS"
|
|
192
|
-
|
|
193
|
-
curl -X GET "$BASE_URL/cohorts/$COHORT_ID/analytics" \
|
|
194
|
-
-H "Content-Type: application/json"
|
|
195
|
-
|
|
196
|
-
# ==================== 7. DELETE COHORT ====================
|
|
197
|
-
print_section "7. DELETE COHORT"
|
|
198
|
-
|
|
199
|
-
# Note: Replace DOCUMENT_ID with actual ID
|
|
200
|
-
curl -X DELETE "$BASE_URL/cohorts/$DOCUMENT_ID" \
|
|
201
|
-
-H "Content-Type: application/json"
|
|
202
|
-
|
|
203
|
-
# ==================== VALIDATION TESTS ====================
|
|
204
|
-
print_section "VALIDATION TESTS"
|
|
205
|
-
|
|
206
|
-
echo "Testing invalid cohort name (too short):"
|
|
207
|
-
curl -X POST "$BASE_URL/cohorts" \
|
|
208
|
-
-H "Content-Type: application/json" \
|
|
209
|
-
-d '{
|
|
210
|
-
"clientId": "11",
|
|
211
|
-
"cohortId": "invalid_test",
|
|
212
|
-
"cohortName": "AB",
|
|
213
|
-
"cohortDescription": "This should fail because name is too short"
|
|
214
|
-
}'
|
|
215
|
-
|
|
216
|
-
echo ""
|
|
217
|
-
echo "Testing missing required field:"
|
|
218
|
-
curl -X POST "$BASE_URL/cohorts" \
|
|
219
|
-
-H "Content-Type: application/json" \
|
|
220
|
-
-d '{
|
|
221
|
-
"clientId": "11",
|
|
222
|
-
"cohortId": "invalid_test",
|
|
223
|
-
"cohortName": "Valid Name"
|
|
224
|
-
}'
|
|
225
|
-
|
|
226
|
-
echo ""
|
|
227
|
-
echo "Testing invalid cohortId (special characters):"
|
|
228
|
-
curl -X POST "$BASE_URL/cohorts" \
|
|
229
|
-
-H "Content-Type: application/json" \
|
|
230
|
-
-d '{
|
|
231
|
-
"clientId": "11",
|
|
232
|
-
"cohortId": "invalid@test",
|
|
233
|
-
"cohortName": "Valid Name",
|
|
234
|
-
"cohortDescription": "This should fail because cohortId has invalid characters"
|
|
235
|
-
}'
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
# Cohort API Test Examples
|
|
4
|
+
# This file contains curl commands to test all cohort API endpoints
|
|
5
|
+
|
|
6
|
+
BASE_URL="https://testtangoretail-api.tangoeye.ai/v3/audio-analitics"
|
|
7
|
+
COHORT_ID="cohort_photochromatic_001"
|
|
8
|
+
CLIENT_ID="11"
|
|
9
|
+
|
|
10
|
+
# ==================== Helper Function ====================
|
|
11
|
+
print_section() {
|
|
12
|
+
echo ""
|
|
13
|
+
echo "=================================="
|
|
14
|
+
echo "$1"
|
|
15
|
+
echo "=================================="
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
# ==================== 1. CREATE COHORT ====================
|
|
19
|
+
print_section "1. CREATE COHORT"
|
|
20
|
+
|
|
21
|
+
curl -X POST "$BASE_URL/cohorts" \
|
|
22
|
+
-H "Content-Type: application/json" \
|
|
23
|
+
-d '{
|
|
24
|
+
"clientId": "11",
|
|
25
|
+
"cohortId": "cohort_photochromatic_001",
|
|
26
|
+
"cohortName": "Photochromatic",
|
|
27
|
+
"cohortDescription": "A strict evaluation of the photochromatic sales journey, focusing on pitch quality, technical accuracy, and customer sentiment",
|
|
28
|
+
"metrics": [
|
|
29
|
+
{
|
|
30
|
+
"metricId": "pitch_quality_001",
|
|
31
|
+
"metricName": "Standard Pitch Quality",
|
|
32
|
+
"metricDescription": "Identify which specific value propositions were explicitly used by the staff",
|
|
33
|
+
"hasContext": true,
|
|
34
|
+
"isNumneric": false,
|
|
35
|
+
"contexts": [
|
|
36
|
+
{
|
|
37
|
+
"contextName": "ONE_GLASS_ALL_NEEDS",
|
|
38
|
+
"priority": 2,
|
|
39
|
+
"description": "Convenience of one pair for all lighting"
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
"contextName": "TRAVEL_CONVENIENCE",
|
|
43
|
+
"priority": 2,
|
|
44
|
+
"description": "Specific benefits for bikers/commuters"
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"contextName": "LIGHT_ADAPTATION",
|
|
48
|
+
"priority": 2,
|
|
49
|
+
"description": "Technical explanation of UV reaction"
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"contextName": "REDUCES_EYE_STRAIN",
|
|
53
|
+
"priority": 2,
|
|
54
|
+
"description": "Glare and fatigue protection"
|
|
55
|
+
}
|
|
56
|
+
]
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
"metricId": "technical_depth_score_001",
|
|
60
|
+
"metricName": "Technical Depth Score",
|
|
61
|
+
"metricDescription": "A numeric evaluation of the staff'\''s product knowledge during the pitch",
|
|
62
|
+
"hasContext": true,
|
|
63
|
+
"isNumneric": true,
|
|
64
|
+
"contexts": [
|
|
65
|
+
{
|
|
66
|
+
"minValue": 0,
|
|
67
|
+
"maxValue": 100
|
|
68
|
+
}
|
|
69
|
+
]
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
"metricId": "customer_sentiment_001",
|
|
73
|
+
"metricName": "Initial Customer Sentiment",
|
|
74
|
+
"metricDescription": "The immediate reaction of the customer when the lens was introduced",
|
|
75
|
+
"hasContext": true,
|
|
76
|
+
"isNumneric": false,
|
|
77
|
+
"contexts": [
|
|
78
|
+
{
|
|
79
|
+
"contextName": "POSITIVE",
|
|
80
|
+
"priority": 2,
|
|
81
|
+
"description": "High interest or curiosity"
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
"contextName": "NEUTRAL",
|
|
85
|
+
"priority": 1,
|
|
86
|
+
"description": "Acknowledgment or price-focused only"
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
"contextName": "NEGATIVE",
|
|
90
|
+
"priority": 0,
|
|
91
|
+
"description": "Dismissive or disinterested"
|
|
92
|
+
}
|
|
93
|
+
]
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
"metricId": "staff_responsiveness_001",
|
|
97
|
+
"metricName": "Staff Responsiveness",
|
|
98
|
+
"metricDescription": "How effectively were customer doubts addressed?",
|
|
99
|
+
"hasContext": true,
|
|
100
|
+
"isNumneric": false,
|
|
101
|
+
"contexts": [
|
|
102
|
+
{
|
|
103
|
+
"contextName": "FULLY_RESPONSIVE",
|
|
104
|
+
"priority": 2,
|
|
105
|
+
"description": "All doubts cleared with evidence"
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
"contextName": "PARTIALLY_RESPONSIVE",
|
|
109
|
+
"priority": 1,
|
|
110
|
+
"description": "Some doubts addressed, others ignored"
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
"contextName": "UNRESPONSIVE",
|
|
114
|
+
"priority": 0,
|
|
115
|
+
"description": "Customer doubts were dismissed"
|
|
116
|
+
}
|
|
117
|
+
]
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
"metricId": "sales_outcome_001",
|
|
121
|
+
"metricName": "Final Sales Outcome",
|
|
122
|
+
"metricDescription": "The final commitment level reached at the end of the conversation",
|
|
123
|
+
"hasContext": true,
|
|
124
|
+
"isNumneric": false,
|
|
125
|
+
"contexts": [
|
|
126
|
+
{
|
|
127
|
+
"contextName": "BOUGHT_PHOTOCHROMATIC",
|
|
128
|
+
"priority": 2,
|
|
129
|
+
"description": "Customer agreed to the upgrade"
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
"contextName": "BOUGHT_SOMETHING_ELSE",
|
|
133
|
+
"priority": 0,
|
|
134
|
+
"description": "Bought standard lenses/frames only"
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
"contextName": "NO_SALE",
|
|
138
|
+
"priority": 0,
|
|
139
|
+
"description": "No purchase commitment"
|
|
140
|
+
}
|
|
141
|
+
]
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
"metricId": "pitch_timing_summary_001",
|
|
145
|
+
"metricName": "Pitch Timing & Context",
|
|
146
|
+
"metricDescription": "Narrative summary of when and how the pitch was introduced",
|
|
147
|
+
"hasContext": false,
|
|
148
|
+
"isNumneric": false
|
|
149
|
+
}
|
|
150
|
+
]
|
|
151
|
+
}'
|
|
152
|
+
|
|
153
|
+
# ==================== 2. GET COHORT BY ID ====================
|
|
154
|
+
print_section "2. GET COHORT BY ID"
|
|
155
|
+
|
|
156
|
+
curl -X GET "$BASE_URL/cohorts/$COHORT_ID" \
|
|
157
|
+
-H "Content-Type: application/json"
|
|
158
|
+
|
|
159
|
+
# ==================== 3. GET COHORTS BY CLIENT ====================
|
|
160
|
+
print_section "3. GET COHORTS BY CLIENT"
|
|
161
|
+
|
|
162
|
+
curl -X GET "$BASE_URL/cohorts/client/$CLIENT_ID?limit=10&offset=0" \
|
|
163
|
+
-H "Content-Type: application/json"
|
|
164
|
+
|
|
165
|
+
# ==================== 4. SEARCH COHORTS ====================
|
|
166
|
+
print_section "4. SEARCH COHORTS"
|
|
167
|
+
|
|
168
|
+
curl -X POST "$BASE_URL/cohorts/search" \
|
|
169
|
+
-H "Content-Type: application/json" \
|
|
170
|
+
-d '{
|
|
171
|
+
"clientId": "11",
|
|
172
|
+
"cohortName": "Photochrom",
|
|
173
|
+
"limit": 10,
|
|
174
|
+
"offset": 0
|
|
175
|
+
}'
|
|
176
|
+
|
|
177
|
+
# ==================== 5. UPDATE COHORT ====================
|
|
178
|
+
print_section "5. UPDATE COHORT"
|
|
179
|
+
|
|
180
|
+
# Note: Replace DOCUMENT_ID with actual ID returned from create
|
|
181
|
+
DOCUMENT_ID="550e8400-e29b-41d4-a716-446655440000"
|
|
182
|
+
|
|
183
|
+
curl -X PUT "$BASE_URL/cohorts/$DOCUMENT_ID" \
|
|
184
|
+
-H "Content-Type: application/json" \
|
|
185
|
+
-d '{
|
|
186
|
+
"cohortName": "Updated Photochromatic",
|
|
187
|
+
"cohortDescription": "Updated description for photochromatic cohort"
|
|
188
|
+
}'
|
|
189
|
+
|
|
190
|
+
# ==================== 6. GET COHORT ANALYTICS ====================
|
|
191
|
+
print_section "6. GET COHORT ANALYTICS"
|
|
192
|
+
|
|
193
|
+
curl -X GET "$BASE_URL/cohorts/$COHORT_ID/analytics" \
|
|
194
|
+
-H "Content-Type: application/json"
|
|
195
|
+
|
|
196
|
+
# ==================== 7. DELETE COHORT ====================
|
|
197
|
+
print_section "7. DELETE COHORT"
|
|
198
|
+
|
|
199
|
+
# Note: Replace DOCUMENT_ID with actual ID
|
|
200
|
+
curl -X DELETE "$BASE_URL/cohorts/$DOCUMENT_ID" \
|
|
201
|
+
-H "Content-Type: application/json"
|
|
202
|
+
|
|
203
|
+
# ==================== VALIDATION TESTS ====================
|
|
204
|
+
print_section "VALIDATION TESTS"
|
|
205
|
+
|
|
206
|
+
echo "Testing invalid cohort name (too short):"
|
|
207
|
+
curl -X POST "$BASE_URL/cohorts" \
|
|
208
|
+
-H "Content-Type: application/json" \
|
|
209
|
+
-d '{
|
|
210
|
+
"clientId": "11",
|
|
211
|
+
"cohortId": "invalid_test",
|
|
212
|
+
"cohortName": "AB",
|
|
213
|
+
"cohortDescription": "This should fail because name is too short"
|
|
214
|
+
}'
|
|
215
|
+
|
|
216
|
+
echo ""
|
|
217
|
+
echo "Testing missing required field:"
|
|
218
|
+
curl -X POST "$BASE_URL/cohorts" \
|
|
219
|
+
-H "Content-Type: application/json" \
|
|
220
|
+
-d '{
|
|
221
|
+
"clientId": "11",
|
|
222
|
+
"cohortId": "invalid_test",
|
|
223
|
+
"cohortName": "Valid Name"
|
|
224
|
+
}'
|
|
225
|
+
|
|
226
|
+
echo ""
|
|
227
|
+
echo "Testing invalid cohortId (special characters):"
|
|
228
|
+
curl -X POST "$BASE_URL/cohorts" \
|
|
229
|
+
-H "Content-Type: application/json" \
|
|
230
|
+
-d '{
|
|
231
|
+
"clientId": "11",
|
|
232
|
+
"cohortId": "invalid@test",
|
|
233
|
+
"cohortName": "Valid Name",
|
|
234
|
+
"cohortDescription": "This should fail because cohortId has invalid characters"
|
|
235
|
+
}'
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tango-app-api-audio-analytics",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.32",
|
|
4
4
|
"description": "audioAnalytics",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"start": "nodemon --exec \"eslint --fix . && node app.js\""
|
|
9
9
|
},
|
|
10
10
|
"engines": {
|
|
11
|
-
"node": ">=18.10.0"
|
|
11
|
+
"node": ">=18.10.0 <25"
|
|
12
12
|
},
|
|
13
13
|
"author": "praveenraj",
|
|
14
14
|
"license": "ISC",
|
|
@@ -7,6 +7,7 @@ const EXTERNAL_STREAM_API = 'http://172.236.179.51:8000/stream';
|
|
|
7
7
|
export async function createCohort( req, res ) {
|
|
8
8
|
try {
|
|
9
9
|
const inputData = req.body;
|
|
10
|
+
const openSearch = JSON.parse( process.env.OPENSEARCH );
|
|
10
11
|
|
|
11
12
|
const cohortId = `cohort_${inputData.clientId}_${randomUUID()}`;
|
|
12
13
|
|
|
@@ -32,7 +33,7 @@ export async function createCohort( req, res ) {
|
|
|
32
33
|
updatedAt: new Date().toISOString(),
|
|
33
34
|
};
|
|
34
35
|
|
|
35
|
-
const result = await insertWithId(
|
|
36
|
+
const result = await insertWithId( openSearch.audioconfig, cohortId, cohortData );
|
|
36
37
|
logger.info( { result } );
|
|
37
38
|
|
|
38
39
|
if ( result && result.body && result.body.result === 'created' ) {
|
|
@@ -50,7 +51,7 @@ export async function createCohort( req, res ) {
|
|
|
50
51
|
export async function createBulkCohort( req, res ) {
|
|
51
52
|
try {
|
|
52
53
|
const { clientId, cohorts } = req.body;
|
|
53
|
-
|
|
54
|
+
const openSearch = JSON.parse( process.env.OPENSEARCH );
|
|
54
55
|
const results = [];
|
|
55
56
|
const failed = [];
|
|
56
57
|
|
|
@@ -68,7 +69,7 @@ export async function createBulkCohort( req, res ) {
|
|
|
68
69
|
updatedAt: new Date().toISOString(),
|
|
69
70
|
};
|
|
70
71
|
|
|
71
|
-
const result = await insertWithId(
|
|
72
|
+
const result = await insertWithId( openSearch.audioconfig, cohortId, cohortData );
|
|
72
73
|
logger.info( { result } );
|
|
73
74
|
|
|
74
75
|
if ( result && result.body && result.body.result === 'created' ) {
|
|
@@ -95,7 +96,7 @@ export async function createBulkCohort( req, res ) {
|
|
|
95
96
|
export async function updateCohort( req, res ) {
|
|
96
97
|
try {
|
|
97
98
|
const { cohortId, ...updateFields } = req.body;
|
|
98
|
-
|
|
99
|
+
const openSearch = JSON.parse( process.env.OPENSEARCH );
|
|
99
100
|
const metrics = updateFields.metrics ?
|
|
100
101
|
updateFields.metrics.map( ( metric ) => ( {
|
|
101
102
|
...metric,
|
|
@@ -119,7 +120,7 @@ export async function updateCohort( req, res ) {
|
|
|
119
120
|
doc_as_upsert: true,
|
|
120
121
|
};
|
|
121
122
|
|
|
122
|
-
const result = await updateOpenSearchData(
|
|
123
|
+
const result = await updateOpenSearchData( openSearch.audioconfig, cohortId, updatePayload );
|
|
123
124
|
logger.info( { result } );
|
|
124
125
|
|
|
125
126
|
if ( result && result.body && result.body.result === 'updated' ) {
|
|
@@ -137,7 +138,7 @@ export async function updateCohort( req, res ) {
|
|
|
137
138
|
export async function deleteCohort( req, res ) {
|
|
138
139
|
try {
|
|
139
140
|
const { cohortId } = req.body;
|
|
140
|
-
|
|
141
|
+
const openSearch = JSON.parse( process.env.OPENSEARCH );
|
|
141
142
|
const updatePayload = {
|
|
142
143
|
doc: {
|
|
143
144
|
isDeleted: true,
|
|
@@ -145,7 +146,7 @@ export async function deleteCohort( req, res ) {
|
|
|
145
146
|
},
|
|
146
147
|
};
|
|
147
148
|
|
|
148
|
-
const result = await updateOpenSearchData(
|
|
149
|
+
const result = await updateOpenSearchData( openSearch.audioconfig, cohortId, updatePayload );
|
|
149
150
|
logger.info( { result } );
|
|
150
151
|
|
|
151
152
|
if ( result && result.body && result.body.result === 'updated' ) {
|
|
@@ -163,14 +164,14 @@ export async function deleteCohort( req, res ) {
|
|
|
163
164
|
export async function getCohort( req, res ) {
|
|
164
165
|
try {
|
|
165
166
|
const { cohortId } = req.params;
|
|
166
|
-
|
|
167
|
+
const openSearch = JSON.parse( process.env.OPENSEARCH );
|
|
167
168
|
const query = {
|
|
168
169
|
query: {
|
|
169
170
|
match: { cohortId },
|
|
170
171
|
},
|
|
171
172
|
};
|
|
172
173
|
|
|
173
|
-
const result = await searchOpenSearchData(
|
|
174
|
+
const result = await searchOpenSearchData( openSearch.audioconfig, query );
|
|
174
175
|
logger.info( { result } );
|
|
175
176
|
|
|
176
177
|
if ( !result || result?.body?.hits?.hits?.length === 0 ) {
|
|
@@ -188,6 +189,7 @@ export async function getCohort( req, res ) {
|
|
|
188
189
|
export async function listCohortsByClient( req, res ) {
|
|
189
190
|
try {
|
|
190
191
|
const { clientId, limit = 10, offset = 0 } = req.query;
|
|
192
|
+
const openSearch = JSON.parse( process.env.OPENSEARCH );
|
|
191
193
|
logger.info( { message: req.query, function: 'listCohortsByClient' } );
|
|
192
194
|
const query = {
|
|
193
195
|
query: {
|
|
@@ -205,7 +207,7 @@ export async function listCohortsByClient( req, res ) {
|
|
|
205
207
|
sort: [ { createdAt: { order: 'desc' } } ],
|
|
206
208
|
};
|
|
207
209
|
|
|
208
|
-
const result = await searchOpenSearchData(
|
|
210
|
+
const result = await searchOpenSearchData( openSearch.audioconfig, query );
|
|
209
211
|
logger.info( { result } );
|
|
210
212
|
|
|
211
213
|
const total = result?.body?.hits?.total?.value || 0;
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
exportConversationsFromLambda,
|
|
6
6
|
getConversationDetailsFromLambda,
|
|
7
7
|
getTranscriptListFromLambda,
|
|
8
|
+
getCategoryAnalysisCardFromLambda,
|
|
8
9
|
// filterConversationsBySearch,
|
|
9
10
|
// sortConversations,
|
|
10
11
|
} from '../services/conversation.service.js';
|
|
@@ -334,3 +335,77 @@ export const getConversationDetails = async ( req, res ) => {
|
|
|
334
335
|
}
|
|
335
336
|
};
|
|
336
337
|
|
|
338
|
+
/**
|
|
339
|
+
* Get Category Analysis Card
|
|
340
|
+
* POST /category-analysis-card
|
|
341
|
+
* @param {Object} req - Express request object
|
|
342
|
+
* @param {Object} req.body - Request body
|
|
343
|
+
* @param {string} req.body.startDate - Start date (YYYY-MM-DD)
|
|
344
|
+
* @param {string} req.body.endDate - End date (YYYY-MM-DD)
|
|
345
|
+
* @param {string[]} req.body.storeId - Array of store IDs
|
|
346
|
+
* @param {string} req.body.cohortId - Cohort ID
|
|
347
|
+
* @param {string} req.body.clientId - Client ID
|
|
348
|
+
* @param {number} req.body.id - Category numeric ID
|
|
349
|
+
* @param {string} req.body.category - Category name
|
|
350
|
+
* @param {Object} res - Express response object
|
|
351
|
+
* @return {void} Returns JSON response with category analysis card data
|
|
352
|
+
*/
|
|
353
|
+
export const getCategoryAnalysisCard = async ( req, res ) => {
|
|
354
|
+
try {
|
|
355
|
+
const { startDate, endDate, storeId, cohortId, clientId, id, category } = req.body;
|
|
356
|
+
|
|
357
|
+
if ( !startDate || !endDate || !storeId || !cohortId || !clientId || id === undefined || !category ) {
|
|
358
|
+
return res.status( 400 ).json( {
|
|
359
|
+
status: 'error',
|
|
360
|
+
message: 'Missing required parameters: startDate, endDate, storeId, cohortId, clientId, id, category',
|
|
361
|
+
code: 'MISSING_PARAMETERS',
|
|
362
|
+
timestamp: new Date().toISOString(),
|
|
363
|
+
} );
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
logger.info( {
|
|
367
|
+
message: 'Fetching category analysis card',
|
|
368
|
+
startDate,
|
|
369
|
+
endDate,
|
|
370
|
+
storeId,
|
|
371
|
+
cohortId,
|
|
372
|
+
clientId,
|
|
373
|
+
id,
|
|
374
|
+
category,
|
|
375
|
+
} );
|
|
376
|
+
|
|
377
|
+
const lambdaResponse = await getCategoryAnalysisCardFromLambda( {
|
|
378
|
+
startDate,
|
|
379
|
+
endDate,
|
|
380
|
+
storeId,
|
|
381
|
+
cohortId,
|
|
382
|
+
clientId,
|
|
383
|
+
id,
|
|
384
|
+
category,
|
|
385
|
+
} );
|
|
386
|
+
|
|
387
|
+
if ( !lambdaResponse ) {
|
|
388
|
+
return res.status( 204 ).json( {
|
|
389
|
+
status: 'error',
|
|
390
|
+
message: 'No data found for the given parameters',
|
|
391
|
+
code: 'DATA_NOT_FOUND',
|
|
392
|
+
timestamp: new Date().toISOString(),
|
|
393
|
+
} );
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
return res.status( 200 ).json( {
|
|
397
|
+
status: lambdaResponse?.status || 'success',
|
|
398
|
+
data: lambdaResponse?.data ?? lambdaResponse,
|
|
399
|
+
timestamp: new Date().toISOString(),
|
|
400
|
+
} );
|
|
401
|
+
} catch ( error ) {
|
|
402
|
+
logger.error( { error, message: 'Error fetching category analysis card', body: req.body } );
|
|
403
|
+
return res.status( 500 ).json( {
|
|
404
|
+
status: 'error',
|
|
405
|
+
message: error.message || 'Internal server error',
|
|
406
|
+
code: 'INTERNAL_ERROR',
|
|
407
|
+
timestamp: new Date().toISOString(),
|
|
408
|
+
} );
|
|
409
|
+
}
|
|
410
|
+
};
|
|
411
|
+
|
|
@@ -510,6 +510,19 @@ export const cohortAnalysisCardValid = joi.object( {
|
|
|
510
510
|
clientId: joi.array().items( joi.string() ).required().min( 1 ).description( 'Array of client IDs' ),
|
|
511
511
|
} ).strict();
|
|
512
512
|
|
|
513
|
+
/**
|
|
514
|
+
* Category Analysis Card Schema
|
|
515
|
+
*/
|
|
516
|
+
export const categoryAnalysisCardValid = joi.object( {
|
|
517
|
+
startDate: joi.string().required().pattern( /^\d{4}-\d{2}-\d{2}$/ ).description( 'Start date in YYYY-MM-DD format' ),
|
|
518
|
+
endDate: joi.string().required().pattern( /^\d{4}-\d{2}-\d{2}$/ ).description( 'End date in YYYY-MM-DD format' ),
|
|
519
|
+
storeId: joi.array().items( joi.string() ).required().min( 1 ).description( 'Array of store IDs' ),
|
|
520
|
+
cohortId: joi.string().required().description( 'Cohort ID' ),
|
|
521
|
+
clientId: joi.string().required().description( 'Client ID' ),
|
|
522
|
+
id: joi.number().integer().required().description( 'Category numeric ID' ),
|
|
523
|
+
category: joi.string().required().description( 'Category name' ),
|
|
524
|
+
} ).strict();
|
|
525
|
+
|
|
513
526
|
/**
|
|
514
527
|
* Conversations List Schema
|
|
515
528
|
*/
|
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
import express from 'express';
|
|
3
3
|
import { validate } from 'tango-app-api-middleware';
|
|
4
4
|
import { createCohortValid, createBulkCohortValid, updateCohortValid, getCohortValid, listCohortsByClientValid, deleteCohortValid } from '../dtos/audioAnalytics.dtos.js';
|
|
5
|
-
import { cohortAnalysisCardValid, conversationsListValid, conversationDetailsValid, chatHistoryListValid, getChatValid, getGeminiResponseValid, transcriptListValid } from '../dtos/audioAnalytics.dtos.js';
|
|
5
|
+
import { cohortAnalysisCardValid, categoryAnalysisCardValid, conversationsListValid, conversationDetailsValid, chatHistoryListValid, getChatValid, getGeminiResponseValid, transcriptListValid } from '../dtos/audioAnalytics.dtos.js';
|
|
6
6
|
import { createCohort, createBulkCohort, updateCohort, deleteCohort, getCohort, listCohortsByClient, chatHistoryList, getChat, getGeminiResponse, aiStreamResponse } from '../controllers/audioAnalytics.controller.js';
|
|
7
7
|
import { getCohortAnalysisCard } from '../controllers/cohortAnalytics.controller.js';
|
|
8
|
-
import { getConversationsList, getTranscriptsList, getConversationDetails } from '../controllers/conversationAnalytics.controller.js';
|
|
8
|
+
import { getConversationsList, getTranscriptsList, getConversationDetails, getCategoryAnalysisCard } from '../controllers/conversationAnalytics.controller.js';
|
|
9
9
|
|
|
10
10
|
export const audioAnalyticsrouter = express.Router(); ;
|
|
11
11
|
|
|
@@ -27,6 +27,7 @@ audioAnalyticsrouter.post( '/chat-history-list', validate( chatHistoryListValid
|
|
|
27
27
|
audioAnalyticsrouter.post( '/get-chat', validate( getChatValid ), getChat );
|
|
28
28
|
// Cohort Analytics Routes
|
|
29
29
|
audioAnalyticsrouter.post( '/cohort-analysis-card', validate( cohortAnalysisCardValid ), getCohortAnalysisCard );
|
|
30
|
+
audioAnalyticsrouter.post( '/category-analysis-card', validate( categoryAnalysisCardValid ), getCategoryAnalysisCard );
|
|
30
31
|
|
|
31
32
|
// Conversation Analytics Routes
|
|
32
33
|
audioAnalyticsrouter.post( '/conversations/list', validate( conversationsListValid ), getConversationsList );
|
|
@@ -289,6 +289,55 @@ export async function getCohortAnalysisSummaryCard( params ) {
|
|
|
289
289
|
}
|
|
290
290
|
}
|
|
291
291
|
|
|
292
|
+
/**
|
|
293
|
+
* Call Lambda to get category analysis card
|
|
294
|
+
* @param {Object} params - Request parameters
|
|
295
|
+
* @param {string} params.startDate - Start date (YYYY-MM-DD)
|
|
296
|
+
* @param {string} params.endDate - End date (YYYY-MM-DD)
|
|
297
|
+
* @param {string[]} params.storeId - Array of store IDs
|
|
298
|
+
* @param {string} params.cohortId - Cohort ID
|
|
299
|
+
* @param {string} params.clientId - Client ID
|
|
300
|
+
* @param {number} params.id - Category numeric ID
|
|
301
|
+
* @param {string} params.category - Category name
|
|
302
|
+
* @return {Promise<Object>} Response from Lambda
|
|
303
|
+
*/
|
|
304
|
+
export async function getCategoryAnalysisCardFromLambda( params ) {
|
|
305
|
+
try {
|
|
306
|
+
const LAMBDA_ENDPOINT = JSON.parse( process.env.URL ) || 'http://lambda-api:8000';
|
|
307
|
+
|
|
308
|
+
const payload = {
|
|
309
|
+
startDate: params.startDate,
|
|
310
|
+
endDate: params.endDate,
|
|
311
|
+
storeId: Array.isArray( params.storeId ) ? params.storeId : [ params.storeId ],
|
|
312
|
+
cohortId: params.cohortId,
|
|
313
|
+
clientId: params.clientId,
|
|
314
|
+
id: params.id,
|
|
315
|
+
category: params.category,
|
|
316
|
+
};
|
|
317
|
+
|
|
318
|
+
logger.info( { message: 'Calling Lambda for category analysis card', url: LAMBDA_ENDPOINT.categoryAnalysisCard, payload } );
|
|
319
|
+
|
|
320
|
+
const response = await axios.post( `${LAMBDA_ENDPOINT.categoryAnalysisCard}`, payload, {
|
|
321
|
+
headers: {
|
|
322
|
+
'Content-Type': 'application/json',
|
|
323
|
+
},
|
|
324
|
+
timeout: 30000,
|
|
325
|
+
} );
|
|
326
|
+
|
|
327
|
+
logger.info( { message: 'Lambda response received for category analysis card', status: response.status } );
|
|
328
|
+
return response.data;
|
|
329
|
+
} catch ( error ) {
|
|
330
|
+
logger.error( {
|
|
331
|
+
error: error.message,
|
|
332
|
+
status: error.response?.status,
|
|
333
|
+
data: error.response?.data,
|
|
334
|
+
message: 'Error calling Lambda for category analysis card',
|
|
335
|
+
params,
|
|
336
|
+
} );
|
|
337
|
+
throw new Error( `Failed to fetch category analysis card: ${error.message}` );
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
292
341
|
/**
|
|
293
342
|
* Process and filter conversations based on search criteria
|
|
294
343
|
* @param {Array} conversations - Array of conversation objects
|