---
document_type: "reference_doc"
service: "MOMENTRY_CORE"
title: "Processor Standardization Template"
date: "2026-04-25"
version: "V1.0"
status: "active"
owner: "Warren"
created_by: "OpenCode"
tags:
  - "processor"
  - "template"
  - "standardization"
ai_query_hints:
  - "查詢 Processor Standardization Template 的內容"
  - "Processor Standardization Template 的主要目的是什麼？"
  - "如何操作或實施 Processor Standardization Template？"
---

# Processor Standardization Template

## Overview
This template provides a standardized approach for creating contract-compliant processor modules based on the AI-Driven Processor Contract. Use this template when creating new processors or updating existing ones.

## Template Structure

### 1. Python Processor Script Template
```python
#!/opt/homebrew/bin/python3.11
"""
{PROCESSOR_NAME} Processor - AI-Driven Processor Contract Version {VERSION}

Compliant with AI-Driven Processor Contract v1.0
Effective Date: {DATE}

Features:
1. Standardized command-line interface
2. Redis progress reporting
3. Signal handling (SIGTERM, SIGINT)
4. Health check mode
5. Resource monitoring
6. Contract-compliant JSON output
7. Unified configuration
"""

import sys
import json
import os
import argparse
import signal
import tempfile
import time
import subprocess
import traceback
import threading
from datetime import datetime
from pathlib import Path
from typing import Dict, Any, List, Optional, Tuple
import atexit

# Redis Publisher for progress reporting
try:
    sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
    from redis_publisher import RedisPublisher

    REDIS_AVAILABLE = True
except ImportError:
    REDIS_AVAILABLE = False
    print(
        f"WARNING: RedisPublisher not available, progress reporting disabled",
        file=sys.stderr,
    )

# Contract version
CONTRACT_VERSION = "1.0"
PROCESSOR_NAME = "{processor_name}"  # e.g., "ocr", "yolo", "face", "pose"
PROCESSOR_VERSION = "{version}"  # e.g., "1.0.0"

# Unified configuration defaults
DEFAULT_TIMEOUT = 3600  # 1 hour
# Add processor-specific defaults here


# Signal handling with timeout support
class SignalHandler:
    """Handle system signals for graceful shutdown"""
    # ... (same as ASR template)


# Timeout manager
class TimeoutManager:
    """Manage processing timeouts"""
    # ... (same as ASR template)


# Health check functions
def check_environment() -> Dict[str, Any]:
    """Check environment and dependencies"""
    checks = []
    
    # Check 1: Processor-specific dependencies
    try:
        # Import processor-specific libraries
        # e.g., import cv2, import pytesseract, etc.
        checks.append({
            "name": "{dependency_name}",
            "status": "available",
            "version": "x.x.x"  # Get actual version
        })
    except ImportError:
        checks.append({"name": "{dependency_name}", "status": "missing", "version": None})
    
    # Check 2: FFmpeg/FFprobe (if needed)
    # ... (same as ASR template)
    
    # Check 3: Redis (optional)
    # ... (same as ASR template)
    
    # Check 4: Python version
    # ... (same as ASR template)
    
    return {"status": "healthy", "dependencies": checks}


# Main processor class
class {ProcessorClass}Processor:
    """{PROCESSOR_NAME} Processor compliant with AI-Driven Processor Contract"""
    
    def __init__(
        self,
        video_path: str,
        output_path: str,
        uuid: Optional[str] = None,
        check_health: bool = False,
    ):
        self.video_path = video_path
        self.output_path = output_path
        self.uuid = uuid or ""
        self.check_health = check_health
        
        # Get unified configuration from environment
        self.timeout = int(os.environ.get(f"MOMENTRY_{PROCESSOR_NAME.upper()}_TIMEOUT", str(DEFAULT_TIMEOUT)))
        # Add processor-specific configuration here
        
        # Initialize components
        self.publisher = None
        if REDIS_AVAILABLE and self.uuid:
            try:
                self.publisher = RedisPublisher(self.uuid)
            except Exception as e:
                print(f"[{PROCESSOR_NAME}] Failed to initialize Redis publisher: {e}", file=sys.stderr)
        
        self.timeout_manager = TimeoutManager(self.timeout, self.timeout, self.timeout)
        self.signal_handler = SignalHandler()
        self.start_time = time.time()
        self.cleanup_files = []
        
        # Set up signal handling
        self.signal_handler.setup()
        atexit.register(self.cleanup)
    
    def publish(self, msg_type: str, message: str, progress: Optional[float] = None):
        """Publish message to Redis if available"""
        # ... (same as ASR template)
    
    def validate_input(self) -> Tuple[bool, str]:
        """Validate input file"""
        if not os.path.exists(self.video_path):
            return False, f"Video file not found: {self.video_path}"
        
        # Add processor-specific validation
        # e.g., check video format, resolution, etc.
        
        return True, "Input validation passed"
    
    def process(self) -> Dict[str, Any]:
        """Main processing method"""
        self.publish("info", f"Starting {PROCESSOR_NAME} processing: {self.video_path}")
        
        # Validate input
        is_valid, validation_msg = self.validate_input()
        if not is_valid:
            raise RuntimeError(f"Input validation failed: {validation_msg}")
        
        self.publish("info", "Input validation passed")
        
        # Start timeout monitoring
        self.timeout_manager.start_overall_timer()
        
        # Processor-specific processing logic
        # This is where the actual processing happens
        try:
            result = self._process_core()
        except Exception as e:
            raise RuntimeError(f"Processing failed: {e}")
        
        # Check for timeout
        timeout_reached, timeout_msg = self.timeout_manager.check_timeout("processing")
        if timeout_reached:
            raise RuntimeError(f"Processing {timeout_msg}")
        
        # Prepare final result
        final_result = {
            "processor_name": PROCESSOR_NAME,
            "processor_version": PROCESSOR_VERSION,
            "contract_version": CONTRACT_VERSION,
            "video_path": self.video_path,
            "timestamp": datetime.utcnow().isoformat() + "Z",
            "processing_time_seconds": time.time() - self.start_time,
            "configuration": {
                "timeout_seconds": self.timeout,
                # Add processor-specific configuration
            },
            **result,  # Include processor-specific results
        }
        
        self.publish("progress", f"{PROCESSOR_NAME} processing complete", 1.0)
        self.publish("complete", f"{PROCESSOR_NAME} processing completed successfully in {final_result['processing_time_seconds']:.1f}s")
        
        return final_result
    
    def _process_core(self) -> Dict[str, Any]:
        """Core processing logic - implement in subclass"""
        raise NotImplementedError("Subclasses must implement _process_core")
    
    def cleanup(self):
        """Clean up temporary resources"""
        # ... (same as ASR template)


def main():
    """Main entry point"""
    parser = argparse.ArgumentParser(
        description=f"{PROCESSOR_NAME} Processor - AI-Driven Processor Contract Version {PROCESSOR_VERSION}"
    )
    
    # Required arguments
    parser.add_argument("video_path", help="Path to input video file")
    parser.add_argument("output_path", help="Path where JSON output should be written")
    
    # Optional arguments
    parser.add_argument("--uuid", "-u", default="", help="UUID for Redis progress reporting")
    parser.add_argument("--check-health", action="store_true", help="Perform health check and exit")
    
    # Hidden configuration arguments (following contract)
    parser.add_argument("--timeout", type=int, help=argparse.SUPPRESS)
    # Add processor-specific hidden arguments
    
    args = parser.parse_args()
    
    # Health check mode
    if args.check_health:
        health_result = check_environment()
        print(json.dumps(health_result, indent=2))
        sys.exit(0 if health_result["status"] == "healthy" else 1)
    
    # Create processor
    processor = {ProcessorClass}Processor(
        video_path=args.video_path,
        output_path=args.output_path,
        uuid=args.uuid if args.uuid else None,
        check_health=args.check_health,
    )
    
    try:
        # Process video
        result = processor.process()
        
        # Write output
        with open(args.output_path, "w", encoding="utf-8") as f:
            json.dump(result, f, indent=2, ensure_ascii=False)
        
        print(f"[{PROCESSOR_NAME}] Processing completed successfully")
        print(f"[{PROCESSOR_NAME}] Output written to: {args.output_path}")
        
        sys.exit(0)
        
    except RuntimeError as e:
        error_msg = f"{PROCESSOR_NAME} processing failed: {e}"
        processor.publish("error", error_msg)
        print(f"[{PROCESSOR_NAME}] ERROR: {error_msg}", file=sys.stderr)
        sys.exit(1)
        
    except KeyboardInterrupt:
        processor.publish("warning", "Processing interrupted by user")
        print(f"[{PROCESSOR_NAME}] Processing interrupted by user", file=sys.stderr)
        sys.exit(130)  # Standard exit code for SIGINT
        
    except Exception as e:
        error_msg = f"Unexpected error: {e}\n{traceback.format_exc()}"
        processor.publish("error", error_msg)
        print(f"[{PROCESSOR_NAME}] CRITICAL ERROR: {error_msg}", file=sys.stderr)
        sys.exit(1)


if __name__ == "__main__":
    main()
```

