@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.
- package/README.md +300 -0
- package/hooks/README.md +494 -0
- package/hooks/pre-compact.js +392 -0
- package/hooks/session-start.js +264 -0
- package/package.json +90 -0
- package/scripts/harvest.js +561 -0
- package/scripts/init-project.js +437 -0
- package/scripts/inject.js +388 -0
- package/src/collaboration/CollaborationPrompt.js +460 -0
- package/src/core/AlertEngine.js +813 -0
- package/src/core/AlertNotifier.js +363 -0
- package/src/core/CorrelationAnalyzer.js +774 -0
- package/src/core/CurationEngine.js +688 -0
- package/src/core/LLMPatternDetector.js +508 -0
- package/src/core/LoadBearingDetector.js +242 -0
- package/src/core/NotificationService.js +1032 -0
- package/src/core/PatternValidator.js +355 -0
- package/src/core/README.md +160 -0
- package/src/core/RapportOrchestrator.js +446 -0
- package/src/core/RelevanceDetector.js +577 -0
- package/src/core/StandardsIngestion.js +575 -0
- package/src/core/TeamLoadBearingDetector.js +431 -0
- package/src/database/dbOperations.js +105 -0
- package/src/handlers/activity/activityGetMe.js +98 -0
- package/src/handlers/activity/activityGetTeam.js +130 -0
- package/src/handlers/alerts/alertsAcknowledge.js +91 -0
- package/src/handlers/alerts/alertsGet.js +250 -0
- package/src/handlers/collaborators/collaboratorAdd.js +201 -0
- package/src/handlers/collaborators/collaboratorInvite.js +218 -0
- package/src/handlers/collaborators/collaboratorList.js +88 -0
- package/src/handlers/collaborators/collaboratorRemove.js +127 -0
- package/src/handlers/collaborators/inviteAccept.js +122 -0
- package/src/handlers/context/contextGet.js +57 -0
- package/src/handlers/context/invariantsGet.js +74 -0
- package/src/handlers/context/loopsGet.js +82 -0
- package/src/handlers/context/notesCreate.js +74 -0
- package/src/handlers/context/purposeGet.js +78 -0
- package/src/handlers/correlations/correlationsDeveloperGet.js +226 -0
- package/src/handlers/correlations/correlationsGet.js +93 -0
- package/src/handlers/correlations/correlationsProjectGet.js +161 -0
- package/src/handlers/github/githubConnectionStatus.js +49 -0
- package/src/handlers/github/githubDiscoverPatterns.js +364 -0
- package/src/handlers/github/githubOAuthCallback.js +166 -0
- package/src/handlers/github/githubOAuthStart.js +59 -0
- package/src/handlers/github/githubPatternsReview.js +109 -0
- package/src/handlers/github/githubReposList.js +105 -0
- package/src/handlers/helpers/checkSuperAdmin.js +85 -0
- package/src/handlers/helpers/dbOperations.js +53 -0
- package/src/handlers/helpers/errorHandler.js +49 -0
- package/src/handlers/helpers/index.js +106 -0
- package/src/handlers/helpers/lambdaWrapper.js +60 -0
- package/src/handlers/helpers/responseUtil.js +55 -0
- package/src/handlers/helpers/subscriptionTiers.js +1168 -0
- package/src/handlers/notifications/getPreferences.js +84 -0
- package/src/handlers/notifications/sendNotification.js +170 -0
- package/src/handlers/notifications/updatePreferences.js +316 -0
- package/src/handlers/patterns/patternUsagePost.js +182 -0
- package/src/handlers/patterns/patternViolationPost.js +185 -0
- package/src/handlers/projects/projectCreate.js +107 -0
- package/src/handlers/projects/projectDelete.js +82 -0
- package/src/handlers/projects/projectGet.js +95 -0
- package/src/handlers/projects/projectUpdate.js +118 -0
- package/src/handlers/reports/aiLeverage.js +206 -0
- package/src/handlers/reports/engineeringInvestment.js +132 -0
- package/src/handlers/reports/riskForecast.js +186 -0
- package/src/handlers/reports/standardsRoi.js +162 -0
- package/src/handlers/scheduled/analyzeCorrelations.js +178 -0
- package/src/handlers/scheduled/analyzeGitHistory.js +510 -0
- package/src/handlers/scheduled/generateAlerts.js +135 -0
- package/src/handlers/scheduled/refreshActivity.js +21 -0
- package/src/handlers/scheduled/scanCompliance.js +334 -0
- package/src/handlers/sessions/sessionEndPost.js +180 -0
- package/src/handlers/sessions/sessionStandardsPost.js +135 -0
- package/src/handlers/stripe/addonManagePost.js +240 -0
- package/src/handlers/stripe/billingPortalPost.js +93 -0
- package/src/handlers/stripe/enterpriseCheckoutPost.js +272 -0
- package/src/handlers/stripe/seatsUpdatePost.js +185 -0
- package/src/handlers/stripe/subscriptionCancelDelete.js +169 -0
- package/src/handlers/stripe/subscriptionCreatePost.js +221 -0
- package/src/handlers/stripe/subscriptionUpdatePut.js +163 -0
- package/src/handlers/stripe/webhookPost.js +454 -0
- package/src/handlers/users/cognitoPostConfirmation.js +150 -0
- package/src/handlers/users/userEntitlementsGet.js +89 -0
- package/src/handlers/users/userGet.js +114 -0
- package/src/handlers/webhooks/githubWebhook.js +223 -0
- 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
|
+
}
|