| from dataclasses import dataclass |
| import logging |
| import re |
| from typing import Any, Dict, List, Set |
|
|
| logger = logging.getLogger(__name__) |
|
|
| @dataclass |
| class OptimizationSuggestion: |
| type: str |
| description: str |
| affected_nodes: List[str] |
| savings_estimate_ms: int |
| action: str |
|
|
| class WorkflowOptimizer: |
| """ |
| Static Analysis engine for Workflows. |
| """ |
| |
| def __init__(self): |
| |
| self.var_pattern = re.compile(r'\{\{([^{}]+)\}\}') |
|
|
| def analyze(self, workflow_def: Dict[str, Any]) -> List[OptimizationSuggestion]: |
| """ |
| Analyze a workflow definition for optimization opportunities. |
| """ |
| suggestions = [] |
| |
| |
| |
| dependencies: Dict[str, Set[str]] = {} |
| |
| |
| steps_map = {s['step_id']: s for s in workflow_def.get('steps', [])} |
| |
| if not steps_map: |
| return [] |
|
|
| |
| for step in workflow_def.get('steps', []): |
| step_id = step['step_id'] |
| deps = self._extract_dependencies(step) |
| dependencies[step_id] = deps |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| for step_id, step in steps_map.items(): |
| |
| next_steps = step.get('next_steps', []) |
| |
| |
| if len(next_steps) == 1: |
| next_step_id = next_steps[0] |
| next_step = steps_map.get(next_step_id) |
| |
| if not next_step: |
| continue |
| |
| |
| |
| b_deps = dependencies.get(next_step_id, set()) |
| is_dependent = step_id in b_deps |
| |
| |
| |
| |
| is_conditional = step.get('step_type') == 'conditional_logic' |
| |
| |
| |
| |
| if not is_dependent and not is_conditional: |
| |
| |
| |
| |
| suggestions.append(OptimizationSuggestion( |
| type="parallelization", |
| description=f"Step '{next_step.get('description', next_step_id)}' follows '{step.get('description', step_id)}' but does not use its data. They could run in parallel.", |
| affected_nodes=[step_id, next_step_id], |
| savings_estimate_ms=1000, |
| action="reconfigure_parallel" |
| )) |
|
|
| return suggestions |
|
|
| def _extract_dependencies(self, step: Dict[str, Any]) -> Set[str]: |
| """ |
| Parse a step definition to find all {{ step_id.key }} references. |
| Returns a set of step_ids that this step depends on. |
| """ |
| deps = set() |
| |
| |
| to_scan = [step.get('parameters', {}), step.get('conditions', {})] |
| |
| while to_scan: |
| current = to_scan.pop() |
| |
| if isinstance(current, dict): |
| for v in current.values(): |
| to_scan.append(v) |
| elif isinstance(current, list): |
| for v in current: |
| to_scan.append(v) |
| elif isinstance(current, str): |
| |
| matches = self.var_pattern.findall(current) |
| for var_path in matches: |
| var_path = var_path.strip() |
| |
| |
| if '.' in var_path: |
| potential_step_id = var_path.split('.')[0] |
| |
| deps.add(potential_step_id) |
| |
| return deps |
|
|