File size: 3,272 Bytes
54286b5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
"""
Unit Tests β€” Active Learning Sampler (backend/active_learning/sampler.py)
No database or HTTP client required.
"""

import pytest

from backend.active_learning.sampler import (
    prediction_entropy,
    should_queue_for_review,
    uncertainty_band,
)


class TestPredictionEntropy:
    async def test_entropy_at_maximum_uncertainty(self):
        assert prediction_entropy(0.5) == pytest.approx(1.0)

    async def test_entropy_at_certain_zero(self):
        # p=0 β†’ certain negative β†’ entropy 0
        assert prediction_entropy(0.0) == pytest.approx(0.0, abs=1e-6)

    async def test_entropy_at_certain_one(self):
        # p=1 β†’ certain positive β†’ entropy 0
        assert prediction_entropy(1.0) == pytest.approx(0.0, abs=1e-6)

    async def test_entropy_symmetric(self):
        assert prediction_entropy(0.3) == pytest.approx(prediction_entropy(0.7))

    async def test_entropy_monotone_towards_half(self):
        # Entropy increases as p approaches 0.5 from 0
        assert prediction_entropy(0.1) < prediction_entropy(0.3) < prediction_entropy(0.5)

    async def test_entropy_returns_float(self):
        result = prediction_entropy(0.4)
        assert isinstance(result, float)

    async def test_entropy_never_exceeds_one(self):
        for p in [0.0, 0.1, 0.25, 0.5, 0.75, 0.9, 1.0]:
            assert prediction_entropy(p) <= 1.0 + 1e-9


class TestShouldQueueForReview:
    async def test_maximum_uncertainty_queued(self):
        assert should_queue_for_review(0.5) is True

    async def test_near_maximum_uncertainty_queued(self):
        assert should_queue_for_review(0.45) is True

    async def test_high_confidence_positive_not_queued(self):
        assert should_queue_for_review(0.95) is False

    async def test_high_confidence_negative_not_queued(self):
        assert should_queue_for_review(0.05) is False

    async def test_custom_threshold_lower(self):
        # With a very low threshold, even moderately uncertain predictions queue
        assert should_queue_for_review(0.4, entropy_threshold=0.5) is True

    async def test_custom_threshold_very_high(self):
        # With threshold > 1.0, nothing queues (entropy max is 1.0)
        assert should_queue_for_review(0.5, entropy_threshold=1.01) is False


class TestUncertaintyBand:
    @pytest.mark.parametrize("probability,expected_band", [
        (0.95, "CERTAIN"),
        (0.05, "CERTAIN"),
        (0.75, "CONFIDENT"),
        (0.25, "CONFIDENT"),
        (0.62, "BORDERLINE"),
        (0.38, "BORDERLINE"),
        (0.50, "UNCERTAIN"),
    ])
    async def test_uncertainty_band_mapping(self, probability, expected_band):
        assert uncertainty_band(probability) == expected_band

    async def test_boundary_high_certain(self):
        # 0.85 β†’ CERTAIN
        assert uncertainty_band(0.85) == "CERTAIN"

    async def test_boundary_low_certain(self):
        # 0.15 β†’ CERTAIN
        assert uncertainty_band(0.15) == "CERTAIN"

    async def test_band_returns_string(self):
        result = uncertainty_band(0.5)
        assert isinstance(result, str)

    async def test_all_bands_covered(self):
        bands = {uncertainty_band(p) for p in [0.5, 0.62, 0.75, 0.95]}
        assert bands == {"UNCERTAIN", "BORDERLINE", "CONFIDENT", "CERTAIN"}