@equilateral_ai/mindmeld 3.5.2 → 4.0.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.
Files changed (140) hide show
  1. package/hooks/session-end.js +25 -0
  2. package/hooks/session-start.js +363 -83
  3. package/hooks/session-watcher.js +585 -0
  4. package/package.json +19 -13
  5. package/scripts/init-project.js +9 -23
  6. package/src/client/dbShim.js +16 -0
  7. package/src/core/AuthManager.js +3 -2
  8. package/src/handlers/helpers/dbOperations.js +9 -46
  9. package/src/index.js +2 -217
  10. package/src/utils/piiMask.js +16 -0
  11. package/scripts/harvest.js +0 -601
  12. package/scripts/inject.js +0 -409
  13. package/scripts/mcp-bridge.js +0 -220
  14. package/scripts/repo-analyzer.js +0 -870
  15. package/src/collaboration/CollaborationPrompt.js +0 -460
  16. package/src/core/AlertEngine.js +0 -813
  17. package/src/core/AlertNotifier.js +0 -363
  18. package/src/core/CorrelationAnalyzer.js +0 -931
  19. package/src/core/CrossReferenceEngine.js +0 -624
  20. package/src/core/CurationEngine.js +0 -688
  21. package/src/core/DeprecationScheduler.js +0 -183
  22. package/src/core/LoadBearingDetector.js +0 -242
  23. package/src/core/NotificationService.js +0 -1032
  24. package/src/core/RapportOrchestrator.js +0 -632
  25. package/src/core/RelevanceDetector.js +0 -694
  26. package/src/core/StandardLifecycle.js +0 -244
  27. package/src/core/StandardsIngestion.js +0 -991
  28. package/src/core/TeamLoadBearingDetector.js +0 -431
  29. package/src/core/parsers/adrParser.js +0 -479
  30. package/src/core/parsers/cursorRulesParser.js +0 -564
  31. package/src/core/parsers/eslintParser.js +0 -439
  32. package/src/database/dbOperations.js +0 -105
  33. package/src/handlers/activity/activityGetMe.js +0 -98
  34. package/src/handlers/activity/activityGetTeam.js +0 -175
  35. package/src/handlers/admin/adminSetup.js +0 -216
  36. package/src/handlers/alerts/alertsAcknowledge.js +0 -92
  37. package/src/handlers/alerts/alertsGet.js +0 -250
  38. package/src/handlers/analytics/activitySummaryGet.js +0 -234
  39. package/src/handlers/analytics/coachingGet.js +0 -361
  40. package/src/handlers/analytics/convergenceGet.js +0 -236
  41. package/src/handlers/analytics/developerScoreGet.js +0 -137
  42. package/src/handlers/collaborators/collaboratorAdd.js +0 -200
  43. package/src/handlers/collaborators/collaboratorInvite.js +0 -219
  44. package/src/handlers/collaborators/collaboratorList.js +0 -82
  45. package/src/handlers/collaborators/collaboratorRemove.js +0 -128
  46. package/src/handlers/collaborators/inviteAccept.js +0 -122
  47. package/src/handlers/company/companyUsersDelete.js +0 -141
  48. package/src/handlers/company/companyUsersGet.js +0 -90
  49. package/src/handlers/company/companyUsersPost.js +0 -267
  50. package/src/handlers/company/companyUsersPut.js +0 -76
  51. package/src/handlers/context/contextGet.js +0 -57
  52. package/src/handlers/context/invariantsGet.js +0 -74
  53. package/src/handlers/context/loopsGet.js +0 -82
  54. package/src/handlers/context/notesCreate.js +0 -74
  55. package/src/handlers/context/purposeGet.js +0 -78
  56. package/src/handlers/correlations/correlationsDeveloperGet.js +0 -227
  57. package/src/handlers/correlations/correlationsGet.js +0 -93
  58. package/src/handlers/correlations/correlationsProjectGet.js +0 -153
  59. package/src/handlers/enterprise/controlTowerGet.js +0 -224
  60. package/src/handlers/enterprise/enterpriseAuditGet.js +0 -108
  61. package/src/handlers/enterprise/enterpriseContributorsGet.js +0 -85
  62. package/src/handlers/enterprise/enterpriseKnowledgeCategoriesGet.js +0 -53
  63. package/src/handlers/enterprise/enterpriseKnowledgeCreate.js +0 -77
  64. package/src/handlers/enterprise/enterpriseKnowledgeDelete.js +0 -71
  65. package/src/handlers/enterprise/enterpriseKnowledgeGet.js +0 -87
  66. package/src/handlers/enterprise/enterpriseKnowledgeUpdate.js +0 -122
  67. package/src/handlers/enterprise/enterpriseOnboardingComplete.js +0 -77
  68. package/src/handlers/enterprise/enterpriseOnboardingInvite.js +0 -138
  69. package/src/handlers/enterprise/enterpriseOnboardingSetup.js +0 -128
  70. package/src/handlers/enterprise/enterpriseOnboardingStatus.js +0 -88
  71. package/src/handlers/github/githubConnectionStatus.js +0 -49
  72. package/src/handlers/github/githubDiscoverPatterns.js +0 -621
  73. package/src/handlers/github/githubOAuthCallback.js +0 -178
  74. package/src/handlers/github/githubOAuthStart.js +0 -59
  75. package/src/handlers/github/githubPatternsReview.js +0 -76
  76. package/src/handlers/github/githubReposList.js +0 -105
  77. package/src/handlers/health/healthGet.js +0 -55
  78. package/src/handlers/helpers/auditLogger.js +0 -201
  79. package/src/handlers/helpers/checkSuperAdmin.js +0 -84
  80. package/src/handlers/helpers/decisionFrames.js +0 -29
  81. package/src/handlers/helpers/errorHandler.js +0 -49
  82. package/src/handlers/helpers/index.js +0 -138
  83. package/src/handlers/helpers/lambdaWrapper.js +0 -60
  84. package/src/handlers/helpers/mindmeldMcpCore.js +0 -1103
  85. package/src/handlers/helpers/predictiveCache.js +0 -51
  86. package/src/handlers/helpers/projectAccess.js +0 -88
  87. package/src/handlers/helpers/responseUtil.js +0 -55
  88. package/src/handlers/helpers/subscriptionTiers.js +0 -1168
  89. package/src/handlers/mcp/mcpHandler.js +0 -569
  90. package/src/handlers/mcp/mindmeldMcpHandler.js +0 -124
  91. package/src/handlers/mcp/mindmeldMcpStreamHandler.js +0 -342
  92. package/src/handlers/notifications/getPreferences.js +0 -84
  93. package/src/handlers/notifications/sendNotification.js +0 -170
  94. package/src/handlers/notifications/updatePreferences.js +0 -316
  95. package/src/handlers/patterns/patternEvaluatePromotionPost.js +0 -173
  96. package/src/handlers/patterns/patternUsagePost.js +0 -182
  97. package/src/handlers/patterns/patternViolationPost.js +0 -185
  98. package/src/handlers/projects/projectCreate.js +0 -248
  99. package/src/handlers/projects/projectDelete.js +0 -82
  100. package/src/handlers/projects/projectGet.js +0 -95
  101. package/src/handlers/projects/projectUpdate.js +0 -117
  102. package/src/handlers/reports/aiLeverage.js +0 -210
  103. package/src/handlers/reports/engineeringInvestment.js +0 -132
  104. package/src/handlers/reports/riskForecast.js +0 -206
  105. package/src/handlers/reports/standardsRoi.js +0 -254
  106. package/src/handlers/scheduled/analyzeCorrelations.js +0 -178
  107. package/src/handlers/scheduled/analyzeGitHistory.js +0 -510
  108. package/src/handlers/scheduled/generateAlerts.js +0 -135
  109. package/src/handlers/scheduled/maturityUpdateJob.js +0 -166
  110. package/src/handlers/scheduled/refreshActivity.js +0 -21
  111. package/src/handlers/scheduled/scanCompliance.js +0 -334
  112. package/src/handlers/sessions/sessionEndPost.js +0 -180
  113. package/src/handlers/sessions/sessionStandardsPost.js +0 -171
  114. package/src/handlers/standards/catalogGet.js +0 -185
  115. package/src/handlers/standards/catalogSync.js +0 -120
  116. package/src/handlers/standards/discoveriesGet.js +0 -89
  117. package/src/handlers/standards/projectStandardsGet.js +0 -129
  118. package/src/handlers/standards/projectStandardsPut.js +0 -151
  119. package/src/handlers/standards/standardsAuditGet.js +0 -65
  120. package/src/handlers/standards/standardsParseUpload.js +0 -149
  121. package/src/handlers/standards/standardsRelevantPost.js +0 -405
  122. package/src/handlers/standards/standardsTransition.js +0 -161
  123. package/src/handlers/stripe/addonManagePost.js +0 -240
  124. package/src/handlers/stripe/billingPortalPost.js +0 -93
  125. package/src/handlers/stripe/enterpriseCheckoutPost.js +0 -272
  126. package/src/handlers/stripe/seatsUpdatePost.js +0 -185
  127. package/src/handlers/stripe/subscriptionCancelDelete.js +0 -169
  128. package/src/handlers/stripe/subscriptionCreatePost.js +0 -221
  129. package/src/handlers/stripe/subscriptionUpdatePut.js +0 -163
  130. package/src/handlers/stripe/webhookPost.js +0 -482
  131. package/src/handlers/user/apiTokenCreate.js +0 -71
  132. package/src/handlers/user/apiTokenList.js +0 -64
  133. package/src/handlers/user/userSplashAck.js +0 -91
  134. package/src/handlers/user/userSplashGet.js +0 -211
  135. package/src/handlers/users/cognitoPostConfirmation.js +0 -186
  136. package/src/handlers/users/cognitoPreSignUp.js +0 -114
  137. package/src/handlers/users/userEntitlementsGet.js +0 -89
  138. package/src/handlers/users/userGet.js +0 -118
  139. package/src/handlers/users/userProfilePut.js +0 -77
  140. package/src/handlers/webhooks/githubWebhook.js +0 -215
