/** * ALWAS ML Client — Drop-in integration for Express.js backend * Replaces Groq API with local ML models for complexity estimation, * bottleneck detection, and completion time prediction. * * Usage: * const ml = require('./alwas-ml-client'); * const estimate = await ml.estimateBlock({ block_type: 'PLL', tech_node: '7nm', ... }); */ const ML_API_URL = process.env.ML_API_URL || 'http://localhost:7860'; class ALWASMLClient { constructor(baseUrl = ML_API_URL) { this.baseUrl = baseUrl; } async _post(endpoint, data) { const response = await fetch(`${this.baseUrl}${endpoint}`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data), }); if (!response.ok) { throw new Error(`ML API error: ${response.status} ${await response.text()}`); } return response.json(); } async _get(endpoint) { const response = await fetch(`${this.baseUrl}${endpoint}`); if (!response.ok) { throw new Error(`ML API error: ${response.status}`); } return response.json(); } /** * Estimate complexity and hours for a new block. * DIRECT REPLACEMENT for Groq AI estimation endpoint. * * @param {Object} block - Block metadata * @param {string} block.block_type - e.g., 'ADC', 'PLL', 'LDO' * @param {string} block.tech_node - e.g., '7nm', '28nm' * @param {string} [block.priority='P3-Medium'] - Priority level * @param {number} [block.transistor_count] - Estimated transistor count * @param {boolean} [block.has_dependencies=false] * @param {number} [block.num_dependencies=0] * @param {number} [block.constraint_complexity=1.0] - 0-3 scale * @param {number} [block.drc_iterations=2] * @param {number} [block.engineer_skill_factor=1.0] - 0.5-1.5 * @returns {Object} { complexity, estimated_hours, confidence, reasoning, ... } */ async estimateBlock(block) { return this._post('/predict/estimate', block); } /** * Predict bottleneck risk for an in-progress block. * Use in the hourly cron bottleneck scanner. * * @param {Object} block - Current block state * @returns {Object} { risk_level, confidence, recommendations, should_alert } */ async predictBottleneck(block) { return this._post('/predict/bottleneck', block); } /** * Predict remaining time to completion. * Use in Block Detail page for "Est. Completion" row. * * @param {Object} block - Block with progress info * @returns {Object} { remaining_hours, remaining_days, estimated_completion_date, progress_percent } */ async predictCompletion(block) { return this._post('/predict/completion', block); } /** * Bulk estimate multiple blocks at once. * Use with CSV bulk import feature. * * @param {Array} blocks - Array of block metadata * @returns {Object} { count, estimates, total_estimated_hours } */ async bulkEstimate(blocks) { return this._post('/predict/bulk-estimate', { blocks }); } /** * Get model performance metrics. * @returns {Object} metrics for all 4 models */ async getMetrics() { return this._get('/model/metrics'); } /** * Get supported block types, tech nodes, priorities. * @returns {Object} { tech_nodes, block_types, priorities, stages } */ async getSupportedValues() { return this._get('/model/supported-values'); } /** * Health check * @returns {Object} { status, models_loaded, timestamp } */ async healthCheck() { return this._get('/health'); } /** * Convert a Mongoose block document to ML API format for estimation. * @param {Object} mongoBlock - Mongoose Block document * @returns {Object} formatted for /predict/estimate */ static formatForEstimate(mongoBlock) { return { block_type: mongoBlock.type || mongoBlock.blockType, tech_node: mongoBlock.techNode || mongoBlock.tech_node, priority: mongoBlock.priority || 'P3-Medium', transistor_count: mongoBlock.transistorCount || mongoBlock.transistor_count, has_dependencies: (mongoBlock.dependencies?.length || 0) > 0, num_dependencies: mongoBlock.dependencies?.length || 0, constraint_complexity: mongoBlock.constraintComplexity || 1.0, drc_iterations: mongoBlock.drcIterations || 2, engineer_skill_factor: 1.0, // default; update from engineer profile }; } /** * Convert a Mongoose block document to ML API format for bottleneck prediction. * @param {Object} mongoBlock - Mongoose Block document * @param {number} daysSinceLastTransition - Days in current stage * @returns {Object} formatted for /predict/bottleneck */ static formatForBottleneck(mongoBlock, daysSinceLastTransition) { return { block_type: mongoBlock.type || mongoBlock.blockType, tech_node: mongoBlock.techNode || mongoBlock.tech_node, priority: mongoBlock.priority || 'P3-Medium', estimated_hours: mongoBlock.estimatedHours || 20, hours_logged: mongoBlock.hoursLogged || 0, drc_iterations: mongoBlock.drcIterations || 2, drc_violations_total: mongoBlock.drcViolations || 0, lvs_mismatches_total: mongoBlock.lvsMismatches || 0, current_stage: mongoBlock.status, days_in_current_stage: daysSinceLastTransition, engineer_skill_factor: 1.0, is_overdue: mongoBlock.dueDate ? new Date() > new Date(mongoBlock.dueDate) : false, }; } /** * Convert a Mongoose block document to ML API format for completion prediction. * @param {Object} mongoBlock - Mongoose Block document * @returns {Object} formatted for /predict/completion */ static formatForCompletion(mongoBlock) { const startDate = new Date(mongoBlock.createdAt || mongoBlock.startDate); const now = new Date(); const daysSinceStart = (now - startDate) / (1000 * 60 * 60 * 24); return { block_type: mongoBlock.type || mongoBlock.blockType, tech_node: mongoBlock.techNode || mongoBlock.tech_node, priority: mongoBlock.priority || 'P3-Medium', estimated_hours: mongoBlock.estimatedHours || 20, engineer_skill_factor: 1.0, drc_iterations: mongoBlock.drcIterations || 2, current_stage: mongoBlock.status, cumulative_hours: mongoBlock.hoursLogged || 0, cumulative_days: daysSinceStart, cumulative_drc_violations: mongoBlock.drcViolations || 0, cumulative_lvs_mismatches: mongoBlock.lvsMismatches || 0, }; } } module.exports = new ALWASMLClient(); module.exports.ALWASMLClient = ALWASMLClient;