822 lines
30 KiB
JavaScript
822 lines
30 KiB
JavaScript
// IPI Index Calculation Service
|
||
const mysql = require('mysql2/promise');
|
||
const logger = require("./logger");
|
||
|
||
class IPICalculationService {
|
||
|
||
|
||
constructor(dbConfig) {
|
||
this.pool = mysql.createPool(dbConfig);
|
||
}
|
||
|
||
|
||
// STEP 1: Calculate and Store Base Year Average Production (2022)
|
||
async calculateBaseYearProduction(baseYear = 2022, forceRecalculate = false) {
|
||
|
||
const connection = await this.pool.getConnection();
|
||
let logId = null;
|
||
|
||
try {
|
||
await connection.beginTransaction();
|
||
|
||
logger.info(`calculateBaseYearProduction - Calculating Base Year Production for ${baseYear}...`);
|
||
|
||
// If force recalculate, delete existing data
|
||
if (forceRecalculate) {
|
||
logger.info('calculateBaseYearProduction - Force recalculate enabled , clearing existing data...');
|
||
await connection.query(
|
||
`DELETE FROM base_year_production WHERE base_year = ?`,
|
||
[baseYear]
|
||
);
|
||
}
|
||
|
||
// Log calculation start
|
||
logId = await this.logRecord( 'base_year', baseYear, null, 'Started', null, 0, null, connection);
|
||
|
||
// Your existing query to calculate base year production
|
||
const query = `
|
||
SELECT
|
||
p.id AS product_id,
|
||
p.hs_code AS product_hs_code,
|
||
u.uom_short_name AS unit,
|
||
SUM(CASE WHEN s.quarter = 'Q1' THEN sp.current_quantity_period_one END) AS Jan,
|
||
SUM(CASE WHEN s.quarter = 'Q1' THEN sp.current_quantity_period_two END) AS Feb,
|
||
SUM(CASE WHEN s.quarter = 'Q1' THEN sp.current_quantity_period_three END) AS Mar,
|
||
SUM(CASE WHEN s.quarter = 'Q2' THEN sp.current_quantity_period_one END) AS Apr,
|
||
SUM(CASE WHEN s.quarter = 'Q2' THEN sp.current_quantity_period_two END) AS May,
|
||
SUM(CASE WHEN s.quarter = 'Q2' THEN sp.current_quantity_period_three END) AS Jun,
|
||
SUM(CASE WHEN s.quarter = 'Q3' THEN sp.current_quantity_period_one END) AS Jul,
|
||
SUM(CASE WHEN s.quarter = 'Q3' THEN sp.current_quantity_period_two END) AS Aug,
|
||
SUM(CASE WHEN s.quarter = 'Q3' THEN sp.current_quantity_period_three END) AS Sep,
|
||
SUM(CASE WHEN s.quarter = 'Q4' THEN sp.current_quantity_period_one END) AS Oct,
|
||
SUM(CASE WHEN s.quarter = 'Q4' THEN sp.current_quantity_period_two END) AS Nov,
|
||
SUM(CASE WHEN s.quarter = 'Q4' THEN sp.current_quantity_period_three END) AS \`Dec\`,
|
||
ROUND((
|
||
COALESCE(SUM(CASE WHEN s.quarter='Q1' THEN sp.current_quantity_period_one END),0) +
|
||
COALESCE(SUM(CASE WHEN s.quarter='Q1' THEN sp.current_quantity_period_two END),0) +
|
||
COALESCE(SUM(CASE WHEN s.quarter='Q1' THEN sp.current_quantity_period_three END),0) +
|
||
COALESCE(SUM(CASE WHEN s.quarter='Q2' THEN sp.current_quantity_period_one END),0) +
|
||
COALESCE(SUM(CASE WHEN s.quarter='Q2' THEN sp.current_quantity_period_two END),0) +
|
||
COALESCE(SUM(CASE WHEN s.quarter='Q2' THEN sp.current_quantity_period_three END),0) +
|
||
COALESCE(SUM(CASE WHEN s.quarter='Q3' THEN sp.current_quantity_period_one END),0) +
|
||
COALESCE(SUM(CASE WHEN s.quarter='Q3' THEN sp.current_quantity_period_two END),0) +
|
||
COALESCE(SUM(CASE WHEN s.quarter='Q3' THEN sp.current_quantity_period_three END),0) +
|
||
COALESCE(SUM(CASE WHEN s.quarter='Q4' THEN sp.current_quantity_period_one END),0) +
|
||
COALESCE(SUM(CASE WHEN s.quarter='Q4' THEN sp.current_quantity_period_two END),0) +
|
||
COALESCE(SUM(CASE WHEN s.quarter='Q4' THEN sp.current_quantity_period_three END),0)
|
||
) / 12, 2) AS avg_by_production
|
||
FROM submission_products sp
|
||
JOIN submission s ON s.id = sp.submission_id
|
||
JOIN products p ON p.id = sp.product_id
|
||
LEFT JOIN unit_master u ON u.id = p.unit_id
|
||
WHERE s.year = ? AND sp.is_active = 1 AND s.status = 'Approved'
|
||
GROUP BY p.id
|
||
`;
|
||
|
||
const [products] = await connection.query(query, [baseYear]);
|
||
|
||
|
||
logger.info(`calculateBaseYearProduction - Found ${products.length} products to process...`);
|
||
|
||
|
||
// Insert into base_year_production table
|
||
let processedCount = 0;
|
||
for (const product of products) {
|
||
await connection.query(
|
||
`INSERT INTO base_year_production
|
||
(product_id, product_hs_code, base_year, unit, jan, feb, mar, apr, may, jun,
|
||
jul, aug, sep, oct, nov, \`dec\`, avg_by_production)
|
||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||
[
|
||
product.product_id, product.product_hs_code, baseYear, product.unit,
|
||
product.Jan || 0, product.Feb || 0, product.Mar || 0,
|
||
product.Apr || 0, product.May || 0, product.Jun || 0,
|
||
product.Jul || 0, product.Aug || 0, product.Sep || 0,
|
||
product.Oct || 0, product.Nov || 0, product.Dec || 0,
|
||
product.avg_by_production
|
||
]
|
||
);
|
||
processedCount++;
|
||
|
||
// Log progress every 50 products
|
||
if (processedCount % 50 === 0) {
|
||
logger.info(`calculateBaseYearProduction - Processed ${processedCount}/${products.length} products...`);
|
||
}
|
||
}
|
||
|
||
// Update log
|
||
await this.logRecord('base_year', baseYear, null, 'Completed', logId, products.length, null,connection);
|
||
|
||
|
||
await connection.commit();
|
||
logger.info(`calculateBaseYearProduction - Base Year Production calculated: ${products.length} products`);
|
||
return { success: true, productsProcessed: products.length };
|
||
|
||
} catch (error) {
|
||
await connection.rollback();
|
||
console.error('Error calculating base year production:', error);
|
||
logger.error(`calculateBaseYearProduction - Error calculating base year production: ${error} `);
|
||
// Update log OUTSIDE the failed transaction
|
||
if (logId) {
|
||
await this.logRecord('base_year', baseYear, null, 'Failed', logId, 0, error.message);
|
||
}
|
||
|
||
throw error;
|
||
} finally {
|
||
connection.release();
|
||
}
|
||
}
|
||
|
||
|
||
// STEP 2: Aggregate Monthly Production
|
||
async aggregateMonthlyProduction(year, month) {
|
||
const connection = await this.pool.getConnection();
|
||
let logId = null;
|
||
|
||
try {
|
||
await connection.beginTransaction();
|
||
|
||
logger.info(`aggregateMonthlyProduction - Aggregating production for ${year}-${month}...`);
|
||
|
||
// Log calculation start
|
||
logId = await this.logRecord( 'monthly_production', year, month, 'Started', null, 0, null, connection);
|
||
|
||
|
||
// Determine quarter and period based on month
|
||
const quarterMap = {
|
||
1: { quarter: 'Q1', period: 'current_quantity_period_one' },
|
||
2: { quarter: 'Q1', period: 'current_quantity_period_two' },
|
||
3: { quarter: 'Q1', period: 'current_quantity_period_three' },
|
||
4: { quarter: 'Q2', period: 'current_quantity_period_one' },
|
||
5: { quarter: 'Q2', period: 'current_quantity_period_two' },
|
||
6: { quarter: 'Q2', period: 'current_quantity_period_three' },
|
||
7: { quarter: 'Q3', period: 'current_quantity_period_one' },
|
||
8: { quarter: 'Q3', period: 'current_quantity_period_two' },
|
||
9: { quarter: 'Q3', period: 'current_quantity_period_three' },
|
||
10: { quarter: 'Q4', period: 'current_quantity_period_one' },
|
||
11: { quarter: 'Q4', period: 'current_quantity_period_two' },
|
||
12: { quarter: 'Q4', period: 'current_quantity_period_three' }
|
||
};
|
||
|
||
const { quarter, period } = quarterMap[month];
|
||
const monthNames = ['', 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
|
||
|
||
// Aggregate production by product
|
||
const query = `
|
||
SELECT
|
||
p.id AS product_id,
|
||
p.hs_code AS product_hs_code,
|
||
u.uom_short_name AS unit,
|
||
SUM(sp.${period}) AS production_quantity
|
||
FROM submission_products sp
|
||
JOIN submission s ON s.id = sp.submission_id
|
||
JOIN products p ON p.id = sp.product_id
|
||
LEFT JOIN unit_master u ON u.id = p.unit_id
|
||
WHERE s.year = ? AND s.quarter = ?
|
||
AND sp.is_active = 1
|
||
AND s.status = 'Approved'
|
||
GROUP BY p.id
|
||
`;
|
||
|
||
const [products] = await connection.query(query, [year, quarter]);
|
||
|
||
// Insert monthly production
|
||
for (const product of products) {
|
||
await connection.query(
|
||
`INSERT INTO monthly_production
|
||
(product_id, product_hs_code, year, month, month_name,
|
||
production_quantity, unit)
|
||
VALUES (?, ?, ?, ?, ?, ?, ?)
|
||
ON DUPLICATE KEY UPDATE
|
||
production_quantity=VALUES(production_quantity),
|
||
unit=VALUES(unit)`,
|
||
[
|
||
product.product_id, product.product_hs_code, year, month,
|
||
monthNames[month], product.production_quantity || 0, product.unit
|
||
]
|
||
);
|
||
}
|
||
|
||
|
||
// Update log
|
||
await this.logRecord('monthly_production', year, month, 'Completed', logId, products.length, null,connection);
|
||
|
||
await connection.commit();
|
||
logger.info(`aggregateMonthlyProduction - Monthly production aggregated: ${products.length} products`);
|
||
return { success: true, productsProcessed: products.length };
|
||
|
||
} catch (error) {
|
||
await connection.rollback();
|
||
console.error('Error aggregating monthly production:', error);
|
||
logger.error(`aggregateMonthlyProduction - Error aggregating monthly production: ${error}`);
|
||
// Update log OUTSIDE the failed transaction
|
||
if (logId) {
|
||
await this.logRecord('monthly_production', year, month, 'Failed', logId, 0, error.message);
|
||
}
|
||
throw error;
|
||
} finally {
|
||
connection.release();
|
||
}
|
||
}
|
||
|
||
|
||
// STEP 3: Calculate Item Level Indices
|
||
async calculateItemLevelIndices(year, month) {
|
||
const connection = await this.pool.getConnection();
|
||
let logId = null;
|
||
try {
|
||
await connection.beginTransaction();
|
||
|
||
logger.info(`calculateItemLevelIndices - Calculating item level indices for ${year}-${month}...`);
|
||
|
||
// Log calculation start
|
||
logId = await this.logRecord( 'item_level_indices', year, month, 'Started', null, 0, null, connection);
|
||
|
||
const monthNames = ['', 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
|
||
|
||
// Calculate indices: Ri = Current Production / Base Year Avg Production
|
||
// Ii = Ri × 100
|
||
const query = `
|
||
SELECT
|
||
mp.product_id,
|
||
mp.product_hs_code,
|
||
mp.production_quantity AS current_production,
|
||
byp.avg_by_production AS base_year_avg_production,
|
||
(mp.production_quantity / byp.avg_by_production) AS production_relative,
|
||
((mp.production_quantity / byp.avg_by_production) * 100) AS item_index
|
||
FROM monthly_production mp
|
||
JOIN base_year_production byp ON byp.product_id = mp.product_id
|
||
WHERE mp.year = ? AND mp.month = ?
|
||
AND byp.avg_by_production > 0
|
||
`;
|
||
|
||
const [items] = await connection.query(query, [year, month]);
|
||
|
||
// Insert item level indices
|
||
for (const item of items) {
|
||
await connection.query(
|
||
`INSERT INTO item_level_indices
|
||
(product_id, product_hs_code, year, month, month_name,
|
||
current_production, base_year_avg_production,
|
||
production_relative, item_index)
|
||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||
ON DUPLICATE KEY UPDATE
|
||
current_production=VALUES(current_production),
|
||
base_year_avg_production=VALUES(base_year_avg_production),
|
||
production_relative=VALUES(production_relative),
|
||
item_index=VALUES(item_index)`,
|
||
[
|
||
item.product_id, item.product_hs_code, year, month, monthNames[month],
|
||
item.current_production, item.base_year_avg_production,
|
||
item.production_relative, item.item_index
|
||
]
|
||
);
|
||
}
|
||
|
||
// Update log
|
||
await this.logRecord('item_level_indices', year, month, 'Completed', logId, items.length, null,connection);
|
||
|
||
await connection.commit();
|
||
logger.info(`calculateItemLevelIndices - Item level indices calculated: ${items.length} items`);
|
||
return { success: true, itemsProcessed: items.length };
|
||
|
||
} catch (error) {
|
||
await connection.rollback();
|
||
console.error('Error calculating item indices:', error);
|
||
logger.error(`calculateItemLevelIndices - Error calculating item indices: ${error}`);
|
||
// Update log OUTSIDE the failed transaction
|
||
if (logId) {
|
||
await this.logRecord('item_level_indices', year, month, 'Failed', logId, 0, error.message);
|
||
}
|
||
throw error;
|
||
} finally {
|
||
connection.release();
|
||
}
|
||
}
|
||
|
||
|
||
// STEP 4: Calculate ISIC 4-Digit Level Indices
|
||
async calculateISIC4DigitIndices(year, month) {
|
||
const connection = await this.pool.getConnection();
|
||
let logId = null;
|
||
try {
|
||
await connection.beginTransaction();
|
||
|
||
logger.info(`calculateISIC4DigitIndices - Calculating ISIC 4-digit indices for ${year}-${month}...`);
|
||
|
||
// Log calculation start
|
||
logId = await this.logRecord( 'isic_4digit_indices', year, month, 'Started', null, 0, null, connection);
|
||
|
||
const monthNames = ['', 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
|
||
|
||
// Get ISIC code from establishments table directly
|
||
// Using simple average of item indices for each ISIC 4-digit code
|
||
const query = `
|
||
SELECT
|
||
LEFT(e.isic_code , 4) as isic_4digit_code,
|
||
COUNT(DISTINCT ili.product_id) AS total_weight,
|
||
SUM(ili.item_index) AS weighted_index_sum,
|
||
AVG(ili.item_index) AS isic_4digit_index
|
||
FROM item_level_indices ili
|
||
JOIN products p ON p.id = ili.product_id
|
||
JOIN submission_products sp ON sp.product_id = p.id
|
||
JOIN submission s ON s.id = sp.submission_id
|
||
AND s.year = ili.year
|
||
JOIN establishments e ON e.id = s.establishment_id
|
||
WHERE ili.year = ? AND ili.month = ?
|
||
AND e.isic_code IS NOT NULL
|
||
AND e.isic_code != ''
|
||
GROUP BY LEFT(e.isic_code , 4)
|
||
`;
|
||
|
||
const [isic4Results] = await connection.query(query, [year, month]);
|
||
|
||
for (const result of isic4Results) {
|
||
await connection.query(
|
||
`INSERT INTO isic_4digit_indices
|
||
(isic_4digit_code, year, month, month_name, total_weight,
|
||
weighted_index_sum, isic_4digit_index)
|
||
VALUES (?, ?, ?, ?, ?, ?, ?)
|
||
ON DUPLICATE KEY UPDATE
|
||
total_weight=VALUES(total_weight),
|
||
weighted_index_sum=VALUES(weighted_index_sum),
|
||
isic_4digit_index=VALUES(isic_4digit_index)`,
|
||
[
|
||
result.isic_4digit_code, year, month, monthNames[month],
|
||
result.total_weight,
|
||
result.weighted_index_sum,
|
||
result.isic_4digit_index
|
||
]
|
||
);
|
||
}
|
||
|
||
// Update log
|
||
await this.logRecord('isic_4digit_indices', year, month, 'Completed', logId, isic4Results.length, null,connection);
|
||
|
||
await connection.commit();
|
||
logger.info(`calculateISIC4DigitIndices - ISIC 4-digit indices calculated: ${isic4Results.length} codes`);
|
||
return { success: true, codesProcessed: isic4Results.length };
|
||
|
||
} catch (error) {
|
||
await connection.rollback();
|
||
console.error('Error calculating ISIC 4-digit indices:', error);
|
||
logger.error(`calculateISIC4DigitIndices - Error calculating ISIC 4-digit indices: ${error} `);
|
||
// Update log OUTSIDE the failed transaction
|
||
if (logId) {
|
||
await this.logRecord('isic_4digit_indices', year, month, 'Failed', logId, 0, error.message);
|
||
}
|
||
throw error;
|
||
} finally {
|
||
connection.release();
|
||
}
|
||
}
|
||
|
||
|
||
// STEP 5: Calculate ISIC 3-Digit Level Indices
|
||
async calculateISIC3DigitIndices(year, month) {
|
||
const connection = await this.pool.getConnection();
|
||
let logId = null;
|
||
try {
|
||
await connection.beginTransaction();
|
||
|
||
logger.info(`calculateISIC3DigitIndices - Calculating ISIC 3-digit indices for ${year}-${month}...`);
|
||
|
||
// Log calculation start
|
||
logId = await this.logRecord( 'isic_3digit_indices', year, month, 'Started', null, 0, null, connection);
|
||
|
||
const monthNames = ['', 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
|
||
|
||
const query = `
|
||
SELECT
|
||
LEFT(isic_4digit_code, 3) AS isic_3digit_code,
|
||
SUM(total_weight) AS total_weight,
|
||
SUM(total_weight * isic_4digit_index) AS weighted_index_sum,
|
||
(SUM(total_weight * isic_4digit_index) / SUM(total_weight)) AS isic_3digit_index
|
||
FROM isic_4digit_indices
|
||
WHERE year = ? AND month = ?
|
||
GROUP BY LEFT(isic_4digit_code, 3)
|
||
`;
|
||
|
||
const [isic3Results] = await connection.query(query, [year, month]);
|
||
|
||
for (const result of isic3Results) {
|
||
await connection.query(
|
||
`INSERT INTO isic_3digit_indices
|
||
(isic_3digit_code, year, month, month_name, total_weight,
|
||
weighted_index_sum, isic_3digit_index)
|
||
VALUES (?, ?, ?, ?, ?, ?, ?)
|
||
ON DUPLICATE KEY UPDATE
|
||
total_weight=VALUES(total_weight),
|
||
weighted_index_sum=VALUES(weighted_index_sum),
|
||
isic_3digit_index=VALUES(isic_3digit_index)`,
|
||
[
|
||
result.isic_3digit_code, year, month, monthNames[month],
|
||
result.total_weight, result.weighted_index_sum,
|
||
result.isic_3digit_index
|
||
]
|
||
);
|
||
}
|
||
|
||
// Update log
|
||
await this.logRecord('isic_3digit_indices', year, month, 'Completed', logId, isic3Results.length, null,connection);
|
||
|
||
await connection.commit();
|
||
logger.info(`calculateISIC3DigitIndices - ISIC 3-digit indices calculated: ${isic3Results.length} codes`);
|
||
return { success: true, codesProcessed: isic3Results.length };
|
||
|
||
} catch (error) {
|
||
await connection.rollback();
|
||
console.error('Error calculating ISIC 3-digit indices:', error);
|
||
logger.error(`calculateISIC3DigitIndices - Error calculating ISIC 3-digit indices: ${error} `);
|
||
// Update log OUTSIDE the failed transaction
|
||
if (logId) {
|
||
await this.logRecord('isic_3digit_indices', year, month, 'Failed', logId, 0, error.message);
|
||
}
|
||
throw error;
|
||
} finally {
|
||
connection.release();
|
||
}
|
||
}
|
||
|
||
|
||
// STEP 6: Calculate ISIC 2-Digit Level Indices
|
||
async calculateISIC2DigitIndices(year, month) {
|
||
const connection = await this.pool.getConnection();
|
||
let logId = null;
|
||
try {
|
||
await connection.beginTransaction();
|
||
|
||
logger.info(`calculateISIC2DigitIndices - Calculating ISIC 2-digit indices for ${year}-${month}...`);
|
||
|
||
// Log calculation start
|
||
logId = await this.logRecord( 'isic_2digit_indices', year, month, 'Started', null, 0, null, connection);
|
||
|
||
const monthNames = ['', 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
|
||
|
||
const query = `
|
||
SELECT
|
||
LEFT(isic_3digit_code, 2) AS isic_2digit_code,
|
||
SUM(total_weight) AS total_weight,
|
||
SUM(total_weight * isic_3digit_index) AS weighted_index_sum,
|
||
(SUM(total_weight * isic_3digit_index) / SUM(total_weight)) AS isic_2digit_index
|
||
FROM isic_3digit_indices
|
||
WHERE year = ? AND month = ?
|
||
GROUP BY LEFT(isic_3digit_code, 2)
|
||
`;
|
||
|
||
const [isic2Results] = await connection.query(query, [year, month]);
|
||
|
||
for (const result of isic2Results) {
|
||
await connection.query(
|
||
`INSERT INTO isic_2digit_indices
|
||
(isic_2digit_code, year, month, month_name, total_weight,
|
||
weighted_index_sum, isic_2digit_index)
|
||
VALUES (?, ?, ?, ?, ?, ?, ?)
|
||
ON DUPLICATE KEY UPDATE
|
||
total_weight=VALUES(total_weight),
|
||
weighted_index_sum=VALUES(weighted_index_sum),
|
||
isic_2digit_index=VALUES(isic_2digit_index)`,
|
||
[
|
||
result.isic_2digit_code, year, month, monthNames[month],
|
||
result.total_weight, result.weighted_index_sum,
|
||
result.isic_2digit_index
|
||
]
|
||
);
|
||
}
|
||
|
||
// Update log
|
||
await this.logRecord('isic_2digit_indices', year, month, 'Completed', logId, isic2Results.length, null,connection);
|
||
|
||
await connection.commit();
|
||
logger.info(`calculateISIC2DigitIndices - ISIC 2-digit indices calculated: ${isic2Results.length} codes`);
|
||
return { success: true, codesProcessed: isic2Results.length };
|
||
|
||
} catch (error) {
|
||
await connection.rollback();
|
||
console.error('Error calculating ISIC 2-digit indices:', error);
|
||
logger.error(`calculateISIC2DigitIndices - Error calculating ISIC 2-digit indices: ${error}`);
|
||
// Update log OUTSIDE the failed transaction
|
||
if (logId) {
|
||
await this.logRecord('isic_2digit_indices', year, month, 'Failed', logId, 0, error.message);
|
||
}
|
||
throw error;
|
||
} finally {
|
||
connection.release();
|
||
}
|
||
}
|
||
|
||
|
||
// STEP 7: Calculate Manufacturing IPI (Headline Index)
|
||
async calculateManufacturingIPI(year, month) {
|
||
const connection = await this.pool.getConnection();
|
||
let logId = null;
|
||
try {
|
||
await connection.beginTransaction();
|
||
|
||
logger.info(`calculateManufacturingIPI - Calculating Manufacturing indices for ${year}-${month}...`);
|
||
|
||
// Log calculation start
|
||
logId = await this.logRecord( 'manufacturing_ipi', year, month, 'Started', null, 0, null, connection);
|
||
|
||
const monthNames = ['', 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
|
||
|
||
// Calculate Manufacturing IPI
|
||
const query = `
|
||
SELECT
|
||
SUM(total_weight) AS total_weight,
|
||
SUM(total_weight * isic_2digit_index) AS weighted_index_sum,
|
||
(SUM(total_weight * isic_2digit_index) / SUM(total_weight)) AS manufacturing_index
|
||
FROM isic_2digit_indices
|
||
WHERE year = ? AND month = ?
|
||
`;
|
||
|
||
const [result] = await connection.query(query, [year, month]);
|
||
|
||
// If no data found, return empty response
|
||
if (!result || result.length === 0) {
|
||
return {
|
||
success: true,
|
||
index: null,
|
||
momChange: null,
|
||
yoyChange: null,
|
||
message: "No IPI data found for the given year and month"
|
||
};
|
||
}
|
||
|
||
const ipiData = result[0];
|
||
|
||
// Calculate MoM and YoY changes
|
||
let momChange = null;
|
||
let yoyChange = null;
|
||
|
||
// Get previous month index for MoM
|
||
const prevMonth = month === 1 ? 12 : month - 1;
|
||
const prevYear = month === 1 ? year - 1 : year;
|
||
|
||
const [prevMonthData] = await connection.query(
|
||
`SELECT manufacturing_index FROM manufacturing_ipi
|
||
WHERE year = ? AND month = ?`,
|
||
[prevYear, prevMonth]
|
||
);
|
||
|
||
if (prevMonthData.length > 0) {
|
||
const prevIndex = prevMonthData[0].manufacturing_index;
|
||
momChange = ((ipiData.manufacturing_index - prevIndex) / prevIndex) * 100;
|
||
}
|
||
|
||
// Get same month last year for YoY
|
||
const [lastYearData] = await connection.query(
|
||
`SELECT manufacturing_index FROM manufacturing_ipi
|
||
WHERE year = ? AND month = ?`,
|
||
[year - 1, month]
|
||
);
|
||
|
||
if (lastYearData.length > 0) {
|
||
const lastYearIndex = lastYearData[0].manufacturing_index;
|
||
yoyChange = ((ipiData.manufacturing_index - lastYearIndex) / lastYearIndex) * 100;
|
||
}
|
||
|
||
// Reference date is the first day of the month
|
||
const referenceDate = `${year}-${String(month).padStart(2, '0')}-01`;
|
||
|
||
// Insert Manufacturing IPI
|
||
await connection.query(
|
||
`INSERT INTO manufacturing_ipi
|
||
(year, month, month_name, reference_date, total_weight,
|
||
weighted_index_sum, manufacturing_index, mom_change, yoy_change, status)
|
||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 'Completed')
|
||
ON DUPLICATE KEY UPDATE
|
||
total_weight=VALUES(total_weight),
|
||
weighted_index_sum=VALUES(weighted_index_sum),
|
||
manufacturing_index=VALUES(manufacturing_index),
|
||
mom_change=VALUES(mom_change),
|
||
yoy_change=VALUES(yoy_change),
|
||
status=VALUES(status)`,
|
||
[
|
||
year, month, monthNames[month], referenceDate,
|
||
ipiData.total_weight, ipiData.weighted_index_sum,
|
||
ipiData.manufacturing_index, momChange, yoyChange
|
||
]
|
||
);
|
||
|
||
|
||
// Update log
|
||
await this.logRecord('manufacturing_ipi', year, month, 'Completed', logId, result.length, null,connection);
|
||
|
||
await connection.commit();
|
||
|
||
|
||
logger.info(`calculateManufacturingIPI - Manufacturing IPI calculated: ${ipiData.manufacturing_index}`);
|
||
logger.info(`calculateManufacturingIPI - MoM: ${momChange ? momChange.toFixed(2) + '%' : 'N/A'}`);
|
||
logger.info(`calculateManufacturingIPI - YoY: ${yoyChange ? yoyChange.toFixed(2) + '%' : 'N/A'}`);
|
||
|
||
return {
|
||
success: true,
|
||
index: ipiData.manufacturing_index,
|
||
momChange,
|
||
yoyChange
|
||
};
|
||
|
||
} catch (error) {
|
||
await connection.rollback();
|
||
console.error('Error calculating Manufacturing IPI:', error);
|
||
logger.error(`calculateManufacturingIPI - Error calculating Manufacturing IPI: ${error}`);
|
||
// Update log OUTSIDE the failed transaction
|
||
if (logId) {
|
||
await this.logRecord('manufacturing_ipi', year, month, 'Failed', logId, 0, error.message);
|
||
}
|
||
throw error;
|
||
} finally {
|
||
connection.release();
|
||
}
|
||
}
|
||
|
||
|
||
// MASTER FUNCTION: Run Complete Calculation Pipeline
|
||
async runCompleteCalculation(year, month) {
|
||
console.log(`\n========================================`);
|
||
console.log(`Starting IPI Calculation for ${year}-${month}`);
|
||
console.log(`========================================\n`);
|
||
|
||
try {
|
||
// Step 1: Aggregate monthly production
|
||
await this.aggregateMonthlyProduction(year, month);
|
||
|
||
// Step 2: Calculate item level indices
|
||
await this.calculateItemLevelIndices(year, month);
|
||
|
||
// Step 3: Calculate ISIC 4-digit indices
|
||
await this.calculateISIC4DigitIndices(year, month);
|
||
|
||
// Step 4: Calculate ISIC 3-digit indices
|
||
await this.calculateISIC3DigitIndices(year, month);
|
||
|
||
// Step 5: Calculate ISIC 2-digit indices
|
||
await this.calculateISIC2DigitIndices(year, month);
|
||
|
||
// Step 6: Calculate Manufacturing IPI
|
||
const result = await this.calculateManufacturingIPI(year, month);
|
||
|
||
console.log(`\n========================================`);
|
||
console.log(`✓ IPI Calculation Completed Successfully`);
|
||
console.log(`========================================\n`);
|
||
|
||
return result;
|
||
|
||
} catch (error) {
|
||
console.error('Error in complete calculation:', error);
|
||
throw error;
|
||
}
|
||
}
|
||
|
||
|
||
// HELPER : calculation log
|
||
async logRecord(calculation_type, reference_year, reference_month = null, status = 'Started', logId = null, records_processed = 0, error_message = null, useConnection = null) {
|
||
|
||
const connection = useConnection || await this.pool.getConnection();
|
||
try {
|
||
if (!logId) {
|
||
const [result] = await connection.query(
|
||
`INSERT INTO calculation_log (calculation_type, reference_year, reference_month, status)
|
||
VALUES (?, ?, ?, ?)`,
|
||
[calculation_type, reference_year, reference_month, status]
|
||
);
|
||
return result.insertId;
|
||
}
|
||
|
||
if(records_processed == 0)
|
||
{
|
||
status = 'Failed';
|
||
}
|
||
|
||
await connection.query(
|
||
`UPDATE calculation_log
|
||
SET status=?, records_processed=?, error_message=?, completed_at=NOW()
|
||
WHERE id=?`,
|
||
[status, records_processed, error_message?.substring(0,65535) || null, logId]
|
||
);
|
||
|
||
} catch (err) {
|
||
console.error('[LOG ERROR]', err.message);
|
||
} finally {
|
||
if (!useConnection) connection.release();
|
||
}
|
||
}
|
||
|
||
|
||
// HELPER: Verify Base Year Data
|
||
async verifyBaseYearData(baseYear = 2022) {
|
||
const connection = await this.pool.getConnection();
|
||
|
||
try {
|
||
console.log(`\nVerifying Base Year ${baseYear} Data...`);
|
||
|
||
// Count products
|
||
const [countResult] = await connection.query(
|
||
`SELECT COUNT(*) as count FROM base_year_production WHERE base_year = ?`,
|
||
[baseYear]
|
||
);
|
||
|
||
// Get sample data
|
||
const [sampleData] = await connection.query(
|
||
`SELECT
|
||
product_id,
|
||
product_hs_code,
|
||
avg_by_production,
|
||
jan, feb, mar, apr, may, jun, jul, aug, sep, oct, nov, \`dec\`
|
||
FROM base_year_production
|
||
WHERE base_year = ?
|
||
ORDER BY product_id
|
||
LIMIT 5`,
|
||
[baseYear]
|
||
);
|
||
|
||
// Check for products with zero average
|
||
const [zeroAvgResult] = await connection.query(
|
||
`SELECT COUNT(*) as count
|
||
FROM base_year_production
|
||
WHERE base_year = ? AND (avg_by_production = 0 OR avg_by_production IS NULL)`,
|
||
[baseYear]
|
||
);
|
||
|
||
console.log(`\n✓ Total products in base year: ${countResult[0].count}`);
|
||
console.log(`✓ Products with zero/null average: ${zeroAvgResult[0].count}`);
|
||
console.log(`\nSample data (first 5 products):`);
|
||
console.table(sampleData);
|
||
|
||
return {
|
||
totalProducts: countResult[0].count,
|
||
zeroAverageProducts: zeroAvgResult[0].count,
|
||
sampleData: sampleData
|
||
};
|
||
|
||
} finally {
|
||
connection.release();
|
||
}
|
||
}
|
||
|
||
|
||
// HELPER: Clear All Calculated Data for a Period
|
||
async clearCalculatedData(year, month) {
|
||
const connection = await this.pool.getConnection();
|
||
|
||
try {
|
||
await connection.beginTransaction();
|
||
|
||
console.log(`Clearing calculated data for ${year}-${month}...`);
|
||
|
||
await connection.query(
|
||
`DELETE FROM monthly_production WHERE year = ? AND month = ?`,
|
||
[year, month]
|
||
);
|
||
|
||
await connection.query(
|
||
`DELETE FROM item_level_indices WHERE year = ? AND month = ?`,
|
||
[year, month]
|
||
);
|
||
|
||
await connection.query(
|
||
`DELETE FROM isic_4digit_indices WHERE year = ? AND month = ?`,
|
||
[year, month]
|
||
);
|
||
|
||
await connection.query(
|
||
`DELETE FROM isic_3digit_indices WHERE year = ? AND month = ?`,
|
||
[year, month]
|
||
);
|
||
|
||
await connection.query(
|
||
`DELETE FROM isic_2digit_indices WHERE year = ? AND month = ?`,
|
||
[year, month]
|
||
);
|
||
|
||
await connection.query(
|
||
`DELETE FROM manufacturing_ipi WHERE year = ? AND month = ?`,
|
||
[year, month]
|
||
);
|
||
|
||
await connection.commit();
|
||
console.log(`✓ Data cleared for ${year}-${month}`);
|
||
|
||
return { success: true };
|
||
|
||
} catch (error) {
|
||
await connection.rollback();
|
||
console.error('Error clearing data:', error);
|
||
throw error;
|
||
} finally {
|
||
connection.release();
|
||
}
|
||
}
|
||
|
||
// Close pool
|
||
async close() {
|
||
await this.pool.end();
|
||
}
|
||
|
||
}
|
||
|
||
|
||
|
||
|
||
module.exports = IPICalculationService; |