File size: 5,155 Bytes
25943d2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
import pytest
from services.wri_service import compute_wri

class TestComputeWRI:
    """TDD: Write failing tests first, then implement."""
    
    def test_standard_weights_safe(self):
        """WRI = 0.3(90) + 0.4(90) + 0.3(90) = 90.0 → safe (>= 88)"""
        result = compute_wri(d=90, g=90, p=90)
        assert result["wri"] == 90.0
        assert result["risk_status"] == "safe"
    
    def test_standard_weights_at_risk(self):
        """WRI = 0.3(60) + 0.4(70) + 0.3(65) = 18 + 28 + 19.5 = 65.5 → at_risk"""
        result = compute_wri(d=60, g=70, p=65)
        assert result["wri"] == 65.5
        assert result["risk_status"] == "at_risk"
    
    def test_standard_weights_intervene(self):
        """WRI = 0.3(78) + 0.4(76) + 0.3(74) = 76.0 → intervene (75-79)"""
        result = compute_wri(d=78, g=76, p=74)
        assert result["wri"] == 76.0
        assert result["risk_status"] == "intervene"
    
    def test_missing_g_defaults_to_d(self):
        """When G is None/missing, it defaults to D value."""
        result = compute_wri(d=70, g=None, p=80)
        assert result["wri"] == 0.3*70 + 0.4*70 + 0.3*80  # G=70 (defaulted from D)
        assert result["g_fallback"] is True
    
    def test_missing_p_defaults_to_d(self):
        """When P is None/missing, it defaults to D value."""
        result = compute_wri(d=75, g=85, p=None)
        assert result["wri"] == 0.3*75 + 0.4*85 + 0.3*75  # P=75 (defaulted from D)
        assert result["p_fallback"] is True
    
    def test_missing_g_and_p_both_default_to_d(self):
        """Both G and P missing → both default to D."""
        result = compute_wri(d=68, g=None, p=None)
        assert result["wri"] == 0.3*68 + 0.4*68 + 0.3*68  # = 68.0
        assert result["g_fallback"] is True
        assert result["p_fallback"] is True
    
    def test_no_diagnostic_returns_none(self):
        """When D is None → cannot compute WRI, return pending status."""
        result = compute_wri(d=None, g=80, p=90)
        assert result["wri"] is None
        assert result["risk_status"] == "pending_assessment"
    
    def test_invalid_weights_raise_error(self):
        """Weights that don't sum to 1.0 → ValueError."""
        with pytest.raises(ValueError, match="Weights must sum to 1.0"):
            compute_wri(d=80, g=80, p=80, weights={"w1": 0.5, "w2": 0.5, "w3": 0.5})
    
    def test_weights_close_to_one_are_valid(self):
        """Allow small floating-point tolerance (abs diff <= 0.001)."""
        result = compute_wri(d=90, g=90, p=90, weights={"w1": 0.333, "w2": 0.333, "w3": 0.334})
        assert result["wri"] == 90.0
        assert result["risk_status"] == "safe"
    
    def test_wri_rounds_to_2_decimal_places(self):
        """WRI result must be rounded to 2 decimal places."""
        result = compute_wri(d=77.777, g=88.888, p=66.666)
        assert result["wri"] == round(0.3*77.777 + 0.4*88.888 + 0.3*66.666, 2)
    
    def test_boundary_88_is_safe(self):
        """Exactly WRI=88 should be classified as safe."""
        result = compute_wri(d=88, g=88, p=88)
        assert result["wri"] == 88.0
        assert result["risk_status"] == "safe"

    def test_boundary_80_is_watch(self):
        """Exactly WRI=80 should be classified as watch."""
        result = compute_wri(d=80, g=80, p=80)
        assert result["wri"] == 80.0
        assert result["risk_status"] == "watch"

    def test_boundary_75_is_intervene(self):
        """Exactly WRI=75 should be classified as intervene."""
        result = compute_wri(d=75, g=75, p=75)
        assert result["wri"] == 75.0
        assert result["risk_status"] == "intervene"

    def test_boundary_68_is_critical(self):
        """Exactly WRI=68 should be classified as critical."""
        result = compute_wri(d=68, g=68, p=68)
        assert result["wri"] == 68.0
        assert result["risk_status"] == "critical"

    def test_boundary_67_is_at_risk(self):
        """WRI just below 68 (e.g. 67.99) should be at_risk."""
        result = compute_wri(d=67.99, g=67.99, p=67.99)
        assert result["wri"] == 67.99
        assert result["risk_status"] == "at_risk"
    
    def test_custom_weights(self):
        """Custom weights w1=0.2, w2=0.5, w3=0.3"""
        result = compute_wri(d=70, g=90, p=80, weights={"w1": 0.2, "w2": 0.5, "w3": 0.3})
        expected = 0.2*70 + 0.5*90 + 0.3*80
        assert result["wri"] == round(expected, 2)
    
    def test_zero_scores(self):
        """All zero scores → WRI = 0 → at_risk"""
        result = compute_wri(d=0, g=0, p=0)
        assert result["wri"] == 0.0
        assert result["risk_status"] == "at_risk"
    
    def test_perfect_scores(self):
        """All perfect scores → WRI = 100 → safe"""
        result = compute_wri(d=100, g=100, p=100)
        assert result["wri"] == 100.0
        assert result["risk_status"] == "safe"

    def test_result_includes_inputs(self):
        """Result dict should include the input values used."""
        result = compute_wri(d=80, g=85, p=90)
        assert result["inputs"]["D"] == 80
        assert result["inputs"]["G"] == 85
        assert result["inputs"]["P"] == 90