Spaces:
Running
Running
| 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 | |