Skip to main content
Glama
jakedx6
by jakedx6
analytics-insights.js28.1 kB
import { z } from 'zod'; import { supabaseService } from '../lib/api-client.js'; import { requireAuth } from '../lib/auth.js'; import { logger } from '../lib/logger.js'; /** * Get comprehensive project analytics */ export const getProjectAnalyticsTool = { name: 'get_project_analytics', description: 'Get comprehensive analytics and insights for projects', inputSchema: { type: 'object', properties: { project_ids: { type: 'array', items: { type: 'string' }, description: 'Specific project IDs to analyze (optional)' }, time_range: { type: 'string', enum: ['week', 'month', 'quarter', 'year', 'all'], default: 'month', description: 'Time range for analytics' }, metrics: { type: 'array', items: { type: 'string', enum: ['completion_rate', 'velocity', 'team_performance', 'resource_utilization', 'quality_metrics', 'collaboration_index'] }, default: ['completion_rate', 'velocity', 'team_performance'], description: 'Specific metrics to calculate' }, include_predictions: { type: 'boolean', default: false, description: 'Include predictive analytics and forecasts' }, benchmark_comparison: { type: 'boolean', default: false, description: 'Compare against historical benchmarks' } } } }; const GetProjectAnalyticsSchema = z.object({ project_ids: z.array(z.string()).optional(), time_range: z.enum(['week', 'month', 'quarter', 'year', 'all']).default('month'), metrics: z.array(z.enum(['completion_rate', 'velocity', 'team_performance', 'resource_utilization', 'quality_metrics', 'collaboration_index'])).default(['completion_rate', 'velocity', 'team_performance']), include_predictions: z.boolean().default(false), benchmark_comparison: z.boolean().default(false) }); export const getProjectAnalytics = requireAuth(async (args) => { const { project_ids, time_range, metrics, include_predictions, benchmark_comparison } = GetProjectAnalyticsSchema.parse(args); logger.info('Getting project analytics', { project_ids, time_range, metrics }); // Get projects to analyze const projects = project_ids ? await Promise.all(project_ids.map(id => supabaseService.getProject(id))) : await supabaseService.getProjects({}, { limit: 100 }); const analytics = { time_range, projects_analyzed: projects.length, generated_at: new Date().toISOString(), metrics: {} }; // Calculate requested metrics for (const metric of metrics) { try { switch (metric) { case 'completion_rate': analytics.metrics.completion_rate = await calculateCompletionRate(projects, time_range); break; case 'velocity': analytics.metrics.velocity = await calculateVelocity(projects, time_range); break; case 'team_performance': analytics.metrics.team_performance = await calculateTeamPerformance(projects, time_range); break; case 'resource_utilization': analytics.metrics.resource_utilization = await calculateResourceUtilization(projects, time_range); break; case 'quality_metrics': analytics.metrics.quality_metrics = await calculateQualityMetrics(projects, time_range); break; case 'collaboration_index': analytics.metrics.collaboration_index = await calculateCollaborationIndex(projects, time_range); break; } } catch (error) { logger.error(`Failed to calculate ${metric}:`, error); analytics.metrics[metric] = { error: 'Calculation failed' }; } } // Add predictions if requested if (include_predictions) { analytics.predictions = await generatePredictions(projects, analytics.metrics, time_range); } // Add benchmark comparison if requested if (benchmark_comparison) { analytics.benchmarks = await getBenchmarkComparison(analytics.metrics, time_range); } // Generate insights and recommendations analytics.insights = generateAnalyticsInsights(analytics.metrics, projects); analytics.recommendations = generateAnalyticsRecommendations(analytics.metrics, analytics.insights); return analytics; }); /** * Get team productivity insights */ export const getTeamProductivityTool = { name: 'get_team_productivity', description: 'Analyze team productivity patterns and performance', inputSchema: { type: 'object', properties: { team_members: { type: 'array', items: { type: 'string' }, description: 'Specific team member IDs to analyze (optional)' }, project_id: { type: 'string', description: 'Project context for analysis (optional)' }, time_range: { type: 'string', enum: ['week', 'month', 'quarter'], default: 'month', description: 'Time range for analysis' }, productivity_dimensions: { type: 'array', items: { type: 'string', enum: ['task_completion', 'collaboration', 'code_quality', 'documentation', 'mentoring', 'innovation'] }, default: ['task_completion', 'collaboration'], description: 'Dimensions of productivity to measure' } } } }; const GetTeamProductivitySchema = z.object({ team_members: z.array(z.string()).optional(), project_id: z.string().optional(), time_range: z.enum(['week', 'month', 'quarter']).default('month'), productivity_dimensions: z.array(z.enum(['task_completion', 'collaboration', 'code_quality', 'documentation', 'mentoring', 'innovation'])).default(['task_completion', 'collaboration']) }); export const getTeamProductivity = requireAuth(async (args) => { const { team_members, project_id, time_range, productivity_dimensions } = GetTeamProductivitySchema.parse(args); logger.info('Analyzing team productivity', { team_members, project_id, time_range }); // Get team data const teamData = await getTeamData(team_members, project_id, time_range); const productivity = { time_range, team_size: teamData.members.length, project_context: project_id, analyzed_at: new Date().toISOString(), dimensions: {} }; // Analyze each productivity dimension for (const dimension of productivity_dimensions) { try { switch (dimension) { case 'task_completion': productivity.dimensions.task_completion = analyzeTaskCompletion(teamData); break; case 'collaboration': productivity.dimensions.collaboration = analyzeCollaboration(teamData); break; case 'code_quality': productivity.dimensions.code_quality = analyzeCodeQuality(teamData); break; case 'documentation': productivity.dimensions.documentation = analyzeDocumentation(teamData); break; case 'mentoring': productivity.dimensions.mentoring = analyzeMentoring(teamData); break; case 'innovation': productivity.dimensions.innovation = analyzeInnovation(teamData); break; } } catch (error) { logger.error(`Failed to analyze ${dimension}:`, error); productivity.dimensions[dimension] = { error: 'Analysis failed' }; } } // Calculate overall productivity score productivity.overall_score = calculateOverallProductivityScore(productivity.dimensions); // Generate team insights productivity.insights = generateTeamInsights(productivity.dimensions, teamData); productivity.improvement_suggestions = generateImprovementSuggestions(productivity.dimensions); return productivity; }); /** * Get workspace health dashboard */ export const getWorkspaceHealthTool = { name: 'get_workspace_health', description: 'Get comprehensive workspace health metrics and indicators', inputSchema: { type: 'object', properties: { health_categories: { type: 'array', items: { type: 'string', enum: ['project_health', 'team_wellness', 'technical_debt', 'documentation_coverage', 'automation_efficiency', 'security_posture'] }, default: ['project_health', 'team_wellness', 'documentation_coverage'], description: 'Categories of health metrics to assess' }, alert_thresholds: { type: 'object', properties: { critical: { type: 'number', default: 30 }, warning: { type: 'number', default: 60 }, good: { type: 'number', default: 80 } }, description: 'Thresholds for health score alerts' }, include_recommendations: { type: 'boolean', default: true, description: 'Include actionable recommendations' } } } }; const GetWorkspaceHealthSchema = z.object({ health_categories: z.array(z.enum(['project_health', 'team_wellness', 'technical_debt', 'documentation_coverage', 'automation_efficiency', 'security_posture'])).default(['project_health', 'team_wellness', 'documentation_coverage']), alert_thresholds: z.object({ critical: z.number().default(30), warning: z.number().default(60), good: z.number().default(80) }).default({}), include_recommendations: z.boolean().default(true) }); export const getWorkspaceHealth = requireAuth(async (args) => { const { health_categories, alert_thresholds, include_recommendations } = GetWorkspaceHealthSchema.parse(args); logger.info('Assessing workspace health', { health_categories }); const health = { overall_score: 0, status: 'unknown', assessed_at: new Date().toISOString(), categories: {}, alerts: [] }; const categoryScores = []; // Assess each health category for (const category of health_categories) { try { let categoryHealth; switch (category) { case 'project_health': categoryHealth = await assessProjectHealth(); break; case 'team_wellness': categoryHealth = await assessTeamWellness(); break; case 'technical_debt': categoryHealth = await assessTechnicalDebt(); break; case 'documentation_coverage': categoryHealth = await assessDocumentationCoverage(); break; case 'automation_efficiency': categoryHealth = await assessAutomationEfficiency(); break; case 'security_posture': categoryHealth = await assessSecurityPosture(); break; default: continue; } health.categories[category] = categoryHealth; categoryScores.push(categoryHealth.score); // Check for alerts if (categoryHealth.score <= alert_thresholds.critical) { health.alerts.push({ level: 'critical', category, message: `${category} score is critically low (${categoryHealth.score}%)`, recommendations: categoryHealth.urgent_actions || [] }); } else if (categoryHealth.score <= alert_thresholds.warning) { health.alerts.push({ level: 'warning', category, message: `${category} needs attention (${categoryHealth.score}%)`, recommendations: categoryHealth.improvements || [] }); } } catch (error) { logger.error(`Failed to assess ${category}:`, error); health.categories[category] = { error: 'Assessment failed', score: 0 }; } } // Calculate overall health score health.overall_score = categoryScores.length > 0 ? Math.round(categoryScores.reduce((sum, score) => sum + score, 0) / categoryScores.length) : 0; // Determine overall status if (health.overall_score >= alert_thresholds.good) { health.status = 'healthy'; } else if (health.overall_score >= alert_thresholds.warning) { health.status = 'needs_attention'; } else { health.status = 'critical'; } // Generate recommendations if requested if (include_recommendations) { health.recommendations = generateWorkspaceRecommendations(health.categories, health.alerts); } return health; }); /** * Generate custom analytics report */ export const generateCustomReportTool = { name: 'generate_custom_report', description: 'Generate a custom analytics report with specified metrics and visualizations', inputSchema: { type: 'object', properties: { report_name: { type: 'string', description: 'Name for the custom report' }, data_sources: { type: 'array', items: { type: 'string', enum: ['projects', 'tasks', 'documents', 'conversations', 'team_members', 'automations'] }, description: 'Data sources to include in the report' }, metrics_config: { type: 'object', properties: { time_grouping: { type: 'string', enum: ['day', 'week', 'month'], default: 'week' }, aggregation_method: { type: 'string', enum: ['sum', 'average', 'count'], default: 'count' }, filters: { type: 'object' } }, description: 'Configuration for metrics calculation' }, output_format: { type: 'string', enum: ['json', 'csv', 'summary'], default: 'json', description: 'Output format for the report' }, schedule: { type: 'object', properties: { frequency: { type: 'string', enum: ['once', 'daily', 'weekly', 'monthly'] }, recipients: { type: 'array', items: { type: 'string' } } }, description: 'Optional scheduling configuration' } }, required: ['report_name', 'data_sources'] } }; const GenerateCustomReportSchema = z.object({ report_name: z.string().min(1), data_sources: z.array(z.enum(['projects', 'tasks', 'documents', 'conversations', 'team_members', 'automations'])).min(1), metrics_config: z.object({ time_grouping: z.enum(['day', 'week', 'month']).default('week'), aggregation_method: z.enum(['sum', 'average', 'count']).default('count'), filters: z.record(z.any()).optional() }).optional(), output_format: z.enum(['json', 'csv', 'summary']).default('json'), schedule: z.object({ frequency: z.enum(['once', 'daily', 'weekly', 'monthly']), recipients: z.array(z.string()) }).optional() }); export const generateCustomReport = requireAuth(async (args) => { const { report_name, data_sources, metrics_config, output_format, schedule } = GenerateCustomReportSchema.parse(args); logger.info('Generating custom report', { report_name, data_sources, output_format }); const report = { name: report_name, generated_at: new Date().toISOString(), data_sources, metrics_config: metrics_config || {}, data: {} }; // Collect data from each source for (const source of data_sources) { try { report.data[source] = await collectReportData(source, metrics_config); } catch (error) { logger.error(`Failed to collect data from ${source}:`, error); report.data[source] = { error: 'Data collection failed' }; } } // Calculate summary statistics report.summary = calculateReportSummary(report.data, metrics_config); // Format output based on requested format if (output_format === 'csv') { report.csv_data = convertToCSV(report.data); } else if (output_format === 'summary') { return { report_name, summary: report.summary, key_insights: generateReportInsights(report.data), generated_at: report.generated_at }; } // Set up scheduling if requested if (schedule) { report.schedule_id = await scheduleReport(report_name, args, schedule); } return report; }); // Helper functions for analytics calculations async function calculateCompletionRate(projects, timeRange) { const projectStats = await Promise.all(projects.map(async (project) => { const tasks = await supabaseService.getTasks({ project_id: project.id }); const completedTasks = tasks.filter(t => t.status === 'done'); return { project_id: project.id, project_name: project.name, total_tasks: tasks.length, completed_tasks: completedTasks.length, completion_rate: tasks.length > 0 ? (completedTasks.length / tasks.length) * 100 : 0 }; })); const overallRate = projectStats.reduce((sum, stats) => sum + stats.completion_rate, 0) / projectStats.length; return { overall_completion_rate: Math.round(overallRate * 10) / 10, project_breakdown: projectStats, trend: 'stable', // Would calculate actual trend time_range: timeRange }; } async function calculateVelocity(projects, timeRange) { // Calculate task completion velocity const now = new Date(); const periodStart = getPeriodStart(now, timeRange); let totalCompleted = 0; let totalEstimated = 0; for (const project of projects) { const tasks = await supabaseService.getTasks({ project_id: project.id }); const recentCompleted = tasks.filter(t => t.status === 'done' && new Date(t.updated_at) >= periodStart); totalCompleted += recentCompleted.length; totalEstimated += recentCompleted.reduce((sum, t) => sum + 8, 0); // Default 8 hours per task } return { tasks_per_period: totalCompleted, estimated_hours_per_period: totalEstimated, velocity_trend: 'increasing', // Would calculate actual trend period: timeRange }; } async function calculateTeamPerformance(projects, timeRange) { // Get all team members and their performance const teamStats = new Map(); for (const project of projects) { const tasks = await supabaseService.getTasks({ project_id: project.id }); tasks.forEach(task => { if (task.assignee_id) { if (!teamStats.has(task.assignee_id)) { teamStats.set(task.assignee_id, { assigned_tasks: 0, completed_tasks: 0, overdue_tasks: 0 }); } const stats = teamStats.get(task.assignee_id); stats.assigned_tasks++; if (task.status === 'done') { stats.completed_tasks++; } if (task.due_date && new Date(task.due_date) < new Date() && task.status !== 'done') { stats.overdue_tasks++; } } }); } const performanceData = Array.from(teamStats.entries()).map(([userId, stats]) => ({ user_id: userId, completion_rate: stats.assigned_tasks > 0 ? (stats.completed_tasks / stats.assigned_tasks) * 100 : 0, overdue_rate: stats.assigned_tasks > 0 ? (stats.overdue_tasks / stats.assigned_tasks) * 100 : 0, total_tasks: stats.assigned_tasks })); return { team_size: performanceData.length, average_completion_rate: performanceData.reduce((sum, p) => sum + p.completion_rate, 0) / performanceData.length, individual_performance: performanceData, top_performers: performanceData.sort((a, b) => b.completion_rate - a.completion_rate).slice(0, 3) }; } async function calculateResourceUtilization(projects, timeRange) { // Placeholder implementation return { utilization_rate: 78.5, peak_usage: 'Tuesday-Thursday', bottlenecks: ['Design review', 'Testing phase'] }; } async function calculateQualityMetrics(projects, timeRange) { // Placeholder implementation return { documentation_coverage: 65, code_review_rate: 89, bug_fix_rate: 92, customer_satisfaction: 4.2 }; } async function calculateCollaborationIndex(projects, timeRange) { // Placeholder implementation return { collaboration_score: 72, cross_team_projects: 3, knowledge_sharing_events: 8, mentoring_relationships: 12 }; } async function generatePredictions(projects, metrics, timeRange) { // Simple prediction based on current trends return { completion_forecast: { next_period: Math.round(metrics.completion_rate?.overall_completion_rate * 1.05) || 0, confidence: 75 }, velocity_forecast: { next_period: Math.round(metrics.velocity?.tasks_per_period * 1.1) || 0, confidence: 80 } }; } async function getBenchmarkComparison(metrics, timeRange) { // Placeholder benchmark data return { industry_average: { completion_rate: 68, velocity: 15, team_performance: 72 }, comparison: { completion_rate: metrics.completion_rate ? 'above_average' : 'unknown', velocity: metrics.velocity ? 'average' : 'unknown', team_performance: metrics.team_performance ? 'above_average' : 'unknown' } }; } function generateAnalyticsInsights(metrics, projects) { const insights = []; if (metrics.completion_rate?.overall_completion_rate > 80) { insights.push('Excellent task completion rate indicates strong project execution'); } if (metrics.team_performance?.average_completion_rate < 60) { insights.push('Team performance below optimal - consider workload redistribution'); } if (projects.length > 10 && metrics.velocity?.tasks_per_period < 20) { insights.push('Low velocity relative to project count - may need process optimization'); } return insights; } function generateAnalyticsRecommendations(metrics, insights) { const recommendations = []; if (metrics.completion_rate?.overall_completion_rate < 70) { recommendations.push('Focus on breaking down large tasks and improving estimation accuracy'); } if (metrics.team_performance?.team_size < 3) { recommendations.push('Consider expanding team capacity for better project coverage'); } return recommendations; } // Additional helper functions would be implemented here... async function getTeamData(teamMembers, projectId, timeRange) { return { members: [] }; // Placeholder } function analyzeTaskCompletion(teamData) { return { score: 85, trends: 'positive' }; // Placeholder } function analyzeCollaboration(teamData) { return { score: 72, interaction_frequency: 'high' }; // Placeholder } function analyzeCodeQuality(teamData) { return { score: 78, review_coverage: 85 }; // Placeholder } function analyzeDocumentation(teamData) { return { score: 65, coverage: 'medium' }; // Placeholder } function analyzeMentoring(teamData) { return { score: 60, active_relationships: 3 }; // Placeholder } function analyzeInnovation(teamData) { return { score: 70, new_ideas: 5 }; // Placeholder } function calculateOverallProductivityScore(dimensions) { const scores = Object.values(dimensions).map((d) => d.score).filter(s => typeof s === 'number'); return scores.length > 0 ? Math.round(scores.reduce((sum, score) => sum + score, 0) / scores.length) : 0; } function generateTeamInsights(dimensions, teamData) { return ['Team showing strong collaboration patterns', 'Documentation practices need improvement']; } function generateImprovementSuggestions(dimensions) { return ['Implement pair programming sessions', 'Create documentation templates']; } async function assessProjectHealth() { return { score: 82, active_projects: 5, blocked_projects: 1 }; } async function assessTeamWellness() { return { score: 75, workload_balance: 'good', burnout_risk: 'low' }; } async function assessTechnicalDebt() { return { score: 65, debt_items: 8, critical_issues: 2 }; } async function assessDocumentationCoverage() { return { score: 70, coverage_percentage: 68, missing_docs: 12 }; } async function assessAutomationEfficiency() { return { score: 85, automated_tasks: 15, manual_processes: 5 }; } async function assessSecurityPosture() { return { score: 88, vulnerabilities: 2, compliance_score: 92 }; } function generateWorkspaceRecommendations(categories, alerts) { const recommendations = []; if (alerts.some(a => a.level === 'critical')) { recommendations.push('Address critical health issues immediately'); } recommendations.push('Schedule regular health assessments'); recommendations.push('Implement automated monitoring for key metrics'); return recommendations; } async function collectReportData(source, config) { // Placeholder for data collection return { count: 10, trend: 'positive' }; } function calculateReportSummary(data, config) { return { total_items: 100, growth_rate: 5.2 }; } function generateReportInsights(data) { return ['Strong growth in documentation', 'Task completion trending upward']; } function convertToCSV(data) { return 'header1,header2\nvalue1,value2'; // Placeholder } async function scheduleReport(name, config, schedule) { return 'schedule_123'; // Placeholder } function getPeriodStart(date, period) { const start = new Date(date); switch (period) { case 'week': start.setDate(date.getDate() - 7); break; case 'month': start.setMonth(date.getMonth() - 1); break; case 'quarter': start.setMonth(date.getMonth() - 3); break; case 'year': start.setFullYear(date.getFullYear() - 1); break; } return start; } // Export all analytics tools export const analyticsInsightsTools = { getProjectAnalyticsTool, getTeamProductivityTool, getWorkspaceHealthTool, generateCustomReportTool }; export const analyticsInsightsHandlers = { get_project_analytics: getProjectAnalytics, get_team_productivity: getTeamProductivity, get_workspace_health: getWorkspaceHealth, generate_custom_report: generateCustomReport };

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/jakedx6/helios9-MCP-Server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server