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.
- package/.eslintrc.cjs +41 -0
- package/API_EXAMPLES.md +310 -0
- package/COHORT_API.md +513 -0
- package/COHORT_API_EXAMPLES.sh +235 -0
- package/COHORT_API_IMPLEMENTATION.md +296 -0
- package/COHORT_API_QUICKSTART.md +387 -0
- package/index.js +6 -0
- package/package.json +36 -0
- package/src/controllers/audioAnalytics.controller.js +116 -0
- package/src/controllers/cohort.controller.js +357 -0
- package/src/controllers/cohortAnalytics.controller.js +46 -0
- package/src/controllers/conversationAnalytics.controller.js +92 -0
- package/src/dtos/audioAnalytics.dtos.js +537 -0
- package/src/middlewares/validation.middleware.js +624 -0
- package/src/routes/audioAnalytics.routes.js +18 -0
- package/src/services/cohort.service.js +332 -0
- package/src/validations/cohort.validation.js +113 -0
|
@@ -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
|
+
};
|