| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| const ml = require('../utils/alwas-ml-client'); |
| const Block = require('../models/Block'); |
| const Notification = require('../models/Notification'); |
| const User = require('../models/User'); |
|
|
| async function mlBottleneckScanner(io) { |
| console.log('[ML Scanner] Starting hourly bottleneck scan...'); |
| |
| try { |
| |
| const blocks = await Block.find({ |
| status: { $nin: ['Not Started', 'Completed'] } |
| }).populate('assignedTo'); |
|
|
| let highRisk = 0; |
| let mediumRisk = 0; |
| let alerts = []; |
|
|
| for (const block of blocks) { |
| try { |
| |
| 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 === 'High') { |
| highRisk++; |
| |
| |
| if (block.assignedTo) { |
| const notification = await Notification.create({ |
| user: block.assignedTo._id || block.assignedTo, |
| type: 'stuck', |
| message: `⚠️ ML Alert: ${block.name} has HIGH bottleneck risk`, |
| data: { |
| blockId: block._id, |
| risk: risk.risk_level, |
| confidence: risk.confidence, |
| recommendations: risk.recommendations, |
| hours_over_budget: risk.hours_over_budget_ratio, |
| } |
| }); |
|
|
| |
| io.emit('newNotification', { |
| userId: block.assignedTo._id || block.assignedTo, |
| notification: notification, |
| }); |
| } |
|
|
| |
| const managers = await User.find({ role: 'manager' }); |
| for (const manager of managers) { |
| await Notification.create({ |
| user: manager._id, |
| type: 'stuck', |
| message: `🔴 ML Bottleneck Alert: ${block.name} (${block.status}) — ${risk.recommendations[0] || 'High risk detected'}`, |
| data: { |
| blockId: block._id, |
| risk: risk.risk_level, |
| confidence: risk.confidence, |
| recommendations: risk.recommendations, |
| engineer: block.assignedTo?.name || 'Unassigned', |
| } |
| }); |
| } |
|
|
| alerts.push({ |
| block: block.name, |
| stage: block.status, |
| risk: risk.risk_level, |
| confidence: risk.confidence, |
| reason: risk.recommendations[0] || 'High risk', |
| }); |
|
|
| } else if (risk.risk_level === 'Medium') { |
| mediumRisk++; |
| } |
| } catch (blockError) { |
| console.error(`[ML Scanner] Error scanning block ${block._id}:`, blockError.message); |
| } |
| } |
|
|
| console.log(`[ML Scanner] Scan complete: ${blocks.length} blocks scanned, ${highRisk} high risk, ${mediumRisk} medium risk`); |
| |
| if (alerts.length > 0) { |
| console.log('[ML Scanner] High-risk blocks:'); |
| alerts.forEach(a => console.log(` - ${a.block} (${a.stage}): ${a.reason}`)); |
| } |
|
|
| return { scanned: blocks.length, highRisk, mediumRisk, alerts }; |
|
|
| } catch (error) { |
| console.error('[ML Scanner] Fatal error:', error.message); |
| return { error: error.message }; |
| } |
| } |
|
|
| module.exports = mlBottleneckScanner; |
|
|