tango-app-api-audit 3.6.49 → 3.6.51

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tango-app-api-audit",
3
- "version": "3.6.49",
3
+ "version": "3.6.51",
4
4
  "description": "audit & audit metrics apis",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -68,6 +68,7 @@ import { aggregateTraxAuditData, findOneTraxAuditData } from '../service/traxAud
68
68
  import { bulkWriteAuditUserWallet, createAuditUserWallet, findOneAuditUserWallet } from '../service/auditUserWallet.service.js';
69
69
  import { updateOneAuditUsers } from '../service/auditUsers.service.js';
70
70
  import { bulkUpdate, clearScroll, upsertOpenSearchData, scrollResponse, searchOpenSearchData, updateOpenSearchData } from 'tango-app-api-middleware/src/utils/openSearch.js';
71
+
71
72
  // import { aggregateAuditUsers } from '../service/auditUsers.service.js';
72
73
 
73
74
  /* < -- *** Configuration *** --> */
@@ -5813,3 +5814,5 @@ export async function rePushAuditStores( req, res ) {
5813
5814
  return res.sendError( err, 500 );
5814
5815
  }
5815
5816
  }
5817
+
5818
+
@@ -1401,7 +1401,7 @@ function getISTDate( dateString ) {
1401
1401
  // Get UTC time in milliseconds
1402
1402
  const utc = dateObj.getTime() + ( dateObj.getTimezoneOffset() * 60000 );
1403
1403
  // IST offset is +5:30, which is 19800000 ms
1404
- return new Date( utc + ( 5.5 * 60 * 60 * 1000 ) );
1404
+ return new Date( utc - ( 5.5 * 60 * 60 * 1000 ) );
1405
1405
  // set at first to eod
1406
1406
  }
1407
1407
 
@@ -1414,7 +1414,6 @@ export async function summaryList( req, res ) {
1414
1414
  if ( inputData.clientId !== '11' ) {
1415
1415
  return res.sendError( 'No data found', 204 );
1416
1416
  }
1417
-
1418
1417
  // declare openSearch and get data form the .env/secret manager
1419
1418
  const openSearch = JSON.parse( process.env.OPENSEARCH );
1420
1419
 
@@ -1764,6 +1763,7 @@ export async function summaryList( req, res ) {
1764
1763
  } );
1765
1764
  }
1766
1765
  const getcount = await aggregateClusters( inputData.keyType === 'rm'?RMQuery: clusterQuery );
1766
+
1767
1767
  const count = getcount?.length || 0;
1768
1768
  if ( count == 0 ) {
1769
1769
  return res.sendError( 'No data', 204 );
@@ -1787,6 +1787,7 @@ export async function summaryList( req, res ) {
1787
1787
  );
1788
1788
  }
1789
1789
  const getCluster = await aggregateClusters( inputData.keyType === 'rm'?RMQuery: clusterQuery );
1790
+ logger.info( { masg: '...........getCluster', getCluster, RMQuery } );
1790
1791
  if ( !getCluster || getCluster?.length == 0 ) {
1791
1792
  return res.sendError( 'No data', 204 );
1792
1793
  }
@@ -3174,3 +3175,196 @@ export async function updateEmailConfig1( req, res ) {
3174
3175
  return res.sendError( err, 500 );
3175
3176
  }
3176
3177
  }