@@ -1,254 +0,0 @@
1
- /**
2
- * Standards ROI Report Handler
3
- * Our key differentiator - correlation between standards and code quality
4
- *
5
- * GET /api/reports/standards-roi
6
- * Query: ?period=7d|30d|90d&Company_ID=xxx
7
- * Auth: Cognito JWT required, Manager or Admin role
8
- */
9
-
10
- const { wrapHandler, executeQuery, createSuccessResponse, createErrorResponse } = require('./helpers');
11
-
12
- exports.handler = wrapHandler(async ({ requestContext, queryStringParameters }) => {
13
- const email = requestContext.authorizer?.claims?.email || requestContext.authorizer?.jwt?.claims?.email;
14
-
15
- if (!email) {
16
- return createErrorResponse(401, 'Unauthorized');
17
- }
18
-
19
- const params = queryStringParameters || {};
20
- const period = params.period || '30d';
21
- const companyId = params.Company_ID;
22
-
23
- // Validate access - must be manager/admin
24
- const accessCheck = await executeQuery(`
25
- SELECT ue.company_id
26
- FROM rapport.user_entitlements ue
27
- WHERE ue.email_address = $1
28
- AND (ue.admin = true OR ue.manager = true)
29
- ${companyId ? 'AND ue.company_id = $2' : ''}
30
- LIMIT 1
31
- `, companyId ? [email, companyId] : [email]);
32
-
33
- if (accessCheck.rows.length === 0) {
34
- return createErrorResponse(403, 'Manager or Admin access required');
35
- }
36
-
37
- const userCompanyId = companyId || accessCheck.rows[0].company_id;
38
- const periodDays = parsePeriod(period);
39
- const periodStart = new Date();
40
- periodStart.setDate(periodStart.getDate() - periodDays);
41
-
42
- // Standards compliance by developer (from developer_metrics)
43
- let compliance = { rows: [] };
44
- try {
45
- compliance = await executeQuery(`
46
- SELECT
47
- dm.developer_email,
48
- dm.developer_name,
49
- SUM(dm.commit_count) as total_commits,
50
- SUM(dm.standards_compliant_commits) as compliant_commits,
51
- AVG(dm.compliance_score) as avg_compliance_score,
52
- SUM(dm.anti_pattern_commits) as anti_pattern_commits
53
- FROM rapport.developer_metrics dm
54
- JOIN rapport.git_repositories r ON dm.repo_id = r.repo_id
55
- WHERE r.company_id = $1
56
- AND dm.period_start >= $2
57
- GROUP BY dm.developer_email, dm.developer_name
58
- HAVING SUM(dm.commit_count) > 0
59
- ORDER BY avg_compliance_score DESC NULLS LAST
60
- `, [userCompanyId, periodStart]);
61
- } catch (e) {
62
- // Table might not have data yet
63
- }
64
-
65
- // Code patterns detected
66
- let patterns = { rows: [] };
67
- try {
68
- patterns = await executeQuery(`
69
- SELECT
70
- cp.pattern_name,
71
- cp.pattern_type,
72
- cp.severity,
73
- cp.language,
74
- cp.description
75
- FROM rapport.code_patterns cp
76
- WHERE cp.enabled = true
77
- ORDER BY cp.pattern_type, cp.severity
78
- LIMIT 20
79
- `, []);
80
- } catch (e) {
81
- // Table might not exist
82
- }
83
-
84
- // Calculate summary metrics
85
- const complianceData = compliance.rows;
86
- const avgCompliance = complianceData.length > 0
87
- ? complianceData.reduce((sum, r) => sum + parseFloat(r.avg_compliance_score || 0), 0) / complianceData.length
88
- : 0;
89
-
90
- const totalCommits = complianceData.reduce((sum, r) => sum + parseInt(r.total_commits || 0), 0);
91
- const compliantCommits = complianceData.reduce((sum, r) => sum + parseInt(r.compliant_commits || 0), 0);
92
- const antiPatternCommits = complianceData.reduce((sum, r) => sum + parseInt(r.anti_pattern_commits || 0), 0);
93
-
94
- // Get standards shown count from session_standards
95
- let standardsShown = 0;
96
- try {
97
- const shownResult = await executeQuery(`
98
- SELECT COUNT(*) as total
99
- FROM rapport.session_standards ss
100
- JOIN rapport.sessions s ON ss.session_id = s.session_id
101
- JOIN rapport.projects p ON s.project_id = p.project_id
102
- WHERE p.company_id = $1
103
- AND s.started_at >= $2
104
- `, [userCompanyId, periodStart]);
105
- standardsShown = parseInt(shownResult.rows[0]?.total) || 0;
106
- } catch (e) {
107
- // Table might not have data
108
- }
109
-
110
- // Adoption trend — standards injected per day
111
- let adoptionTrend = [];
112
- try {
113
- const trendResult = await executeQuery(`
114
- SELECT
115
- DATE(ss.created_at) as date,
116
- COUNT(*) as standards_shown,
117
- COUNT(DISTINCT ss.session_id) as sessions,
118
- COUNT(CASE WHEN ss.followed = true THEN 1 END) as followed,
119
- COUNT(CASE WHEN ss.violated = true THEN 1 END) as violated
120
- FROM rapport.session_standards ss
121
- JOIN rapport.sessions s ON ss.session_id = s.session_id
122
- JOIN rapport.projects p ON s.project_id = p.project_id
123
- WHERE p.company_id = $1
124
- AND ss.created_at >= $2
125
- GROUP BY DATE(ss.created_at)
126
- ORDER BY date
127
- `, [userCompanyId, periodStart]);
128
- adoptionTrend = trendResult.rows;
129
- } catch (e) {
130
- // Table might not have data
131
- }
132
-
133
- // Top standards — most frequently injected
134
- let topStandards = [];
135
- try {
136
- const topResult = await executeQuery(`
137
- SELECT
138
- ss.standard_id,
139
- ss.standard_name,
140
- COUNT(*) as times_shown,
141
- COUNT(DISTINCT ss.session_id) as unique_sessions,
142
- ROUND(AVG(ss.relevance_score), 2) as avg_relevance,
143
- COUNT(CASE WHEN ss.followed = true THEN 1 END) as times_followed,
144
- COUNT(CASE WHEN ss.violated = true THEN 1 END) as times_violated
145
- FROM rapport.session_standards ss
146
- JOIN rapport.sessions s ON ss.session_id = s.session_id
147
- JOIN rapport.projects p ON s.project_id = p.project_id
148
- WHERE p.company_id = $1
149
- AND ss.created_at >= $2
150
- GROUP BY ss.standard_id, ss.standard_name
151
- ORDER BY times_shown DESC
152
- LIMIT 10
153
- `, [userCompanyId, periodStart]);
154
- topStandards = topResult.rows;
155
- } catch (e) {
156
- // Table might not have data
157
- }
158
-
159
- // Anti-patterns detected — standards that were violated
160
- let antiPatternsDetected = [];
161
- try {
162
- const violationResult = await executeQuery(`
163
- SELECT
164
- ss.standard_id,
165
- ss.standard_name,
166
- COUNT(*) as violation_count,
167
- MAX(ss.created_at) as last_violated
168
- FROM rapport.session_standards ss
169
- JOIN rapport.sessions s ON ss.session_id = s.session_id
170
- JOIN rapport.projects p ON s.project_id = p.project_id
171
- WHERE p.company_id = $1
172
- AND ss.created_at >= $2
173
- AND ss.violated = true
174
- GROUP BY ss.standard_id, ss.standard_name
175
- ORDER BY violation_count DESC
176
- LIMIT 10
177
- `, [userCompanyId, periodStart]);
178
- antiPatternsDetected = violationResult.rows;
179
- } catch (e) {
180
- // Table might not have data
181
- }
182
-
183
- return createSuccessResponse({
184
- report_type: 'standards_roi',
185
- period: period,
186
- period_start: periodStart.toISOString(),
187
- period_end: new Date().toISOString(),
188
- summary: {
189
- avg_compliance_score: avgCompliance.toFixed(1),
190
- total_commits: totalCommits,
191
- compliant_commits: compliantCommits,
192
- compliance_rate: totalCommits > 0 ? ((compliantCommits / totalCommits) * 100).toFixed(1) : '0',
193
- anti_pattern_commits: antiPatternCommits,
194
- anti_pattern_rate: totalCommits > 0 ? ((antiPatternCommits / totalCommits) * 100).toFixed(1) : '0',
195
- standards_shown: standardsShown
196
- },
197
- adoption_trend: adoptionTrend,
198
- compliance_by_developer: complianceData,
199
- top_standards: topStandards,
200
- anti_patterns_detected: antiPatternsDetected,
201
- configured_patterns: patterns.rows.filter(p => p.pattern_type === 'standard'),
202
- anti_patterns: patterns.rows.filter(p => p.pattern_type === 'anti_pattern'),
203
- insights: generateInsights(complianceData)
204
- });
205
- });
206
-
207
- function generateInsights(compliance) {
208
- const insights = [];
209
-
210
- if (compliance.length === 0) {
211
- insights.push({
212
- type: 'info',
213
- message: 'No developer metrics available yet. Connect a git repository to start tracking.'
214
- });
215
- return insights;
216
- }
217
-
218
- // Compliance insights
219
- const highPerformers = compliance.filter(d => parseFloat(d.avg_compliance_score || 0) >= 80);
220
- const lowPerformers = compliance.filter(d => parseFloat(d.avg_compliance_score || 0) < 50 && parseInt(d.total_commits) > 5);
221
-
222
- if (highPerformers.length > 0) {
223
- insights.push({
224
- type: 'positive',
225
- message: `${highPerformers.length} developer(s) maintaining >80% compliance score`,
226
- developers: highPerformers.map(d => d.developer_name || d.developer_email)
227
- });
228
- }
229
-
230
- if (lowPerformers.length > 0) {
231
- insights.push({
232
- type: 'attention',
233
- message: `${lowPerformers.length} developer(s) below 50% compliance - may benefit from standards training`,
234
- developers: lowPerformers.map(d => d.developer_name || d.developer_email)
235
- });
236
- }
237
-
238
- return insights;
239
- }
240
-
241
- function parsePeriod(period) {
242
- const match = period.match(/^(\d+)([dwm])$/);
243
- if (!match) return 30;
244
-
245
- const [, num, unit] = match;
246
- const n = parseInt(num);
247
-
248
- switch (unit) {
249
- case 'd': return n;
250
- case 'w': return n * 7;
251
- case 'm': return n * 30;
252
- default: return 30;
253
- }
254
- }
@@ -1,178 +0,0 @@
1
- /**
2
- * Analyze Correlations Scheduled Job
3
- * Correlates Claude Code sessions with git commits
4
- *
5
- * Schedule: Every 2 hours (or triggered via API)
6
- * Auth: None (Lambda scheduled event)
7
- *
8
- * Features:
9
- * - Session-to-commit correlation by timestamp proximity
10
- * - Pattern success tracking
11
- * - Struggle detection (sessions without commits)
12
- * - Aggregate productivity metrics
13
- */
14
-
15
- const { wrapHandler, executeQuery, createSuccessResponse } = require('./helpers');
16
- const { CorrelationAnalyzer } = require('./core/CorrelationAnalyzer');
17
-
18
- /**
19
- * Main handler - wrapped with wrapHandler for consistent error handling
20
- */
21
- exports.handler = wrapHandler(async (event, context) => {
22
- console.log('[AnalyzeCorrelations] Starting correlation analysis job...');
23
-
24
- // Parse options from event (allows manual triggering with specific parameters)
25
- const options = parseEventOptions(event);
26
-
27
- // Initialize analyzer with default config
28
- const analyzer = new CorrelationAnalyzer();
29
-
30
- // Run correlation analysis
31
- const summary = await analyzer.analyzeCorrelations(options);
32
-
33
- // Log results
34
- console.log('[AnalyzeCorrelations] Analysis summary:', JSON.stringify(summary, null, 2));
35
-
36
- // If running for specific company, also generate struggling developer alerts
37
- if (options.companyId && summary.uncorrelatedSessions > 0) {
38
- const strugglingDevs = await analyzer.identifyStrugglingDevelopers(
39
- options.companyId,
40
- options.lookbackDays || 30
41
- );
42
-
43
- if (strugglingDevs.length > 0) {
44
- console.log(`[AnalyzeCorrelations] Found ${strugglingDevs.length} potentially struggling developers`);
45
-
46
- // Create alerts for struggling developers
47
- await createStrugglingDeveloperAlerts(options.companyId, strugglingDevs);
48
- }
49
- }
50
-
51
- return createSuccessResponse({
52
- sessionsAnalyzed: summary.sessionsAnalyzed,
53
- correlationsCreated: summary.correlationsCreated,
54
- uncorrelatedSessions: summary.uncorrelatedSessions,
55
- patternCorrelations: summary.patternCorrelations,
56
- errors: summary.errors.length,
57
- duration: calculateDuration(summary.startedAt, summary.completedAt)
58
- }, 'Correlation analysis complete');
59
- });
60
-
61
- /**
62
- * Parse options from Lambda event
63
- *
64
- * @param {Object} event - Lambda event
65
- * @returns {Object} Parsed options
66
- */
67
- function parseEventOptions(event) {
68
- const options = {
69
- lookbackDays: 30
70
- };
71
-
72
- // Support both direct invocation and API Gateway trigger
73
- if (event.body) {
74
- try {
75
- const body = typeof event.body === 'string' ? JSON.parse(event.body) : event.body;
76
- options.projectId = body.projectId;
77
- options.companyId = body.companyId;
78
- options.lookbackDays = body.lookbackDays || 30;
79
- } catch (e) {
80
- console.log('[AnalyzeCorrelations] Could not parse event body:', e.message);
81
- }
82
- } else if (event.projectId || event.companyId) {
83
- // Direct invocation with parameters
84
- options.projectId = event.projectId;
85
- options.companyId = event.companyId;
86
- options.lookbackDays = event.lookbackDays || 30;
87
- }
88
-
89
- return options;
90
- }
91
-
92
- /**
93
- * Create alerts for struggling developers
94
- *
95
- * @param {string} companyId - Company ID
96
- * @param {Array} developers - Struggling developers list
97
- */
98
- async function createStrugglingDeveloperAlerts(companyId, developers) {
99
- const cooldownHours = 24;
100
-
101
- for (const dev of developers) {
102
- try {
103
- // Check if alert already exists within cooldown period
104
- const existingCheck = await executeQuery(`
105
- SELECT 1 FROM rapport.attention_alerts
106
- WHERE email_address = $1
107
- AND alert_type = 'low_conversion'
108
- AND (status = 'active' OR created_at > NOW() - $2 * INTERVAL '1 hour')
109
- LIMIT 1
110
- `, [dev.email, cooldownHours]);
111
-
112
- if (existingCheck.rows.length > 0) {
113
- console.log(`[AnalyzeCorrelations] Skipping alert for ${dev.email} - already exists`);
114
- continue;
115
- }
116
-
117
- // Determine severity based on unproductive rate
118
- let severity = 'info';
119
- if (dev.unproductiveRate >= 80) {
120
- severity = 'critical';
121
- } else if (dev.unproductiveRate >= 60) {
122
- severity = 'warning';
123
- }
124
-
125
- // Create alert
126
- await executeQuery(`
127
- INSERT INTO rapport.attention_alerts (
128
- email_address,
129
- company_id,
130
- alert_type,
131
- severity,
132
- details,
133
- expires_at
134
- ) VALUES ($1, $2, 'low_conversion', $3, $4, NOW() + INTERVAL '7 days')
135
- `, [
136
- dev.email,
137
- companyId,
138
- severity,
139
- JSON.stringify({
140
- total_sessions: dev.totalSessions,
141
- unproductive_sessions: dev.unproductiveSessions,
142
- unproductive_rate: dev.unproductiveRate,
143
- last_productive_session: dev.lastProductiveSession,
144
- avg_session_minutes: dev.avgSessionMinutes,
145
- generated_by: 'CorrelationAnalyzer'
146
- })
147
- ]);
148
-
149
- console.log(`[AnalyzeCorrelations] Created ${severity} alert for ${dev.email}`);
150
-
151
- } catch (error) {
152
- console.error(`[AnalyzeCorrelations] Error creating alert for ${dev.email}:`, error.message);
153
- }
154
- }
155
- }
156
-
157
- /**
158
- * Calculate duration between two ISO timestamps
159
- *
160
- * @param {string} start - Start timestamp
161
- * @param {string} end - End timestamp
162
- * @returns {string} Duration string
163
- */
164
- function calculateDuration(start, end) {
165
- if (!start || !end) return 'unknown';
166
-
167
- const startTime = new Date(start);
168
- const endTime = new Date(end);
169
- const durationMs = endTime - startTime;
170
-
171
- if (durationMs < 1000) {
172
- return `${durationMs}ms`;
173
- } else if (durationMs < 60000) {
174
- return `${Math.round(durationMs / 1000)}s`;
175
- } else {
176
- return `${Math.round(durationMs / 60000)}m ${Math.round((durationMs % 60000) / 1000)}s`;
177
- }
178
- }