@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
package/README.md
CHANGED
|
@@ -62,7 +62,7 @@ adf init --rapid --tool cursor
|
|
|
62
62
|
|
|
63
63
|
### Configure ADF Settings
|
|
64
64
|
|
|
65
|
-
Configure ADF settings like AI provider, learning system, and more:
|
|
65
|
+
Configure ADF settings like AI provider, AI analysis settings, learning system, and more:
|
|
66
66
|
|
|
67
67
|
```bash
|
|
68
68
|
adf config
|
|
@@ -70,14 +70,17 @@ adf config
|
|
|
70
70
|
|
|
71
71
|
This command provides an interactive menu with:
|
|
72
72
|
- **AI Provider Setup** - Configure Anthropic, OpenAI, Google Gemini, or OpenRouter
|
|
73
|
+
- **AI Analysis Settings** - Configure performance modes and AI features
|
|
74
|
+
- **IDE Deployment** - Deploy to multiple IDEs
|
|
73
75
|
- **Learning System** - Manage interview learning data and preferences
|
|
74
76
|
- Status indicators showing what's configured (green ✓), disabled (yellow ○), or has data
|
|
75
|
-
- Future configuration options (coming soon)
|
|
76
77
|
|
|
77
78
|
You can run `adf config` anytime to:
|
|
78
79
|
- Configure AI for the first time
|
|
79
80
|
- Switch AI providers or models
|
|
80
81
|
- Update your API keys
|
|
82
|
+
- Adjust AI analysis performance and features
|
|
83
|
+
- Deploy to development tools
|
|
81
84
|
- Review learning patterns and manage learned preferences
|
|
82
85
|
|
|
83
86
|
### Deploy to Development Tool
|
|
@@ -203,6 +206,78 @@ Select "AI Provider Setup" and you'll see:
|
|
|
203
206
|
|
|
204
207
|
Your previous API keys remain saved in `.adf/.env` for easy switching between providers.
|
|
205
208
|
|
|
209
|
+
## AI Analysis Settings
|
|
210
|
+
|
|
211
|
+
ADF CLI provides three performance modes and five configurable AI features, giving you complete control over the speed vs intelligence tradeoff during interviews.
|
|
212
|
+
|
|
213
|
+
### Performance Modes
|
|
214
|
+
|
|
215
|
+
Configure via `adf config` → AI Analysis Settings:
|
|
216
|
+
|
|
217
|
+
**Fast Mode:**
|
|
218
|
+
- Zero AI delays (~0.5s per answer)
|
|
219
|
+
- All AI features disabled
|
|
220
|
+
- Best for: Quick workflows, prototyping, low-priority projects
|
|
221
|
+
|
|
222
|
+
**Balanced Mode (Default):**
|
|
223
|
+
- 2-3 seconds per answer
|
|
224
|
+
- Quality Analysis, Smart Filtering, and Pattern Detection enabled
|
|
225
|
+
- Question Reordering and Follow-up Questions disabled
|
|
226
|
+
- Best for: Most projects - optimal balance of speed and intelligence
|
|
227
|
+
|
|
228
|
+
**Comprehensive Mode:**
|
|
229
|
+
- 4-6 seconds per answer
|
|
230
|
+
- All AI features enabled
|
|
231
|
+
- Maximum intelligence and guidance
|
|
232
|
+
- Best for: Complex projects, critical systems, maximum thoroughness
|
|
233
|
+
|
|
234
|
+
### Configurable AI Features
|
|
235
|
+
|
|
236
|
+
Five AI features can be toggled individually:
|
|
237
|
+
|
|
238
|
+
1. **AI-Powered Quality Analysis** (Medium Impact: 1-2s)
|
|
239
|
+
- Real-time answer quality scoring (0-100)
|
|
240
|
+
- Improvement suggestions for low-quality answers
|
|
241
|
+
- Helps ensure comprehensive requirements gathering
|
|
242
|
+
|
|
243
|
+
2. **Intelligent Question Reordering** (High Impact: 2-3s)
|
|
244
|
+
- Dynamically reorders questions based on extracted knowledge
|
|
245
|
+
- Prioritizes fundamental questions first
|
|
246
|
+
- Adapts interview flow to your answers
|
|
247
|
+
|
|
248
|
+
3. **AI-Generated Follow-Up Questions** (Medium Impact: 1-2s when triggered)
|
|
249
|
+
- Context-specific follow-ups for incomplete answers
|
|
250
|
+
- Only triggers for low-quality answers (< 70 score)
|
|
251
|
+
- Helps gather missing information intelligently
|
|
252
|
+
|
|
253
|
+
4. **Pattern Detection & Learning** (Low Impact: minimal)
|
|
254
|
+
- Learns from your skip behavior over time
|
|
255
|
+
- Generates learned rules from patterns
|
|
256
|
+
- Lightweight, runs locally
|
|
257
|
+
|
|
258
|
+
5. **Smart Question Filtering** (Low Impact: minimal)
|
|
259
|
+
- Analyzes project type and context
|
|
260
|
+
- Filters out irrelevant questions automatically
|
|
261
|
+
- Saves time on specialized projects (CLI tools, APIs, etc.)
|
|
262
|
+
|
|
263
|
+
### Configuration
|
|
264
|
+
|
|
265
|
+
Access AI Analysis Settings:
|
|
266
|
+
|
|
267
|
+
```bash
|
|
268
|
+
adf config
|
|
269
|
+
# Select "AI Analysis Settings"
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
Settings are saved to `.adf/analysis-config.json` and apply to all future interviews.
|
|
273
|
+
|
|
274
|
+
**Performance Mode Display:**
|
|
275
|
+
At interview start, you'll see:
|
|
276
|
+
```
|
|
277
|
+
🎛️ AI Analysis Mode: Balanced
|
|
278
|
+
(Configure via: adf config → AI Analysis Settings)
|
|
279
|
+
```
|
|
280
|
+
|
|
206
281
|
## Learning System
|
|
207
282
|
|
|
208
283
|
ADF CLI includes an intelligent Learning System that improves your interview experience by learning from your behavior across sessions.
|
|
@@ -216,7 +291,13 @@ ADF CLI includes an intelligent Learning System that improves your interview exp
|
|
|
216
291
|
- Framework-specific skips (e.g., routing questions for Next.js projects)
|
|
217
292
|
- Answer style preferences (brief vs detailed)
|
|
218
293
|
3. **Rule Generation** - Converts high-confidence patterns (≥75%) into learned rules
|
|
219
|
-
4. **
|
|
294
|
+
4. **Pattern Decay** - Automatically keeps patterns fresh and relevant:
|
|
295
|
+
- Inactive patterns lose confidence over time using exponential decay
|
|
296
|
+
- High-confidence patterns (≥90%) decay slower than weak patterns
|
|
297
|
+
- Patterns reconfirmed by your behavior get +10 confidence boost
|
|
298
|
+
- Stale patterns (confidence <40 or inactive 6+ months) automatically removed
|
|
299
|
+
- User-approved patterns protected (decay at half rate)
|
|
300
|
+
5. **Adaptive Filtering** - Applies learned rules in future interviews (with your approval)
|
|
220
301
|
|
|
221
302
|
### Managing Learning Data
|
|
222
303
|
|
|
@@ -414,27 +495,41 @@ When we release updates to the framework:
|
|
|
414
495
|
|
|
415
496
|
See [CHANGELOG.md](./CHANGELOG.md) for detailed version history.
|
|
416
497
|
|
|
417
|
-
**Latest:** v0.
|
|
418
|
-
- **
|
|
419
|
-
-
|
|
420
|
-
-
|
|
421
|
-
-
|
|
422
|
-
-
|
|
423
|
-
-
|
|
424
|
-
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
-
|
|
429
|
-
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
-
|
|
433
|
-
-
|
|
434
|
-
-
|
|
435
|
-
-
|
|
436
|
-
-
|
|
437
|
-
-
|
|
498
|
+
**Latest:** v0.10.0 (2025-10-27)
|
|
499
|
+
- **Pattern Decay Algorithm (v0.10.0)** - Phase 6: Advanced Learning Features
|
|
500
|
+
- Time-based exponential decay for inactive patterns
|
|
501
|
+
- Confidence-based decay rates (high/medium/low)
|
|
502
|
+
- Automatic pattern cleanup and renewal system
|
|
503
|
+
- 40+ comprehensive tests for decay functionality
|
|
504
|
+
- Pattern metadata tracking (timestamps, renewal counts)
|
|
505
|
+
- Removal history and analytics
|
|
506
|
+
|
|
507
|
+
**Previous Release:** v0.9.1 (2025-10-05)
|
|
508
|
+
- **AI Analysis Settings (v0.9.0)** - Performance modes and configurable AI features
|
|
509
|
+
- Three performance modes: Fast, Balanced, Comprehensive
|
|
510
|
+
- Five individually configurable AI features
|
|
511
|
+
- Complete control over speed vs intelligence tradeoff
|
|
512
|
+
- New configuration category in `adf config`
|
|
513
|
+
- Performance mode display at interview start
|
|
514
|
+
- **Resume from Exit (v0.8.0)** - Resume interviews after exit
|
|
515
|
+
- Type `exit` or press Ctrl+C to save progress and quit
|
|
516
|
+
- Resume with `adf init` continues from last question
|
|
517
|
+
- Already-answered questions automatically skipped
|
|
518
|
+
- Graceful quit handling everywhere
|
|
519
|
+
- **UX Improvements (v0.5.1-v0.7.1)** - Enhanced user experience
|
|
520
|
+
- Terminal input restoration (v0.7.1)
|
|
521
|
+
- Better existing project detection with clear options
|
|
522
|
+
- Post-install information display
|
|
523
|
+
- Configuration validation and auto-reset
|
|
524
|
+
|
|
525
|
+
**Previous Releases:**
|
|
526
|
+
- **v0.5.0** - Intelligent Answer Analysis & Dynamic Question Pipeline
|
|
527
|
+
- **v0.4.36** - Multi-IDE Improvements & Config Command Enhancement
|
|
528
|
+
- **v0.4.12** - Learning System (Phase 4.2) & Smart Question Filtering (Phase 4.1)
|
|
529
|
+
- **v0.3.6** - Configuration Command & Optional AI Setup
|
|
530
|
+
- **v0.3.4** - Multi-Provider AI Integration (Anthropic, OpenAI, Google, OpenRouter)
|
|
531
|
+
- **v0.2.0** - Quality-Based Progress Tracking & Resume Capability
|
|
532
|
+
- **v0.1.0** - Initial Release
|
|
438
533
|
|
|
439
534
|
## Troubleshooting
|
|
440
535
|
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
const fs = require('fs-extra');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Learning Analytics Exporter
|
|
6
|
+
*
|
|
7
|
+
* Handles exporting analytics data in multiple formats:
|
|
8
|
+
* - JSON: Single comprehensive file with all analytics
|
|
9
|
+
* - CSV: Multiple files for different metrics
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Export analytics data
|
|
14
|
+
* @param {Object} analyticsData - Complete analytics data
|
|
15
|
+
* @param {string} projectPath - Project root path
|
|
16
|
+
* @param {string} format - Export format ('json' or 'csv')
|
|
17
|
+
* @returns {Promise<Object>} Export result with file paths
|
|
18
|
+
*/
|
|
19
|
+
async function exportAnalytics(analyticsData, projectPath, format = 'json') {
|
|
20
|
+
const learningPath = path.join(projectPath, '.adf', 'learning');
|
|
21
|
+
const exportsPath = path.join(learningPath, 'exports');
|
|
22
|
+
|
|
23
|
+
// Ensure exports directory exists
|
|
24
|
+
await fs.ensureDir(exportsPath);
|
|
25
|
+
|
|
26
|
+
if (format === 'json') {
|
|
27
|
+
return await exportJSON(analyticsData, exportsPath);
|
|
28
|
+
} else if (format === 'csv') {
|
|
29
|
+
return await exportCSV(analyticsData, exportsPath);
|
|
30
|
+
} else {
|
|
31
|
+
throw new Error(`Unsupported export format: ${format}`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Export analytics as JSON
|
|
37
|
+
* @param {Object} analyticsData - Analytics data
|
|
38
|
+
* @param {string} exportsPath - Exports directory path
|
|
39
|
+
* @returns {Promise<Object>} Export result
|
|
40
|
+
*/
|
|
41
|
+
async function exportJSON(analyticsData, exportsPath) {
|
|
42
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, -5);
|
|
43
|
+
const filename = `analytics-${timestamp}.json`;
|
|
44
|
+
const filePath = path.join(exportsPath, filename);
|
|
45
|
+
|
|
46
|
+
// Write JSON file
|
|
47
|
+
await fs.writeJSON(filePath, analyticsData, { spaces: 2 });
|
|
48
|
+
|
|
49
|
+
// Get file size
|
|
50
|
+
const stats = await fs.stat(filePath);
|
|
51
|
+
const sizeKB = (stats.size / 1024).toFixed(2);
|
|
52
|
+
|
|
53
|
+
return {
|
|
54
|
+
file: filePath,
|
|
55
|
+
size: `${sizeKB} KB`,
|
|
56
|
+
format: 'json'
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Export analytics as CSV (multiple files)
|
|
62
|
+
* @param {Object} analyticsData - Analytics data
|
|
63
|
+
* @param {string} exportsPath - Exports directory path
|
|
64
|
+
* @returns {Promise<Object>} Export result
|
|
65
|
+
*/
|
|
66
|
+
async function exportCSV(analyticsData, exportsPath) {
|
|
67
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, -5);
|
|
68
|
+
const files = [];
|
|
69
|
+
|
|
70
|
+
// Export overview
|
|
71
|
+
const overviewFile = path.join(exportsPath, `analytics-overview-${timestamp}.csv`);
|
|
72
|
+
await fs.writeFile(overviewFile, generateOverviewCSV(analyticsData.overview));
|
|
73
|
+
files.push(overviewFile);
|
|
74
|
+
|
|
75
|
+
// Export skip trends
|
|
76
|
+
const trendsFile = path.join(exportsPath, `analytics-skip-trends-${timestamp}.csv`);
|
|
77
|
+
await fs.writeFile(trendsFile, generateSkipTrendsCSV(analyticsData.skipTrends));
|
|
78
|
+
files.push(trendsFile);
|
|
79
|
+
|
|
80
|
+
// Export category preferences
|
|
81
|
+
const categoriesFile = path.join(exportsPath, `analytics-categories-${timestamp}.csv`);
|
|
82
|
+
await fs.writeFile(categoriesFile, generateCategoriesCSV(analyticsData.categoryPreferences));
|
|
83
|
+
files.push(categoriesFile);
|
|
84
|
+
|
|
85
|
+
// Export patterns
|
|
86
|
+
const patternsFile = path.join(exportsPath, `analytics-patterns-${timestamp}.csv`);
|
|
87
|
+
await fs.writeFile(patternsFile, generatePatternsCSV(analyticsData.patternDistribution, analyticsData.decayStatus));
|
|
88
|
+
files.push(patternsFile);
|
|
89
|
+
|
|
90
|
+
// Export effectiveness
|
|
91
|
+
const effectivenessFile = path.join(exportsPath, `analytics-effectiveness-${timestamp}.csv`);
|
|
92
|
+
await fs.writeFile(effectivenessFile, generateEffectivenessCSV(analyticsData.effectiveness));
|
|
93
|
+
files.push(effectivenessFile);
|
|
94
|
+
|
|
95
|
+
return {
|
|
96
|
+
files,
|
|
97
|
+
format: 'csv'
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Generate overview CSV
|
|
103
|
+
* @param {Object} overview - Overview data
|
|
104
|
+
* @returns {string} CSV content
|
|
105
|
+
*/
|
|
106
|
+
function generateOverviewCSV(overview) {
|
|
107
|
+
const rows = [
|
|
108
|
+
['Metric', 'Value', 'Unit'],
|
|
109
|
+
['Total Sessions', overview.totalSessions, 'sessions'],
|
|
110
|
+
['Total Skips', overview.totalSkips, 'questions'],
|
|
111
|
+
['Manual Skips', overview.manualSkips, 'questions'],
|
|
112
|
+
['Filtered Skips', overview.filteredSkips, 'questions'],
|
|
113
|
+
['Total Answers', overview.totalAnswers, 'questions'],
|
|
114
|
+
['Active Patterns', overview.activePatterns, 'patterns'],
|
|
115
|
+
['Active Rules', overview.activeRules, 'rules'],
|
|
116
|
+
['Learning Age', overview.learningAge, 'days']
|
|
117
|
+
];
|
|
118
|
+
|
|
119
|
+
return rows.map(row => row.join(',')).join('\n');
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Generate skip trends CSV
|
|
124
|
+
* @param {Array} skipTrends - Skip trends data
|
|
125
|
+
* @returns {string} CSV content
|
|
126
|
+
*/
|
|
127
|
+
function generateSkipTrendsCSV(skipTrends) {
|
|
128
|
+
const rows = [
|
|
129
|
+
['Week', 'Week Number', 'Manual Skips', 'Filtered Skips', 'Total Skips', 'Skip Rate (%)']
|
|
130
|
+
];
|
|
131
|
+
|
|
132
|
+
for (const trend of skipTrends) {
|
|
133
|
+
rows.push([
|
|
134
|
+
trend.week,
|
|
135
|
+
trend.weekNumber,
|
|
136
|
+
trend.manualSkips,
|
|
137
|
+
trend.filteredSkips,
|
|
138
|
+
trend.totalSkips,
|
|
139
|
+
trend.skipRate
|
|
140
|
+
]);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return rows.map(row => row.join(',')).join('\n');
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Generate categories CSV
|
|
148
|
+
* @param {Array} categories - Category preferences data
|
|
149
|
+
* @returns {string} CSV content
|
|
150
|
+
*/
|
|
151
|
+
function generateCategoriesCSV(categories) {
|
|
152
|
+
const rows = [
|
|
153
|
+
['Category', 'Skips', 'Answers', 'Total', 'Skip Rate (%)', 'Level']
|
|
154
|
+
];
|
|
155
|
+
|
|
156
|
+
for (const cat of categories) {
|
|
157
|
+
rows.push([
|
|
158
|
+
escapeCSV(cat.category),
|
|
159
|
+
cat.skips,
|
|
160
|
+
cat.answers,
|
|
161
|
+
cat.total,
|
|
162
|
+
cat.skipRate,
|
|
163
|
+
cat.level
|
|
164
|
+
]);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return rows.map(row => row.join(',')).join('\n');
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Generate patterns CSV
|
|
172
|
+
* @param {Object} patternDistribution - Pattern distribution data
|
|
173
|
+
* @param {Object} decayStatus - Decay status data
|
|
174
|
+
* @returns {string} CSV content
|
|
175
|
+
*/
|
|
176
|
+
function generatePatternsCSV(patternDistribution, decayStatus) {
|
|
177
|
+
const dist = patternDistribution.distribution || { high: 0, medium: 0, low: 0, veryLow: 0 };
|
|
178
|
+
const decay = decayStatus || { healthy: 0, warning: 0, critical: 0, recentlyRenewed: 0, avgAge: 0 };
|
|
179
|
+
|
|
180
|
+
const rows = [
|
|
181
|
+
['Metric', 'Value', 'Description'],
|
|
182
|
+
['Total Patterns', patternDistribution.totalPatterns || 0, 'All patterns'],
|
|
183
|
+
['High Confidence (90%+)', dist.high, 'Very strong patterns'],
|
|
184
|
+
['Medium Confidence (75-89%)', dist.medium, 'Strong patterns'],
|
|
185
|
+
['Low Confidence (50-74%)', dist.low, 'Weak patterns'],
|
|
186
|
+
['Very Low Confidence (<50%)', dist.veryLow, 'Very weak patterns'],
|
|
187
|
+
['Average Confidence', (patternDistribution.avgConfidence || 0) + '%', 'Mean confidence score'],
|
|
188
|
+
['At Risk', patternDistribution.atRiskCount || 0, 'Patterns at risk of removal'],
|
|
189
|
+
['', '', ''],
|
|
190
|
+
['Decay Status', '', ''],
|
|
191
|
+
['Healthy Patterns', decay.healthy, 'Active and strong'],
|
|
192
|
+
['Warning Patterns', decay.warning, 'Needs attention'],
|
|
193
|
+
['Critical Patterns', decay.critical, 'May be removed soon'],
|
|
194
|
+
['Recently Renewed', decay.recentlyRenewed, 'Last 30 days'],
|
|
195
|
+
['Average Age', decay.avgAge + ' days', 'Mean pattern age']
|
|
196
|
+
];
|
|
197
|
+
|
|
198
|
+
return rows.map(row => row.join(',')).join('\n');
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Generate effectiveness CSV
|
|
203
|
+
* @param {Object} effectiveness - Effectiveness data
|
|
204
|
+
* @returns {string} CSV content
|
|
205
|
+
*/
|
|
206
|
+
function generateEffectivenessCSV(effectiveness) {
|
|
207
|
+
const rows = [
|
|
208
|
+
['Metric', 'Value', 'Unit'],
|
|
209
|
+
['Time Saved', effectiveness.timeSavedMinutes, 'minutes'],
|
|
210
|
+
['Time Saved (Hours)', effectiveness.timeSavedHours, 'hours'],
|
|
211
|
+
['Questions Filtered', effectiveness.questionsFiltered, 'questions'],
|
|
212
|
+
['False Positives', effectiveness.falsePositives, 'questions'],
|
|
213
|
+
['False Positive Rate', effectiveness.falsePositiveRate, '%'],
|
|
214
|
+
['Pattern Accuracy', effectiveness.patternAccuracy, '%'],
|
|
215
|
+
['Total Rule Applications', effectiveness.totalRuleApplications, 'applications'],
|
|
216
|
+
['Avg Applications Per Rule', effectiveness.avgApplicationsPerRule, 'applications'],
|
|
217
|
+
['Overall Effectiveness', effectiveness.overallEffectiveness, '%']
|
|
218
|
+
];
|
|
219
|
+
|
|
220
|
+
return rows.map(row => row.join(',')).join('\n');
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Escape CSV value
|
|
225
|
+
* @param {string} value - Value to escape
|
|
226
|
+
* @returns {string} Escaped value
|
|
227
|
+
*/
|
|
228
|
+
function escapeCSV(value) {
|
|
229
|
+
if (typeof value !== 'string') return value;
|
|
230
|
+
|
|
231
|
+
// Escape quotes and wrap in quotes if contains special chars
|
|
232
|
+
if (value.includes(',') || value.includes('"') || value.includes('\n')) {
|
|
233
|
+
return '"' + value.replace(/"/g, '""') + '"';
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
return value;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
module.exports = {
|
|
240
|
+
exportAnalytics
|
|
241
|
+
};
|