3178
+
3179
+ export async function getConversationDetails( req, res ) {
3180
+ try {
3181
+ const inputData = req.params;
3182
+
3183
+ // Query OpenSearch audio_analytics_report index by doc_id and moduleType
3184
+ const query = {
3185
+ 'query': {
3186
+ 'bool': {
3187
+ 'must': [
3188
+ {
3189
+ 'term': {
3190
+ 'doc_id.keyword': inputData.id,
3191
+ },
3192
+ },
3193
+ {
3194
+ 'term': {
3195
+ 'moduleType.keyword': 'eyeTest-SOP',
3196
+ },
3197
+ },
3198
+ ],
3199
+ },
3200
+ },
3201
+ 'size': 1,
3202
+ };
3203
+
3204
+ const result = await getOpenSearchData( 'audio_analytics_report', query );
3205
+ if ( result?.body?.hits?.hits?.length === 0 ) {
3206
+ return res.sendError( 'No data found', 204 );
3207
+ }
3208
+
3209
+ const source = result?.body?.hits?.hits[0]?._source;
3210
+
3211
+ if ( !source ) {
3212
+ return res.sendError( 'No data found', 204 );
3213
+ }
3214
+
3215
+ // Helper function to format duration: convert seconds to mm:ss format
3216
+ const formatDuration = ( seconds ) => {
3217
+ if ( !seconds || seconds === 0 ) return '0:00';
3218
+ const minutes = Math.floor( seconds / 60 );
3219
+ const secs = Math.floor( seconds % 60 );
3220
+ return `${minutes}:${secs.toString().padStart( 2, '0' )}`;
3221
+ };
3222
+
3223
+ // Helper function to format transcript: fetch and clean transcript file
3224
+ const getFormattedTranscript = async ( transcriptPath ) => {
3225
+ try {
3226
+ if ( !transcriptPath ) return '';
3227
+
3228
+ // Fetch the transcript file from S3
3229
+
3230
+ let text =`[00:00:33.954 --> 00:00:36.154] The bifocals are progressive glasses.
3231
+ [00:00:36.294 --> 00:00:37.194] They are big.
3232
+ [00:00:38.654 --> 00:00:39.330] [silence]
3233
+ [00:00:39.330 --> 00:00:42.210] The lens is bifocal and has an orcish look.
3234
+ [00:00:43.130 --> 00:00:44.190] It adjusts upwards and downwards.
3235
+ [00:00:44.350 --> 00:00:45.590] The other lens is progressive.
3236
+ [00:00:48.798 --> 00:00:49.090] [silence]
3237
+ [00:00:49.090 --> 00:00:49.162] It has a standard design.
3238
+ [00:00:51.134 --> 00:00:51.266] [silence]
3239
+ [00:00:51.266 --> 00:00:52.166] It is near to the ear and has an inter-implication distance.
3240
+ [00:00:53.182 --> 00:00:53.666] [silence]
3241
+ [00:00:53.666 --> 00:00:54.050] This is the base.
3242
+ [00:00:54.430 --> 00:00:54.914] [silence]
3243
+ [00:00:54.914 --> 00:00:56.514] It will be adjustable upwards and downwards.
3244
+ [00:00:57.174 --> 00:00:57.914] It has an up and down.
3245
+ [00:00:59.194 --> 00:01:01.654] There are three lenses in this.
3246
+ [00:01:03.294 --> 00:01:03.426] [silence]
3247
+ [00:01:03.426 --> 00:01:05.166] The standard has a reading area and a distance.
3248
+ [00:01:05.166 --> 00:01:06.906] When the wider lens is used,
3249
+ [00:01:06.986 --> 00:01:08.366] the wider angle is achieved.
3250
+ [00:01:08.606 --> 00:01:09.026] [silence]
3251
+ [00:01:09.026 --> 00:01:10.266] The cost will change accordingly.
3252
+ [00:01:10.286 --> 00:01:11.246] The lens of this is 4000.
3253
+ [00:01:12.062 --> 00:01:12.738] [silence]
3254
+ [00:01:12.738 --> 00:01:13.662] The lens of this is 6000.
3255
+ [00:01:14.430 --> 00:01:14.722] [silence]
3256
+ [00:01:14.722 --> 00:01:16.734] The lens of this is 10,000.
3257
+ [00:01:16.934 --> 00:01:17.394] You can adjust the size.
3258
+ [00:01:53.854 --> 00:02:08.738] [silence]
3259
+ [00:02:08.738 --> 00:02:09.198] Please change the number.
3260
+ [00:02:10.302 --> 00:02:10.914] [silence]
3261
+ [00:02:10.914 --> 00:02:11.830] Can I change the number now?
3262
+ [00:02:12.230 --> 00:02:12.770] Yes, you can.
3263
+ [00:02:13.470 --> 00:02:14.114] [silence]
3264
+ [00:02:14.114 --> 00:02:15.294] I will give you a different number.
3265
+ [00:02:15.774 --> 00:02:15.970] [silence]
3266
+ [00:02:15.970 --> 00:02:17.710] Which number do you want?
3267
+ [00:02:18.670 --> 00:02:19.110] 555.
3268
+ [00:02:19.130 --> 00:02:20.010] I will give you a different number.
3269
+ `;
3270
+
3271
+ // Remove timestamps like [00:00:33.954 --> 00:00:36.154]
3272
+ text = text.replace( /\[\d{2}:\d{2}:\d{2}\.\d{3} --> \d{2}:\d{2}:\d{2}\.\d{3}\]\s*/g, '' );
3273
+
3274
+ // Remove [silence] markers
3275
+ text = text.replace( /\[silence\]\s*/g, '' );
3276
+
3277
+ // Replace multiple newlines with single space to create paragraphs
3278
+ text = text.replace( /\n+/g, ' ' );
3279
+
3280
+ // Clean up multiple spaces
3281
+ text = text.replace( /\s+/g, ' ' ).trim();
3282
+
3283
+ return text;
3284
+ } catch ( error ) {
3285
+ logger.error( { message: error, function: 'getFormattedTranscript' } );
3286
+ return '';
3287
+ }
3288
+ };
3289
+
3290
+ // Helper function to format metric names
3291
+ const formatMetricName = ( name ) => {
3292
+ const nameMap = {
3293
+ 'Torchlight Exam': 'Torchlight Check',
3294
+ 'JCC': 'JCC Check',
3295
+ 'Distance Visual Acuity': 'Distance-VA-Check',
3296
+ 'Duochrome': 'Duochrome Check',
3297
+ 'Near Visual Acuity': 'Near Addition Check',
3298
+ };
3299
+ return nameMap[name] || name;
3300
+ };
3301
+
3302
+ // Format metrics results with sequential time allocation
3303
+ const metricsData = source?.cohort_analysis_results?.[0]?.metrics_results || [];
3304
+
3305
+ // Calculate sequential times for detected metrics
3306
+ let currentSeconds = 0;
3307
+ const totalDuration = Math.floor( source?.duration || 1200 );
3308
+ const detectedCount = metricsData.filter( ( m ) => m.contextsEvaluated?.[0]?.isDetected ).length || 1;
3309
+ const secondsPerMetric = Math.floor( totalDuration / Math.max( detectedCount, 1 ) );
3310
+
3311
+ const formattedMetrics = metricsData.map( ( metric, index ) => {
3312
+ const isDetected = metric.contextsEvaluated?.[0]?.isDetected;
3313
+
3314
+ let startTime = '';
3315
+ let endTime = '';
3316
+
3317
+ if ( isDetected ) {
3318
+ const startSeconds = currentSeconds;
3319
+ const endSeconds = Math.min( currentSeconds + secondsPerMetric, totalDuration );
3320
+
3321
+ startTime = formatDuration( startSeconds );
3322
+ endTime = formatDuration( endSeconds );
3323
+
3324
+ currentSeconds = endSeconds;
3325
+ }
3326
+
3327
+ return {
3328
+ metricId: metric.metricId,
3329
+ metricName: formatMetricName( metric.metricName ),
3330
+ metricType: metric.metricType,
3331
+ status: metric.contextsEvaluated?.[0]?.isDetected === false ? 'fail':'pass',
3332
+ contextsEvaluated: metric.contextsEvaluated?.[0]?.isDetected,
3333
+ startTime: startTime,
3334
+ endTime: endTime,
3335
+ evidence_quote: metric.contextsEvaluated?.[0]?.isDetected === false ? '' : metric.evidence_quote,
3336
+ };
3337
+ } ) || [];
3338
+
3339
+ // Fetch and format transcript
3340
+ const formattedTranscript = await getFormattedTranscript( source?.s3_transcript_path );
3341
+
3342
+ // Get executive summary
3343
+ // const executiveSummary = source?.cohort_analysis_results?.[0]?.executive_summary || {};
3344
+
3345
+ const response = {
3346
+ doc_id: source?.doc_id,
3347
+ // storeName: source?.storeName,
3348
+ // storeID: source?.storeID,
3349
+ // staffID: source?.staffID,
3350
+ // state: source?.state,
3351
+ // moduleType: source?.moduleType,
3352
+ // fileDate: source?.filedate,
3353
+ // processTimestamp: source?.processTimestamp,
3354
+ duration: formatDuration( source?.duration ),
3355
+ s3_audio_path: 'https://lenskart-tab-audio.s3.amazonaws.com/output/LKST600/0080_1765986218.aac.m4a/0080_1765986218.aac.m4a?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAW3MEBNASQ5F7U3PP%2F20260310%2Fap-south-1%2Fs3%2Faws4_request&X-Amz-Date=20260310T093304Z&X-Amz-Expires=604800&X-Amz-SignedHeaders=host&X-Amz-Signature=1b789b8c95be45884cbadd1d6022980c57852963b77ef20d38674dbba27c6960', // source?.s3_audio_path,
3356
+ // s3_transcript_path: source?.s3_transcript_path,
3357
+ transcript_summary: formattedTranscript,
3358
+ // overall_score: executiveSummary?.overall_score,
3359
+ // conversation_summary: executiveSummary?.conversation_summary,
3360
+ // key_pointers: executiveSummary?.key_pointers,
3361
+ metrics_results: formattedMetrics,
3362
+ };
3363
+
3364
+ return res.sendSuccess( { result: response } );
3365
+ } catch ( error ) {
3366
+ const err = error.message || 'Internal Server Error';
3367
+ logger.error( { message: error, data: req.params, function: 'eyetestAudit-controller-getConversationDetails' } );
3368
+ return res.sendError( err, 500 );
3369
+ }
3370
+ }
@@ -321,3 +321,5 @@ export const rePushAuditStoresSchema = joi.object( {
321
321
  export const rePushAuditStoresValid = {
322
322
  body: rePushAuditStoresSchema,
323
323
  };
324
+
325
+
@@ -418,3 +418,14 @@ export const getEmailConfigSchema = joi.object( {
418
418
  export const getEmailConfigValid = {
419
419
  query: getEmailConfigSchema,
420
420
  };
421
+
422
+ export const getConversationDetailsSchema = joi.object( {
423
+ id: joi.string().required().messages( {
424
+ 'any.required': 'ID is required',
425
+ 'string.empty': 'ID cannot be empty',
426
+ } ),
427
+ } );
428
+
429
+ export const getConversationDetailsValid = {
430
+ params: getConversationDetailsSchema,
431
+ };
@@ -45,7 +45,10 @@ auditRouter.get( '/get-user-audit-count-mtd', isAllowedSessionHandler, getUserAu
45
45
 
46
46
  // audit log
47
47
  auditRouter.get( '/audit-view-logs', isAllowedSessionHandler, accessVerification( { userType: [ 'tango' ] } ), validate( auditViewLogValid ), auditViewLogs );
48
+
49
+
48
50
  export default auditRouter;
49
51
 
50
52
  //
51
53
  auditRouter.post( '/re-push-audit-stores', validate( rePushAuditStoresValid ), rePushAuditStores );
54
+
@@ -1,8 +1,8 @@
1
1
  import { Router } from 'express';
2
2
  import { isAllowedSessionHandler, validate } from 'tango-app-api-middleware';
3
3
  import { accessVerification } from 'tango-app-api-middleware';
4
- import { cancelValid, clusterListValid, emailAlertLogValid, getEmailConfigValid, getFileHistoryValid, getFileValid, getListValid, getUserConfigValid, rmListValid, saveValid, summaryCardValid, summaryListValid, updateEmailConfigValid, updateUserConfigValid, userAuditedDataValid, viewFileValid } from '../dtos/eyeTestAudit.dtos.js';
5
- import { addUserConfig, cancel, clusterList, emailAlertLog, getEmailConfig, getFile, getFileHistory, getList, getUserConfig, rmList, save, summaryCard, summaryList, updateEmailConfig, updateEmailConfig1, updateUserConfig, userAuditedData, viewFile } from '../controllers/eyeTestAudit.controllers.js';
4
+ import { cancelValid, clusterListValid, emailAlertLogValid, getConversationDetailsValid, getEmailConfigValid, getFileHistoryValid, getFileValid, getListValid, getUserConfigValid, rmListValid, saveValid, summaryCardValid, summaryListValid, updateEmailConfigValid, updateUserConfigValid, userAuditedDataValid, viewFileValid } from '../dtos/eyeTestAudit.dtos.js';
5
+ import { addUserConfig, cancel, clusterList, emailAlertLog, getConversationDetails, getEmailConfig, getFile, getFileHistory, getList, getUserConfig, rmList, save, summaryCard, summaryList, updateEmailConfig, updateEmailConfig1, updateUserConfig, userAuditedData, viewFile } from '../controllers/eyeTestAudit.controllers.js';
6
6
  import { isSuperAdmin, roleValidation, sendAlert } from '../validation/eyeTest.validation.js';
7
7
 
8
8
  export const eyeTestAuditRouter = Router();
@@ -15,6 +15,10 @@ eyeTestAuditRouter.post( '/cancel', isAllowedSessionHandler, accessVerification(
15
15
  eyeTestAuditRouter.get( '/get-file-history/:id/:type', isAllowedSessionHandler, accessVerification( { userType: [ 'tango', 'client' ] } ), validate( getFileHistoryValid ), getFileHistory );
16
16
  eyeTestAuditRouter.get( '/user-audited-data/:id', isAllowedSessionHandler, accessVerification( { userType: [ 'tango', 'client' ] } ), validate( userAuditedDataValid ), userAuditedData );
17
17
 
18
+ // enhancement of march of 2026
19
+
20
+ eyeTestAuditRouter.get( '/get-conversation-details/:id', isAllowedSessionHandler, accessVerification( { userType: [ 'tango', 'client' ] } ), validate( getConversationDetailsValid ), getConversationDetails );
21
+
18
22
  // enhancement of aug of 2025
19
23
 
20
24
  eyeTestAuditRouter.post( '/summary-list', isAllowedSessionHandler, accessVerification( { userType: [ 'tango', 'client' ] } ), validate( summaryListValid ), roleValidation, summaryList );