File size: 5,413 Bytes
fcf8749
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
"""
Unit tests for fairness metrics calculations.
Tests Gini index and fairness score functions.
"""

import pytest
from app.services.fairness import (
    gini_index,
    calculate_fairness_score,
    calculate_global_fairness,
)


class TestGiniIndex:
    """Tests for the Gini index calculation."""
    
    def test_gini_perfect_equality(self):
        """All equal workloads should give Gini = 0."""
        workloads = [50.0, 50.0, 50.0, 50.0]
        result = gini_index(workloads)
        assert result == 0.0
    
    def test_gini_maximum_inequality(self):
        """One person has all work, Gini should be close to 1."""
        workloads = [100.0, 0.0, 0.0, 0.0]
        result = gini_index(workloads)
        # For n=4, maximum Gini is (n-1)/n = 0.75
        assert result > 0.7
    
    def test_gini_moderate_inequality(self):
        """Moderate spread should give moderate Gini."""
        workloads = [20.0, 40.0, 60.0, 80.0]
        result = gini_index(workloads)
        assert 0.1 < result < 0.4
    
    def test_gini_empty_list(self):
        """Empty list should return 0."""
        result = gini_index([])
        assert result == 0.0
    
    def test_gini_single_element(self):
        """Single element should return 0 (no inequality possible)."""
        result = gini_index([100.0])
        assert result == 0.0
    
    def test_gini_two_elements_equal(self):
        """Two equal elements should return 0."""
        result = gini_index([50.0, 50.0])
        assert result == 0.0
    
    def test_gini_two_elements_unequal(self):
        """Two unequal elements should give positive Gini."""
        result = gini_index([30.0, 70.0])
        assert result > 0.0
        assert result < 1.0
    
    def test_gini_zero_sum(self):
        """All zeros should return 0."""
        result = gini_index([0.0, 0.0, 0.0])
        assert result == 0.0
    
    def test_gini_realistic_workloads(self):
        """Realistic workload distribution."""
        # Simulating a fairly balanced day
        workloads = [62.5, 58.3, 71.2, 65.8, 55.4]
        result = gini_index(workloads)
        assert 0.0 < result < 0.15  # Should be well balanced


class TestFairnessScore:
    """Tests for individual fairness score calculation."""
    
    def test_fairness_exactly_average(self):
        """Workload exactly at average should give score of 1.0."""
        result = calculate_fairness_score(50.0, 50.0)
        assert result == 1.0
    
    def test_fairness_above_average(self):
        """Workload above average should give score < 1.0."""
        result = calculate_fairness_score(75.0, 50.0)
        assert result < 1.0
        assert result >= 0.0
    
    def test_fairness_below_average(self):
        """Workload below average should give score < 1.0."""
        result = calculate_fairness_score(25.0, 50.0)
        assert result < 1.0
        assert result >= 0.0
    
    def test_fairness_symmetric(self):
        """Same deviation above and below should give same score."""
        score_above = calculate_fairness_score(60.0, 50.0)
        score_below = calculate_fairness_score(40.0, 50.0)
        assert abs(score_above - score_below) < 0.01
    
    def test_fairness_zero_average(self):
        """Zero average should use 1.0 as denominator."""
        result = calculate_fairness_score(5.0, 0.0)
        assert result >= 0.0
        assert result <= 1.0
    
    def test_fairness_double_average(self):
        """Workload at double average."""
        result = calculate_fairness_score(100.0, 50.0)
        # |100 - 50| / 50 = 1.0, so fairness = 1 - 1 = 0
        assert result == 0.0


class TestGlobalFairness:
    """Tests for global fairness metrics calculation."""
    
    def test_global_fairness_empty(self):
        """Empty workloads should return zero metrics."""
        result = calculate_global_fairness([])
        assert result.avg_workload == 0.0
        assert result.std_dev == 0.0
        assert result.gini_index == 0.0
    
    def test_global_fairness_single(self):
        """Single workload should have zero std dev and gini."""
        result = calculate_global_fairness([50.0])
        assert result.avg_workload == 50.0
        assert result.std_dev == 0.0
        assert result.gini_index == 0.0
    
    def test_global_fairness_equal(self):
        """Equal workloads should have zero std dev and gini."""
        result = calculate_global_fairness([50.0, 50.0, 50.0])
        assert result.avg_workload == 50.0
        assert result.std_dev == 0.0
        assert result.gini_index == 0.0
    
    def test_global_fairness_varied(self):
        """Varied workloads should have positive std dev and gini."""
        result = calculate_global_fairness([30.0, 50.0, 70.0])
        assert result.avg_workload == 50.0
        assert result.std_dev > 0.0
        assert result.gini_index > 0.0
    
    def test_global_fairness_realistic(self):
        """Test with realistic workload values."""
        workloads = [62.5, 58.3, 71.2, 65.8, 55.4]
        result = calculate_global_fairness(workloads)
        
        # Check average
        expected_avg = sum(workloads) / len(workloads)
        assert abs(result.avg_workload - expected_avg) < 0.1
        
        # Check that metrics are reasonable
        assert result.std_dev > 0
        assert result.gini_index > 0
        assert result.gini_index < 0.2  # Should be fairly balanced