tango-app-api-audit 3.6.50 → 3.6.52
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 +1 -1
- package/src/controllers/audit.controllers.js +3 -0
- package/src/controllers/eyeTestAudit.controllers.js +173 -1
- package/src/dtos/audit.dtos.js +2 -0
- package/src/dtos/eyeTestAudit.dtos.js +11 -0
- package/src/routes/audit.routes.js +3 -0
- package/src/routes/eyeTestAudit.routes.js +6 -2
package/package.json
CHANGED
|
@@ -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
|
+
|
|
@@ -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,174 @@ 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
|
+
'unique_id.keyword': inputData.id,
|
|
3191
|
+
},
|
|
3192
|
+
},
|
|
3193
|
+
],
|
|
3194
|
+
},
|
|
3195
|
+
},
|
|
3196
|
+
'size': 1,
|
|
3197
|
+
};
|
|
3198
|
+
|
|
3199
|
+
const result = await getOpenSearchData( 'audio_analytics_report_v2', query );
|
|
3200
|
+
if ( result?.body?.hits?.hits?.length === 0 ) {
|
|
3201
|
+
return res.sendError( 'No data found', 204 );
|
|
3202
|
+
}
|
|
3203
|
+
|
|
3204
|
+
const source = result?.body?.hits?.hits[0]?._source;
|
|
3205
|
+
|
|
3206
|
+
if ( !source ) {
|
|
3207
|
+
return res.sendError( 'No data found', 204 );
|
|
3208
|
+
}
|
|
3209
|
+
|
|
3210
|
+
// Helper function to format duration: convert seconds to mm:ss format
|
|
3211
|
+
const formatDuration = ( seconds ) => {
|
|
3212
|
+
if ( !seconds || seconds === 0 ) return '0:00';
|
|
3213
|
+
const minutes = Math.floor( seconds / 60 );
|
|
3214
|
+
const secs = Math.floor( seconds % 60 );
|
|
3215
|
+
return `${minutes}:${secs.toString().padStart( 2, '0' )}`;
|
|
3216
|
+
};
|
|
3217
|
+
|
|
3218
|
+
// Helper: parse "s3://bucket/path/to/file" into { Bucket, file_path }
|
|
3219
|
+
const parseS3Uri = ( uri ) => {
|
|
3220
|
+
if ( !uri ) return null;
|
|
3221
|
+
const withoutScheme = uri.replace( /^s3:\/\//, '' );
|
|
3222
|
+
const slashIdx = withoutScheme.indexOf( '/' );
|
|
3223
|
+
if ( slashIdx === -1 ) return null;
|
|
3224
|
+
return {
|
|
3225
|
+
Bucket: withoutScheme.slice( 0, slashIdx ),
|
|
3226
|
+
file_path: withoutScheme.slice( slashIdx + 1 ),
|
|
3227
|
+
};
|
|
3228
|
+
};
|
|
3229
|
+
|
|
3230
|
+
// Helper function to format transcript: fetch and clean transcript file
|
|
3231
|
+
const getFormattedTranscript = async ( transcriptPath ) => {
|
|
3232
|
+
try {
|
|
3233
|
+
if ( !transcriptPath ) return '';
|
|
3234
|
+
|
|
3235
|
+
const s3Parts = parseS3Uri( transcriptPath );
|
|
3236
|
+
logger.info( { s3Parts: s3Parts, function: 'getFormattedTranscript' } );
|
|
3237
|
+
if ( !s3Parts ) return '';
|
|
3238
|
+
|
|
3239
|
+
const transcriptSignedUrl = await signedUrl( { ...s3Parts, key: 'aws' } );
|
|
3240
|
+
if ( !transcriptSignedUrl ) return '';
|
|
3241
|
+
|
|
3242
|
+
const fetchRes = await fetch( transcriptSignedUrl );
|
|
3243
|
+
if ( !fetchRes.ok ) return '';
|
|
3244
|
+
let text = await fetchRes.text();
|
|
3245
|
+
|
|
3246
|
+
// Remove timestamps like [00:00:33.954 --> 00:00:36.154]
|
|
3247
|
+
text = text.replace( /\[\d{2}:\d{2}:\d{2}\.\d{3} --> \d{2}:\d{2}:\d{2}\.\d{3}\]\s*/g, '' );
|
|
3248
|
+
|
|
3249
|
+
// Remove [silence] markers
|
|
3250
|
+
text = text.replace( /\[silence\]\s*/g, '' );
|
|
3251
|
+
|
|
3252
|
+
// Replace multiple newlines with single space to create paragraphs
|
|
3253
|
+
text = text.replace( /\n+/g, ' ' );
|
|
3254
|
+
|
|
3255
|
+
// Clean up multiple spaces
|
|
3256
|
+
text = text.replace( /\s+/g, ' ' ).trim();
|
|
3257
|
+
|
|
3258
|
+
return text;
|
|
3259
|
+
} catch ( error ) {
|
|
3260
|
+
logger.error( { message: error, function: 'getFormattedTranscript' } );
|
|
3261
|
+
return '';
|
|
3262
|
+
}
|
|
3263
|
+
};
|
|
3264
|
+
|
|
3265
|
+
// Helper function to format metric names
|
|
3266
|
+
const formatMetricName = ( name ) => {
|
|
3267
|
+
const nameMap = {
|
|
3268
|
+
'Torchlight Exam': 'Torchlight Check',
|
|
3269
|
+
'JCC': 'JCC Check',
|
|
3270
|
+
'Distance Visual Acuity': 'Distance-VA-Check',
|
|
3271
|
+
'Duochrome': 'Duochrome Check',
|
|
3272
|
+
'Near Visual Acuity': 'Near Addition Check',
|
|
3273
|
+
};
|
|
3274
|
+
return nameMap[name] || name;
|
|
3275
|
+
};
|
|
3276
|
+
|
|
3277
|
+
// Format metrics results with sequential time allocation
|
|
3278
|
+
const metricsData = source?.cohort_analysis_results?.[0]?.metrics_results || [];
|
|
3279
|
+
|
|
3280
|
+
// Calculate sequential times for detected metrics
|
|
3281
|
+
let currentSeconds = 0;
|
|
3282
|
+
const totalDuration = Math.floor( source?.duration || 1200 );
|
|
3283
|
+
const detectedCount = metricsData.filter( ( m ) => m.contextsEvaluated?.[0]?.isDetected ).length || 1;
|
|
3284
|
+
const secondsPerMetric = Math.floor( totalDuration / Math.max( detectedCount, 1 ) );
|
|
3285
|
+
|
|
3286
|
+
const formattedMetrics = metricsData.map( ( metric, index ) => {
|
|
3287
|
+
const isDetected = metric.contextsEvaluated?.[0]?.isDetected;
|
|
3288
|
+
|
|
3289
|
+
let startTime = '';
|
|
3290
|
+
let endTime = '';
|
|
3291
|
+
|
|
3292
|
+
if ( isDetected ) {
|
|
3293
|
+
const startSeconds = currentSeconds;
|
|
3294
|
+
const endSeconds = Math.min( currentSeconds + secondsPerMetric, totalDuration );
|
|
3295
|
+
|
|
3296
|
+
startTime = formatDuration( startSeconds );
|
|
3297
|
+
endTime = formatDuration( endSeconds );
|
|
3298
|
+
|
|
3299
|
+
currentSeconds = endSeconds;
|
|
3300
|
+
}
|
|
3301
|
+
|
|
3302
|
+
return {
|
|
3303
|
+
metricId: metric.metricId,
|
|
3304
|
+
metricName: formatMetricName( metric.metricName ),
|
|
3305
|
+
metricType: metric.metricType,
|
|
3306
|
+
status: metric.contextsEvaluated?.[0]?.isDetected === false ? 'fail':'pass',
|
|
3307
|
+
contextsEvaluated: metric.contextsEvaluated?.[0]?.isDetected,
|
|
3308
|
+
startTime: startTime,
|
|
3309
|
+
endTime: endTime,
|
|
3310
|
+
evidence_quote: metric.contextsEvaluated?.[0]?.isDetected === false ? '' : metric.evidence_quote,
|
|
3311
|
+
};
|
|
3312
|
+
} ) || [];
|
|
3313
|
+
|
|
3314
|
+
// Fetch signed URL for audio
|
|
3315
|
+
const audioS3Parts = parseS3Uri( source?.s3_audio_path );
|
|
3316
|
+
const audioSignedUrl = audioS3Parts ? await signedUrl( { ...audioS3Parts, key: 'aws' } ) : null;
|
|
3317
|
+
// Fetch and format transcript
|
|
3318
|
+
const formattedTranscript = await getFormattedTranscript( source?.s3_transcript_path );
|
|
3319
|
+
|
|
3320
|
+
// Get executive summary
|
|
3321
|
+
// const executiveSummary = source?.cohort_analysis_results?.[0]?.executive_summary || {};
|
|
3322
|
+
|
|
3323
|
+
const response = {
|
|
3324
|
+
doc_id: source?.doc_id,
|
|
3325
|
+
// storeName: source?.storeName,
|
|
3326
|
+
// storeID: source?.storeID,
|
|
3327
|
+
// staffID: source?.staffID,
|
|
3328
|
+
// state: source?.state,
|
|
3329
|
+
// moduleType: source?.moduleType,
|
|
3330
|
+
// fileDate: source?.filedate,
|
|
3331
|
+
// processTimestamp: source?.processTimestamp,
|
|
3332
|
+
duration: formatDuration( source?.duration ),
|
|
3333
|
+
audio_url: audioSignedUrl,
|
|
3334
|
+
// s3_transcript_path: source?.s3_transcript_path,
|
|
3335
|
+
transcript_summary: formattedTranscript,
|
|
3336
|
+
// overall_score: executiveSummary?.overall_score,
|
|
3337
|
+
// conversation_summary: executiveSummary?.conversation_summary,
|
|
3338
|
+
// key_pointers: executiveSummary?.key_pointers,
|
|
3339
|
+
metrics_results: formattedMetrics,
|
|
3340
|
+
};
|
|
3341
|
+
|
|
3342
|
+
return res.sendSuccess( { result: response } );
|
|
3343
|
+
} catch ( error ) {
|
|
3344
|
+
const err = error.message || 'Internal Server Error';
|
|
3345
|
+
logger.error( { message: error, data: req.params, function: 'eyetestAudit-controller-getConversationDetails' } );
|
|
3346
|
+
return res.sendError( err, 500 );
|
|
3347
|
+
}
|
|
3348
|
+
}
|
package/src/dtos/audit.dtos.js
CHANGED
|
@@ -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 );
|