@equilateral_ai/mindmeld 3.0.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.
Files changed (86) hide show
  1. package/README.md +300 -0
  2. package/hooks/README.md +494 -0
  3. package/hooks/pre-compact.js +392 -0
  4. package/hooks/session-start.js +264 -0
  5. package/package.json +90 -0
  6. package/scripts/harvest.js +561 -0
  7. package/scripts/init-project.js +437 -0
  8. package/scripts/inject.js +388 -0
  9. package/src/collaboration/CollaborationPrompt.js +460 -0
  10. package/src/core/AlertEngine.js +813 -0
  11. package/src/core/AlertNotifier.js +363 -0
  12. package/src/core/CorrelationAnalyzer.js +774 -0
  13. package/src/core/CurationEngine.js +688 -0
  14. package/src/core/LLMPatternDetector.js +508 -0
  15. package/src/core/LoadBearingDetector.js +242 -0
  16. package/src/core/NotificationService.js +1032 -0
  17. package/src/core/PatternValidator.js +355 -0
  18. package/src/core/README.md +160 -0
  19. package/src/core/RapportOrchestrator.js +446 -0
  20. package/src/core/RelevanceDetector.js +577 -0
  21. package/src/core/StandardsIngestion.js +575 -0
  22. package/src/core/TeamLoadBearingDetector.js +431 -0
  23. package/src/database/dbOperations.js +105 -0
  24. package/src/handlers/activity/activityGetMe.js +98 -0
  25. package/src/handlers/activity/activityGetTeam.js +130 -0
  26. package/src/handlers/alerts/alertsAcknowledge.js +91 -0
  27. package/src/handlers/alerts/alertsGet.js +250 -0
  28. package/src/handlers/collaborators/collaboratorAdd.js +201 -0
  29. package/src/handlers/collaborators/collaboratorInvite.js +218 -0
  30. package/src/handlers/collaborators/collaboratorList.js +88 -0
  31. package/src/handlers/collaborators/collaboratorRemove.js +127 -0
  32. package/src/handlers/collaborators/inviteAccept.js +122 -0
  33. package/src/handlers/context/contextGet.js +57 -0
  34. package/src/handlers/context/invariantsGet.js +74 -0
  35. package/src/handlers/context/loopsGet.js +82 -0
  36. package/src/handlers/context/notesCreate.js +74 -0
  37. package/src/handlers/context/purposeGet.js +78 -0
  38. package/src/handlers/correlations/correlationsDeveloperGet.js +226 -0
  39. package/src/handlers/correlations/correlationsGet.js +93 -0
  40. package/src/handlers/correlations/correlationsProjectGet.js +161 -0
  41. package/src/handlers/github/githubConnectionStatus.js +49 -0
  42. package/src/handlers/github/githubDiscoverPatterns.js +364 -0
  43. package/src/handlers/github/githubOAuthCallback.js +166 -0
  44. package/src/handlers/github/githubOAuthStart.js +59 -0
  45. package/src/handlers/github/githubPatternsReview.js +109 -0
  46. package/src/handlers/github/githubReposList.js +105 -0
  47. package/src/handlers/helpers/checkSuperAdmin.js +85 -0
  48. package/src/handlers/helpers/dbOperations.js +53 -0
  49. package/src/handlers/helpers/errorHandler.js +49 -0
  50. package/src/handlers/helpers/index.js +106 -0
  51. package/src/handlers/helpers/lambdaWrapper.js +60 -0
  52. package/src/handlers/helpers/responseUtil.js +55 -0
  53. package/src/handlers/helpers/subscriptionTiers.js +1168 -0
  54. package/src/handlers/notifications/getPreferences.js +84 -0
  55. package/src/handlers/notifications/sendNotification.js +170 -0
  56. package/src/handlers/notifications/updatePreferences.js +316 -0
  57. package/src/handlers/patterns/patternUsagePost.js +182 -0
  58. package/src/handlers/patterns/patternViolationPost.js +185 -0
  59. package/src/handlers/projects/projectCreate.js +107 -0
  60. package/src/handlers/projects/projectDelete.js +82 -0
  61. package/src/handlers/projects/projectGet.js +95 -0
  62. package/src/handlers/projects/projectUpdate.js +118 -0
  63. package/src/handlers/reports/aiLeverage.js +206 -0
  64. package/src/handlers/reports/engineeringInvestment.js +132 -0
  65. package/src/handlers/reports/riskForecast.js +186 -0
  66. package/src/handlers/reports/standardsRoi.js +162 -0
  67. package/src/handlers/scheduled/analyzeCorrelations.js +178 -0
  68. package/src/handlers/scheduled/analyzeGitHistory.js +510 -0
  69. package/src/handlers/scheduled/generateAlerts.js +135 -0
  70. package/src/handlers/scheduled/refreshActivity.js +21 -0
  71. package/src/handlers/scheduled/scanCompliance.js +334 -0
  72. package/src/handlers/sessions/sessionEndPost.js +180 -0
  73. package/src/handlers/sessions/sessionStandardsPost.js +135 -0
  74. package/src/handlers/stripe/addonManagePost.js +240 -0
  75. package/src/handlers/stripe/billingPortalPost.js +93 -0
  76. package/src/handlers/stripe/enterpriseCheckoutPost.js +272 -0
  77. package/src/handlers/stripe/seatsUpdatePost.js +185 -0
  78. package/src/handlers/stripe/subscriptionCancelDelete.js +169 -0
  79. package/src/handlers/stripe/subscriptionCreatePost.js +221 -0
  80. package/src/handlers/stripe/subscriptionUpdatePut.js +163 -0
  81. package/src/handlers/stripe/webhookPost.js +454 -0
  82. package/src/handlers/users/cognitoPostConfirmation.js +150 -0
  83. package/src/handlers/users/userEntitlementsGet.js +89 -0
  84. package/src/handlers/users/userGet.js +114 -0
  85. package/src/handlers/webhooks/githubWebhook.js +223 -0
  86. package/src/index.js +969 -0
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Loops Get Handler
3
+ * Retrieves active loops for a user
4
+ *
5
+ * GET /api/context/loops
6
+ */
7
+
8
+ const { wrapHandler, executeQuery, createSuccessResponse, createErrorResponse, handleError, checkSuperAdmin } = require('./helpers');
9
+
10
+ /**
11
+ * Get active loops
12
+ */
13
+ async function getLoops({ queryStringParameters: queryParams = {}, requestContext }) {
14
+ try {
15
+ const Request_ID = requestContext.requestId;
16
+ const email = requestContext.authorizer?.claims?.email || requestContext.authorizer?.jwt?.claims?.email;
17
+
18
+ if (!email) {
19
+ return createErrorResponse(401, 'Unauthorized');
20
+ }
21
+
22
+ // Gate to super admins only (internal/beta endpoint)
23
+ await checkSuperAdmin.requireSuperAdmin(email);
24
+
25
+ const includeCompleted = queryParams.include_completed === 'true';
26
+
27
+ let query = `
28
+ SELECT
29
+ loop_id,
30
+ project_scope,
31
+ task_description,
32
+ completion_promise,
33
+ max_iterations,
34
+ current_iteration,
35
+ status,
36
+ promise_found,
37
+ started_at,
38
+ updated_at,
39
+ completed_at
40
+ FROM rapport.active_loops
41
+ WHERE email_address = $1
42
+ `;
43
+
44
+ if (!includeCompleted) {
45
+ query += ` AND status = 'active'`;
46
+ }
47
+
48
+ query += ` ORDER BY started_at DESC LIMIT 20`;
49
+
50
+ const result = await executeQuery(query, [email]);
51
+
52
+ return createSuccessResponse(
53
+ {
54
+ loops: result.rows.map(row => ({
55
+ id: row.loop_id,
56
+ scope: row.project_scope,
57
+ task: row.task_description,
58
+ promise: row.completion_promise,
59
+ iteration: row.current_iteration,
60
+ max_iterations: row.max_iterations,
61
+ status: row.status,
62
+ promise_found: row.promise_found,
63
+ started_at: row.started_at,
64
+ updated_at: row.updated_at,
65
+ completed_at: row.completed_at
66
+ })),
67
+ count: result.rowCount
68
+ },
69
+ `${result.rowCount} loops`,
70
+ {
71
+ Request_ID,
72
+ Timestamp: new Date().toISOString()
73
+ }
74
+ );
75
+
76
+ } catch (error) {
77
+ console.error('Handler Error:', error);
78
+ return handleError(error);
79
+ }
80
+ }
81
+
82
+ exports.handler = wrapHandler(getLoops);
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Notes Create Handler
3
+ * Creates a new context note from mobile/iOS
4
+ *
5
+ * POST /api/context/notes
6
+ * Body: { content: "...", scope: "jarvis", tags: ["mobile"] }
7
+ */
8
+
9
+ const { wrapHandler, executeQuery, createSuccessResponse, createErrorResponse, handleError, checkSuperAdmin } = require('./helpers');
10
+
11
+ /**
12
+ * Create context note
13
+ */
14
+ async function createNote({ body, requestContext }) {
15
+ try {
16
+ const Request_ID = requestContext.requestId;
17
+ const email = requestContext.authorizer?.claims?.email || requestContext.authorizer?.jwt?.claims?.email;
18
+
19
+ if (!email) {
20
+ return createErrorResponse(401, 'Unauthorized');
21
+ }
22
+
23
+ // Gate to super admins only (internal/beta endpoint)
24
+ await checkSuperAdmin.requireSuperAdmin(email);
25
+
26
+ const data = typeof body === 'string' ? JSON.parse(body) : body;
27
+
28
+ if (!data.content || data.content.trim() === '') {
29
+ return createErrorResponse(400, 'Content is required');
30
+ }
31
+
32
+ const scope = data.scope || 'global';
33
+ const tags = data.tags || [];
34
+ const source = data.source || 'mobile';
35
+ const metadata = data.metadata || {};
36
+
37
+ const query = `
38
+ INSERT INTO rapport.context_notes
39
+ (email_address, scope, content, tags, source, metadata)
40
+ VALUES ($1, $2, $3, $4, $5, $6)
41
+ RETURNING note_id, created_at
42
+ `;
43
+
44
+ const result = await executeQuery(query, [
45
+ email,
46
+ scope,
47
+ data.content.trim(),
48
+ JSON.stringify(tags),
49
+ source,
50
+ JSON.stringify(metadata)
51
+ ]);
52
+
53
+ const note = result.rows[0];
54
+
55
+ return createSuccessResponse(
56
+ {
57
+ note_id: note.note_id,
58
+ scope,
59
+ created_at: note.created_at
60
+ },
61
+ `Note created: ${note.note_id}`,
62
+ {
63
+ Request_ID,
64
+ Timestamp: new Date().toISOString()
65
+ }
66
+ );
67
+
68
+ } catch (error) {
69
+ console.error('Handler Error:', error);
70
+ return handleError(error);
71
+ }
72
+ }
73
+
74
+ exports.handler = wrapHandler(createNote);
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Purpose Get Handler
3
+ * Retrieves relationship purpose for a scope
4
+ *
5
+ * GET /api/context/purpose?scope=jarvis
6
+ */
7
+
8
+ const { wrapHandler, executeQuery, createSuccessResponse, createErrorResponse, handleError, checkSuperAdmin } = require('./helpers');
9
+
10
+ /**
11
+ * Get relationship purpose
12
+ */
13
+ async function getPurpose({ queryStringParameters: queryParams = {}, requestContext }) {
14
+ try {
15
+ const Request_ID = requestContext.requestId;
16
+ const email = requestContext.authorizer?.claims?.email || requestContext.authorizer?.jwt?.claims?.email;
17
+
18
+ if (!email) {
19
+ return createErrorResponse(401, 'Unauthorized');
20
+ }
21
+
22
+ // Gate to super admins only (internal/beta endpoint)
23
+ await checkSuperAdmin.requireSuperAdmin(email);
24
+
25
+ const scope = queryParams.scope || 'jarvis';
26
+
27
+ const query = `
28
+ SELECT
29
+ purpose_text,
30
+ confidence,
31
+ confirmed,
32
+ maturity,
33
+ evolution,
34
+ created_at,
35
+ updated_at
36
+ FROM rapport.relationship_purpose
37
+ WHERE email_address = $1
38
+ AND scope = $2
39
+ `;
40
+ const result = await executeQuery(query, [email, scope]);
41
+
42
+ if (result.rowCount === 0) {
43
+ return createSuccessResponse(
44
+ {
45
+ scope,
46
+ purpose: null,
47
+ message: 'No purpose defined for this scope'
48
+ },
49
+ 'No purpose found',
50
+ { Request_ID, Timestamp: new Date().toISOString() }
51
+ );
52
+ }
53
+
54
+ const row = result.rows[0];
55
+
56
+ return createSuccessResponse(
57
+ {
58
+ scope,
59
+ purpose: row.purpose_text,
60
+ confidence: parseFloat(row.confidence),
61
+ confirmed: row.confirmed,
62
+ maturity: row.maturity,
63
+ evolution: row.evolution || []
64
+ },
65
+ row.purpose_text,
66
+ {
67
+ Request_ID,
68
+ Timestamp: new Date().toISOString()
69
+ }
70
+ );
71
+
72
+ } catch (error) {
73
+ console.error('Handler Error:', error);
74
+ return handleError(error);
75
+ }
76
+ }
77
+
78
+ exports.handler = wrapHandler(getPurpose);
@@ -0,0 +1,226 @@
1
+ /**
2
+ * Get Developer Correlations Handler
3
+ * Returns session-to-commit correlation data for a specific developer
4
+ *
5
+ * GET /api/correlations/developer/{email}
6
+ * Query params:
7
+ * - lookbackDays (optional, default: 30)
8
+ *
9
+ * Returns:
10
+ * - Developer productivity metrics
11
+ * - Session history with commit correlation
12
+ * - Trend analysis
13
+ */
14
+
15
+ const { wrapHandler, executeQuery, createSuccessResponse, createErrorResponse } = require('./helpers');
16
+ const { CorrelationAnalyzer } = require('../../core/CorrelationAnalyzer');
17
+
18
+ exports.handler = wrapHandler(async (event, context) => {
19
+ // Extract user email from Cognito claims
20
+ const requestingEmail = event.requestContext?.authorizer?.claims?.email;
21
+ if (!requestingEmail) {
22
+ return createErrorResponse(401, 'Unauthorized - no email in claims');
23
+ }
24
+
25
+ // Get target developer email from path parameters
26
+ const targetEmail = decodeURIComponent(event.pathParameters?.email || '');
27
+ if (!targetEmail) {
28
+ return createErrorResponse(400, 'Developer email is required');
29
+ }
30
+
31
+ // Parse query parameters
32
+ const queryParams = event.queryStringParameters || {};
33
+ const lookbackDays = parseInt(queryParams.lookbackDays) || 30;
34
+
35
+ // Check if requesting own data or if admin
36
+ const isSelf = requestingEmail.toLowerCase() === targetEmail.toLowerCase();
37
+
38
+ if (!isSelf) {
39
+ // Verify requesting user is admin in a shared company
40
+ const accessResult = await executeQuery(`
41
+ SELECT DISTINCT ue1."Company_ID"
42
+ FROM "UserEntitlements" ue1
43
+ JOIN "UserEntitlements" ue2 ON ue1."Company_ID" = ue2."Company_ID"
44
+ WHERE ue1."Email_Address" = $1
45
+ AND ue2."Email_Address" = $2
46
+ AND (ue1."Admin" = true OR ue1."Manager" = true)
47
+ `, [requestingEmail, targetEmail]);
48
+
49
+ if (accessResult.rows.length === 0) {
50
+ return createErrorResponse(403, 'Access denied - admin privileges required to view other developers');
51
+ }
52
+ }
53
+
54
+ // Get developer info
55
+ const developerResult = await executeQuery(`
56
+ SELECT
57
+ u."Email_Address" as email,
58
+ u."User_Display_Name" as display_name,
59
+ u."First_Name" as first_name,
60
+ u."Last_Name" as last_name
61
+ FROM "Users" u
62
+ WHERE u."Email_Address" = $1
63
+ `, [targetEmail]);
64
+
65
+ if (developerResult.rows.length === 0) {
66
+ return createErrorResponse(404, 'Developer not found');
67
+ }
68
+
69
+ const developer = developerResult.rows[0];
70
+
71
+ // Initialize analyzer
72
+ const analyzer = new CorrelationAnalyzer();
73
+
74
+ // Get developer productivity metrics
75
+ const productivity = await analyzer.getDeveloperProductivity(targetEmail, lookbackDays);
76
+
77
+ // Get session history
78
+ const sessionHistory = await getSessionHistory(targetEmail, lookbackDays, 20);
79
+
80
+ // Get weekly trend
81
+ const weeklyTrend = await getWeeklyTrend(targetEmail, lookbackDays);
82
+
83
+ // Get pattern usage
84
+ const patternUsage = await getPatternUsage(targetEmail, lookbackDays);
85
+
86
+ return createSuccessResponse({
87
+ developer: {
88
+ email: developer.email,
89
+ displayName: developer.display_name,
90
+ firstName: developer.first_name,
91
+ lastName: developer.last_name
92
+ },
93
+ productivity,
94
+ sessionHistory,
95
+ weeklyTrend,
96
+ patternUsage
97
+ });
98
+ });
99
+
100
+ /**
101
+ * Get session history for a developer
102
+ */
103
+ async function getSessionHistory(email, lookbackDays, limit) {
104
+ const query = `
105
+ SELECT
106
+ sc.session_id,
107
+ sc.project_id,
108
+ p.project_name,
109
+ sc.session_started_at,
110
+ sc.session_ended_at,
111
+ sc.session_duration_seconds,
112
+ sc.has_commits,
113
+ sc.commit_count,
114
+ sc.total_insertions,
115
+ sc.total_deletions,
116
+ sc.total_files_changed,
117
+ sc.avg_commit_latency_minutes,
118
+ sc.patterns_used,
119
+ sc.correlation_type,
120
+ sc.correlation_score
121
+ FROM rapport.session_correlations sc
122
+ LEFT JOIN rapport.projects p ON sc.project_id = p.project_id
123
+ WHERE sc.email_address = $1
124
+ AND sc.session_started_at > NOW() - INTERVAL '${lookbackDays} days'
125
+ ORDER BY sc.session_started_at DESC
126
+ LIMIT $2
127
+ `;
128
+
129
+ const result = await executeQuery(query, [email, limit]);
130
+
131
+ return result.rows.map(row => ({
132
+ sessionId: row.session_id,
133
+ projectId: row.project_id,
134
+ projectName: row.project_name,
135
+ startedAt: row.session_started_at,
136
+ endedAt: row.session_ended_at,
137
+ durationMinutes: Math.round((row.session_duration_seconds || 0) / 60),
138
+ hasCommits: row.has_commits,
139
+ commitCount: row.commit_count,
140
+ insertions: row.total_insertions,
141
+ deletions: row.total_deletions,
142
+ filesChanged: row.total_files_changed,
143
+ commitLatencyMinutes: row.avg_commit_latency_minutes,
144
+ patternsUsed: row.patterns_used,
145
+ correlationType: row.correlation_type,
146
+ correlationScore: parseFloat(row.correlation_score) || 0
147
+ }));
148
+ }
149
+
150
+ /**
151
+ * Get weekly productivity trend for a developer
152
+ */
153
+ async function getWeeklyTrend(email, lookbackDays) {
154
+ const query = `
155
+ SELECT
156
+ DATE_TRUNC('week', sc.session_started_at) as week,
157
+ COUNT(*) as total_sessions,
158
+ COUNT(*) FILTER (WHERE sc.has_commits = true) as productive_sessions,
159
+ ROUND(
160
+ COUNT(*) FILTER (WHERE sc.has_commits = true)::decimal /
161
+ NULLIF(COUNT(*), 0) * 100,
162
+ 1
163
+ ) as conversion_rate,
164
+ SUM(sc.commit_count) as total_commits,
165
+ SUM(sc.total_insertions) as total_insertions,
166
+ SUM(sc.total_deletions) as total_deletions,
167
+ SUM(sc.session_duration_seconds) / 3600.0 as session_hours
168
+ FROM rapport.session_correlations sc
169
+ WHERE sc.email_address = $1
170
+ AND sc.session_started_at > NOW() - INTERVAL '${lookbackDays} days'
171
+ GROUP BY DATE_TRUNC('week', sc.session_started_at)
172
+ ORDER BY week DESC
173
+ `;
174
+
175
+ const result = await executeQuery(query, [email]);
176
+
177
+ return result.rows.map(row => ({
178
+ week: row.week,
179
+ totalSessions: parseInt(row.total_sessions) || 0,
180
+ productiveSessions: parseInt(row.productive_sessions) || 0,
181
+ conversionRate: parseFloat(row.conversion_rate) || 0,
182
+ totalCommits: parseInt(row.total_commits) || 0,
183
+ totalInsertions: parseInt(row.total_insertions) || 0,
184
+ totalDeletions: parseInt(row.total_deletions) || 0,
185
+ sessionHours: parseFloat(row.session_hours) || 0
186
+ }));
187
+ }
188
+
189
+ /**
190
+ * Get pattern usage statistics for a developer
191
+ */
192
+ async function getPatternUsage(email, lookbackDays) {
193
+ const query = `
194
+ SELECT
195
+ p.pattern_id,
196
+ p.intent,
197
+ p.maturity,
198
+ COUNT(DISTINCT pu.session_id) as sessions_used,
199
+ COUNT(DISTINCT sc.session_id) FILTER (WHERE sc.has_commits = true) as sessions_with_commits,
200
+ ROUND(
201
+ COUNT(DISTINCT sc.session_id) FILTER (WHERE sc.has_commits = true)::decimal /
202
+ NULLIF(COUNT(DISTINCT pu.session_id), 0) * 100,
203
+ 1
204
+ ) as pattern_conversion_rate
205
+ FROM rapport.pattern_usage pu
206
+ JOIN rapport.patterns p ON pu.pattern_id = p.pattern_id
207
+ LEFT JOIN rapport.session_correlations sc ON pu.session_id = sc.session_id
208
+ WHERE pu.email_address = $1
209
+ AND pu.used_at > NOW() - INTERVAL '${lookbackDays} days'
210
+ GROUP BY p.pattern_id, p.intent, p.maturity
211
+ HAVING COUNT(DISTINCT pu.session_id) >= 2
212
+ ORDER BY sessions_used DESC
213
+ LIMIT 10
214
+ `;
215
+
216
+ const result = await executeQuery(query, [email]);
217
+
218
+ return result.rows.map(row => ({
219
+ patternId: row.pattern_id,
220
+ intent: row.intent,
221
+ maturity: row.maturity,
222
+ sessionsUsed: parseInt(row.sessions_used) || 0,
223
+ sessionsWithCommits: parseInt(row.sessions_with_commits) || 0,
224
+ patternConversionRate: parseFloat(row.pattern_conversion_rate) || 0
225
+ }));
226
+ }
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Get Correlations Handler
3
+ * Returns session-to-commit correlation summary for a company
4
+ *
5
+ * GET /api/correlations
6
+ * Query params:
7
+ * - lookbackDays (optional, default: 30)
8
+ *
9
+ * Returns:
10
+ * - Overall correlation summary
11
+ * - Conversion rate metrics
12
+ * - Correlation type distribution
13
+ */
14
+
15
+ const { wrapHandler, executeQuery, createSuccessResponse, createErrorResponse } = require('./helpers');
16
+ const { CorrelationAnalyzer } = require('../../core/CorrelationAnalyzer');
17
+
18
+ exports.handler = wrapHandler(async (event, context) => {
19
+ // Extract user email from Cognito claims
20
+ const email = event.requestContext?.authorizer?.claims?.email;
21
+ if (!email) {
22
+ return createErrorResponse(401, 'Unauthorized - no email in claims');
23
+ }
24
+
25
+ // Parse query parameters
26
+ const queryParams = event.queryStringParameters || {};
27
+ const lookbackDays = parseInt(queryParams.lookbackDays) || 30;
28
+
29
+ // Get user's company
30
+ const companyResult = await executeQuery(`
31
+ SELECT ue."Company_ID"
32
+ FROM "UserEntitlements" ue
33
+ WHERE ue."Email_Address" = $1
34
+ LIMIT 1
35
+ `, [email]);
36
+
37
+ if (companyResult.rows.length === 0) {
38
+ return createErrorResponse(403, 'User not associated with any company');
39
+ }
40
+
41
+ const companyId = companyResult.rows[0].Company_ID;
42
+
43
+ // Initialize analyzer
44
+ const analyzer = new CorrelationAnalyzer();
45
+
46
+ // Get correlation summary
47
+ let summary = null;
48
+ let patternEffectiveness = [];
49
+ let strugglingDevelopers = [];
50
+
51
+ try {
52
+ summary = await analyzer.getCorrelationSummary(companyId, lookbackDays);
53
+ } catch (err) {
54
+ console.error('Error getting correlation summary:', err.message);
55
+ summary = { error: err.message };
56
+ }
57
+
58
+ try {
59
+ patternEffectiveness = await analyzer.getPatternEffectiveness(null, lookbackDays);
60
+ } catch (err) {
61
+ console.error('Error getting pattern effectiveness:', err.message);
62
+ }
63
+
64
+ // Get struggling developers (for admins only)
65
+ try {
66
+ const isAdmin = await checkIsAdmin(email, companyId);
67
+ if (isAdmin) {
68
+ strugglingDevelopers = await analyzer.identifyStrugglingDevelopers(companyId, lookbackDays);
69
+ }
70
+ } catch (err) {
71
+ console.error('Error getting struggling developers:', err.message);
72
+ }
73
+
74
+ return createSuccessResponse({
75
+ summary,
76
+ patternEffectiveness,
77
+ strugglingDevelopers
78
+ });
79
+ });
80
+
81
+ /**
82
+ * Check if user is admin for the company
83
+ */
84
+ async function checkIsAdmin(email, companyId) {
85
+ const result = await executeQuery(`
86
+ SELECT "Admin", "Manager"
87
+ FROM "UserEntitlements"
88
+ WHERE "Email_Address" = $1 AND "Company_ID" = $2
89
+ `, [email, companyId]);
90
+
91
+ if (result.rows.length === 0) return false;
92
+ return result.rows[0].Admin || result.rows[0].Manager;
93
+ }