Skip to main content
Glama

π“‚€π“’π“‹Ήπ”Έβ„•π•Œπ”Ήπ•€π•Šπ“‹Ήπ“’π“‚€ - Intelligent Guidance for

by Hive-Academy
execution-data-enricher.service.tsβ€’10.8 kB
import { Injectable, Inject } from '@nestjs/common'; import { ProgressMetrics } from '../types/progress-calculator.types'; import { BaseServiceConfig, ConfigurableService, } from '../utils/configurable-service.base'; import { ExecutionDataUtils } from '../utils/execution-data.utils'; import { RoleTransitionService } from './role-transition.service'; import { StepExecutionService } from './step-execution.service'; import { WorkflowExecutionWithRelations } from '../repositories/types/workflow-execution.types'; import { WorkflowGuidanceService } from './workflow-guidance.service'; import { IWorkflowExecutionRepository } from '../repositories/interfaces/workflow-execution.repository.interface'; import { IStepProgressRepository } from '../repositories/interfaces/step-progress.repository.interface'; // Configuration interfaces to eliminate hardcoding export interface DataEnricherConfig extends BaseServiceConfig { defaults: { projectPath: string; fallbackRecommendations: string[]; completionMessages: { nearCompletion: string; stepsRemaining: string; }; }; fallbackSteps: { executionNotFound: { name: string; description: string; }; noNextStep: { name: string; description: string; }; errorFallback: { name: string; description: string; }; }; performance: { cacheTimeoutMs: number; maxRecommendations: number; queryTimeoutMs: number; }; } export interface NextStep { name: string; status: 'pending' | 'ready' | 'completed' | 'skipped'; description?: string; } export interface ProgressOverview { averageProgress: number; totalActive: number; } export interface EnrichedExecutionData { execution: WorkflowExecutionWithRelations; nextSteps: NextStep[]; availableTransitions: unknown[]; progressMetrics: ProgressMetrics; } /** * Execution Data Enricher Service * * Focused service for enriching execution data with additional context. * Follows Single Responsibility Principle - only handles data enrichment. */ @Injectable() export class ExecutionDataEnricherService extends ConfigurableService<DataEnricherConfig> { // Configuration with sensible defaults protected readonly defaultConfig: DataEnricherConfig = { defaults: { projectPath: process.cwd(), fallbackRecommendations: [ 'Review task requirements', 'Execute next workflow step', 'Validate current progress', 'Check for blockers', 'Update task status', ], completionMessages: { nearCompletion: 'Near completion', stepsRemaining: 'steps remaining', }, }, fallbackSteps: { executionNotFound: { name: 'Review workflow state', description: 'Review current execution state and determine next actions', }, noNextStep: { name: 'Role transition or completion', description: 'All steps completed for current role - consider role transition', }, errorFallback: { name: 'Review workflow state', description: 'Review current execution state and determine next actions', }, }, performance: { cacheTimeoutMs: 300000, // 5 minutes maxRecommendations: 10, queryTimeoutMs: 5000, }, }; constructor( private readonly stepExecution: StepExecutionService, private readonly roleTransition: RoleTransitionService, private readonly workflowGuidance: WorkflowGuidanceService, @Inject('IWorkflowExecutionRepository') private readonly workflowExecutionRepository: IWorkflowExecutionRepository, @Inject('IStepProgressRepository') private readonly stepProgressRepository: IStepProgressRepository, ) { super(); this.initializeConfig(); } /** * Enrich execution with all additional context data */ async enrichExecutionData( execution: WorkflowExecutionWithRelations, ): Promise<EnrichedExecutionData> { const nextSteps = await this.getNextStepsForExecution(execution.id); const availableTransitions = await this.getAvailableTransitions(execution); const progressMetrics = this.calculateProgressMetrics(execution); return { execution, nextSteps, availableTransitions, progressMetrics, }; } /** * Get next steps for execution */ async getNextStepsForExecution(executionId: string): Promise<NextStep[]> { try { // Get the execution to find current role and task const execution = await this.workflowExecutionRepository.findById( executionId, { currentRole: true, task: true, }, ); if (!execution) { return [ { name: this.getConfigValue('fallbackSteps').executionNotFound.name, status: 'ready', description: this.getConfigValue('fallbackSteps').executionNotFound .description, }, ]; } // Get the next available step for the current role const nextStep = await this.stepExecution.getNextAvailableStep( Number(execution.taskId), execution.currentRoleId, ); if (!nextStep) { return [ { name: this.getConfigValue('fallbackSteps').noNextStep.name, status: 'ready', description: this.getConfigValue('fallbackSteps').noNextStep.description, }, ]; } // Get step progress to determine status const stepProgressList = await this.stepProgressRepository.findMany({ where: { taskId: String(execution.taskId), roleId: execution.currentRoleId, stepId: nextStep.id, }, take: 1, }); const stepProgress = stepProgressList.length > 0 ? stepProgressList[0] : null; const stepStatus = stepProgress?.status === 'IN_PROGRESS' ? 'pending' : 'ready'; return [ { name: nextStep.name, status: stepStatus, description: nextStep.description || `Execute ${nextStep.description}`, }, ]; } catch (_error) { return [ { name: this.getConfigValue('fallbackSteps').errorFallback.name, status: 'ready', description: this.getConfigValue('fallbackSteps').errorFallback.description, }, ]; } } /** * Get available role transitions */ private async getAvailableTransitions( execution: WorkflowExecutionWithRelations, ): Promise<unknown[]> { try { return await this.roleTransition.getAvailableTransitions( execution.currentRoleId, ); } catch (_error) { return []; } } /** * Calculate progress metrics with type safety */ calculateProgressMetrics( execution: WorkflowExecutionWithRelations, ): ProgressMetrics { const percentage = ExecutionDataUtils.safeGetNumber( execution, 'progressPercentage', 0, ); const stepsCompleted = ExecutionDataUtils.safeGetNumber( execution, 'stepsCompleted', 0, ); const totalSteps = ExecutionDataUtils.safeGetNumber( execution, 'totalSteps', 0, ); return { percentage, stepsCompleted, totalSteps, estimatedCompletion: ExecutionDataUtils.formatTimeEstimate( totalSteps, stepsCompleted, ), }; } /** * CENTRALIZED PROGRESS CALCULATIONS * * All progress calculation logic centralized here for DRY compliance. * ExecutionAnalyticsService will consume these enriched calculations. */ /** * Calculate overall progress using centralized utility (DRY compliance) * * DEPENDENCY REDUCTION: Now uses ExecutionDataUtils.calculateOverallProgress * to maintain consistency with ExecutionAnalyticsService and eliminate duplication. */ calculateOverallProgress( executions: WorkflowExecutionWithRelations[], ): ProgressOverview { return ExecutionDataUtils.calculateOverallProgress( executions, (exec) => ExecutionDataUtils.safeGetNumber(exec, 'progressPercentage', 0), 0, ); } /** * Calculate enhanced progress metrics with additional context */ calculateEnhancedProgressMetrics( execution: WorkflowExecutionWithRelations, options: { includeEstimation?: boolean; roundingPrecision?: number; } = {}, ): ProgressMetrics & { progressPhase: 'starting' | 'in-progress' | 'near-completion' | 'completed'; efficiency: number; } { const basicMetrics = this.calculateProgressMetrics(execution); const { includeEstimation = true, roundingPrecision = 0 } = options; // Determine progress phase let progressPhase: | 'starting' | 'in-progress' | 'near-completion' | 'completed'; if (basicMetrics.percentage! >= 100) { progressPhase = 'completed'; } else if (basicMetrics.percentage! >= 80) { progressPhase = 'near-completion'; } else if (basicMetrics.percentage! > 10) { progressPhase = 'in-progress'; } else { progressPhase = 'starting'; } // Calculate efficiency (steps completed vs expected based on time) const startedAt = ExecutionDataUtils.safeGetDate(execution, 'startedAt'); const now = new Date(); const hoursElapsed = (now.getTime() - startedAt.getTime()) / (1000 * 60 * 60); const expectedStepsPerHour = 2; // Configurable assumption const expectedSteps = Math.max(1, hoursElapsed * expectedStepsPerHour); const efficiency = ExecutionDataUtils.calculatePercentage( basicMetrics.stepsCompleted!, expectedSteps, roundingPrecision, ); return { ...basicMetrics, percentage: ExecutionDataUtils.roundProgress( basicMetrics.percentage!, roundingPrecision, ), progressPhase, efficiency: Math.min(efficiency, 200), // Cap at 200% efficiency estimatedCompletion: includeEstimation ? basicMetrics.estimatedCompletion : null, }; } /** * Batch calculate progress metrics for multiple executions */ batchCalculateProgressMetrics( executions: WorkflowExecutionWithRelations[], ): Map<string, ProgressMetrics> { const results = new Map<string, ProgressMetrics>(); executions.forEach((execution) => { try { const metrics = this.calculateProgressMetrics(execution); results.set(execution.id, metrics); } catch (_error) { // Set fallback metrics results.set(execution.id, { percentage: 0, stepsCompleted: 0, totalSteps: 0, estimatedCompletion: null, }); } }); return results; } }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Hive-Academy/Anubis-MCP'

If you have feedback or need assistance with the MCP directory API, please join our Discord server