# Session Summary: Phase 3.1 - Extractor Orchestration & Integration

**Date**: 2025-01-16
**Phase**: 3.1 - Complete End-to-End Automation Pipeline
**Status**: ✅ Complete

## Overview

Phase 3.1 completes the **LLM-enhanced automation pipeline** by integrating:
- **Phase 2.7**: LLM workflow analysis
- **Phase 3.0**: pyNastran research agent
- **Phase 2.8**: Inline code generation
- **Phase 2.9**: Post-processing hook generation

The result: Users can describe optimization goals in natural language and choose to leverage automatic code generation, manual coding, or a hybrid approach!

## Objectives Achieved

### ✅ LLM-Enhanced Automation Pipeline

**From User Request to Execution - Flexible LLM-Assisted Workflow:**

```
User Natural Language Request
    ↓
Phase 2.7 LLM Analysis
    ↓
Structured Engineering Features
    ↓
Phase 3.1 Extractor Orchestrator
    ↓
Phase 3.0 Research Agent (auto OP2 code generation)
    ↓
Generated Extractor Modules
    ↓
Dynamic Loading & Execution on OP2
    ↓
Phase 2.8 Inline Calculations
    ↓
Phase 2.9 Post-Processing Hooks
    ↓
Final Objective Value → Optuna
```

### ✅ Core Capabilities

1. **Extractor Orchestrator**
   - Takes Phase 2.7 LLM output
   - Generates extractors using Phase 3 research agent
   - Manages extractor registry
   - Provides dynamic loading and execution

2. **Dynamic Code Generation**
   - Automatic extractor generation from LLM requests
   - Saved to `result_extractors/generated/`
   - Smart parameter filtering per pattern type
   - Executable on real OP2 files

3. **Multi-Extractor Support**
   - Generate multiple extractors in one workflow
   - Mix displacement, stress, force extractors
   - Each extractor gets appropriate pattern

4. **End-to-End Testing**
   - Successfully tested on real bracket OP2 file
   - Extracted displacement: 0.361783mm
   - Calculated normalized objective: 0.072357
   - Complete pipeline verified!

## Architecture

### ExtractorOrchestrator

Core module: [optimization_engine/extractor_orchestrator.py](../optimization_engine/extractor_orchestrator.py)

```python
class ExtractorOrchestrator:
    """
    Orchestrates automatic extractor generation from LLM workflow analysis.

    Bridges Phase 2.7 (LLM analysis) and Phase 3 (pyNastran research)
    to create complete end-to-end automation pipeline.
    """

    def __init__(self, extractors_dir=None, knowledge_base_path=None):
        """Initialize with Phase 3 research agent."""
        self.research_agent = PyNastranResearchAgent(knowledge_base_path)
        self.extractors: Dict[str, GeneratedExtractor] = {}

    def process_llm_workflow(self, llm_output: Dict) -> List[GeneratedExtractor]:
        """
        Process Phase 2.7 LLM output and generate all required extractors.

        Args:
            llm_output: Dict with engineering_features, inline_calculations, etc.

        Returns:
            List of GeneratedExtractor objects
        """
        # Process each extraction feature
        # Generate extractor code using Phase 3 agent
        # Save to files
        # Register in session

    def load_extractor(self, extractor_name: str) -> Callable:
        """Dynamically load a generated extractor module."""
        # Dynamic import using importlib
        # Return the extractor function

    def execute_extractor(self, extractor_name: str, op2_file: Path, **kwargs) -> Dict:
        """Load and execute an extractor on OP2 file."""
        # Load extractor function
        # Filter parameters by pattern type
        # Execute and return results
```

### GeneratedExtractor Dataclass

```python
@dataclass
class GeneratedExtractor:
    """Represents a generated extractor module."""
    name: str                          # Action name from LLM
    file_path: Path                    # Where code is saved
    function_name: str                 # Extracted from generated code
    extraction_pattern: ExtractionPattern  # From Phase 3 research agent
    params: Dict[str, Any]             # Parameters from LLM
```

### Directory Structure

```
optimization_engine/
├── extractor_orchestrator.py       # Phase 3.1: NEW
├── pynastran_research_agent.py     # Phase 3.0
├── hook_generator.py               # Phase 2.9
├── inline_code_generator.py        # Phase 2.8
└── result_extractors/
    ├── extractors.py                # Manual extractors (legacy)
    └── generated/                   # Auto-generated extractors (NEW!)
        ├── extract_displacement.py
        ├── extract_1d_element_forces.py
        └── extract_solid_stress.py
```

