@iservu-inc/adf-cli 0.9.1 → 0.11.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.
- package/.claude/settings.local.json +18 -0
- package/.project/PROJECT-SETTINGS.md +68 -0
- package/.project/chats/current/2025-10-05_UX-IMPROVEMENTS-AND-AI-ANALYSIS-CONFIG.md +389 -0
- package/.project/chats/current/SESSION-STATUS.md +205 -228
- package/.project/docs/DOCUMENTATION-UPDATE-CHECKLIST.md +196 -0
- package/.project/docs/ROADMAP.md +142 -44
- package/.project/docs/designs/LEARNING-ANALYTICS-DASHBOARD.md +1383 -0
- package/.project/docs/designs/PATTERN-DECAY-ALGORITHM.md +526 -0
- package/CHANGELOG.md +683 -0
- package/README.md +119 -24
- package/lib/learning/analytics-exporter.js +241 -0
- package/lib/learning/analytics-view.js +508 -0
- package/lib/learning/analytics.js +681 -0
- package/lib/learning/decay-manager.js +336 -0
- package/lib/learning/learning-manager.js +19 -6
- package/lib/learning/pattern-detector.js +285 -2
- package/lib/learning/storage.js +49 -1
- package/lib/utils/pre-publish-check.js +74 -0
- package/package.json +3 -2
- package/scripts/generate-test-data.js +557 -0
- package/tests/analytics-exporter.test.js +477 -0
- package/tests/analytics-view.test.js +466 -0
- package/tests/analytics.test.js +712 -0
- package/tests/decay-manager.test.js +394 -0
- package/tests/pattern-decay.test.js +339 -0
- /package/.project/chats/{current → complete}/2025-10-05_INTELLIGENT-ANSWER-ANALYSIS.md +0 -0
- /package/.project/chats/{current → complete}/2025-10-05_MULTI-IDE-IMPROVEMENTS.md +0 -0
|
@@ -0,0 +1,508 @@
|
|
|
1
|
+
const inquirer = require('inquirer');
|
|
2
|
+
const chalk = require('chalk');
|
|
3
|
+
const analytics = require('./analytics');
|
|
4
|
+
const exporter = require('./analytics-exporter');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Learning Analytics Dashboard - CLI View
|
|
8
|
+
*
|
|
9
|
+
* Interactive dashboard for visualizing learning system analytics with:
|
|
10
|
+
* - Overview statistics
|
|
11
|
+
* - Skip trends visualization
|
|
12
|
+
* - Category preferences heatmap
|
|
13
|
+
* - Pattern health indicators
|
|
14
|
+
* - ASCII charts and visualizations
|
|
15
|
+
* - Export functionality
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Display analytics dashboard
|
|
20
|
+
* @param {string} projectPath - Project root path
|
|
21
|
+
*/
|
|
22
|
+
async function showAnalyticsDashboard(projectPath) {
|
|
23
|
+
try {
|
|
24
|
+
console.log(chalk.cyan('\n' + '═'.repeat(70)));
|
|
25
|
+
console.log(chalk.cyan.bold(' LEARNING ANALYTICS DASHBOARD'));
|
|
26
|
+
console.log(chalk.cyan('═'.repeat(70)) + '\n');
|
|
27
|
+
|
|
28
|
+
// Generate analytics data
|
|
29
|
+
const analyticsData = await analytics.generateAnalytics(projectPath);
|
|
30
|
+
|
|
31
|
+
// Show main dashboard
|
|
32
|
+
await showMainDashboard(analyticsData, projectPath);
|
|
33
|
+
} catch (error) {
|
|
34
|
+
console.error(chalk.red('\n❌ Error displaying analytics:'), error.message);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Show main dashboard view
|
|
40
|
+
* @param {Object} analyticsData - Complete analytics data
|
|
41
|
+
* @param {string} projectPath - Project root path
|
|
42
|
+
*/
|
|
43
|
+
async function showMainDashboard(analyticsData, projectPath) {
|
|
44
|
+
// Display overview
|
|
45
|
+
displayOverview(analyticsData.overview);
|
|
46
|
+
console.log(chalk.gray('─'.repeat(70)) + '\n');
|
|
47
|
+
|
|
48
|
+
// Display skip trends
|
|
49
|
+
displaySkipTrends(analyticsData.skipTrends);
|
|
50
|
+
console.log(chalk.gray('─'.repeat(70)) + '\n');
|
|
51
|
+
|
|
52
|
+
// Display effectiveness
|
|
53
|
+
displayEffectiveness(analyticsData.effectiveness);
|
|
54
|
+
console.log(chalk.gray('─'.repeat(70)) + '\n');
|
|
55
|
+
|
|
56
|
+
// Interactive menu
|
|
57
|
+
const { action } = await inquirer.prompt([
|
|
58
|
+
{
|
|
59
|
+
type: 'list',
|
|
60
|
+
name: 'action',
|
|
61
|
+
message: 'What would you like to do?',
|
|
62
|
+
choices: [
|
|
63
|
+
{ name: 'View Category Breakdown', value: 'categories' },
|
|
64
|
+
{ name: 'View Pattern Health', value: 'patterns' },
|
|
65
|
+
{ name: 'View Session Timeline', value: 'timeline' },
|
|
66
|
+
{ name: 'View Question Statistics', value: 'questions' },
|
|
67
|
+
{ name: 'View Impactful Patterns', value: 'impact' },
|
|
68
|
+
new inquirer.Separator(),
|
|
69
|
+
{ name: chalk.green('📦 Export Analytics (JSON/CSV)'), value: 'export' },
|
|
70
|
+
new inquirer.Separator(),
|
|
71
|
+
{ name: chalk.gray('← Back to Learning System'), value: 'back' }
|
|
72
|
+
]
|
|
73
|
+
}
|
|
74
|
+
]);
|
|
75
|
+
|
|
76
|
+
// Handle action
|
|
77
|
+
switch (action) {
|
|
78
|
+
case 'categories':
|
|
79
|
+
await showCategoryView(analyticsData);
|
|
80
|
+
await showMainDashboard(analyticsData, projectPath);
|
|
81
|
+
break;
|
|
82
|
+
case 'patterns':
|
|
83
|
+
await showPatternHealthView(analyticsData);
|
|
84
|
+
await showMainDashboard(analyticsData, projectPath);
|
|
85
|
+
break;
|
|
86
|
+
case 'timeline':
|
|
87
|
+
await showTimelineView(analyticsData);
|
|
88
|
+
await showMainDashboard(analyticsData, projectPath);
|
|
89
|
+
break;
|
|
90
|
+
case 'questions':
|
|
91
|
+
await showQuestionStatsView(analyticsData);
|
|
92
|
+
await showMainDashboard(analyticsData, projectPath);
|
|
93
|
+
break;
|
|
94
|
+
case 'impact':
|
|
95
|
+
await showImpactfulPatternsView(analyticsData);
|
|
96
|
+
await showMainDashboard(analyticsData, projectPath);
|
|
97
|
+
break;
|
|
98
|
+
case 'export':
|
|
99
|
+
await handleExport(analyticsData, projectPath);
|
|
100
|
+
await showMainDashboard(analyticsData, projectPath);
|
|
101
|
+
break;
|
|
102
|
+
case 'back':
|
|
103
|
+
// Return to learning system menu
|
|
104
|
+
break;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Display overview statistics
|
|
110
|
+
* @param {Object} overview - Overview data
|
|
111
|
+
*/
|
|
112
|
+
function displayOverview(overview) {
|
|
113
|
+
console.log(chalk.bold('📊 OVERVIEW\n'));
|
|
114
|
+
|
|
115
|
+
console.log(`Total Sessions: ${chalk.cyan(overview.totalSessions)} sessions`);
|
|
116
|
+
console.log(`Total Skips: ${chalk.yellow(overview.totalSkips)} questions (${overview.manualSkips} manual, ${overview.filteredSkips} filtered)`);
|
|
117
|
+
console.log(`Total Answers: ${chalk.green(overview.totalAnswers)} questions`);
|
|
118
|
+
console.log(`Active Patterns: ${chalk.magenta(overview.activePatterns)} patterns`);
|
|
119
|
+
console.log(`Active Rules: ${chalk.blue(overview.activeRules)} rules`);
|
|
120
|
+
console.log(`Learning System Age: ${chalk.white(overview.learningAge)} days`);
|
|
121
|
+
if (overview.dataSizeMB > 0) {
|
|
122
|
+
console.log(`Data Size: ${chalk.white(overview.dataSizeMB.toFixed(2))} MB`);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Display skip trends
|
|
128
|
+
* @param {Array} skipTrends - Skip trends data
|
|
129
|
+
*/
|
|
130
|
+
function displaySkipTrends(skipTrends) {
|
|
131
|
+
console.log(chalk.bold('📈 SKIP TRENDS (Last 12 Weeks)\n'));
|
|
132
|
+
|
|
133
|
+
// Find max for scaling
|
|
134
|
+
const maxSkips = Math.max(...skipTrends.map(t => t.totalSkips), 1);
|
|
135
|
+
const chartWidth = 20;
|
|
136
|
+
|
|
137
|
+
// Show last 4 weeks with bars
|
|
138
|
+
const recentWeeks = skipTrends.slice(-4);
|
|
139
|
+
|
|
140
|
+
for (const week of recentWeeks) {
|
|
141
|
+
const barWidth = Math.round((week.totalSkips / maxSkips) * chartWidth);
|
|
142
|
+
const bar = chalk.cyan('█'.repeat(barWidth)) + chalk.gray('░'.repeat(chartWidth - barWidth));
|
|
143
|
+
const label = `Week ${week.weekNumber}:`.padEnd(10);
|
|
144
|
+
const details = `${week.totalSkips} skips (${week.manualSkips} manual, ${week.filteredSkips} filtered)`;
|
|
145
|
+
|
|
146
|
+
console.log(`${label} ${bar} ${details}`);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Calculate trend
|
|
150
|
+
const trend = calculateTrend(skipTrends);
|
|
151
|
+
console.log(`\nTrend: ${formatTrend(trend)}`);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Display effectiveness metrics
|
|
156
|
+
* @param {Object} effectiveness - Effectiveness data
|
|
157
|
+
*/
|
|
158
|
+
function displayEffectiveness(effectiveness) {
|
|
159
|
+
console.log(chalk.bold('✨ EFFECTIVENESS\n'));
|
|
160
|
+
|
|
161
|
+
console.log(`Time Saved: ${chalk.green(effectiveness.timeSavedMinutes)} minutes (${effectiveness.timeSavedHours} hours)`);
|
|
162
|
+
console.log(`Questions Filtered: ${chalk.yellow(effectiveness.questionsFiltered)} questions`);
|
|
163
|
+
console.log(`Pattern Accuracy: ${chalk.cyan(effectiveness.patternAccuracy + '%')} ${getAccuracyIndicator(effectiveness.patternAccuracy)}`);
|
|
164
|
+
console.log(`Rule Applications: ${chalk.blue(effectiveness.totalRuleApplications)} total (${effectiveness.avgApplicationsPerRule} avg per rule)`);
|
|
165
|
+
|
|
166
|
+
const status = getEffectivenessStatus(effectiveness.overallEffectiveness);
|
|
167
|
+
console.log(`\nOverall Effectiveness: ${chalk.bold(effectiveness.overallEffectiveness + '%')} ${status}`);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Show category view
|
|
172
|
+
* @param {Object} analyticsData - Analytics data
|
|
173
|
+
*/
|
|
174
|
+
async function showCategoryView(analyticsData) {
|
|
175
|
+
console.clear();
|
|
176
|
+
console.log(chalk.cyan('\n' + '═'.repeat(70)));
|
|
177
|
+
console.log(chalk.cyan.bold(' CATEGORY PREFERENCES'));
|
|
178
|
+
console.log(chalk.cyan('═'.repeat(70)) + '\n');
|
|
179
|
+
|
|
180
|
+
const categories = analyticsData.categoryPreferences;
|
|
181
|
+
|
|
182
|
+
if (categories.length === 0) {
|
|
183
|
+
console.log(chalk.gray('No category data available yet.'));
|
|
184
|
+
} else {
|
|
185
|
+
console.log(chalk.bold('🎯 SKIP RATE BY CATEGORY\n'));
|
|
186
|
+
|
|
187
|
+
// Find max for scaling
|
|
188
|
+
const maxRate = 100;
|
|
189
|
+
const chartWidth = 20;
|
|
190
|
+
|
|
191
|
+
for (const cat of categories) {
|
|
192
|
+
const barWidth = Math.round((cat.skipRate / maxRate) * chartWidth);
|
|
193
|
+
const bar = chalk.red('█'.repeat(barWidth)) + chalk.gray('░'.repeat(chartWidth - barWidth));
|
|
194
|
+
const label = cat.category.padEnd(20);
|
|
195
|
+
const details = `${cat.skipRate}% (${cat.skips}/${cat.total})`.padEnd(15);
|
|
196
|
+
const level = getLevelBadge(cat.level);
|
|
197
|
+
|
|
198
|
+
console.log(`${label} ${bar} ${details} ${level}`);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
await pressAnyKey();
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Show pattern health view
|
|
207
|
+
* @param {Object} analyticsData - Analytics data
|
|
208
|
+
*/
|
|
209
|
+
async function showPatternHealthView(analyticsData) {
|
|
210
|
+
console.clear();
|
|
211
|
+
console.log(chalk.cyan('\n' + '═'.repeat(70)));
|
|
212
|
+
console.log(chalk.cyan.bold(' PATTERN HEALTH'));
|
|
213
|
+
console.log(chalk.cyan('═'.repeat(70)) + '\n');
|
|
214
|
+
|
|
215
|
+
const { patternDistribution, decayStatus } = analyticsData;
|
|
216
|
+
|
|
217
|
+
console.log(chalk.bold('🧬 PATTERN CONFIDENCE DISTRIBUTION\n'));
|
|
218
|
+
|
|
219
|
+
const chartWidth = 10;
|
|
220
|
+
const total = patternDistribution.totalPatterns;
|
|
221
|
+
|
|
222
|
+
if (total === 0) {
|
|
223
|
+
console.log(chalk.gray('No patterns detected yet.'));
|
|
224
|
+
} else {
|
|
225
|
+
displayDistributionBar('High (90%+):', patternDistribution.distribution.high, total, chartWidth, chalk.green);
|
|
226
|
+
displayDistributionBar('Medium (75-89%):', patternDistribution.distribution.medium, total, chartWidth, chalk.yellow);
|
|
227
|
+
displayDistributionBar('Low (50-74%):', patternDistribution.distribution.low, total, chartWidth, chalk.orange);
|
|
228
|
+
displayDistributionBar('Very Low (<50%):', patternDistribution.distribution.veryLow, total, chartWidth, chalk.red);
|
|
229
|
+
|
|
230
|
+
console.log(`\nAverage Confidence: ${chalk.cyan(patternDistribution.avgConfidence + '%')}`);
|
|
231
|
+
console.log(`At Risk (decay): ${chalk.red(patternDistribution.atRiskCount)} patterns\n`);
|
|
232
|
+
|
|
233
|
+
console.log(chalk.bold('⏰ PATTERN DECAY STATUS\n'));
|
|
234
|
+
|
|
235
|
+
displayDistributionBar('Healthy (75%+, <3mo):', decayStatus.healthy, total, chartWidth, chalk.green);
|
|
236
|
+
displayDistributionBar('Warning (50-74%, 3-5mo):', decayStatus.warning, total, chartWidth, chalk.yellow);
|
|
237
|
+
displayDistributionBar('Critical (<50%, >5mo):', decayStatus.critical, total, chartWidth, chalk.red);
|
|
238
|
+
|
|
239
|
+
console.log(`\nRecently Renewed: ${chalk.green(decayStatus.recentlyRenewed)} patterns (last 30 days)`);
|
|
240
|
+
console.log(`Average Age: ${chalk.white(decayStatus.avgAge)} days`);
|
|
241
|
+
|
|
242
|
+
if (decayStatus.critical > 0) {
|
|
243
|
+
console.log(`\n${chalk.yellow('⚠')} Warning: ${decayStatus.critical} patterns approaching removal threshold`);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
await pressAnyKey();
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Show timeline view
|
|
252
|
+
* @param {Object} analyticsData - Analytics data
|
|
253
|
+
*/
|
|
254
|
+
async function showTimelineView(analyticsData) {
|
|
255
|
+
console.clear();
|
|
256
|
+
console.log(chalk.cyan('\n' + '═'.repeat(70)));
|
|
257
|
+
console.log(chalk.cyan.bold(' SESSION TIMELINE'));
|
|
258
|
+
console.log(chalk.cyan('═'.repeat(70)) + '\n');
|
|
259
|
+
|
|
260
|
+
const { sessionTimeline } = analyticsData;
|
|
261
|
+
|
|
262
|
+
console.log(chalk.bold('📅 SESSION HISTORY\n'));
|
|
263
|
+
console.log(`Total Sessions: ${chalk.cyan(sessionTimeline.totalSessions)}`);
|
|
264
|
+
console.log(`Sessions per Week: ${chalk.yellow(sessionTimeline.sessionsPerWeek)}`);
|
|
265
|
+
console.log(`First Session: ${chalk.white(sessionTimeline.firstSession || 'N/A')}`);
|
|
266
|
+
console.log(`Last Session: ${chalk.white(sessionTimeline.lastSession || 'N/A')}\n`);
|
|
267
|
+
|
|
268
|
+
if (sessionTimeline.timeline.length > 0) {
|
|
269
|
+
console.log(chalk.bold('Recent Sessions (Last 10):\n'));
|
|
270
|
+
|
|
271
|
+
const recentSessions = sessionTimeline.timeline.slice(0, 10);
|
|
272
|
+
|
|
273
|
+
for (const session of recentSessions) {
|
|
274
|
+
const date = new Date(session.timestamp).toLocaleDateString();
|
|
275
|
+
const time = new Date(session.timestamp).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
|
|
276
|
+
const projectType = session.projectType || 'unknown';
|
|
277
|
+
const stats = `${session.answerCount}A / ${session.skipCount}S`;
|
|
278
|
+
|
|
279
|
+
console.log(`${chalk.gray(date + ' ' + time)} - ${chalk.cyan(projectType.padEnd(15))} ${chalk.white(stats)}`);
|
|
280
|
+
}
|
|
281
|
+
} else {
|
|
282
|
+
console.log(chalk.gray('No sessions recorded yet.'));
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
await pressAnyKey();
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Show question stats view
|
|
290
|
+
* @param {Object} analyticsData - Analytics data
|
|
291
|
+
*/
|
|
292
|
+
async function showQuestionStatsView(analyticsData) {
|
|
293
|
+
console.clear();
|
|
294
|
+
console.log(chalk.cyan('\n' + '═'.repeat(70)));
|
|
295
|
+
console.log(chalk.cyan.bold(' QUESTION STATISTICS'));
|
|
296
|
+
console.log(chalk.cyan('═'.repeat(70)) + '\n');
|
|
297
|
+
|
|
298
|
+
const { questionStats } = analyticsData;
|
|
299
|
+
|
|
300
|
+
// Most answered
|
|
301
|
+
console.log(chalk.bold('✅ MOST ANSWERED QUESTIONS (Top 5)\n'));
|
|
302
|
+
if (questionStats.mostAnswered.length > 0) {
|
|
303
|
+
questionStats.mostAnswered.slice(0, 5).forEach((q, i) => {
|
|
304
|
+
console.log(`${chalk.cyan((i + 1) + '.')} ${q.text}`);
|
|
305
|
+
console.log(` ${chalk.gray(`Answered ${q.answers} times | Category: ${q.category || 'N/A'}`)}\n`);
|
|
306
|
+
});
|
|
307
|
+
} else {
|
|
308
|
+
console.log(chalk.gray('No data available.\n'));
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// Most skipped
|
|
312
|
+
console.log(chalk.bold('⏭️ MOST SKIPPED QUESTIONS (Top 5)\n'));
|
|
313
|
+
if (questionStats.mostSkipped.length > 0) {
|
|
314
|
+
questionStats.mostSkipped.slice(0, 5).forEach((q, i) => {
|
|
315
|
+
console.log(`${chalk.yellow((i + 1) + '.')} ${q.text}`);
|
|
316
|
+
console.log(` ${chalk.gray(`Skipped ${q.skips} times | Skip rate: ${q.skipRate}%`)}\n`);
|
|
317
|
+
});
|
|
318
|
+
} else {
|
|
319
|
+
console.log(chalk.gray('No data available.\n'));
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
await pressAnyKey();
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Show impactful patterns view
|
|
327
|
+
* @param {Object} analyticsData - Analytics data
|
|
328
|
+
*/
|
|
329
|
+
async function showImpactfulPatternsView(analyticsData) {
|
|
330
|
+
console.clear();
|
|
331
|
+
console.log(chalk.cyan('\n' + '═'.repeat(70)));
|
|
332
|
+
console.log(chalk.cyan.bold(' IMPACTFUL PATTERNS'));
|
|
333
|
+
console.log(chalk.cyan('═'.repeat(70)) + '\n');
|
|
334
|
+
|
|
335
|
+
const { impactfulPatterns } = analyticsData;
|
|
336
|
+
|
|
337
|
+
// Top by time saved
|
|
338
|
+
console.log(chalk.bold('⏱️ TOP PATTERNS BY TIME SAVED\n'));
|
|
339
|
+
if (impactfulPatterns.topByTimeSaved.length > 0) {
|
|
340
|
+
impactfulPatterns.topByTimeSaved.forEach((item, i) => {
|
|
341
|
+
console.log(`${chalk.green((i + 1) + '.')} ${item.pattern.questionText || item.pattern.questionId}`);
|
|
342
|
+
console.log(` ${chalk.gray(`${item.timeSaved} minutes saved | ${item.appliedCount} applications | ${item.pattern.confidence}% confidence`)}\n`);
|
|
343
|
+
});
|
|
344
|
+
} else {
|
|
345
|
+
console.log(chalk.gray('No patterns with impact yet.\n'));
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// Perfect confidence
|
|
349
|
+
if (impactfulPatterns.perfectConfidence.length > 0) {
|
|
350
|
+
console.log(chalk.bold(`💯 PERFECT CONFIDENCE PATTERNS (${impactfulPatterns.perfectConfidence.length})\n`));
|
|
351
|
+
impactfulPatterns.perfectConfidence.slice(0, 5).forEach((pattern, i) => {
|
|
352
|
+
console.log(`${chalk.magenta((i + 1) + '.')} ${pattern.questionText || pattern.questionId}`);
|
|
353
|
+
console.log(` ${chalk.gray(`100% confidence | Type: ${pattern.type}`)}\n`);
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
await pressAnyKey();
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* Handle export
|
|
362
|
+
* @param {Object} analyticsData - Analytics data
|
|
363
|
+
* @param {string} projectPath - Project root path
|
|
364
|
+
*/
|
|
365
|
+
async function handleExport(analyticsData, projectPath) {
|
|
366
|
+
console.clear();
|
|
367
|
+
console.log(chalk.cyan('\n' + '═'.repeat(70)));
|
|
368
|
+
console.log(chalk.cyan.bold(' EXPORT ANALYTICS'));
|
|
369
|
+
console.log(chalk.cyan('═'.repeat(70)) + '\n');
|
|
370
|
+
|
|
371
|
+
const { format } = await inquirer.prompt([
|
|
372
|
+
{
|
|
373
|
+
type: 'list',
|
|
374
|
+
name: 'format',
|
|
375
|
+
message: 'Select export format:',
|
|
376
|
+
choices: [
|
|
377
|
+
{ name: 'JSON (single comprehensive file)', value: 'json' },
|
|
378
|
+
{ name: 'CSV (multiple files)', value: 'csv' },
|
|
379
|
+
new inquirer.Separator(),
|
|
380
|
+
{ name: chalk.gray('← Cancel'), value: 'cancel' }
|
|
381
|
+
]
|
|
382
|
+
}
|
|
383
|
+
]);
|
|
384
|
+
|
|
385
|
+
if (format === 'cancel') return;
|
|
386
|
+
|
|
387
|
+
try {
|
|
388
|
+
const result = await exporter.exportAnalytics(analyticsData, projectPath, format);
|
|
389
|
+
|
|
390
|
+
console.log(chalk.green('\n✅ Analytics exported successfully!\n'));
|
|
391
|
+
|
|
392
|
+
if (format === 'json') {
|
|
393
|
+
console.log(`File: ${chalk.cyan(result.file)}`);
|
|
394
|
+
console.log(`Size: ${chalk.white(result.size)}`);
|
|
395
|
+
} else {
|
|
396
|
+
console.log(`Files exported: ${chalk.cyan(result.files.length)}`);
|
|
397
|
+
result.files.forEach(f => {
|
|
398
|
+
console.log(` - ${chalk.white(f)}`);
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
console.log();
|
|
403
|
+
} catch (error) {
|
|
404
|
+
console.error(chalk.red('\n❌ Export failed:'), error.message);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
await pressAnyKey();
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// =============================================================================
|
|
411
|
+
// Helper Functions
|
|
412
|
+
// =============================================================================
|
|
413
|
+
|
|
414
|
+
/**
|
|
415
|
+
* Display distribution bar
|
|
416
|
+
* @param {string} label - Label
|
|
417
|
+
* @param {number} count - Count
|
|
418
|
+
* @param {number} total - Total
|
|
419
|
+
* @param {number} chartWidth - Chart width
|
|
420
|
+
* @param {Function} colorFn - Chalk color function
|
|
421
|
+
*/
|
|
422
|
+
function displayDistributionBar(label, count, total, chartWidth, colorFn) {
|
|
423
|
+
const percentage = total > 0 ? Math.round((count / total) * 100) : 0;
|
|
424
|
+
const barWidth = Math.round((count / total) * chartWidth);
|
|
425
|
+
const bar = colorFn('█'.repeat(barWidth)) + chalk.gray('░'.repeat(chartWidth - barWidth));
|
|
426
|
+
|
|
427
|
+
console.log(`${label.padEnd(25)} ${bar} ${count} (${percentage}%)`);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
/**
|
|
431
|
+
* Calculate trend from skip trends
|
|
432
|
+
* @param {Array} skipTrends - Skip trends data
|
|
433
|
+
* @returns {string} Trend direction
|
|
434
|
+
*/
|
|
435
|
+
function calculateTrend(skipTrends) {
|
|
436
|
+
if (skipTrends.length < 4) return 'stable';
|
|
437
|
+
|
|
438
|
+
const recent = skipTrends.slice(-4);
|
|
439
|
+
const first = recent[0].totalSkips;
|
|
440
|
+
const last = recent[recent.length - 1].totalSkips;
|
|
441
|
+
|
|
442
|
+
if (last > first * 1.2) return 'increasing';
|
|
443
|
+
if (last < first * 0.8) return 'decreasing';
|
|
444
|
+
return 'stable';
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
/**
|
|
448
|
+
* Format trend with icon and color
|
|
449
|
+
* @param {string} trend - Trend direction
|
|
450
|
+
* @returns {string} Formatted trend
|
|
451
|
+
*/
|
|
452
|
+
function formatTrend(trend) {
|
|
453
|
+
if (trend === 'increasing') return chalk.red('Increasing ↑');
|
|
454
|
+
if (trend === 'decreasing') return chalk.green('Decreasing ↓');
|
|
455
|
+
return chalk.yellow('Stable →');
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
/**
|
|
459
|
+
* Get accuracy indicator
|
|
460
|
+
* @param {number} accuracy - Accuracy percentage
|
|
461
|
+
* @returns {string} Indicator
|
|
462
|
+
*/
|
|
463
|
+
function getAccuracyIndicator(accuracy) {
|
|
464
|
+
if (accuracy >= 90) return chalk.green('(Excellent)');
|
|
465
|
+
if (accuracy >= 75) return chalk.yellow('(Good)');
|
|
466
|
+
if (accuracy >= 50) return chalk.orange('(Fair)');
|
|
467
|
+
return chalk.red('(Needs Improvement)');
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
/**
|
|
471
|
+
* Get effectiveness status
|
|
472
|
+
* @param {number} effectiveness - Effectiveness percentage
|
|
473
|
+
* @returns {string} Status
|
|
474
|
+
*/
|
|
475
|
+
function getEffectivenessStatus(effectiveness) {
|
|
476
|
+
if (effectiveness >= 90) return chalk.green('✓ EXCELLENT');
|
|
477
|
+
if (effectiveness >= 75) return chalk.green('✓ GOOD');
|
|
478
|
+
if (effectiveness >= 50) return chalk.yellow('○ FAIR');
|
|
479
|
+
return chalk.red('✗ NEEDS IMPROVEMENT');
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
/**
|
|
483
|
+
* Get level badge
|
|
484
|
+
* @param {string} level - Level ('high', 'medium', 'low')
|
|
485
|
+
* @returns {string} Badge
|
|
486
|
+
*/
|
|
487
|
+
function getLevelBadge(level) {
|
|
488
|
+
if (level === 'high') return chalk.red.bold('HIGH');
|
|
489
|
+
if (level === 'medium') return chalk.yellow.bold('MEDIUM');
|
|
490
|
+
return chalk.green.bold('LOW');
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
/**
|
|
494
|
+
* Press any key to continue
|
|
495
|
+
*/
|
|
496
|
+
async function pressAnyKey() {
|
|
497
|
+
await inquirer.prompt([
|
|
498
|
+
{
|
|
499
|
+
type: 'input',
|
|
500
|
+
name: 'continue',
|
|
501
|
+
message: chalk.gray('Press Enter to continue...')
|
|
502
|
+
}
|
|
503
|
+
]);
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
module.exports = {
|
|
507
|
+
showAnalyticsDashboard
|
|
508
|
+
};
|