UNPKG

datapilot-cli

Version:

Enterprise-grade streaming multi-format data analysis with comprehensive statistical insights and intelligent relationship detection - supports CSV, JSON, Excel, TSV, Parquet - memory-efficient, cross-platform

453 lines • 16.3 kB
"use strict"; /** * Enhanced Progress Orchestration System * Manages complex progress reporting across multiple analyzers with aggregation and callbacks */ Object.defineProperty(exports, "__esModule", { value: true }); exports.ProgressOrchestrator = void 0; exports.createProgressOrchestrator = createProgressOrchestrator; const logger_1 = require("../utils/logger"); /** * Enhanced progress orchestrator with hierarchical progress tracking */ class ProgressOrchestrator { callbacks; phases = new Map(); currentPhase; currentSection; sectionProgress = 0; phaseProgress = 0; overallProgress = 0; startTime; phaseStartTime = 0; sectionStartTime = 0; completedSections = 0; totalSections = 0; resourceMetrics; context; constructor(callbacks = {}, options = {}, context = {}) { this.callbacks = callbacks; this.context = context; this.startTime = Date.now(); this.resourceMetrics = { memoryUsage: 0, processingTime: 0, rowsProcessed: 0, sectionsCompleted: 0, }; this.setupDefaultPhases(); this.setupProgressLogging(options); } /** * Setup default phase configurations for DataPilot sections */ setupDefaultPhases() { const defaultPhases = [ { name: 'initialization', weight: 5, estimatedDuration: 2000, subPhases: [ { name: 'validation', weight: 40 }, { name: 'setup', weight: 60 }, ], }, { name: 'section1', weight: 15, estimatedDuration: 10000, subPhases: [ { name: 'metadata_collection', weight: 30 }, { name: 'structural_analysis', weight: 40 }, { name: 'environment_profiling', weight: 30 }, ], }, { name: 'section2', weight: 20, estimatedDuration: 15000, subPhases: [ { name: 'completeness_analysis', weight: 25 }, { name: 'uniqueness_analysis', weight: 25 }, { name: 'validity_analysis', weight: 25 }, { name: 'quality_scoring', weight: 25 }, ], }, { name: 'section3', weight: 25, estimatedDuration: 20000, subPhases: [ { name: 'univariate_analysis', weight: 40 }, { name: 'bivariate_analysis', weight: 35 }, { name: 'correlation_analysis', weight: 25 }, ], }, { name: 'section4', weight: 15, estimatedDuration: 8000, subPhases: [ { name: 'chart_selection', weight: 50 }, { name: 'accessibility_optimization', weight: 30 }, { name: 'recommendation_generation', weight: 20 }, ], }, { name: 'section5', weight: 10, estimatedDuration: 12000, subPhases: [ { name: 'schema_analysis', weight: 40 }, { name: 'transformation_analysis', weight: 35 }, { name: 'ml_readiness', weight: 25 }, ], }, { name: 'section6', weight: 10, estimatedDuration: 10000, subPhases: [ { name: 'task_identification', weight: 30 }, { name: 'algorithm_selection', weight: 40 }, { name: 'workflow_generation', weight: 30 }, ], }, ]; defaultPhases.forEach((phase) => this.phases.set(phase.name, phase)); } /** * Setup progress logging based on CLI options */ setupProgressLogging(options) { if (options.verbose) { this.callbacks.onProgress = (state) => { logger_1.logger.debug(`Progress: ${state.phase} - ${state.progress}% - ${state.message}`, { ...this.context, progress: state.progress, phase: state.phase, }); }; } if (!options.quiet && options.showProgress !== false) { // Add default console progress if no custom callback if (!this.callbacks.onProgress) { this.callbacks.onProgress = (state) => { const timeStr = this.formatDuration(state.timeElapsed); const etaStr = state.estimatedTimeRemaining ? ` (ETA: ${this.formatDuration(state.estimatedTimeRemaining)})` : ''; console.log(` ${state.progress}% - ${state.message} [${timeStr}]${etaStr}`); }; } } } /** * Start monitoring for resource usage */ startMonitoring(context) { this.context = { ...this.context, ...context }; this.startTime = Date.now(); logger_1.logger.debug('Started progress monitoring', this.context); } /** * Set the total number of sections for progress calculation */ setTotalSections(total) { this.totalSections = total; logger_1.logger.debug(`Set total sections to ${total}`, this.context); } /** * Start a new phase with optional subphase tracking */ startPhase(phaseName, message) { this.currentPhase = phaseName; this.phaseStartTime = Date.now(); this.phaseProgress = 0; const phase = this.phases.get(phaseName); if (phase) { logger_1.logger.debug(`Starting phase ${phaseName} (weight: ${phase.weight}%, estimated: ${phase.estimatedDuration}ms)`, { ...this.context, phase: phaseName }); } this.callbacks.onPhaseStart?.(phaseName, message); this.updateOverallProgress(); } /** * Start a new section within the current phase */ startSection(sectionName, message) { this.currentSection = sectionName; this.sectionStartTime = Date.now(); this.sectionProgress = 0; logger_1.logger.debug(`Starting section ${sectionName}`, { ...this.context, phase: this.currentPhase, section: sectionName, }); this.callbacks.onPhaseStart?.(sectionName, message); this.updateOverallProgress(); } /** * Update progress within the current phase/section */ updateProgress(state) { if (state.progress !== undefined) { this.sectionProgress = Math.max(0, Math.min(100, state.progress)); } // Update resource metrics if provided if (state.timeElapsed) { this.resourceMetrics.processingTime = state.timeElapsed; } this.updateOverallProgress(); const fullState = { phase: state.phase || this.currentPhase || 'unknown', progress: this.sectionProgress, message: state.message || 'Processing...', timeElapsed: state.timeElapsed || Date.now() - this.startTime, estimatedTimeRemaining: state.estimatedTimeRemaining || this.calculateETA(), }; this.callbacks.onProgress?.(fullState); } /** * Update resource metrics */ updateMetrics(metrics) { this.resourceMetrics = { ...this.resourceMetrics, ...metrics }; if (metrics.rowsProcessed !== undefined) { logger_1.logger.debug(`Processed ${metrics.rowsProcessed} rows`, { ...this.context, rowsProcessed: metrics.rowsProcessed, }); } } /** * Complete the current phase */ completePhase(message) { const duration = Date.now() - this.phaseStartTime; if (this.currentPhase) { logger_1.logger.info(`Completed phase ${this.currentPhase} in ${duration}ms`, { ...this.context, phase: this.currentPhase, duration, }); } this.callbacks.onPhaseComplete?.(message, duration); this.updateOverallProgress(); } /** * Complete the current section */ completeSection(message) { const duration = Date.now() - this.sectionStartTime; this.completedSections++; this.resourceMetrics.sectionsCompleted = this.completedSections; if (this.currentSection) { logger_1.logger.info(`Completed section ${this.currentSection} in ${duration}ms`, { ...this.context, section: this.currentSection, duration, completedSections: this.completedSections, }); } this.callbacks.onPhaseComplete?.(message, duration); this.updateOverallProgress(); } /** * Report an error in the current phase */ reportError(message, error) { logger_1.logger.error(`Error in ${this.currentPhase || 'unknown phase'}: ${message}`, this.context, error); this.callbacks.onError?.(message); } /** * Report a warning */ reportWarning(message) { logger_1.logger.warn(`Warning in ${this.currentPhase || 'unknown phase'}: ${message}`, this.context); this.callbacks.onWarning?.(message); } /** * Calculate overall progress based on phase weights and section completion */ updateOverallProgress() { let totalProgress = 0; // Add progress from completed phases for (const [phaseName, config] of this.phases) { if (phaseName === this.currentPhase) { // Current phase contributes partial progress totalProgress += (config.weight * this.sectionProgress) / 100; break; } else if (this.isPhaseCompleted(phaseName)) { // Completed phases contribute full weight totalProgress += config.weight; } } this.overallProgress = Math.max(0, Math.min(100, totalProgress)); } /** * Check if a phase has been completed */ isPhaseCompleted(phaseName) { // This is a simplified check - in practice, we'd track completed phases const phaseOrder = [ 'initialization', 'section1', 'section2', 'section3', 'section4', 'section5', 'section6', ]; const currentIndex = phaseOrder.indexOf(this.currentPhase || ''); const checkIndex = phaseOrder.indexOf(phaseName); return checkIndex >= 0 && checkIndex < currentIndex; } /** * Calculate estimated time remaining */ calculateETA() { if (this.overallProgress <= 0) return undefined; const timeElapsed = Date.now() - this.startTime; const estimatedTotal = (timeElapsed / this.overallProgress) * 100; const remaining = estimatedTotal - timeElapsed; return Math.max(0, remaining); } /** * Get current overall progress state */ getOverallState() { return { currentPhase: this.currentPhase || 'unknown', currentSection: this.currentSection || 'unknown', overallProgress: this.overallProgress, sectionProgress: this.sectionProgress, phaseProgress: this.phaseProgress, timeElapsed: Date.now() - this.startTime, estimatedTimeRemaining: this.calculateETA(), sectionsCompleted: this.completedSections, totalSections: this.totalSections, memoryUsage: this.resourceMetrics.memoryUsage, rowsProcessed: this.resourceMetrics.rowsProcessed, }; } /** * Check if resource thresholds are exceeded */ checkThresholds() { const warnings = []; let exceeded = false; // Check memory usage (if available) if (this.resourceMetrics.memoryUsage > 0) { const memoryMB = this.resourceMetrics.memoryUsage / (1024 * 1024); if (memoryMB > 1024) { // 1GB threshold warnings.push(`High memory usage: ${memoryMB.toFixed(1)}MB`); exceeded = true; } } // Check processing time const timeElapsed = Date.now() - this.startTime; if (timeElapsed > 300000) { // 5 minutes warnings.push(`Long processing time: ${this.formatDuration(timeElapsed)}`); } return { exceeded, warnings }; } /** * Stop monitoring and return final metrics */ stopMonitoring() { const finalMetrics = { ...this.resourceMetrics, processingTime: Date.now() - this.startTime, sectionsCompleted: this.completedSections, }; logger_1.logger.debug('Stopped progress monitoring', { ...this.context, finalMetrics, }); return finalMetrics; } /** * Format duration in human-readable format */ formatDuration(milliseconds) { if (milliseconds < 1000) { return `${Math.round(milliseconds)}ms`; } else if (milliseconds < 60000) { return `${(milliseconds / 1000).toFixed(1)}s`; } else { const minutes = Math.floor(milliseconds / 60000); const seconds = Math.round((milliseconds % 60000) / 1000); return `${minutes}m ${seconds}s`; } } /** * Create a scoped progress callback for a specific section */ createSectionCallback(sectionName, sectionWeight = 100) { return (state) => { // Scale the progress based on section weight const scaledProgress = (state.progress * sectionWeight) / 100; this.updateProgress({ ...state, progress: scaledProgress, phase: sectionName, }); }; } /** * Create a progress callback that aggregates multiple sub-operations */ createAggregatedCallback(subOperations) { const operationProgress = new Map(); return (operationName, progress) => { operationProgress.set(operationName, progress); // Calculate weighted average let totalWeight = 0; let weightedSum = 0; for (const op of subOperations) { const opProgress = operationProgress.get(op.name) || 0; totalWeight += op.weight; weightedSum += opProgress * op.weight; } const aggregatedProgress = totalWeight > 0 ? weightedSum / totalWeight : 0; this.updateProgress({ progress: aggregatedProgress, message: `${operationName}: ${progress}%`, timeElapsed: Date.now() - this.startTime, }); }; } } exports.ProgressOrchestrator = ProgressOrchestrator; /** * Factory function to create a progress orchestrator with sensible defaults */ function createProgressOrchestrator(options, context = {}) { const callbacks = {}; // Setup callbacks based on options if (!options.quiet) { callbacks.onPhaseStart = (phase, message) => { if (options.verbose) { console.log(`\nšŸ”„ ${phase}: ${message}`); } }; callbacks.onPhaseComplete = (message, timeElapsed) => { const duration = timeElapsed < 1000 ? `${Math.round(timeElapsed)}ms` : `${(timeElapsed / 1000).toFixed(1)}s`; console.log(`āœ… ${message} [${duration}]`); }; callbacks.onError = (message) => { console.error(`āŒ ${message}`); }; callbacks.onWarning = (message) => { console.warn(`āš ļø ${message}`); }; } return new ProgressOrchestrator(callbacks, options, context); } //# sourceMappingURL=progress-orchestrator.js.map