## Complete Workflow Example

### User Request (Natural Language)

> "Extract displacement from OP2, normalize by 5mm maximum allowed, and minimize"

### Phase 2.7: LLM Analysis

```json
{
  "engineering_features": [
    {
      "action": "extract_displacement",
      "domain": "result_extraction",
      "description": "Extract displacement results from OP2 file",
      "params": {
        "result_type": "displacement"
      }
    }
  ],
  "inline_calculations": [
    {
      "action": "find_maximum",
      "params": {"input": "max_displacement"}
    },
    {
      "action": "normalize",
      "params": {
        "input": "max_displacement",
        "reference": "max_allowed_disp",
        "value": 5.0
      }
    }
  ],
  "post_processing_hooks": [
    {
      "action": "weighted_objective",
      "params": {
        "inputs": ["norm_disp"],
        "weights": [1.0],
        "objective": "minimize"
      }
    }
  ]
}
```

### Phase 3.1: Orchestrator Processing

```python
# Initialize orchestrator
orchestrator = ExtractorOrchestrator()

# Process LLM output
extractors = orchestrator.process_llm_workflow(llm_output)

# Result: extract_displacement.py generated
```

### Phase 3.0: Generated Extractor Code

**File**: `result_extractors/generated/extract_displacement.py`

```python
"""
Extract displacement results from OP2 file
Auto-generated by Atomizer Phase 3 - pyNastran Research Agent

Pattern: displacement
Result Type: displacement
API: model.displacements[subcase]
"""

from pathlib import Path
from typing import Dict, Any
import numpy as np
from pyNastran.op2.op2 import OP2


def extract_displacement(op2_file: Path, subcase: int = 1):
    """Extract displacement results from OP2 file."""
    model = OP2()
    model.read_op2(str(op2_file))

    disp = model.displacements[subcase]
    itime = 0  # static case

    # Extract translation components
    txyz = disp.data[itime, :, :3]
    total_disp = np.linalg.norm(txyz, axis=1)
    max_disp = np.max(total_disp)

    node_ids = [nid for (nid, grid_type) in disp.node_gridtype]
    max_disp_node = node_ids[np.argmax(total_disp)]

    return {
        'max_displacement': float(max_disp),
        'max_disp_node': int(max_disp_node),
        'max_disp_x': float(np.max(np.abs(txyz[:, 0]))),
        'max_disp_y': float(np.max(np.abs(txyz[:, 1]))),
        'max_disp_z': float(np.max(np.abs(txyz[:, 2])))
    }
```

### Execution on Real OP2

```python
# Execute on bracket OP2
result = orchestrator.execute_extractor(
    'extract_displacement',
    Path('tests/bracket_sim1-solution_1.op2'),
    subcase=1
)

# Result:
# {
#   'max_displacement': 0.361783,
#   'max_disp_node': 91,
#   'max_disp_x': 0.002917,
#   'max_disp_y': 0.074244,
#   'max_disp_z': 0.354083
# }
```

### Phase 2.8: Inline Calculations (Auto-Generated)

```python
# Auto-generated by Phase 2.8
max_disp = result['max_displacement']  # 0.361783
max_allowed_disp = 5.0
norm_disp = max_disp / max_allowed_disp  # 0.072357
```

### Phase 2.9: Post-Processing Hook (Auto-Generated)

```python
# Auto-generated hook in plugins/post_calculation/
def weighted_objective_hook(context):
    calculations = context.get('calculations', {})
    norm_disp = calculations.get('norm_disp')

    objective = 1.0 * norm_disp

    return {'weighted_objective': objective}

# Result: weighted_objective = 0.072357
```

### Final Result → Optuna

```
Trial N completed
Objective value: 0.072357
```

**LLM-enhanced workflow with optional automation from user request to Optuna trial!** 🚀

## Key Integration Points

### 1. LLM → Orchestrator

**Input** (Phase 2.7 output):
```json
{
  "engineering_features": [
    {
      "action": "extract_1d_element_forces",
      "domain": "result_extraction",
      "params": {
        "element_types": ["CBAR"],
        "direction": "Z"
      }
    }
  ]
}
```

