tango-app-api-analysis-traffic 3.0.0-beta.2 → 3.1.0-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1862 @@
1
+ import { logger, insertOpenSearchData, getOpenSearchData, getAssinedStoreEmailers, sendMessageToQueue, signedUrl, sendEmailWithSES, fileUpload, updateOpenSearchData } from 'tango-app-api-middleware';
2
+ import * as clientService from '../services/clients.services.js';
3
+ import * as userService from '../services/user.service.js';
4
+ import * as storeService from '../services/stores.service.js';
5
+ import { findCamera } from '../services/camera.service.js';
6
+ import handlebars, { registerHelpers } from './handlebars.js';
7
+ const __filename = fileURLToPath( import.meta.url );
8
+ const __dirname = dirname( __filename );
9
+ import { fileURLToPath } from 'url';
10
+ import { dirname } from 'path';
11
+ import puppeteer from 'puppeteer';
12
+ import { readFileSync } from 'fs';
13
+ import { join } from 'path';
14
+ import dayjs from 'dayjs';
15
+ registerHelpers();
16
+
17
+ // Lamda Service Call //
18
+ async function LamdaServiceCall( url, data ) {
19
+ try {
20
+ const requestOptions = {
21
+ method: 'POST',
22
+ headers: {
23
+ 'Content-Type': 'application/json',
24
+ },
25
+ body: JSON.stringify( data ),
26
+ };
27
+ const response = await fetch( url, requestOptions );
28
+ if ( !response.ok ) {
29
+ throw new Error( `Response status: ${response.status}` );
30
+ return false;
31
+ }
32
+ const json = await response.json();
33
+ return json;
34
+ } catch ( error ) {
35
+ logger.error( { error: error, message: data, function: 'LamdaServiceCall' } );
36
+ return false;
37
+ }
38
+ }
39
+
40
+ // ///// V1 API's ///////
41
+ export const cardsFunnelV1 = async ( req, res ) => {
42
+ try {
43
+ let reqestData = req.body;
44
+ let getClientData = await getClientConfig( reqestData.clientId );
45
+ if ( !getClientData ) {
46
+ return res.sendError( 'Invalid Client Id', 400 );
47
+ }
48
+ reqestData.featureConfigs = getClientData.featureConfigs;
49
+ reqestData.currency = getClientData.paymentInvoice?.currencyType || 'inr';
50
+ reqestData.revenue = getClientData.averageTransactionValue || '0';
51
+ let LamdaURL = 'https://55mojecvuvtphucgsalx5jtyki0untzp.lambda-url.ap-south-1.on.aws/';
52
+ let resultData = await LamdaServiceCall( LamdaURL, reqestData );
53
+ if ( resultData ) {
54
+ if ( resultData.status_code == '200' ) {
55
+ return res.sendSuccess( resultData );
56
+ } else {
57
+ return res.sendError( 'No Content', 204 );
58
+ }
59
+ } else {
60
+ return res.sendError( 'No Content', 204 );
61
+ }
62
+ } catch ( error ) {
63
+ logger.error( { error: error, message: req.query, function: 'cardsFunnelV1' } );
64
+ return res.sendError( { error: error }, 500 );
65
+ }
66
+ };
67
+
68
+ async function getClientConfig( clientId ) {
69
+ try {
70
+ let getClientData = await clientService.findOne( { clientId: clientId }, { 'paymentInvoice.currencyType': 1, 'averageTransactionValue': 1, 'featureConfigs.billableCalculation': 1, 'featureConfigs.missedOpportunityCalculation': 1, 'featureConfigs.conversionCalculation': 1, 'featureConfigs.open': 1, 'featureConfigs.close': 1, 'isFootfallAuditStores': 1 } );
71
+ if ( !getClientData ) {
72
+ return false;
73
+ }
74
+ return getClientData;
75
+ } catch ( error ) {
76
+ logger.error( { error: error, message: data, function: 'getClientConfig' } );
77
+ return false;
78
+ }
79
+ }
80
+
81
+ export const welcome = async ( req, res ) => {
82
+ try {
83
+ let result = {
84
+ 'Message': 'Welcome',
85
+ };
86
+ return res.sendSuccess( result );
87
+ } catch ( error ) {
88
+ logger.error( { error: error, message: req.query, function: 'trafficCards' } );
89
+ return res.sendError( { error: error }, 500 );
90
+ }
91
+ };
92
+
93
+ export const createEmailers = async ( req, res ) => {
94
+ try {
95
+ let requestData = req.body;
96
+ const kolkataOffset = 5.5 * 60; // +5:30 in minutes
97
+ // // Check Client config ////
98
+ let getClientData = await clientService.findOne( { clientId: requestData.clientId }, { clientId: 1, emailersConfig: 1 } );
99
+
100
+ if ( !getClientData ) {
101
+ return res.sendError( 'Invalid clientId', 400 );
102
+ }
103
+
104
+ switch ( requestData.templateType ) {
105
+ case 'monthly':
106
+ if ( getClientData && !getClientData.emailersConfig.monthly ) {
107
+ return res.sendError( 'Client Not Enabled Monthly Emails', 400 );
108
+ }
109
+ break;
110
+ case 'weekly':
111
+ if ( getClientData && !getClientData.emailersConfig.weekly ) {
112
+ return res.sendError( 'Client Not Enabled Weekly Emails', 400 );
113
+ }
114
+ break;
115
+ default:
116
+ if ( getClientData && !getClientData.emailersConfig.daily ) {
117
+ return res.sendError( 'Client Not Enabled Daily Emails', 400 );
118
+ }
119
+ break;
120
+ }
121
+
122
+ // // Get active Users ////
123
+ let getUserData = await userService.findUserModel( { clientId: requestData.clientId, isActive: true, userType: 'client' }, { userName: 1, email: 1, role: 1, clientId: 1, isActive: 1, userType: 1, assignedStores: 1 } );
124
+
125
+ if ( !getUserData && getUserData.length < 1 ) {
126
+ return res.sendError( 'Active Users Empty', 400 );
127
+ }
128
+ let emailersIds = [];
129
+ // // Make for Loop and Get Assigned Stores each users ////
130
+ for ( let i = 0; i < getUserData.length; i++ ) {
131
+ let getUserStores = await getAssinedStoreEmailers( getUserData[i] );
132
+ let currentDate = new Date();
133
+ let emailersInsertData = {
134
+ clientId: requestData.clientId,
135
+ fromDate: requestData.fromDate,
136
+ toDate: requestData.toDate,
137
+ templateType: requestData.templateType,
138
+ status: 'open',
139
+ createdAt: currentDate,
140
+ dateISO: new Date( currentDate.getTime() + kolkataOffset * 60 * 1000 ),
141
+ userEmail: getUserData[i].email,
142
+ userName: getUserData[i].userName,
143
+ storeIds: getUserStores || [],
144
+ };
145
+ // // Insert emailers ////
146
+ let insertOS = await insertOpenSearchData( JSON.parse( process.env.OPENSEARCH ).emailers, emailersInsertData );
147
+ if ( insertOS && insertOS.body.result == 'created' ) {
148
+ emailersIds.push( insertOS.body._id );
149
+ }
150
+ }
151
+ // // Send SQS Message ////
152
+ for ( let j = 0; j < emailersIds.length; j++ ) {
153
+ let sqsMessageRequestData = {
154
+ 'emailersId': emailersIds[j],
155
+ };
156
+ const msg = sendMessageToQueue( `${JSON.parse( process.env.SQS ).url}${JSON.parse( process.env.SQS ).emailers}`, JSON.stringify( sqsMessageRequestData ) );
157
+ console.log( 'Send SQS Message =>'+[ j ], msg );
158
+ }
159
+ return res.sendSuccess( 'Emailers Triggered Successfully' );
160
+ } catch ( error ) {
161
+ // console.log( 'error =>', error );
162
+ logger.error( { error: error, message: req.query, function: 'createEmailers' } );
163
+ return res.sendError( { error: error }, 500 );
164
+ }
165
+ };
166
+
167
+ export const emailersList = async ( req, res ) => {
168
+ try {
169
+ let requestBody = req.query;
170
+ // // Get Data From Emailers Collection Based on Filters////
171
+ let from = ( req.query.offset - 1 ) * req.query.limit;
172
+ let start = new Date( requestBody.fromDate );
173
+ let userTimezoneOffset = start.getTimezoneOffset() * 60000;
174
+ start = new Date( start.getTime() - userTimezoneOffset );
175
+ start.setUTCHours( 0, 0, 0, 0 );
176
+ let end = new Date( requestBody.toDate );
177
+ end = new Date( end.getTime() - userTimezoneOffset );
178
+ end.setUTCHours( 23, 59, 59, 59 );
179
+
180
+ const mustConditions = [];
181
+
182
+ if ( requestBody.clientId ) {
183
+ mustConditions.push( { match: { clientId: requestBody.clientId } } );
184
+ }
185
+
186
+ if ( requestBody.templateType ) {
187
+ mustConditions.push( { match: { templateType: requestBody.templateType } } );
188
+ }
189
+
190
+ if ( requestBody.userEmail ) {
191
+ mustConditions.push( { match: { userEmail: requestBody.userEmail } } );
192
+ }
193
+
194
+ if ( requestBody.status ) {
195
+ mustConditions.push( { match: { status: requestBody.status } } );
196
+ }
197
+
198
+ if ( requestBody._id ) {
199
+ mustConditions.push( { match: { _id: requestBody._id } } );
200
+ }
201
+
202
+ if ( requestBody.dateRange ) {
203
+ mustConditions.push( {
204
+ range: {
205
+ dateIso: {
206
+ gte: start, // Start date
207
+ lte: end, // End date
208
+ },
209
+ },
210
+ } );
211
+ }
212
+
213
+ // console.log( 'mustConditions =>', mustConditions );
214
+ let query = {
215
+ from: from,
216
+ size: req.query.limit,
217
+ query: {
218
+ bool: {
219
+ must: mustConditions,
220
+ },
221
+ },
222
+ sort: [
223
+ {
224
+ createdAt: {
225
+ order: 'desc',
226
+ },
227
+ },
228
+ ],
229
+ };
230
+ let result = await getOpenSearchData( JSON.parse( process.env.OPENSEARCH ).emailers, query );
231
+ if ( !result || !result.body.hits.hits.length ) {
232
+ return res.sendError( 'no data found', 204 );
233
+ }
234
+ return res.sendSuccess( { count: result.body.hits.total.value, result: result.body.hits.hits } );
235
+ } catch ( error ) {
236
+ logger.error( { error: error, message: req.query, function: 'emailersList' } );
237
+ return res.sendError( { error: error }, 500 );
238
+ }
239
+ };
240
+
241
+ export const sendEmailers = async ( req, res ) => {
242
+ try {
243
+ let requestBody = req.body;
244
+ // // Get Data From Emailers Collection Based on _id////
245
+ const mustConditions = [];
246
+ if ( requestBody._id ) {
247
+ mustConditions.push( { match: { _id: requestBody._id } } );
248
+ }
249
+ let query = {
250
+ query: {
251
+ bool: {
252
+ must: mustConditions,
253
+ },
254
+ },
255
+ };
256
+ let getEmailSourceData = await getOpenSearchData( JSON.parse( process.env.OPENSEARCH ).emailers, query );
257
+ // console.log( 'getEmailSourceData =>', getEmailSourceData.body.hits.hits[0] );
258
+ // console.log( 'getEmailSourceData.length =>', getEmailSourceData.body.hits.hits.length );
259
+ if ( !getEmailSourceData.body.hits.hits.length ) {
260
+ return res.sendError( 'Invalide Emailer ID', 400 );
261
+ }
262
+
263
+ let emailerSourceData = getEmailSourceData.body.hits.hits[0];
264
+ if ( emailerSourceData._source.status == 'completed' ) {
265
+ return res.sendError( 'Email Already Send', 400 );
266
+ }
267
+ // // Get Stores Data ////
268
+ let getStoreSourceData = await storeService.findOneStore( { storeId: emailerSourceData._source.storeIds[0] }, { storeId: 1, storeName: 1, status: 1, storeProfile: 1 } );
269
+ const camera = await findCamera( { storeId: emailerSourceData._source.storeIds[0], isUp: true, isActivated: true }, { thumbnailImage: 1 } );
270
+ let cameraBaseImage = '';
271
+ const bucket= JSON.parse( process.env.BUCKET );
272
+ if ( camera ) {
273
+ const params = {
274
+ file_path: camera.thumbnailImage,
275
+ Bucket: bucket.baseImage,
276
+ };
277
+ cameraBaseImage = await signedUrl( params );
278
+ }
279
+ let storeData = {
280
+ storeId: getStoreSourceData.storeId,
281
+ storeName: getStoreSourceData.storeName,
282
+ storeLocation: getStoreSourceData.storeProfile.city+' '+getStoreSourceData.storeProfile.pincode,
283
+ storeCount: emailerSourceData._source.storeIds.length-1,
284
+ storeBaseImage: cameraBaseImage,
285
+ };
286
+
287
+ // // Get Client Data ///
288
+ let clientData = await clientService.findOne( { clientId: emailerSourceData._source.clientId }, { clientId: 1, emailersConfig: 1, featureConfigs: 1, clientName: 1 } );
289
+
290
+ // // Get Template Data ///
291
+ let templateData = {
292
+ userEmail: emailerSourceData._source.userEmail,
293
+ userName: emailerSourceData._source.userName,
294
+ templateType: emailerSourceData._source.templateType,
295
+ clientId: emailerSourceData._source.clientId,
296
+ clientName: clientData.clientName,
297
+ storeId: emailerSourceData._source.storeIds.slice( 0, 30 ),
298
+ allStores: emailerSourceData._source.storeIds,
299
+ fromDate: emailerSourceData._source.fromDate,
300
+ toDate: emailerSourceData._source.toDate,
301
+ templateType: emailerSourceData._source.templateType,
302
+ hourFormat: 12,
303
+ featureConfigs: {
304
+ open: clientData?.featureConfigs?.open || '10:00:00',
305
+ close: clientData?.featureConfigs?.close || '23:00:00',
306
+ billableCalculation: clientData?.featureConfigs?.billableCalculation || 'engagers-count',
307
+ conversionCalculation: clientData?.featureConfigs?.conversionCalculation || 'engagers-count',
308
+ missedOpportunityCalculation: clientData?.featureConfigs?.missedOpportunityCalculation || 'engagers-conversion',
309
+ bufferTime: 30,
310
+ },
311
+ };
312
+ // console.log( 'templateData =>', templateData );
313
+
314
+ // // Get Lamda Metrics Data ////
315
+ let lamdaMetrics = await getLamdaMetricsData( templateData );
316
+
317
+ // // Get Lamda Chart Data ///
318
+ let lamdaCharts = await getLamdaChartData( templateData );
319
+
320
+ // // Call Send Email Function ////
321
+ let emailStatus = await emailerSendEmail( storeData, clientData, lamdaMetrics, lamdaCharts, templateData );
322
+
323
+ // return res.sendSuccess( 'Email Send Successfully' );
324
+ // // Once Email Successfully Send Update Status to Completed ////
325
+ if ( emailStatus ) {
326
+ const document = {
327
+ doc: {
328
+ status: 'open',
329
+ // fromDate: '2025-01-13',
330
+ // toDate: '2025-01-19',
331
+ // status: 'completed',
332
+ // storeIds: [ '430-8' ],
333
+ },
334
+ };
335
+ let updateResult = await updateOpenSearchData( JSON.parse( process.env.OPENSEARCH ).emailers, requestBody._id, document );
336
+ if ( updateResult?.statusCode == 200 && updateResult?.body?.result == 'updated' ) {
337
+ return res.sendSuccess( 'Email Send Successfully' );
338
+ } else {
339
+ return res.sendSuccess( 'Email Send Successfully' );
340
+ // return res.sendError( { error: error }, 500 );
341
+ }
342
+ }
343
+ } catch ( error ) {
344
+ // console.log( 'error =>', error );
345
+ logger.error( { error: error, message: req.query, function: 'sendEmails' } );
346
+ return res.sendError( { error: error }, 500 );
347
+ }
348
+ };
349
+
350
+ async function getLamdaMetricsData( templateData ) {
351
+ try {
352
+ let resultLamdaMetricsData;
353
+ if ( templateData.allStores.length == 1 && templateData.templateType == 'daily' ) {
354
+ // // Call Lamda API No 1 Single Store Single Date
355
+ // API Number: 1 (Daily Emailer With Single Store)
356
+ resultLamdaMetricsData = await lamdaAPI1( templateData );
357
+ return resultLamdaMetricsData;
358
+ } else if ( templateData.allStores.length > 1 && templateData.templateType == 'daily' ) {
359
+ // // Call Lamda API No 2 Multiple Store Single Date
360
+ // API Number: 3 (Daily Emailer With Multi Stores)
361
+ resultLamdaMetricsData = await lamdaAPI3( templateData );
362
+ return resultLamdaMetricsData;
363
+ } else if ( templateData.allStores.length == 1 && templateData.templateType == 'weekly' ) {
364
+ // // Call Lamda API No 3 Single Store Multiple Date
365
+ // API Number: 2 (Weekly Emailer With Single Store)
366
+ resultLamdaMetricsData = await lamdaAPI2( templateData );
367
+ return resultLamdaMetricsData;
368
+ } else if ( templateData.allStores.length > 1 && templateData.templateType == 'weekly' ) {
369
+ // // Call Lamda API No 4 Multiple Store Multiple Date
370
+ // API Number: 4 (Weekly Emailer With Multi Stores)
371
+ resultLamdaMetricsData = await lamdaAPI4( templateData );
372
+ return resultLamdaMetricsData;
373
+ } else if ( templateData.allStores.length == 1 && templateData.templateType == 'monthly' ) {
374
+ // // Call Lamda API No 5 Single Store Multiple Date
375
+
376
+ } else if ( templateData.allStores.length > 1 && templateData.templateType == 'monthly' ) {
377
+ // // Call Lamda API No 6 Multiple Store Multiple Date
378
+
379
+ } else {
380
+ // Default Value
381
+ }
382
+ } catch ( error ) {
383
+ // console.log( 'error =>', error );
384
+ logger.error( { error: error, function: 'getLamdaMetricsData' } );
385
+ return false;
386
+ }
387
+ }
388
+
389
+ async function lamdaAPI1( templateData ) {
390
+ try {
391
+ // let lamdaAPIResultData = {
392
+ // 'status_code': '200',
393
+ // 'emailerCards': {
394
+ // 'footfall': {
395
+ // 'totalCount': 100,
396
+ // 'lastWeekRate': 89, // Daily => sameDayLastWeek
397
+ // 'lastWeekComparisonFlag': false, // Daily => sameDayLastWeekComparisonFlag
398
+ // 'vsMTDAvgRate': 60,
399
+ // 'vsMTDAvgComparisonFlag': true,
400
+ // },
401
+ // 'potentialBuyers': {
402
+ // 'totalCount': 100,
403
+ // 'lastWeekRate': 89, // Daily => sameDayLastWeek
404
+ // 'lastWeekComparisonFlag': true, // Daily => sameDayLastWeekComparisonFlag
405
+ // 'vsMTDAvgRate': 70,
406
+ // 'vsMTDAvgComparisonFlag': false,
407
+ // },
408
+ // 'conversion': {
409
+ // 'totalCount': 100,
410
+ // 'lastWeekRate': 89, // Daily => sameDayLastWeek
411
+ // 'lastWeekComparisonFlag': true, // Daily => sameDayLastWeekComparisonFlag
412
+ // 'vsMTDAvgRate': 70,
413
+ // 'vsMTDAvgComparisonFlag': false,
414
+ // },
415
+ // 'avgDwellTime': {
416
+ // 'dwellTime': 100,
417
+ // 'lastWeekRate': 89, // Daily => sameDayLastWeek
418
+ // 'lastWeekComparisonFlag': true, // Daily => sameDayLastWeekComparisonFlag
419
+ // 'vsMTDAvg': 70,
420
+ // 'vsMTDAvgComparisonFlag': false,
421
+ // },
422
+ // 'operationalHours': {
423
+ // 'openTime': '09:00 AM',
424
+ // 'closeTime': '10:00 PM',
425
+ // },
426
+ // },
427
+ // 'highestFootfall': {
428
+ // 'PeekFootfallBetween': '04:00 PM - 06:00 PM',
429
+ // 'customerCount': 60,
430
+ // 'vsSameDayLastWeekRate': 20,
431
+ // 'vsSameDayLastWeekComparisonFlag': true,
432
+ // },
433
+ // };
434
+ let newTemplateData = templateData;
435
+
436
+ // newTemplateData.dateType = 'daily';
437
+ // newTemplateData.valueType = 'actual';
438
+ // newTemplateData.filterBy = 'actual';
439
+ // newTemplateData.nob = false;
440
+ // newTemplateData.processType = 'conversion';
441
+ // newTemplateData.limit = 20;
442
+ // newTemplateData.offset = 0;
443
+
444
+ const overallLamdaURL = 'https://qkox3l2cg5qhblymaie7qlgku40toykp.lambda-url.ap-south-1.on.aws/';
445
+ let lamdaAPIResultData = await LamdaServiceCall( overallLamdaURL, newTemplateData );
446
+ if ( !lamdaAPIResultData || lamdaAPIResultData.status_code !== '200' ) {
447
+ return '';
448
+ }
449
+ return lamdaAPIResultData;
450
+ } catch ( error ) {
451
+ logger.error( { error: error, message: data, function: 'lamdaAPI1' } );
452
+ return false;
453
+ }
454
+ }
455
+
456
+ async function lamdaAPI2( templateData ) {
457
+ try {
458
+ // let lamdaAPIResultData = {
459
+ // 'status_code': '200',
460
+ // 'emailerCards': {
461
+ // 'footfall': {
462
+ // 'totalCount': 100,
463
+ // 'lastWeekRate': 89,
464
+ // 'lastWeekComparisonFlag': true,
465
+ // 'vsMTDAvgRate': 60,
466
+ // 'vsMTDAvgComparisonFlag': false,
467
+ // },
468
+ // 'potentialBuyers': {
469
+ // 'totalCount': 100,
470
+ // 'lastWeekRate': 89,
471
+ // 'lastWeekComparisonFlag': false,
472
+ // 'vsMTDAvgRate': 70,
473
+ // 'vsMTDAvgComparisonFlag': true,
474
+ // },
475
+ // 'conversion': {
476
+ // 'totalCount': 100,
477
+ // 'lastWeekRate': 89,
478
+ // 'lastWeekComparisonFlag': false,
479
+ // 'vsMTDAvgRate': 70,
480
+ // 'vsMTDAvgComparisonFlag': true,
481
+ // },
482
+ // 'avgDwellTime': {
483
+ // 'dwellTime': 100,
484
+ // 'lastWeekRate': 89,
485
+ // 'lastWeekComparisonFlag': false,
486
+ // 'vsMTDAvgRate': 70,
487
+ // 'vsMTDAvgComparisonFlag': true,
488
+ // },
489
+ // 'operationalHours': {
490
+ // 'lastWeekRate': 89,
491
+ // 'lastWeekComparisonFlag': false,
492
+ // 'lateOpenDays': 3,
493
+ // 'earlyCloseDays': 5,
494
+ // },
495
+ // },
496
+ // 'highestFootfall': {
497
+ // 'PeekFootfallDay': 'Sunday',
498
+ // 'customerCount': 60,
499
+ // 'PeekFootfallBetween': '04:00 PM - 06:00 PM',
500
+ // 'vsLastWeekRate': 20,
501
+ // 'vsLastWeekComparisonFlag': false,
502
+ // },
503
+ // 'highestWeekdayFootfall': {
504
+ // 'PeekFootfallDay': 'Sunday',
505
+ // 'customerCount': 60,
506
+ // 'PeekFootfallBetween': '04:00 PM - 06:00 PM',
507
+ // 'vsLastWeekRate': 20,
508
+ // 'vsLastWeekComparisonFlag': false,
509
+ // },
510
+ // 'storeHeath': {
511
+ // 'storeHeath': 'Good', // ["Red","Amber", "Good"]
512
+ // },
513
+ // };
514
+ let newTemplateData = templateData;
515
+ const overallLamdaURL = 'https://6mgiyf73ktst7nskse7zn5zcxy0ptezt.lambda-url.ap-south-1.on.aws/';
516
+ let lamdaAPIResultData = await LamdaServiceCall( overallLamdaURL, newTemplateData );
517
+ if ( !lamdaAPIResultData || lamdaAPIResultData.status_code !== '200' ) {
518
+ return '';
519
+ }
520
+ return lamdaAPIResultData;
521
+ } catch ( error ) {
522
+ logger.error( { error: error, message: data, function: 'lamdaAPI2' } );
523
+ return false;
524
+ }
525
+ }
526
+
527
+ async function lamdaAPI3( templateData ) {
528
+ try {
529
+ // let lamdaAPIResultData = {
530
+ // 'status_code': '200',
531
+ // 'emailerCards': {
532
+ // 'avgFootfall': {
533
+ // 'totalCount': 100,
534
+ // 'lastWeekRate': 89, // Daily => sameDayLastWeek
535
+ // 'lastWeekComparisonFlag': true, // Daily => sameDayLastWeekComparisonFlag
536
+ // 'vsMTDAvgRate': 60,
537
+ // 'vsMTDAvgComparisonFlag': false,
538
+ // },
539
+ // 'avgPotentialBuyers': {
540
+ // 'totalCount': 100,
541
+ // 'lastWeekRate': 89, // Daily => sameDayLastWeek
542
+ // 'lastWeekComparisonFlag': false, // Daily => sameDayLastWeekComparisonFlag
543
+ // 'vsMTDAvgRate': 70,
544
+ // 'vsMTDAvgComparisonFlag': true,
545
+ // },
546
+ // 'avgConversion': {
547
+ // 'totalCount': 100,
548
+ // 'lastWeekRate': 89, // Daily => sameDayLastWeek
549
+ // 'lastWeekComparisonFlag': false, // Daily => sameDayLastWeekComparisonFlag
550
+ // 'vsMTDAvgRate': 70,
551
+ // 'vsMTDAvgComparisonFlag': true,
552
+ // },
553
+ // 'avgDwellTime': {
554
+ // 'dwellTime': 100,
555
+ // 'lastWeekRate': 89, // Daily => sameDayLastWeek
556
+ // 'lastWeekComparisonFlag': false, // Daily => sameDayLastWeekComparisonFlag
557
+ // 'vsMTDAvgRate': 70,
558
+ // 'vsMTDAvgComparisonFlag': true,
559
+ // },
560
+ // 'avgOperationalHours': {
561
+ // 'avgOperationalHours': 9,
562
+ // 'lastWeekRate': 30,
563
+ // 'lastWeekComparisonFlag': true,
564
+ // },
565
+ // },
566
+ // 'conversionMetrics': {
567
+ // 'highEngagers': {
568
+ // 'storeName': 'LKST2025',
569
+ // 'storeId': '11-2025',
570
+ // 'vsSameDayLastWeekRate': 20,
571
+ // 'vsSameDayLastWeekComparisonFlag': false,
572
+ // 'vsMTDAvgRate': 20,
573
+ // 'vsMTDAvgComparisonFlag': false,
574
+ // },
575
+ // 'lowEngagers': {
576
+ // 'storeName': 'LKST2025',
577
+ // 'storeId': '11-2025',
578
+ // 'vsSameDayLastWeekRate': 20,
579
+ // 'vsSameDayLastWeekComparisonFlag': false,
580
+ // 'vsMTDAvgRate': 20,
581
+ // 'vsMTDAvgComparisonFlag': false,
582
+ // },
583
+ // 'highPotentialBuyers': {
584
+ // 'storeName': 'LKST2025',
585
+ // 'storeId': '11-2025',
586
+ // 'vsSameDayLastWeekRate': 20,
587
+ // 'vsSameDayLastWeekComparisonFlag': false,
588
+ // 'vsMTDAvgRate': 20,
589
+ // 'vsMTDAvgComparisonFlag': false,
590
+ // },
591
+ // 'lowPotentialBuyers': {
592
+ // 'storeName': 'LKST2025',
593
+ // 'storeId': '11-2025',
594
+ // 'vsSameDayLastWeekRate': 20,
595
+ // 'vsSameDayLastWeekComparisonFlag': false,
596
+ // 'vsMTDAvgRate': 20,
597
+ // 'vsMTDAvgComparisonFlag': false,
598
+ // },
599
+ // 'highFootfallCount': {
600
+ // 'storeName': 'LKST2025',
601
+ // 'storeId': '11-2025',
602
+ // 'vsSameDayLastWeekRate': 20,
603
+ // 'vsSameDayLastWeekComparisonFlag': false,
604
+ // 'vsMTDAvgRate': 20,
605
+ // 'vsMTDAvgComparisonFlag': false,
606
+ // },
607
+ // 'lowFootfallCount': {
608
+ // 'storeName': 'LKST2025',
609
+ // 'storeId': '11-2025',
610
+ // 'vsSameDayLastWeekRate': 20,
611
+ // 'vsSameDayLastWeekComparisonFlag': false,
612
+ // 'vsMTDAvgRate': 20,
613
+ // 'vsMTDAvgComparisonFlag': false,
614
+ // },
615
+ // },
616
+ // 'lowOperationalHours': [
617
+ // { 'storeName': 'LKST2025', 'storeId': '11-2025' },
618
+ // { 'storeName': 'LKST2024', 'storeId': '11-2024' },
619
+ // { 'storeName': 'LKST2023', 'storeId': '11-2023' },
620
+ // ],
621
+ // 'fullDownTime': [
622
+ // { 'storeName': 'LKST2025', 'storeId': '11-2025' },
623
+ // { 'storeName': 'LKST2024', 'storeId': '11-2024' },
624
+ // { 'storeName': 'LKST2023', 'storeId': '11-2023' },
625
+ // ],
626
+ // };
627
+ let newTemplateData = templateData;
628
+ const overallLamdaURL = 'https://bqk7ozecu7smfrzyb2hbb5lo540avjkl.lambda-url.ap-south-1.on.aws/';
629
+ let lamdaAPIResultData = await LamdaServiceCall( overallLamdaURL, newTemplateData );
630
+ if ( !lamdaAPIResultData || lamdaAPIResultData.status_code !== 200 ) {
631
+ return '';
632
+ }
633
+ return lamdaAPIResultData;
634
+ } catch ( error ) {
635
+ logger.error( { error: error, message: data, function: 'lamdaAPI2' } );
636
+ return false;
637
+ }
638
+ }
639
+
640
+
641
+ async function lamdaAPI4( templateData ) {
642
+ try {
643
+ // let lamdaAPIResultData = {
644
+ // 'status_code': '200',
645
+ // 'emailerCards': {
646
+ // 'avgFootfall': {
647
+ // 'totalCount': 100,
648
+ // 'lastWeekRate': 89,
649
+ // 'lastWeekComparisonFlag': true,
650
+ // 'vsMTDAvgRate': 60,
651
+ // 'vsMTDAvgComparisonFlag': false,
652
+ // },
653
+ // 'avgPotentialBuyers': {
654
+ // 'totalCount': 100,
655
+ // 'lastWeekRate': 89,
656
+ // 'lastWeekComparisonFlag': false,
657
+ // 'vsMTDAvgRate': 70,
658
+ // 'vsMTDAvgComparisonFlag': true,
659
+ // },
660
+ // 'avgConversion': {
661
+ // 'totalCount': 100,
662
+ // 'lastWeekRate': 89,
663
+ // 'lastWeekComparisonFlag': false,
664
+ // 'vsMTDAvgRate': 70,
665
+ // 'vsMTDAvgComparisonFlag': true,
666
+ // },
667
+ // 'avgDwellTime': {
668
+ // 'dwellTime': 100,
669
+ // 'lastWeekRate': 89,
670
+ // 'lastWeekComparisonFlag': false,
671
+ // 'vsMTDAvgRate': 70,
672
+ // 'vsMTDAvgComparisonFlag': true,
673
+ // },
674
+ // 'avgOperationalHours': {
675
+ // 'lastWeekRate': 89,
676
+ // 'lastWeekComparisonFlag': false,
677
+ // 'lateOpenDays': 3,
678
+ // 'earlyCloseDays': 5,
679
+ // },
680
+ // },
681
+ // 'conversionMetrics': {
682
+ // 'highEngagers': {
683
+ // 'storeName': 'LKST2025',
684
+ // 'storeId': '11-2025',
685
+ // 'vsSameDayLastWeekRate': 20,
686
+ // 'vsSameDayLastWeekComparisonFlag': false,
687
+ // 'vsMTDAvgRate': 20,
688
+ // 'vsMTDAvgComparisonFlag': false,
689
+ // },
690
+ // 'lowEngagers': {
691
+ // 'storeName': 'LKST2025',
692
+ // 'storeId': '11-2025',
693
+ // 'vsSameDayLastWeekRate': 20,
694
+ // 'vsSameDayLastWeekComparisonFlag': false,
695
+ // 'vsMTDAvgRate': 20,
696
+ // 'vsMTDAvgComparisonFlag': false,
697
+ // },
698
+ // 'highPotentialBuyers': {
699
+ // 'storeName': 'LKST2025',
700
+ // 'storeId': '11-2025',
701
+ // 'vsSameDayLastWeekRate': 20,
702
+ // 'vsSameDayLastWeekComparisonFlag': false,
703
+ // 'vsMTDAvgRate': 20,
704
+ // 'vsMTDAvgComparisonFlag': false,
705
+ // },
706
+ // 'lowPotentialBuyers': {
707
+ // 'storeName': 'LKST2025',
708
+ // 'storeId': '11-2025',
709
+ // 'vsSameDayLastWeekRate': 20,
710
+ // 'vsSameDayLastWeekComparisonFlag': false,
711
+ // 'vsMTDAvgRate': 20,
712
+ // 'vsMTDAvgComparisonFlag': false,
713
+ // },
714
+ // 'highFootfallCount': {
715
+ // 'storeName': 'LKST2025',
716
+ // 'storeId': '11-2025',
717
+ // 'vsSameDayLastWeekRate': 20,
718
+ // 'vsSameDayLastWeekComparisonFlag': false,
719
+ // 'vsMTDAvgRate': 20,
720
+ // 'vsMTDAvgComparisonFlag': false,
721
+ // },
722
+ // 'lowFootfallCount': {
723
+ // 'storeName': 'LKST2025',
724
+ // 'storeId': '11-2025',
725
+ // 'vsSameDayLastWeekRate': 20,
726
+ // 'vsSameDayLastWeekComparisonFlag': false,
727
+ // 'vsMTDAvgRate': 20,
728
+ // 'vsMTDAvgComparisonFlag': false,
729
+ // },
730
+ // },
731
+ // 'avgWeekdayFootfall': {
732
+ // 'weekDay': 'Sunday',
733
+ // 'customerCount': 60,
734
+ // 'PeekFootfallBetween': '04:00 PM - 06:00 PM',
735
+ // 'vsLastWeekRate': 20,
736
+ // 'vsLastWeekComparisonFlag': false,
737
+ // },
738
+ // 'lowOperationalHours': [
739
+ // { 'storeName': 'LKST2025', 'storeId': '11-2025' },
740
+ // { 'storeName': 'LKST2024', 'storeId': '11-2024' },
741
+ // { 'storeName': 'LKST2023', 'storeId': '11-2023' },
742
+ // ],
743
+ // 'fullDownTime': [
744
+ // { 'storeName': 'LKST2025', 'storeId': '11-2025' },
745
+ // { 'storeName': 'LKST2024', 'storeId': '11-2024' },
746
+ // { 'storeName': 'LKST2023', 'storeId': '11-2023' },
747
+ // ],
748
+ // };
749
+ let newTemplateData = templateData;
750
+ const overallLamdaURL = 'https://7ozawm36scwmwhh4vzkecqkgv40lpjxt.lambda-url.ap-south-1.on.aws/';
751
+ let lamdaAPIResultData = await LamdaServiceCall( overallLamdaURL, newTemplateData );
752
+ if ( !lamdaAPIResultData || lamdaAPIResultData.status_code !== '200' ) {
753
+ return '';
754
+ }
755
+ return lamdaAPIResultData;
756
+ } catch ( error ) {
757
+ logger.error( { error: error, message: data, function: 'lamdaAPI4' } );
758
+ return false;
759
+ }
760
+ }
761
+
762
+ async function hourlyChartLamdaAPI( templateData ) {
763
+ try {
764
+ // console.log( 'hourlyChartLamdaAPI' );
765
+ const bucketDetails = JSON.parse( process.env.BUCKET );
766
+ let hourlyChartUrl;
767
+
768
+ // let lamdaAPIResultData = {
769
+ // 'status_code': 200,
770
+ // 'hourlyData': [
771
+ // { rate: 6, count: 22, time: '01:00' },
772
+ // { rate: 1, count: 2, time: '03:00' },
773
+ // { rate: 1, count: 3, time: '05:00' },
774
+ // { rate: 10, count: 36, time: '07:00' },
775
+ // { rate: 6, count: 21, time: '09:00' },
776
+ // { rate: 8, count: 32, time: '11:00' },
777
+ // { rate: 7, count: 28, time: '13:00' },
778
+ // { rate: 15, count: 56, time: '15:00' },
779
+ // { rate: 12, count: 47, time: '17:00' },
780
+ // { rate: 12, count: 47, time: '19:00' },
781
+ // { rate: 10, count: 36, time: '21:00' },
782
+ // { rate: 13, count: 48, time: '23:59' },
783
+ // ],
784
+ // };
785
+ // return lamdaAPIResultData;
786
+
787
+ let newTemplateData = templateData;
788
+ newTemplateData.valueType = 'actual';
789
+ newTemplateData.nob = false;
790
+ newTemplateData.processType = 'footfall';
791
+ newTemplateData.limit = 20;
792
+ newTemplateData.offset = 0;
793
+
794
+ const overallLamdaURL = 'https://okpiyyhjvzbopvufnburhf3ase0hjfnl.lambda-url.ap-south-1.on.aws/';
795
+ let lamdaAPIResultData = await LamdaServiceCall( overallLamdaURL, newTemplateData );
796
+ if ( !lamdaAPIResultData || lamdaAPIResultData.status_code !== 200 ) {
797
+ return hourlyChartUrl;
798
+ }
799
+
800
+ let hourlyData = lamdaAPIResultData.hourlyData || [];
801
+ let hourlyChartImagePath = await overallHourlyChart( hourlyData );
802
+
803
+ // Upload hourly chart
804
+ const hourlyChartParams = {
805
+ Bucket: bucketDetails.mailer,
806
+ Key: `${dayjs().format( 'YYYY-MM-DD-HH:mm:ss' )}/mail/emailHourlyUserChart.png`,
807
+ ContentType: 'image/png',
808
+ body: hourlyChartImagePath,
809
+ };
810
+
811
+ const hourlyChartResponse = await fileUpload( hourlyChartParams );
812
+ if ( hourlyChartResponse && hourlyChartResponse.Key ) {
813
+ hourlyChartUrl = await signedUrl( {
814
+ Bucket: bucketDetails.mailer,
815
+ file_path: hourlyChartResponse.Key,
816
+ } );
817
+ } else {
818
+ throw new Error( 'S3 Upload failed for hourly chart' );
819
+ }
820
+
821
+ return hourlyChartUrl;
822
+ } catch ( error ) {
823
+ // console.log( ' error hourlyChartLamdaAPI=>', error );
824
+ logger.error( { error: error, function: 'hourlyChartLamdaAPI' } );
825
+ return false;
826
+ }
827
+ }
828
+
829
+ async function dailyChartLamdaAPI( templateData ) {
830
+ try {
831
+ const bucketDetails = JSON.parse( process.env.BUCKET );
832
+ let chartUrl;
833
+
834
+ // let lamdaAPIResultData = {
835
+ // 'status_code': '200',
836
+ // 'storeData': [ { count: 378, storeName: 'VS T1', storeId: '430-8' } ],
837
+ // 'totalCount': 1,
838
+ // };
839
+ // return lamdaAPIResultData;
840
+
841
+ let newTemplateData = templateData;
842
+ newTemplateData.valueType = 'actual';
843
+ newTemplateData.nob = false;
844
+ newTemplateData.processType = 'footfall';
845
+ newTemplateData.limit = 20;
846
+ newTemplateData.offset = 0;
847
+ const overallLamdaURL = 'https://eboa7d5kzefml2srjo567sbxoe0bzmog.lambda-url.ap-south-1.on.aws/';
848
+ let lamdaAPIResultData = await LamdaServiceCall( overallLamdaURL, newTemplateData );
849
+ // console.log( 'newTemplateData =>', newTemplateData );
850
+ // console.log( 'lamdaAPIResultData =>', lamdaAPIResultData );
851
+ if ( !lamdaAPIResultData || lamdaAPIResultData.status_code !== '200' ) {
852
+ return chartUrl;
853
+ }
854
+
855
+ let dailyData = lamdaAPIResultData.storeData || [];
856
+ let chartImagePath = await overallChart( dailyData );
857
+ // Upload overall chart
858
+ const overallChartParams = {
859
+ Bucket: bucketDetails.mailer,
860
+ Key: `${dayjs().format( 'YYYY-MM-DD-HH:mm:ss' )}/mail/emaildailyUserChart.png`,
861
+ ContentType: 'image/png',
862
+ body: chartImagePath,
863
+ };
864
+ const overallChartResponse = await fileUpload( overallChartParams );
865
+ if ( overallChartResponse && overallChartResponse.Key ) {
866
+ chartUrl = await signedUrl( {
867
+ Bucket: bucketDetails.mailer,
868
+ file_path: overallChartResponse.Key,
869
+ } );
870
+ } else {
871
+ throw new Error( 'S3 Upload failed for overall chart' );
872
+ }
873
+
874
+ return chartUrl;
875
+ } catch ( error ) {
876
+ logger.error( { error: error, message: data, function: 'dailyChartLamdaAPI' } );
877
+ return false;
878
+ }
879
+ }
880
+
881
+ async function dailyFootfallTrendChartLamdaAPI( templateData ) {
882
+ try {
883
+ const bucketDetails = JSON.parse( process.env.BUCKET );
884
+ let chartUrl;
885
+
886
+ // let lamdaAPIResultData = {
887
+ // 'fromDate': '2025-01-02',
888
+ // 'status_code': '200',
889
+ // 'toDate': '2025-01-08',
890
+ // 'totalCount': 7,
891
+ // 'footfallTrendData': [
892
+ // {
893
+ // 'engagers_count': 39,
894
+ // 'footfallGroupCount': 37,
895
+ // 'down_time': '0%',
896
+ // 'conversion_count': 16,
897
+ // 'bounced_footfall_count_avg': 1,
898
+ // 'engagersGroupCount': 36,
899
+ // 'storeId': '11-1002',
900
+ // 'conversionRate': 40,
901
+ // 'bounced_count': 1,
902
+ // 'footfall_count': 40,
903
+ // 'potentialBuyers_count': 36,
904
+ // 'footfall_count_avg': 40,
905
+ // 'avgDwellTime': 21,
906
+ // 'engagers_count_avg': 39,
907
+ // 'storeName': '2025-01-03',
908
+ // 'missedOpportunity_count_avg': 4,
909
+ // 'missedOpportunity_count': 4,
910
+ // },
911
+ // {
912
+ // 'engagers_count': 51,
913
+ // 'footfallGroupCount': 48,
914
+ // 'down_time': '0%',
915
+ // 'conversion_count': 31,
916
+ // 'bounced_footfall_count_avg': 6,
917
+ // 'engagersGroupCount': 43,
918
+ // 'storeId': '11-1002',
919
+ // 'conversionRate': 54,
920
+ // 'bounced_count': 6,
921
+ // 'footfall_count': 57,
922
+ // 'potentialBuyers_count': 43,
923
+ // 'footfall_count_avg': 57,
924
+ // 'avgDwellTime': 32,
925
+ // 'engagers_count_avg': 51,
926
+ // 'storeName': '2025-01-08',
927
+ // 'missedOpportunity_count_avg': 1,
928
+ // 'missedOpportunity_count': 1,
929
+ // },
930
+ // {
931
+ // 'engagers_count': 79,
932
+ // 'footfallGroupCount': 80,
933
+ // 'down_time': '1%',
934
+ // 'conversion_count': 44,
935
+ // 'bounced_footfall_count_avg': 8,
936
+ // 'engagersGroupCount': 72,
937
+ // 'storeId': '11-1002',
938
+ // 'conversionRate': 51,
939
+ // 'bounced_count': 8,
940
+ // 'footfall_count': 87,
941
+ // 'potentialBuyers_count': 72,
942
+ // 'footfall_count_avg': 87,
943
+ // 'avgDwellTime': 38,
944
+ // 'engagers_count_avg': 79,
945
+ // 'storeName': '2025-01-06',
946
+ // 'missedOpportunity_count_avg': 10,
947
+ // 'missedOpportunity_count': 10,
948
+ // },
949
+ // {
950
+ // 'engagers_count': 70,
951
+ // 'footfallGroupCount': 63,
952
+ // 'down_time': '0%',
953
+ // 'conversion_count': 33,
954
+ // 'bounced_footfall_count_avg': 1,
955
+ // 'engagersGroupCount': 62,
956
+ // 'storeId': '11-1002',
957
+ // 'conversionRate': 46,
958
+ // 'bounced_count': 1,
959
+ // 'footfall_count': 71,
960
+ // 'potentialBuyers_count': 62,
961
+ // 'footfall_count_avg': 71,
962
+ // 'avgDwellTime': 35,
963
+ // 'engagers_count_avg': 70,
964
+ // 'storeName': '2025-01-05',
965
+ // 'missedOpportunity_count_avg': 5,
966
+ // 'missedOpportunity_count': 5,
967
+ // },
968
+ // {
969
+ // 'engagers_count': 45,
970
+ // 'footfallGroupCount': 43,
971
+ // 'down_time': '1%',
972
+ // 'conversion_count': 23,
973
+ // 'bounced_footfall_count_avg': 2,
974
+ // 'engagersGroupCount': 41,
975
+ // 'storeId': '11-1002',
976
+ // 'conversionRate': 49,
977
+ // 'bounced_count': 2,
978
+ // 'footfall_count': 47,
979
+ // 'potentialBuyers_count': 41,
980
+ // 'footfall_count_avg': 47,
981
+ // 'avgDwellTime': 32,
982
+ // 'engagers_count_avg': 45,
983
+ // 'storeName': '2025-01-02',
984
+ // 'missedOpportunity_count_avg': 7,
985
+ // 'missedOpportunity_count': 7,
986
+ // },
987
+ // {
988
+ // 'engagers_count': 70,
989
+ // 'footfallGroupCount': 68,
990
+ // 'down_time': '1%',
991
+ // 'conversion_count': 36,
992
+ // 'bounced_footfall_count_avg': 8,
993
+ // 'engagersGroupCount': 60,
994
+ // 'storeId': '11-1002',
995
+ // 'conversionRate': 46,
996
+ // 'bounced_count': 8,
997
+ // 'footfall_count': 78,
998
+ // 'potentialBuyers_count': 60,
999
+ // 'footfall_count_avg': 78,
1000
+ // 'avgDwellTime': 31,
1001
+ // 'engagers_count_avg': 70,
1002
+ // 'storeName': '2025-01-04',
1003
+ // 'missedOpportunity_count_avg': 5,
1004
+ // 'missedOpportunity_count': 5,
1005
+ // },
1006
+ // {
1007
+ // 'engagers_count': 60,
1008
+ // 'footfallGroupCount': 61,
1009
+ // 'down_time': '1%',
1010
+ // 'conversion_count': 23,
1011
+ // 'bounced_footfall_count_avg': 6,
1012
+ // 'engagersGroupCount': 55,
1013
+ // 'storeId': '11-1002',
1014
+ // 'conversionRate': 35,
1015
+ // 'bounced_count': 6,
1016
+ // 'footfall_count': 66,
1017
+ // 'potentialBuyers_count': 55,
1018
+ // 'footfall_count_avg': 66,
1019
+ // 'avgDwellTime': 28,
1020
+ // 'engagers_count_avg': 60,
1021
+ // 'storeName': '2025-01-07',
1022
+ // 'missedOpportunity_count_avg': 6,
1023
+ // 'missedOpportunity_count': 6,
1024
+ // },
1025
+ // ],
1026
+ // };
1027
+ // return lamdaAPIResultData;
1028
+
1029
+ let newTemplateData = templateData;
1030
+ // newTemplateData.dateType = 'weekly';
1031
+ let processTypeValue = 'engagers';
1032
+ if ( templateData.featureConfigs.conversionCalculation !== 'engagers-count' ) {
1033
+ processTypeValue='potentialBuyer';
1034
+ }
1035
+ let filterByTypeValue = 'average';
1036
+ if ( templateData.templateType == 'daily' ) {
1037
+ filterByTypeValue='actual';
1038
+ }
1039
+ newTemplateData.dateType = templateData.templateType;
1040
+ newTemplateData.valueType = 'actual';
1041
+ newTemplateData.filterBy = filterByTypeValue;
1042
+ newTemplateData.nob = false;
1043
+ newTemplateData.processType = processTypeValue;
1044
+ newTemplateData.limit = 31;
1045
+ newTemplateData.offset = 0;
1046
+ const overallLamdaURL = 'https://x6sjlqwaqd64kyioxhwrwfesbm0jjitx.lambda-url.ap-south-1.on.aws/';
1047
+ let lamdaAPIResultData = await LamdaServiceCall( overallLamdaURL, newTemplateData );
1048
+ if ( !lamdaAPIResultData || lamdaAPIResultData.status_code !== '200' ) {
1049
+ return chartUrl;
1050
+ }
1051
+
1052
+ let dailyData = lamdaAPIResultData.footfallTrendData || [];
1053
+ let chartImagePath = await footfallTrend( dailyData );
1054
+ // Upload overall chart
1055
+ const overallChartParams = {
1056
+ Bucket: bucketDetails.mailer,
1057
+ Key: `${dayjs().format( 'YYYY-MM-DD-HH:mm:ss' )}/mail/emailweeklyUserChart.png`,
1058
+ ContentType: 'image/png',
1059
+ body: chartImagePath,
1060
+ };
1061
+ const overallChartResponse = await fileUpload( overallChartParams );
1062
+ if ( overallChartResponse && overallChartResponse.Key ) {
1063
+ chartUrl = await signedUrl( {
1064
+ Bucket: bucketDetails.mailer,
1065
+ file_path: overallChartResponse.Key,
1066
+ } );
1067
+ } else {
1068
+ throw new Error( 'S3 Upload failed for overall chart' );
1069
+ }
1070
+
1071
+ return chartUrl;
1072
+ } catch ( error ) {
1073
+ logger.error( { error: error, message: data, function: 'dailyFootfallTrendChartLamdaAPI' } );
1074
+ return false;
1075
+ }
1076
+ }
1077
+
1078
+ async function getLamdaChartData( templateData ) {
1079
+ try {
1080
+ let resultLamdaChartData = {
1081
+ dailyData: '',
1082
+ hourlyData: '',
1083
+ };
1084
+ if ( templateData.allStores.length == 1 && templateData.templateType == 'daily' ) {
1085
+ // // Call Lamda API No 1 Single Store Single Date
1086
+ const [ chartImagePath, hourlyChartImagePath ] = await Promise.all( [
1087
+ dailyChartLamdaAPI( templateData ), // For the overall chart (col-8)
1088
+ hourlyChartLamdaAPI( templateData ), // For the hourly chart (col-4)
1089
+ ] );
1090
+ resultLamdaChartData.dailyData = chartImagePath;
1091
+ resultLamdaChartData.hourlyData = hourlyChartImagePath;
1092
+ return resultLamdaChartData;
1093
+ } else {
1094
+ // Default Value
1095
+ resultLamdaChartData.dailyFootfallTrendData = await dailyFootfallTrendChartLamdaAPI( templateData );
1096
+ return resultLamdaChartData;
1097
+ }
1098
+ } catch ( error ) {
1099
+ // console.log( 'error =>', error );
1100
+ logger.error( { error: error, function: 'getLamdaChartData' } );
1101
+ return false;
1102
+ }
1103
+ }
1104
+
1105
+ async function emailerSendEmail( storeData, clientData, lamdaMetrics, lamdaCharts, templateData ) {
1106
+ try {
1107
+ let hbsFileName = '../hbs/dailyMailerSingle.hbs';
1108
+ if ( templateData.allStores.length == 1 && templateData.templateType == 'daily' ) {
1109
+ // // Call Lamda API No 1 Single Store Single Date
1110
+ hbsFileName = '../hbs/dailyMailerSingle.hbs';
1111
+ } else if ( templateData.allStores.length > 1 && templateData.templateType == 'daily' ) {
1112
+ // // Call Lamda API No 3 Multiple Store Single Date
1113
+ hbsFileName = '../hbs/dailyMailerMultiple.hbs';
1114
+ } else if ( templateData.allStores.length == 1 && templateData.templateType == 'weekly' ) {
1115
+ // // Call Lamda API No 2 Single Store Multiple Date
1116
+ hbsFileName = '../hbs/weeklyMailerSingle.hbs';
1117
+ } else if ( templateData.allStores.length > 1 && templateData.templateType == 'weekly' ) {
1118
+ // // Call Lamda API No 4 Multiple Store Multiple Date
1119
+ hbsFileName = '../hbs/weeklyMailerMultiple.hbs';
1120
+ } else if ( templateData.allStores.length == 1 && templateData.templateType == 'monthly' ) {
1121
+ // // Call Lamda API No 5 Single Store Multiple Date
1122
+ hbsFileName = '../hbs/dailyMailerSingle.hbs';
1123
+ } else if ( templateData.allStores.length > 1 && templateData.templateType == 'monthly' ) {
1124
+ // // Call Lamda API No 6 Multiple Store Multiple Date
1125
+ hbsFileName = '../hbs/dailyMailerSingle.hbs';
1126
+ } else {
1127
+ // Default Value
1128
+ }
1129
+ // Prepare email with Handlebars template
1130
+ const fileContent = readFileSync(
1131
+ join( __dirname, hbsFileName ),
1132
+ 'utf8',
1133
+ );
1134
+ const htmlContent = handlebars.compile( fileContent );
1135
+
1136
+ const emailVars = {
1137
+ // email: inputData.email,
1138
+ clientName: clientData.clientName,
1139
+ domain: JSON.parse( process.env.URL ).apiDomain,
1140
+ overallChartImage: lamdaCharts.dailyData, // Use the S3 URL for the overall chart
1141
+ hourlyChartImage: lamdaCharts.hourlyData, // Use the S3 URL for the hourly chart
1142
+ lamdaMetrics: lamdaMetrics,
1143
+ storeData: storeData,
1144
+ templateData: templateData,
1145
+ chartImage: lamdaCharts.dailyFootfallTrendData,
1146
+ featureConfigs: {
1147
+ open: clientData?.featureConfigs?.open || '10:00:00',
1148
+ close: clientData?.featureConfigs?.close || '23:00:00',
1149
+ billableCalculation: clientData?.featureConfigs?.billableCalculation || 'engagers-count',
1150
+ conversionCalculation: clientData?.featureConfigs?.conversionCalculation || 'engagers-count',
1151
+ missedOpportunityCalculation: clientData?.featureConfigs?.missedOpportunityCalculation || 'engagers-conversion',
1152
+ bufferTime: 30,
1153
+ },
1154
+ };
1155
+
1156
+ const html = htmlContent( { data: emailVars } );
1157
+
1158
+ // Prepare email subject and send the email
1159
+ const subject = `Footfall Trend Report for ${clientData.clientName}`;
1160
+ const ses = JSON.parse( process.env.SES );
1161
+
1162
+ // templateData.userEmail
1163
+ await sendEmailWithSES(
1164
+ 'nafila@tangotech.co.in',
1165
+ subject,
1166
+ html,
1167
+ '',
1168
+ ses.adminEmail,
1169
+ );
1170
+
1171
+ // return res.sendSuccess( { result: 'EMAIL-SENT' } );
1172
+
1173
+ return true;
1174
+ } catch ( error ) {
1175
+ logger.error( { error: error, function: 'emailerSendEmail' } );
1176
+ return false;
1177
+ }
1178
+ }
1179
+
1180
+ const overallChart = async ( data ) => {
1181
+ // console.log( 'Generating footfall chart...' );
1182
+
1183
+ // let chartImagePath = path.resolve( __dirname, 'emailchart-image.png' );
1184
+ // console.log( 'Chart Image Path:', chartImagePath );
1185
+
1186
+ const browser = await puppeteer.launch( { headless: true } );
1187
+ const page = await browser.newPage();
1188
+
1189
+ const chartHtmlContent = `
1190
+ <!DOCTYPE html>
1191
+ <html>
1192
+ <head>
1193
+ <style>
1194
+ #testGraphics {
1195
+ width: 100%;
1196
+ height: 600px;
1197
+ }
1198
+ </style>
1199
+ <script src="https://cdn.amcharts.com/lib/5/index.js"></script>
1200
+ <script src="https://cdn.amcharts.com/lib/5/xy.js"></script>
1201
+ <script src="https://cdn.amcharts.com/lib/5/themes/Animated.js"></script>
1202
+ </head>
1203
+ <body>
1204
+ <div id="testGraphics"></div>
1205
+ <script>
1206
+ am5.ready(function() {
1207
+ const root = am5.Root.new("testGraphics");
1208
+ root.setThemes([am5themes_Animated.new(root)]);
1209
+
1210
+ const chart = root.container.children.push(
1211
+ am5xy.XYChart.new(root, {
1212
+ panX: true,
1213
+ panY: true,
1214
+ wheelX: "panX",
1215
+ wheelY: "zoomX",
1216
+ pinchZoomX: true,
1217
+ paddingLeft: 0,
1218
+ paddingRight: 0,
1219
+ width: am5.percent(100),
1220
+ })
1221
+ );
1222
+
1223
+ const cursor = chart.set("cursor", am5xy.XYCursor.new(root, {}));
1224
+ cursor.lineY.set("visible", false);
1225
+
1226
+ const xRenderer = am5xy.AxisRendererX.new(root, {
1227
+ minGridDistance: 30,
1228
+ minorGridEnabled: true,
1229
+ });
1230
+
1231
+ xRenderer.labels.template.setAll({
1232
+ rotation: -45,
1233
+ centerY: am5.p50,
1234
+ centerX: am5.p100,
1235
+ paddingRight: 5,
1236
+ });
1237
+
1238
+ const xAxis = chart.xAxes.push(
1239
+ am5xy.CategoryAxis.new(root, {
1240
+ categoryField: "storeName",
1241
+ renderer: xRenderer,
1242
+ })
1243
+ );
1244
+
1245
+ const yRenderer = am5xy.AxisRendererY.new(root, {
1246
+ strokeOpacity: 0.1,
1247
+ });
1248
+
1249
+ const yAxis = chart.yAxes.push(
1250
+ am5xy.ValueAxis.new(root, {
1251
+ renderer: yRenderer,
1252
+ })
1253
+ );
1254
+
1255
+ const formattedData = ${JSON.stringify( data )}.map((item) => ({
1256
+ storeName: item.storeName,
1257
+ value: Number(item.count),
1258
+ }));
1259
+
1260
+ const series = chart.series.push(
1261
+ am5xy.ColumnSeries.new(root, {
1262
+ xAxis: xAxis,
1263
+ yAxis: yAxis,
1264
+ valueYField: "value",
1265
+ categoryXField: "storeName",
1266
+ })
1267
+ );
1268
+
1269
+ xAxis.data.setAll(formattedData);
1270
+ series.data.setAll(formattedData);
1271
+
1272
+ series.columns.template.setAll({ strokeOpacity: 0 });
1273
+
1274
+ series.appear(1000);
1275
+ chart.appear(1000, 100);
1276
+ });
1277
+ </script>
1278
+ </body>
1279
+ </html>
1280
+ `;
1281
+ let chartBuffer;
1282
+ try {
1283
+ // console.log( 'overallChart... Try' );
1284
+ await page.setContent( chartHtmlContent, { waitUntil: 'domcontentloaded' } );
1285
+ await page.waitForSelector( '#testGraphics', { visible: true } );
1286
+ chartBuffer = await page.screenshot( { encoding: 'binary' } );
1287
+ // console.log( 'Screenshot saved to:', chartImagePath );
1288
+ } catch ( err ) {
1289
+ // console.error( 'Error generating chart screenshot:', err );
1290
+ throw new Error( 'Failed to generate chart screenshot' );
1291
+ } finally {
1292
+ await browser.close();
1293
+ }
1294
+ // console.log( 'chartBuffer daily... Try', chartBuffer );
1295
+ return chartBuffer;
1296
+ };
1297
+
1298
+ const overallHourlyChart = async ( data ) => {
1299
+ // console.log( 'Generating heatmap chart...' );
1300
+
1301
+ const browser = await puppeteer.launch( { headless: true } );
1302
+ const page = await browser.newPage();
1303
+
1304
+ const chartHtmlContent = `
1305
+ <!DOCTYPE html>
1306
+ <html>
1307
+ <head>
1308
+ <style>
1309
+ #heatmapchart {
1310
+ width: 100%;
1311
+ height: 600px;
1312
+ }
1313
+ </style>
1314
+ <script src="https://cdn.amcharts.com/lib/5/index.js"></script>
1315
+ <script src="https://cdn.amcharts.com/lib/5/xy.js"></script>
1316
+ <script src="https://cdn.amcharts.com/lib/5/themes/Animated.js"></script>
1317
+ <script src="https://cdn.amcharts.com/lib/5/themes/Responsive.js"></script>
1318
+ </head>
1319
+ <body>
1320
+ <div id="heatmapchart"></div>
1321
+ <script>
1322
+ am5.ready(function () {
1323
+ const root = am5.Root.new("heatmapchart");
1324
+ root.setThemes([am5themes_Animated.new(root), am5themes_Responsive.new(root)]);
1325
+
1326
+ // Create chart
1327
+ const chart = root.container.children.push(
1328
+ am5xy.XYChart.new(root, {
1329
+ panX: false,
1330
+ panY: false,
1331
+ wheelX: "none",
1332
+ wheelY: "none",
1333
+ paddingLeft: 0,
1334
+ layout: root.verticalLayout,
1335
+ })
1336
+ );
1337
+
1338
+ // Create axes and renderers
1339
+ const yRenderer = am5xy.AxisRendererY.new(root, {
1340
+ minGridDistance: 20,
1341
+ inversed: true,
1342
+ opposite: true,
1343
+ minorGridEnabled: true,
1344
+ });
1345
+
1346
+ yRenderer.grid.template.set("visible", false);
1347
+
1348
+ const yAxis = chart.yAxes.push(
1349
+ am5xy.CategoryAxis.new(root, {
1350
+ renderer: yRenderer,
1351
+ categoryField: "time",
1352
+ })
1353
+ );
1354
+
1355
+ const xRenderer = am5xy.AxisRendererX.new(root, {
1356
+ minGridDistance: 30,
1357
+ opposite: true,
1358
+ minorGridEnabled: true,
1359
+ });
1360
+
1361
+ xRenderer.grid.template.set("visible", false);
1362
+
1363
+ const xAxis = chart.xAxes.push(
1364
+ am5xy.CategoryAxis.new(root, {
1365
+ renderer: xRenderer,
1366
+ categoryField: "weekday",
1367
+ })
1368
+ );
1369
+
1370
+ // Create series
1371
+ const series = chart.series.push(
1372
+ am5xy.ColumnSeries.new(root, {
1373
+ calculateAggregates: true,
1374
+ stroke: am5.color(0xffffff),
1375
+ clustered: false,
1376
+ xAxis: xAxis,
1377
+ yAxis: yAxis,
1378
+ categoryXField: "weekday",
1379
+ categoryYField: "time",
1380
+ valueField: "rate",
1381
+ })
1382
+ );
1383
+
1384
+ series.columns.template.setAll({
1385
+ tooltipText: "{rate}",
1386
+ strokeOpacity: 1,
1387
+ strokeWidth: 2,
1388
+ width: am5.percent(100),
1389
+ height: am5.percent(100),
1390
+ fill: am5.color(0xffffff),
1391
+ });
1392
+
1393
+ // Apply range-based color rules
1394
+ series.columns.template.adapters.add("fill", (fill, target) => {
1395
+ const dataItem = target.dataItem?.dataContext;
1396
+ if (dataItem && dataItem.rate !== undefined) {
1397
+ const rate = dataItem.rate;
1398
+ if (rate >= 0 && rate <= 10) {
1399
+ return am5.color(0xEAF8FF);
1400
+ } else if (rate > 10 && rate <= 20) {
1401
+ return am5.color(0xCCE9FE);
1402
+ } else if (rate > 20 && rate <= 30) {
1403
+ return am5.color(0xB0D6FC);
1404
+ } else if (rate > 30 && rate <= 40) {
1405
+ return am5.color(0x94C2FA);
1406
+ } else if (rate > 40 && rate <= 55) {
1407
+ return am5.color(0x78A9F8);
1408
+ } else if (rate > 55 && rate <= 75) {
1409
+ return am5.color(0x99D2FD);
1410
+ } else if (rate > 75 && rate <= 90) {
1411
+ return am5.color(0x66BCFD);
1412
+ } else if (rate > 90 && rate <= 100) {
1413
+ return am5.color(0x3460F3);
1414
+ } else {
1415
+ return am5.color(0xFFF);
1416
+ }
1417
+ }
1418
+ return fill;
1419
+ });
1420
+
1421
+ series.bullets.push(() =>
1422
+ am5.Bullet.new(root, {
1423
+ sprite: am5.Label.new(root, {
1424
+ text: "{rate.formatNumber('#.#')}%",
1425
+ fill: am5.color(0x000000),
1426
+ centerY: am5.p50,
1427
+ centerX: am5.p50,
1428
+ populateText: true,
1429
+ fontSize: 16,
1430
+ fontWeight: "bold",
1431
+ }),
1432
+ })
1433
+ );
1434
+
1435
+ // Format data
1436
+ const formattedData = ${JSON.stringify( data )}.map((item) => ({
1437
+ ...item,
1438
+ weekday: "Hourly Data",
1439
+ }));
1440
+
1441
+ series.data.setAll(formattedData);
1442
+
1443
+ // Set axis data
1444
+ const times = Array.from(new Set(formattedData.map((item) => item.time)));
1445
+
1446
+ xAxis.data.setAll([{ weekday: "Hourly Data" }]);
1447
+ yAxis.data.setAll(times.map((time) => ({ time })));
1448
+
1449
+ chart.appear(1000, 100);
1450
+ });
1451
+ </script>
1452
+ </body>
1453
+ </html>
1454
+ `;
1455
+
1456
+ let chartBuffer;
1457
+ try {
1458
+ await page.setContent( chartHtmlContent, { waitUntil: 'domcontentloaded' } );
1459
+ await page.waitForSelector( '#heatmapchart', { visible: true } );
1460
+ chartBuffer = await page.screenshot( { encoding: 'binary' } );
1461
+ } catch ( err ) {
1462
+ // console.error( 'Error generating chart screenshot:', err );
1463
+ throw new Error( 'Failed to generate chart screenshot' );
1464
+ } finally {
1465
+ await browser.close();
1466
+ }
1467
+ // console.log( 'chartBuffer hourly... Try', chartBuffer );
1468
+ return chartBuffer;
1469
+ };
1470
+
1471
+ const footfallTrend = async ( data ) => {
1472
+ // console.log( 'Generating footfall chart...' );
1473
+
1474
+ const browser = await puppeteer.launch( { headless: true } );
1475
+ const page = await browser.newPage();
1476
+
1477
+ const chartHtmlContent = `
1478
+ <!DOCTYPE html>
1479
+ <html>
1480
+ <head>
1481
+ <style>
1482
+ #Footfalltrend {
1483
+ width: 100%;
1484
+ height: 600px;
1485
+ }
1486
+ </style>
1487
+ <script src="https://cdn.amcharts.com/lib/5/index.js"></script>
1488
+ <script src="https://cdn.amcharts.com/lib/5/xy.js"></script>
1489
+ <script src="https://cdn.amcharts.com/lib/5/themes/Animated.js"></script>
1490
+ </head>
1491
+ <body>
1492
+ <div id="Footfalltrend"></div>
1493
+ <script>
1494
+ am5.ready(function() {
1495
+ const root = am5.Root.new('Footfalltrend');
1496
+ root.setThemes([am5themes_Animated.new(root)]);
1497
+
1498
+ const chart = root.container.children.push(
1499
+ am5xy.XYChart.new(root, {
1500
+ panX: false,
1501
+ panY: false,
1502
+ wheelX: 'panX',
1503
+ wheelY: 'zoomX',
1504
+ layout: root.verticalLayout
1505
+ })
1506
+ );
1507
+
1508
+ chart.set("scrollbarX", am5.Scrollbar.new(root, {
1509
+ orientation: "horizontal"
1510
+ }));
1511
+
1512
+ const xRenderer = am5xy.AxisRendererX.new(root, {
1513
+ minGridDistance: 20,
1514
+ minorGridEnabled: true,
1515
+ });
1516
+
1517
+ xRenderer.labels.template.setAll({
1518
+ rotation: -45,
1519
+ centerY: am5.p50,
1520
+ centerX: am5.p100,
1521
+ paddingRight: 5,
1522
+ });
1523
+
1524
+ const xAxis = chart.xAxes.push(
1525
+ am5xy.CategoryAxis.new(root, {
1526
+ categoryField: "storeName",
1527
+ renderer: xRenderer,
1528
+ tooltip: am5.Tooltip.new(root, { labelText: "{fullStoreName}" })
1529
+ })
1530
+ );
1531
+
1532
+ const formattedData = ${JSON.stringify( data )}.map((item) => ({
1533
+ fullStoreName: item.storeName,
1534
+ storeName: item.storeName,
1535
+ footfall: Number(item.footfall_count),
1536
+ conversion: Number(item.conversion_count),
1537
+ bounced: Number(item.bounced_count),
1538
+ engagers: Number(item.engagers_count),
1539
+ missedopportunities: Number(item.missedOpportunity_count),
1540
+ }));
1541
+
1542
+ formattedData.sort((a, b) => {
1543
+ const dateA = new Date(a.storeName);
1544
+ const dateB = new Date(b.storeName);
1545
+ return dateA - dateB;
1546
+ });
1547
+
1548
+ xAxis.data.setAll(formattedData);
1549
+
1550
+ const yAxis = chart.yAxes.push(
1551
+ am5xy.ValueAxis.new(root, {
1552
+ min: 0,
1553
+ renderer: am5xy.AxisRendererY.new(root, {})
1554
+ })
1555
+ );
1556
+
1557
+ yAxis.children.unshift(am5.Label.new(root, {
1558
+ rotation: -90,
1559
+ text: "Footfall",
1560
+ y: am5.p50,
1561
+ centerX: am5.p50,
1562
+ fontFamily: "Arial",
1563
+ fontSize: "14px",
1564
+ fill: am5.color(0x000000)
1565
+ }));
1566
+
1567
+ const createColumnSeries = (name, field, color) => {
1568
+ const series = chart.series.push(
1569
+ am5xy.ColumnSeries.new(root, {
1570
+ name: name,
1571
+ xAxis: xAxis,
1572
+ yAxis: yAxis,
1573
+ valueYField: field,
1574
+ categoryXField: "storeName",
1575
+ fill: color,
1576
+ stroke: color,
1577
+ stacked: true,
1578
+ })
1579
+ );
1580
+
1581
+ series.data.setAll(formattedData);
1582
+
1583
+ series.bullets.push(() => {
1584
+ return am5.Bullet.new(root, {
1585
+ sprite: am5.Label.new(root, {
1586
+ text: "{valueY}",
1587
+ fill: root.interfaceColors.get("alternativeText"),
1588
+ centerY: am5.p50,
1589
+ centerX: am5.p50,
1590
+ populateText: true,
1591
+ fontSize: 12 // Reduce font size
1592
+ })
1593
+ });
1594
+ });
1595
+
1596
+ series.columns.template.setAll({
1597
+ tooltipText: "{name}: {valueY}",
1598
+ width: am5.percent(90),
1599
+ tooltipY: 0,
1600
+ });
1601
+
1602
+ series.appear(1000);
1603
+ return series;
1604
+ };
1605
+
1606
+ createColumnSeries("Footfall", "footfall", am5.color(0x67b7dc));
1607
+ createColumnSeries("Bounced", "bounced", am5.color(0x6794dc));
1608
+ createColumnSeries("Engagers", "engagers", am5.color(0x6771dc));
1609
+
1610
+ const createLineSeries = (name, fieldName, xAxis, yAxis, color) => {
1611
+ const series = chart.series.push(
1612
+ am5xy.LineSeries.new(root, {
1613
+ name: name,
1614
+ xAxis: xAxis,
1615
+ yAxis: yAxis,
1616
+ valueYField: fieldName,
1617
+ categoryXField: 'storeName',
1618
+ tooltip: am5.Tooltip.new(root, {
1619
+ pointerOrientation: "horizontal",
1620
+ labelText: "{name}: {valueY}",
1621
+ background: am5.Rectangle.new(root, {
1622
+ fill: color // Tooltip background color
1623
+ })
1624
+ })
1625
+ })
1626
+ );
1627
+
1628
+ series.strokes.template.setAll({
1629
+ strokeWidth: 2,
1630
+ stroke: color,
1631
+ templateField: "strokeSettings",
1632
+ });
1633
+
1634
+ series.data.setAll(formattedData);
1635
+
1636
+ series.bullets.push(() => {
1637
+ return am5.Bullet.new(root, {
1638
+ sprite: am5.Circle.new(root, {
1639
+ strokeWidth: 1,
1640
+ stroke: color,
1641
+ radius: 1,
1642
+ fill: root.interfaceColors.get("background")
1643
+ }),
1644
+ });
1645
+ });
1646
+
1647
+ series.appear();
1648
+ };
1649
+
1650
+ createLineSeries("Conversion", "conversion", xAxis, yAxis, am5.color(0x00FF00));
1651
+
1652
+ const legend = chart.children.push(
1653
+ am5.Legend.new(root, {
1654
+ centerX: am5.p50,
1655
+ x: am5.p50
1656
+ })
1657
+ );
1658
+
1659
+ legend.data.setAll(chart.series.values);
1660
+
1661
+ chart.set("cursor", am5xy.XYCursor.new(root, {
1662
+ behavior: "none"
1663
+ }));
1664
+
1665
+ chart.appear(1000, 100);
1666
+ });
1667
+ </script>
1668
+ </body>
1669
+ </html>
1670
+ `;
1671
+
1672
+ let weeklyChartBuffer;
1673
+ try {
1674
+ await page.setContent( chartHtmlContent, { waitUntil: 'domcontentloaded' } );
1675
+ await page.waitForSelector( '#Footfalltrend', { visible: true } );
1676
+ weeklyChartBuffer = await page.screenshot( { encoding: 'binary' } );
1677
+ } catch ( err ) {
1678
+ // console.error( 'Error generating chart screenshot:', err );
1679
+ throw new Error( 'Failed to generate chart screenshot' );
1680
+ } finally {
1681
+ await browser.close();
1682
+ }
1683
+
1684
+ return weeklyChartBuffer;
1685
+ };
1686
+
1687
+
1688
+ // const footfallTrend = async ( data ) => {
1689
+ // console.log( 'Generating footfall chart...' );
1690
+
1691
+ // const browser = await puppeteer.launch( { headless: true } );
1692
+ // const page = await browser.newPage();
1693
+
1694
+ // const chartHtmlContent = `
1695
+ // <!DOCTYPE html>
1696
+ // <html>
1697
+ // <head>
1698
+ // <style>
1699
+ // #Footfalltrend {
1700
+ // width: 100%;
1701
+ // height: 600px;
1702
+ // }
1703
+ // </style>
1704
+ // <script src="https://cdn.amcharts.com/lib/5/index.js"></script>
1705
+ // <script src="https://cdn.amcharts.com/lib/5/xy.js"></script>
1706
+ // <script src="https://cdn.amcharts.com/lib/5/themes/Animated.js"></script>
1707
+ // </head>
1708
+ // <body>
1709
+ // <div id="Footfalltrend"></div>
1710
+ // <script>
1711
+ // am5.ready(function() {
1712
+ // const root = am5.Root.new('Footfalltrend');
1713
+ // root.setThemes([am5themes_Animated.new(root)]);
1714
+
1715
+ // const chart = root.container.children.push(
1716
+ // am5xy.XYChart.new(root, {
1717
+ // panX: false,
1718
+ // panY: false,
1719
+ // wheelX: 'panX',
1720
+ // wheelY: 'zoomX',
1721
+ // layout: root.verticalLayout
1722
+ // })
1723
+ // );
1724
+
1725
+ // chart.set("scrollbarX", am5.Scrollbar.new(root, {
1726
+ // orientation: "horizontal"
1727
+ // }));
1728
+
1729
+ // const xRenderer = am5xy.AxisRendererX.new(root, {
1730
+ // minGridDistance: 20,
1731
+ // minorGridEnabled: true,
1732
+ // });
1733
+
1734
+ // xRenderer.labels.template.setAll({
1735
+ // rotation: -45,
1736
+ // centerY: am5.p50,
1737
+ // centerX: am5.p100,
1738
+ // paddingRight: 5,
1739
+ // });
1740
+
1741
+ // const xAxis = chart.xAxes.push(
1742
+ // am5xy.CategoryAxis.new(root, {
1743
+ // categoryField: "storeName",
1744
+ // renderer: xRenderer,
1745
+ // tooltip: am5.Tooltip.new(root, { labelText: "{fullStoreName}" })
1746
+ // })
1747
+ // );
1748
+
1749
+ // const formattedData = ${JSON.stringify( data )}.map((item) => ({
1750
+ // fullStoreName: item.storeName,
1751
+ // storeName: item.storeName,
1752
+ // footfall: Number(item.footfall_count),
1753
+ // conversion: Number(item.conversion_count),
1754
+ // bounced: Number(item.bounced_count),
1755
+ // engagers: Number(item.engagers_count),
1756
+ // missedopportunities: Number(item.missedOpportunity_count),
1757
+ // }));
1758
+
1759
+ // formattedData.sort((a, b) => {
1760
+ // const dateA = new Date(a.storeName);
1761
+ // const dateB = new Date(b.storeName);
1762
+ // return dateA - dateB;
1763
+ // });
1764
+
1765
+ // xAxis.data.setAll(formattedData);
1766
+
1767
+ // const yAxis = chart.yAxes.push(
1768
+ // am5xy.ValueAxis.new(root, {
1769
+ // min: 0,
1770
+ // renderer: am5xy.AxisRendererY.new(root, {})
1771
+ // })
1772
+ // );
1773
+
1774
+ // yAxis.children.unshift(am5.Label.new(root, {
1775
+ // rotation: -90,
1776
+ // text: "Footfall",
1777
+ // y: am5.p50,
1778
+ // centerX: am5.p50,
1779
+ // fontFamily: "Arial",
1780
+ // fontSize: "14px",
1781
+ // fill: am5.color(0x000000)
1782
+ // }));
1783
+
1784
+ // const createColumnSeries = (name, field, color) => {
1785
+ // const series = chart.series.push(
1786
+ // am5xy.ColumnSeries.new(root, {
1787
+ // name: name,
1788
+ // xAxis: xAxis,
1789
+ // yAxis: yAxis,
1790
+ // valueYField: field,
1791
+ // categoryXField: "storeName",
1792
+ // fill: color,
1793
+ // stroke: color,
1794
+ // stacked: true,
1795
+ // })
1796
+ // );
1797
+
1798
+ // series.data.setAll(formattedData);
1799
+
1800
+ // series.bullets.push(() => {
1801
+ // return am5.Bullet.new(root, {
1802
+ // sprite: am5.Label.new(root, {
1803
+ // text: "{valueY}",
1804
+ // fill: root.interfaceColors.get("alternativeText"),
1805
+ // centerY: am5.p50,
1806
+ // centerX: am5.p50,
1807
+ // populateText: true,
1808
+ // fontSize: 12 // Reduce font size
1809
+ // })
1810
+ // });
1811
+ // });
1812
+
1813
+ // series.columns.template.setAll({
1814
+ // tooltipText: "{name}: {valueY}",
1815
+ // width: am5.percent(90),
1816
+ // tooltipY: 0,
1817
+ // });
1818
+
1819
+ // series.appear(1000);
1820
+ // return series;
1821
+ // };
1822
+
1823
+ // createColumnSeries("Bounced", "bounced", am5.color(0x67b7dc));
1824
+ // createColumnSeries("Missed Opportunity", "missedopportunities", am5.color(0x6794dc));
1825
+ // createColumnSeries("Conversion", "conversion", am5.color(0x6771dc));
1826
+
1827
+ // const legend = chart.children.push(
1828
+ // am5.Legend.new(root, {
1829
+ // centerX: am5.p50,
1830
+ // x: am5.p50
1831
+ // })
1832
+ // );
1833
+
1834
+ // legend.data.setAll(chart.series.values);
1835
+
1836
+ // chart.set("cursor", am5xy.XYCursor.new(root, {
1837
+ // behavior: "none"
1838
+ // }));
1839
+
1840
+ // chart.appear(1000, 100);
1841
+ // });
1842
+ // </script>
1843
+ // </body>
1844
+ // </html>
1845
+ // `;
1846
+ // let weeklyChartBuffer;
1847
+ // try {
1848
+ // await page.setContent( chartHtmlContent, { waitUntil: 'domcontentloaded' } );
1849
+ // await page.waitForSelector( '#Footfalltrend', { visible: true } );
1850
+ // weeklyChartBuffer = await page.screenshot( { encoding: 'binary' } );
1851
+ // // await page.screenshot( { path: chartImagePath } );
1852
+ // // console.log( 'Screenshot saved to:', chartImagePath );
1853
+ // // console.log( 'Screenshot saved to:', chartImagePath );
1854
+ // } catch ( err ) {
1855
+ // console.error( 'Error generating chart screenshot:', err );
1856
+ // throw new Error( 'Failed to generate chart screenshot' );
1857
+ // } finally {
1858
+ // await browser.close();
1859
+ // }
1860
+
1861
+ // return weeklyChartBuffer;
1862
+ // };