code-simplifier 1.0.0 → 1.2.0

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,674 @@
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
+ if (!trend.regression || trend.confidence < 0.5) {
247
+ return {
248
+ predicted: trend.values[trend.values.length - 1] || 0,
249
+ confidence: trend.confidence,
250
+ warning: 'Low confidence prediction due to insufficient data'
251
+ }
252
+ }
253
+
254
+ const lastDate = trend.dates[trend.dates.length - 1]
255
+ const futureDate = lastDate + (days * 24 * 60 * 60 * 1000)
256
+
257
+ const predicted = simpleStatistics.linearRegressionLine(trend.regression)(futureDate)
258
+
259
+ return {
260
+ predicted,
261
+ confidence: trend.confidence,
262
+ direction: predicted > trend.values[trend.values.length - 1] ? 'up' : 'down'
263
+ }
264
+ }
265
+
266
+ /**
267
+ * Predict overall quality
268
+ */
269
+ predictOverall(trend, days) {
270
+ const current = trend.score
271
+ const change = trend.change
272
+
273
+ // Predict based on current trend
274
+ const predictedChange = (change / 7) * days // Extrapolate daily change
275
+ const predicted = current + predictedChange
276
+
277
+ return {
278
+ predicted,
279
+ direction: predicted > current ? 'improving' : 'declining',
280
+ confidence: Math.min(100, Math.abs(change) * 2)
281
+ }
282
+ }
283
+
284
+ /**
285
+ * Identify risk points
286
+ */
287
+ identifyRisks(trends, predictions) {
288
+ const risks = []
289
+
290
+ // Quality degradation risk
291
+ if (trends.quality.direction === 'declining' && trends.quality.confidence > 0.7) {
292
+ risks.push({
293
+ type: 'quality_degradation',
294
+ severity: 'high',
295
+ metric: 'qualityScore',
296
+ description: 'Quality score is declining significantly',
297
+ trend: trends.quality.direction,
298
+ confidence: trends.quality.confidence,
299
+ prediction: predictions.quality.predicted
300
+ })
301
+ }
302
+
303
+ // Security issues risk
304
+ if (trends.security.direction === 'declining' || predictions.security.predicted > 0) {
305
+ risks.push({
306
+ type: 'security_issues',
307
+ severity: 'critical',
308
+ metric: 'security',
309
+ description: 'Potential security issues detected',
310
+ trend: trends.security.direction,
311
+ confidence: trends.security.confidence
312
+ })
313
+ }
314
+
315
+ // Complexity growth risk
316
+ if (trends.complexity.direction === 'declining' && trends.complexity.confidence > 0.6) {
317
+ risks.push({
318
+ type: 'complexity_growth',
319
+ severity: 'medium',
320
+ metric: 'complexity',
321
+ description: 'Code complexity is increasing',
322
+ trend: trends.complexity.direction,
323
+ confidence: trends.complexity.confidence
324
+ })
325
+ }
326
+
327
+ // Performance degradation risk
328
+ if (trends.performance.direction === 'declining' || predictions.performance.predicted > 10) {
329
+ risks.push({
330
+ type: 'performance_degradation',
331
+ severity: 'high',
332
+ metric: 'performance',
333
+ description: 'Performance issues may arise',
334
+ trend: trends.performance.direction,
335
+ confidence: trends.performance.confidence
336
+ })
337
+ }
338
+
339
+ // Issue volume risk
340
+ if (trends.issues.direction === 'declining' && trends.issues.confidence > 0.7) {
341
+ risks.push({
342
+ type: 'issue_volume',
343
+ severity: 'medium',
344
+ metric: 'issues',
345
+ description: 'Number of issues is increasing',
346
+ trend: trends.issues.direction,
347
+ confidence: trends.issues.confidence
348
+ })
349
+ }
350
+
351
+ return risks
352
+ }
353
+
354
+ /**
355
+ * Generate recommendations
356
+ */
357
+ generateRecommendations(trends, predictions, risks) {
358
+ const recommendations = []
359
+
360
+ // Overall quality recommendations
361
+ if (trends.overall.direction === 'declining') {
362
+ recommendations.push({
363
+ priority: 'high',
364
+ category: 'quality',
365
+ title: 'Focus on Quality Improvement',
366
+ description: 'Overall quality is declining. Prioritize code reviews and refactoring.',
367
+ actions: [
368
+ 'Schedule mandatory code reviews',
369
+ 'Implement stricter quality gates',
370
+ 'Allocate time for technical debt reduction'
371
+ ]
372
+ })
373
+ }
374
+
375
+ // Security recommendations
376
+ const securityRisk = risks.find(r => r.type === 'security_issues')
377
+ if (securityRisk) {
378
+ recommendations.push({
379
+ priority: 'critical',
380
+ category: 'security',
381
+ title: 'Address Security Issues',
382
+ description: 'Security risk detected. Immediate action required.',
383
+ actions: [
384
+ 'Run security audit',
385
+ 'Review all user input handling',
386
+ 'Update dependencies with known vulnerabilities',
387
+ 'Implement security best practices'
388
+ ]
389
+ })
390
+ }
391
+
392
+ // Complexity recommendations
393
+ const complexityRisk = risks.find(r => r.type === 'complexity_growth')
394
+ if (complexityRisk) {
395
+ recommendations.push({
396
+ priority: 'medium',
397
+ category: 'complexity',
398
+ title: 'Reduce Code Complexity',
399
+ description: 'Code complexity is increasing. Consider refactoring.',
400
+ actions: [
401
+ 'Identify and refactor complex functions',
402
+ 'Apply SOLID principles',
403
+ 'Break down large functions',
404
+ 'Improve code documentation'
405
+ ]
406
+ })
407
+ }
408
+
409
+ // Performance recommendations
410
+ const performanceRisk = risks.find(r => r.type === 'performance_degradation')
411
+ if (performanceRisk) {
412
+ recommendations.push({
413
+ priority: 'high',
414
+ category: 'performance',
415
+ title: 'Optimize Performance',
416
+ description: 'Performance degradation predicted. Take preventive action.',
417
+ actions: [
418
+ 'Profile performance bottlenecks',
419
+ 'Optimize database queries',
420
+ 'Implement caching strategies',
421
+ 'Review algorithm efficiency'
422
+ ]
423
+ })
424
+ }
425
+
426
+ // Preventive recommendations
427
+ if (predictions.overall.confidence > 70) {
428
+ recommendations.push({
429
+ priority: 'low',
430
+ category: 'prevention',
431
+ title: 'Implement Preventive Measures',
432
+ description: 'Predictions suggest proactive monitoring.',
433
+ actions: [
434
+ 'Set up automated quality monitoring',
435
+ 'Create quality dashboards',
436
+ 'Establish quality metrics baseline',
437
+ 'Schedule regular quality assessments'
438
+ ]
439
+ })
440
+ }
441
+
442
+ return recommendations
443
+ }
444
+
445
+ /**
446
+ * Create comprehensive trend report
447
+ */
448
+ createTrendReport(data) {
449
+ const { trends, predictions, risks, recommendations, metadata } = data
450
+
451
+ return {
452
+ metadata,
453
+ summary: {
454
+ overallTrend: trends.overall.direction,
455
+ overallScore: trends.overall.score,
456
+ overallChange: trends.overall.change,
457
+ riskLevel: this.calculateRiskLevel(risks),
458
+ totalRisks: risks.length,
459
+ totalRecommendations: recommendations.length
460
+ },
461
+ trends,
462
+ predictions,
463
+ risks,
464
+ recommendations,
465
+ alerts: this.generateAlerts(risks, recommendations),
466
+ timestamp: new Date().toISOString()
467
+ }
468
+ }
469
+
470
+ /**
471
+ * Calculate overall risk level
472
+ */
473
+ calculateRiskLevel(risks) {
474
+ if (risks.length === 0) return 'low'
475
+
476
+ const severityWeights = { low: 1, medium: 2, high: 3, critical: 4 }
477
+ const totalScore = risks.reduce((sum, risk) => {
478
+ return sum + (severityWeights[risk.severity] || 1)
479
+ }, 0)
480
+
481
+ const averageScore = totalScore / risks.length
482
+
483
+ if (averageScore >= 3.5) return 'critical'
484
+ if (averageScore >= 2.5) return 'high'
485
+ if (averageScore >= 1.5) return 'medium'
486
+ return 'low'
487
+ }
488
+
489
+ /**
490
+ * Generate alerts for critical risks
491
+ */
492
+ generateAlerts(risks, recommendations) {
493
+ const alerts = []
494
+
495
+ risks.forEach(risk => {
496
+ if (risk.severity === 'critical' || risk.severity === 'high') {
497
+ alerts.push({
498
+ type: risk.type,
499
+ severity: risk.severity,
500
+ message: risk.description,
501
+ timestamp: new Date().toISOString(),
502
+ actionable: true,
503
+ recommendation: recommendations.find(rec => rec.category === risk.metric)
504
+ })
505
+ }
506
+ })
507
+
508
+ return alerts
509
+ }
510
+
511
+ /**
512
+ * Export trend report to Markdown
513
+ */
514
+ exportToMarkdown(report, outputPath) {
515
+ const markdown = this.generateMarkdownReport(report)
516
+ return fs.writeFile(outputPath, markdown, 'utf8')
517
+ }
518
+
519
+ /**
520
+ * Generate Markdown report
521
+ */
522
+ generateMarkdownReport(report) {
523
+ const { metadata, summary, trends, predictions, risks, recommendations, alerts } = report
524
+
525
+ let md = `# Code Quality Trend Analysis Report\n\n`
526
+ md += `**Generated**: ${new Date(metadata.analysisDate).toLocaleString()}\n`
527
+ md += `**Data Period**: ${metadata.dataPeriod} days\n`
528
+ md += `**Prediction Horizon**: ${metadata.predictionHorizon} days\n\n`
529
+
530
+ // Summary
531
+ md += `## šŸ“Š Summary\n\n`
532
+ md += `- **Overall Trend**: ${summary.overallTrend}\n`
533
+ md += `- **Quality Score**: ${summary.overallScore.toFixed(2)}\n`
534
+ md += `- **Change**: ${summary.overallChange > 0 ? '+' : ''}${summary.overallChange.toFixed(2)}%\n`
535
+ md += `- **Risk Level**: ${summary.riskLevel}\n`
536
+ md += `- **Total Risks**: ${summary.totalRisks}\n`
537
+ md += `- **Recommendations**: ${summary.totalRecommendations}\n\n`
538
+
539
+ // Trends
540
+ md += `## šŸ“ˆ Trends\n\n`
541
+ md += `### Overall Quality\n`
542
+ md += `- Direction: ${trends.overall.direction}\n`
543
+ md += `- Score: ${trends.overall.score.toFixed(2)}\n`
544
+ md += `- Change: ${trends.overall.change > 0 ? '+' : ''}${trends.overall.change.toFixed(2)}%\n\n`
545
+
546
+ md += `### Quality Score\n`
547
+ md += `- Direction: ${trends.quality.direction}\n`
548
+ md += `- Rate: ${trends.quality.rate.toFixed(4)}\n`
549
+ md += `- Confidence: ${(trends.quality.confidence * 100).toFixed(2)}%\n\n`
550
+
551
+ md += `### Issues\n`
552
+ md += `- Direction: ${trends.issues.direction}\n`
553
+ md += `- Confidence: ${(trends.issues.confidence * 100).toFixed(2)}%\n\n`
554
+
555
+ md += `### Complexity\n`
556
+ md += `- Direction: ${trends.complexity.direction}\n`
557
+ md += `- Confidence: ${(trends.complexity.confidence * 100).toFixed(2)}%\n\n`
558
+
559
+ md += `### Security\n`
560
+ md += `- Direction: ${trends.security.direction}\n`
561
+ md += `- Confidence: ${(trends.security.confidence * 100).toFixed(2)}%\n\n`
562
+
563
+ md += `### Performance\n`
564
+ md += `- Direction: ${trends.performance.direction}\n`
565
+ md += `- Confidence: ${(trends.performance.confidence * 100).toFixed(2)}%\n\n`
566
+
567
+ // Predictions
568
+ md += `## šŸ”® Predictions (Next ${metadata.predictionHorizon} days)\n\n`
569
+ md += `### Overall Quality\n`
570
+ md += `- Predicted Score: ${predictions.overall.predicted.toFixed(2)}\n`
571
+ md += `- Direction: ${predictions.overall.direction}\n`
572
+ md += `- Confidence: ${predictions.overall.confidence.toFixed(2)}%\n\n`
573
+
574
+ // Risks
575
+ md += `## āš ļø Risk Assessment\n\n`
576
+ if (risks.length === 0) {
577
+ md += `No significant risks identified.\n\n`
578
+ } else {
579
+ risks.forEach((risk, i) => {
580
+ const severityIcon = risk.severity === 'critical' ? 'šŸ”“' :
581
+ risk.severity === 'high' ? '🟠' :
582
+ risk.severity === 'medium' ? '🟔' : '🟢'
583
+ md += `### ${severityIcon} Risk ${i + 1}: ${risk.type}\n`
584
+ md += `- **Severity**: ${risk.severity}\n`
585
+ md += `- **Metric**: ${risk.metric}\n`
586
+ md += `- **Description**: ${risk.description}\n`
587
+ md += `- **Trend**: ${risk.trend}\n`
588
+ md += `- **Confidence**: ${(risk.confidence * 100).toFixed(2)}%\n\n`
589
+ })
590
+ }
591
+
592
+ // Recommendations
593
+ md += `## šŸ’” Recommendations\n\n`
594
+ recommendations.forEach((rec, i) => {
595
+ const priorityIcon = rec.priority === 'critical' ? '🚨' :
596
+ rec.priority === 'high' ? '⚔' :
597
+ rec.priority === 'medium' ? 'šŸ“' : 'šŸ’”'
598
+ md += `### ${priorityIcon} ${i + 1}. ${rec.title}\n`
599
+ md += `- **Priority**: ${rec.priority}\n`
600
+ md += `- **Category**: ${rec.category}\n`
601
+ md += `- **Description**: ${rec.description}\n`
602
+ md += `- **Actions**:\n`
603
+ rec.actions.forEach(action => {
604
+ md += ` - ${action}\n`
605
+ })
606
+ md += `\n`
607
+ })
608
+
609
+ // Alerts
610
+ if (alerts.length > 0) {
611
+ md += `## 🚨 Alerts\n\n`
612
+ alerts.forEach(alert => {
613
+ md += `- **${alert.type}**: ${alert.message}\n`
614
+ })
615
+ md += `\n`
616
+ }
617
+
618
+ md += `---\n`
619
+ md += `*Generated by Code-Simplifier AI Trend Analyzer*\n`
620
+
621
+ return md
622
+ }
623
+
624
+ /**
625
+ * Get period key for time series
626
+ */
627
+ getPeriodKey(timestamp, granularity) {
628
+ const date = new Date(timestamp)
629
+
630
+ switch (granularity) {
631
+ case 'hour':
632
+ return `${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()} ${date.getHours()}:00`
633
+ case 'day':
634
+ return `${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()}`
635
+ case 'week': {
636
+ const weekStart = new Date(date)
637
+ weekStart.setDate(date.getDate() - date.getDay())
638
+ return `${weekStart.getFullYear()}-W${this.getWeekNumber(weekStart)}`
639
+ }
640
+ case 'month':
641
+ return `${date.getFullYear()}-${date.getMonth() + 1}`
642
+ default:
643
+ return date.toISOString()
644
+ }
645
+ }
646
+
647
+ /**
648
+ * Get week number
649
+ */
650
+ getWeekNumber(date) {
651
+ const d = new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate()))
652
+ const dayNum = d.getUTCDay() || 7
653
+ d.setUTCDate(d.getUTCDate() + 4 - dayNum)
654
+ const yearStart = new Date(Date.UTC(d.getUTCFullYear(), 0, 1))
655
+ return Math.ceil((((d - yearStart) / 86400000) + 1) / 7)
656
+ }
657
+
658
+ /**
659
+ * Calculate average
660
+ */
661
+ average(arr) {
662
+ if (arr.length === 0) return 0
663
+ return arr.reduce((a, b) => a + b, 0) / arr.length
664
+ }
665
+
666
+ /**
667
+ * Calculate sum
668
+ */
669
+ sum(arr) {
670
+ return arr.reduce((a, b) => a + b, 0)
671
+ }
672
+ }
673
+
674
+ module.exports = new AITrendAnalyzer()