const express = require('express'); const cors = require('cors'); const CaseAnalyticsService = require('./case-analytics-service'); const app = express(); const PORT = process.env.ANALYTICS_PORT || 3001; // Middleware app.use(cors()); app.use(express.json()); // Initialize analytics service const analyticsService = new CaseAnalyticsService(); // Health check app.get('/health', (req, res) => { res.json({ status: 'OK', service: 'Case Analytics API', timestamp: new Date().toISOString() }); }); // ==================== // DASHBOARD API ENDPOINTS // ==================== // Overall KPIs - Homepage dashboard cards app.get('/api/analytics/kpis', async (req, res) => { try { const kpis = await analyticsService.getOverallKPIs(); res.json({ success: true, data: kpis, cached_at: new Date().toISOString() }); } catch (error) { res.status(500).json({ success: false, error: error.message, endpoint: '/api/analytics/kpis' }); } }); // Project analytics - Project performance dashboard // Project analytics with filtering and pagination app.get('/api/analytics/projects', async (req, res) => { try { const { limit = 50, offset = 0, sortBy = 'profit_margin', sortDir = 'desc' } = req.query; const projects = await analyticsService.generateProjectAnalytics(); res.json({ success: true, data: projects, pagination: { limit: parseInt(limit), offset: parseInt(offset) }, cached_at: new Date().toISOString() }); } catch (error) { res.status(500).json({ success: false, error: error.message, endpoint: '/api/analytics/projects' }); } }); // Single project detailed analytics app.get('/api/analytics/projects/:id', async (req, res) => { try { const { id } = req.params; const projectAnalytics = await analyticsService.getSingleProjectAnalytics(parseInt(id)); res.json({ success: true, data: projectAnalytics, cached_at: new Date().toISOString() }); } catch (error) { res.status(500).json({ success: false, error: error.message, endpoint: `/api/analytics/projects/${req.params.id}` }); } }); // Employee performance metrics app.get('/api/analytics/employees', async (req, res) => { try { const employees = await analyticsService.getEmployeePerformanceMetrics(); res.json({ success: true, data: employees, count: employees.length }); } catch (error) { res.status(500).json({ success: false, error: error.message, endpoint: '/api/analytics/employees' }); } }); // Customer analytics and insights app.get('/api/analytics/customers', async (req, res) => { try { const { top = 25 } = req.query; let customers = await analyticsService.getCustomerAnalytics(); // Get top customers by value customers = customers.slice(0, parseInt(top)); res.json({ success: true, data: customers, count: customers.length, showing_top: parseInt(top) }); } catch (error) { res.status(500).json({ success: false, error: error.message, endpoint: '/api/analytics/customers' }); } }); // Material and supplier insights app.get('/api/analytics/materials', async (req, res) => { try { const materials = await analyticsService.getMaterialInsights(); res.json({ success: true, data: materials, count: materials.length }); } catch (error) { res.status(500).json({ success: false, error: error.message, endpoint: '/api/analytics/materials' }); } }); // Trend analysis - charts and time series app.get('/api/analytics/trends', async (req, res) => { try { const { months = 12 } = req.query; const trends = await analyticsService.getTrendAnalytics(parseInt(months)); res.json({ success: true, data: trends, period_months: parseInt(months), count: trends.length }); } catch (error) { res.status(500).json({ success: false, error: error.message, endpoint: '/api/analytics/trends' }); } }); // Quote accuracy metrics - tilbudsberegner performance app.get('/api/analytics/quote-accuracy', async (req, res) => { try { const accuracy = await analyticsService.calculateQuoteAccuracyMetrics(); res.json({ success: true, data: accuracy, generated_at: new Date().toISOString() }); } catch (error) { res.status(500).json({ success: false, error: error.message, endpoint: '/api/analytics/quote-accuracy' }); } }); // Comprehensive analytics report - full dashboard data app.get('/api/analytics/report/full', async (req, res) => { try { const report = await analyticsService.generateFullAnalyticsReport(); res.json({ success: true, data: report }); } catch (error) { res.status(500).json({ success: false, error: error.message, endpoint: '/api/analytics/report/full' }); } }); // Single project deep dive analysis app.get('/api/analytics/projects/:caseNumber', async (req, res) => { try { const { caseNumber } = req.params; const projects = await analyticsService.generateProjectAnalytics(caseNumber); if (projects.length === 0) { return res.status(404).json({ success: false, error: `Case ${caseNumber} not found or has no data` }); } res.json({ success: true, data: projects[0], case_number: caseNumber }); } catch (error) { res.status(500).json({ success: false, error: error.message, endpoint: `/api/analytics/projects/${req.params.caseNumber}` }); } }); // ==================== // BUSINESS INTELLIGENCE ENDPOINTS // ==================== // Executive summary for management app.get('/api/analytics/executive-summary', async (req, res) => { try { const kpis = await analyticsService.getOverallKPIs(); const trends = await analyticsService.getTrendAnalytics(3); // Last 3 months const topCustomers = await analyticsService.getCustomerAnalytics(); const currentMonth = trends[0] || {}; const previousMonth = trends[1] || {}; const summary = { period: "Last 30 days vs Previous 30 days", revenue: { current: kpis.total_project_value, trend: currentMonth.estimated_revenue && previousMonth.estimated_revenue ? ((currentMonth.estimated_revenue - previousMonth.estimated_revenue) / previousMonth.estimated_revenue * 100).toFixed(1) + '%' : 'N/A' }, projects: { active: kpis.total_projects, trend: currentMonth.active_projects && previousMonth.active_projects ? ((currentMonth.active_projects - previousMonth.active_projects) / previousMonth.active_projects * 100).toFixed(1) + '%' : 'N/A' }, profit_margin: kpis.overall_profit_margin, top_customers: topCustomers.slice(0, 5), team_utilization: currentMonth.utilization_rate || 0, key_metrics: { avg_project_value: kpis.avg_project_value, avg_hours_per_project: kpis.avg_hours_per_project, material_margin: kpis.avg_material_margin } }; res.json({ success: true, data: summary, generated_at: new Date().toISOString() }); } catch (error) { res.status(500).json({ success: false, error: error.message, endpoint: '/api/analytics/executive-summary' }); } }); // Performance benchmarks for quote calibration app.get('/api/analytics/benchmarks', async (req, res) => { try { const { project_size = 'medium', complexity = 3 } = req.query; // Filter projects by similar characteristics const allProjects = await analyticsService.generateProjectAnalytics(); let similarProjects = allProjects; // Size filtering if (project_size === 'small') { similarProjects = similarProjects.filter(p => p.actual_hours <= 200); } else if (project_size === 'medium') { similarProjects = similarProjects.filter(p => p.actual_hours > 200 && p.actual_hours <= 800); } else if (project_size === 'large') { similarProjects = similarProjects.filter(p => p.actual_hours > 800 && p.actual_hours <= 1500); } else if (project_size === 'xlarge') { similarProjects = similarProjects.filter(p => p.actual_hours > 1500); } if (similarProjects.length === 0) { return res.json({ success: true, data: { message: "No similar projects found for benchmarking" }, filters: { project_size, complexity } }); } // Calculate benchmarks const totalHours = similarProjects.reduce((sum, p) => sum + p.actual_hours, 0); const totalValue = similarProjects.reduce((sum, p) => sum + p.total_project_value, 0); const avgHours = totalHours / similarProjects.length; const avgValue = totalValue / similarProjects.length; const avgHourlyRate = totalValue / totalHours; const benchmarks = { sample_size: similarProjects.length, filters_applied: { project_size, complexity }, benchmarks: { avg_hours: Math.round(avgHours * 10) / 10, avg_project_value: Math.round(avgValue), avg_hourly_rate: Math.round(avgHourlyRate * 10) / 10, hours_range: { min: Math.min(...similarProjects.map(p => p.actual_hours)), max: Math.max(...similarProjects.map(p => p.actual_hours)), percentile_25: this.calculatePercentile(similarProjects.map(p => p.actual_hours), 25), percentile_75: this.calculatePercentile(similarProjects.map(p => p.actual_hours), 75) }, material_ratio: totalValue > 0 ? (similarProjects.reduce((sum, p) => sum + (p.material_sales || 0), 0) / totalValue * 100) : 0 }, use_case: "Quote calculator calibration and accuracy improvement" }; res.json({ success: true, data: benchmarks }); } catch (error) { res.status(500).json({ success: false, error: error.message, endpoint: '/api/analytics/benchmarks' }); } }); // Helper function for percentile calculation app.calculatePercentile = function(arr, percentile) { const sorted = arr.slice().sort((a, b) => a - b); const index = (percentile / 100) * (sorted.length - 1); const lower = Math.floor(index); const upper = Math.ceil(index); const weight = index % 1; if (upper >= sorted.length) return sorted[sorted.length - 1]; return sorted[lower] * (1 - weight) + sorted[upper] * weight; }; // Error handling middleware app.use((error, req, res, next) => { console.error('Analytics API Error:', error); res.status(500).json({ success: false, error: 'Internal server error', message: error.message, timestamp: new Date().toISOString() }); }); // Start server app.listen(PORT, () => { console.log(`🚀 Case Analytics API server running on port ${PORT}`); console.log(`📊 Available endpoints:`); console.log(` GET /api/analytics/kpis - Overall business KPIs`); console.log(` GET /api/analytics/projects - Project analytics`); console.log(` GET /api/analytics/employees - Employee performance`); console.log(` GET /api/analytics/customers - Customer insights`); console.log(` GET /api/analytics/materials - Material/supplier analysis`); console.log(` GET /api/analytics/trends - Time-based trends`); console.log(` GET /api/analytics/quote-accuracy - Quote calculator performance`); console.log(` GET /api/analytics/report/full - Comprehensive analytics report`); console.log(` GET /api/analytics/executive-summary - Executive dashboard`); console.log(` GET /api/analytics/benchmarks - Performance benchmarks`); }); module.exports = app;