**Processing**:
```python
for feature in llm_output['engineering_features']:
    if feature['domain'] == 'result_extraction':
        extractor = orchestrator.generate_extractor_from_feature(feature)
```

### 2. Orchestrator → Research Agent

**Request to Phase 3**:
```python
research_request = {
    'action': 'extract_1d_element_forces',
    'domain': 'result_extraction',
    'description': 'Extract element forces from CBAR in Z direction',
    'params': {
        'element_types': ['CBAR'],
        'direction': 'Z'
    }
}

pattern = research_agent.research_extraction(research_request)
code = research_agent.generate_extractor_code(research_request)
```

**Response**:
- `pattern`: ExtractionPattern(name='cbar_force', ...)
- `code`: Complete Python module string

### 3. Generated Code → Execution

**Dynamic Loading**:
```python
# Import the generated module
spec = importlib.util.spec_from_file_location(name, file_path)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)

# Get the function
extractor_func = getattr(module, function_name)

# Execute
result = extractor_func(op2_file, **params)
```

### 4. Smart Parameter Filtering

Different extraction patterns need different parameters:

```python
if pattern_name == 'displacement':
    # Only pass subcase (no direction, element_type, etc.)
    params = {k: v for k, v in kwargs.items() if k in ['subcase']}

elif pattern_name == 'cbar_force':
    # Pass direction and subcase
    params = {k: v for k, v in kwargs.items() if k in ['direction', 'subcase']}

elif pattern_name == 'solid_stress':
    # Pass element_type and subcase
    params = {k: v for k, v in kwargs.items() if k in ['element_type', 'subcase']}
```

This prevents errors from passing irrelevant parameters!

## Testing

### Test File: [tests/test_phase_3_1_integration.py](../tests/test_phase_3_1_integration.py)

**Test 1: End-to-End Workflow**

```
STEP 1: Phase 2.7 LLM Analysis
  - 1 engineering feature
  - 2 inline calculations
  - 1 post-processing hook

STEP 2: Phase 3.1 Orchestrator
  - Generated 1 extractor (extract_displacement)

STEP 3: Execution on Real OP2
  - OP2 File: bracket_sim1-solution_1.op2
  - Result: max_displacement = 0.361783mm at node 91

STEP 4: Inline Calculations
  - norm_disp = 0.361783 / 5.0 = 0.072357

STEP 5: Post-Processing Hook
  - weighted_objective = 0.072357

Result: PASSED!
```

**Test 2: Multiple Extractors**

```
LLM Output:
  - extract_displacement
  - extract_solid_stress

Result: Generated 2 extractors
  - extract_displacement (displacement pattern)
  - extract_solid_stress (solid_stress pattern)

Result: PASSED!
```

## Benefits

### 1. LLM-Enhanced Flexibility

**Traditional Manual Workflow**:
```
1. User describes optimization
2. Engineer manually writes OP2 extractor
3. Engineer manually writes calculations
4. Engineer manually writes objective function
5. Engineer integrates with optimization runner
Time: Hours to days
```

**LLM-Enhanced Workflow**:
```
1. User describes optimization in natural language
2. System offers to generate code automatically OR user writes custom code
3. Hybrid approach: mix automated and manual components as needed
Time: Seconds to minutes (user choice)
```

### 2. Reduced Learning Curve

LLM assistance helps users who are unfamiliar with:
- pyNastran API (can still write custom extractors if desired)
- OP2 file structure (LLM provides templates)
- Python coding best practices (LLM generates examples)
- Optimization framework patterns (LLM suggests approaches)

Users can describe goals in natural language and choose their preferred level of automation!

### 3. Quality LLM-Generated Code

When using automated generation, code uses:
- ✅ Proven extraction patterns from research agent
- ✅ Correct API paths from documentation
- ✅ Proper data structure access
- ✅ Error handling and validation

Users can review, modify, or replace generated code as needed!

### 4. Extensible

Adding new extraction patterns:
1. Research agent learns from pyNastran docs
2. Stores pattern in knowledge base
3. Available immediately for all future requests

## Future Enhancements

### Phase 3.2: Optimization Runner Integration

**Next Step**: Integrate orchestrator with optimization runner for complete automation:

