179 lines
6.8 KiB
JavaScript
179 lines
6.8 KiB
JavaScript
const express = require('express');
|
|
const { Pool } = require('pg');
|
|
const cors = require('cors');
|
|
require('dotenv').config();
|
|
|
|
const app = express();
|
|
app.use(cors());
|
|
app.use(express.json());
|
|
|
|
const pool = new Pool({
|
|
user: process.env.DB_USER || 'admin',
|
|
host: process.env.DB_HOST || 'localhost',
|
|
database: process.env.DB_NAME || 'sop_monitoring',
|
|
password: process.env.DB_PASS || 'password',
|
|
port: process.env.DB_PORT || 5432,
|
|
});
|
|
|
|
app.get('/api/changeovers', async (req, res) => {
|
|
try {
|
|
const result = await pool.query('SELECT DISTINCT changeover_id, changeover_date FROM events ORDER BY changeover_date DESC');
|
|
res.json(result.rows);
|
|
} catch (err) {
|
|
console.error(err);
|
|
res.status(500).json({ error: 'Internal Server Error' });
|
|
}
|
|
});
|
|
|
|
app.get('/api/events', async (req, res) => {
|
|
try {
|
|
const { camera_zone, state, changeover_id } = req.query;
|
|
let query = 'SELECT * FROM events WHERE 1=1';
|
|
let values = [];
|
|
|
|
if (camera_zone) {
|
|
values.push(camera_zone);
|
|
query += ` AND camera_zone = $${values.length}`;
|
|
}
|
|
if (state) {
|
|
values.push(state);
|
|
query += ` AND state = $${values.length}`;
|
|
}
|
|
if (changeover_id) {
|
|
values.push(changeover_id);
|
|
query += ` AND changeover_id = $${values.length}`;
|
|
}
|
|
query += ' ORDER BY start_time ASC';
|
|
|
|
const result = await pool.query(query, values);
|
|
res.json(result.rows);
|
|
} catch (err) {
|
|
console.error(err);
|
|
res.status(500).json({ error: 'Internal Server Error' });
|
|
}
|
|
});
|
|
|
|
app.get('/api/analytics/idle-time', async (req, res) => {
|
|
try {
|
|
const { changeover_id } = req.query;
|
|
let filter = changeover_id ? `AND changeover_id = $1` : '';
|
|
let params = changeover_id ? [changeover_id] : [];
|
|
|
|
// 1. CO Idle Time: Sum of all events marked Waste in CO
|
|
const coResult = await pool.query(`
|
|
SELECT SUM(state_duration_seconds) as total_idle
|
|
FROM events
|
|
WHERE camera_zone = 'CO' AND state = 'Waste' ${filter}
|
|
`, params);
|
|
const coIdle = parseInt(coResult.rows[0].total_idle || 0);
|
|
|
|
// 2. Mill Idle Time: Sum of Waste in Mill + specific Mill gap/idle
|
|
const millResult = await pool.query(`
|
|
SELECT SUM(state_duration_seconds) as total_idle
|
|
FROM events
|
|
WHERE camera_zone = 'Mill' AND (state = 'Waste' OR state = 'Idle') ${filter}
|
|
`, params);
|
|
const millIdle = parseInt(millResult.rows[0].total_idle || 0);
|
|
|
|
// 3. Conveyor Idle Time: Sum of No Load (Waste) + Load waiting before start
|
|
const conveyorResult = await pool.query(`
|
|
SELECT SUM(state_duration_seconds) as total_idle
|
|
FROM events
|
|
WHERE camera_zone = 'Conveyor' AND (state = 'Waste' OR state = 'Load waiting') ${filter}
|
|
`, params);
|
|
const conveyorIdle = parseInt(conveyorResult.rows[0].total_idle || 0);
|
|
|
|
res.json({
|
|
CO: coIdle,
|
|
Mill: millIdle,
|
|
Conveyor: conveyorIdle
|
|
});
|
|
} catch (err) {
|
|
console.error(err);
|
|
res.status(500).json({ error: 'Internal Server Error' });
|
|
}
|
|
});
|
|
|
|
app.get('/api/analytics/bottlenecks', async (req, res) => {
|
|
try {
|
|
const { changeover_id } = req.query;
|
|
let filter = changeover_id ? `AND changeover_id = $1` : '';
|
|
let params = changeover_id ? [changeover_id] : [];
|
|
|
|
const result = await pool.query(`
|
|
SELECT event_description, camera_zone, SUM(state_duration_seconds) as total_duration, COUNT(*) as occurrences
|
|
FROM events
|
|
WHERE state IN ('Waste', 'Idle', 'Load waiting') ${filter}
|
|
GROUP BY event_description, camera_zone
|
|
ORDER BY total_duration DESC
|
|
LIMIT 10
|
|
`, params);
|
|
res.json(result.rows);
|
|
} catch (err) {
|
|
console.error(err);
|
|
res.status(500).json({ error: 'Internal Server Error' });
|
|
}
|
|
});
|
|
|
|
app.get('/api/analytics/sop-compliance', async (req, res) => {
|
|
try {
|
|
const { changeover_id } = req.query;
|
|
let filter = changeover_id ? `AND changeover_id = $1` : '';
|
|
let params = changeover_id ? [changeover_id] : [];
|
|
|
|
// 1. Conveyor must be started 15 mins before changeover
|
|
const coStartResult = await pool.query(`SELECT start_time FROM events WHERE camera_zone = 'CO' ${filter} ORDER BY start_time ASC LIMIT 1`, params);
|
|
const convStartResult = await pool.query(`SELECT start_time FROM events WHERE camera_zone = 'Conveyor' AND (event_description ILIKE '%Started%' OR event_description ILIKE '%Active%') ${filter} ORDER BY start_time ASC LIMIT 1`, params);
|
|
|
|
let conveyorCompliance = { rule: "Conveyor started 15 min before CO", status: "Unknown", detail: "" };
|
|
if (coStartResult.rows.length > 0 && convStartResult.rows.length > 0) {
|
|
const coTime = coStartResult.rows[0].start_time;
|
|
const convTime = convStartResult.rows[0].start_time;
|
|
// Compare times (assuming same day)
|
|
const coDate = new Date(`1970-01-01T${coTime}Z`);
|
|
const convDate = new Date(`1970-01-01T${convTime}Z`);
|
|
const diffMins = (coDate - convDate) / 60000;
|
|
|
|
if (diffMins >= 15) {
|
|
conveyorCompliance.status = "Pass";
|
|
conveyorCompliance.detail = `Started ${diffMins.toFixed(1)} mins before CO`;
|
|
} else {
|
|
conveyorCompliance.status = "Fail";
|
|
if (diffMins > 0) conveyorCompliance.detail = `Started only ${diffMins.toFixed(1)} mins before CO`;
|
|
else conveyorCompliance.detail = `Started ${Math.abs(diffMins).toFixed(1)} mins AFTER CO`;
|
|
}
|
|
}
|
|
|
|
// 2. Mill should be completed within 15 mins after pushout rubber collection
|
|
const pushoutResult = await pool.query(`SELECT end_time FROM events WHERE event_description ILIKE '%Pushout of Rubber - 4%' ${filter} ORDER BY end_time DESC LIMIT 1`, params);
|
|
const millCompleteResult = await pool.query(`SELECT end_time FROM events WHERE camera_zone = 'Mill' AND event_description ILIKE '%Sheeting Out - 3%' ${filter} ORDER BY end_time DESC LIMIT 1`, params);
|
|
|
|
let millCompliance = { rule: "Mill completed within 15 min of Pushout", status: "Unknown", detail: "" };
|
|
if (pushoutResult.rows.length > 0 && millCompleteResult.rows.length > 0) {
|
|
const pushoutTime = pushoutResult.rows[0].end_time;
|
|
const millTime = millCompleteResult.rows[0].end_time;
|
|
const pushoutDate = new Date(`1970-01-01T${pushoutTime}Z`);
|
|
const millDate = new Date(`1970-01-01T${millTime}Z`);
|
|
const diffMins = (millDate - pushoutDate) / 60000;
|
|
|
|
if (diffMins <= 15 && diffMins >= 0) {
|
|
millCompliance.status = "Pass";
|
|
millCompliance.detail = `Completed in ${diffMins.toFixed(1)} mins`;
|
|
} else {
|
|
millCompliance.status = "Fail";
|
|
millCompliance.detail = `Completed in ${diffMins.toFixed(1)} mins (limit 15)`;
|
|
}
|
|
}
|
|
|
|
res.json([conveyorCompliance, millCompliance]);
|
|
} catch (err) {
|
|
console.error(err);
|
|
res.status(500).json({ error: 'Internal Server Error' });
|
|
}
|
|
});
|
|
|
|
const PORT = process.env.PORT || 3000;
|
|
app.listen(PORT, () => {
|
|
console.log(`Server running on port ${PORT}`);
|
|
});
|