From c3cac46cceb842d8821abfffd6d8fcc474176ef5 Mon Sep 17 00:00:00 2001 From: Gowtham M Date: Thu, 16 Apr 2026 14:31:48 +0530 Subject: [PATCH] IIP calculation corrected : GWM --- app/controllers/ipiDemo.controller.js | 114 ++++++++++++++++++++++++++ app/views/ipi-demo.ejs | 93 +++++++++++++++++++++ server.js | 2 + 3 files changed, 209 insertions(+) diff --git a/app/controllers/ipiDemo.controller.js b/app/controllers/ipiDemo.controller.js index b7862d4..dc563fc 100644 --- a/app/controllers/ipiDemo.controller.js +++ b/app/controllers/ipiDemo.controller.js @@ -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, + }); + } +}; + diff --git a/app/views/ipi-demo.ejs b/app/views/ipi-demo.ejs index f3876a7..471c108 100644 --- a/app/views/ipi-demo.ejs +++ b/app/views/ipi-demo.ejs @@ -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; } @@ -39,8 +49,18 @@ + + +
+
+ Live Run Monitor + Idle +
+
+
+
@@ -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 '
No rows found.
'; @@ -179,6 +200,52 @@ refreshTableControls(); } + function statusBadge(status) { + const s = String(status || "Pending"); + const lower = s.toLowerCase(); + if (lower === "completed") return `Completed`; + if (lower === "started") return `Started`; + if (lower === "failed") return `Failed`; + return `Pending`; + } + + 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) => ` +
+
${s.step} ${statusBadge(s.status)}
+
Records: ${s.records_processed ?? 0}
+
Started: ${s.started_at || "-"}
+
Completed: ${s.completed_at || "-"}
+ ${s.error_message ? `
Error: ${s.error_message}
` : ""} +
+ `).join(""); + document.getElementById("stepGrid").innerHTML = html || '
No run status yet.
'; + } + + 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); diff --git a/server.js b/server.js index 3ad69b2..a249a0b 100644 --- a/server.js +++ b/server.js @@ -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); /**