Files
tilbudgivern/archive/analysis/analytics-auto-update-service.js
T
2025-10-30 18:28:58 +00:00

282 lines
11 KiB
JavaScript

const CaseAnalyticsService = require('./case-analytics-service');
const cron = require('node-cron');
const fs = require('fs').promises;
const path = require('path');
class AnalyticsAutoUpdateService {
constructor() {
this.analyticsService = new CaseAnalyticsService();
this.cacheDir = path.join(__dirname, 'analytics-cache');
this.isRunning = false;
// Ensure cache directory exists
this.initializeCacheDirectory();
}
async initializeCacheDirectory() {
try {
await fs.mkdir(this.cacheDir, { recursive: true });
console.log(`📁 Analytics cache directory ready: ${this.cacheDir}`);
} catch (error) {
console.error('Error creating cache directory:', error);
}
}
// Cache analytics data to files for fast API responses
async cacheAnalyticsData(data, filename) {
try {
const filePath = path.join(this.cacheDir, `${filename}.json`);
const cacheData = {
data: data,
cached_at: new Date().toISOString(),
expires_at: new Date(Date.now() + 30 * 60 * 1000).toISOString() // 30 minutes
};
await fs.writeFile(filePath, JSON.stringify(cacheData, null, 2));
console.log(`💾 Cached ${filename} data (${JSON.stringify(cacheData).length} bytes)`);
} catch (error) {
console.error(`Error caching ${filename}:`, error);
}
}
// Load cached analytics data
async loadCachedData(filename) {
try {
const filePath = path.join(this.cacheDir, `${filename}.json`);
const fileContent = await fs.readFile(filePath, 'utf8');
const cacheData = JSON.parse(fileContent);
// Check if cache is still valid
const expiresAt = new Date(cacheData.expires_at);
if (new Date() > expiresAt) {
return null; // Cache expired
}
return cacheData.data;
} catch (error) {
return null; // File doesn't exist or error reading
}
}
// Generate and cache comprehensive analytics
async updateAnalyticsCache() {
if (this.isRunning) {
console.log('⏳ Analytics update already running, skipping...');
return;
}
this.isRunning = true;
console.log('🔄 Starting analytics cache update...');
try {
const startTime = Date.now();
// Generate all analytics data
console.log('📊 Generating KPIs...');
const kpis = await this.analyticsService.getOverallKPIs();
await this.cacheAnalyticsData(kpis, 'kpis');
console.log('👥 Generating employee metrics...');
const employees = await this.analyticsService.getEmployeePerformanceMetrics();
await this.cacheAnalyticsData(employees, 'employees');
console.log('🏢 Generating customer analytics...');
const customers = await this.analyticsService.getCustomerAnalytics();
await this.cacheAnalyticsData(customers, 'customers');
console.log('🔨 Generating material insights...');
const materials = await this.analyticsService.getMaterialInsights();
await this.cacheAnalyticsData(materials, 'materials');
console.log('📈 Generating trend data...');
const trends = await this.analyticsService.getTrendAnalytics(12);
await this.cacheAnalyticsData(trends, 'trends');
console.log('📋 Generating project analytics...');
const projects = await this.analyticsService.generateProjectAnalytics();
await this.cacheAnalyticsData(projects, 'projects');
// Generate comprehensive report
console.log('📊 Generating full analytics report...');
const fullReport = await this.analyticsService.generateFullAnalyticsReport();
await this.cacheAnalyticsData(fullReport, 'full-report');
// Cache summary for quick access
const summary = {
last_updated: new Date().toISOString(),
total_projects: kpis.total_projects,
total_value: kpis.total_project_value,
profit_margin: kpis.overall_profit_margin,
active_employees: kpis.active_employees,
top_employee: employees[0]?.full_name || 'N/A',
top_customer: customers[0]?.customer_number || 'N/A',
update_duration_ms: Date.now() - startTime
};
await this.cacheAnalyticsData(summary, 'summary');
console.log(`✅ Analytics cache update completed in ${Date.now() - startTime}ms`);
console.log(`📈 Summary: ${kpis.total_projects} projects, ${kpis.total_project_value?.toLocaleString()} kr total value`);
} catch (error) {
console.error('❌ Error updating analytics cache:', error);
} finally {
this.isRunning = false;
}
}
// Update quote calculator benchmarks based on fresh analytics
async updateQuoteCalculatorBenchmarks() {
try {
console.log('🎯 Updating quote calculator benchmarks...');
const projects = await this.analyticsService.generateProjectAnalytics();
// Calculate size-based benchmarks
const sizeRanges = {
small: projects.filter(p => p.actual_hours <= 200),
medium: projects.filter(p => p.actual_hours > 200 && p.actual_hours <= 800),
large: projects.filter(p => p.actual_hours > 800 && p.actual_hours <= 1500),
xlarge: projects.filter(p => p.actual_hours > 1500)
};
const benchmarks = {};
Object.keys(sizeRanges).forEach(size => {
const sizeProjects = sizeRanges[size];
if (sizeProjects.length > 0) {
const avgHours = sizeProjects.reduce((sum, p) => sum + p.actual_hours, 0) / sizeProjects.length;
const avgValue = sizeProjects.reduce((sum, p) => sum + p.total_project_value, 0) / sizeProjects.length;
const avgHourlyRate = avgValue / avgHours;
benchmarks[size] = {
sample_size: sizeProjects.length,
avg_hours: Math.round(avgHours * 10) / 10,
avg_value: Math.round(avgValue),
avg_hourly_rate: Math.round(avgHourlyRate * 10) / 10,
material_ratio: avgValue > 0 ?
(sizeProjects.reduce((sum, p) => sum + (p.material_sales || 0), 0) / (avgValue * sizeProjects.length) * 100) : 0
};
}
});
await this.cacheAnalyticsData(benchmarks, 'quote-benchmarks');
console.log('✅ Quote calculator benchmarks updated');
} catch (error) {
console.error('❌ Error updating quote benchmarks:', error);
}
}
// Check for new/updated cases and trigger analytics update
async checkForUpdatesAndRefresh() {
// This would integrate with the Ordrestyring sync process
// For now, we'll just run analytics update on schedule
console.log('🔍 Checking for data updates...');
await this.updateAnalyticsCache();
await this.updateQuoteCalculatorBenchmarks();
}
// Start scheduled analytics updates
startScheduledUpdates() {
console.log('🚀 Starting analytics auto-update service...');
// Initial update
this.updateAnalyticsCache();
// Update every 30 minutes
cron.schedule('*/30 * * * *', async () => {
console.log('⏰ Scheduled analytics update triggered');
await this.checkForUpdatesAndRefresh();
});
// Full deep analytics every 4 hours
cron.schedule('0 */4 * * *', async () => {
console.log('⏰ Deep analytics update triggered');
await this.updateAnalyticsCache();
await this.updateQuoteCalculatorBenchmarks();
});
// Daily comprehensive report generation
cron.schedule('0 6 * * *', async () => {
console.log('📊 Daily comprehensive analytics report generation');
const report = await this.analyticsService.generateFullAnalyticsReport();
await this.cacheAnalyticsData(report, `daily-report-${new Date().toISOString().split('T')[0]}`);
});
console.log('✅ Analytics auto-update service started');
console.log('📅 Schedule:');
console.log(' - Every 30 minutes: Analytics cache refresh');
console.log(' - Every 4 hours: Deep analytics + benchmarks update');
console.log(' - Daily at 06:00: Comprehensive report generation');
}
// Stop scheduled updates
stopScheduledUpdates() {
// This would stop all cron jobs if needed
console.log('🛑 Analytics auto-update service stopped');
}
// Enhanced API method to serve cached data or generate fresh
async getAnalyticsData(type, maxAge = 30 * 60 * 1000) { // 30 minutes default
try {
// Try to load from cache first
const cachedData = await this.loadCachedData(type);
if (cachedData) {
console.log(`📱 Serving cached ${type} data`);
return { success: true, data: cachedData, source: 'cache' };
}
// If no cache, generate fresh data
console.log(`🔄 Generating fresh ${type} data`);
let freshData;
switch (type) {
case 'kpis':
freshData = await this.analyticsService.getOverallKPIs();
break;
case 'employees':
freshData = await this.analyticsService.getEmployeePerformanceMetrics();
break;
case 'customers':
freshData = await this.analyticsService.getCustomerAnalytics();
break;
case 'materials':
freshData = await this.analyticsService.getMaterialInsights();
break;
case 'trends':
freshData = await this.analyticsService.getTrendAnalytics();
break;
case 'projects':
freshData = await this.analyticsService.generateProjectAnalytics();
break;
default:
throw new Error(`Unknown analytics type: ${type}`);
}
// Cache the fresh data
await this.cacheAnalyticsData(freshData, type);
return { success: true, data: freshData, source: 'fresh' };
} catch (error) {
console.error(`Error getting ${type} analytics:`, error);
return { success: false, error: error.message };
}
}
// Get service status and statistics
getServiceStatus() {
return {
service: 'Analytics Auto-Update Service',
status: 'running',
cache_directory: this.cacheDir,
update_in_progress: this.isRunning,
last_check: new Date().toISOString()
};
}
}
module.exports = AnalyticsAutoUpdateService;