249 lines
9.4 KiB
JavaScript
249 lines
9.4 KiB
JavaScript
const path = require('path');
|
|
|
|
// Change working directory to script directory
|
|
process.chdir(path.dirname(__filename));
|
|
|
|
require('dotenv').config({ path: path.resolve(__dirname, '..', '.env') });
|
|
|
|
// Add error handler for uncaught exceptions
|
|
process.on('uncaughtException', (error) => {
|
|
console.error('Uncaught Exception:', error);
|
|
process.exit(1);
|
|
});
|
|
|
|
// Add error handler for unhandled promise rejections
|
|
process.on('unhandledRejection', (reason, promise) => {
|
|
console.error('Unhandled Rejection at:', promise, 'reason:', reason);
|
|
process.exit(1);
|
|
});
|
|
|
|
const progress = require('./metrics/utils/progress');
|
|
console.log('Progress module loaded:', {
|
|
modulePath: require.resolve('./metrics/utils/progress'),
|
|
exports: Object.keys(progress),
|
|
currentDir: process.cwd(),
|
|
scriptDir: __dirname
|
|
});
|
|
|
|
// Store progress functions in global scope to ensure availability
|
|
global.formatElapsedTime = progress.formatElapsedTime;
|
|
global.estimateRemaining = progress.estimateRemaining;
|
|
global.calculateRate = progress.calculateRate;
|
|
global.outputProgress = progress.outputProgress;
|
|
global.clearProgress = progress.clearProgress;
|
|
global.getProgress = progress.getProgress;
|
|
global.logError = progress.logError;
|
|
|
|
const { getConnection, closePool } = require('./metrics/utils/db');
|
|
const calculateProductMetrics = require('./metrics/product-metrics');
|
|
const calculateTimeAggregates = require('./metrics/time-aggregates');
|
|
const calculateFinancialMetrics = require('./metrics/financial-metrics');
|
|
const calculateVendorMetrics = require('./metrics/vendor-metrics');
|
|
const calculateCategoryMetrics = require('./metrics/category-metrics');
|
|
const calculateBrandMetrics = require('./metrics/brand-metrics');
|
|
const calculateSalesForecasts = require('./metrics/sales-forecasts');
|
|
|
|
// Set to 1 to skip product metrics and only calculate the remaining metrics
|
|
const SKIP_PRODUCT_METRICS = 1;
|
|
|
|
// Add cancel handler
|
|
let isCancelled = false;
|
|
|
|
function cancelCalculation() {
|
|
isCancelled = true;
|
|
global.clearProgress();
|
|
// Format as SSE event
|
|
const event = {
|
|
progress: {
|
|
status: 'cancelled',
|
|
operation: 'Calculation cancelled',
|
|
current: 0,
|
|
total: 0,
|
|
elapsed: null,
|
|
remaining: null,
|
|
rate: 0,
|
|
timestamp: Date.now()
|
|
}
|
|
};
|
|
process.stdout.write(JSON.stringify(event) + '\n');
|
|
process.exit(0);
|
|
}
|
|
|
|
// Handle SIGTERM signal for cancellation
|
|
process.on('SIGTERM', cancelCalculation);
|
|
|
|
// Update the main calculation function to use the new modular structure
|
|
async function calculateMetrics() {
|
|
let connection;
|
|
const startTime = Date.now();
|
|
let processedCount = 0;
|
|
let totalProducts = 0;
|
|
|
|
try {
|
|
// Add debug logging for the progress functions
|
|
console.log('Debug - Progress functions:', {
|
|
formatElapsedTime: typeof global.formatElapsedTime,
|
|
estimateRemaining: typeof global.estimateRemaining,
|
|
calculateRate: typeof global.calculateRate,
|
|
startTime: startTime
|
|
});
|
|
|
|
try {
|
|
const elapsed = global.formatElapsedTime(startTime);
|
|
console.log('Debug - formatElapsedTime test successful:', elapsed);
|
|
} catch (err) {
|
|
console.error('Debug - Error testing formatElapsedTime:', err);
|
|
throw err;
|
|
}
|
|
|
|
isCancelled = false;
|
|
connection = await getConnection();
|
|
|
|
try {
|
|
global.outputProgress({
|
|
status: 'running',
|
|
operation: 'Starting metrics calculation',
|
|
current: 0,
|
|
total: 100,
|
|
elapsed: '0s',
|
|
remaining: 'Calculating...',
|
|
rate: 0,
|
|
percentage: '0'
|
|
});
|
|
|
|
// Get total number of products
|
|
const [countResult] = await connection.query('SELECT COUNT(*) as total FROM products')
|
|
.catch(err => {
|
|
global.logError(err, 'Failed to count products');
|
|
throw err;
|
|
});
|
|
totalProducts = countResult[0].total;
|
|
|
|
if (!SKIP_PRODUCT_METRICS) {
|
|
processedCount = await calculateProductMetrics(startTime, totalProducts);
|
|
} else {
|
|
console.log('Skipping product metrics calculation...');
|
|
processedCount = Math.floor(totalProducts * 0.6);
|
|
global.outputProgress({
|
|
status: 'running',
|
|
operation: 'Skipping product metrics calculation',
|
|
current: processedCount,
|
|
total: totalProducts,
|
|
elapsed: global.formatElapsedTime(startTime),
|
|
remaining: global.estimateRemaining(startTime, processedCount, totalProducts),
|
|
rate: global.calculateRate(startTime, processedCount),
|
|
percentage: '60'
|
|
});
|
|
}
|
|
|
|
// Calculate time-based aggregates
|
|
processedCount = await calculateTimeAggregates(startTime, totalProducts, processedCount);
|
|
|
|
// Calculate financial metrics
|
|
processedCount = await calculateFinancialMetrics(startTime, totalProducts, processedCount);
|
|
|
|
// Calculate vendor metrics
|
|
processedCount = await calculateVendorMetrics(startTime, totalProducts, processedCount);
|
|
|
|
// Calculate category metrics
|
|
processedCount = await calculateCategoryMetrics(startTime, totalProducts, processedCount);
|
|
|
|
// Calculate brand metrics
|
|
processedCount = await calculateBrandMetrics(startTime, totalProducts, processedCount);
|
|
|
|
// Calculate sales forecasts
|
|
processedCount = await calculateSalesForecasts(startTime, totalProducts, processedCount);
|
|
|
|
// Calculate ABC classification
|
|
const [abcConfig] = await connection.query('SELECT a_threshold, b_threshold FROM abc_classification_config WHERE id = 1');
|
|
const abcThresholds = abcConfig[0] || { a_threshold: 20, b_threshold: 50 };
|
|
|
|
await connection.query(`
|
|
WITH revenue_rankings AS (
|
|
SELECT
|
|
product_id,
|
|
total_revenue,
|
|
PERCENT_RANK() OVER (ORDER BY COALESCE(total_revenue, 0) DESC) * 100 as revenue_percentile
|
|
FROM product_metrics
|
|
),
|
|
classification_update AS (
|
|
SELECT
|
|
product_id,
|
|
CASE
|
|
WHEN revenue_percentile <= ? THEN 'A'
|
|
WHEN revenue_percentile <= ? THEN 'B'
|
|
ELSE 'C'
|
|
END as abc_class
|
|
FROM revenue_rankings
|
|
)
|
|
UPDATE product_metrics pm
|
|
JOIN classification_update cu ON pm.product_id = cu.product_id
|
|
SET pm.abc_class = cu.abc_class,
|
|
pm.last_calculated_at = NOW()
|
|
`, [abcThresholds.a_threshold, abcThresholds.b_threshold]);
|
|
|
|
// Final success message
|
|
global.outputProgress({
|
|
status: 'complete',
|
|
operation: 'Metrics calculation complete',
|
|
current: totalProducts,
|
|
total: totalProducts,
|
|
elapsed: global.formatElapsedTime(startTime),
|
|
remaining: '0s',
|
|
rate: global.calculateRate(startTime, totalProducts),
|
|
percentage: '100'
|
|
});
|
|
|
|
// Clear progress file on successful completion
|
|
global.clearProgress();
|
|
|
|
} catch (error) {
|
|
if (isCancelled) {
|
|
global.outputProgress({
|
|
status: 'cancelled',
|
|
operation: 'Calculation cancelled',
|
|
current: processedCount,
|
|
total: totalProducts || 0,
|
|
elapsed: global.formatElapsedTime(startTime),
|
|
remaining: null,
|
|
rate: global.calculateRate(startTime, processedCount),
|
|
percentage: ((processedCount / (totalProducts || 1)) * 100).toFixed(1)
|
|
});
|
|
} else {
|
|
global.outputProgress({
|
|
status: 'error',
|
|
operation: 'Error: ' + error.message,
|
|
current: processedCount,
|
|
total: totalProducts || 0,
|
|
elapsed: global.formatElapsedTime(startTime),
|
|
remaining: null,
|
|
rate: global.calculateRate(startTime, processedCount),
|
|
percentage: ((processedCount / (totalProducts || 1)) * 100).toFixed(1)
|
|
});
|
|
}
|
|
throw error;
|
|
} finally {
|
|
if (connection) {
|
|
connection.release();
|
|
}
|
|
}
|
|
} finally {
|
|
// Close the connection pool when we're done
|
|
await closePool();
|
|
}
|
|
}
|
|
|
|
// Export both functions and progress checker
|
|
module.exports = calculateMetrics;
|
|
module.exports.cancelCalculation = cancelCalculation;
|
|
module.exports.getProgress = global.getProgress;
|
|
|
|
// Run directly if called from command line
|
|
if (require.main === module) {
|
|
calculateMetrics().catch(error => {
|
|
if (!error.message.includes('Operation cancelled')) {
|
|
console.error('Error:', error);
|
|
}
|
|
process.exit(1);
|
|
});
|
|
}
|