/** * ALWAS ML Routes — Drop-in Express.js routes * Add to your server/routes/ directory and mount in app.js * * Mount: app.use('/api/ml', require('./routes/ml')); */ const express = require('express'); const router = express.Router(); const ml = require('../utils/alwas-ml-client'); // adjust path as needed const Block = require('../models/Block'); const { isAuthenticated, isManager } = require('../middleware/auth'); /** * POST /api/ml/estimate * AI-powered block complexity estimation (replaces Groq API) * Access: Manager */ router.post('/estimate', isAuthenticated, isManager, async (req, res) => { try { const estimate = await ml.estimateBlock(req.body); res.json(estimate); } catch (error) { console.error('ML estimation error:', error.message); res.status(500).json({ error: 'ML estimation failed', details: error.message }); } }); /** * GET /api/ml/block/:id/risk * Get bottleneck risk assessment for a specific block * Access: User */ router.get('/block/:id/risk', isAuthenticated, async (req, res) => { try { const block = await Block.findById(req.params.id); if (!block) return res.status(404).json({ error: 'Block not found' }); // Calculate days in current stage const lastTransition = block.transitions?.[block.transitions.length - 1]; const daysSince = lastTransition ? (Date.now() - new Date(lastTransition.timestamp)) / (1000 * 60 * 60 * 24) : 0; const riskData = ml.constructor.formatForBottleneck(block, daysSince); const risk = await ml.predictBottleneck(riskData); res.json({ blockId: block._id, blockName: block.name, ...risk }); } catch (error) { console.error('ML risk prediction error:', error.message); res.status(500).json({ error: 'Risk prediction failed', details: error.message }); } }); /** * GET /api/ml/block/:id/eta * Get estimated completion time for a specific block * Access: User */ router.get('/block/:id/eta', isAuthenticated, async (req, res) => { try { const block = await Block.findById(req.params.id); if (!block) return res.status(404).json({ error: 'Block not found' }); if (block.status === 'Completed') { return res.json({ remaining_hours: 0, progress_percent: 100, status: 'completed' }); } const completionData = ml.constructor.formatForCompletion(block); const eta = await ml.predictCompletion(completionData); res.json({ blockId: block._id, blockName: block.name, ...eta }); } catch (error) { console.error('ML completion prediction error:', error.message); res.status(500).json({ error: 'Completion prediction failed', details: error.message }); } }); /** * POST /api/ml/bulk-estimate * Bulk estimation for CSV import * Access: Manager */ router.post('/bulk-estimate', isAuthenticated, isManager, async (req, res) => { try { const { blocks } = req.body; if (!blocks || !Array.isArray(blocks)) { return res.status(400).json({ error: 'blocks array required' }); } if (blocks.length > 200) { return res.status(400).json({ error: 'Maximum 200 blocks per request' }); } const estimates = await ml.bulkEstimate(blocks); res.json(estimates); } catch (error) { console.error('ML bulk estimation error:', error.message); res.status(500).json({ error: 'Bulk estimation failed', details: error.message }); } }); /** * GET /api/ml/scan/bottlenecks * Scan ALL in-progress blocks for bottleneck risks * Access: Manager */ router.get('/scan/bottlenecks', isAuthenticated, isManager, async (req, res) => { try { const blocks = await Block.find({ status: { $nin: ['Not Started', 'Completed'] } }); const results = []; for (const block of blocks) { const lastTransition = block.transitions?.[block.transitions.length - 1]; const daysSince = lastTransition ? (Date.now() - new Date(lastTransition.timestamp)) / (1000 * 60 * 60 * 24) : 0; const riskData = ml.constructor.formatForBottleneck(block, daysSince); const risk = await ml.predictBottleneck(riskData); if (risk.risk_level !== 'Low') { results.push({ blockId: block._id, blockName: block.name, stage: block.status, engineer: block.assignedTo, ...risk, }); } } // Sort by risk (High first) results.sort((a, b) => { const order = { High: 0, Medium: 1, Low: 2 }; return (order[a.risk_level] || 2) - (order[b.risk_level] || 2); }); res.json({ total_scanned: blocks.length, at_risk: results.length, high_risk: results.filter(r => r.risk_level === 'High').length, medium_risk: results.filter(r => r.risk_level === 'Medium').length, blocks: results }); } catch (error) { console.error('ML bottleneck scan error:', error.message); res.status(500).json({ error: 'Bottleneck scan failed', details: error.message }); } }); /** * GET /api/ml/health * ML service health check */ router.get('/health', async (req, res) => { try { const health = await ml.healthCheck(); res.json(health); } catch (error) { res.status(503).json({ status: 'unhealthy', error: error.message }); } }); /** * GET /api/ml/metrics * Get model performance metrics * Access: Manager */ router.get('/metrics', isAuthenticated, isManager, async (req, res) => { try { const metrics = await ml.getMetrics(); res.json(metrics); } catch (error) { res.status(500).json({ error: 'Failed to fetch metrics', details: error.message }); } }); module.exports = router;