# EXT_02: Create Lifecycle Hook

<!--
PROTOCOL: Create Lifecycle Hook Plugin
LAYER: Extensions
VERSION: 1.0
STATUS: Active
LAST_UPDATED: 2025-12-05
PRIVILEGE: power_user
LOAD_WITH: []
-->

## Overview

This protocol guides you through creating lifecycle hooks that execute at specific points during optimization. Hooks enable custom logic injection without modifying core code.

**Privilege Required**: power_user or admin

---

## When to Use

| Trigger | Action |
|---------|--------|
| Need custom logic at specific point | Follow this protocol |
| "create hook", "callback" | Follow this protocol |
| Want to log/validate/modify at runtime | Follow this protocol |

---

## Quick Reference

**Hook Points Available**:

| Hook Point | When It Runs | Use Case |
|------------|--------------|----------|
| `pre_mesh` | Before meshing | Validate geometry |
| `post_mesh` | After meshing | Check mesh quality |
| `pre_solve` | Before solver | Log trial start |
| `post_solve` | After solver | Validate results |
| `post_extraction` | After extraction | Custom metrics |
| `post_calculation` | After objectives | Derived quantities |
| `custom_objective` | Custom objective | Complex objectives |

**Create in**: `optimization_engine/plugins/{hook_point}/`

---

## Step-by-Step Guide

### Step 1: Identify Hook Point

Choose the appropriate hook point:

```
Trial Flow:
    │
    ├─► PRE_MESH         → Validate model before meshing
    │
    ├─► POST_MESH        → Check mesh quality
    │
    ├─► PRE_SOLVE        → Log trial start, validate inputs
    │
    ├─► POST_SOLVE       → Check solve success, capture timing
    │
    ├─► POST_EXTRACTION  → Compute derived quantities
    │
    ├─► POST_CALCULATION → Final validation, logging
    │
    └─► CUSTOM_OBJECTIVE → Custom objective functions
```

### Step 2: Create Hook File

Create `optimization_engine/plugins/{hook_point}/{hook_name}.py`:

```python
"""
{Hook Description}

Author: {Your Name}
Created: {Date}
Version: 1.0
Hook Point: {hook_point}
"""

from typing import Dict, Any


def {hook_name}_hook(context: Dict[str, Any]) -> Dict[str, Any]:
    """
    {Description of what this hook does}.

    Args:
        context: Dictionary containing:
            - trial_number: Current trial number
            - design_params: Current design parameters
            - results: Results so far (if post-extraction)
            - config: Optimization config
            - working_dir: Path to working directory

    Returns:
        Dictionary with computed values or modifications.
        Return empty dict if no modifications needed.

    Example:
        >>> result = {hook_name}_hook({'trial_number': 1, ...})
        >>> print(result)
        {'{computed_key}': 123.45}
    """
    # Access context
    trial_num = context.get('trial_number')
    design_params = context.get('design_params', {})
    results = context.get('results', {})

    # Your logic here
    # ...

    # Return computed values
    return {
        '{computed_key}': computed_value,
    }


def register_hooks(hook_manager):
    """
    Register this hook with the hook manager.

    This function is called automatically when plugins are loaded.

    Args:
        hook_manager: The HookManager instance
    """
    hook_manager.register_hook(
        hook_point='{hook_point}',
        function={hook_name}_hook,
        name='{hook_name}_hook',
        description='{Brief description}',
        priority=100,  # Lower = runs earlier
        enabled=True
    )
```

### Step 3: Test Hook

```python
# Test in isolation
from optimization_engine.plugins.{hook_point}.{hook_name} import {hook_name}_hook

test_context = {
    'trial_number': 1,
    'design_params': {'thickness': 5.0},
    'results': {'max_stress': 200.0},
}

result = {hook_name}_hook(test_context)
print(result)
```

### Step 4: Enable Hook

Hooks are auto-discovered from the plugins directory. To verify:

```python
from optimization_engine.plugins.hook_manager import HookManager

manager = HookManager()
manager.discover_plugins()
print(manager.list_hooks())
```

---

## Hook Examples

### Example 1: Safety Factor Calculator (post_calculation)

