Amol Kaushik commited on
Commit
c901d12
·
1 Parent(s): 9352277
.github/workflows/push_to_hf_space.yml CHANGED
@@ -60,6 +60,12 @@ jobs:
60
  run: |
61
  echo "No tests implemented yet — placeholder step."
62
  echo "This will later run pytest."
 
 
 
 
 
 
63
 
64
  # -------------------------
65
  # 6. Push to HuggingFace
 
60
  run: |
61
  echo "No tests implemented yet — placeholder step."
62
  echo "This will later run pytest."
63
+
64
+ # this is the completed test step
65
+ # - name: Run unit tests
66
+ # run: |
67
+ # pip install pytest
68
+ # pytest A4/ -v --tb=short
69
 
70
  # -------------------------
71
  # 6. Push to HuggingFace
A4/conftest.py ADDED
@@ -0,0 +1,82 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Provides reusable model paths sample data and loaded model fixtures for testing regression and classification models that we have so far.
2
+
3
+ import pytest
4
+ import os
5
+ import pickle
6
+ import pandas as pd
7
+
8
+ # path fixtures
9
+
10
+ @pytest.fixture
11
+ def repo_root():
12
+ return os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
13
+
14
+
15
+ @pytest.fixture
16
+ def models_dir(repo_root):
17
+ return os.path.join(repo_root, "A3", "models")
18
+
19
+
20
+ @pytest.fixture
21
+ def regression_model_path(models_dir):
22
+ return os.path.join(models_dir, "champion_model_final_2.pkl")
23
+
24
+
25
+ @pytest.fixture
26
+ def classification_model_path(models_dir):
27
+ return os.path.join(models_dir, "final_champion_model_A3.pkl")
28
+
29
+
30
+ @pytest.fixture
31
+ def datasets_dir(repo_root):
32
+ return os.path.join(repo_root, "Datasets_all")
33
+
34
+
35
+ # Model Fixtures
36
+
37
+ @pytest.fixture
38
+ def regression_artifact(regression_model_path):
39
+
40
+ # return the regression model dict
41
+ if not os.path.exists(regression_model_path):
42
+ pytest.skip(f"Model not found: {regression_model_path}")
43
+
44
+ with open(regression_model_path, "rb") as f:
45
+ return pickle.load(f)
46
+
47
+
48
+ @pytest.fixture
49
+ def classification_artifact(classification_model_path):
50
+
51
+ # return the classification model dict
52
+ if not os.path.exists(classification_model_path):
53
+ pytest.skip(f"Model not found: {classification_model_path}")
54
+
55
+ with open(classification_model_path, "rb") as f:
56
+ return pickle.load(f)
57
+
58
+
59
+ # sample data
60
+
61
+ @pytest.fixture
62
+ def sample_regression_features(regression_artifact):
63
+
64
+ # sample feature and data for testing
65
+ feature_columns = regression_artifact["feature_columns"]
66
+
67
+ sample_data = {col: [0.5] for col in feature_columns}
68
+ return pd.DataFrame(sample_data)
69
+
70
+
71
+ @pytest.fixture
72
+ def sample_classification_features(classification_artifact):
73
+
74
+ feature_columns = classification_artifact["feature_columns"]
75
+
76
+ sample_data = {col: [0.5] for col in feature_columns}
77
+ return pd.DataFrame(sample_data)
78
+
79
+ # expected values
80
+ @pytest.fixture
81
+ def expected_classification_classes():
82
+ return ["Lower Body", "Upper Body"]
A4/test_models.py ADDED
@@ -0,0 +1,152 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pytest
2
+ import os
3
+ import pickle
4
+ import numpy as np
5
+
6
+ # regression model tests
7
+ class TestRegressionModelLoading:
8
+
9
+ def test_regression_model_file_exists(self, regression_model_path):
10
+ assert os.path.exists(regression_model_path)
11
+
12
+ def test_regression_artifact_is_dict(self, regression_artifact):
13
+ assert isinstance(regression_artifact, dict)
14
+
15
+ def test_regression_artifact_has_model_key(self, regression_artifact):
16
+ assert "model" in regression_artifact
17
+
18
+ def test_regression_artifact_has_feature_columns(self, regression_artifact):
19
+ assert "feature_columns" in regression_artifact
20
+
21
+ def test_regression_feature_columns_not_empty(self, regression_artifact):
22
+ assert len(regression_artifact["feature_columns"]) > 0
23
+
24
+ def test_regression_model_has_predict_method(self, regression_artifact):
25
+ model = regression_artifact["model"]
26
+ assert hasattr(model, "predict")
27
+
28
+
29
+ class TestRegressionModelPrediction:
30
+
31
+ def test_regression_prediction_returns_array(
32
+ self, regression_artifact, sample_regression_features
33
+ ):
34
+ # regression model should return numpy
35
+ model = regression_artifact["model"]
36
+ prediction = model.predict(sample_regression_features)
37
+ assert isinstance(prediction, np.ndarray)
38
+
39
+ def test_regression_prediction_shape(
40
+ self, regression_artifact, sample_regression_features
41
+ ):
42
+ # one value for sample
43
+ model = regression_artifact["model"]
44
+ prediction = model.predict(sample_regression_features)
45
+ assert prediction.shape[0] == len(sample_regression_features)
46
+
47
+ def test_regression_prediction_is_numeric(
48
+ self, regression_artifact, sample_regression_features
49
+ ):
50
+ # should be a number
51
+ model = regression_artifact["model"]
52
+ prediction = model.predict(sample_regression_features)
53
+ assert np.issubdtype(prediction.dtype, np.number)
54
+
55
+ def test_regression_prediction_in_reasonable_range(
56
+ self, regression_artifact, sample_regression_features
57
+ ):
58
+ model = regression_artifact["model"]
59
+ prediction = model.predict(sample_regression_features)[0]
60
+ # Allow some tolerance outside 0-1 for edge cases
61
+ assert -0.5 <= prediction <= 1.5
62
+
63
+
64
+ class TestClassificationModelLoading:
65
+
66
+ def test_classification_model_file_exists(self, classification_model_path):
67
+ assert os.path.exists(classification_model_path)
68
+
69
+ def test_classification_artifact_is_dict(self, classification_artifact):
70
+ assert isinstance(classification_artifact, dict)
71
+
72
+ def test_classification_artifact_has_model_key(self, classification_artifact):
73
+ assert "model" in classification_artifact
74
+
75
+ def test_classification_artifact_has_feature_columns(self, classification_artifact):
76
+ assert "feature_columns" in classification_artifact
77
+
78
+ def test_classification_artifact_has_classes(self, classification_artifact):
79
+ assert "classes" in classification_artifact
80
+
81
+ def test_classification_model_has_predict_method(self, classification_artifact):
82
+ model = classification_artifact["model"]
83
+ assert hasattr(model, "predict")
84
+
85
+ def test_classification_classes_match_expected(
86
+ self, classification_artifact, expected_classification_classes
87
+ ):
88
+ classes = list(classification_artifact["classes"])
89
+ assert sorted(classes) == sorted(expected_classification_classes)
90
+
91
+
92
+ class TestClassificationModelPrediction:
93
+
94
+ def test_classification_prediction_returns_array(
95
+ self, classification_artifact, sample_classification_features
96
+ ):
97
+ model = classification_artifact["model"]
98
+ prediction = model.predict(sample_classification_features)
99
+ assert isinstance(prediction, np.ndarray)
100
+
101
+ def test_classification_prediction_shape(
102
+ self, classification_artifact, sample_classification_features
103
+ ):
104
+ # one class per sample
105
+ model = classification_artifact["model"]
106
+ prediction = model.predict(sample_classification_features)
107
+ assert prediction.shape[0] == len(sample_classification_features)
108
+
109
+ def test_classification_prediction_is_valid_class(
110
+ self, classification_artifact, sample_classification_features,
111
+ expected_classification_classes
112
+ ):
113
+ # should be a valid class
114
+ model = classification_artifact["model"]
115
+ prediction = model.predict(sample_classification_features)[0]
116
+ assert prediction in expected_classification_classes
117
+
118
+
119
+ class TestModelArtifactStructure:
120
+
121
+ def test_regression_artifact_has_metrics(self, regression_artifact):
122
+ assert "test_metrics" in regression_artifact
123
+
124
+ def test_classification_artifact_has_metrics(self, classification_artifact):
125
+ assert "test_metrics" in classification_artifact
126
+
127
+ def test_regression_metrics_has_r2(self, regression_artifact):
128
+ metrics = regression_artifact.get("test_metrics", {})
129
+ assert "r2" in metrics
130
+
131
+ def test_regression_r2_is_positive(self, regression_artifact):
132
+ metrics = regression_artifact.get("test_metrics", {})
133
+ r2 = metrics.get("r2", 0)
134
+ assert r2 > 0
135
+
136
+ class TestErrorHandling:
137
+
138
+ def test_load_nonexistent_model_raises_error(self, repo_root):
139
+ fake_path = os.path.join(repo_root, "nonexistent_model.pkl")
140
+ with pytest.raises(FileNotFoundError):
141
+ with open(fake_path, "rb") as f:
142
+ pickle.load(f)
143
+
144
+ def test_regression_model_with_wrong_features_raises(
145
+ self, regression_artifact
146
+ ):
147
+ import pandas as pd
148
+ model = regression_artifact["model"]
149
+ wrong_features = pd.DataFrame({"wrong_feature": [0.5]})
150
+
151
+ with pytest.raises((ValueError, KeyError)):
152
+ model.predict(wrong_features)
pytest.ini ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [pytest]
2
+ testpaths = A4
3
+
4
+ python_files = test_*.py
5
+ python_classes = Test*
6
+ python_functions = test_*
7
+
8
+ # output options ??
9
+ addopts = -v --tb=short
10
+
11
+ filterwarnings =
12
+ ignore::DeprecationWarning
13
+ ignore::PendingDeprecationWarning
requirements.txt CHANGED
@@ -1,7 +1,10 @@
1
- gradio>=4.0.0
2
- pandas>=2.0.0
3
- numpy>=1.24.0
4
  scikit-learn==1.7.2
5
- statsmodels>=0.14.0
6
- matplotlib>=3.7.0
7
- gdown>=4.7.0
 
 
 
 
1
+ gradio==4.44.0
2
+ pandas==2.2.3
3
+ numpy==1.26.4
4
  scikit-learn==1.7.2
5
+ statsmodels==0.14.4
6
+ matplotlib==3.9.2
7
+ gdown==5.2.0
8
+
9
+ pytest==8.3.4
10
+ pytest-cov==6.0.0