Spaces:
Sleeping
Sleeping
| import pickle | |
| import numpy as np | |
| import pandas as pd | |
| import pytest | |
| from unittest.mock import MagicMock, patch | |
| from src.models.predict import predict, load_model, save_to_database | |
| def mock_model(): | |
| model = MagicMock() | |
| model.predict.return_value = np.array([0, 1, 0]) | |
| model.predict_proba.return_value = np.array([ | |
| [0.8, 0.2], | |
| [0.3, 0.7], | |
| [0.9, 0.1], | |
| ]) | |
| return model | |
| def sample_df(): | |
| return pd.DataFrame({ | |
| "SK_ID_CURR": [100001, 100002, 100003], | |
| "feature_a": [1.0, 2.0, 3.0], | |
| "feature_b": [4.0, 5.0, 6.0], | |
| }) | |
| def test_predict_returns_correct_columns(mock_model, sample_df): | |
| result = predict(mock_model, sample_df) | |
| assert set(result.columns) == {"sk_id_curr", "predicted_class", "proba_class_0", "proba_class_1"} | |
| def test_predict_correct_class_values(mock_model, sample_df): | |
| result = predict(mock_model, sample_df) | |
| assert list(result["predicted_class"]) == [0, 1, 0] | |
| def test_predict_preserves_sk_id(mock_model, sample_df): | |
| result = predict(mock_model, sample_df) | |
| assert list(result["sk_id_curr"]) == [100001, 100002, 100003] | |
| def test_predict_probas_sum_to_one(mock_model, sample_df): | |
| result = predict(mock_model, sample_df) | |
| sums = result["proba_class_0"] + result["proba_class_1"] | |
| assert all(abs(s - 1.0) < 1e-6 for s in sums) | |
| def test_predict_with_target_includes_true_class(mock_model): | |
| df = pd.DataFrame({ | |
| "SK_ID_CURR": [100001, 100002], | |
| "TARGET": [0, 1], | |
| "feature_a": [1.0, 2.0], | |
| }) | |
| mock_model.predict.return_value = np.array([0, 1]) | |
| mock_model.predict_proba.return_value = np.array([[0.8, 0.2], [0.3, 0.7]]) | |
| result = predict(mock_model, df) | |
| assert "true_class" in result.columns | |
| assert list(result["true_class"]) == [0, 1] | |
| def test_predict_without_target_excludes_true_class(mock_model, sample_df): | |
| result = predict(mock_model, sample_df) | |
| assert "true_class" not in result.columns | |
| def test_predict_drops_sk_id_from_features(mock_model, sample_df): | |
| predict(mock_model, sample_df) | |
| features_passed = mock_model.predict.call_args[0][0] | |
| assert "SK_ID_CURR" not in features_passed.columns | |
| def test_load_model_charge_le_fichier(tmp_path): | |
| model_obj = {"type": "fake_model"} | |
| model_file = tmp_path / "model.pkl" | |
| with open(model_file, "wb") as f: | |
| pickle.dump(model_obj, f) | |
| result = load_model(str(model_file)) | |
| assert result == model_obj | |
| def test_save_to_database_appelle_to_sql(sample_df): | |
| mock_engine = MagicMock() | |
| with patch("src.models.predict.create_engine", return_value=mock_engine): | |
| with patch.object(sample_df.__class__, "to_sql") as mock_to_sql: | |
| save_to_database(sample_df, table="predictions") | |
| mock_to_sql.assert_called_once_with( | |
| "predictions", mock_engine, if_exists="replace", index=False | |
| ) | |
| def test_predict_drops_target_from_features(mock_model): | |
| df = pd.DataFrame({ | |
| "SK_ID_CURR": [100001], | |
| "TARGET": [0], | |
| "feature_a": [1.0], | |
| }) | |
| mock_model.predict.return_value = np.array([0]) | |
| mock_model.predict_proba.return_value = np.array([[0.8, 0.2]]) | |
| predict(mock_model, df) | |
| features_passed = mock_model.predict.call_args[0][0] | |
| assert "TARGET" not in features_passed.columns | |
| from src.models.export_onnx import export_to_onnx | |
| def test_export_to_onnx_cree_un_fichier_onnx(tmp_path): | |
| from catboost import CatBoostClassifier | |
| import numpy as np | |
| X = np.random.rand(50, 3).astype(np.float32) | |
| y = (X[:, 0] > 0.5).astype(int) | |
| model = CatBoostClassifier(iterations=5, verbose=0) | |
| model.fit(X, y) | |
| pkl_path = str(tmp_path / "model.pkl") | |
| onnx_path = str(tmp_path / "model.onnx") | |
| import pickle | |
| with open(pkl_path, "wb") as f: | |
| pickle.dump(model, f) | |
| export_to_onnx(pkl_path, onnx_path) | |
| assert (tmp_path / "model.onnx").exists() | |
| assert (tmp_path / "model.onnx").stat().st_size > 0 | |
| from src.models.predict import predict_onnx | |
| def test_predict_onnx_retourne_les_memes_colonnes(tmp_path): | |
| from catboost import CatBoostClassifier | |
| import numpy as np | |
| import pickle | |
| from src.models.export_onnx import export_to_onnx | |
| X = np.random.rand(30, 3).astype(np.float32) | |
| y = (X[:, 0] > 0.5).astype(int) | |
| model = CatBoostClassifier(iterations=5, verbose=0) | |
| model.fit(X, y) | |
| pkl_path = str(tmp_path / "model.pkl") | |
| onnx_path = str(tmp_path / "model.onnx") | |
| with open(pkl_path, "wb") as f: | |
| pickle.dump(model, f) | |
| export_to_onnx(pkl_path, onnx_path) | |
| df = pd.DataFrame(X, columns=["f0", "f1", "f2"]) | |
| df.insert(0, "SK_ID_CURR", range(30)) | |
| result = predict_onnx(onnx_path, df) | |
| assert set(result.columns) == {"sk_id_curr", "predicted_class", "proba_class_0", "proba_class_1"} | |
| def test_predict_onnx_probabilites_proches_de_catboost(tmp_path): | |
| from catboost import CatBoostClassifier | |
| import numpy as np | |
| import pickle | |
| from src.models.export_onnx import export_to_onnx | |
| from src.models.predict import predict | |
| np.random.seed(42) | |
| X = np.random.rand(30, 3).astype(np.float32) | |
| y = (X[:, 0] > 0.5).astype(int) | |
| model = CatBoostClassifier(iterations=5, verbose=0) | |
| model.fit(X, y) | |
| pkl_path = str(tmp_path / "model.pkl") | |
| onnx_path = str(tmp_path / "model.onnx") | |
| with open(pkl_path, "wb") as f: | |
| pickle.dump(model, f) | |
| export_to_onnx(pkl_path, onnx_path) | |
| df = pd.DataFrame(X, columns=["f0", "f1", "f2"]) | |
| df.insert(0, "SK_ID_CURR", range(30)) | |
| result_cb = predict(model, df) | |
| result_onnx = predict_onnx(onnx_path, df) | |
| np.testing.assert_allclose( | |
| result_cb["proba_class_1"].values, | |
| result_onnx["proba_class_1"].values, | |
| atol=1e-5, | |
| err_msg="Les probabilités ONNX diffèrent de CatBoost de plus de 1e-5", | |
| ) | |