```python
"""Calculate safety factor after stress extraction."""

def safety_factor_hook(context):
    """Calculate safety factor from stress results."""
    results = context.get('results', {})
    config = context.get('config', {})

    max_stress = results.get('max_von_mises', 0)
    yield_strength = config.get('material', {}).get('yield_strength', 250)

    if max_stress > 0:
        safety_factor = yield_strength / max_stress
    else:
        safety_factor = float('inf')

    return {
        'safety_factor': safety_factor,
        'yield_strength': yield_strength,
    }


def register_hooks(hook_manager):
    hook_manager.register_hook(
        hook_point='post_calculation',
        function=safety_factor_hook,
        name='safety_factor_hook',
        description='Calculate safety factor from stress',
        priority=100,
        enabled=True
    )
```

### Example 2: Trial Logger (pre_solve)

```python
"""Log trial information before solve."""

import json
from datetime import datetime
from pathlib import Path


def trial_logger_hook(context):
    """Log trial start information."""
    trial_num = context.get('trial_number')
    design_params = context.get('design_params', {})
    working_dir = context.get('working_dir', Path('.'))

    log_entry = {
        'trial': trial_num,
        'timestamp': datetime.now().isoformat(),
        'params': design_params,
    }

    log_file = working_dir / 'trial_log.jsonl'
    with open(log_file, 'a') as f:
        f.write(json.dumps(log_entry) + '\n')

    return {}  # No modifications


def register_hooks(hook_manager):
    hook_manager.register_hook(
        hook_point='pre_solve',
        function=trial_logger_hook,
        name='trial_logger_hook',
        description='Log trial parameters before solve',
        priority=10,  # Run early
        enabled=True
    )
```

### Example 3: Mesh Quality Check (post_mesh)

```python
"""Validate mesh quality after meshing."""


def mesh_quality_hook(context):
    """Check mesh quality metrics."""
    mesh_file = context.get('mesh_file')

    # Check quality metrics
    quality_issues = []

    # ... quality checks ...

    if quality_issues:
        context['warnings'] = context.get('warnings', []) + quality_issues

    return {
        'mesh_quality_passed': len(quality_issues) == 0,
        'mesh_issues': quality_issues,
    }


def register_hooks(hook_manager):
    hook_manager.register_hook(
        hook_point='post_mesh',
        function=mesh_quality_hook,
        name='mesh_quality_hook',
        description='Validate mesh quality',
        priority=50,
        enabled=True
    )
```

---

## Hook Context Reference

### Standard Context Keys

| Key | Type | Available At | Description |
|-----|------|--------------|-------------|
| `trial_number` | int | All | Current trial number |
| `design_params` | dict | All | Design parameter values |
| `config` | dict | All | Optimization config |
| `working_dir` | Path | All | Study working directory |
| `model_file` | Path | pre_mesh+ | NX model file path |
| `mesh_file` | Path | post_mesh+ | Mesh file path |
| `op2_file` | Path | post_solve+ | Results file path |
| `results` | dict | post_extraction+ | Extracted results |
| `objectives` | dict | post_calculation | Computed objectives |

### Priority Guidelines

| Priority Range | Use For |
|----------------|---------|
| 1-50 | Critical hooks that must run first |
| 50-100 | Standard hooks |
| 100-150 | Logging and monitoring |
| 150+ | Cleanup and finalization |

---

## Managing Hooks

### Enable/Disable at Runtime

```python
hook_manager.disable_hook('my_hook')
hook_manager.enable_hook('my_hook')
```

### Check Hook Status

```python
hooks = hook_manager.list_hooks()
for hook in hooks:
    print(f"{hook['name']}: {'enabled' if hook['enabled'] else 'disabled'}")
```

### Hook Execution Order

Hooks at the same point run in priority order (lower first):
```
Priority 10: trial_logger_hook
Priority 50: mesh_quality_hook
Priority 100: safety_factor_hook
```

---

## Troubleshooting

| Issue | Cause | Solution |
|-------|-------|----------|
| Hook not running | Not registered | Check `register_hooks` function |
| Wrong hook point | Misnamed directory | Check directory name matches hook point |
| Context missing key | Wrong hook point | Use appropriate hook point for data needed |
| Hook error crashes trial | Unhandled exception | Add try/except in hook |

---

## Cross-References

- **Related**: [EXT_01_CREATE_EXTRACTOR](./EXT_01_CREATE_EXTRACTOR.md)
- **System**: `optimization_engine/plugins/hook_manager.py`
- **Template**: `templates/hook_template.py`

---

## Version History

| Version | Date | Changes |
|---------|------|---------|
| 1.0 | 2025-12-05 | Initial release |
