| """ |
| 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): |
| |
| assert prediction_entropy(0.0) == pytest.approx(0.0, abs=1e-6) |
|
|
| async def test_entropy_at_certain_one(self): |
| |
| 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): |
| |
| 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): |
| |
| assert should_queue_for_review(0.4, entropy_threshold=0.5) is True |
|
|
| async def test_custom_threshold_very_high(self): |
| |
| 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): |
| |
| assert uncertainty_band(0.85) == "CERTAIN" |
|
|
| async def test_boundary_low_certain(self): |
| |
| 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"} |
|
|