@equilateral_ai/mindmeld 3.5.3 → 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.
- package/hooks/session-start.js +312 -85
- package/package.json +20 -14
- package/scripts/init-project.js +9 -23
- package/src/client/dbShim.js +16 -0
- package/src/core/AuthManager.js +3 -2
- package/src/handlers/helpers/dbOperations.js +9 -46
- package/src/index.js +2 -217
- package/src/utils/piiMask.js +16 -0
- package/scripts/harvest.js +0 -601
- package/scripts/inject.js +0 -409
- package/scripts/mcp-bridge.js +0 -220
- package/scripts/repo-analyzer.js +0 -870
- package/scripts/standards.js +0 -285
- package/src/collaboration/CollaborationPrompt.js +0 -460
- package/src/core/AlertEngine.js +0 -813
- package/src/core/AlertNotifier.js +0 -363
- package/src/core/CorrelationAnalyzer.js +0 -931
- package/src/core/CrossReferenceEngine.js +0 -624
- package/src/core/CurationEngine.js +0 -688
- package/src/core/DeprecationScheduler.js +0 -183
- package/src/core/LoadBearingDetector.js +0 -242
- package/src/core/NotificationService.js +0 -1032
- package/src/core/RapportOrchestrator.js +0 -632
- package/src/core/RelevanceDetector.js +0 -694
- package/src/core/StandardLifecycle.js +0 -244
- package/src/core/StandardsIngestion.js +0 -991
- package/src/core/TeamLoadBearingDetector.js +0 -431
- package/src/core/parsers/adrParser.js +0 -479
- package/src/core/parsers/cursorRulesParser.js +0 -564
- package/src/core/parsers/eslintParser.js +0 -439
- package/src/database/dbOperations.js +0 -105
- package/src/handlers/activity/activityGetMe.js +0 -98
- package/src/handlers/activity/activityGetTeam.js +0 -175
- package/src/handlers/admin/adminSetup.js +0 -216
- package/src/handlers/alerts/alertsAcknowledge.js +0 -92
- package/src/handlers/alerts/alertsGet.js +0 -250
- package/src/handlers/analytics/activitySummaryGet.js +0 -234
- package/src/handlers/analytics/coachingGet.js +0 -361
- package/src/handlers/analytics/convergenceGet.js +0 -236
- package/src/handlers/analytics/developerScoreGet.js +0 -137
- package/src/handlers/collaborators/collaboratorAdd.js +0 -200
- package/src/handlers/collaborators/collaboratorInvite.js +0 -219
- package/src/handlers/collaborators/collaboratorList.js +0 -82
- package/src/handlers/collaborators/collaboratorRemove.js +0 -128
- package/src/handlers/collaborators/inviteAccept.js +0 -122
- package/src/handlers/company/companyUsersDelete.js +0 -141
- package/src/handlers/company/companyUsersGet.js +0 -90
- package/src/handlers/company/companyUsersPost.js +0 -267
- package/src/handlers/company/companyUsersPut.js +0 -76
- package/src/handlers/context/contextGet.js +0 -57
- package/src/handlers/context/invariantsGet.js +0 -74
- package/src/handlers/context/loopsGet.js +0 -82
- package/src/handlers/context/notesCreate.js +0 -74
- package/src/handlers/context/purposeGet.js +0 -78
- package/src/handlers/correlations/correlationsDeveloperGet.js +0 -227
- package/src/handlers/correlations/correlationsGet.js +0 -93
- package/src/handlers/correlations/correlationsProjectGet.js +0 -153
- package/src/handlers/enterprise/controlTowerGet.js +0 -224
- package/src/handlers/enterprise/enterpriseAuditGet.js +0 -108
- package/src/handlers/enterprise/enterpriseContributorsGet.js +0 -85
- package/src/handlers/enterprise/enterpriseKnowledgeCategoriesGet.js +0 -53
- package/src/handlers/enterprise/enterpriseKnowledgeCreate.js +0 -77
- package/src/handlers/enterprise/enterpriseKnowledgeDelete.js +0 -71
- package/src/handlers/enterprise/enterpriseKnowledgeGet.js +0 -87
- package/src/handlers/enterprise/enterpriseKnowledgeUpdate.js +0 -122
- package/src/handlers/enterprise/enterpriseOnboardingComplete.js +0 -77
- package/src/handlers/enterprise/enterpriseOnboardingInvite.js +0 -138
- package/src/handlers/enterprise/enterpriseOnboardingSetup.js +0 -128
- package/src/handlers/enterprise/enterpriseOnboardingStatus.js +0 -88
- package/src/handlers/github/githubConnectionStatus.js +0 -49
- package/src/handlers/github/githubDiscoverPatterns.js +0 -621
- package/src/handlers/github/githubOAuthCallback.js +0 -178
- package/src/handlers/github/githubOAuthStart.js +0 -59
- package/src/handlers/github/githubPatternsReview.js +0 -76
- package/src/handlers/github/githubReposList.js +0 -105
- package/src/handlers/health/healthGet.js +0 -55
- package/src/handlers/helpers/auditLogger.js +0 -201
- package/src/handlers/helpers/checkSuperAdmin.js +0 -84
- package/src/handlers/helpers/decisionFrames.js +0 -29
- package/src/handlers/helpers/errorHandler.js +0 -49
- package/src/handlers/helpers/index.js +0 -138
- package/src/handlers/helpers/lambdaWrapper.js +0 -60
- package/src/handlers/helpers/mindmeldMcpCore.js +0 -1103
- package/src/handlers/helpers/predictiveCache.js +0 -51
- package/src/handlers/helpers/projectAccess.js +0 -88
- package/src/handlers/helpers/responseUtil.js +0 -55
- package/src/handlers/helpers/subscriptionTiers.js +0 -1168
- package/src/handlers/mcp/mcpHandler.js +0 -569
- package/src/handlers/mcp/mindmeldMcpHandler.js +0 -124
- package/src/handlers/mcp/mindmeldMcpStreamHandler.js +0 -342
- package/src/handlers/notifications/getPreferences.js +0 -84
- package/src/handlers/notifications/sendNotification.js +0 -170
- package/src/handlers/notifications/updatePreferences.js +0 -316
- package/src/handlers/patterns/patternEvaluatePromotionPost.js +0 -173
- package/src/handlers/patterns/patternUsagePost.js +0 -182
- package/src/handlers/patterns/patternViolationPost.js +0 -185
- package/src/handlers/projects/projectCreate.js +0 -248
- package/src/handlers/projects/projectDelete.js +0 -82
- package/src/handlers/projects/projectGet.js +0 -95
- package/src/handlers/projects/projectUpdate.js +0 -117
- package/src/handlers/reports/aiLeverage.js +0 -210
- package/src/handlers/reports/engineeringInvestment.js +0 -132
- package/src/handlers/reports/riskForecast.js +0 -206
- package/src/handlers/reports/standardsRoi.js +0 -254
- package/src/handlers/scheduled/analyzeCorrelations.js +0 -178
- package/src/handlers/scheduled/analyzeGitHistory.js +0 -510
- package/src/handlers/scheduled/generateAlerts.js +0 -135
- package/src/handlers/scheduled/maturityUpdateJob.js +0 -166
- package/src/handlers/scheduled/refreshActivity.js +0 -21
- package/src/handlers/scheduled/scanCompliance.js +0 -334
- package/src/handlers/sessions/sessionEndPost.js +0 -180
- package/src/handlers/sessions/sessionStandardsPost.js +0 -171
- package/src/handlers/standards/catalogGet.js +0 -185
- package/src/handlers/standards/catalogSync.js +0 -120
- package/src/handlers/standards/discoveriesGet.js +0 -89
- package/src/handlers/standards/projectStandardsGet.js +0 -129
- package/src/handlers/standards/projectStandardsPut.js +0 -151
- package/src/handlers/standards/standardsAuditGet.js +0 -65
- package/src/handlers/standards/standardsParseUpload.js +0 -149
- package/src/handlers/standards/standardsRelevantPost.js +0 -405
- package/src/handlers/standards/standardsTransition.js +0 -161
- package/src/handlers/stripe/addonManagePost.js +0 -240
- package/src/handlers/stripe/billingPortalPost.js +0 -93
- package/src/handlers/stripe/enterpriseCheckoutPost.js +0 -272
- package/src/handlers/stripe/seatsUpdatePost.js +0 -185
- package/src/handlers/stripe/subscriptionCancelDelete.js +0 -169
- package/src/handlers/stripe/subscriptionCreatePost.js +0 -221
- package/src/handlers/stripe/subscriptionUpdatePut.js +0 -163
- package/src/handlers/stripe/webhookPost.js +0 -482
- package/src/handlers/user/apiTokenCreate.js +0 -71
- package/src/handlers/user/apiTokenList.js +0 -64
- package/src/handlers/user/userSplashAck.js +0 -91
- package/src/handlers/user/userSplashGet.js +0 -211
- package/src/handlers/users/cognitoPostConfirmation.js +0 -186
- package/src/handlers/users/cognitoPreSignUp.js +0 -114
- package/src/handlers/users/userEntitlementsGet.js +0 -89
- package/src/handlers/users/userGet.js +0 -118
- package/src/handlers/users/userProfilePut.js +0 -77
- 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
|
-
}
|