tango-app-api-audio-analytics 1.0.0

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.
@@ -0,0 +1,537 @@
1
+ import joi from 'joi';
2
+
3
+ /**
4
+ * Audio Analytics DTOs (Data Transfer Objects)
5
+ * Defines request and response structures for audio analytics API endpoints
6
+ */
7
+
8
+ // ======================= REQUEST DTOs =======================
9
+
10
+ /**
11
+ * Cohort Analysis Request DTO
12
+ * @typedef {Object} CohortAnalysisRequestDTO
13
+ * @property {string} startDate - Start date (YYYY-MM-DD)
14
+ * @property {string} endDate - End date (YYYY-MM-DD)
15
+ * @property {string[]} storeId - Array of store IDs
16
+ * @property {string} cohortType - Type of cohort (e.g., "Photochromatic")
17
+ * @property {string[]} clientId - Array of client IDs
18
+ */
19
+ export class CohortAnalysisRequestDTO {
20
+ constructor( startDate, endDate, storeId, cohortType, clientId ) {
21
+ this.startDate = startDate;
22
+ this.endDate = endDate;
23
+ this.storeId = storeId;
24
+ this.cohortType = cohortType;
25
+ this.clientId = clientId;
26
+ }
27
+
28
+ validate() {
29
+ if ( !this.startDate || !this.endDate || !this.storeId ) {
30
+ throw new Error( 'Missing required parameters: startDate, endDate, storeId' );
31
+ }
32
+ return true;
33
+ }
34
+ }
35
+
36
+ /**
37
+ * Conversations List Request DTO
38
+ * @typedef {Object} ConversationsListRequestDTO
39
+ * @property {string} startDate - Start date (YYYY-MM-DD)
40
+ * @property {string} endDate - End date (YYYY-MM-DD)
41
+ * @property {string[]} storeId - Array of store IDs
42
+ * @property {string[]} [clientId] - Optional array of client IDs
43
+ * @property {boolean} [isAI] - Filter for AI-generated conversations
44
+ * @property {string} [type] - Type of conversation (e.g., "audio")
45
+ * @property {string} [searchValue] - Search term
46
+ * @property {boolean} [isExport] - Flag to export as CSV
47
+ * @property {number} [limit] - Pagination limit
48
+ * @property {number} [offset] - Pagination offset
49
+ */
50
+ export class ConversationsListRequestDTO {
51
+ constructor( data ) {
52
+ this.startDate = data.startDate;
53
+ this.endDate = data.endDate;
54
+ this.storeId = data.storeId;
55
+ this.clientId = data.clientId || [];
56
+ this.isAI = data.isAI || false;
57
+ this.type = data.type || 'audio';
58
+ this.searchValue = data.searchValue || '';
59
+ this.isExport = data.isExport || false;
60
+ this.limit = data.limit || 10;
61
+ this.offset = data.offset || 0;
62
+ }
63
+
64
+ validate() {
65
+ if ( !this.startDate || !this.endDate || !this.storeId ) {
66
+ throw new Error( 'Missing required parameters: startDate, endDate, storeId' );
67
+ }
68
+ if ( this.limit < 1 || this.limit > 1000 ) {
69
+ throw new Error( 'Limit must be between 1 and 1000' );
70
+ }
71
+ return true;
72
+ }
73
+ }
74
+
75
+ /**
76
+ * Conversation Details Request DTO
77
+ * @typedef {Object} ConversationDetailsRequestDTO
78
+ * @property {string} conversationId - Conversation ID (from URL param)
79
+ * @property {string} storeId - Store ID
80
+ */
81
+ export class ConversationDetailsRequestDTO {
82
+ constructor( conversationId, storeId ) {
83
+ this.conversationId = conversationId;
84
+ this.storeId = storeId;
85
+ }
86
+
87
+ validate() {
88
+ if ( !this.conversationId || !this.storeId ) {
89
+ throw new Error( 'Missing required parameters: conversationId, storeId' );
90
+ }
91
+ return true;
92
+ }
93
+ }
94
+
95
+ /**
96
+ * Conversation Metrics Request DTO
97
+ * @typedef {Object} ConversationMetricsRequestDTO
98
+ * @property {string} conversationId - Conversation ID (from URL param)
99
+ * @property {string} storeId - Store ID
100
+ * @property {boolean} [includeBreakdown] - Include detailed breakdowns
101
+ */
102
+ export class ConversationMetricsRequestDTO {
103
+ constructor( conversationId, storeId, includeBreakdown = true ) {
104
+ this.conversationId = conversationId;
105
+ this.storeId = storeId;
106
+ this.includeBreakdown = includeBreakdown;
107
+ }
108
+
109
+ validate() {
110
+ if ( !this.conversationId || !this.storeId ) {
111
+ throw new Error( 'Missing required parameters: conversationId, storeId' );
112
+ }
113
+ return true;
114
+ }
115
+ }
116
+
117
+ /**
118
+ * AI Insights Request DTO
119
+ * @typedef {Object} AIInsightsRequestDTO
120
+ * @property {string} conversationId - Conversation ID (from URL param)
121
+ * @property {string} storeId - Store ID
122
+ * @property {string} [startDate] - Optional start date for trend analysis
123
+ * @property {string} [endDate] - Optional end date for trend analysis
124
+ * @property {string[]} [productModules] - Optional product modules filter
125
+ * @property {string} [country] - Optional country filter
126
+ * @property {string} [region] - Optional region filter
127
+ * @property {string[]} [clusters] - Optional cluster filter
128
+ * @property {boolean} [includeRecommendations] - Include AI recommendations
129
+ * @property {string} [analysisDepth] - Analysis depth ("brief" | "detailed")
130
+ */
131
+ export class AIInsightsRequestDTO {
132
+ constructor( data ) {
133
+ this.conversationId = data.conversationId;
134
+ this.storeId = data.storeId;
135
+ this.startDate = data.startDate;
136
+ this.endDate = data.endDate;
137
+ this.productModules = data.productModules || [];
138
+ this.country = data.country || '';
139
+ this.region = data.region || '';
140
+ this.clusters = data.clusters || [];
141
+ this.includeRecommendations = data.includeRecommendations !== false;
142
+ this.analysisDepth = data.analysisDepth || 'detailed';
143
+ }
144
+
145
+ validate() {
146
+ if ( !this.conversationId || !this.storeId ) {
147
+ throw new Error( 'Missing required parameters: conversationId, storeId' );
148
+ }
149
+ if ( this.analysisDepth && ![ 'brief', 'detailed' ].includes( this.analysisDepth ) ) {
150
+ throw new Error( 'Invalid analysisDepth. Must be "brief" or "detailed"' );
151
+ }
152
+ return true;
153
+ }
154
+ }
155
+
156
+ /**
157
+ * Conversation Transcription Request DTO
158
+ * @typedef {Object} ConversationTranscriptionRequestDTO
159
+ * @property {string} conversationId - Conversation ID (from URL param)
160
+ * @property {string} storeId - Store ID
161
+ * @property {boolean} [includeTimestamps] - Include timestamps in transcription
162
+ * @property {boolean} [highlightKeyPhases] - Highlight key phrases
163
+ */
164
+ export class ConversationTranscriptionRequestDTO {
165
+ constructor( conversationId, storeId, includeTimestamps = true, highlightKeyPhases = true ) {
166
+ this.conversationId = conversationId;
167
+ this.storeId = storeId;
168
+ this.includeTimestamps = includeTimestamps;
169
+ this.highlightKeyPhases = highlightKeyPhases;
170
+ }
171
+
172
+ validate() {
173
+ if ( !this.conversationId || !this.storeId ) {
174
+ throw new Error( 'Missing required parameters: conversationId, storeId' );
175
+ }
176
+ return true;
177
+ }
178
+ }
179
+
180
+ /**
181
+ * Dashboard Filter Request DTO
182
+ * @typedef {Object} DashboardFilterRequestDTO
183
+ * @property {Object} dateRange - Date range object
184
+ * @property {string} dateRange.startDate - Start date (YYYY-MM-DD)
185
+ * @property {string} dateRange.endDate - End date (YYYY-MM-DD)
186
+ * @property {string[]} [productModules] - Array of product module IDs
187
+ * @property {string} [country] - Selected country
188
+ * @property {string} [region] - Selected region
189
+ * @property {string[]} [clusters] - Array of cluster/store group IDs
190
+ * @property {boolean} [includeExport] - Include export option
191
+ * @property {string} [analyticsType] - Type of analytics ("overview" | "detailed")
192
+ */
193
+ export class DashboardFilterRequestDTO {
194
+ constructor( data ) {
195
+ this.dateRange = {
196
+ startDate: data.dateRange.startDate,
197
+ endDate: data.dateRange.endDate,
198
+ };
199
+ this.productModules = data.productModules || [];
200
+ this.country = data.country || '';
201
+ this.region = data.region || '';
202
+ this.clusters = data.clusters || [];
203
+ this.includeExport = data.includeExport || false;
204
+ this.analyticsType = data.analyticsType || 'overview';
205
+ }
206
+
207
+ validate() {
208
+ if ( !this.dateRange.startDate || !this.dateRange.endDate ) {
209
+ throw new Error( 'Missing required fields: dateRange.startDate, dateRange.endDate' );
210
+ }
211
+ return true;
212
+ }
213
+ }
214
+
215
+ /**
216
+ * Export Data Request DTO
217
+ * @typedef {Object} ExportDataRequestDTO
218
+ * @property {Object} dateRange - Date range object
219
+ * @property {string} dateRange.startDate - Start date (YYYY-MM-DD)
220
+ * @property {string} dateRange.endDate - End date (YYYY-MM-DD)
221
+ * @property {string[]} [productModules] - Array of product module IDs
222
+ * @property {string} [country] - Selected country
223
+ * @property {string} [region] - Selected region
224
+ * @property {string[]} [clusters] - Array of cluster/store group IDs
225
+ * @property {string} exportFormat - Export format ("csv" | "json" | "xlsx")
226
+ * @property {string[]} [includeColumns] - Columns to include in export
227
+ */
228
+ export class ExportDataRequestDTO {
229
+ constructor( data ) {
230
+ this.dateRange = {
231
+ startDate: data.dateRange.startDate,
232
+ endDate: data.dateRange.endDate,
233
+ };
234
+ this.productModules = data.productModules || [];
235
+ this.country = data.country || '';
236
+ this.region = data.region || '';
237
+ this.clusters = data.clusters || [];
238
+ this.exportFormat = data.exportFormat || 'csv';
239
+ this.includeColumns = data.includeColumns || [];
240
+ }
241
+
242
+ validate() {
243
+ if ( !this.dateRange.startDate || !this.dateRange.endDate ) {
244
+ throw new Error( 'Missing required fields: dateRange.startDate, dateRange.endDate' );
245
+ }
246
+ if ( ![ 'csv', 'json', 'xlsx' ].includes( this.exportFormat ) ) {
247
+ throw new Error( 'Invalid exportFormat. Must be "csv", "json", or "xlsx"' );
248
+ }
249
+ return true;
250
+ }
251
+ }
252
+
253
+ // ======================= RESPONSE DTOs =======================
254
+
255
+ /**
256
+ * Standard API Response DTO
257
+ * @typedef {Object} ApiResponseDTO
258
+ * @property {string} status - Response status ("success" | "error")
259
+ * @property {*} data - Response data
260
+ * @property {string} [message] - Optional message
261
+ * @property {string} [code] - Optional error code
262
+ * @property {string} timestamp - ISO timestamp
263
+ */
264
+ export class ApiResponseDTO {
265
+ constructor( status, data, timestamp = new Date().toISOString(), message = null, code = null ) {
266
+ this.status = status;
267
+ this.data = data;
268
+ this.timestamp = timestamp;
269
+ if ( message ) this.message = message;
270
+ if ( code ) this.code = code;
271
+ }
272
+
273
+ static success( data, timestamp = new Date().toISOString() ) {
274
+ return new ApiResponseDTO( 'success', data, timestamp );
275
+ }
276
+
277
+ static error( message, code = 'INTERNAL_ERROR', timestamp = new Date().toISOString() ) {
278
+ return new ApiResponseDTO( 'error', null, timestamp, message, code );
279
+ }
280
+ }
281
+
282
+ /**
283
+ * Conversation Data DTO
284
+ * @typedef {Object} ConversationDTO
285
+ * @property {string} audioId - Audio ID
286
+ * @property {string} storeId - Store ID
287
+ * @property {string} storeName - Store name
288
+ * @property {string} userName - User/staff name
289
+ * @property {string} location - Store location
290
+ * @property {string} date - Conversation date
291
+ * @property {string} time - Conversation time
292
+ * @property {string} duration - Conversation duration
293
+ * @property {string} audioDescription - Description of audio/conversation
294
+ * @property {Object} metrics - Metrics object
295
+ * @property {Object} aiInsights - AI-generated insights
296
+ * @property {Object} conversationDetails - Detailed conversation info
297
+ */
298
+ export class ConversationDTO {
299
+ constructor( data ) {
300
+ this.audioId = data.audioId;
301
+ this.storeId = data.storeId;
302
+ this.storeName = data.storeName;
303
+ this.userName = data.userName || '';
304
+ this.location = data.location;
305
+ this.date = data.date;
306
+ this.time = data.time;
307
+ this.duration = data.duration;
308
+ this.audioDescription = data.audioDescription;
309
+ this.metrics = data.metrics || {};
310
+ this.aiInsights = data.aiInsights || {};
311
+ this.conversationDetails = data.conversationDetails || {};
312
+ }
313
+ }
314
+
315
+ /**
316
+ * Metrics DTO
317
+ * @typedef {Object} MetricsDTO
318
+ * @property {Object} greetings - Greeting metrics
319
+ * @property {Object} salesReport - Sales metrics
320
+ * @property {Object[]} pitchQuality - Pitch quality metrics
321
+ * @property {Object[]} uniquePitches - Unique pitches data
322
+ * @property {Object} [conversationQuality] - Quality metrics
323
+ */
324
+ export class MetricsDTO {
325
+ constructor( data ) {
326
+ this.greetings = data.greetings || {};
327
+ this.salesReport = data.salesReport || {};
328
+ this.pitchQuality = data.pitchQuality || [];
329
+ this.uniquePitches = data.uniquePitches || [];
330
+ this.conversationQuality = data.conversationQuality || {};
331
+ }
332
+ }
333
+
334
+ /**
335
+ * AI Insights DTO
336
+ * @typedef {Object} AIInsightsDTO
337
+ * @property {string} title - Insights title
338
+ * @property {string} [summary] - Brief summary
339
+ * @property {Object[]} keyInsights - Array of key insights
340
+ * @property {Object} performanceMetrics - Performance metrics
341
+ * @property {Object} sentimentAnalysis - Sentiment analysis
342
+ * @property {Object} pitchEffectiveness - Pitch effectiveness data
343
+ * @property {Object[]} [recommendations] - AI recommendations
344
+ */
345
+ export class AIInsightsDTO {
346
+ constructor( data ) {
347
+ this.title = data.title || 'Tango AI Generated Insights';
348
+ this.summary = data.summary || [];
349
+ this.keyInsights = data.keyInsights || [];
350
+ this.performanceMetrics = data.performanceMetrics || {};
351
+ this.sentimentAnalysis = data.sentimentAnalysis || {};
352
+ this.pitchEffectiveness = data.pitchEffectiveness || {};
353
+ this.recommendations = data.recommendations || [];
354
+ }
355
+ }
356
+
357
+ /**
358
+ * Transcription DTO
359
+ * @typedef {Object} TranscriptionDTO
360
+ * @property {string} audioId - Audio ID
361
+ * @property {string} duration - Total duration
362
+ * @property {string} date - Conversation date
363
+ * @property {string} time - Conversation time
364
+ * @property {string} store - Store name
365
+ * @property {string} location - Store location
366
+ * @property {Object[]} participants - Array of participants
367
+ * @property {string} summary - Audio summary
368
+ * @property {Object[]} fullTranscription - Full transcription with timestamps
369
+ * @property {Object} keyPhrases - Key phrases extracted
370
+ */
371
+ export class TranscriptionDTO {
372
+ constructor( data ) {
373
+ this.audioId = data.audioId;
374
+ this.duration = data.duration;
375
+ this.date = data.date;
376
+ this.time = data.time;
377
+ this.store = data.store;
378
+ this.location = data.location;
379
+ this.participants = data.participants || [];
380
+ this.summary = data.summary;
381
+ this.fullTranscription = data.fullTranscription || [];
382
+ this.keyPhrases = data.keyPhrases || {};
383
+ }
384
+ }
385
+
386
+ /**
387
+ * Dashboard State DTO
388
+ * @typedef {Object} DashboardStateDTO
389
+ * @property {Object} currentFilters - Currently applied filters
390
+ * @property {string} lastUpdated - Last update timestamp
391
+ * @property {number} recordsMatching - Total records matching filters
392
+ * @property {string} viewMode - Current view mode
393
+ * @property {Object[]} [savedViews] - Array of saved views
394
+ */
395
+ export class DashboardStateDTO {
396
+ constructor( data ) {
397
+ this.currentFilters = data.currentFilters || {};
398
+ this.lastUpdated = data.lastUpdated || new Date().toISOString();
399
+ this.recordsMatching = data.recordsMatching || 0;
400
+ this.viewMode = data.viewMode || 'overview';
401
+ this.savedViews = data.savedViews || [];
402
+ }
403
+ }
404
+
405
+ /**
406
+ * Export Response DTO
407
+ * @typedef {Object} ExportResponseDTO
408
+ * @property {string} exportId - Unique export ID
409
+ * @property {string} status - Export status ("processing" | "completed" | "failed")
410
+ * @property {string} format - Export format (csv | json | xlsx)
411
+ * @property {number} recordCount - Number of records exported
412
+ * @property {string} estimatedFileSize - Estimated file size
413
+ * @property {number} estimatedTimeSeconds - Estimated processing time
414
+ * @property {string} [downloadUrl] - Download URL when ready
415
+ * @property {number} expiresIn - URL expiration time in seconds
416
+ */
417
+ export class ExportResponseDTO {
418
+ constructor( data ) {
419
+ this.exportId = data.exportId;
420
+ this.status = data.status || 'processing';
421
+ this.format = data.format;
422
+ this.recordCount = data.recordCount;
423
+ this.estimatedFileSize = data.estimatedFileSize;
424
+ this.estimatedTimeSeconds = data.estimatedTimeSeconds;
425
+ if ( data.downloadUrl ) this.downloadUrl = data.downloadUrl;
426
+ this.expiresIn = data.expiresIn || 3600;
427
+ }
428
+ }
429
+
430
+ /**
431
+ * Error Response DTO
432
+ * @typedef {Object} ErrorResponseDTO
433
+ * @property {string} status - Status ("error")
434
+ * @property {string} message - Error message
435
+ * @property {string} code - Error code
436
+ * @property {*} [details] - Additional error details
437
+ * @property {string} timestamp - ISO timestamp
438
+ */
439
+ export class ErrorResponseDTO {
440
+ constructor( message, code = 'INTERNAL_ERROR', details = null ) {
441
+ this.status = 'error';
442
+ this.message = message;
443
+ this.code = code;
444
+ if ( details ) this.details = details;
445
+ this.timestamp = new Date().toISOString();
446
+ }
447
+ }
448
+
449
+ /**
450
+ * Pagination DTO
451
+ * @typedef {Object} PaginationDTO
452
+ * @property {number} total - Total records
453
+ * @property {number} limit - Records per page
454
+ * @property {number} offset - Current offset
455
+ * @property {boolean} hasMore - Has more records
456
+ * @property {number} [nextOffset] - Next offset value
457
+ */
458
+ export class PaginationDTO {
459
+ constructor( total, limit, offset ) {
460
+ this.total = total;
461
+ this.limit = limit;
462
+ this.offset = offset;
463
+ this.hasMore = offset + limit < total;
464
+ if ( this.hasMore ) this.nextOffset = offset + limit;
465
+ }
466
+ }
467
+
468
+ /**
469
+ * External Stream API Request DTO
470
+ * @typedef {Object} ExternalStreamAPIRequestDTO
471
+ * @property {string} prompt - The prompt/query for AI analysis (required)
472
+ * @property {string} [storeId] - Store identifier for filtering
473
+ * @property {string} [productModule] - Product module name
474
+ * @property {string} [country] - Country code or name
475
+ * @property {string} [region] - Region identifier
476
+ * @property {string} [fromDate] - Start date for analytics (YYYY-MM-DD)
477
+ * @property {string} [toDate] - End date for analytics (YYYY-MM-DD)
478
+ * @property {string} [clusters] - Cluster identifiers for grouping
479
+ */
480
+ export class ExternalStreamAPIRequestDTO {
481
+ constructor( data ) {
482
+ this.prompt = data.prompt;
483
+ this.storeId = data.storeId || null;
484
+ this.productModule = data.productModule || null;
485
+ this.country = data.country || null;
486
+ this.region = data.region || null;
487
+ this.fromDate = data.fromDate || null;
488
+ this.toDate = data.toDate || null;
489
+ this.clusters = data.clusters || null;
490
+ }
491
+
492
+ validate() {
493
+ if ( !this.prompt || this.prompt.trim() === '' ) {
494
+ throw new Error( 'Missing required parameter: prompt' );
495
+ }
496
+ return true;
497
+ }
498
+ }
499
+
500
+
501
+ const cohortContextNonNumericSchema = joi.object( {
502
+ contextName: joi.string().required(),
503
+ priority: joi.number().required(),
504
+ description: joi.string().required(),
505
+ } );
506
+
507
+
508
+ const cohortMetricSchema = joi.object( {
509
+ metricName: joi.string().required(),
510
+ metricDescription: joi.string().required(),
511
+ isNumeric: joi.boolean().optional().default( false ),
512
+
513
+ minValue: joi.when( 'isNumeric', {
514
+ is: true,
515
+ then: joi.number().optional().default( null ).allow( null ),
516
+ otherwise: joi.forbidden(),
517
+ } ),
518
+
519
+ maxValue: joi.when( 'isNumeric', {
520
+ is: true,
521
+ then: joi.number().optional().default( null ).allow( null ),
522
+ otherwise: joi.forbidden(),
523
+ } ),
524
+
525
+ contexts: joi.array().items( cohortContextNonNumericSchema ).optional(),
526
+ } );
527
+
528
+ export const createCohortSchema = joi.object( {
529
+ clientId: joi.string().required(),
530
+ cohortName: joi.string().required(),
531
+ cohortDescription: joi.string().required(),
532
+ metrics: joi.array().items( cohortMetricSchema ).optional(),
533
+ } );
534
+
535
+ export const createCohortValid = {
536
+ body: createCohortSchema,
537
+ };