# MPC Survey Analytics Methodology

## Complete Documentation of Calculation Logic

**Version:** 1.0  
**Last Updated:** January 2026  
**Platform:** Talent Arabia Survey System

---

## Table of Contents

1. [Question Types Overview](#1-question-types-overview)
2. [Likert Scale (1-5) Calculations](#2-likert-scale-1-5-calculations)
3. [NPS (0-10) Calculations](#3-nps-0-10-calculations)
4. [Rating Scale (1-10) Calculations](#4-rating-scale-1-10-calculations)
5. [Single Choice Calculations](#5-single-choice-calculations)
6. [Multiple Choice Calculations](#6-multiple-choice-calculations)
7. [Open Text Analysis](#7-open-text-analysis)
8. [Engagement Index Calculation](#8-engagement-index-calculation)
9. [Category Scores](#9-category-scores)
10. [Demographic Breakdowns](#10-demographic-breakdowns)
11. [Benchmarking](#11-benchmarking)
12. [Statistical Considerations](#12-statistical-considerations)

---

## 1. Question Types Overview

| Type | Scale | Use Case | Primary Metric |
|------|-------|----------|----------------|
| Likert Scale | 1-5 | Agreement/Satisfaction | Mean Score, % Favorable |
| NPS | 0-10 | Loyalty/Recommendation | NPS Score (-100 to +100) |
| Rating | 1-10 | General Rating | Mean Score |
| Single Choice | Categorical | Demographics, Preferences | Frequency Distribution |
| Multiple Choice | Categorical | Multi-select Options | Frequency per Option |
| Open Text | Qualitative | Feedback, Comments | Thematic Analysis |

---

## 2. Likert Scale (1-5) Calculations

### 2.1 Scale Definition

| Value | Label | Classification |
|-------|-------|----------------|
| 1 | Strongly Disagree | Unfavorable |
| 2 | Disagree | Unfavorable |
| 3 | Neutral | Neutral |
| 4 | Agree | Favorable |
| 5 | Strongly Agree | Favorable |

### 2.2 Basic Metrics

#### Mean Score
```
Mean = Σ(all responses) / n

Example:
Responses: [5, 4, 4, 3, 5, 2, 4, 5, 4, 3]
Sum = 39
n = 10
Mean = 39 / 10 = 3.90
```

#### Standard Deviation
```
SD = √[Σ(xi - mean)² / (n-1)]

Example (using mean = 3.90):
Deviations: [1.1, 0.1, 0.1, -0.9, 1.1, -1.9, 0.1, 1.1, 0.1, -0.9]
Squared: [1.21, 0.01, 0.01, 0.81, 1.21, 3.61, 0.01, 1.21, 0.01, 0.81]
Sum of squares = 8.90
SD = √(8.90 / 9) = √0.989 = 0.99
```

### 2.3 Distribution Analysis

#### Response Distribution
```
For each value (1-5):
Count = number of responses with that value
Percentage = (Count / Total Responses) × 100

Example:
Value 1: 0 responses (0%)
Value 2: 1 response (10%)
Value 3: 2 responses (20%)
Value 4: 4 responses (40%)
Value 5: 3 responses (30%)
```

#### Favorability Score
```
Favorable = responses with value 4 or 5
Unfavorable = responses with value 1 or 2
Neutral = responses with value 3

% Favorable = (Favorable Count / Total) × 100
% Unfavorable = (Unfavorable Count / Total) × 100
% Neutral = (Neutral Count / Total) × 100

Example:
Favorable: 7 responses (70%)
Neutral: 2 responses (20%)
Unfavorable: 1 response (10%)
```

### 2.4 Score Interpretation

| Mean Score | Interpretation | Color Code |
|------------|----------------|------------|
| 4.5 - 5.0 | Excellent | Dark Green |
| 4.0 - 4.49 | Good | Green |
| 3.5 - 3.99 | Moderate | Yellow |
| 3.0 - 3.49 | Needs Attention | Orange |
| < 3.0 | Critical | Red |

### 2.5 Code Implementation
```php
// File: app/Services/AnalyticsService.php

public function calculateLikertMetrics(array $responses): array
{
    $values = array_filter($responses, fn($v) => is_numeric($v) && $v >= 1 && $v <= 5);
    
    if (empty($values)) {
        return $this->emptyMetrics();
    }
    
    $count = count($values);
    $sum = array_sum($values);
    $mean = $sum / $count;
    
    // Standard Deviation
    $squaredDiffs = array_map(fn($v) => pow($v - $mean, 2), $values);
    $variance = array_sum($squaredDiffs) / ($count - 1);
    $stdDev = sqrt($variance);
    
    // Distribution
    $distribution = array_count_values($values);
    $percentages = [];
    for ($i = 1; $i <= 5; $i++) {
        $percentages[$i] = [
            'count' => $distribution[$i] ?? 0,
            'percentage' => (($distribution[$i] ?? 0) / $count) * 100
        ];
    }
    
    // Favorability
    $favorable = count(array_filter($values, fn($v) => $v >= 4));
    $unfavorable = count(array_filter($values, fn($v) => $v <= 2));
    $neutral = count(array_filter($values, fn($v) => $v == 3));
    
    return [
        'mean' => round($mean, 2),
        'std_dev' => round($stdDev, 2),
        'count' => $count,
        'distribution' => $percentages,
        'favorable_percent' => round(($favorable / $count) * 100, 1),
        'unfavorable_percent' => round(($unfavorable / $count) * 100, 1),
        'neutral_percent' => round(($neutral / $count) * 100, 1),
    ];
}
```

---

## 3. NPS (0-10) Calculations

### 3.1 Scale Definition

| Value Range | Classification | Description |
|-------------|----------------|-------------|
| 9 - 10 | Promoters | Loyal enthusiasts |
| 7 - 8 | Passives | Satisfied but unenthusiastic |
| 0 - 6 | Detractors | Unhappy, can damage brand |

### 3.2 NPS Score Calculation
```
NPS = % Promoters - % Detractors

Range: -100 to +100

Example:
Total Responses: 100
- Promoters (9-10): 45 responses = 45%
- Passives (7-8): 35 responses = 35%
- Detractors (0-6): 20 responses = 20%

NPS = 45% - 20% = +25
```

### 3.3 NPS Interpretation

| NPS Score | Interpretation | Industry Benchmark |
|-----------|----------------|-------------------|
| 70+ | World Class | Top companies |
| 50-69 | Excellent | Above average |
| 30-49 | Good | Average |
| 0-29 | Needs Improvement | Below average |
| < 0 | Critical | Poor performance |

### 3.4 Additional NPS Metrics

#### Response Distribution
```
For each value (0-10):
Percentage = (Count / Total) × 100
```

#### Category Percentages
```
% Promoters = (Count of 9-10) / Total × 100
% Passives = (Count of 7-8) / Total × 100
% Detractors = (Count of 0-6) / Total × 100
```

### 3.5 Code Implementation
```php
public function calculateNPSMetrics(array $responses): array
{
    $values = array_filter($responses, fn($v) => is_numeric($v) && $v >= 0 && $v <= 10);
    
    if (empty($values)) {
        return $this->emptyNPSMetrics();
    }
    
    $count = count($values);
    
    // Categorize responses
    $promoters = count(array_filter($values, fn($v) => $v >= 9));
    $passives = count(array_filter($values, fn($v) => $v >= 7 && $v <= 8));
    $detractors = count(array_filter($values, fn($v) => $v <= 6));
    
    // Calculate percentages
    $promoterPercent = ($promoters / $count) * 100;
    $passivePercent = ($passives / $count) * 100;
    $detractorPercent = ($detractors / $count) * 100;
    
    // NPS Score
    $nps = $promoterPercent - $detractorPercent;
    
    // Distribution
    $distribution = array_count_values($values);
    $percentages = [];
    for ($i = 0; $i <= 10; $i++) {
        $percentages[$i] = [
            'count' => $distribution[$i] ?? 0,
            'percentage' => round((($distribution[$i] ?? 0) / $count) * 100, 1)
        ];
    }
    
    return [
        'nps_score' => round($nps, 0),
        'promoters' => [
            'count' => $promoters,
            'percentage' => round($promoterPercent, 1)
        ],
        'passives' => [
            'count' => $passives,
            'percentage' => round($passivePercent, 1)
        ],
        'detractors' => [
            'count' => $detractors,
            'percentage' => round($detractorPercent, 1)
        ],
        'distribution' => $percentages,
        'mean' => round(array_sum($values) / $count, 2),
        'count' => $count
    ];
}
```

---

## 4. Rating Scale (1-10) Calculations

### 4.1 Scale Definition

| Value Range | Classification |
|-------------|----------------|
| 9 - 10 | Excellent |
| 7 - 8 | Good |
| 5 - 6 | Average |
| 3 - 4 | Below Average |
| 1 - 2 | Poor |

### 4.2 Metrics
```
Mean = Σ(all responses) / n
Median = Middle value when sorted
Mode = Most frequent value

% High Rating (8-10) = (Count of 8-10) / Total × 100
% Low Rating (1-4) = (Count of 1-4) / Total × 100
```

### 4.3 Code Implementation
```php
public function calculateRatingMetrics(array $responses): array
{
    $values = array_filter($responses, fn($v) => is_numeric($v) && $v >= 1 && $v <= 10);
    
    if (empty($values)) {
        return $this->emptyRatingMetrics();
    }
    
    sort($values);
    $count = count($values);
    $mean = array_sum($values) / $count;
    
    // Median
    $middle = floor($count / 2);
    $median = $count % 2 ? $values[$middle] : ($values[$middle - 1] + $values[$middle]) / 2;
    
    // Mode
    $frequency = array_count_values($values);
    $mode = array_search(max($frequency), $frequency);
    
    // Categories
    $excellent = count(array_filter($values, fn($v) => $v >= 9));
    $good = count(array_filter($values, fn($v) => $v >= 7 && $v <= 8));
    $average = count(array_filter($values, fn($v) => $v >= 5 && $v <= 6));
    $poor = count(array_filter($values, fn($v) => $v <= 4));
    
    return [
        'mean' => round($mean, 2),
        'median' => round($median, 2),
        'mode' => $mode,
        'count' => $count,
        'excellent_percent' => round(($excellent / $count) * 100, 1),
        'good_percent' => round(($good / $count) * 100, 1),
        'average_percent' => round(($average / $count) * 100, 1),
        'poor_percent' => round(($poor / $count) * 100, 1),
    ];
}
```

---

## 5. Single Choice Calculations

### 5.1 Metrics
```
For each option:
- Count = Number of selections
- Percentage = (Count / Total Responses) × 100

Example Question: "What is your department?"
Options: HR, IT, Finance, Operations

Results:
- HR: 25 responses (25%)
- IT: 30 responses (30%)
- Finance: 20 responses (20%)
- Operations: 25 responses (25%)
Total: 100 responses
```

### 5.2 Code Implementation
```php
public function calculateSingleChoiceMetrics(array $responses, array $options): array
{
    $validResponses = array_filter($responses, fn($v) => in_array($v, $options));
    $count = count($validResponses);
    
    if ($count === 0) {
        return ['options' => [], 'count' => 0];
    }
    
    $frequency = array_count_values($validResponses);
    
    $results = [];
    foreach ($options as $option) {
        $optionCount = $frequency[$option] ?? 0;
        $results[$option] = [
            'count' => $optionCount,
            'percentage' => round(($optionCount / $count) * 100, 1)
        ];
    }
    
    // Sort by count descending
    uasort($results, fn($a, $b) => $b['count'] - $a['count']);
    
    return [
        'options' => $results,
        'count' => $count,
        'most_selected' => array_key_first($results),
        'least_selected' => array_key_last($results)
    ];
}
```

---

## 6. Multiple Choice Calculations

### 6.1 Metrics
```
For each option:
- Count = Number of times selected
- Percentage = (Count / Total Respondents) × 100

Note: Percentages can exceed 100% total since respondents can select multiple options.

Example Question: "Which benefits are important to you?" (Select all that apply)
Options: Health Insurance, Retirement Plan, Flexible Hours, Remote Work

Results (50 respondents):
- Health Insurance: 45 selections (90%)
- Retirement Plan: 35 selections (70%)
- Flexible Hours: 40 selections (80%)
- Remote Work: 38 selections (76%)

Average selections per respondent: (45+35+40+38) / 50 = 3.16
```

### 6.2 Code Implementation
```php
public function calculateMultipleChoiceMetrics(array $responses, array $options): array
{
    // Each response is an array of selected options
    $totalRespondents = count($responses);
    
    if ($totalRespondents === 0) {
        return ['options' => [], 'count' => 0];
    }
    
    // Flatten and count
    $allSelections = [];
    foreach ($responses as $response) {
        if (is_array($response)) {
            foreach ($response as $selection) {
                $allSelections[] = $selection;
            }
        }
    }
    
    $frequency = array_count_values($allSelections);
    $totalSelections = count($allSelections);
    
    $results = [];
    foreach ($options as $option) {
        $optionCount = $frequency[$option] ?? 0;
        $results[$option] = [
            'count' => $optionCount,
            'percentage_of_respondents' => round(($optionCount / $totalRespondents) * 100, 1),
            'percentage_of_selections' => round(($optionCount / $totalSelections) * 100, 1)
        ];
    }
    
    // Sort by count descending
    uasort($results, fn($a, $b) => $b['count'] - $a['count']);
    
    return [
        'options' => $results,
        'respondent_count' => $totalRespondents,
        'total_selections' => $totalSelections,
        'avg_selections_per_respondent' => round($totalSelections / $totalRespondents, 2)
    ];
}
```

---

## 7. Open Text Analysis

### 7.1 Basic Metrics
```
- Response Count = Total text responses
- Response Rate = (Responses with text / Total respondents) × 100
- Average Word Count = Total words / Response count
```

### 7.2 Thematic Analysis (Manual)

Open text responses require manual or AI-assisted analysis:

1. **Keyword Frequency** - Count common words/phrases
2. **Sentiment Classification** - Positive, Negative, Neutral
3. **Theme Categorization** - Group responses by topic

### 7.3 Code Implementation
```php
public function calculateOpenTextMetrics(array $responses): array
{
    $validResponses = array_filter($responses, fn($v) => !empty(trim($v)));
    $count = count($validResponses);
    
    if ($count === 0) {
        return ['count' => 0, 'responses' => []];
    }
    
    // Word count analysis
    $totalWords = 0;
    $wordCounts = [];
    
    foreach ($validResponses as $response) {
        $words = str_word_count($response);
        $totalWords += $words;
        $wordCounts[] = $words;
    }
    
    // Simple keyword extraction (top words)
    $allText = implode(' ', $validResponses);
    $allText = strtolower(preg_replace('/[^a-zA-Z\s]/', '', $allText));
    $wordList = str_word_count($allText, 1);
    
    // Remove common stop words
    $stopWords = ['the', 'a', 'an', 'is', 'are', 'was', 'were', 'be', 'been', 
                  'being', 'have', 'has', 'had', 'do', 'does', 'did', 'will', 
                  'would', 'could', 'should', 'may', 'might', 'must', 'shall',
                  'to', 'of', 'in', 'for', 'on', 'with', 'at', 'by', 'from',
                  'it', 'this', 'that', 'and', 'or', 'but', 'not', 'i', 'we', 'they'];
    
    $filteredWords = array_filter($wordList, fn($w) => !in_array($w, $stopWords) && strlen($w) > 2);
    $wordFrequency = array_count_values($filteredWords);
    arsort($wordFrequency);
    
    return [
        'count' => $count,
        'total_words' => $totalWords,
        'avg_word_count' => round($totalWords / $count, 1),
        'min_word_count' => min($wordCounts),
        'max_word_count' => max($wordCounts),
        'top_keywords' => array_slice($wordFrequency, 0, 20, true),
        'responses' => $validResponses // For display/export
    ];
}
```

---

## 8. Engagement Index Calculation

### 8.1 Methodology

The Engagement Index is a composite score derived from all Likert-scale questions.
```
Engagement Index = (Sum of all Likert means / Number of Likert questions)

Converted to percentage:
Engagement % = ((Engagement Index - 1) / 4) × 100
```

### 8.2 Employee Classification

Based on average Likert score per respondent:

| Average Score | Classification | Description |
|---------------|----------------|-------------|
| ≥ 4.0 | Engaged | Highly committed employees |
| 3.0 - 3.99 | Passive | Neutral, neither engaged nor disengaged |
| < 3.0 | Disengaged | At risk of leaving, low commitment |

### 8.3 Calculation Steps
```
Step 1: For each respondent, calculate their average Likert score
        Respondent Avg = Sum of their Likert answers / Number of Likert questions answered

Step 2: Classify each respondent
        If Avg >= 4.0 → Engaged
        If Avg >= 3.0 and < 4.0 → Passive
        If Avg < 3.0 → Disengaged

Step 3: Calculate percentages
        % Engaged = (Engaged count / Total respondents) × 100
        % Passive = (Passive count / Total respondents) × 100
        % Disengaged = (Disengaged count / Total respondents) × 100

Step 4: Calculate overall Engagement Index
        Engagement Index = Average of all respondent averages
```

### 8.4 Code Implementation
```php
public function calculateEngagementIndex(Survey $survey): array
{
    $responses = $survey->responses()->where('is_complete', true)->get();
    $likertQuestions = $survey->questions()->where('question_type', 'likert_5')->pluck('id');
    
    if ($responses->isEmpty() || $likertQuestions->isEmpty()) {
        return $this->emptyEngagementMetrics();
    }
    
    $respondentAverages = [];
    $allScores = [];
    
    foreach ($responses as $response) {
        $answers = $response->answers()
            ->whereIn('question_id', $likertQuestions)
            ->whereNotNull('answer_value')
            ->pluck('answer_value')
            ->map(fn($v) => (int) $v)
            ->filter(fn($v) => $v >= 1 && $v <= 5);
        
        if ($answers->isNotEmpty()) {
            $avg = $answers->avg();
            $respondentAverages[] = $avg;
            $allScores = array_merge($allScores, $answers->toArray());
        }
    }
    
    if (empty($respondentAverages)) {
        return $this->emptyEngagementMetrics();
    }
    
    // Classify respondents
    $engaged = count(array_filter($respondentAverages, fn($v) => $v >= 4.0));
    $passive = count(array_filter($respondentAverages, fn($v) => $v >= 3.0 && $v < 4.0));
    $disengaged = count(array_filter($respondentAverages, fn($v) => $v < 3.0));
    $totalRespondents = count($respondentAverages);
    
    // Overall metrics
    $engagementIndex = array_sum($respondentAverages) / $totalRespondents;
    $engagementPercent = (($engagementIndex - 1) / 4) * 100;
    
    return [
        'engagement_index' => round($engagementIndex, 2),
        'engagement_percent' => round($engagementPercent, 1),
        'classification' => [
            'engaged' => [
                'count' => $engaged,
                'percentage' => round(($engaged / $totalRespondents) * 100, 1)
            ],
            'passive' => [
                'count' => $passive,
                'percentage' => round(($passive / $totalRespondents) * 100, 1)
            ],
            'disengaged' => [
                'count' => $disengaged,
                'percentage' => round(($disengaged / $totalRespondents) * 100, 1)
            ]
        ],
        'total_respondents' => $totalRespondents,
        'total_responses' => count($allScores),
        'overall_mean' => round(array_sum($allScores) / count($allScores), 2)
    ];
}
```

---

## 9. Category Scores

### 9.1 Methodology

Each category score is the average of all Likert questions within that category.
```
Category Score = Σ(Question Means in Category) / Number of Questions in Category

Alternative: Weighted by response count
Category Score = Σ(Question Mean × Question Response Count) / Σ(Response Counts)
```

### 9.2 Category Ranking

Categories are ranked by their scores to identify:
- **Strengths**: Top-scoring categories (≥ 4.0)
- **Opportunities**: Mid-range categories (3.5 - 3.99)
- **Focus Areas**: Lower-scoring categories (< 3.5)

### 9.3 Code Implementation
```php
public function calculateCategoryScores(Survey $survey): array
{
    $categories = Category::active()->get();
    $results = [];
    
    foreach ($categories as $category) {
        $questions = $survey->questions()
            ->where('category_id', $category->id)
            ->where('question_type', 'likert_5')
            ->get();
        
        if ($questions->isEmpty()) {
            continue;
        }
        
        $questionScores = [];
        $totalResponses = 0;
        $weightedSum = 0;
        
        foreach ($questions as $question) {
            $answers = $question->answers()
                ->whereHas('response', fn($q) => $q->where('is_complete', true))
                ->pluck('answer_value')
                ->filter(fn($v) => is_numeric($v) && $v >= 1 && $v <= 5);
            
            if ($answers->isNotEmpty()) {
                $mean = $answers->avg();
                $count = $answers->count();
                
                $questionScores[] = [
                    'question' => $question->question_text,
                    'mean' => round($mean, 2),
                    'count' => $count
                ];
                
                $weightedSum += $mean * $count;
                $totalResponses += $count;
            }
        }
        
        if ($totalResponses > 0) {
            $categoryScore = $weightedSum / $totalResponses;
            
            $results[] = [
                'category_id' => $category->id,
                'category_name' => $category->name,
                'category_icon' => $category->icon,
                'category_color' => $category->color,
                'score' => round($categoryScore, 2),
                'question_count' => count($questionScores),
                'response_count' => $totalResponses,
                'questions' => $questionScores,
                'classification' => $this->classifyScore($categoryScore)
            ];
        }
    }
    
    // Sort by score descending
    usort($results, fn($a, $b) => $b['score'] <=> $a['score']);
    
    return $results;
}

private function classifyScore(float $score): string
{
    if ($score >= 4.5) return 'excellent';
    if ($score >= 4.0) return 'good';
    if ($score >= 3.5) return 'moderate';
    if ($score >= 3.0) return 'needs_attention';
    return 'critical';
}
```

---

## 10. Demographic Breakdowns

### 10.1 Methodology

For each demographic group, calculate separate metrics:
```
Example: Score by Department

Department: IT
- Responses: 25
- Engagement Index: 4.12
- % Engaged: 68%

Department: HR
- Responses: 15
- Engagement Index: 3.85
- % Engaged: 53%

Gap Analysis:
IT vs Company Average: +0.22 (higher)
HR vs Company Average: -0.05 (lower)
```

### 10.2 Statistical Significance

When comparing demographic groups:
```
Minimum sample size: 5 responses (for anonymity)
Statistical significance: p < 0.05 (t-test)
Effect size: Cohen's d

Small effect: d = 0.2
Medium effect: d = 0.5
Large effect: d = 0.8
```

### 10.3 Code Implementation
```php
public function calculateDemographicBreakdown(Survey $survey, string $demographicField): array
{
    $responses = $survey->responses()
        ->where('is_complete', true)
        ->whereNotNull("demographics->{$demographicField}")
        ->get();
    
    $groups = $responses->groupBy("demographics.{$demographicField}");
    $results = [];
    
    $overallMean = $this->calculateOverallMean($survey);
    
    foreach ($groups as $groupName => $groupResponses) {
        if ($groupResponses->count() < 5) {
            // Skip groups with fewer than 5 responses (anonymity protection)
            continue;
        }
        
        $likertQuestions = $survey->questions()->where('question_type', 'likert_5')->pluck('id');
        $allScores = [];
        
        foreach ($groupResponses as $response) {
            $answers = $response->answers()
                ->whereIn('question_id', $likertQuestions)
                ->pluck('answer_value')
                ->filter(fn($v) => is_numeric($v) && $v >= 1 && $v <= 5);
            
            $allScores = array_merge($allScores, $answers->toArray());
        }
        
        if (!empty($allScores)) {
            $groupMean = array_sum($allScores) / count($allScores);
            $gap = $groupMean - $overallMean;
            
            $results[] = [
                'group' => $groupName,
                'response_count' => $groupResponses->count(),
                'mean' => round($groupMean, 2),
                'gap_from_average' => round($gap, 2),
                'gap_direction' => $gap >= 0 ? 'above' : 'below',
                'gap_significance' => abs($gap) >= 0.3 ? 'significant' : 'minor'
            ];
        }
    }
    
    // Sort by mean descending
    usort($results, fn($a, $b) => $b['mean'] <=> $a['mean']);
    
    return [
        'demographic_field' => $demographicField,
        'overall_mean' => round($overallMean, 2),
        'groups' => $results,
        'highest_group' => $results[0]['group'] ?? null,
        'lowest_group' => end($results)['group'] ?? null,
        'max_gap' => max(array_column($results, 'gap_from_average')),
        'min_gap' => min(array_column($results, 'gap_from_average'))
    ];
}
```

---

## 11. Benchmarking

### 11.1 Internal Benchmarking

Compare against previous surveys:
```
Trend Analysis:
Q1 2025: 3.85
Q2 2025: 3.92
Q3 2025: 4.01
Q4 2025: 3.98

Change from Q1 to Q4: +0.13 (+3.4%)
```

### 11.2 External Benchmarking

Compare against industry standards:

| Metric | Company Score | Industry Average | Gap |
|--------|---------------|------------------|-----|
| Engagement | 3.95 | 3.80 | +0.15 |
| Leadership | 4.02 | 3.75 | +0.27 |
| Communication | 3.65 | 3.70 | -0.05 |

### 11.3 Code Implementation
```php
public function calculateBenchmark(Survey $survey, ?Survey $previousSurvey = null): array
{
    $currentMetrics = $this->calculateEngagementIndex($survey);
    
    $result = [
        'current' => $currentMetrics,
        'previous' => null,
        'change' => null,
        'trend' => null
    ];
    
    if ($previousSurvey) {
        $previousMetrics = $this->calculateEngagementIndex($previousSurvey);
        $change = $currentMetrics['engagement_index'] - $previousMetrics['engagement_index'];
        $percentChange = ($change / $previousMetrics['engagement_index']) * 100;
        
        $result['previous'] = $previousMetrics;
        $result['change'] = [
            'absolute' => round($change, 2),
            'percentage' => round($percentChange, 1),
            'direction' => $change >= 0 ? 'up' : 'down',
            'significance' => abs($change) >= 0.2 ? 'significant' : 'minor'
        ];
        $result['trend'] = $change >= 0 ? 'improving' : 'declining';
    }
    
    return $result;
}
```

---

## 12. Statistical Considerations

### 12.1 Sample Size Requirements

| Analysis Type | Minimum Sample | Recommended Sample |
|---------------|----------------|-------------------|
| Overall scores | 30 | 100+ |
| Category scores | 20 | 50+ |
| Demographic breakdown | 5 (per group) | 20+ (per group) |
| Statistical comparison | 30 (per group) | 50+ (per group) |

### 12.2 Confidence Intervals
```
For mean scores:
CI = Mean ± (t-value × Standard Error)

Standard Error = SD / √n

95% CI uses t-value ≈ 1.96 for large samples

Example:
Mean = 3.85, SD = 0.75, n = 100
SE = 0.75 / √100 = 0.075
95% CI = 3.85 ± (1.96 × 0.075)
95% CI = 3.85 ± 0.147
95% CI = [3.70, 4.00]
```

### 12.3 Anonymity Protection
```
Rules:
1. Minimum 5 responses per demographic group for reporting
2. Suppress results for groups with < 5 responses
3. Combine small groups into "Other" category
4. Never display individual responses linked to demographics
```

### 12.4 Data Quality Checks
```php
public function validateResponseQuality(SurveyResponse $response): array
{
    $answers = $response->answers()->get();
    $issues = [];
    
    // Check for straight-lining (same answer for all questions)
    $likertAnswers = $answers->where('question.question_type', 'likert_5')->pluck('answer_value');
    if ($likertAnswers->unique()->count() === 1 && $likertAnswers->count() > 5) {
        $issues[] = 'straight_lining';
    }
    
    // Check completion time (too fast = suspicious)
    $completionMinutes = $response->started_at->diffInMinutes($response->completed_at);
    $expectedMinutes = $answers->count() * 0.5; // 30 seconds per question
    if ($completionMinutes < $expectedMinutes * 0.3) {
        $issues[] = 'too_fast';
    }
    
    // Check for excessive neutral responses
    $neutralCount = $likertAnswers->filter(fn($v) => $v == 3)->count();
    if ($likertAnswers->count() > 0 && ($neutralCount / $likertAnswers->count()) > 0.8) {
        $issues[] = 'excessive_neutral';
    }
    
    return [
        'is_valid' => empty($issues),
        'issues' => $issues,
        'completion_time_minutes' => $completionMinutes
    ];
}
```

---

## Summary

| Metric | Formula | Range | Good Score |
|--------|---------|-------|------------|
| Likert Mean | Σ(responses) / n | 1-5 | ≥ 4.0 |
| % Favorable | (4+5 count / total) × 100 | 0-100% | ≥ 70% |
| NPS | % Promoters - % Detractors | -100 to +100 | ≥ 30 |
| Engagement Index | Avg of respondent means | 1-5 | ≥ 4.0 |
| % Engaged | (Engaged count / total) × 100 | 0-100% | ≥ 60% |

---

## Document History

| Version | Date | Author | Changes |
|---------|------|--------|---------|
| 1.0 | Jan 2026 | Talent Arabia | Initial documentation |

