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}`); });