IIP calculation corrected : GWM

This commit is contained in:
Gowtham M 2026-04-16 14:31:48 +05:30
parent 7608e93334
commit c3cac46cce
3 changed files with 209 additions and 0 deletions

View File

@ -1,4 +1,5 @@
const db = require("../models");
const IPICalculationService = require("../services/ipi_calculation_service");
const ManufacturingIpi = db.ManufacturingIpi;
const Isic2DigitIndices = db.Isic2DigitIndices;
@ -6,6 +7,29 @@ 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);
@ -217,3 +241,93 @@ exports.getTableData = async (req, res) => {
}
};
exports.runMonthCalculation = async (req, res) => {
try {
const year = Number(req.body.year);
const month = Number(req.body.month);
if (!year || !month || month < 1 || month > 12) {
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, month);
await service.close();
return res.json({
success: true,
message: `Calculation completed for ${year}-${String(month).padStart(2, "0")}`,
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);
if (!year || !month || month < 1 || month > 12) {
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: month,
},
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,
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,
});
}
};

View File

@ -28,6 +28,16 @@
.panel.active { display: block; }
.pager { display: flex; gap: 8px; align-items: center; margin: 10px 0; flex-wrap: wrap; }
.pager input { width: 220px; }
.run-monitor { margin-top: 12px; border: 1px solid #e5e7eb; border-radius: 8px; padding: 12px; background: #f8fafc; }
.progress { width: 100%; height: 10px; background: #e5e7eb; border-radius: 999px; overflow: hidden; margin: 10px 0; }
.progress > div { height: 100%; background: #2563eb; width: 0%; transition: width 0.3s ease; }
.step-grid { display: grid; grid-template-columns: repeat(3, minmax(220px, 1fr)); gap: 8px; }
.step-card { border: 1px solid #d1d5db; border-radius: 6px; padding: 8px; background: #fff; }
.badge { display: inline-block; font-size: 11px; padding: 2px 6px; border-radius: 999px; }
.badge-pending { background: #f3f4f6; color: #374151; }
.badge-started { background: #dbeafe; color: #1d4ed8; }
.badge-completed { background: #dcfce7; color: #166534; }
.badge-failed { background: #fee2e2; color: #991b1b; }
</style>
</head>
<body>
@ -39,8 +49,18 @@
<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>
<button id="loadBtn">Load Data</button>
<button id="runBtn">Run Month Calculation</button>
<span id="status" class="muted"></span>
</div>
<div class="run-monitor">
<div class="row" style="justify-content: space-between;">
<b>Live Run Monitor</b>
<span id="runState" class="muted">Idle</span>
</div>
<div class="progress"><div id="runProgress"></div></div>
<div id="stepGrid" class="step-grid"></div>
</div>
</div>
<div class="card">
@ -96,6 +116,7 @@
items: { page: 1, pageSize: 50, total: 0, search: "" },
};
let activeTab = "isic4";
let pollTimer = null;
function tableHtml(columns, rows) {
if (!rows || rows.length === 0) return '<div class="muted">No rows found.</div>';
@ -179,6 +200,52 @@
refreshTableControls();
}
function statusBadge(status) {
const s = String(status || "Pending");
const lower = s.toLowerCase();
if (lower === "completed") return `<span class="badge badge-completed">Completed</span>`;
if (lower === "started") return `<span class="badge badge-started">Started</span>`;
if (lower === "failed") return `<span class="badge badge-failed">Failed</span>`;
return `<span class="badge badge-pending">Pending</span>`;
}
function renderRunStatus(data) {
document.getElementById("runProgress").style.width = `${data.progressPercent || 0}%`;
const state = data.isFinished
? (data.failedStep ? `Failed at ${data.failedStep}` : "Completed")
: (data.started ? "Running..." : "Idle");
document.getElementById("runState").textContent = `${state} (${data.completedCount || 0}/${data.totalSteps || 6})`;
const html = (data.steps || []).map((s) => `
<div class="step-card">
<div><b>${s.step}</b> ${statusBadge(s.status)}</div>
<div class="muted">Records: ${s.records_processed ?? 0}</div>
<div class="muted">Started: ${s.started_at || "-"}</div>
<div class="muted">Completed: ${s.completed_at || "-"}</div>
${s.error_message ? `<div class="err">Error: ${s.error_message}</div>` : ""}
</div>
`).join("");
document.getElementById("stepGrid").innerHTML = html || '<div class="muted">No run status yet.</div>';
}
async function fetchRunStatus() {
const year = document.getElementById("year").value;
const month = document.getElementById("month").value;
try {
const resp = await fetch(`/ipi-demo/run-status?year=${encodeURIComponent(year)}&month=${encodeURIComponent(month)}`);
const data = await resp.json();
if (!resp.ok || !data.success) throw new Error(data.message || "Status fetch failed");
renderRunStatus(data);
if (data.isFinished && pollTimer) {
clearInterval(pollTimer);
pollTimer = null;
loadData();
}
} catch (err) {
document.getElementById("runState").textContent = err.message;
}
}
function getColumnsForTable(tab) {
if (tab === "monthly") {
return [
@ -264,6 +331,30 @@
}
document.getElementById("loadBtn").addEventListener("click", loadData);
document.getElementById("runBtn").addEventListener("click", async () => {
const year = Number(document.getElementById("year").value);
const month = Number(document.getElementById("month").value);
statusEl.textContent = "Starting run...";
statusEl.className = "muted";
if (pollTimer) clearInterval(pollTimer);
pollTimer = setInterval(fetchRunStatus, 2500);
try {
const resp = await fetch("/ipi-demo/run-month", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ year, month }),
});
const data = await resp.json();
if (!resp.ok || !data.success) throw new Error(data.message || "Run failed");
statusEl.textContent = "Run completed";
statusEl.className = "ok";
await fetchRunStatus();
await loadData();
} catch (err) {
statusEl.textContent = err.message;
statusEl.className = "err";
}
});
document.querySelectorAll(".tab").forEach((btn) => {
btn.addEventListener("click", () => {
document.querySelectorAll(".tab").forEach((b) => b.classList.remove("active"));
@ -302,6 +393,8 @@
});
loadData();
fetchRunStatus();
pollTimer = setInterval(fetchRunStatus, 2500);
</script>
</body>
</html>

View File

@ -308,6 +308,8 @@ app.post("/deploy", deploymentController.deployment);
app.get("/ipi-demo", ipiDemoController.renderPage);
app.get("/ipi-demo/data", ipiDemoController.getData);
app.get("/ipi-demo/table-data", ipiDemoController.getTableData);
app.get("/ipi-demo/run-status", ipiDemoController.getRunStatus);
app.post("/ipi-demo/run-month", ipiDemoController.runMonthCalculation);
/**