### 2. Rust Configuration Template
```rust
// Add to src/core/config.rs
pub mod {processor_name} {  // e.g., ocr, yolo, face, pose
    use super::*;

    // Unified configuration
    pub static TIMEOUT_SECS: Lazy<u64> = Lazy::new(|| {
        env::var(format!("MOMENTRY_{}_TIMEOUT", {PROCESSOR_NAME_UPPER}))
            .unwrap_or_else(|_| "3600".to_string())
            .parse()
            .unwrap_or(3600)
    });

    // Add processor-specific configuration
    // e.g., model paths, confidence thresholds, etc.
}
```

### 3. Rust Processor Module Template
```rust
use anyhow::{Context, Result};
use serde::{Deserialize, Serialize};
use std::time::Duration;

use super::executor::PythonExecutor;
use crate::core::config::{processor, {processor_name}};

#[derive(Debug, Serialize, Deserialize)]
pub struct {ProcessorClass}Result {
    // Define processor-specific result structure
}

pub async fn process_{processor_name}(
    video_path: &str,
    output_path: &str,
    uuid: Option<&str>,
) -> Result<{ProcessorClass}Result> {
    let executor = PythonExecutor::new()?;
    let script_path = executor.script_path("{processor_name}_processor_contract.py");

    tracing::info!("[{PROCESSOR_NAME_UPPER}] Starting processing: {}", video_path);

    executor
        .run(
            "{processor_name}_processor_contract.py",
            &[video_path, output_path],
            uuid,
            "{PROCESSOR_NAME_UPPER}",
            Some(Duration::from_secs(*{processor_name}::TIMEOUT_SECS)),
        )
        .await
        .with_context(|| format!("Failed to run {:?}", script_path))?;

    let json_str = std::fs::read_to_string(output_path)
        .context("Failed to read {PROCESSOR_NAME} output")?;

    let result: {ProcessorClass}Result =
        serde_json::from_str(&json_str).context("Failed to parse {PROCESSOR_NAME} output")?;

    tracing::info!("[{PROCESSOR_NAME_UPPER}] Processing completed successfully");

    Ok(result)
}
```

