Spaces:
Sleeping
Sleeping
| """ | |
| Unit tests for ML explainability module | |
| """ | |
| import pytest | |
| import sys | |
| import os | |
| sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) | |
| from explainability import ( | |
| SHAPExplainer, | |
| CounterfactualExplainer, | |
| RecommendationGenerator, | |
| ExplanationAggregator, | |
| ) | |
| class TestSHAPExplainer: | |
| """Tests for SHAPExplainer class""" | |
| def explainer(self): | |
| return SHAPExplainer() | |
| def test_explain_returns_required_fields(self, explainer): | |
| """Test that explanation contains required fields""" | |
| features = { | |
| "complexity_normalized": 0.6, | |
| "pri_attention_demand": 0.5, | |
| "trait_conscientiousness": 0.7, | |
| } | |
| prediction = 0.75 | |
| explanation = explainer.explain(features, prediction) | |
| assert "shap_values" in explanation | |
| assert "base_value" in explanation | |
| assert "feature_ranking" in explanation | |
| assert "prediction" in explanation | |
| def test_shap_values_exist_for_features(self, explainer): | |
| """Test that SHAP values are computed for input features""" | |
| features = { | |
| "complexity_normalized": 0.6, | |
| "pri_attention_demand": 0.5, | |
| "trait_conscientiousness": 0.7, | |
| } | |
| prediction = 0.75 | |
| explanation = explainer.explain(features, prediction) | |
| # Should have SHAP values for the features we provided | |
| assert len(explanation["shap_values"]) > 0 | |
| def test_feature_ranking_ordered_by_importance(self, explainer): | |
| """Test that features are ranked by importance""" | |
| features = { | |
| "complexity_normalized": 1.0, # High complexity | |
| "pri_attention_demand": 0.1, | |
| "trait_conscientiousness": 0.3, | |
| } | |
| prediction = 0.4 | |
| explanation = explainer.explain(features, prediction) | |
| ranking = explanation["feature_ranking"] | |
| # Check that ranking is sorted by absolute impact | |
| impacts = [abs(r["impact"]) for r in ranking] | |
| assert impacts == sorted(impacts, reverse=True) | |
| class TestCounterfactualExplainer: | |
| """Tests for CounterfactualExplainer class""" | |
| def explainer(self): | |
| return CounterfactualExplainer() | |
| def test_generate_counterfactuals(self, explainer): | |
| """Test counterfactual generation""" | |
| features = {"complexity_normalized": 1.0, "pri_attention_demand": 0.8} | |
| prediction = 0.4 | |
| target = 0.7 | |
| counterfactuals = explainer.generate_counterfactuals(features, prediction, target) | |
| assert isinstance(counterfactuals, list) | |
| # Should generate some counterfactuals | |
| assert len(counterfactuals) >= 0 # May be empty if no improvements possible | |
| def test_counterfactuals_when_already_meeting_target(self, explainer): | |
| """Test counterfactuals when prediction already meets target""" | |
| features = {"complexity_normalized": 0.3, "pri_attention_demand": 0.6} | |
| prediction = 0.8 # Already above target | |
| target = 0.7 | |
| counterfactuals = explainer.generate_counterfactuals(features, prediction, target) | |
| # Should return message that target is already met | |
| assert len(counterfactuals) > 0 | |
| class TestRecommendationGenerator: | |
| """Tests for RecommendationGenerator class""" | |
| def generator(self): | |
| return RecommendationGenerator() | |
| def test_generate_recommendations(self, generator): | |
| """Test recommendation generation""" | |
| features = { | |
| "complexity_normalized": 0.8, | |
| "time_pressure": 0.5, | |
| "trait_conscientiousness": 0.6, | |
| } | |
| prediction = 0.4 | |
| stress_level = 8 | |
| difficulty = "HARD" | |
| recommendations = generator.generate_recommendations(features, prediction, stress_level, difficulty) | |
| assert isinstance(recommendations, list) | |
| assert len(recommendations) > 0 | |
| for rec in recommendations: | |
| assert "title" in rec | |
| assert "description" in rec | |
| assert "priority" in rec | |
| def test_high_stress_triggers_recommendations(self, generator): | |
| """Test that high stress triggers appropriate recommendations""" | |
| features = { | |
| "complexity_normalized": 0.5, | |
| "time_pressure": 0.2, | |
| } | |
| prediction = 0.7 | |
| stress_level = 9 # High stress | |
| difficulty = "MODERATE" | |
| recommendations = generator.generate_recommendations(features, prediction, stress_level, difficulty) | |
| # Recommendations should exist | |
| assert len(recommendations) > 0 | |
| def test_low_probability_triggers_recommendations(self, generator): | |
| """Test that low probability triggers task-related recommendations""" | |
| features = { | |
| "complexity_normalized": 0.9, # High complexity | |
| "time_pressure": 0.6, | |
| } | |
| prediction = 0.3 | |
| stress_level = 4 | |
| difficulty = "HARD" | |
| recommendations = generator.generate_recommendations(features, prediction, stress_level, difficulty) | |
| # Should have recommendations | |
| assert len(recommendations) > 0 | |
| # At least one should be high priority | |
| priorities = [r["priority"] for r in recommendations] | |
| assert "high" in priorities or "medium" in priorities | |
| class TestExplanationAggregator: | |
| """Tests for ExplanationAggregator class""" | |
| def aggregator(self): | |
| return ExplanationAggregator() | |
| def test_generate_full_explanation(self, aggregator): | |
| """Test that aggregator creates comprehensive explanation""" | |
| features = { | |
| "complexity_normalized": 0.6, | |
| "trait_conscientiousness": 0.7, | |
| "time_pressure": 0.4, | |
| } | |
| prediction = { | |
| "completion_probability": 0.65, | |
| "stress_level": 6, | |
| "difficulty_level": "MODERATE", | |
| } | |
| task_data = { | |
| "title": "Test Task", | |
| "category": "WORK", | |
| } | |
| explanation = aggregator.generate_full_explanation(features, prediction, task_data) | |
| assert isinstance(explanation, dict) | |
| assert "prediction_summary" in explanation | |
| assert "feature_attribution" in explanation | |
| assert "recommendations" in explanation | |
| def test_generate_full_explanation_with_low_probability(self, aggregator): | |
| """Test explanation generation for low probability task""" | |
| features = { | |
| "complexity_normalized": 0.9, | |
| "trait_conscientiousness": 0.4, | |
| "time_pressure": 0.7, | |
| } | |
| prediction = { | |
| "completion_probability": 0.35, | |
| "stress_level": 8, | |
| "difficulty_level": "HARD", | |
| } | |
| task_data = { | |
| "title": "Difficult Task", | |
| "category": "ACADEMIC", | |
| } | |
| explanation = aggregator.generate_full_explanation(features, prediction, task_data) | |
| # Should have counterfactual scenarios for low probability | |
| assert "counterfactual_scenarios" in explanation | |
| # Should have recommendations | |
| assert len(explanation["recommendations"]) > 0 | |
| if __name__ == "__main__": | |
| pytest.main([__file__, "-v"]) | |