fcsc_ipi_backend/app/controllers/ipiDemo.controller.js
2026-04-29 10:39:44 +05:30

332 lines
11 KiB
JavaScript

const db = require("../models");
const IPICalculationService = require("../services/ipi_calculation_service");
const ManufacturingIpi = db.ManufacturingIpi;
const Isic2DigitIndices = db.Isic2DigitIndices;
const Isic3DigitIndices = db.Isic3DigitIndices;
const Isic4DigitIndices = db.Isic4DigitIndices;
const CalculationLog = db.CalculationLog;
const dbConfig = {
host: process.env.DB_HOST,
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME,
dialect: process.env.DB_DIALECT || "mysql",
pool: {
max: 5,
min: 0,
acquire: 30000,
idle: 10000,
},
};
const CALCULATION_STEPS = [
"monthly_production",
"item_level_indices",
"isic_4digit_indices",
"isic_3digit_indices",
"isic_2digit_indices",
"manufacturing_ipi",
];
const formatNumber = (value) => {
if (value === null || value === undefined || value === "") return null;
const parsed = Number(value);
if (Number.isNaN(parsed)) return null;
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();
const month = Number(req.query.month) || now.getMonth() + 1;
return res.render("ipi-demo", {
title: "IPI Calculation Demo Dashboard",
defaultYear: year,
defaultMonth: month,
});
};
exports.getData = async (req, res) => {
try {
const year = Number(req.query.year);
const month = Number(req.query.month);
if (!year || !month || month < 1 || month > 12) {
return res.status(400).json({
success: false,
message: "Valid year and month are required",
});
}
const quarter = monthToQuarter(month);
const whereClause = { year, quarter };
const [manufacturing, isic2, isic3, isic4, itemCount, logs] = await Promise.all([
ManufacturingIpi.findOne({ where: whereClause, raw: true }),
Isic2DigitIndices.findAll({ where: whereClause, order: [["isic_2digit_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 }),
db.sequelize.query(
`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: quarter },
order: [["started_at", "DESC"]],
raw: true,
})
]);
return res.json({
success: true,
year,
month,
quarter,
summary: {
item_count: Number(itemCount[0]?.total || 0),
isic4_count: isic4.length,
isic3_count: isic3.length,
isic2_count: isic2.length,
},
manufacturing: manufacturing
? {
...manufacturing,
total_weight: formatNumber(manufacturing.total_weight),
weighted_index_sum: formatNumber(manufacturing.weighted_index_sum),
manufacturing_index: formatNumber(manufacturing.manufacturing_index),
mom_change: formatNumber(manufacturing.mom_change),
yoy_change: formatNumber(manufacturing.yoy_change),
}
: null,
isic2: isic2.map((row) => ({
...row,
total_weight: formatNumber(row.total_weight),
weighted_index_sum: formatNumber(row.weighted_index_sum),
isic_2digit_index: formatNumber(row.isic_2digit_index),
})),
isic3: isic3.map((row) => ({
...row,
total_weight: formatNumber(row.total_weight),
weighted_index_sum: formatNumber(row.weighted_index_sum),
isic_3digit_index: formatNumber(row.isic_3digit_index),
})),
isic4: isic4.map((row) => ({
...row,
total_weight: formatNumber(row.total_weight),
weighted_index_sum: formatNumber(row.weighted_index_sum),
isic_4digit_index: formatNumber(row.isic_4digit_index),
})),
logs,
});
} catch (error) {
return res.status(500).json({
success: false,
message: "Failed to load IPI demo data",
error: error.message,
});
}
};
exports.getTableData = async (req, res) => {
try {
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));
const search = String(req.query.search || "").trim();
const offset = (page - 1) * pageSize;
const queryConfig = {
monthly: {
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 quarter = ?`,
dataSql: `SELECT product_id, product_hs_code, year, quarter, quarter_name, production_quantity, unit
FROM monthly_production
WHERE year = ? AND quarter = ?`,
orderSql: ` ORDER BY product_id`,
},
baseyear: {
whereSql: `base_year = ?`,
whereParams: [baseYear],
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, q1, q2, q3, q4
FROM base_year_production
WHERE base_year = ?`,
orderSql: ` ORDER BY product_id`,
},
items: {
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 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 quarter = ?`,
orderSql: ` ORDER BY product_id`,
},
};
if (!queryConfig[table]) {
return res.status(400).json({ success: false, message: "Invalid table parameter" });
}
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" });
}
const cfg = queryConfig[table];
const whereParams = [...cfg.whereParams, ...cfg.searchParams];
const [countResult] = await db.sequelize.query(
cfg.countSql + (search ? cfg.searchSql.replace(" AND ", " AND ") : ""),
{ replacements: whereParams, type: db.Sequelize.QueryTypes.SELECT }
);
const rows = await db.sequelize.query(
cfg.dataSql + cfg.searchSql + cfg.orderSql + " LIMIT ? OFFSET ?",
{ replacements: [...whereParams, pageSize, offset], type: db.Sequelize.QueryTypes.SELECT }
);
const formattedRows = rows.map((row) => {
const copy = { ...row };
Object.keys(copy).forEach((k) => {
if (
["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]);
}
});
return copy;
});
return res.json({
success: true,
table,
page,
pageSize,
total: Number(countResult.total || 0),
rows: formattedRows,
});
} catch (error) {
return res.status(500).json({
success: false,
message: "Failed to load table data",
error: error.message,
});
}
};
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 || !quarter) {
return res.status(400).json({
success: false,
message: "Valid year and month are required",
});
}
const service = new IPICalculationService(dbConfig);
const result = await service.runCompleteCalculation(year, quarter);
await service.close();
return res.json({
success: true,
message: `Calculation completed for ${year}-${quarter}`,
data: result,
});
} catch (error) {
return res.status(500).json({
success: false,
message: "Failed to run month calculation",
error: error.message,
});
}
};
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 || !quarter) {
return res.status(400).json({
success: false,
message: "Valid year and month are required",
});
}
const logs = await CalculationLog.findAll({
where: {
reference_year: year,
reference_month: quarter,
},
order: [["started_at", "ASC"]],
raw: true,
});
const map = new Map(logs.map((l) => [l.calculation_type, l]));
const steps = CALCULATION_STEPS.map((step) => {
const row = map.get(step);
return {
step,
status: row?.status || "Pending",
records_processed: row?.records_processed ?? 0,
started_at: row?.started_at || null,
completed_at: row?.completed_at || null,
error_message: row?.error_message || null,
};
});
const completedCount = steps.filter((s) => s.status === "Completed").length;
const failedStep = steps.find((s) => s.status === "Failed");
const startedAny = steps.some((s) => s.status !== "Pending");
const isFinished = completedCount === CALCULATION_STEPS.length || Boolean(failedStep);
return res.json({
success: true,
year,
month,
quarter,
started: startedAny,
isFinished,
completedCount,
totalSteps: CALCULATION_STEPS.length,
progressPercent: Math.round((completedCount / CALCULATION_STEPS.length) * 100),
failedStep: failedStep?.step || null,
steps,
});
} catch (error) {
return res.status(500).json({
success: false,
message: "Failed to fetch run status",
error: error.message,
});
}
};