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