## Implementation Steps

### Step 1: Analyze Existing Processor
1. Review current Python script (`scripts/{processor_name}_processor.py`)
2. Identify dependencies and requirements
3. Understand input/output formats
4. Note any configuration needs

### Step 2: Create Contract-Compliant Version
1. Copy template to `scripts/{processor_name}_processor_contract.py`
2. Fill in processor-specific details:
   - `PROCESSOR_NAME` and `PROCESSOR_VERSION`
   - Dependencies in `check_environment()`
   - Configuration defaults
   - Core processing logic in `_process_core()`
   - Result structure

### Step 3: Update Rust Configuration
1. Add configuration module to `src/core/config.rs`
2. Define environment variables and defaults

### Step 4: Update Rust Processor Module
1. Update or create `src/core/processor/{processor_name}.rs`
2. Use contract-compliant script
3. Pass configuration via environment variables

### Step 5: Test and Validate
1. Create test script
2. Verify health check mode
3. Test basic processing
4. Validate contract compliance
5. Test configuration unification

## Processor-Specific Considerations

### OCR Processor
- **Dependencies**: Tesseract, OpenCV, PIL
- **Configuration**: Language models, confidence thresholds
- **Output**: Text with bounding boxes, confidence scores

### YOLO Processor  
- **Dependencies**: Ultralytics YOLO, OpenCV
- **Configuration**: Model path, confidence threshold, classes
- **Output**: Detected objects with bounding boxes, classes, confidence

