Files
tilbudgivern/archive/analysis/analytics-api-server.js
alexpolo1 4a372d2e28 clean up
2025-10-30 18:28:58 +00:00

387 lines
13 KiB
JavaScript

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;