> ## Documentation Index
> Fetch the complete documentation index at: https://docs.jobhive.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Bulk Operations

> Efficiently process multiple interviews and candidates at scale

## Overview

JobHive's bulk operations allow you to efficiently manage high-volume hiring scenarios, from startup scaling to enterprise-level recruitment campaigns. Process hundreds of candidates while maintaining consistent quality and experience.

<CardGroup cols={2}>
  <Card title="Bulk Interview Creation" icon="plus-circle">
    Create multiple interviews in a single API call with optimized processing
  </Card>

  <Card title="Parallel Processing" icon="network-wired">
    Handle thousands of concurrent interviews with automatic load balancing
  </Card>

  <Card title="Batch Results Export" icon="download">
    Export comprehensive results for multiple interviews in various formats
  </Card>

  <Card title="Smart Rate Limiting" icon="gauge">
    Intelligent request batching to maximize throughput within rate limits
  </Card>
</CardGroup>

## Bulk Interview Creation

### Single API Call for Multiple Interviews

Create up to 100 interviews per request with the bulk endpoint:

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST "https://backend.jobhive.ai/v1/interviews/bulk" \
    -H "Authorization: Bearer YOUR_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "interviews": [
        {
          "candidate_email": "john.doe@example.com",
          "position": "Frontend Developer",
          "skills": ["React", "TypeScript", "CSS"]
        },
        {
          "candidate_email": "jane.smith@example.com", 
          "position": "Backend Developer",
          "skills": ["Node.js", "PostgreSQL", "Docker"]
        },
        {
          "candidate_email": "bob.johnson@example.com",
          "position": "Full Stack Developer", 
          "skills": ["React", "Node.js", "MongoDB"]
        }
      ],
      "defaults": {
        "duration_minutes": 45,
        "difficulty": "intermediate",
        "company_name": "TechCorp Inc",
        "send_invitation": true
      }
    }'
  ```

  ```javascript JavaScript theme={null}
  const interviews = await fetch('https://backend.jobhive.ai/v1/interviews/bulk', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${process.env.JOBHIVE_API_KEY}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      interviews: [
        {
          candidate_email: 'alice@example.com',
          position: 'Data Scientist',
          skills: ['Python', 'Machine Learning', 'SQL']
        },
        {
          candidate_email: 'charlie@example.com',
          position: 'DevOps Engineer', 
          skills: ['Kubernetes', 'AWS', 'Terraform']
        }
      ],
      defaults: {
        duration_minutes: 60,
        difficulty: 'senior',
        company_name: 'DataCorp'
      }
    })
  });

  const result = await interviews.json();
  console.log(`Created ${result.data.successful.length} interviews`);
  ```

  ```python Python theme={null}
  import requests

  response = requests.post('https://backend.jobhive.ai/v1/interviews/bulk',
      headers={
          'Authorization': f'Bearer {os.environ["JOBHIVE_API_KEY"]}',
          'Content-Type': 'application/json'
      },
      json={
          'interviews': [
              {
                  'candidate_email': 'dev1@example.com',
                  'position': 'Software Engineer',
                  'skills': ['Java', 'Spring Boot', 'MySQL']
              },
              {
                  'candidate_email': 'dev2@example.com',
                  'position': 'Mobile Developer',
                  'skills': ['React Native', 'iOS', 'Android']
              }
          ],
          'defaults': {
              'duration_minutes': 30,
              'difficulty': 'intermediate',
              'send_invitation': False  # Send invitations manually
          }
      }
  )

  result = response.json()
  print(f"Successfully created: {len(result['data']['successful'])}")
  print(f"Failed: {len(result['data']['failed'])}")
  ```
</CodeGroup>

### Bulk Response Format

The bulk endpoint returns detailed success and failure information:

```json theme={null}
{
  "success": true,
  "data": {
    "successful": [
      {
        "index": 0,
        "interview": {
          "id": "int_abc123def456",
          "candidate_email": "john.doe@example.com", 
          "interview_url": "https://app.jobhive.ai/interview/int_abc123def456",
          "status": "scheduled"
        }
      },
      {
        "index": 2,
        "interview": {
          "id": "int_ghi789jkl012",
          "candidate_email": "bob.johnson@example.com",
          "interview_url": "https://app.jobhive.ai/interview/int_ghi789jkl012", 
          "status": "scheduled"
        }
      }
    ],
    "failed": [
      {
        "index": 1,
        "candidate_email": "jane.smith@example.com",
        "error": {
          "code": "INVALID_EMAIL",
          "message": "Email format is invalid"
        }
      }
    ],
    "summary": {
      "total_requested": 3,
      "successful_count": 2,
      "failed_count": 1,
      "success_rate": 0.67
    }
  }
}
```

## Advanced Bulk Patterns

### CSV/Excel Import Processing

Process candidate lists from spreadsheet uploads:

<CodeGroup>
  ```javascript CSV Processing theme={null}
  const csv = require('csv-parser');
  const fs = require('fs');

  async function processCSVFile(filePath) {
    const candidates = [];
    
    return new Promise((resolve, reject) => {
      fs.createReadStream(filePath)
        .pipe(csv())
        .on('data', (row) => {
          // Transform CSV row to interview format
          candidates.push({
            candidate_email: row.email,
            position: row.position,
            skills: row.skills.split(',').map(s => s.trim()),
            // Add custom fields from CSV
            experience_level: row.experience,
            preferred_start_date: row.start_date
          });
        })
        .on('end', async () => {
          try {
            // Process in batches of 50
            const batches = chunkArray(candidates, 50);
            const results = [];
            
            for (const batch of batches) {
              const response = await createBulkInterviews(batch);
              results.push(response);
              
              // Rate limiting delay
              await new Promise(resolve => setTimeout(resolve, 1000));
            }
            
            resolve(results);
          } catch (error) {
            reject(error);
          }
        });
    });
  }

  function chunkArray(array, chunkSize) {
    const chunks = [];
    for (let i = 0; i < array.length; i += chunkSize) {
      chunks.push(array.slice(i, i + chunkSize));
    }
    return chunks;
  }

  async function createBulkInterviews(candidates) {
    const response = await fetch('https://backend.jobhive.ai/v1/interviews/bulk', {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${process.env.JOBHIVE_API_KEY}`,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        interviews: candidates,
        defaults: {
          duration_minutes: 45,
          difficulty: 'intermediate',
          send_invitation: true
        }
      })
    });
    
    return response.json();
  }

  // Usage
  processCSVFile('./candidates.csv')
    .then(results => {
      const totalSuccess = results.reduce((sum, batch) => 
        sum + batch.data.successful.length, 0);
      console.log(`Successfully created ${totalSuccess} interviews`);
    })
    .catch(console.error);
  ```

  ```python Pandas Integration theme={null}
  import pandas as pd
  import requests
  import time
  from typing import List, Dict

  def process_excel_file(file_path: str) -> List[Dict]:
      """Process Excel file and create bulk interviews"""
      
      # Read Excel file
      df = pd.read_excel(file_path)
      
      # Clean and validate data
      df = df.dropna(subset=['email', 'position'])
      df['skills'] = df['skills'].apply(lambda x: [s.strip() for s in x.split(',')])
      
      # Convert to interview format
      interviews = []
      for _, row in df.iterrows():
          interview = {
              'candidate_email': row['email'],
              'position': row['position'], 
              'skills': row['skills']
          }
          
          # Add optional fields if present
          if 'experience_level' in row and pd.notna(row['experience_level']):
              interview['difficulty'] = map_experience_to_difficulty(row['experience_level'])
          
          if 'duration' in row and pd.notna(row['duration']):
              interview['duration_minutes'] = int(row['duration'])
              
          interviews.append(interview)
      
      # Process in batches
      return create_interviews_in_batches(interviews)

  def map_experience_to_difficulty(experience: str) -> str:
      """Map experience level to interview difficulty"""
      experience_map = {
          'Entry Level': 'junior',
          'Mid Level': 'intermediate', 
          'Senior Level': 'senior',
          'Executive': 'expert'
      }
      return experience_map.get(experience, 'intermediate')

  def create_interviews_in_batches(interviews: List[Dict], batch_size: int = 50) -> List[Dict]:
      """Create interviews in batches with rate limiting"""
      
      results = []
      total_batches = len(interviews) // batch_size + (1 if len(interviews) % batch_size else 0)
      
      for i in range(0, len(interviews), batch_size):
          batch = interviews[i:i + batch_size]
          batch_num = i // batch_size + 1
          
          print(f"Processing batch {batch_num}/{total_batches} ({len(batch)} interviews)")
          
          try:
              response = requests.post('https://backend.jobhive.ai/v1/interviews/bulk',
                  headers={
                      'Authorization': f'Bearer {os.environ["JOBHIVE_API_KEY"]}',
                      'Content-Type': 'application/json'
                  },
                  json={
                      'interviews': batch,
                      'defaults': {
                          'duration_minutes': 45,
                          'company_name': 'TechCorp',
                          'send_invitation': True
                      }
                  }
              )
              
              if response.status_code == 200:
                  result = response.json()
                  results.append(result)
                  print(f"✅ Batch {batch_num}: {result['data']['successful_count']}/{len(batch)} successful")
              else:
                  print(f"❌ Batch {batch_num} failed: {response.status_code}")
                  
          except Exception as e:
              print(f"❌ Batch {batch_num} error: {e}")
          
          # Rate limiting delay
          if batch_num < total_batches:
              time.sleep(2)
      
      return results

  # Usage
  if __name__ == "__main__":
      results = process_excel_file('candidate_list.xlsx')
      
      total_successful = sum(r['data']['successful_count'] for r in results)
      total_failed = sum(r['data']['failed_count'] for r in results)
      
      print(f"\n📊 Final Results:")
      print(f"✅ Successfully created: {total_successful} interviews")
      print(f"❌ Failed: {total_failed} interviews")
      print(f"📈 Success rate: {total_successful/(total_successful + total_failed)*100:.1f}%")
  ```
</CodeGroup>

### Bulk Results Processing

Efficiently retrieve and process results from multiple completed interviews:

<CodeGroup>
  ```javascript Batch Results theme={null}
  async function getBulkResults(interviewIds, includeTranscripts = false) {
    const batchSize = 20; // API limit for bulk results
    const results = [];
    
    for (let i = 0; i < interviewIds.length; i += batchSize) {
      const batch = interviewIds.slice(i, i + batchSize);
      
      try {
        const response = await fetch('https://backend.jobhive.ai/v1/interviews/bulk-results', {
          method: 'POST',
          headers: {
            'Authorization': `Bearer ${process.env.JOBHIVE_API_KEY}`,
            'Content-Type': 'application/json'
          },
          body: JSON.stringify({
            interview_ids: batch,
            include_results: true,
            include_transcripts: includeTranscripts
          })
        });
        
        const batchResults = await response.json();
        results.push(...batchResults.data);
        
        // Rate limiting
        if (i + batchSize < interviewIds.length) {
          await new Promise(resolve => setTimeout(resolve, 1000));
        }
        
      } catch (error) {
        console.error(`Error processing batch ${i/batchSize + 1}:`, error);
      }
    }
    
    return results;
  }

  async function generateHiringReport(interviewIds) {
    const interviews = await getBulkResults(interviewIds);
    
    const report = {
      total_interviews: interviews.length,
      completed: interviews.filter(i => i.status === 'completed').length,
      average_score: 0,
      recommendations: {
        hire: 0,
        maybe: 0, 
        no_hire: 0
      },
      skill_analysis: {},
      position_breakdown: {}
    };
    
    const completedInterviews = interviews.filter(i => i.status === 'completed' && i.results);
    
    if (completedInterviews.length > 0) {
      // Calculate average score
      report.average_score = completedInterviews.reduce((sum, i) => 
        sum + i.results.overall_score, 0) / completedInterviews.length;
      
      // Count recommendations
      completedInterviews.forEach(interview => {
        report.recommendations[interview.results.recommendation]++;
        
        // Analyze by position
        const position = interview.position;
        if (!report.position_breakdown[position]) {
          report.position_breakdown[position] = { count: 0, avg_score: 0, total_score: 0 };
        }
        report.position_breakdown[position].count++;
        report.position_breakdown[position].total_score += interview.results.overall_score;
      });
      
      // Calculate position averages
      Object.keys(report.position_breakdown).forEach(position => {
        const data = report.position_breakdown[position];
        data.avg_score = data.total_score / data.count;
        delete data.total_score;
      });
    }
    
    return report;
  }

  // Usage
  const interviewIds = ['int_abc123', 'int_def456', 'int_ghi789']; // ... more IDs
  generateHiringReport(interviewIds)
    .then(report => {
      console.log('📊 Hiring Report:', JSON.stringify(report, null, 2));
    })
    .catch(console.error);
  ```

  ```python Analytics Pipeline theme={null}
  import requests
  import pandas as pd
  from datetime import datetime, timedelta
  from typing import List, Dict, Optional

  class JobHiveAnalytics:
      def __init__(self, api_key: str):
          self.api_key = api_key
          self.base_url = 'https://backend.jobhive.ai/v1'
          
      def get_bulk_results(self, interview_ids: List[str], include_transcripts: bool = False) -> List[Dict]:
          """Retrieve results for multiple interviews efficiently"""
          batch_size = 20
          all_results = []
          
          for i in range(0, len(interview_ids), batch_size):
              batch = interview_ids[i:i + batch_size]
              
              response = requests.post(f'{self.base_url}/interviews/bulk-results',
                  headers={'Authorization': f'Bearer {self.api_key}'},
                  json={
                      'interview_ids': batch,
                      'include_results': True,
                      'include_transcripts': include_transcripts
                  }
              )
              
              if response.status_code == 200:
                  batch_results = response.json()
                  all_results.extend(batch_results['data'])
              
              # Rate limiting
              if i + batch_size < len(interview_ids):
                  time.sleep(1)
          
          return all_results
      
      def export_to_dataframe(self, interviews: List[Dict]) -> pd.DataFrame:
          """Convert interview results to pandas DataFrame for analysis"""
          
          data = []
          for interview in interviews:
              if interview['status'] == 'completed' and interview.get('results'):
                  row = {
                      'interview_id': interview['id'],
                      'candidate_email': interview['candidate_email'],
                      'position': interview['position'],
                      'skills': ', '.join(interview['skills']),
                      'overall_score': interview['results']['overall_score'],
                      'technical_score': interview['results']['technical_score'], 
                      'communication_score': interview['results']['communication_score'],
                      'recommendation': interview['results']['recommendation'],
                      'completed_at': interview['schedule']['completed_at'],
                      'duration_actual': interview['duration']['actual_minutes']
                  }
                  
                  # Add individual skill scores
                  for skill_assessment in interview['results'].get('skill_assessments', []):
                      row[f"skill_{skill_assessment['skill'].lower().replace(' ', '_')}"] = skill_assessment['score']
                  
                  data.append(row)
          
          return pd.DataFrame(data)
      
      def generate_comprehensive_report(self, start_date: str, end_date: str) -> Dict:
          """Generate comprehensive hiring analytics report"""
          
          # Get all interviews in date range
          interviews = self.get_interviews_by_date_range(start_date, end_date)
          
          # Convert to DataFrame for analysis
          df = self.export_to_dataframe(interviews)
          
          if df.empty:
              return {'error': 'No completed interviews found in date range'}
          
          report = {
              'summary': {
                  'total_interviews': len(df),
                  'date_range': f"{start_date} to {end_date}",
                  'average_score': df['overall_score'].mean(),
                  'score_std': df['overall_score'].std(),
                  'average_duration': df['duration_actual'].mean()
              },
              'recommendations': df['recommendation'].value_counts().to_dict(),
              'position_analysis': {},
              'skill_analysis': {},
              'score_distribution': {
                  '90-100': len(df[df['overall_score'] >= 90]),
                  '80-89': len(df[(df['overall_score'] >= 80) & (df['overall_score'] < 90)]), 
                  '70-79': len(df[(df['overall_score'] >= 70) & (df['overall_score'] < 80)]),
                  '60-69': len(df[(df['overall_score'] >= 60) & (df['overall_score'] < 70)]),
                  'Below 60': len(df[df['overall_score'] < 60])
              }
          }
          
          # Position analysis
          for position in df['position'].unique():
              pos_data = df[df['position'] == position]
              report['position_analysis'][position] = {
                  'count': len(pos_data),
                  'avg_score': pos_data['overall_score'].mean(),
                  'hire_rate': len(pos_data[pos_data['recommendation'] == 'hire']) / len(pos_data),
                  'avg_duration': pos_data['duration_actual'].mean()
              }
          
          return report
      
      def export_results_csv(self, interviews: List[Dict], filename: str):
          """Export results to CSV for external analysis"""
          df = self.export_to_dataframe(interviews)
          df.to_csv(filename, index=False)
          print(f"📊 Exported {len(df)} interview results to {filename}")

  # Usage
  analytics = JobHiveAnalytics(os.environ['JOBHIVE_API_KEY'])

  # Generate report for last 30 days
  end_date = datetime.now().isoformat()
  start_date = (datetime.now() - timedelta(days=30)).isoformat()

  report = analytics.generate_comprehensive_report(start_date, end_date)
  print("📈 Hiring Analytics Report:")
  print(json.dumps(report, indent=2, default=str))
  ```
</CodeGroup>

## Performance Optimization

### Parallel Processing Strategies

<AccordionGroup>
  <Accordion title="Concurrent Request Patterns">
    **JavaScript Promise.all Pattern**

    ```javascript theme={null}
    async function createInterviewsParallel(candidates) {
      const MAX_CONCURRENT = 5;
      const results = [];
      
      for (let i = 0; i < candidates.length; i += MAX_CONCURRENT) {
        const batch = candidates.slice(i, i + MAX_CONCURRENT);
        
        const promises = batch.map(candidate => 
          createSingleInterview(candidate)
            .catch(error => ({ error, candidate }))
        );
        
        const batchResults = await Promise.all(promises);
        results.push(...batchResults);
      }
      
      return results;
    }
    ```

    **Python ThreadPoolExecutor**

    ```python theme={null}
    from concurrent.futures import ThreadPoolExecutor, as_completed
    import time

    def create_interviews_parallel(candidates, max_workers=5):
        results = []
        
        with ThreadPoolExecutor(max_workers=max_workers) as executor:
            future_to_candidate = {
                executor.submit(create_single_interview, candidate): candidate 
                for candidate in candidates
            }
            
            for future in as_completed(future_to_candidate):
                candidate = future_to_candidate[future]
                try:
                    result = future.result()
                    results.append(result)
                except Exception as e:
                    results.append({'error': str(e), 'candidate': candidate})
                    
        return results
    ```
  </Accordion>

  <Accordion title="Smart Rate Limiting">
    **Adaptive Delay Strategy**

    ```javascript theme={null}
    class RateLimitedClient {
      constructor(apiKey, requestsPerMinute = 300) {
        this.apiKey = apiKey;
        this.requestsPerMinute = requestsPerMinute;
        this.requestTimes = [];
      }
      
      async makeRequest(url, options) {
        await this.enforceRateLimit();
        
        const response = await fetch(url, {
          ...options,
          headers: {
            'Authorization': `Bearer ${this.apiKey}`,
            ...options.headers
          }
        });
        
        this.requestTimes.push(Date.now());
        
        if (response.status === 429) {
          const retryAfter = response.headers.get('X-RateLimit-Retry-After');
          await new Promise(resolve => setTimeout(resolve, retryAfter * 1000));
          return this.makeRequest(url, options);
        }
        
        return response;
      }
      
      async enforceRateLimit() {
        const now = Date.now();
        const oneMinuteAgo = now - 60000;
        
        // Remove old requests
        this.requestTimes = this.requestTimes.filter(time => time > oneMinuteAgo);
        
        if (this.requestTimes.length >= this.requestsPerMinute) {
          const oldestRequest = this.requestTimes[0];
          const waitTime = 60000 - (now - oldestRequest);
          
          if (waitTime > 0) {
            await new Promise(resolve => setTimeout(resolve, waitTime));
          }
        }
      }
    }
    ```
  </Accordion>

  <Accordion title="Batch Size Optimization">
    **Dynamic Batch Sizing**

    ```python theme={null}
    class OptimalBatchProcessor:
        def __init__(self, api_key):
            self.api_key = api_key
            self.optimal_batch_size = 50
            self.performance_history = []
        
        def process_candidates(self, candidates):
            total_processed = 0
            start_time = time.time()
            
            while total_processed < len(candidates):
                batch_start = total_processed
                batch_end = min(total_processed + self.optimal_batch_size, len(candidates))
                batch = candidates[batch_start:batch_end]
                
                batch_start_time = time.time()
                result = self.create_bulk_interviews(batch)
                batch_duration = time.time() - batch_start_time
                
                # Track performance
                self.performance_history.append({
                    'batch_size': len(batch),
                    'duration': batch_duration,
                    'success_rate': result['data']['successful_count'] / len(batch)
                })
                
                # Adjust batch size based on performance
                self.adjust_batch_size()
                
                total_processed = batch_end
            
            total_duration = time.time() - start_time
            return {
                'total_processed': total_processed,
                'duration': total_duration,
                'throughput': total_processed / total_duration
            }
        
        def adjust_batch_size(self):
            if len(self.performance_history) < 3:
                return
            
            recent_performance = self.performance_history[-3:]
            avg_duration = sum(p['duration'] for p in recent_performance) / 3
            avg_success_rate = sum(p['success_rate'] for p in recent_performance) / 3
            
            # Increase batch size if performing well
            if avg_duration < 5 and avg_success_rate > 0.95:
                self.optimal_batch_size = min(self.optimal_batch_size + 10, 100)
            # Decrease batch size if struggling
            elif avg_duration > 15 or avg_success_rate < 0.8:
                self.optimal_batch_size = max(self.optimal_batch_size - 10, 10)
    ```
  </Accordion>
</AccordionGroup>

## Monitoring & Observability

### Bulk Operation Metrics

Track the performance and success of your bulk operations:

<CodeGroup>
  ```javascript Metrics Collection theme={null}
  class BulkOperationMetrics {
    constructor() {
      this.metrics = {
        total_requests: 0,
        successful_interviews: 0,
        failed_interviews: 0,
        average_batch_time: 0,
        error_rates: {},
        throughput_per_minute: 0
      };
      this.start_time = Date.now();
    }
    
    recordBatchResult(batchSize, duration, result) {
      this.metrics.total_requests += batchSize;
      this.metrics.successful_interviews += result.data.successful_count;
      this.metrics.failed_interviews += result.data.failed_count;
      
      // Update average batch time
      this.metrics.average_batch_time = 
        (this.metrics.average_batch_time + duration) / 2;
      
      // Track error patterns
      result.data.failed.forEach(failure => {
        const errorCode = failure.error.code;
        this.metrics.error_rates[errorCode] = 
          (this.metrics.error_rates[errorCode] || 0) + 1;
      });
      
      // Calculate throughput
      const elapsed_minutes = (Date.now() - this.start_time) / 60000;
      this.metrics.throughput_per_minute = 
        this.metrics.successful_interviews / elapsed_minutes;
    }
    
    getReport() {
      const success_rate = this.metrics.successful_interviews / 
        (this.metrics.successful_interviews + this.metrics.failed_interviews);
      
      return {
        ...this.metrics,
        success_rate: success_rate,
        total_runtime_minutes: (Date.now() - this.start_time) / 60000
      };
    }
  }

  // Usage
  const metrics = new BulkOperationMetrics();

  async function processCandidatesWithMetrics(candidates) {
    const batches = chunkArray(candidates, 50);
    
    for (const batch of batches) {
      const start = Date.now();
      const result = await createBulkInterviews(batch);
      const duration = Date.now() - start;
      
      metrics.recordBatchResult(batch.length, duration, result);
      
      console.log(`Batch completed: ${result.data.successful_count}/${batch.length} successful`);
    }
    
    const report = metrics.getReport();
    console.log('📊 Final Metrics:', report);
  }
  ```

  ```python Performance Dashboard theme={null}
  import json
  import time
  from dataclasses import dataclass, asdict
  from typing import Dict, List

  @dataclass
  class BulkMetrics:
      total_candidates: int = 0
      successful_interviews: int = 0
      failed_interviews: int = 0
      total_batches: int = 0
      average_batch_time: float = 0
      error_breakdown: Dict[str, int] = None
      start_time: float = None
      
      def __post_init__(self):
          if self.error_breakdown is None:
              self.error_breakdown = {}
          if self.start_time is None:
              self.start_time = time.time()

  class BulkOperationDashboard:
      def __init__(self):
          self.metrics = BulkMetrics()
          self.batch_history = []
      
      def record_batch(self, batch_size: int, duration: float, result: Dict):
          """Record metrics for a completed batch"""
          self.metrics.total_candidates += batch_size
          self.metrics.successful_interviews += result['data']['successful_count']
          self.metrics.failed_interviews += result['data']['failed_count']
          self.metrics.total_batches += 1
          
          # Update average batch time
          self.metrics.average_batch_time = (
              (self.metrics.average_batch_time * (self.metrics.total_batches - 1) + duration)
              / self.metrics.total_batches
          )
          
          # Track error patterns
          for failure in result['data']['failed']:
              error_code = failure['error']['code']
              self.metrics.error_breakdown[error_code] = (
                  self.metrics.error_breakdown.get(error_code, 0) + 1
              )
          
          # Store batch history for trend analysis
          self.batch_history.append({
              'batch_number': self.metrics.total_batches,
              'timestamp': time.time(),
              'batch_size': batch_size,
              'duration': duration,
              'success_rate': result['data']['successful_count'] / batch_size,
              'throughput': batch_size / duration if duration > 0 else 0
          })
      
      def get_live_metrics(self) -> Dict:
          """Get current performance metrics"""
          elapsed_time = time.time() - self.metrics.start_time
          total_processed = self.metrics.successful_interviews + self.metrics.failed_interviews
          
          return {
              'summary': {
                  'total_processed': total_processed,
                  'success_rate': self.metrics.successful_interviews / total_processed if total_processed > 0 else 0,
                  'interviews_per_minute': (self.metrics.successful_interviews / elapsed_time) * 60 if elapsed_time > 0 else 0,
                  'average_batch_time': self.metrics.average_batch_time,
                  'runtime_minutes': elapsed_time / 60
              },
              'error_analysis': self.metrics.error_breakdown,
              'recent_performance': self.batch_history[-5:] if len(self.batch_history) >= 5 else self.batch_history
          }
      
      def export_performance_report(self, filename: str):
          """Export detailed performance report"""
          report = {
              'metrics': asdict(self.metrics),
              'batch_history': self.batch_history,
              'analysis': self.get_live_metrics()
          }
          
          with open(filename, 'w') as f:
              json.dump(report, f, indent=2, default=str)
          
          print(f"📊 Performance report exported to {filename}")

  # Usage example
  dashboard = BulkOperationDashboard()

  def process_with_monitoring(candidates):
      batches = [candidates[i:i+50] for i in range(0, len(candidates), 50)]
      
      for i, batch in enumerate(batches):
          print(f"Processing batch {i+1}/{len(batches)}...")
          
          start_time = time.time()
          result = create_bulk_interviews(batch)  # Your bulk creation function
          duration = time.time() - start_time
          
          dashboard.record_batch(len(batch), duration, result)
          
          # Print live metrics every 5 batches
          if (i + 1) % 5 == 0:
              metrics = dashboard.get_live_metrics()
              print(f"📈 Current performance: {metrics['summary']['interviews_per_minute']:.1f} interviews/min")
              print(f"✅ Success rate: {metrics['summary']['success_rate']:.1%}")
      
      # Final report
      dashboard.export_performance_report('bulk_operation_report.json')
      return dashboard.get_live_metrics()
  ```
</CodeGroup>

## Best Practices

### Optimization Guidelines

<CardGroup cols={2}>
  <Card title="Batch Size Strategy" icon="layer-group">
    **Recommended Sizes**

    * Start with 50 interviews per batch
    * Increase to 100 for stable operations
    * Reduce to 25 if seeing high error rates
    * Monitor performance and adjust dynamically
  </Card>

  <Card title="Error Handling" icon="exclamation-triangle">
    **Resilience Patterns**

    * Retry failed interviews individually
    * Log all failures for manual review
    * Implement exponential backoff
    * Set maximum retry limits
  </Card>

  <Card title="Rate Limiting" icon="gauge">
    **Respect Limits**

    * Stay under 80% of rate limit
    * Implement request queuing
    * Use intelligent delays between batches
    * Monitor rate limit headers
  </Card>

  <Card title="Data Validation" icon="check-circle">
    **Quality Assurance**

    * Validate email formats before API calls
    * Check required fields completeness
    * Remove duplicates from candidate lists
    * Sanitize skill inputs
  </Card>
</CardGroup>

### Common Pitfalls to Avoid

<Warning>
  **Anti-Patterns**

  * **Don't** send all interviews in a single massive request
  * **Don't** ignore rate limit headers and retry immediately
  * **Don't** assume all interviews will succeed
  * **Don't** forget to handle partial failures gracefully
</Warning>

<Note>
  **Pro Tips**

  * Use webhooks for real-time updates instead of polling
  * Process results asynchronously to avoid blocking operations
  * Implement comprehensive logging for debugging
  * Test with small batches before scaling up
</Note>

## Next Steps

<CardGroup cols={2}>
  <Card title="Webhook Integration" icon="webhook" href="/api-reference/endpoint/webhook">
    Set up real-time notifications for bulk operations
  </Card>

  <Card title="Error Handling Guide" icon="shield-exclamation" href="/api-reference/error-handling">
    Comprehensive error handling patterns
  </Card>

  <Card title="Performance Monitoring" icon="chart-line" href="/integration/real-time-monitoring">
    Advanced monitoring and alerting setup
  </Card>

  <Card title="Rate Limiting" icon="clock" href="/api-reference/rate-limiting">
    Optimize request patterns for maximum throughput
  </Card>
</CardGroup>