### Face Processor
- **Dependencies**: face_recognition, dlib, OpenCV
- **Configuration**: Model paths, encoding methods
- **Output**: Face locations, encodings, landmarks

### Pose Processor
- **Dependencies**: MediaPipe, OpenCV
- **Configuration**: Model complexity, confidence thresholds
- **Output**: Pose keypoints, connections, confidence scores

## Configuration Variables

Each processor should support:
- `MOMENTRY_{PROCESSOR}_TIMEOUT` - Overall timeout
- `MOMENTRY_{PROCESSOR}_MODEL_PATH` - Model file path (if applicable)
- `MOMENTRY_{PROCESSOR}_CONFIDENCE` - Confidence threshold (if applicable)
- Processor-specific configuration

## Testing Checklist

- [ ] Health check mode works
- [ ] Basic processing with sample video
- [ ] Configuration loading from environment
- [ ] Signal handling (SIGINT, SIGTERM)
- [ ] Timeout handling
- [ ] Redis progress reporting (if Redis available)
- [ ] Output JSON follows contract schema
- [ ] Error handling and graceful degradation

## Migration Strategy

1. **Phase 1**: Create contract-compliant version alongside existing
2. **Phase 2**: Update Rust module to use new version
3. **Phase 3**: Test thoroughly
4. **Phase 4**: Remove old version (keep as backup)

## Example: OCR Processor Implementation

### Configuration (config.rs)
```rust
pub mod ocr {
    use super::*;

    pub static TIMEOUT_SECS: Lazy<u64> = Lazy::new(|| {
        env::var("MOMENTRY_OCR_TIMEOUT")
            .unwrap_or_else(|_| "1800".to_string())
            .parse()
            .unwrap_or(1800)
    });

    pub static LANGUAGE: Lazy<String> = Lazy::new(|| {
        env::var("MOMENTRY_OCR_LANGUAGE")
            .unwrap_or_else(|_| "eng".to_string())
    });

    pub static CONFIDENCE_THRESHOLD: Lazy<f32> = Lazy::new(|| {
        env::var("MOMENTRY_OCR_CONFIDENCE")
            .unwrap_or_else(|_| "0.7".to_string())
            .parse()
            .unwrap_or(0.7)
    });
}
```

### Python Script
- `PROCESSOR_NAME = "ocr"`
- `PROCESSOR_VERSION = "1.0.0"`
- Dependencies: `pytesseract`, `opencv-python`, `PIL`
- Core logic: Extract frames, run OCR, return text with positions

## Success Criteria

1. **Contract Compliance**: Follows AI-Driven Processor Contract
2. **Configuration Unification**: Uses unified configuration system
3. **Backward Compatibility**: Existing workflows continue to work
4. **Improved Maintainability**: Clean, standardized code
5. **Better Observability**: Consistent logging and monitoring

---

*Template Version: 1.0*  
*Last Updated: 2026-03-27*  
*Based on: AI-Driven Processor Contract v1.0*
