#!/usr/bin/env node /** * Forecast Pipeline Orchestrator * * Spawns the Python forecast engine with database credentials from the * environment. Can be run manually, via cron, or integrated into the * existing metrics pipeline. * * Usage: * node run_forecast.js * * Environment: * Reads DB_HOST, DB_USER, DB_PASSWORD, DB_NAME, DB_PORT from * /var/www/html/inventory/.env (or current process env). */ const { spawn } = require('child_process'); const path = require('path'); const fs = require('fs'); // Load .env file if it exists (production path) const envPaths = [ '/var/www/html/inventory/.env', path.join(__dirname, '../../.env'), ]; for (const envPath of envPaths) { if (fs.existsSync(envPath)) { const envContent = fs.readFileSync(envPath, 'utf-8'); for (const line of envContent.split('\n')) { const trimmed = line.trim(); if (!trimmed || trimmed.startsWith('#')) continue; const eqIndex = trimmed.indexOf('='); if (eqIndex === -1) continue; const key = trimmed.slice(0, eqIndex); const value = trimmed.slice(eqIndex + 1); if (!process.env[key]) { process.env[key] = value; } } console.log(`Loaded env from ${envPath}`); break; } } // Verify required env vars const required = ['DB_HOST', 'DB_USER', 'DB_PASSWORD', 'DB_NAME']; const missing = required.filter(k => !process.env[k]); if (missing.length > 0) { console.error(`Missing required environment variables: ${missing.join(', ')}`); process.exit(1); } const SCRIPT_DIR = __dirname; const PYTHON_SCRIPT = path.join(SCRIPT_DIR, 'forecast_engine.py'); const VENV_DIR = path.join(SCRIPT_DIR, 'venv'); const REQUIREMENTS = path.join(SCRIPT_DIR, 'requirements.txt'); // Determine python binary (prefer venv if it exists) function getPythonBin() { const venvPython = path.join(VENV_DIR, 'bin', 'python'); if (fs.existsSync(venvPython)) return venvPython; // Fall back to system python return 'python3'; } // Ensure venv and dependencies are installed async function ensureDependencies() { if (!fs.existsSync(path.join(VENV_DIR, 'bin', 'python'))) { console.log('Creating virtual environment...'); await runCommand('python3', ['-m', 'venv', VENV_DIR]); } // Always run pip install — idempotent, fast when packages already present console.log('Checking dependencies...'); const python = path.join(VENV_DIR, 'bin', 'python'); await runCommand(python, ['-m', 'pip', 'install', '--quiet', '-r', REQUIREMENTS]); } function runCommand(cmd, args, options = {}) { return new Promise((resolve, reject) => { const proc = spawn(cmd, args, { stdio: 'inherit', ...options, }); proc.on('close', code => { if (code === 0) resolve(); else reject(new Error(`${cmd} exited with code ${code}`)); }); proc.on('error', reject); }); } async function main() { const startTime = Date.now(); console.log('='.repeat(60)); console.log(`Forecast Pipeline - ${new Date().toISOString()}`); console.log('='.repeat(60)); try { await ensureDependencies(); const pythonBin = getPythonBin(); console.log(`Using Python: ${pythonBin}`); console.log(`Running: ${PYTHON_SCRIPT}`); console.log(''); await runCommand(pythonBin, [PYTHON_SCRIPT], { env: { ...process.env, PYTHONUNBUFFERED: '1', // Real-time output }, }); const duration = ((Date.now() - startTime) / 1000).toFixed(1); console.log(''); console.log('='.repeat(60)); console.log(`Forecast pipeline completed in ${duration}s`); console.log('='.repeat(60)); } catch (err) { const duration = ((Date.now() - startTime) / 1000).toFixed(1); console.error(`Forecast pipeline FAILED after ${duration}s:`, err.message); process.exit(1); } } main();