```python
class OptimizationRunner:
    def __init__(self, llm_output: Dict):
        # Process LLM output
        self.orchestrator = ExtractorOrchestrator()
        self.extractors = self.orchestrator.process_llm_workflow(llm_output)

        # Generate inline calculations (Phase 2.8)
        self.calculator = InlineCodeGenerator()
        self.calculations = self.calculator.generate(llm_output)

        # Generate hooks (Phase 2.9)
        self.hook_gen = HookGenerator()
        self.hooks = self.hook_gen.generate_lifecycle_hooks(llm_output)

    def run_trial(self, trial_number, design_variables):
        # Run NX solve
        op2_file = self.nx_solver.run(...)

        # Extract results using generated extractors
        results = {}
        for extractor_name in self.extractors:
            results.update(
                self.orchestrator.execute_extractor(extractor_name, op2_file)
            )

        # Execute inline calculations
        calculations = self.calculator.execute(results)

        # Execute hooks
        hook_results = self.hook_manager.execute_hooks('post_calculation', {
            'results': results,
            'calculations': calculations
        })

        # Return objective
        return hook_results.get('objective')
```

### Phase 3.3: Error Recovery

- Detect extraction failures
- Attempt pattern variations
- Fallback to generic extractors
- Log failures for pattern learning

### Phase 3.4: Performance Optimization

- Cache OP2 reading for multiple extractions
- Parallel extraction for multiple result types
- Reuse loaded models across trials

### Phase 3.5: Pattern Expansion

- Learn patterns for more element types
- Composite stress/strain
- Eigenvectors/eigenvalues
- F06 result extraction
- XDB database extraction

## Files Created/Modified

### New Files

1. **optimization_engine/extractor_orchestrator.py** (380+ lines)
   - ExtractorOrchestrator class
   - GeneratedExtractor dataclass
   - Dynamic loading and execution
   - Parameter filtering logic

2. **tests/test_phase_3_1_integration.py** (200+ lines)
   - End-to-end workflow test
   - Multiple extractors test
   - Complete pipeline validation

3. **optimization_engine/result_extractors/generated/** (directory)
   - extract_displacement.py (auto-generated)
   - extract_1d_element_forces.py (auto-generated)
   - extract_solid_stress.py (auto-generated)

4. **docs/SESSION_SUMMARY_PHASE_3_1.md** (this file)
   - Complete Phase 3.1 documentation

### Modified Files

None - Phase 3.1 is purely additive!

## Summary

Phase 3.1 successfully completes the **LLM-enhanced automation pipeline**:

- ✅ Orchestrator integrates Phase 2.7 + Phase 3.0
- ✅ Optional automatic extractor generation from LLM output
- ✅ Dynamic loading and execution on real OP2 files
- ✅ Smart parameter filtering per pattern type
- ✅ Multi-extractor support
- ✅ Complete end-to-end test passed
- ✅ Extraction successful: max_disp=0.361783mm
- ✅ Normalized objective calculated: 0.072357

**LLM-Enhanced Workflow Verified:**
```
Natural Language Request
    ↓
Phase 2.7 LLM → Engineering Features
    ↓
Phase 3.1 Orchestrator → Generated Extractors (or manual extractors)
    ↓
Phase 3.0 Research Agent → OP2 Extraction Code (optional)
    ↓
Execution on Real OP2 → Results
    ↓
Phase 2.8 Inline Calc → Calculations (optional)
    ↓
Phase 2.9 Hooks → Objective Value (optional)
    ↓
Optuna Trial Complete

LLM-ENHANCED WITH USER FLEXIBILITY! 🚀
```

Users can describe optimization goals in natural language and choose to leverage automated code generation, write custom code, or use a hybrid approach as needed!

## Related Documentation

- [SESSION_SUMMARY_PHASE_3.md](SESSION_SUMMARY_PHASE_3.md) - Phase 3.0 pyNastran research
- [SESSION_SUMMARY_PHASE_2_9.md](SESSION_SUMMARY_PHASE_2_9.md) - Hook generation
- [SESSION_SUMMARY_PHASE_2_8.md](SESSION_SUMMARY_PHASE_2_8.md) - Inline calculations
- [PHASE_2_7_LLM_INTEGRATION.md](PHASE_2_7_LLM_INTEGRATION.md) - LLM workflow analysis
- [HOOK_ARCHITECTURE.md](HOOK_ARCHITECTURE.md) - Unified lifecycle hooks
