tango-app-api-trax 3.9.67 → 3.9.68

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-trax",
3
- "version": "3.9.67",
3
+ "version": "3.9.68",
4
4
  "description": "Trax",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -451,7 +451,7 @@ export async function redoChecklist( req, res ) {
451
451
 
452
452
  let findQuestion = question[sectionIndex].questions.findIndex( ( ele ) => ele.qno == req.body.payload.qno );
453
453
 
454
- let data = { ...question[sectionIndex].questions[findQuestion], redo: true, ...( !req.body.payload?.answerIndex && { redoComment: req.body.payload?.checklistDescription || '' } ) };
454
+ let data = { ...question[sectionIndex].questions[findQuestion], redo: true, redoCount: ( question[sectionIndex].questions[findQuestion]?.redoCount || 0 ) + 1, ...( !req.body.payload?.answerIndex && { redoComment: req.body.payload?.checklistDescription || '' } ) };
455
455
  let answerIndex = req.body.payload?.answerIndex;
456
456
  let isSingleAnswerRedo = answerIndex != null;
457
457
 
@@ -575,7 +575,7 @@ export async function redoChecklist( req, res ) {
575
575
  type: checklistDetails.checkListType,
576
576
  userAnswer: userAnswer,
577
577
  initiatedBy: req.user.userName,
578
- initiatedTime: dayjs.utc( ).format(),
578
+ initiatedTime: dayjs().format(),
579
579
  answerType: question[sectionIndex].questions[findQuestion].answerType,
580
580
  submitedBy: checklistDetails.userName,
581
581
  submitTime: dayjs.utc( checklistDetails.submitTime ).format(),
@@ -667,7 +667,7 @@ export async function redomultiChecklist( req, res ) {
667
667
 
668
668
  let findQuestion = question[sectionIndex].questions.findIndex( ( ele ) => ele.qno == req.body.payload.qno );
669
669
 
670
- let data = { ...question[sectionIndex].questions[findQuestion], redo: true, redoComment: req.body.payload?.showcomment ? req.body.payload?.checklistDescription : findcomment?.Comments };
670
+ let data = { ...question[sectionIndex].questions[findQuestion], redo: true, redoCount: ( question[sectionIndex].questions[findQuestion]?.redoCount || 0 ) + 1, redoComment: req.body.payload?.showcomment ? req.body.payload?.checklistDescription : findcomment?.Comments };
671
671
  // if ( checklistDetails.client_id == '458' ) {
672
672
  data.answers.forEach( ( item ) => {
673
673
  if ( item.showLinked ) {
@@ -2669,7 +2669,12 @@ function QuestionFlag( req, res ) {
2669
2669
  let answervalidate = question.userAnswer.filter( ( answer ) => {
2670
2670
  return answer.sopFlag == true;
2671
2671
  } );
2672
- flagCount = flagCount + answervalidate.length;
2672
+ let answerCount = 0;
2673
+ if ( answervalidate?.length ) {
2674
+ answerCount = 1;
2675
+ }
2676
+ // flagCount = flagCount + answervalidate.length;
2677
+ flagCount = flagCount + answerCount;
2673
2678
  }
2674
2679
  } );
2675
2680
  } );
@@ -444,13 +444,47 @@ export const flagCardsV1 = async ( req, res ) => {
444
444
  },
445
445
  ];
446
446
 
447
- // These three lookups are independent of one another, so run them concurrently
448
- // instead of sequentially (Mongo aggregation + config find + detection Lambda).
447
+ // Fast path: return only the Mongo-derived flag counts. The detection Lambda
448
+ // (slow) is fetched separately via flagCardsDetectionV1 and merged into these
449
+ // cards on the client once it resolves.
450
+ const getOverallChecklistData = await processedchecklistService.aggregate( findQuery );
451
+
452
+ if ( !getOverallChecklistData.length ) {
453
+ return res.sendSuccess( { flagCards } );
454
+ }
455
+
456
+ const [ data ] = getOverallChecklistData;
457
+ if ( data.questionFlag ) flagCards.questionFlag.count = data.questionFlag;
458
+ if ( data.delayInSubmission ) flagCards.delayInSubmission.count = data.delayInSubmission;
459
+ flagCards.totalFlag = flagCards.questionFlag.count + flagCards.delayInSubmission.count;
460
+
461
+ return res.sendSuccess( { flagCards } );
462
+ } catch ( error ) {
463
+ console.error( 'Error in flagCardsV1:', error );
464
+ logger.error( { error, function: 'flagCardsV1' } );
465
+ return res.sendError( error, 500 );
466
+ }
467
+ };
468
+
469
+ // Detection-only companion for flagCardsV1. Runs the published-checklist lookup
470
+ // + the detection Lambda (the slow part) and returns just the detection
471
+ // contribution, so the UI can merge it into the cards returned by flagCardsV1:
472
+ // flagCards.detectionFlag = res.flagCards.detectionFlag;
473
+ // flagCards.runAIFlag = res.flagCards.runAIFlag; // when present
474
+ // flagCards.totalFlag += res.flagCards.detectionTotalFlag;
475
+ export const flagCardsDetectionV1 = async ( req, res ) => {
476
+ try {
477
+ const { clientId, storeId, fromDate, toDate } = req.body;
478
+
479
+ const flagCards = {
480
+ detectionFlag: { count: 0 },
481
+ detectionTotalFlag: 0,
482
+ };
483
+
449
484
  const detectionPayload = { fromDate, toDate, storeId, clientId };
450
485
  const lambdaURL = 'https://f65azvtljclaxp6l7rnx65cdmm0lcgvp.lambda-url.ap-south-1.on.aws/';
451
486
 
452
- const [ getOverallChecklistData, publishedAiChecklists, resultData ] = await Promise.all( [
453
- processedchecklistService.aggregate( findQuery ),
487
+ const [ publishedAiChecklists, resultData ] = await Promise.all( [
454
488
  checklistconfigService.find(
455
489
  {
456
490
  client_id: clientId,
@@ -462,15 +496,6 @@ export const flagCardsV1 = async ( req, res ) => {
462
496
  LamdaServiceCall( lambdaURL, detectionPayload ),
463
497
  ] );
464
498
 
465
- if ( !getOverallChecklistData.length ) {
466
- return res.sendSuccess( { flagCards } );
467
- }
468
-
469
- const [ data ] = getOverallChecklistData;
470
- if ( data.questionFlag ) flagCards.questionFlag.count = data.questionFlag;
471
- if ( data.delayInSubmission ) flagCards.delayInSubmission.count = data.delayInSubmission;
472
- flagCards.totalFlag = flagCards.questionFlag.count + flagCards.delayInSubmission.count;
473
-
474
499
  const published = publishedAiChecklists.map( ( val ) => val?.checkListType );
475
500
  if ( resultData && resultData.status_code === '200' ) {
476
501
  published.forEach( ( item ) => {
@@ -478,18 +503,18 @@ export const flagCardsV1 = async ( req, res ) => {
478
503
  flagCards.detectionFlag.count += resultData[item];
479
504
  }
480
505
  } );
481
- flagCards.totalFlag += flagCards.detectionFlag.count;
506
+ flagCards.detectionTotalFlag += flagCards.detectionFlag.count;
482
507
  if ( resultData?.runAI ) {
483
508
  resultData.runAI.flagCount = resultData.runaiflags;
484
509
  flagCards.runAIFlag = resultData?.runAI;
485
- flagCards.totalFlag += resultData.runaiflags;
510
+ flagCards.detectionTotalFlag += resultData.runaiflags;
486
511
  }
487
512
  }
488
513
 
489
514
  return res.sendSuccess( { flagCards } );
490
515
  } catch ( error ) {
491
- console.error( 'Error in flagCardsV1:', error );
492
- logger.error( { error, function: 'flagCardsV1' } );
516
+ console.error( 'Error in flagCardsDetectionV1:', error );
517
+ logger.error( { error, function: 'flagCardsDetectionV1' } );
493
518
  return res.sendError( error, 500 );
494
519
  }
495
520
  };
@@ -1996,6 +2021,151 @@ export const flagComparisonCardsV2 = async ( req, res ) => {
1996
2021
  return { comparisonData, ComparisonFlag };
1997
2022
  };
1998
2023
 
2024
+ // Fast path: only the Mongo aggregations + client flag. The detection Lambda
2025
+ // (slow) is fetched separately via flagComparisonCardsDetectionV2, which
2026
+ // returns the total/detection/runAI comparisons for the UI to overwrite.
2027
+ const [ rangeOneData, rangeTwoData, traxMonthlyComparisonData ] = await Promise.all( [
2028
+ processedchecklistService.aggregate( createFindQuery( rangeOneFromDate, rangeOneToDate ) ),
2029
+ processedchecklistService.aggregate( createFindQuery( rangeTwoFromDate, rangeTwoToDate ) ),
2030
+ clientService.findOne( { clientId: requestData.clientId }, { traxMonthlyComparison: 1 } ),
2031
+ ] );
2032
+
2033
+ // console.log( 'rangeOneFromDate =>', rangeOneFromDate );
2034
+ // console.log( 'rangeOneToDate =>', rangeOneToDate );
2035
+ // console.log( 'rangeOneData =>', rangeOneData );
2036
+
2037
+ // console.log( 'rangeTwoFromDate =>', rangeTwoFromDate );
2038
+ // console.log( 'rangeTwoToDate =>', rangeTwoToDate );
2039
+ // console.log( 'rangeTwoData =>', rangeTwoData );
2040
+
2041
+ const flagComparisonCards = {
2042
+ totalComparisonFlag: { comparisonData: 0, ComparisonFlag: false },
2043
+ questionComparisonFlag: { comparisonData: 0, ComparisonFlag: false },
2044
+ delayInSubmissionComparisonFlag: { comparisonData: 0, ComparisonFlag: false },
2045
+ detectionComparisonFlag: { comparisonData: 0, ComparisonFlag: false },
2046
+ runAIComparisonFlag: { comparisonData: 0, ComparisonFlag: false },
2047
+ };
2048
+
2049
+ if ( rangeOneData.length && rangeTwoData.length ) {
2050
+ // Mongo-only total (question + delay). flagComparisonCardsDetectionV2 returns
2051
+ // the detection-inclusive total plus the detection/runAI comparisons, which
2052
+ // the UI overwrites onto these cards once the Lambda resolves.
2053
+ flagComparisonCards.totalComparisonFlag = calculateComparison( rangeOneData[0].totalFlag, rangeTwoData[0].totalFlag );
2054
+ flagComparisonCards.questionComparisonFlag = calculateComparison( rangeOneData[0].questionFlag, rangeTwoData[0].questionFlag );
2055
+ flagComparisonCards.delayInSubmissionComparisonFlag = calculateComparison( rangeOneData[0].delayInSubmission, rangeTwoData[0].delayInSubmission );
2056
+ }
2057
+ flagComparisonCards.traxMonthlyComparison = false;
2058
+ if ( traxMonthlyComparisonData && traxMonthlyComparisonData.traxMonthlyComparison ) {
2059
+ flagComparisonCards.traxMonthlyComparison = traxMonthlyComparisonData.traxMonthlyComparison;
2060
+ }
2061
+ return res.sendSuccess( { flagComparisonCards } );
2062
+ } catch ( error ) {
2063
+ console.log( 'error =>', error );
2064
+ logger.error( { error, function: 'flagComparisonCardsV2' } );
2065
+ return res.sendError( error, 500 );
2066
+ }
2067
+ };
2068
+
2069
+ // Detection-only companion for flagComparisonCardsV2. Re-runs the (cheap) range
2070
+ // aggregations alongside the two detection Lambda calls, and returns only the
2071
+ // comparisons that depend on detection data. The UI overwrites these three keys
2072
+ // onto the cards from flagComparisonCardsV2 once this resolves:
2073
+ // totalComparisonFlag, detectionComparisonFlag, runAIComparisonFlag.
2074
+ // NOTE: the date-range / createFindQuery logic below mirrors flagComparisonCardsV2 —
2075
+ // keep the two in sync if that logic changes.
2076
+ export const flagComparisonCardsDetectionV2 = async ( req, res ) => {
2077
+ try {
2078
+ let requestData = req.body;
2079
+ let toDate = new Date( requestData.toDate );
2080
+ let userTimezoneOffset = toDate.getTimezoneOffset() * 60000;
2081
+ toDate = new Date( toDate.getTime() - userTimezoneOffset );
2082
+ toDate.setUTCHours( 23, 59, 59, 59 );
2083
+
2084
+ let rangeOneFromDate;
2085
+ let rangeOneToDate;
2086
+ let rangeTwoFromDate;
2087
+ let rangeTwoToDate;
2088
+ if ( requestData.dateType == 'weekly' ) {
2089
+ rangeOneFromDate = new Date( requestData.toDate );
2090
+ rangeOneFromDate.setDate( rangeOneFromDate.getDate() - 6 );
2091
+ rangeOneToDate = new Date( requestData.toDate );
2092
+ rangeOneToDate = new Date( rangeOneToDate.getTime() - userTimezoneOffset );
2093
+ rangeOneToDate.setUTCHours( 23, 59, 59, 59 );
2094
+ rangeTwoToDate = new Date( requestData.toDate );
2095
+ rangeTwoToDate.setDate( rangeTwoToDate.getDate() - 7 );
2096
+ rangeTwoFromDate = new Date( rangeTwoToDate );
2097
+ rangeTwoFromDate.setDate( rangeTwoFromDate.getDate() - 6 );
2098
+ rangeTwoToDate = new Date( rangeTwoToDate.getTime() - userTimezoneOffset );
2099
+ rangeTwoToDate.setUTCHours( 23, 59, 59, 59 );
2100
+ } else if ( requestData.dateType == 'monthly' ) {
2101
+ rangeOneFromDate = new Date( requestData.toDate );
2102
+ rangeOneFromDate.setDate( rangeOneFromDate.getDate() - 29 );
2103
+ rangeOneToDate = new Date( requestData.toDate );
2104
+ rangeOneToDate = new Date( rangeOneToDate.getTime() - userTimezoneOffset );
2105
+ rangeOneToDate.setUTCHours( 23, 59, 59, 59 );
2106
+ rangeTwoToDate = new Date( requestData.toDate );
2107
+ rangeTwoToDate.setDate( rangeTwoToDate.getDate() - 30 );
2108
+ rangeTwoFromDate = new Date( rangeTwoToDate );
2109
+ rangeTwoFromDate.setDate( rangeTwoFromDate.getDate() - 29 );
2110
+ rangeTwoToDate = new Date( rangeTwoToDate.getTime() - userTimezoneOffset );
2111
+ rangeTwoToDate.setUTCHours( 23, 59, 59, 59 );
2112
+ } else {
2113
+ rangeOneFromDate = new Date( requestData.toDate );
2114
+ rangeOneFromDate.setDate( rangeOneFromDate.getDate() - 0 );
2115
+ rangeOneToDate = new Date( requestData.toDate );
2116
+ rangeOneToDate = new Date( rangeOneToDate.getTime() - userTimezoneOffset );
2117
+ rangeOneToDate.setUTCHours( 23, 59, 59, 59 );
2118
+ rangeTwoToDate = new Date( requestData.toDate );
2119
+ rangeTwoToDate.setDate( rangeTwoToDate.getDate() - 1 );
2120
+ rangeTwoFromDate = new Date( rangeTwoToDate );
2121
+ rangeTwoFromDate.setDate( rangeTwoFromDate.getDate() - 0 );
2122
+ rangeTwoToDate = new Date( rangeTwoToDate.getTime() - userTimezoneOffset );
2123
+ rangeTwoToDate.setUTCHours( 23, 59, 59, 59 );
2124
+ }
2125
+
2126
+ const createFindQuery = ( fromDate, toDate ) => [
2127
+ { $match: { client_id: requestData.clientId,
2128
+ date_iso: { $gte: fromDate, $lte: toDate },
2129
+ $or: requestData.clientId === '11' ?
2130
+ [
2131
+ { store_id: { $in: requestData.storeId } },
2132
+ { store_id: { $eq: '' }, userEmail: { $in: requestData.userEmailes } },
2133
+ ] :
2134
+ [
2135
+ { store_id: { $in: requestData.storeId } },
2136
+ { store_id: { $eq: '' }, userEmail: { $in: requestData.userEmailes } },
2137
+ { aiStoreList: { $in: requestData.storeId } },
2138
+ ],
2139
+ } },
2140
+ {
2141
+ $project: {
2142
+ timeFlag: 1,
2143
+ questionFlag: 1,
2144
+ },
2145
+ },
2146
+ {
2147
+ $group: {
2148
+ _id: '',
2149
+ totalFlag: { $sum: { $add: [ '$questionFlag', '$timeFlag' ] } },
2150
+ questionFlag: { $sum: '$questionFlag' },
2151
+ delayInSubmission: { $sum: '$timeFlag' },
2152
+ },
2153
+ },
2154
+ ];
2155
+
2156
+ const calculateComparison = ( rangeOneVal, rangeTwoVal ) => {
2157
+ if ( rangeOneVal === 0 ) {
2158
+ return { comparisonData: 0, ComparisonFlag: false };
2159
+ }
2160
+
2161
+ let comparisonData = Math.abs( ( ( rangeOneVal - rangeTwoVal ) / rangeTwoVal ) * 100 );
2162
+ if ( comparisonData === Infinity ) comparisonData = 0;
2163
+ comparisonData = comparisonData % 1 === 0 ? comparisonData.toFixed( 0 ) : comparisonData.toFixed( 1 );
2164
+ comparisonData = parseInt( comparisonData );
2165
+ const ComparisonFlag = rangeOneVal > rangeTwoVal;
2166
+ return { comparisonData, ComparisonFlag };
2167
+ };
2168
+
1999
2169
  const getDetectionCount = async ( fromDate, toDate ) => {
2000
2170
  const detectionPayload = {
2001
2171
  fromDate: dayjs( fromDate ).format( 'YYYY-MM-DD' ),
@@ -2004,7 +2174,6 @@ export const flagComparisonCardsV2 = async ( req, res ) => {
2004
2174
  clientId: requestData.clientId,
2005
2175
  };
2006
2176
 
2007
- // The config lookup and the detection Lambda are independent — run them together.
2008
2177
  const LamdaURL = 'https://f65azvtljclaxp6l7rnx65cdmm0lcgvp.lambda-url.ap-south-1.on.aws/';
2009
2178
  const [ publishedAiChecklists, resultData ] = await Promise.all( [
2010
2179
  checklistconfigService.find(
@@ -2017,9 +2186,6 @@ export const flagComparisonCardsV2 = async ( req, res ) => {
2017
2186
  ),
2018
2187
  LamdaServiceCall( LamdaURL, detectionPayload ),
2019
2188
  ] );
2020
- // if ( !publishedAiChecklists?.length ) {
2021
- // return 0;
2022
- // }
2023
2189
  const published = publishedAiChecklists.map( ( val ) => val?.checkListType );
2024
2190
  if ( resultData?.status_code === '200' ) {
2025
2191
  let result = 0;
@@ -2037,51 +2203,268 @@ export const flagComparisonCardsV2 = async ( req, res ) => {
2037
2203
  return 0;
2038
2204
  };
2039
2205
 
2040
- const [ rangeOneData, rangeTwoData, rangeOneDetectionCount, rangeTwoDetectionCount, traxMonthlyComparisonData ] = await Promise.all( [
2206
+ const [ rangeOneData, rangeTwoData, rangeOneDetectionCount, rangeTwoDetectionCount ] = await Promise.all( [
2041
2207
  processedchecklistService.aggregate( createFindQuery( rangeOneFromDate, rangeOneToDate ) ),
2042
2208
  processedchecklistService.aggregate( createFindQuery( rangeTwoFromDate, rangeTwoToDate ) ),
2043
2209
  getDetectionCount( rangeOneFromDate, rangeOneToDate ),
2044
2210
  getDetectionCount( rangeTwoFromDate, rangeTwoToDate ),
2045
- clientService.findOne( { clientId: requestData.clientId }, { traxMonthlyComparison: 1 } ),
2046
2211
  ] );
2047
2212
 
2048
- // console.log( 'rangeOneFromDate =>', rangeOneFromDate );
2049
- // console.log( 'rangeOneToDate =>', rangeOneToDate );
2050
- // console.log( 'rangeOneData =>', rangeOneData );
2051
-
2052
- // console.log( 'rangeTwoFromDate =>', rangeTwoFromDate );
2053
- // console.log( 'rangeTwoToDate =>', rangeTwoToDate );
2054
- // console.log( 'rangeTwoData =>', rangeTwoData );
2055
-
2056
2213
  const flagComparisonCards = {
2057
2214
  totalComparisonFlag: { comparisonData: 0, ComparisonFlag: false },
2058
- questionComparisonFlag: { comparisonData: 0, ComparisonFlag: false },
2059
- delayInSubmissionComparisonFlag: { comparisonData: 0, ComparisonFlag: false },
2060
2215
  detectionComparisonFlag: { comparisonData: 0, ComparisonFlag: false },
2061
2216
  runAIComparisonFlag: { comparisonData: 0, ComparisonFlag: false },
2062
2217
  };
2063
2218
 
2064
2219
  if ( rangeOneData.length && rangeTwoData.length ) {
2065
- let rangeOneTotalFlag = rangeOneData[0].totalFlag + rangeOneDetectionCount.result || 0;
2066
- let rangeTwoTotalFlag = rangeTwoData[0].totalFlag + rangeTwoDetectionCount.result || 0;
2220
+ // Guard against detection failure (getDetectionCount returns 0, not an object).
2221
+ const rangeOneDetection = ( rangeOneDetectionCount && rangeOneDetectionCount.result ) || 0;
2222
+ const rangeTwoDetection = ( rangeTwoDetectionCount && rangeTwoDetectionCount.result ) || 0;
2223
+ const rangeOneAi = ( rangeOneDetectionCount && rangeOneDetectionCount.ai ) || 0;
2224
+ const rangeTwoAi = ( rangeTwoDetectionCount && rangeTwoDetectionCount.ai ) || 0;
2225
+
2226
+ const rangeOneTotalFlag = rangeOneData[0].totalFlag + rangeOneDetection;
2227
+ const rangeTwoTotalFlag = rangeTwoData[0].totalFlag + rangeTwoDetection;
2067
2228
  flagComparisonCards.totalComparisonFlag = calculateComparison( rangeOneTotalFlag, rangeTwoTotalFlag );
2068
- flagComparisonCards.questionComparisonFlag = calculateComparison( rangeOneData[0].questionFlag, rangeTwoData[0].questionFlag );
2069
- flagComparisonCards.delayInSubmissionComparisonFlag = calculateComparison( rangeOneData[0].delayInSubmission, rangeTwoData[0].delayInSubmission );
2070
- flagComparisonCards.detectionComparisonFlag = calculateComparison( rangeOneDetectionCount.result, rangeTwoDetectionCount.result );
2071
- flagComparisonCards.runAIComparisonFlag = calculateComparison( rangeOneDetectionCount.ai, rangeTwoDetectionCount.ai );
2072
- }
2073
- flagComparisonCards.traxMonthlyComparison = false;
2074
- if ( traxMonthlyComparisonData && traxMonthlyComparisonData.traxMonthlyComparison ) {
2075
- flagComparisonCards.traxMonthlyComparison = traxMonthlyComparisonData.traxMonthlyComparison;
2229
+ flagComparisonCards.detectionComparisonFlag = calculateComparison( rangeOneDetection, rangeTwoDetection );
2230
+ flagComparisonCards.runAIComparisonFlag = calculateComparison( rangeOneAi, rangeTwoAi );
2076
2231
  }
2232
+
2077
2233
  return res.sendSuccess( { flagComparisonCards } );
2078
2234
  } catch ( error ) {
2079
2235
  console.log( 'error =>', error );
2080
- logger.error( { error, function: 'flagComparisonCardsV1' } );
2236
+ logger.error( { error, function: 'flagComparisonCardsDetectionV2' } );
2081
2237
  return res.sendError( error, 500 );
2082
2238
  }
2083
2239
  };
2084
2240
 
2241
+ // Builds the per-checklist aggregation pipeline for the flag table. Used by the
2242
+ // detection endpoint (flagTablesDetectionV2) and mirrored by flagTablesV2's own
2243
+ // inline build — the two MUST produce an identical pipeline so the detection map
2244
+ // keys line up with the rows the client already rendered from flagTablesV2.
2245
+ function buildFlagTablesFindQuery( requestData ) {
2246
+ let fromDate = new Date( requestData.fromDate );
2247
+ let toDate = new Date( requestData.toDate );
2248
+ let userTimezoneOffset = toDate.getTimezoneOffset() * 60000;
2249
+ toDate = new Date( toDate.getTime() - userTimezoneOffset );
2250
+ toDate.setUTCHours( 23, 59, 59, 59 );
2251
+
2252
+ const findQuery = [];
2253
+ const findAndQuery = [];
2254
+ findAndQuery.push(
2255
+ { client_id: requestData.clientId },
2256
+ { date_iso: { $gte: fromDate, $lte: toDate } },
2257
+ { $or: [ { store_id: { $in: requestData.storeId } }, { store_id: { $eq: '' }, userEmail: { $in: requestData.userEmailes } }, { aiStoreList: { $in: requestData.storeId } } ] },
2258
+ );
2259
+
2260
+ if ( requestData?.filter === 'all' ) {
2261
+ findAndQuery.push( { $or: [ { checkListType: { $in: [ 'custom', 'customerunattended', 'mobileusagedetection', 'staffleftinthemiddle', 'storeopenandclose', 'uniformdetection', 'cleaning', 'scrum', 'outsidebusinesshoursqueuetracking', 'halfshutter', 'tvcompliance', 'cameratampering', 'queuealert', 'staffgrouping', 'boxalert', 'employeeCount', 'storehygienemonitoring', 'unattendeddetection', 'vehicle_check_in', 'employeemonitoring', 'activitymonitoring' ] } } ] } );
2262
+ } else if ( requestData?.filter === 'question' ) {
2263
+ findAndQuery.push( { checkListType: 'custom' } );
2264
+ findAndQuery.push( { questionFlag: { $gte: 1 } } );
2265
+ } else if ( requestData?.filter === 'time' ) {
2266
+ findAndQuery.push( { checkListType: 'custom' } );
2267
+ findAndQuery.push( { timeFlag: { $gte: 1 } } );
2268
+ } else if ( requestData?.filter === 'detection' ) {
2269
+ findAndQuery.push( { checkListType: { $ne: 'custom' } } );
2270
+ } else if ( requestData?.filter === 'runAI' ) {
2271
+ if ( requestData.runAIChecklistName ) {
2272
+ findAndQuery.push( { checkListName: { $in: requestData.runAIChecklistName } } );
2273
+ }
2274
+ }
2275
+
2276
+ findQuery.push( { $match: { $and: findAndQuery } } );
2277
+
2278
+ if ( requestData.searchValue && requestData.searchValue != '' ) {
2279
+ findQuery.push( { $match: { $or: [ { checkListName: { $regex: requestData.searchValue, $options: 'i' } } ] } } );
2280
+ }
2281
+
2282
+ findQuery.push( {
2283
+ $project: {
2284
+ sourceCheckList_id: 1,
2285
+ checkListId: 1,
2286
+ checkListName: 1,
2287
+ coverage: 1,
2288
+ storeCount: 1,
2289
+ createdBy: 1,
2290
+ createdByName: 1,
2291
+ checklistStatus: 1,
2292
+ timeFlag: 1,
2293
+ questionFlag: 1,
2294
+ questionCount: 1,
2295
+ mobileDetectionFlag: 1,
2296
+ storeOpenCloseFlag: 1,
2297
+ uniformDetectionFlag: 1,
2298
+ checkListType: 1,
2299
+ scheduleRepeatedType: 1,
2300
+ store_id: 1,
2301
+ aiStoreList: 1,
2302
+ runAIQuestionCount: 1,
2303
+ },
2304
+ } );
2305
+
2306
+ findQuery.push( {
2307
+ $group: {
2308
+ _id: '$sourceCheckList_id',
2309
+ checkListName: { $last: '$checkListName' },
2310
+ checkListChar: { $last: { $substr: [ '$checkListName', 0, 2 ] } },
2311
+ coverage: { $last: '$coverage' },
2312
+ sourceCheckList_id: { $last: '$sourceCheckList_id' },
2313
+ checkListType: { $last: '$checkListType' },
2314
+ storeCount: { $sum: 1 },
2315
+ storeCountAi: { $max: '$storeCount' },
2316
+ flaggedStores: { $sum: { $cond: [
2317
+ {
2318
+ $or: [
2319
+ { '$gt': [ '$timeFlag', 0 ] },
2320
+ { '$gt': [ '$questionFlag', 0 ] },
2321
+ { '$gt': [ '$mobileDetectionFlag', 0 ] },
2322
+ { '$gt': [ '$storeOpenCloseFlag', 0 ] },
2323
+ { '$gt': [ '$uniformDetectionFlag', 0 ] },
2324
+ { '$gt': [ '$customerUnattended', 0 ] },
2325
+ { '$gt': [ '$staffLeftInTheMiddle', 0 ] },
2326
+ { '$gt': [ '$cleaning', 0 ] },
2327
+ { '$gt': [ '$scrum', 0 ] },
2328
+ { '$gt': [ '$employeeCount', 0 ] },
2329
+ { '$gt': [ '$employeemonitoring', 0 ] },
2330
+ { '$gt': [ '$activitymonitoring', 0 ] },
2331
+ ],
2332
+ },
2333
+ 1,
2334
+ 0,
2335
+ ] } },
2336
+ flagCount: {
2337
+ $sum: {
2338
+ $add: [ '$questionFlag', '$timeFlag' ],
2339
+ },
2340
+ },
2341
+ submittedChecklist: {
2342
+ $sum: {
2343
+ $cond: [ { $eq: [ '$checklistStatus', 'submit' ] }, 1, 0 ],
2344
+ },
2345
+ },
2346
+ questionFlag: { $sum: '$questionFlag' },
2347
+ timeFlag: { $sum: '$timeFlag' },
2348
+ questionCount: { $sum: '$questionCount' },
2349
+ aiStoreList: { $max: '$aiStoreList' },
2350
+ aiStoreListNew: { $sum: { $size: '$aiStoreList' } },
2351
+ submittedQuestionCount: { $sum: { $cond: [ { $eq: [ '$checklistStatus', 'submit' ] }, '$questionCount', 0 ] } },
2352
+ runAIQuestionCount: { $last: '$runAIQuestionCount' },
2353
+ },
2354
+ } );
2355
+
2356
+ findQuery.push( {
2357
+ $project: {
2358
+ assignedStores: '$storeCount',
2359
+ assignedStoresAi: '$aiStoreListNew',
2360
+ checkListName: 1,
2361
+ coverage: 1,
2362
+ checkListChar: 1,
2363
+ sourceCheckList_id: 1,
2364
+ checkListType: 1,
2365
+ flagType: 1,
2366
+ uniqueFlaggedStores: 1,
2367
+ flaggedStores: '$flaggedStores',
2368
+ flagCount: 1,
2369
+ questionCount: 1,
2370
+ correctAnswers: { $subtract: [ '$submittedQuestionCount', '$questionFlag' ] },
2371
+ customQuestionFlagCount: '$questionFlag',
2372
+ customTimeFlagCount: '$timeFlag',
2373
+ submittedChecklist: 1,
2374
+ aiStoreList: 1,
2375
+ runAIQuestionCount: 1,
2376
+ },
2377
+ } );
2378
+
2379
+ findQuery.push( {
2380
+ $project: {
2381
+ checkListName: 1,
2382
+ checkListChar: 1,
2383
+ coverage: {
2384
+ $concat: [
2385
+ { $toUpper: { $substr: [ { $ifNull: [ '$coverage', '' ] }, 0, 1 ] } },
2386
+ { $substr: [ { $ifNull: [ '$coverage', '' ] }, 1, { $strLenCP: { $ifNull: [ '$coverage', '' ] } } ] },
2387
+ ],
2388
+ },
2389
+ sourceCheckList_id: 1,
2390
+ checkListType: 1,
2391
+ flagType: 1,
2392
+ assignedStores: 1,
2393
+ assignedStoresAi: 1,
2394
+ flaggedStores: 1,
2395
+ flagCount: 1,
2396
+ uniqueFlaggedStores: 1,
2397
+ complianceRate: {
2398
+ $cond: {
2399
+ if: { $eq: [ '$questionCount', 0 ] },
2400
+ then: 0,
2401
+ else: {
2402
+ $round: [ { $multiply: [ { $divide: [ '$correctAnswers', '$questionCount' ] }, 100 ] }, 2 ],
2403
+ },
2404
+ },
2405
+ },
2406
+ customQuestionFlagCount: 1,
2407
+ customTimeFlagCount: 1,
2408
+ submittedChecklist: 1,
2409
+ aiStoreList: 1,
2410
+ runAIQuestionCount: 1,
2411
+ },
2412
+ } );
2413
+
2414
+ findQuery.push( { $sort: { ['submittedChecklist']: -1 } } );
2415
+ return findQuery;
2416
+ }
2417
+
2418
+ // Merges detection / runAI / all flag values from the Lambda into the aggregated
2419
+ // rows (mutates in place). Single source of truth shared by flagTablesV2's export
2420
+ // path and flagTablesDetectionV2 — the detection-column values (flaggedStores,
2421
+ // flagCount, complianceRate, assignedStores) and their edge cases live here only.
2422
+ function mergeDetectionIntoRows( getChecklistPerformanceData, lambdaResultData, requestData ) {
2423
+ if ( !getChecklistPerformanceData?.length ) return;
2424
+ const detectionChecklists = getChecklistPerformanceData?.filter( ( val ) => val?.checkListType !== 'custom' );
2425
+ if ( !( detectionChecklists?.length || [ 'runAI', 'all' ].includes( requestData?.filter ) ) ) return;
2426
+ const resultData = lambdaResultData;
2427
+ if ( !resultData || resultData.status_code != '200' ) return;
2428
+
2429
+ for ( let index = 0; index < getChecklistPerformanceData.length; index++ ) {
2430
+ if ( [ 'runAI', 'all' ].includes( requestData?.filter ) ) {
2431
+ let findCheckList = resultData?.runAI?.list?.find( ( ele ) => ele.checkListName == getChecklistPerformanceData[index].checkListName );
2432
+ if ( findCheckList ) {
2433
+ if ( requestData?.filter == 'all' ) {
2434
+ getChecklistPerformanceData[index].runAIFlag = findCheckList.flagCount;
2435
+ getChecklistPerformanceData[index].flaggedStores = getChecklistPerformanceData[index].flaggedStores + findCheckList.flagCount;
2436
+ } else {
2437
+ getChecklistPerformanceData[index].flagCount = findCheckList.flagCount;
2438
+ getChecklistPerformanceData[index].flaggedStores = findCheckList.flagStoreCount;
2439
+ }
2440
+ } else {
2441
+ if ( getChecklistPerformanceData[index].checkListType !== 'custom' ) {
2442
+ if ( requestData?.filter == 'all' ) {
2443
+ getChecklistPerformanceData[index].runAIFlag = 0;
2444
+ getChecklistPerformanceData[index].flagCount = resultData?.[getChecklistPerformanceData[index]?.checkListType] || 0;
2445
+ getChecklistPerformanceData[index].flaggedStores = resultData?.[`${getChecklistPerformanceData[index]?.checkListType}_flaggedstores`] || 0;
2446
+ } else {
2447
+ getChecklistPerformanceData[index].flagCount = 0;
2448
+ getChecklistPerformanceData[index].flaggedStores = 0;
2449
+ }
2450
+ }
2451
+ }
2452
+ getChecklistPerformanceData[index].complianceRate = ( 100- ( getChecklistPerformanceData[index].flaggedStores / getChecklistPerformanceData[index].assignedStores ) * 100 );
2453
+ getChecklistPerformanceData[index].complianceRate = Math.min( 100, Math.max( 0, getChecklistPerformanceData[index].complianceRate ) );
2454
+ getChecklistPerformanceData[index].complianceRate = ( getChecklistPerformanceData[index].complianceRate % 1 === 0 ) ? getChecklistPerformanceData[index].complianceRate.toFixed( 0 ) : getChecklistPerformanceData[index].complianceRate.toFixed( 2 );
2455
+ } else {
2456
+ if ( getChecklistPerformanceData[index].checkListType !== 'custom' ) {
2457
+ getChecklistPerformanceData[index].flagCount = resultData?.[getChecklistPerformanceData[index]?.checkListType] || 0;
2458
+ getChecklistPerformanceData[index].flaggedStores = resultData?.[`${getChecklistPerformanceData[index]?.checkListType}_flaggedstores`] || 0;
2459
+ getChecklistPerformanceData[index].assignedStores = getChecklistPerformanceData[index]?.aiStoreList?.filter( ( element ) => requestData.storeId.includes( element ) )?.length || 0;
2460
+ getChecklistPerformanceData[index].complianceRate = ( 100- ( getChecklistPerformanceData[index].flaggedStores / getChecklistPerformanceData[index].assignedStores ) * 100 );
2461
+ getChecklistPerformanceData[index].complianceRate = Math.min( 100, Math.max( 0, getChecklistPerformanceData[index].complianceRate ) );
2462
+ getChecklistPerformanceData[index].complianceRate = ( getChecklistPerformanceData[index].complianceRate % 1 === 0 ) ? getChecklistPerformanceData[index].complianceRate.toFixed( 0 ) : getChecklistPerformanceData[index].complianceRate.toFixed( 2 );
2463
+ }
2464
+ }
2465
+ }
2466
+ }
2467
+
2085
2468
  export const flagTablesV2 = async ( req, res ) => {
2086
2469
  try {
2087
2470
  let requestData = req.body;
@@ -2091,8 +2474,6 @@ export const flagTablesV2 = async ( req, res ) => {
2091
2474
  toDate = new Date( toDate.getTime() - userTimezoneOffset );
2092
2475
  toDate.setUTCHours( 23, 59, 59, 59 );
2093
2476
  let result = {};
2094
- let limit = parseInt( requestData?.limit ) || 10;
2095
- let skip = limit * ( requestData?.offset ) || 0;
2096
2477
 
2097
2478
  const lamdaUrl = JSON.parse( process.env.LAMBDAURL );
2098
2479
 
@@ -2281,10 +2662,11 @@ export const flagTablesV2 = async ( req, res ) => {
2281
2662
  // fixes. One row per checklist keeps the in-memory set small.
2282
2663
  findQuery.push( { $sort: { ['submittedChecklist']: -1 } } );
2283
2664
 
2284
- // The detection Lambda only needs request params (not the aggregation result),
2285
- // so fire it concurrently with the Mongo aggregation instead of after it.
2665
+ // Only the export download needs the detection values merged inline (a one-off
2666
+ // download can tolerate the ~8s Lambda). The interactive table load skips the
2667
+ // Lambda entirely and hydrates detection values via flagTablesDetectionV2.
2286
2668
  // 'question'/'time' filters are custom-only, so the Lambda is never needed there.
2287
- const mayNeedLambda = ![ 'question', 'time' ].includes( requestData?.filter );
2669
+ const mayNeedLambda = !!requestData.export && ![ 'question', 'time' ].includes( requestData?.filter );
2288
2670
  const detectionPayload = {
2289
2671
  'fromDate': requestData.fromDate,
2290
2672
  'toDate': requestData.toDate,
@@ -2307,54 +2689,10 @@ export const flagTablesV2 = async ( req, res ) => {
2307
2689
  }
2308
2690
 
2309
2691
  let getTotalCount = getChecklistPerformanceData.length;
2310
- if ( getChecklistPerformanceData.length ) {
2311
- const detectionChecklists = getChecklistPerformanceData?.filter( ( val ) => val?.checkListType !== 'custom' );
2312
-
2313
- if ( detectionChecklists?.length || [ 'runAI', 'all' ].includes( requestData?.filter ) ) {
2314
- let resultData = lambdaResultData;
2315
- if ( resultData ) {
2316
- if ( resultData.status_code == '200' ) {
2317
- for ( let index = 0; index < getChecklistPerformanceData.length; index++ ) {
2318
- if ( [ 'runAI', 'all' ].includes( requestData?.filter ) ) {
2319
- let findCheckList = resultData?.runAI?.list?.find( ( ele ) => ele.checkListName == getChecklistPerformanceData[index].checkListName );
2320
- if ( findCheckList ) {
2321
- if ( requestData?.filter == 'all' ) {
2322
- getChecklistPerformanceData[index].runAIFlag = findCheckList.flagCount;
2323
- getChecklistPerformanceData[index].flaggedStores = getChecklistPerformanceData[index].flaggedStores + findCheckList.flagCount;
2324
- } else {
2325
- getChecklistPerformanceData[index].flagCount = findCheckList.flagCount;
2326
- getChecklistPerformanceData[index].flaggedStores = findCheckList.flagStoreCount;
2327
- }
2328
- } else {
2329
- if ( getChecklistPerformanceData[index].checkListType !== 'custom' ) {
2330
- if ( requestData?.filter == 'all' ) {
2331
- getChecklistPerformanceData[index].runAIFlag = 0;
2332
- getChecklistPerformanceData[index].flagCount = resultData?.[getChecklistPerformanceData[index]?.checkListType] || 0;
2333
- getChecklistPerformanceData[index].flaggedStores = resultData?.[`${getChecklistPerformanceData[index]?.checkListType}_flaggedstores`] || 0;
2334
- } else {
2335
- getChecklistPerformanceData[index].flagCount = 0;
2336
- getChecklistPerformanceData[index].flaggedStores = 0;
2337
- }
2338
- }
2339
- }
2340
- getChecklistPerformanceData[index].complianceRate = ( 100- ( getChecklistPerformanceData[index].flaggedStores / getChecklistPerformanceData[index].assignedStores ) * 100 );
2341
- getChecklistPerformanceData[index].complianceRate = Math.min( 100, Math.max( 0, getChecklistPerformanceData[index].complianceRate ) );
2342
- getChecklistPerformanceData[index].complianceRate = ( getChecklistPerformanceData[index].complianceRate % 1 === 0 ) ? getChecklistPerformanceData[index].complianceRate.toFixed( 0 ) : getChecklistPerformanceData[index].complianceRate.toFixed( 2 );
2343
- } else {
2344
- if ( getChecklistPerformanceData[index].checkListType !== 'custom' ) {
2345
- getChecklistPerformanceData[index].flagCount = resultData?.[getChecklistPerformanceData[index]?.checkListType] || 0;
2346
- getChecklistPerformanceData[index].flaggedStores = resultData?.[`${getChecklistPerformanceData[index]?.checkListType}_flaggedstores`] || 0;
2347
- getChecklistPerformanceData[index].assignedStores = getChecklistPerformanceData[index]?.aiStoreList?.filter( ( element ) => requestData.storeId.includes( element ) )?.length || 0;
2348
- getChecklistPerformanceData[index].complianceRate = ( 100- ( getChecklistPerformanceData[index].flaggedStores / getChecklistPerformanceData[index].assignedStores ) * 100 );
2349
- getChecklistPerformanceData[index].complianceRate = Math.min( 100, Math.max( 0, getChecklistPerformanceData[index].complianceRate ) );
2350
- getChecklistPerformanceData[index].complianceRate = ( getChecklistPerformanceData[index].complianceRate % 1 === 0 ) ? getChecklistPerformanceData[index].complianceRate.toFixed( 0 ) : getChecklistPerformanceData[index].complianceRate.toFixed( 2 );
2351
- }
2352
- }
2353
- }
2354
- }
2355
- }
2356
- }
2357
- }
2692
+ // For export, lambdaResultData is populated and detection values are merged in
2693
+ // here. For the interactive load lambdaResultData is null, so this is a no-op
2694
+ // and the detection values are hydrated later via flagTablesDetectionV2.
2695
+ mergeDetectionIntoRows( getChecklistPerformanceData, lambdaResultData, requestData );
2358
2696
 
2359
2697
  // Sort in JS AFTER the Lambda merge so detection / runAI / all rows order by
2360
2698
  // their real (merged) flaggedStores / flagCount / complianceRate / assignedStores
@@ -2446,8 +2784,10 @@ export const flagTablesV2 = async ( req, res ) => {
2446
2784
  }
2447
2785
 
2448
2786
  result.totalCount = getTotalCount;
2449
- // Paginate in JS now that the full set is sorted by the merged values.
2450
- result.checklistPerformance = getChecklistPerformanceData.slice( skip, skip + limit );
2787
+ // Fast path returns the FULL Mongo-only set (one row per checklist); the client
2788
+ // paginates and re-sorts after merging the detection values from
2789
+ // flagTablesDetectionV2. Export returns earlier via download() above.
2790
+ result.checklistPerformance = getChecklistPerformanceData;
2451
2791
  return res.sendSuccess( result );
2452
2792
  } catch ( error ) {
2453
2793
  console.log( 'error =>', error );
@@ -2456,6 +2796,71 @@ export const flagTablesV2 = async ( req, res ) => {
2456
2796
  }
2457
2797
  };
2458
2798
 
2799
+ // Detection-only companion for flagTablesV2. Re-runs the SAME (cheap) aggregation
2800
+ // alongside the detection Lambda (the slow part), merges the detection values, and
2801
+ // returns them keyed by sourceCheckList_id. The client patches these onto the rows
2802
+ // it already rendered from flagTablesV2, then re-sorts / re-paginates:
2803
+ // for ( const row of rows ) {
2804
+ // const d = res.detection[ row.sourceCheckList_id ];
2805
+ // if ( d ) Object.assign( row, d );
2806
+ // }
2807
+ // The aggregation is re-run (not passed in) so the two endpoints stay independent
2808
+ // and the Lambda still runs concurrently with Mongo; the aggregation is cheap
2809
+ // relative to the ~8s Lambda that dominates this call's latency.
2810
+ export const flagTablesDetectionV2 = async ( req, res ) => {
2811
+ try {
2812
+ const requestData = req.body;
2813
+
2814
+ // 'question'/'time' filters are custom-only — no detection data to fetch.
2815
+ const mayNeedLambda = ![ 'question', 'time' ].includes( requestData?.filter );
2816
+ if ( !mayNeedLambda ) {
2817
+ return res.sendSuccess( { detection: {} } );
2818
+ }
2819
+
2820
+ const findQuery = buildFlagTablesFindQuery( requestData );
2821
+
2822
+ const lamdaUrl = JSON.parse( process.env.LAMBDAURL );
2823
+ const LamdaURL = lamdaUrl.flagTablesV2;
2824
+ const detectionPayload = {
2825
+ 'fromDate': requestData.fromDate,
2826
+ 'toDate': requestData.toDate,
2827
+ 'storeId': requestData.storeId,
2828
+ 'clientId': requestData.clientId,
2829
+ };
2830
+
2831
+ const [ getChecklistPerformanceData, lambdaResultData ] = await Promise.all( [
2832
+ processedchecklistService.aggregate( findQuery ),
2833
+ LamdaServiceCall( LamdaURL, detectionPayload ),
2834
+ ] );
2835
+
2836
+ if ( !getChecklistPerformanceData?.length ) {
2837
+ return res.sendSuccess( { detection: {} } );
2838
+ }
2839
+
2840
+ mergeDetectionIntoRows( getChecklistPerformanceData, lambdaResultData, requestData );
2841
+
2842
+ // Return only the detection-affected fields, keyed by sourceCheckList_id, so the
2843
+ // client can patch them onto its already-rendered rows.
2844
+ const detection = {};
2845
+ getChecklistPerformanceData.forEach( ( row ) => {
2846
+ const key = String( row.sourceCheckList_id ?? row._id );
2847
+ detection[key] = {
2848
+ flagCount: row.flagCount,
2849
+ flaggedStores: row.flaggedStores,
2850
+ complianceRate: row.complianceRate,
2851
+ assignedStores: row.assignedStores,
2852
+ ...( row.runAIFlag !== undefined ? { runAIFlag: row.runAIFlag } : {} ),
2853
+ };
2854
+ } );
2855
+
2856
+ return res.sendSuccess( { detection } );
2857
+ } catch ( error ) {
2858
+ console.log( 'error =>', error );
2859
+ logger.error( { error: error, message: req.query, function: 'flagTablesDetectionV2' } );
2860
+ return res.sendError( { error: error }, 500 );
2861
+ }
2862
+ };
2863
+
2459
2864
  export const checklistCountByType = async ( req, res ) => {
2460
2865
  try {
2461
2866
  let { clientId, fromDate, toDate } = req.body;
@@ -9,11 +9,14 @@ import {
9
9
  flagChecklistTableV1,
10
10
  overallFlagMetrics,
11
11
  flagCardsV1,
12
+ flagCardsDetectionV1,
12
13
  flagComparisonCardsV1,
13
14
  flagTablesV1,
14
15
  checklistDropdownV1,
15
16
  flagTablesV2,
17
+ flagTablesDetectionV2,
16
18
  flagComparisonCardsV2,
19
+ flagComparisonCardsDetectionV2,
17
20
  checklistCountByType,
18
21
  } from '../controllers/teaxFlag.controller.js';
19
22
  import { isAllowedSessionHandler } from 'tango-app-api-middleware';
@@ -42,6 +45,9 @@ traxFlagRouter
42
45
  .post( '/flagCardsV1', isAllowedSessionHandler,
43
46
  accessVerification( { userType: [ 'tango', 'client' ], access: [ { featureName: 'TangoTrax', name: 'checklist', permissions: [] } ] } ),
44
47
  flagCardsV1 )
48
+ .post( '/flagCardsDetectionV1', isAllowedSessionHandler,
49
+ accessVerification( { userType: [ 'tango', 'client' ], access: [ { featureName: 'TangoTrax', name: 'checklist', permissions: [] } ] } ),
50
+ flagCardsDetectionV1 )
45
51
  .post( '/flagComparisonCardsV1', isAllowedSessionHandler,
46
52
  accessVerification( { userType: [ 'tango', 'client' ], access: [ { featureName: 'TangoTrax', name: 'checklist', permissions: [] } ] } ),
47
53
  flagComparisonCardsV1 )
@@ -51,9 +57,15 @@ traxFlagRouter
51
57
  .post( '/flagComparisonCardsV2', isAllowedSessionHandler,
52
58
  accessVerification( { userType: [ 'tango', 'client' ], access: [ { featureName: 'TangoTrax', name: 'checklist', permissions: [] } ] } ),
53
59
  flagComparisonCardsV2 )
60
+ .post( '/flagComparisonCardsDetectionV2', isAllowedSessionHandler,
61
+ accessVerification( { userType: [ 'tango', 'client' ], access: [ { featureName: 'TangoTrax', name: 'checklist', permissions: [] } ] } ),
62
+ flagComparisonCardsDetectionV2 )
54
63
  .post( '/flagTablesV2', isAllowedSessionHandler,
55
64
  accessVerification( { userType: [ 'tango', 'client' ], access: [ { featureName: 'TangoTrax', name: 'checklist', permissions: [] } ] } ),
56
65
  flagTablesV2 )
66
+ .post( '/flagTablesDetectionV2', isAllowedSessionHandler,
67
+ accessVerification( { userType: [ 'tango', 'client' ], access: [ { featureName: 'TangoTrax', name: 'checklist', permissions: [] } ] } ),
68
+ flagTablesDetectionV2 )
57
69
  .post( '/checklistCountByType', isAllowedSessionHandler,
58
70
  accessVerification( { userType: [ 'tango', 'client' ], access: [ { featureName: 'TangoTrax', name: 'checklist', permissions: [] } ] } ),
59
71
  checklistCountByType );