GWM : iip calculation
This commit is contained in:
parent
5ffb872c3a
commit
7be44b562d
@ -94,7 +94,7 @@ exports.calculate_base_year = async (req, res) => {
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: `Base year ${baseYear} production calculated successfully`,
|
||||
message: `Base year ${baseYear} production calculated successfully (quarter-based)`,
|
||||
data: result
|
||||
});
|
||||
|
||||
@ -109,34 +109,32 @@ exports.calculate_base_year = async (req, res) => {
|
||||
};
|
||||
|
||||
|
||||
// 2. Calculate IPI for Specific Month
|
||||
// 2. Calculate IPI for Specific Quarter
|
||||
exports.calculate_month = async (req, res) => {
|
||||
try {
|
||||
// const { year, month } = req.body;
|
||||
const year = 2026;
|
||||
const month = 3;
|
||||
const { year, quarter } = req.body;
|
||||
|
||||
if (!year || !month) {
|
||||
if (!year || !quarter) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: 'Year and month are required'
|
||||
message: 'Year and quarter are required'
|
||||
});
|
||||
}
|
||||
|
||||
if (month < 1 || month > 12) {
|
||||
if (!['Q1', 'Q2', 'Q3', 'Q4'].includes(quarter)) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: 'Month must be between 1 and 12'
|
||||
message: 'Quarter must be one of Q1, Q2, Q3, Q4'
|
||||
});
|
||||
}
|
||||
|
||||
const service = new IPICalculationService(dbConfig);
|
||||
const result = await service.runCompleteCalculation(year, month);
|
||||
const result = await service.runCompleteCalculation(year, quarter);
|
||||
await service.close();
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: `IPI calculated for ${year}-${String(month).padStart(2, '0')}`,
|
||||
message: `IPI calculated for ${year}-${quarter}`,
|
||||
data: result
|
||||
});
|
||||
|
||||
@ -151,7 +149,7 @@ exports.calculate_month = async (req, res) => {
|
||||
};
|
||||
|
||||
|
||||
// 3. Calculate IPI for Multiple Months
|
||||
// 3. Calculate IPI for quarter
|
||||
exports.calculate_quarter = async (req, res) => {
|
||||
try {
|
||||
|
||||
@ -164,39 +162,19 @@ exports.calculate_quarter = async (req, res) => {
|
||||
});
|
||||
}
|
||||
|
||||
// Quarter to month mapping
|
||||
const quarterMap = {
|
||||
Q1: { startMonth: 1, endMonth: 3 },
|
||||
Q2: { startMonth: 4, endMonth: 6 },
|
||||
Q3: { startMonth: 7, endMonth: 9 },
|
||||
Q4: { startMonth: 10, endMonth: 12 },
|
||||
};
|
||||
|
||||
const selectedQuarter = quarterMap[quarter];
|
||||
|
||||
if (!selectedQuarter) {
|
||||
if (!['Q1', 'Q2', 'Q3', 'Q4'].includes(quarter)) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: 'Invalid quarter. Accepted values: Q1, Q2, Q3, Q4'
|
||||
});
|
||||
}
|
||||
|
||||
const { startMonth, endMonth } = selectedQuarter;
|
||||
|
||||
const service = new IPICalculationService(dbConfig);
|
||||
const results = [];
|
||||
|
||||
for (let month = startMonth; month <= endMonth; month++) {
|
||||
const result = await service.runCompleteCalculation(year, month);
|
||||
results.push({
|
||||
month,
|
||||
...result
|
||||
});
|
||||
}
|
||||
const result = await service.runCompleteCalculation(year, quarter);
|
||||
|
||||
await service.close();
|
||||
|
||||
res.json({ success: true, message: `IPI calculated for ${year} months ${startMonth}-${endMonth}`, data: results });
|
||||
res.json({ success: true, message: `IPI calculated for ${year} ${quarter}`, data: result });
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error:', error);
|
||||
@ -289,7 +267,7 @@ exports.survey_auto_submit = async (req, res) => {
|
||||
|
||||
exports.getCalculationLogsByYearMonth = async (req, res) => {
|
||||
try {
|
||||
const { year, month } = req.query;
|
||||
const { year, quarter, month } = req.query;
|
||||
|
||||
if (!year) {
|
||||
return res.status(400).json({
|
||||
@ -297,16 +275,18 @@ exports.getCalculationLogsByYearMonth = async (req, res) => {
|
||||
});
|
||||
}
|
||||
|
||||
if (!month) {
|
||||
const resolvedQuarter = quarter || (month ? (month <= 3 ? "Q1" : month <= 6 ? "Q2" : month <= 9 ? "Q3" : "Q4") : null);
|
||||
|
||||
if (!resolvedQuarter) {
|
||||
return res.status(400).json({
|
||||
message: "Month is required",
|
||||
message: "Quarter (or month to derive quarter) is required",
|
||||
});
|
||||
}
|
||||
|
||||
const logs = await CalculationLog.findAll({
|
||||
where: {
|
||||
reference_year: year,
|
||||
reference_month: month,
|
||||
reference_month: resolvedQuarter,
|
||||
},
|
||||
order: [["started_at", "DESC"]],
|
||||
});
|
||||
|
||||
@ -37,6 +37,14 @@ const formatNumber = (value) => {
|
||||
return parsed.toFixed(2);
|
||||
};
|
||||
|
||||
const monthToQuarter = (month) => {
|
||||
if (month >= 1 && month <= 3) return "Q1";
|
||||
if (month >= 4 && month <= 6) return "Q2";
|
||||
if (month >= 7 && month <= 9) return "Q3";
|
||||
if (month >= 10 && month <= 12) return "Q4";
|
||||
return null;
|
||||
};
|
||||
|
||||
exports.renderPage = async (req, res) => {
|
||||
const now = new Date();
|
||||
const year = Number(req.query.year) || now.getFullYear();
|
||||
@ -61,7 +69,8 @@ exports.getData = async (req, res) => {
|
||||
});
|
||||
}
|
||||
|
||||
const whereClause = { year, month };
|
||||
const quarter = monthToQuarter(month);
|
||||
const whereClause = { year, quarter };
|
||||
|
||||
const [manufacturing, isic2, isic3, isic4, itemCount, logs] = await Promise.all([
|
||||
ManufacturingIpi.findOne({ where: whereClause, raw: true }),
|
||||
@ -69,36 +78,21 @@ exports.getData = async (req, res) => {
|
||||
Isic3DigitIndices.findAll({ where: whereClause, order: [["isic_3digit_code", "ASC"]], raw: true }),
|
||||
Isic4DigitIndices.findAll({ where: whereClause, order: [["isic_4digit_code", "ASC"]], raw: true }),
|
||||
db.sequelize.query(
|
||||
`SELECT COUNT(*) AS total FROM item_level_indices WHERE year = ? AND month = ?`,
|
||||
{ replacements: [year, month], type: db.Sequelize.QueryTypes.SELECT }
|
||||
`SELECT COUNT(*) AS total FROM item_level_indices WHERE year = ? AND quarter = ?`,
|
||||
{ replacements: [year, quarter], type: db.Sequelize.QueryTypes.SELECT }
|
||||
),
|
||||
CalculationLog.findAll({
|
||||
where: { reference_year: year, reference_month: month },
|
||||
where: { reference_year: year, reference_month: quarter },
|
||||
order: [["started_at", "DESC"]],
|
||||
raw: true,
|
||||
}),
|
||||
db.sequelize.query(
|
||||
`SELECT product_id, product_hs_code, base_year, avg_by_production, jan, feb, mar, apr, may, jun, jul, aug, sep, oct, nov, \`dec\`
|
||||
FROM base_year_production
|
||||
WHERE base_year = 2022
|
||||
ORDER BY product_id
|
||||
LIMIT 25`,
|
||||
{ type: db.Sequelize.QueryTypes.SELECT }
|
||||
),
|
||||
db.sequelize.query(
|
||||
`SELECT product_id, product_hs_code, year, month, month_name, production_quantity, unit
|
||||
FROM monthly_production
|
||||
WHERE year = ? AND month = ?
|
||||
ORDER BY product_id
|
||||
LIMIT 50`,
|
||||
{ replacements: [year, month], type: db.Sequelize.QueryTypes.SELECT }
|
||||
),
|
||||
})
|
||||
]);
|
||||
|
||||
return res.json({
|
||||
success: true,
|
||||
year,
|
||||
month,
|
||||
quarter,
|
||||
summary: {
|
||||
item_count: Number(itemCount[0]?.total || 0),
|
||||
isic4_count: isic4.length,
|
||||
@ -149,6 +143,7 @@ exports.getTableData = async (req, res) => {
|
||||
const table = String(req.query.table || "");
|
||||
const year = Number(req.query.year);
|
||||
const month = Number(req.query.month);
|
||||
const quarter = monthToQuarter(month);
|
||||
const baseYear = Number(req.query.baseYear) || 2022;
|
||||
const page = Math.max(1, Number(req.query.page) || 1);
|
||||
const pageSize = Math.min(200, Math.max(1, Number(req.query.pageSize) || 50));
|
||||
@ -157,14 +152,14 @@ exports.getTableData = async (req, res) => {
|
||||
|
||||
const queryConfig = {
|
||||
monthly: {
|
||||
whereSql: `year = ? AND month = ?`,
|
||||
whereParams: [year, month],
|
||||
searchSql: search ? ` AND (CAST(product_id AS CHAR) LIKE ? OR CAST(product_hs_code AS CHAR) LIKE ? OR month_name LIKE ?)` : "",
|
||||
whereSql: `year = ? AND quarter = ?`,
|
||||
whereParams: [year, quarter],
|
||||
searchSql: search ? ` AND (CAST(product_id AS CHAR) LIKE ? OR CAST(product_hs_code AS CHAR) LIKE ? OR quarter_name LIKE ?)` : "",
|
||||
searchParams: search ? [`%${search}%`, `%${search}%`, `%${search}%`] : [],
|
||||
countSql: `SELECT COUNT(*) AS total FROM monthly_production WHERE year = ? AND month = ?`,
|
||||
dataSql: `SELECT product_id, product_hs_code, year, month, month_name, production_quantity, unit
|
||||
countSql: `SELECT COUNT(*) AS total FROM monthly_production WHERE year = ? AND quarter = ?`,
|
||||
dataSql: `SELECT product_id, product_hs_code, year, quarter, quarter_name, production_quantity, unit
|
||||
FROM monthly_production
|
||||
WHERE year = ? AND month = ?`,
|
||||
WHERE year = ? AND quarter = ?`,
|
||||
orderSql: ` ORDER BY product_id`,
|
||||
},
|
||||
baseyear: {
|
||||
@ -173,20 +168,20 @@ exports.getTableData = async (req, res) => {
|
||||
searchSql: search ? ` AND (CAST(product_id AS CHAR) LIKE ? OR CAST(product_hs_code AS CHAR) LIKE ?)` : "",
|
||||
searchParams: search ? [`%${search}%`, `%${search}%`] : [],
|
||||
countSql: `SELECT COUNT(*) AS total FROM base_year_production WHERE base_year = ?`,
|
||||
dataSql: `SELECT product_id, product_hs_code, base_year, avg_by_production, jan, feb, mar, apr, may, jun, jul, aug, sep, oct, nov, \`dec\`
|
||||
dataSql: `SELECT product_id, product_hs_code, base_year, avg_by_production, q1, q2, q3, q4
|
||||
FROM base_year_production
|
||||
WHERE base_year = ?`,
|
||||
orderSql: ` ORDER BY product_id`,
|
||||
},
|
||||
items: {
|
||||
whereSql: `year = ? AND month = ?`,
|
||||
whereParams: [year, month],
|
||||
whereSql: `year = ? AND quarter = ?`,
|
||||
whereParams: [year, quarter],
|
||||
searchSql: search ? ` AND (CAST(product_id AS CHAR) LIKE ? OR CAST(product_hs_code AS CHAR) LIKE ?)` : "",
|
||||
searchParams: search ? [`%${search}%`, `%${search}%`] : [],
|
||||
countSql: `SELECT COUNT(*) AS total FROM item_level_indices WHERE year = ? AND month = ?`,
|
||||
countSql: `SELECT COUNT(*) AS total FROM item_level_indices WHERE year = ? AND quarter = ?`,
|
||||
dataSql: `SELECT product_id, product_hs_code, current_production, base_year_avg_production, production_relative, item_index
|
||||
FROM item_level_indices
|
||||
WHERE year = ? AND month = ?`,
|
||||
WHERE year = ? AND quarter = ?`,
|
||||
orderSql: ` ORDER BY product_id`,
|
||||
},
|
||||
};
|
||||
@ -195,7 +190,7 @@ exports.getTableData = async (req, res) => {
|
||||
return res.status(400).json({ success: false, message: "Invalid table parameter" });
|
||||
}
|
||||
|
||||
if ((table === "monthly" || table === "items") && (!year || !month || month < 1 || month > 12)) {
|
||||
if ((table === "monthly" || table === "items") && (!year || !month || month < 1 || month > 12 || !quarter)) {
|
||||
return res.status(400).json({ success: false, message: "Valid year and month are required" });
|
||||
}
|
||||
|
||||
@ -216,7 +211,7 @@ exports.getTableData = async (req, res) => {
|
||||
const copy = { ...row };
|
||||
Object.keys(copy).forEach((k) => {
|
||||
if (
|
||||
["production_quantity", "avg_by_production", "current_production", "base_year_avg_production", "production_relative", "item_index", "jan", "feb", "mar", "apr", "may", "jun", "jul", "aug", "sep", "oct", "nov", "dec"].includes(k)
|
||||
["production_quantity", "avg_by_production", "current_production", "base_year_avg_production", "production_relative", "item_index", "q1", "q2", "q3", "q4"].includes(k)
|
||||
) {
|
||||
copy[k] = formatNumber(copy[k]);
|
||||
}
|
||||
@ -245,8 +240,9 @@ exports.runMonthCalculation = async (req, res) => {
|
||||
try {
|
||||
const year = Number(req.body.year);
|
||||
const month = Number(req.body.month);
|
||||
const quarter = monthToQuarter(month);
|
||||
|
||||
if (!year || !month || month < 1 || month > 12) {
|
||||
if (!year || !month || month < 1 || month > 12 || !quarter) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: "Valid year and month are required",
|
||||
@ -254,12 +250,12 @@ exports.runMonthCalculation = async (req, res) => {
|
||||
}
|
||||
|
||||
const service = new IPICalculationService(dbConfig);
|
||||
const result = await service.runCompleteCalculation(year, month);
|
||||
const result = await service.runCompleteCalculation(year, quarter);
|
||||
await service.close();
|
||||
|
||||
return res.json({
|
||||
success: true,
|
||||
message: `Calculation completed for ${year}-${String(month).padStart(2, "0")}`,
|
||||
message: `Calculation completed for ${year}-${quarter}`,
|
||||
data: result,
|
||||
});
|
||||
} catch (error) {
|
||||
@ -275,8 +271,9 @@ exports.getRunStatus = async (req, res) => {
|
||||
try {
|
||||
const year = Number(req.query.year);
|
||||
const month = Number(req.query.month);
|
||||
const quarter = monthToQuarter(month);
|
||||
|
||||
if (!year || !month || month < 1 || month > 12) {
|
||||
if (!year || !month || month < 1 || month > 12 || !quarter) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: "Valid year and month are required",
|
||||
@ -286,7 +283,7 @@ exports.getRunStatus = async (req, res) => {
|
||||
const logs = await CalculationLog.findAll({
|
||||
where: {
|
||||
reference_year: year,
|
||||
reference_month: month,
|
||||
reference_month: quarter,
|
||||
},
|
||||
order: [["started_at", "ASC"]],
|
||||
raw: true,
|
||||
@ -314,6 +311,7 @@ exports.getRunStatus = async (req, res) => {
|
||||
success: true,
|
||||
year,
|
||||
month,
|
||||
quarter,
|
||||
started: startedAny,
|
||||
isFinished,
|
||||
completedCount,
|
||||
|
||||
@ -61,27 +61,27 @@ exports.getAllManufacturingIndexDetails = async (req, res) => {
|
||||
|
||||
exports.getManufacturingMonthlyOverviewByYearMonth = async (req, res) => {
|
||||
try {
|
||||
const { year, month } = req.query;
|
||||
const { year, month, quarter } = req.query;
|
||||
|
||||
// Validate parameters exist
|
||||
if (!year || !month) {
|
||||
if (!year || (!month && !quarter)) {
|
||||
logger.warn('getManufacturingMonthlyOverview API: Missing required parameters');
|
||||
return res.status(400).send({
|
||||
status: "failed",
|
||||
message: "Year and month are required"
|
||||
message: "Year and quarter (or month to derive quarter) are required"
|
||||
});
|
||||
}
|
||||
|
||||
// Sanitize and validate input - prevent SQL injection
|
||||
const yearInt = parseInt(year, 10);
|
||||
const monthInt = parseInt(month, 10);
|
||||
const monthInt = month ? parseInt(month, 10) : null;
|
||||
|
||||
// Validate that parsing was successful
|
||||
if (isNaN(yearInt) || isNaN(monthInt)) {
|
||||
logger.warn(`getManufacturingMonthlyOverview API: Invalid input - year: ${year}, month: ${month}`);
|
||||
if (isNaN(yearInt) || (month && isNaN(monthInt))) {
|
||||
logger.warn(`getManufacturingMonthlyOverview API: Invalid input - year: ${year}, month: ${month}, quarter: ${quarter}`);
|
||||
return res.status(400).send({
|
||||
status: "failed",
|
||||
message: "Year and month must be valid numbers"
|
||||
message: "Year/month must be valid numbers"
|
||||
});
|
||||
}
|
||||
|
||||
@ -93,18 +93,25 @@ exports.getManufacturingMonthlyOverviewByYearMonth = async (req, res) => {
|
||||
});
|
||||
}
|
||||
|
||||
// Validate month range
|
||||
if (monthInt < 1 || monthInt > 12) {
|
||||
if (month && (monthInt < 1 || monthInt > 12)) {
|
||||
return res.status(400).send({
|
||||
status: "failed",
|
||||
message: "Month must be between 1 and 12"
|
||||
});
|
||||
}
|
||||
|
||||
const derivedQuarter = quarter || (monthInt <= 3 ? "Q1" : monthInt <= 6 ? "Q2" : monthInt <= 9 ? "Q3" : "Q4");
|
||||
if (!["Q1", "Q2", "Q3", "Q4"].includes(derivedQuarter)) {
|
||||
return res.status(400).send({
|
||||
status: "failed",
|
||||
message: "Quarter must be one of Q1, Q2, Q3, Q4"
|
||||
});
|
||||
}
|
||||
|
||||
// Common where clause
|
||||
const whereClause = {
|
||||
year: yearInt,
|
||||
month: monthInt
|
||||
quarter: derivedQuarter
|
||||
};
|
||||
|
||||
// Fetch data from all tables in parallel
|
||||
@ -132,7 +139,7 @@ exports.getManufacturingMonthlyOverviewByYearMonth = async (req, res) => {
|
||||
|
||||
// Check if manufacturing data exists (main data)
|
||||
if (!manufacturingData) {
|
||||
logger.info(`getManufacturingMonthlyOverview API: No data found for year ${yearInt}, month ${monthInt}`);
|
||||
logger.info(`getManufacturingMonthlyOverview API: No data found for year ${yearInt}, quarter ${derivedQuarter}`);
|
||||
return res.status(404).send({
|
||||
status: "failed",
|
||||
message: "No data found for the specified period"
|
||||
@ -183,7 +190,7 @@ exports.getManufacturingMonthlyOverviewByYearMonth = async (req, res) => {
|
||||
};
|
||||
});
|
||||
|
||||
logger.info(`getManufacturingMonthlyOverview API: Successfully fetched data for year ${yearInt}, month ${monthInt}`);
|
||||
logger.info(`getManufacturingMonthlyOverview API: Successfully fetched data for year ${yearInt}, quarter ${derivedQuarter}`);
|
||||
|
||||
res.status(200).send({
|
||||
status: "success",
|
||||
|
||||
@ -22,11 +22,11 @@ module.exports = (sequelize, DataTypes) => {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
},
|
||||
month: {
|
||||
type: DataTypes.INTEGER,
|
||||
quarter: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
month_name: {
|
||||
quarter_name: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
|
||||
@ -22,11 +22,11 @@ module.exports = (sequelize, DataTypes) => {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
},
|
||||
month: {
|
||||
type: DataTypes.INTEGER,
|
||||
quarter: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
month_name: {
|
||||
quarter_name: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
|
||||
@ -22,11 +22,11 @@ module.exports = (sequelize, DataTypes) => {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
},
|
||||
month: {
|
||||
type: DataTypes.INTEGER,
|
||||
quarter: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
month_name: {
|
||||
quarter_name: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
|
||||
@ -9,11 +9,11 @@ module.exports = (sequelize, DataTypes) => {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
},
|
||||
month: {
|
||||
type: DataTypes.INTEGER,
|
||||
quarter: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
month_name: {
|
||||
quarter_name: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
|
||||
@ -10,7 +10,7 @@ class IPICalculationService {
|
||||
}
|
||||
|
||||
|
||||
// STEP 1: Calculate and Store Base Year Average Production (2022)
|
||||
// STEP 1: Calculate and Store Base Year Average Production (Quarter-based)
|
||||
async calculateBaseYearProduction(baseYear = 2022, forceRecalculate = false) {
|
||||
|
||||
const connection = await this.pool.getConnection();
|
||||
@ -33,38 +33,22 @@ class IPICalculationService {
|
||||
// Log calculation start
|
||||
logId = await this.logRecord( 'base_year', baseYear, null, 'Started', null, 0, null, connection);
|
||||
|
||||
// Your existing query to calculate base year production
|
||||
// Quarter-based base year production using submission_products.current_quantity
|
||||
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\`,
|
||||
SUM(CASE WHEN s.quarter = 'Q1' THEN COALESCE(sp.current_quantity, 0) END) AS q1,
|
||||
SUM(CASE WHEN s.quarter = 'Q2' THEN COALESCE(sp.current_quantity, 0) END) AS q2,
|
||||
SUM(CASE WHEN s.quarter = 'Q3' THEN COALESCE(sp.current_quantity, 0) END) AS q3,
|
||||
SUM(CASE WHEN s.quarter = 'Q4' THEN COALESCE(sp.current_quantity, 0) END) AS q4,
|
||||
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
|
||||
COALESCE(SUM(CASE WHEN s.quarter='Q1' THEN sp.current_quantity END),0) +
|
||||
COALESCE(SUM(CASE WHEN s.quarter='Q2' THEN sp.current_quantity END),0) +
|
||||
COALESCE(SUM(CASE WHEN s.quarter='Q3' THEN sp.current_quantity END),0) +
|
||||
COALESCE(SUM(CASE WHEN s.quarter='Q4' THEN sp.current_quantity END),0)
|
||||
) / 4, 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
|
||||
@ -84,15 +68,11 @@ class IPICalculationService {
|
||||
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_id, product_hs_code, base_year, unit, q1, q2, q3, q4, 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.q1 || 0, product.q2 || 0, product.q3 || 0, product.q4 || 0,
|
||||
product.avg_by_production
|
||||
]
|
||||
);
|
||||
@ -128,38 +108,23 @@ class IPICalculationService {
|
||||
}
|
||||
|
||||
|
||||
// STEP 2: Aggregate Monthly Production
|
||||
async aggregateMonthlyProduction(year, month) {
|
||||
// STEP 2: Aggregate Quarterly Production
|
||||
async aggregateQuarterlyProduction(year, quarter) {
|
||||
const connection = await this.pool.getConnection();
|
||||
let logId = null;
|
||||
|
||||
try {
|
||||
await connection.beginTransaction();
|
||||
|
||||
logger.info(`aggregateMonthlyProduction - Aggregating production for ${year}-${month}...`);
|
||||
logger.info(`aggregateQuarterlyProduction - Aggregating production for ${year}-${quarter}...`);
|
||||
|
||||
// Log calculation start
|
||||
logId = await this.logRecord( 'monthly_production', year, month, 'Started', null, 0, null, connection);
|
||||
logId = await this.logRecord('monthly_production', year, quarter, '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'];
|
||||
const validQuarters = new Set(['Q1', 'Q2', 'Q3', 'Q4']);
|
||||
if (!validQuarters.has(quarter)) {
|
||||
throw new Error('Invalid quarter. Accepted values: Q1, Q2, Q3, Q4');
|
||||
}
|
||||
|
||||
// Aggregate production by product
|
||||
const query = `
|
||||
@ -167,7 +132,7 @@ class IPICalculationService {
|
||||
p.id AS product_id,
|
||||
p.hs_code AS product_hs_code,
|
||||
u.uom_short_name AS unit,
|
||||
SUM(sp.${period}) AS production_quantity
|
||||
SUM(COALESCE(sp.current_quantity, 0)) 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
|
||||
@ -184,34 +149,35 @@ class IPICalculationService {
|
||||
for (const product of products) {
|
||||
await connection.query(
|
||||
`INSERT INTO monthly_production
|
||||
(product_id, product_hs_code, year, month, month_name,
|
||||
(product_id, product_hs_code, year, quarter, quarter_name,
|
||||
production_quantity, unit)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||
ON DUPLICATE KEY UPDATE
|
||||
production_quantity=VALUES(production_quantity),
|
||||
quarter_name=VALUES(quarter_name),
|
||||
unit=VALUES(unit)`,
|
||||
[
|
||||
product.product_id, product.product_hs_code, year, month,
|
||||
monthNames[month], product.production_quantity || 0, product.unit
|
||||
product.product_id, product.product_hs_code, year, quarter,
|
||||
quarter, product.production_quantity || 0, product.unit
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
// Update log
|
||||
await this.logRecord('monthly_production', year, month, 'Completed', logId, products.length, null,connection);
|
||||
await this.logRecord('monthly_production', year, quarter, 'Completed', logId, products.length, null, connection);
|
||||
|
||||
await connection.commit();
|
||||
logger.info(`aggregateMonthlyProduction - Monthly production aggregated: ${products.length} products`);
|
||||
logger.info(`aggregateQuarterlyProduction - Quarterly 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}`);
|
||||
logger.error(`aggregateQuarterlyProduction - Error aggregating quarterly production: ${error}`);
|
||||
// Update log OUTSIDE the failed transaction
|
||||
if (logId) {
|
||||
await this.logRecord('monthly_production', year, month, 'Failed', logId, 0, error.message);
|
||||
await this.logRecord('monthly_production', year, quarter, 'Failed', logId, 0, error.message);
|
||||
}
|
||||
throw error;
|
||||
} finally {
|
||||
@ -221,18 +187,16 @@ class IPICalculationService {
|
||||
|
||||
|
||||
// STEP 3: Calculate Item Level Indices
|
||||
async calculateItemLevelIndices(year, month) {
|
||||
async calculateItemLevelIndices(year, quarter) {
|
||||
const connection = await this.pool.getConnection();
|
||||
let logId = null;
|
||||
try {
|
||||
await connection.beginTransaction();
|
||||
|
||||
logger.info(`calculateItemLevelIndices - Calculating item level indices for ${year}-${month}...`);
|
||||
logger.info(`calculateItemLevelIndices - Calculating item level indices for ${year}-${quarter}...`);
|
||||
|
||||
// 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'];
|
||||
logId = await this.logRecord('item_level_indices', year, quarter, 'Started', null, 0, null, connection);
|
||||
|
||||
// Calculate indices: Ri = Current Production / Base Year Avg Production
|
||||
// Ii = Ri × 100
|
||||
@ -246,17 +210,17 @@ class IPICalculationService {
|
||||
((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 = ?
|
||||
WHERE mp.year = ? AND mp.quarter = ?
|
||||
AND byp.avg_by_production > 0
|
||||
`;
|
||||
|
||||
const [items] = await connection.query(query, [year, month]);
|
||||
const [items] = await connection.query(query, [year, quarter]);
|
||||
|
||||
// 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,
|
||||
(product_id, product_hs_code, year, quarter, quarter_name,
|
||||
current_production, base_year_avg_production,
|
||||
production_relative, item_index)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
@ -266,7 +230,7 @@ class IPICalculationService {
|
||||
production_relative=VALUES(production_relative),
|
||||
item_index=VALUES(item_index)`,
|
||||
[
|
||||
item.product_id, item.product_hs_code, year, month, monthNames[month],
|
||||
item.product_id, item.product_hs_code, year, quarter, quarter,
|
||||
item.current_production, item.base_year_avg_production,
|
||||
item.production_relative, item.item_index
|
||||
]
|
||||
@ -274,7 +238,7 @@ class IPICalculationService {
|
||||
}
|
||||
|
||||
// Update log
|
||||
await this.logRecord('item_level_indices', year, month, 'Completed', logId, items.length, null,connection);
|
||||
await this.logRecord('item_level_indices', year, quarter, 'Completed', logId, items.length, null, connection);
|
||||
|
||||
await connection.commit();
|
||||
logger.info(`calculateItemLevelIndices - Item level indices calculated: ${items.length} items`);
|
||||
@ -286,7 +250,7 @@ class IPICalculationService {
|
||||
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);
|
||||
await this.logRecord('item_level_indices', year, quarter, 'Failed', logId, 0, error.message);
|
||||
}
|
||||
throw error;
|
||||
} finally {
|
||||
@ -296,52 +260,65 @@ class IPICalculationService {
|
||||
|
||||
|
||||
// STEP 4: Calculate ISIC 4-Digit Level Indices
|
||||
async calculateISIC4DigitIndices(year, month) {
|
||||
async calculateISIC4DigitIndices(year, quarter) {
|
||||
const connection = await this.pool.getConnection();
|
||||
let logId = null;
|
||||
try {
|
||||
await connection.beginTransaction();
|
||||
|
||||
logger.info(`calculateISIC4DigitIndices - Calculating ISIC 4-digit indices for ${year}-${month}...`);
|
||||
logger.info(`calculateISIC4DigitIndices - Calculating ISIC 4-digit indices for ${year}-${quarter}...`);
|
||||
|
||||
// Log calculation start
|
||||
logId = await this.logRecord( 'isic_4digit_indices', year, month, 'Started', null, 0, null, connection);
|
||||
logId = await this.logRecord('isic_4digit_indices', year, quarter, 'Started', null, 0, null, connection);
|
||||
|
||||
const monthNames = ['', 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
|
||||
|
||||
// ISIC mapping source is now products.isic_code.
|
||||
// Aggregate weighted item indices to ISIC 4-digit using product master mapping.
|
||||
// ISIC 4-digit grouping uses establishments.isic_code (via approved submissions).
|
||||
// One establishment per product-period is chosen (highest current_quantity, then submission id)
|
||||
// so item_index and weight_in_ib are not multiplied when several establishments report the same product.
|
||||
const query = `
|
||||
WITH product_isic_from_establishment AS (
|
||||
SELECT
|
||||
ili.product_id,
|
||||
ili.item_index,
|
||||
LEFT(TRIM(CAST(e.isic_code AS CHAR)), 4) AS isic_4digit_code,
|
||||
ROW_NUMBER() OVER (
|
||||
PARTITION BY ili.product_id
|
||||
ORDER BY COALESCE(sp.current_quantity, 0) DESC, s.id ASC
|
||||
) AS rn
|
||||
FROM item_level_indices ili
|
||||
JOIN submission_products sp ON sp.product_id = ili.product_id
|
||||
JOIN submission s ON s.id = sp.submission_id
|
||||
AND s.year = ?
|
||||
AND s.quarter = ?
|
||||
AND s.status = 'Approved'
|
||||
JOIN establishments e ON e.id = s.establishment_id
|
||||
WHERE ili.year = ?
|
||||
AND ili.quarter = ?
|
||||
AND sp.is_active = 1
|
||||
AND e.isic_code IS NOT NULL
|
||||
AND TRIM(CAST(e.isic_code AS CHAR)) <> ''
|
||||
)
|
||||
SELECT
|
||||
LEFT(CAST(p.isic_code AS CHAR), 4) AS isic_4digit_code,
|
||||
pie.isic_4digit_code,
|
||||
SUM(COALESCE(p.weight_in_ib, 0)) AS total_weight,
|
||||
SUM(
|
||||
COALESCE(p.weight_in_ib, 0) *
|
||||
ili.item_index
|
||||
) AS weighted_index_sum,
|
||||
SUM(COALESCE(p.weight_in_ib, 0) * pie.item_index) AS weighted_index_sum,
|
||||
(
|
||||
SUM(
|
||||
COALESCE(p.weight_in_ib, 0) *
|
||||
ili.item_index
|
||||
) /
|
||||
SUM(COALESCE(p.weight_in_ib, 0) * pie.item_index) /
|
||||
NULLIF(SUM(COALESCE(p.weight_in_ib, 0)), 0)
|
||||
) AS isic_4digit_index
|
||||
FROM item_level_indices ili
|
||||
JOIN products p ON p.id = ili.product_id
|
||||
WHERE ili.year = ?
|
||||
AND ili.month = ?
|
||||
AND p.isic_code IS NOT NULL
|
||||
FROM product_isic_from_establishment pie
|
||||
JOIN products p ON p.id = pie.product_id
|
||||
WHERE pie.rn = 1
|
||||
AND p.weight_in_ib IS NOT NULL
|
||||
AND p.weight_in_ib > 0
|
||||
GROUP BY LEFT(CAST(p.isic_code AS CHAR), 4)
|
||||
GROUP BY pie.isic_4digit_code
|
||||
`;
|
||||
|
||||
const [isic4Results] = await connection.query(query, [year, month]);
|
||||
const [isic4Results] = await connection.query(query, [year, quarter, year, quarter]);
|
||||
|
||||
for (const result of isic4Results) {
|
||||
await connection.query(
|
||||
`INSERT INTO isic_4digit_indices
|
||||
(isic_4digit_code, year, month, month_name, total_weight,
|
||||
(isic_4digit_code, year, quarter, quarter_name, total_weight,
|
||||
weighted_index_sum, isic_4digit_index)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||
ON DUPLICATE KEY UPDATE
|
||||
@ -349,7 +326,7 @@ class IPICalculationService {
|
||||
weighted_index_sum=VALUES(weighted_index_sum),
|
||||
isic_4digit_index=VALUES(isic_4digit_index)`,
|
||||
[
|
||||
result.isic_4digit_code, year, month, monthNames[month],
|
||||
result.isic_4digit_code, year, quarter, quarter,
|
||||
result.total_weight,
|
||||
result.weighted_index_sum,
|
||||
result.isic_4digit_index
|
||||
@ -358,7 +335,7 @@ class IPICalculationService {
|
||||
}
|
||||
|
||||
// Update log
|
||||
await this.logRecord('isic_4digit_indices', year, month, 'Completed', logId, isic4Results.length, null,connection);
|
||||
await this.logRecord('isic_4digit_indices', year, quarter, 'Completed', logId, isic4Results.length, null, connection);
|
||||
|
||||
await connection.commit();
|
||||
logger.info(`calculateISIC4DigitIndices - ISIC 4-digit indices calculated: ${isic4Results.length} codes`);
|
||||
@ -370,7 +347,7 @@ class IPICalculationService {
|
||||
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);
|
||||
await this.logRecord('isic_4digit_indices', year, quarter, 'Failed', logId, 0, error.message);
|
||||
}
|
||||
throw error;
|
||||
} finally {
|
||||
@ -380,18 +357,16 @@ class IPICalculationService {
|
||||
|
||||
|
||||
// STEP 5: Calculate ISIC 3-Digit Level Indices
|
||||
async calculateISIC3DigitIndices(year, month) {
|
||||
async calculateISIC3DigitIndices(year, quarter) {
|
||||
const connection = await this.pool.getConnection();
|
||||
let logId = null;
|
||||
try {
|
||||
await connection.beginTransaction();
|
||||
|
||||
logger.info(`calculateISIC3DigitIndices - Calculating ISIC 3-digit indices for ${year}-${month}...`);
|
||||
logger.info(`calculateISIC3DigitIndices - Calculating ISIC 3-digit indices for ${year}-${quarter}...`);
|
||||
|
||||
// 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'];
|
||||
logId = await this.logRecord('isic_3digit_indices', year, quarter, 'Started', null, 0, null, connection);
|
||||
|
||||
const query = `
|
||||
SELECT
|
||||
@ -400,16 +375,16 @@ class IPICalculationService {
|
||||
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 = ?
|
||||
WHERE year = ? AND quarter = ?
|
||||
GROUP BY LEFT(isic_4digit_code, 3)
|
||||
`;
|
||||
|
||||
const [isic3Results] = await connection.query(query, [year, month]);
|
||||
const [isic3Results] = await connection.query(query, [year, quarter]);
|
||||
|
||||
for (const result of isic3Results) {
|
||||
await connection.query(
|
||||
`INSERT INTO isic_3digit_indices
|
||||
(isic_3digit_code, year, month, month_name, total_weight,
|
||||
(isic_3digit_code, year, quarter, quarter_name, total_weight,
|
||||
weighted_index_sum, isic_3digit_index)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||
ON DUPLICATE KEY UPDATE
|
||||
@ -417,7 +392,7 @@ class IPICalculationService {
|
||||
weighted_index_sum=VALUES(weighted_index_sum),
|
||||
isic_3digit_index=VALUES(isic_3digit_index)`,
|
||||
[
|
||||
result.isic_3digit_code, year, month, monthNames[month],
|
||||
result.isic_3digit_code, year, quarter, quarter,
|
||||
result.total_weight, result.weighted_index_sum,
|
||||
result.isic_3digit_index
|
||||
]
|
||||
@ -425,7 +400,7 @@ class IPICalculationService {
|
||||
}
|
||||
|
||||
// Update log
|
||||
await this.logRecord('isic_3digit_indices', year, month, 'Completed', logId, isic3Results.length, null,connection);
|
||||
await this.logRecord('isic_3digit_indices', year, quarter, 'Completed', logId, isic3Results.length, null, connection);
|
||||
|
||||
await connection.commit();
|
||||
logger.info(`calculateISIC3DigitIndices - ISIC 3-digit indices calculated: ${isic3Results.length} codes`);
|
||||
@ -437,7 +412,7 @@ class IPICalculationService {
|
||||
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);
|
||||
await this.logRecord('isic_3digit_indices', year, quarter, 'Failed', logId, 0, error.message);
|
||||
}
|
||||
throw error;
|
||||
} finally {
|
||||
@ -447,18 +422,16 @@ class IPICalculationService {
|
||||
|
||||
|
||||
// STEP 6: Calculate ISIC 2-Digit Level Indices
|
||||
async calculateISIC2DigitIndices(year, month) {
|
||||
async calculateISIC2DigitIndices(year, quarter) {
|
||||
const connection = await this.pool.getConnection();
|
||||
let logId = null;
|
||||
try {
|
||||
await connection.beginTransaction();
|
||||
|
||||
logger.info(`calculateISIC2DigitIndices - Calculating ISIC 2-digit indices for ${year}-${month}...`);
|
||||
logger.info(`calculateISIC2DigitIndices - Calculating ISIC 2-digit indices for ${year}-${quarter}...`);
|
||||
|
||||
// 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'];
|
||||
logId = await this.logRecord('isic_2digit_indices', year, quarter, 'Started', null, 0, null, connection);
|
||||
|
||||
const query = `
|
||||
SELECT
|
||||
@ -467,16 +440,16 @@ class IPICalculationService {
|
||||
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 = ?
|
||||
WHERE year = ? AND quarter = ?
|
||||
GROUP BY LEFT(isic_3digit_code, 2)
|
||||
`;
|
||||
|
||||
const [isic2Results] = await connection.query(query, [year, month]);
|
||||
const [isic2Results] = await connection.query(query, [year, quarter]);
|
||||
|
||||
for (const result of isic2Results) {
|
||||
await connection.query(
|
||||
`INSERT INTO isic_2digit_indices
|
||||
(isic_2digit_code, year, month, month_name, total_weight,
|
||||
(isic_2digit_code, year, quarter, quarter_name, total_weight,
|
||||
weighted_index_sum, isic_2digit_index)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||
ON DUPLICATE KEY UPDATE
|
||||
@ -484,7 +457,7 @@ class IPICalculationService {
|
||||
weighted_index_sum=VALUES(weighted_index_sum),
|
||||
isic_2digit_index=VALUES(isic_2digit_index)`,
|
||||
[
|
||||
result.isic_2digit_code, year, month, monthNames[month],
|
||||
result.isic_2digit_code, year, quarter, quarter,
|
||||
result.total_weight, result.weighted_index_sum,
|
||||
result.isic_2digit_index
|
||||
]
|
||||
@ -492,7 +465,7 @@ class IPICalculationService {
|
||||
}
|
||||
|
||||
// Update log
|
||||
await this.logRecord('isic_2digit_indices', year, month, 'Completed', logId, isic2Results.length, null,connection);
|
||||
await this.logRecord('isic_2digit_indices', year, quarter, 'Completed', logId, isic2Results.length, null, connection);
|
||||
|
||||
await connection.commit();
|
||||
logger.info(`calculateISIC2DigitIndices - ISIC 2-digit indices calculated: ${isic2Results.length} codes`);
|
||||
@ -504,7 +477,7 @@ class IPICalculationService {
|
||||
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);
|
||||
await this.logRecord('isic_2digit_indices', year, quarter, 'Failed', logId, 0, error.message);
|
||||
}
|
||||
throw error;
|
||||
} finally {
|
||||
@ -514,18 +487,16 @@ class IPICalculationService {
|
||||
|
||||
|
||||
// STEP 7: Calculate Manufacturing IPI (Headline Index)
|
||||
async calculateManufacturingIPI(year, month) {
|
||||
async calculateManufacturingIPI(year, quarter) {
|
||||
const connection = await this.pool.getConnection();
|
||||
let logId = null;
|
||||
try {
|
||||
await connection.beginTransaction();
|
||||
|
||||
logger.info(`calculateManufacturingIPI - Calculating Manufacturing indices for ${year}-${month}...`);
|
||||
logger.info(`calculateManufacturingIPI - Calculating Manufacturing indices for ${year}-${quarter}...`);
|
||||
|
||||
// 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'];
|
||||
logId = await this.logRecord('manufacturing_ipi', year, quarter, 'Started', null, 0, null, connection);
|
||||
|
||||
// Calculate Manufacturing IPI
|
||||
const query = `
|
||||
@ -534,10 +505,10 @@ class IPICalculationService {
|
||||
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 = ?
|
||||
WHERE year = ? AND quarter = ?
|
||||
`;
|
||||
|
||||
const [result] = await connection.query(query, [year, month]);
|
||||
const [result] = await connection.query(query, [year, quarter]);
|
||||
|
||||
// If no data found (or aggregate returns NULLs), skip insert to avoid NOT NULL DB errors
|
||||
if (
|
||||
@ -547,47 +518,50 @@ class IPICalculationService {
|
||||
result[0].weighted_index_sum == null ||
|
||||
result[0].manufacturing_index == null
|
||||
) {
|
||||
await this.logRecord('manufacturing_ipi', year, month, 'Completed', logId, 0, null, connection);
|
||||
await this.logRecord('manufacturing_ipi', year, quarter, 'Completed', logId, 0, null, connection);
|
||||
await connection.commit();
|
||||
|
||||
logger.info(`calculateManufacturingIPI - No valid ISIC 2-digit aggregates found for ${year}-${month}`);
|
||||
logger.info(`calculateManufacturingIPI - No valid ISIC 2-digit aggregates found for ${year}-${quarter}`);
|
||||
return {
|
||||
success: true,
|
||||
index: null,
|
||||
momChange: null,
|
||||
qoqChange: null,
|
||||
momChange: null,
|
||||
yoyChange: null,
|
||||
message: "No IPI data found for the given year and month"
|
||||
message: "No IPI data found for the given year and quarter"
|
||||
};
|
||||
}
|
||||
|
||||
const ipiData = result[0];
|
||||
|
||||
// Calculate MoM and YoY changes
|
||||
let momChange = null;
|
||||
// Calculate QoQ and YoY changes
|
||||
let qoqChange = null;
|
||||
let yoyChange = null;
|
||||
|
||||
// Get previous month index for MoM
|
||||
const prevMonth = month === 1 ? 12 : month - 1;
|
||||
const prevYear = month === 1 ? year - 1 : year;
|
||||
// Previous quarter (QoQ)
|
||||
const quarterOrder = ['Q1', 'Q2', 'Q3', 'Q4'];
|
||||
const quarterIdx = quarterOrder.indexOf(quarter);
|
||||
const prevQuarter = quarterIdx === 0 ? 'Q4' : quarterOrder[quarterIdx - 1];
|
||||
const prevYear = quarterIdx === 0 ? year - 1 : year;
|
||||
|
||||
const [prevMonthData] = await connection.query(
|
||||
const [prevQuarterData] = await connection.query(
|
||||
`SELECT manufacturing_index FROM manufacturing_ipi
|
||||
WHERE year = ? AND month = ?`,
|
||||
[prevYear, prevMonth]
|
||||
WHERE year = ? AND quarter = ?`,
|
||||
[prevYear, prevQuarter]
|
||||
);
|
||||
|
||||
if (prevMonthData.length > 0 && prevMonthData[0].manufacturing_index) {
|
||||
const prevIndex = prevMonthData[0].manufacturing_index;
|
||||
if (prevQuarterData.length > 0 && prevQuarterData[0].manufacturing_index) {
|
||||
const prevIndex = prevQuarterData[0].manufacturing_index;
|
||||
if (Number(prevIndex) !== 0) {
|
||||
momChange = ((ipiData.manufacturing_index - prevIndex) / prevIndex) * 100;
|
||||
qoqChange = ((ipiData.manufacturing_index - prevIndex) / prevIndex) * 100;
|
||||
}
|
||||
}
|
||||
|
||||
// Get same month last year for YoY
|
||||
// Get same quarter last year for YoY
|
||||
const [lastYearData] = await connection.query(
|
||||
`SELECT manufacturing_index FROM manufacturing_ipi
|
||||
WHERE year = ? AND month = ?`,
|
||||
[year - 1, month]
|
||||
WHERE year = ? AND quarter = ?`,
|
||||
[year - 1, quarter]
|
||||
);
|
||||
|
||||
if (lastYearData.length > 0 && lastYearData[0].manufacturing_index) {
|
||||
@ -597,13 +571,13 @@ class IPICalculationService {
|
||||
}
|
||||
}
|
||||
|
||||
// Reference date is the first day of the month
|
||||
const referenceDate = `${year}-${String(month).padStart(2, '0')}-01`;
|
||||
const quarterStartMonth = { Q1: '01', Q2: '04', Q3: '07', Q4: '10' };
|
||||
const referenceDate = `${year}-${quarterStartMonth[quarter]}-01`;
|
||||
|
||||
// Insert Manufacturing IPI
|
||||
await connection.query(
|
||||
`INSERT INTO manufacturing_ipi
|
||||
(year, month, month_name, reference_date, total_weight,
|
||||
(year, quarter, quarter_name, reference_date, total_weight,
|
||||
weighted_index_sum, manufacturing_index, mom_change, yoy_change, status, generated_on)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 'Completed',?)
|
||||
ON DUPLICATE KEY UPDATE
|
||||
@ -615,27 +589,28 @@ class IPICalculationService {
|
||||
status=VALUES(status),
|
||||
generated_on = VALUES(generated_on)`,
|
||||
[
|
||||
year, month, monthNames[month], referenceDate,
|
||||
year, quarter, quarter, referenceDate,
|
||||
ipiData.total_weight, ipiData.weighted_index_sum,
|
||||
ipiData.manufacturing_index, momChange, yoyChange , new Date()
|
||||
ipiData.manufacturing_index, qoqChange, yoyChange , new Date()
|
||||
]
|
||||
);
|
||||
|
||||
|
||||
// Update log
|
||||
await this.logRecord('manufacturing_ipi', year, month, 'Completed', logId, result.length, null,connection);
|
||||
await this.logRecord('manufacturing_ipi', year, quarter, '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 - QoQ: ${qoqChange ? qoqChange.toFixed(2) + '%' : 'N/A'}`);
|
||||
logger.info(`calculateManufacturingIPI - YoY: ${yoyChange ? yoyChange.toFixed(2) + '%' : 'N/A'}`);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
index: ipiData.manufacturing_index,
|
||||
momChange,
|
||||
qoqChange,
|
||||
momChange: qoqChange,
|
||||
yoyChange
|
||||
};
|
||||
|
||||
@ -645,7 +620,7 @@ class IPICalculationService {
|
||||
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);
|
||||
await this.logRecord('manufacturing_ipi', year, quarter, 'Failed', logId, 0, error.message);
|
||||
}
|
||||
throw error;
|
||||
} finally {
|
||||
@ -655,28 +630,28 @@ class IPICalculationService {
|
||||
|
||||
|
||||
// MASTER FUNCTION: Run Complete Calculation Pipeline
|
||||
async runCompleteCalculation(year, month) {
|
||||
async runCompleteCalculation(year, quarter) {
|
||||
|
||||
logger.info(`**************************** Starting IPI Calculation for ${year}-${month} ****************************`);
|
||||
logger.info(`**************************** Starting IPI Calculation for ${year}-${quarter} ****************************`);
|
||||
|
||||
try {
|
||||
// Step 1: Aggregate monthly production
|
||||
await this.aggregateMonthlyProduction(year, month);
|
||||
// Step 1: Aggregate quarterly production
|
||||
await this.aggregateQuarterlyProduction(year, quarter);
|
||||
|
||||
// Step 2: Calculate item level indices
|
||||
await this.calculateItemLevelIndices(year, month);
|
||||
await this.calculateItemLevelIndices(year, quarter);
|
||||
|
||||
// Step 3: Calculate ISIC 4-digit indices
|
||||
await this.calculateISIC4DigitIndices(year, month);
|
||||
await this.calculateISIC4DigitIndices(year, quarter);
|
||||
|
||||
// Step 4: Calculate ISIC 3-digit indices
|
||||
await this.calculateISIC3DigitIndices(year, month);
|
||||
await this.calculateISIC3DigitIndices(year, quarter);
|
||||
|
||||
// Step 5: Calculate ISIC 2-digit indices
|
||||
await this.calculateISIC2DigitIndices(year, month);
|
||||
await this.calculateISIC2DigitIndices(year, quarter);
|
||||
|
||||
// Step 6: Calculate Manufacturing IPI
|
||||
const result = await this.calculateManufacturingIPI(year, month);
|
||||
const result = await this.calculateManufacturingIPI(year, quarter);
|
||||
|
||||
logger.info(`**************************** IPI Calculation Completed Successfully ****************************`);
|
||||
|
||||
@ -746,7 +721,7 @@ async logRecord(calculation_type, reference_year, reference_month = null, status
|
||||
product_id,
|
||||
product_hs_code,
|
||||
avg_by_production,
|
||||
jan, feb, mar, apr, may, jun, jul, aug, sep, oct, nov, \`dec\`
|
||||
q1, q2, q3, q4
|
||||
FROM base_year_production
|
||||
WHERE base_year = ?
|
||||
ORDER BY product_id
|
||||
@ -780,46 +755,46 @@ async logRecord(calculation_type, reference_year, reference_month = null, status
|
||||
|
||||
|
||||
// HELPER: Clear All Calculated Data for a Period
|
||||
async clearCalculatedData(year, month) {
|
||||
async clearCalculatedData(year, quarter) {
|
||||
const connection = await this.pool.getConnection();
|
||||
|
||||
try {
|
||||
await connection.beginTransaction();
|
||||
|
||||
logger.info(` Clearing calculated data for ${year}-${month} `);
|
||||
logger.info(` Clearing calculated data for ${year}-${quarter} `);
|
||||
|
||||
await connection.query(
|
||||
`DELETE FROM monthly_production WHERE year = ? AND month = ?`,
|
||||
[year, month]
|
||||
`DELETE FROM monthly_production WHERE year = ? AND quarter = ?`,
|
||||
[year, quarter]
|
||||
);
|
||||
|
||||
await connection.query(
|
||||
`DELETE FROM item_level_indices WHERE year = ? AND month = ?`,
|
||||
[year, month]
|
||||
`DELETE FROM item_level_indices WHERE year = ? AND quarter = ?`,
|
||||
[year, quarter]
|
||||
);
|
||||
|
||||
await connection.query(
|
||||
`DELETE FROM isic_4digit_indices WHERE year = ? AND month = ?`,
|
||||
[year, month]
|
||||
`DELETE FROM isic_4digit_indices WHERE year = ? AND quarter = ?`,
|
||||
[year, quarter]
|
||||
);
|
||||
|
||||
await connection.query(
|
||||
`DELETE FROM isic_3digit_indices WHERE year = ? AND month = ?`,
|
||||
[year, month]
|
||||
`DELETE FROM isic_3digit_indices WHERE year = ? AND quarter = ?`,
|
||||
[year, quarter]
|
||||
);
|
||||
|
||||
await connection.query(
|
||||
`DELETE FROM isic_2digit_indices WHERE year = ? AND month = ?`,
|
||||
[year, month]
|
||||
`DELETE FROM isic_2digit_indices WHERE year = ? AND quarter = ?`,
|
||||
[year, quarter]
|
||||
);
|
||||
|
||||
await connection.query(
|
||||
`DELETE FROM manufacturing_ipi WHERE year = ? AND month = ?`,
|
||||
[year, month]
|
||||
`DELETE FROM manufacturing_ipi WHERE year = ? AND quarter = ?`,
|
||||
[year, quarter]
|
||||
);
|
||||
|
||||
await connection.commit();
|
||||
logger.info(` Data cleared for ${year}-${month} `);
|
||||
logger.info(` Data cleared for ${year}-${quarter} `);
|
||||
|
||||
return { success: true };
|
||||
|
||||
|
||||
@ -107,31 +107,12 @@ class AutomatedSchedulerService {
|
||||
console.log(`[${new Date().toISOString()}] Starting calculate_quarter for ${year} ${quarter}`);
|
||||
|
||||
try {
|
||||
// Quarter to month mapping
|
||||
const quarterMap = {
|
||||
Q1: { startMonth: 1, endMonth: 3 },
|
||||
Q2: { startMonth: 4, endMonth: 6 },
|
||||
Q3: { startMonth: 7, endMonth: 9 },
|
||||
Q4: { startMonth: 10, endMonth: 12 },
|
||||
};
|
||||
|
||||
const selectedQuarter = quarterMap[quarter];
|
||||
|
||||
if (!selectedQuarter) {
|
||||
if (!['Q1', 'Q2', 'Q3', 'Q4'].includes(quarter)) {
|
||||
throw new Error('Invalid quarter. Accepted values: Q1, Q2, Q3, Q4');
|
||||
}
|
||||
|
||||
const { startMonth, endMonth } = selectedQuarter;
|
||||
const service = new IPICalculationService(dbConfig);
|
||||
const results = [];
|
||||
|
||||
for (let month = startMonth; month <= endMonth; month++) {
|
||||
const result = await service.runCompleteCalculation(year, month);
|
||||
results.push({
|
||||
month,
|
||||
...result
|
||||
});
|
||||
}
|
||||
const result = await service.runCompleteCalculation(year, quarter);
|
||||
|
||||
await service.close();
|
||||
|
||||
@ -139,8 +120,8 @@ class AutomatedSchedulerService {
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: `IPI calculated for ${year} months ${startMonth}-${endMonth}`,
|
||||
data: results
|
||||
message: `IPI calculated for ${year} ${quarter}`,
|
||||
data: result
|
||||
};
|
||||
} catch (error) {
|
||||
console.error(`[${new Date().toISOString()}] Error in calculate_quarter:`, error);
|
||||
|
||||
@ -44,7 +44,7 @@
|
||||
<div class="wrap">
|
||||
<div class="card">
|
||||
<h2 style="margin:0 0 8px 0;">IPI Calculation Demo Dashboard</h2>
|
||||
<div class="muted">Use Swagger to trigger calculation first, then view data here by year/month.</div>
|
||||
<div class="muted">Use Swagger to trigger calculation first, then view data by year + month (month is mapped to quarter).</div>
|
||||
<div class="row" style="margin-top:12px;">
|
||||
<label>Year <input id="year" type="number" value="<%= defaultYear %>" min="2000" max="2100" /></label>
|
||||
<label>Month <input id="month" type="number" value="<%= defaultMonth %>" min="1" max="12" /></label>
|
||||
@ -252,8 +252,8 @@
|
||||
{ key: "product_id", label: "Product ID" },
|
||||
{ key: "product_hs_code", label: "HS Code" },
|
||||
{ key: "year", label: "Year" },
|
||||
{ key: "month", label: "Month" },
|
||||
{ key: "month_name", label: "Month Name" },
|
||||
{ key: "quarter", label: "Quarter" },
|
||||
{ key: "quarter_name", label: "Quarter Name" },
|
||||
{ key: "production_quantity", label: "Production Qty" },
|
||||
{ key: "unit", label: "Unit" },
|
||||
];
|
||||
@ -264,10 +264,10 @@
|
||||
{ key: "product_hs_code", label: "HS Code" },
|
||||
{ key: "base_year", label: "Base Year" },
|
||||
{ key: "avg_by_production", label: "Avg Base Year Prod." },
|
||||
{ key: "jan", label: "Jan" }, { key: "feb", label: "Feb" }, { key: "mar", label: "Mar" },
|
||||
{ key: "apr", label: "Apr" }, { key: "may", label: "May" }, { key: "jun", label: "Jun" },
|
||||
{ key: "jul", label: "Jul" }, { key: "aug", label: "Aug" }, { key: "sep", label: "Sep" },
|
||||
{ key: "oct", label: "Oct" }, { key: "nov", label: "Nov" }, { key: "dec", label: "Dec" },
|
||||
{ key: "q1", label: "Q1" },
|
||||
{ key: "q2", label: "Q2" },
|
||||
{ key: "q3", label: "Q3" },
|
||||
{ key: "q4", label: "Q4" },
|
||||
];
|
||||
}
|
||||
return [
|
||||
|
||||
2792
ipidev_correctly_working_sample.sql
Normal file
2792
ipidev_correctly_working_sample.sql
Normal file
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue
Block a user