code-simplifier 1.1.0 → 1.2.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,682 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * AI-Powered Trend Analyzer
5
+ * Analyzes code quality trends and predicts future issues
6
+ */
7
+
8
+ const fs = require('fs-extra')
9
+ const path = require('path')
10
+ const chalk = require('chalk')
11
+ const ora = require('ora')
12
+ const simpleStatistics = require('simple-statistics')
13
+
14
+ class AITrendAnalyzer {
15
+ constructor() {
16
+ this.dataStore = new Map()
17
+ this.predictions = []
18
+ this.alerts = []
19
+ this.thresholdSettings = {
20
+ qualityScore: 70,
21
+ issuesPerWeek: 10,
22
+ complexityIncrease: 0.15,
23
+ securityIssues: 0,
24
+ performanceDegradation: 0.10
25
+ }
26
+ }
27
+
28
+ /**
29
+ * Analyze quality trends from historical data
30
+ */
31
+ async analyzeTrends(historicalData, options = {}) {
32
+ const {
33
+ days = 30,
34
+ granularity = 'day',
35
+ predictDays = 7
36
+ } = options
37
+
38
+ console.log(chalk.cyan.bold('\nšŸ“Š AI-Powered Trend Analyzer'))
39
+ console.log(chalk.gray('='.repeat(70)))
40
+
41
+ const spinner = ora(chalk.blue('Analyzing quality trends...')).start()
42
+
43
+ try {
44
+ // 1. Process historical data
45
+ spinner.text = chalk.blue('Processing historical data...')
46
+ const processedData = this.processHistoricalData(historicalData, granularity)
47
+
48
+ // 2. Calculate trends
49
+ spinner.text = chalk.blue('Calculating quality trends...')
50
+ const trends = this.calculateTrends(processedData)
51
+
52
+ // 3. Generate predictions
53
+ spinner.text = chalk.blue('Generating predictions...')
54
+ const predictions = await this.generatePredictions(trends, predictDays)
55
+
56
+ // 4. Identify risk points
57
+ spinner.text = chalk.blue('Identifying risk points...')
58
+ const risks = this.identifyRisks(trends, predictions)
59
+
60
+ // 5. Generate recommendations
61
+ spinner.text = chalk.blue('Generating recommendations...')
62
+ const recommendations = this.generateRecommendations(trends, predictions, risks)
63
+
64
+ // 6. Create report
65
+ spinner.text = chalk.blue('Creating trend report...')
66
+ const report = this.createTrendReport({
67
+ trends,
68
+ predictions,
69
+ risks,
70
+ recommendations,
71
+ metadata: {
72
+ analysisDate: new Date().toISOString(),
73
+ dataPeriod: days,
74
+ granularity,
75
+ predictionHorizon: predictDays
76
+ }
77
+ })
78
+
79
+ spinner.succeed(chalk.green('āœ“ Trend analysis complete'))
80
+
81
+ return report
82
+ } catch (error) {
83
+ spinner.fail(chalk.red('Trend analysis failed'))
84
+ throw error
85
+ }
86
+ }
87
+
88
+ /**
89
+ * Process historical data into time series
90
+ */
91
+ processHistoricalData(rawData, granularity) {
92
+ const timeSeries = {}
93
+
94
+ // Group data by time period
95
+ rawData.forEach(record => {
96
+ const timestamp = new Date(record.timestamp)
97
+ const periodKey = this.getPeriodKey(timestamp, granularity)
98
+
99
+ if (!timeSeries[periodKey]) {
100
+ timeSeries[periodKey] = {
101
+ date: periodKey,
102
+ qualityScore: [],
103
+ issues: [],
104
+ complexity: [],
105
+ security: [],
106
+ performance: [],
107
+ count: 0
108
+ }
109
+ }
110
+
111
+ timeSeries[periodKey].qualityScore.push(record.qualityScore || 0)
112
+ timeSeries[periodKey].issues.push(record.issues || 0)
113
+ timeSeries[periodKey].complexity.push(record.complexity || 0)
114
+ timeSeries[periodKey].security.push(record.security || 0)
115
+ timeSeries[periodKey].performance.push(record.performance || 0)
116
+ timeSeries[periodKey].count++
117
+ })
118
+
119
+ // Calculate averages for each period
120
+ const processed = Object.values(timeSeries).map(period => ({
121
+ date: period.date,
122
+ qualityScore: this.average(period.qualityScore),
123
+ issues: this.sum(period.issues),
124
+ complexity: this.average(period.complexity),
125
+ security: this.average(period.security),
126
+ performance: this.average(period.performance)
127
+ }))
128
+
129
+ // Sort by date
130
+ processed.sort((a, b) => new Date(a.date) - new Date(b.date))
131
+
132
+ return processed
133
+ }
134
+
135
+ /**
136
+ * Calculate quality trends
137
+ */
138
+ calculateTrends(timeSeries) {
139
+ const trends = {
140
+ quality: this.calculateMetricTrend(timeSeries, 'qualityScore'),
141
+ issues: this.calculateMetricTrend(timeSeries, 'issues'),
142
+ complexity: this.calculateMetricTrend(timeSeries, 'complexity'),
143
+ security: this.calculateMetricTrend(timeSeries, 'security'),
144
+ performance: this.calculateMetricTrend(timeSeries, 'performance')
145
+ }
146
+
147
+ // Overall quality trend
148
+ trends.overall = this.calculateOverallTrend(timeSeries)
149
+
150
+ return trends
151
+ }
152
+
153
+ /**
154
+ * Calculate trend for a specific metric
155
+ */
156
+ calculateMetricTrend(timeSeries, metric) {
157
+ if (timeSeries.length < 2) {
158
+ return {
159
+ direction: 'stable',
160
+ rate: 0,
161
+ confidence: 0
162
+ }
163
+ }
164
+
165
+ const values = timeSeries.map(d => d[metric])
166
+ const dates = timeSeries.map(d => new Date(d.date).getTime())
167
+
168
+ // Linear regression
169
+ const regression = simpleStatistics.linearRegression(
170
+ dates.map((date, i) => [date, values[i]])
171
+ )
172
+ const correlation = simpleStatistics.sampleCorrelation(dates, values)
173
+
174
+ const rate = regression.m
175
+ const confidence = Math.abs(correlation)
176
+
177
+ // Determine direction
178
+ let direction = 'stable'
179
+ if (Math.abs(rate) > 0.01) {
180
+ direction = rate > 0 ? (metric === 'qualityScore' ? 'improving' : 'declining')
181
+ : (metric === 'qualityScore' ? 'declining' : 'improving')
182
+ }
183
+
184
+ return {
185
+ direction,
186
+ rate,
187
+ confidence,
188
+ values,
189
+ dates,
190
+ regression
191
+ }
192
+ }
193
+
194
+ /**
195
+ * Calculate overall quality trend
196
+ */
197
+ calculateOverallTrend(timeSeries) {
198
+ const recentPeriods = timeSeries.slice(-7) // Last 7 periods
199
+ const previousPeriods = timeSeries.slice(-14, -7) // Previous 7 periods
200
+
201
+ if (recentPeriods.length === 0 || previousPeriods.length === 0) {
202
+ return {
203
+ direction: 'stable',
204
+ score: 0,
205
+ change: 0
206
+ }
207
+ }
208
+
209
+ const recentScore = this.average(recentPeriods.map(d => d.qualityScore))
210
+ const previousScore = this.average(previousPeriods.map(d => d.qualityScore))
211
+ const change = ((recentScore - previousScore) / previousScore) * 100
212
+
213
+ let direction = 'stable'
214
+ if (Math.abs(change) > 5) {
215
+ direction = change > 0 ? 'improving' : 'declining'
216
+ }
217
+
218
+ return {
219
+ direction,
220
+ score: recentScore,
221
+ change,
222
+ previousScore
223
+ }
224
+ }
225
+
226
+ /**
227
+ * Generate predictions using AI/ML
228
+ */
229
+ async generatePredictions(trends, days) {
230
+ const predictions = {
231
+ quality: this.predictMetric(trends.quality, days),
232
+ issues: this.predictMetric(trends.issues, days),
233
+ complexity: this.predictMetric(trends.complexity, days),
234
+ security: this.predictMetric(trends.security, days),
235
+ performance: this.predictMetric(trends.performance, days),
236
+ overall: this.predictOverall(trends.overall, days)
237
+ }
238
+
239
+ return predictions
240
+ }
241
+
242
+ /**
243
+ * Predict future values for a metric
244
+ */
245
+ predictMetric(trend, days) {
246
+ // Handle edge case where trend.values is undefined
247
+ const lastValue = trend.values && trend.values.length > 0
248
+ ? trend.values[trend.values.length - 1]
249
+ : 0
250
+
251
+ if (!trend.regression || trend.confidence < 0.5) {
252
+ return {
253
+ predicted: lastValue,
254
+ confidence: trend.confidence || 0,
255
+ warning: 'Low confidence prediction due to insufficient data'
256
+ }
257
+ }
258
+
259
+ const lastDate = trend.dates && trend.dates.length > 0
260
+ ? trend.dates[trend.dates.length - 1]
261
+ : Date.now()
262
+
263
+ const futureDate = lastDate + (days * 24 * 60 * 60 * 1000)
264
+
265
+ const predicted = simpleStatistics.linearRegressionLine(trend.regression)(futureDate)
266
+
267
+ return {
268
+ predicted,
269
+ confidence: trend.confidence,
270
+ direction: predicted > lastValue ? 'up' : 'down'
271
+ }
272
+ }
273
+
274
+ /**
275
+ * Predict overall quality
276
+ */
277
+ predictOverall(trend, days) {
278
+ const current = trend.score
279
+ const change = trend.change
280
+
281
+ // Predict based on current trend
282
+ const predictedChange = (change / 7) * days // Extrapolate daily change
283
+ const predicted = current + predictedChange
284
+
285
+ return {
286
+ predicted,
287
+ direction: predicted > current ? 'improving' : 'declining',
288
+ confidence: Math.min(100, Math.abs(change) * 2)
289
+ }
290
+ }
291
+
292
+ /**
293
+ * Identify risk points
294
+ */
295
+ identifyRisks(trends, predictions) {
296
+ const risks = []
297
+
298
+ // Quality degradation risk
299
+ if (trends.quality.direction === 'declining' && trends.quality.confidence > 0.7) {
300
+ risks.push({
301
+ type: 'quality_degradation',
302
+ severity: 'high',
303
+ metric: 'qualityScore',
304
+ description: 'Quality score is declining significantly',
305
+ trend: trends.quality.direction,
306
+ confidence: trends.quality.confidence,
307
+ prediction: predictions.quality.predicted
308
+ })
309
+ }
310
+
311
+ // Security issues risk
312
+ if (trends.security.direction === 'declining' || predictions.security.predicted > 0) {
313
+ risks.push({
314
+ type: 'security_issues',
315
+ severity: 'critical',
316
+ metric: 'security',
317
+ description: 'Potential security issues detected',
318
+ trend: trends.security.direction,
319
+ confidence: trends.security.confidence
320
+ })
321
+ }
322
+
323
+ // Complexity growth risk
324
+ if (trends.complexity.direction === 'declining' && trends.complexity.confidence > 0.6) {
325
+ risks.push({
326
+ type: 'complexity_growth',
327
+ severity: 'medium',
328
+ metric: 'complexity',
329
+ description: 'Code complexity is increasing',
330
+ trend: trends.complexity.direction,
331
+ confidence: trends.complexity.confidence
332
+ })
333
+ }
334
+
335
+ // Performance degradation risk
336
+ if (trends.performance.direction === 'declining' || predictions.performance.predicted > 10) {
337
+ risks.push({
338
+ type: 'performance_degradation',
339
+ severity: 'high',
340
+ metric: 'performance',
341
+ description: 'Performance issues may arise',
342
+ trend: trends.performance.direction,
343
+ confidence: trends.performance.confidence
344
+ })
345
+ }
346
+
347
+ // Issue volume risk
348
+ if (trends.issues.direction === 'declining' && trends.issues.confidence > 0.7) {
349
+ risks.push({
350
+ type: 'issue_volume',
351
+ severity: 'medium',
352
+ metric: 'issues',
353
+ description: 'Number of issues is increasing',
354
+ trend: trends.issues.direction,
355
+ confidence: trends.issues.confidence
356
+ })
357
+ }
358
+
359
+ return risks
360
+ }
361
+
362
+ /**
363
+ * Generate recommendations
364
+ */
365
+ generateRecommendations(trends, predictions, risks) {
366
+ const recommendations = []
367
+
368
+ // Overall quality recommendations
369
+ if (trends.overall.direction === 'declining') {
370
+ recommendations.push({
371
+ priority: 'high',
372
+ category: 'quality',
373
+ title: 'Focus on Quality Improvement',
374
+ description: 'Overall quality is declining. Prioritize code reviews and refactoring.',
375
+ actions: [
376
+ 'Schedule mandatory code reviews',
377
+ 'Implement stricter quality gates',
378
+ 'Allocate time for technical debt reduction'
379
+ ]
380
+ })
381
+ }
382
+
383
+ // Security recommendations
384
+ const securityRisk = risks.find(r => r.type === 'security_issues')
385
+ if (securityRisk) {
386
+ recommendations.push({
387
+ priority: 'critical',
388
+ category: 'security',
389
+ title: 'Address Security Issues',
390
+ description: 'Security risk detected. Immediate action required.',
391
+ actions: [
392
+ 'Run security audit',
393
+ 'Review all user input handling',
394
+ 'Update dependencies with known vulnerabilities',
395
+ 'Implement security best practices'
396
+ ]
397
+ })
398
+ }
399
+
400
+ // Complexity recommendations
401
+ const complexityRisk = risks.find(r => r.type === 'complexity_growth')
402
+ if (complexityRisk) {
403
+ recommendations.push({
404
+ priority: 'medium',
405
+ category: 'complexity',
406
+ title: 'Reduce Code Complexity',
407
+ description: 'Code complexity is increasing. Consider refactoring.',
408
+ actions: [
409
+ 'Identify and refactor complex functions',
410
+ 'Apply SOLID principles',
411
+ 'Break down large functions',
412
+ 'Improve code documentation'
413
+ ]
414
+ })
415
+ }
416
+
417
+ // Performance recommendations
418
+ const performanceRisk = risks.find(r => r.type === 'performance_degradation')
419
+ if (performanceRisk) {
420
+ recommendations.push({
421
+ priority: 'high',
422
+ category: 'performance',
423
+ title: 'Optimize Performance',
424
+ description: 'Performance degradation predicted. Take preventive action.',
425
+ actions: [
426
+ 'Profile performance bottlenecks',
427
+ 'Optimize database queries',
428
+ 'Implement caching strategies',
429
+ 'Review algorithm efficiency'
430
+ ]
431
+ })
432
+ }
433
+
434
+ // Preventive recommendations
435
+ if (predictions.overall.confidence > 70) {
436
+ recommendations.push({
437
+ priority: 'low',
438
+ category: 'prevention',
439
+ title: 'Implement Preventive Measures',
440
+ description: 'Predictions suggest proactive monitoring.',
441
+ actions: [
442
+ 'Set up automated quality monitoring',
443
+ 'Create quality dashboards',
444
+ 'Establish quality metrics baseline',
445
+ 'Schedule regular quality assessments'
446
+ ]
447
+ })
448
+ }
449
+
450
+ return recommendations
451
+ }
452
+
453
+ /**
454
+ * Create comprehensive trend report
455
+ */
456
+ createTrendReport(data) {
457
+ const { trends, predictions, risks, recommendations, metadata } = data
458
+
459
+ return {
460
+ metadata,
461
+ summary: {
462
+ overallTrend: trends.overall.direction,
463
+ overallScore: trends.overall.score,
464
+ overallChange: trends.overall.change,
465
+ riskLevel: this.calculateRiskLevel(risks),
466
+ totalRisks: risks.length,
467
+ totalRecommendations: recommendations.length
468
+ },
469
+ trends,
470
+ predictions,
471
+ risks,
472
+ recommendations,
473
+ alerts: this.generateAlerts(risks, recommendations),
474
+ timestamp: new Date().toISOString()
475
+ }
476
+ }
477
+
478
+ /**
479
+ * Calculate overall risk level
480
+ */
481
+ calculateRiskLevel(risks) {
482
+ if (risks.length === 0) return 'low'
483
+
484
+ const severityWeights = { low: 1, medium: 2, high: 3, critical: 4 }
485
+ const totalScore = risks.reduce((sum, risk) => {
486
+ return sum + (severityWeights[risk.severity] || 1)
487
+ }, 0)
488
+
489
+ const averageScore = totalScore / risks.length
490
+
491
+ if (averageScore >= 3.5) return 'critical'
492
+ if (averageScore >= 2.5) return 'high'
493
+ if (averageScore >= 1.5) return 'medium'
494
+ return 'low'
495
+ }
496
+
497
+ /**
498
+ * Generate alerts for critical risks
499
+ */
500
+ generateAlerts(risks, recommendations) {
501
+ const alerts = []
502
+
503
+ risks.forEach(risk => {
504
+ if (risk.severity === 'critical' || risk.severity === 'high') {
505
+ alerts.push({
506
+ type: risk.type,
507
+ severity: risk.severity,
508
+ message: risk.description,
509
+ timestamp: new Date().toISOString(),
510
+ actionable: true,
511
+ recommendation: recommendations.find(rec => rec.category === risk.metric)
512
+ })
513
+ }
514
+ })
515
+
516
+ return alerts
517
+ }
518
+
519
+ /**
520
+ * Export trend report to Markdown
521
+ */
522
+ exportToMarkdown(report, outputPath) {
523
+ const markdown = this.generateMarkdownReport(report)
524
+ return fs.writeFile(outputPath, markdown, 'utf8')
525
+ }
526
+
527
+ /**
528
+ * Generate Markdown report
529
+ */
530
+ generateMarkdownReport(report) {
531
+ const { metadata, summary, trends, predictions, risks, recommendations, alerts } = report
532
+
533
+ let md = `# Code Quality Trend Analysis Report\n\n`
534
+ md += `**Generated**: ${new Date(metadata.analysisDate).toLocaleString()}\n`
535
+ md += `**Data Period**: ${metadata.dataPeriod} days\n`
536
+ md += `**Prediction Horizon**: ${metadata.predictionHorizon} days\n\n`
537
+
538
+ // Summary
539
+ md += `## šŸ“Š Summary\n\n`
540
+ md += `- **Overall Trend**: ${summary.overallTrend}\n`
541
+ md += `- **Quality Score**: ${summary.overallScore.toFixed(2)}\n`
542
+ md += `- **Change**: ${summary.overallChange > 0 ? '+' : ''}${summary.overallChange.toFixed(2)}%\n`
543
+ md += `- **Risk Level**: ${summary.riskLevel}\n`
544
+ md += `- **Total Risks**: ${summary.totalRisks}\n`
545
+ md += `- **Recommendations**: ${summary.totalRecommendations}\n\n`
546
+
547
+ // Trends
548
+ md += `## šŸ“ˆ Trends\n\n`
549
+ md += `### Overall Quality\n`
550
+ md += `- Direction: ${trends.overall.direction}\n`
551
+ md += `- Score: ${trends.overall.score.toFixed(2)}\n`
552
+ md += `- Change: ${trends.overall.change > 0 ? '+' : ''}${trends.overall.change.toFixed(2)}%\n\n`
553
+
554
+ md += `### Quality Score\n`
555
+ md += `- Direction: ${trends.quality.direction}\n`
556
+ md += `- Rate: ${trends.quality.rate.toFixed(4)}\n`
557
+ md += `- Confidence: ${(trends.quality.confidence * 100).toFixed(2)}%\n\n`
558
+
559
+ md += `### Issues\n`
560
+ md += `- Direction: ${trends.issues.direction}\n`
561
+ md += `- Confidence: ${(trends.issues.confidence * 100).toFixed(2)}%\n\n`
562
+
563
+ md += `### Complexity\n`
564
+ md += `- Direction: ${trends.complexity.direction}\n`
565
+ md += `- Confidence: ${(trends.complexity.confidence * 100).toFixed(2)}%\n\n`
566
+
567
+ md += `### Security\n`
568
+ md += `- Direction: ${trends.security.direction}\n`
569
+ md += `- Confidence: ${(trends.security.confidence * 100).toFixed(2)}%\n\n`
570
+
571
+ md += `### Performance\n`
572
+ md += `- Direction: ${trends.performance.direction}\n`
573
+ md += `- Confidence: ${(trends.performance.confidence * 100).toFixed(2)}%\n\n`
574
+
575
+ // Predictions
576
+ md += `## šŸ”® Predictions (Next ${metadata.predictionHorizon} days)\n\n`
577
+ md += `### Overall Quality\n`
578
+ md += `- Predicted Score: ${predictions.overall.predicted.toFixed(2)}\n`
579
+ md += `- Direction: ${predictions.overall.direction}\n`
580
+ md += `- Confidence: ${predictions.overall.confidence.toFixed(2)}%\n\n`
581
+
582
+ // Risks
583
+ md += `## āš ļø Risk Assessment\n\n`
584
+ if (risks.length === 0) {
585
+ md += `No significant risks identified.\n\n`
586
+ } else {
587
+ risks.forEach((risk, i) => {
588
+ const severityIcon = risk.severity === 'critical' ? 'šŸ”“' :
589
+ risk.severity === 'high' ? '🟠' :
590
+ risk.severity === 'medium' ? '🟔' : '🟢'
591
+ md += `### ${severityIcon} Risk ${i + 1}: ${risk.type}\n`
592
+ md += `- **Severity**: ${risk.severity}\n`
593
+ md += `- **Metric**: ${risk.metric}\n`
594
+ md += `- **Description**: ${risk.description}\n`
595
+ md += `- **Trend**: ${risk.trend}\n`
596
+ md += `- **Confidence**: ${(risk.confidence * 100).toFixed(2)}%\n\n`
597
+ })
598
+ }
599
+
600
+ // Recommendations
601
+ md += `## šŸ’” Recommendations\n\n`
602
+ recommendations.forEach((rec, i) => {
603
+ const priorityIcon = rec.priority === 'critical' ? '🚨' :
604
+ rec.priority === 'high' ? '⚔' :
605
+ rec.priority === 'medium' ? 'šŸ“' : 'šŸ’”'
606
+ md += `### ${priorityIcon} ${i + 1}. ${rec.title}\n`
607
+ md += `- **Priority**: ${rec.priority}\n`
608
+ md += `- **Category**: ${rec.category}\n`
609
+ md += `- **Description**: ${rec.description}\n`
610
+ md += `- **Actions**:\n`
611
+ rec.actions.forEach(action => {
612
+ md += ` - ${action}\n`
613
+ })
614
+ md += `\n`
615
+ })
616
+
617
+ // Alerts
618
+ if (alerts.length > 0) {
619
+ md += `## 🚨 Alerts\n\n`
620
+ alerts.forEach(alert => {
621
+ md += `- **${alert.type}**: ${alert.message}\n`
622
+ })
623
+ md += `\n`
624
+ }
625
+
626
+ md += `---\n`
627
+ md += `*Generated by Code-Simplifier AI Trend Analyzer*\n`
628
+
629
+ return md
630
+ }
631
+
632
+ /**
633
+ * Get period key for time series
634
+ */
635
+ getPeriodKey(timestamp, granularity) {
636
+ const date = new Date(timestamp)
637
+
638
+ switch (granularity) {
639
+ case 'hour':
640
+ return `${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()} ${date.getHours()}:00`
641
+ case 'day':
642
+ return `${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()}`
643
+ case 'week': {
644
+ const weekStart = new Date(date)
645
+ weekStart.setDate(date.getDate() - date.getDay())
646
+ return `${weekStart.getFullYear()}-W${this.getWeekNumber(weekStart)}`
647
+ }
648
+ case 'month':
649
+ return `${date.getFullYear()}-${date.getMonth() + 1}`
650
+ default:
651
+ return date.toISOString()
652
+ }
653
+ }
654
+
655
+ /**
656
+ * Get week number
657
+ */
658
+ getWeekNumber(date) {
659
+ const d = new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate()))
660
+ const dayNum = d.getUTCDay() || 7
661
+ d.setUTCDate(d.getUTCDate() + 4 - dayNum)
662
+ const yearStart = new Date(Date.UTC(d.getUTCFullYear(), 0, 1))
663
+ return Math.ceil((((d - yearStart) / 86400000) + 1) / 7)
664
+ }
665
+
666
+ /**
667
+ * Calculate average
668
+ */
669
+ average(arr) {
670
+ if (arr.length === 0) return 0
671
+ return arr.reduce((a, b) => a + b, 0) / arr.length
672
+ }
673
+
674
+ /**
675
+ * Calculate sum
676
+ */
677
+ sum(arr) {
678
+ return arr.reduce((a, b) => a + b, 0)
679
+ }
680
+ }
681
+
682
+ module.exports = new AITrendAnalyzer()