SIENTIAPDE-1430: Introduce comprehensive integration testing with JSON-based scenarios and detailed README documentation. Enhance training workflow to support advanced model configurations, including polynomial regression with mandatory scaler validation. Ensure robust prediction handling by calculating training predictions (y_train_pred) before denormalization and automatically configuring datetime indices for time-series operations.
This commit is contained in:
@@ -183,11 +183,11 @@ def test_train_model_result_is_dataclass(sample_params, sample_dataframes):
|
||||
|
||||
|
||||
def test_train_model_result_field_count():
|
||||
"""Test that TrainModelResult has exactly 19 fields."""
|
||||
"""Test that TrainModelResult has exactly 20 fields."""
|
||||
from dataclasses import fields
|
||||
|
||||
result_fields = fields(TrainModelResult)
|
||||
assert len(result_fields) == 19
|
||||
assert len(result_fields) == 20
|
||||
|
||||
field_names = {f.name for f in result_fields}
|
||||
expected_fields = {
|
||||
@@ -200,6 +200,7 @@ def test_train_model_result_field_count():
|
||||
'regr',
|
||||
'scaler_dict',
|
||||
'y_pred',
|
||||
'y_train_pred',
|
||||
'mse_val',
|
||||
'mae_val',
|
||||
'r2_val',
|
||||
|
||||
@@ -325,8 +325,14 @@ class TestAfterTrainCalculation:
|
||||
y_train = pd.Series([100, 200, 300], index=[0, 1, 2], name='target')
|
||||
y_test = pd.Series([400, 500], index=[3, 4], name='target')
|
||||
|
||||
# Mock predict to return a simple array
|
||||
sample_linear_model.predict = MagicMock(return_value=np.array([450.0, 550.0]))
|
||||
# Mock predict to return arrays with correct length based on input
|
||||
def mock_predict(data):
|
||||
if len(data) == 3: # x_train
|
||||
return np.array([150.0, 250.0, 350.0])
|
||||
else: # x_test
|
||||
return np.array([450.0, 550.0])
|
||||
|
||||
sample_linear_model.predict = MagicMock(side_effect=mock_predict)
|
||||
|
||||
return TrainModelResult(
|
||||
params=sample_params,
|
||||
@@ -409,13 +415,24 @@ class TestAfterTrainCalculation:
|
||||
y_train = pd.Series([100, 200, 300], index=[0, 1, 2], name='target')
|
||||
y_test = pd.Series([400, 500], index=[3, 4], name='target')
|
||||
|
||||
# Mock predict to return a simple array
|
||||
sample_linear_model.predict = MagicMock(return_value=np.array([450.0, 550.0]))
|
||||
# Mock predict to return arrays with correct length based on input
|
||||
def mock_predict(data):
|
||||
if len(data) == 3: # x_train
|
||||
return np.array([150.0, 250.0, 350.0])
|
||||
else: # x_test
|
||||
return np.array([450.0, 550.0])
|
||||
|
||||
sample_linear_model.predict = MagicMock(side_effect=mock_predict)
|
||||
|
||||
# Create mock scaler with denormalize methods
|
||||
mock_scaler = MagicMock()
|
||||
mock_scaler.denormalize_single_input = MagicMock(side_effect=lambda x, col: x * 2)
|
||||
mock_scaler.denormalize_predictions = MagicMock(return_value=np.array([900.0, 1100.0]))
|
||||
|
||||
# denormalize_predictions needs to return correct length based on input
|
||||
def mock_denormalize_predictions(arr, col):
|
||||
return arr * 2
|
||||
|
||||
mock_scaler.denormalize_predictions = MagicMock(side_effect=mock_denormalize_predictions)
|
||||
|
||||
# Create mock preprocessor
|
||||
mock_process_data = MagicMock()
|
||||
@@ -453,8 +470,14 @@ class TestAfterTrainCalculation:
|
||||
y_train = pd.Series([100, 200, 300], index=[0, 1, 2], name='target')
|
||||
y_test = pd.Series([400, 500], index=[3, 4], name='target')
|
||||
|
||||
# Mock predict
|
||||
sample_linear_model.predict = MagicMock(return_value=np.array([450.0, 550.0]))
|
||||
# Mock predict to return arrays with correct length based on input
|
||||
def mock_predict(data):
|
||||
if len(data) == 3: # x_train
|
||||
return np.array([150.0, 250.0, 350.0])
|
||||
else: # x_test
|
||||
return np.array([450.0, 550.0])
|
||||
|
||||
sample_linear_model.predict = MagicMock(side_effect=mock_predict)
|
||||
|
||||
# Create mock sklearn scaler (without denormalize methods)
|
||||
mock_scaler = MagicMock()
|
||||
@@ -506,6 +529,7 @@ class TestTrain:
|
||||
"""
|
||||
return BytesIO(csv_content.encode('utf-8'))
|
||||
|
||||
@patch.object(TrainingRepository, '_configure_datetime_index', lambda self, df: df)
|
||||
@patch('model_manager.utils.repository.training_repository.split_train_test')
|
||||
@patch('model_manager.utils.repository.training_repository.load_data')
|
||||
def test_train_basic_workflow(
|
||||
@@ -551,6 +575,7 @@ class TestTrain:
|
||||
# Verify split was called
|
||||
assert mock_split_train_test.called
|
||||
|
||||
@patch.object(TrainingRepository, '_configure_datetime_index', lambda self, df: df)
|
||||
@patch('model_manager.utils.repository.training_repository.split_train_test')
|
||||
@patch('model_manager.utils.repository.training_repository.load_data')
|
||||
def test_train_with_scaler(
|
||||
@@ -580,6 +605,7 @@ class TestTrain:
|
||||
assert result is not None
|
||||
assert result.scaler_dict is not None
|
||||
|
||||
@patch.object(TrainingRepository, '_configure_datetime_index', lambda self, df: df)
|
||||
@patch('model_manager.utils.repository.training_repository.split_train_test')
|
||||
@patch('model_manager.utils.repository.training_repository.load_data')
|
||||
def test_train_with_shuffle_enabled(
|
||||
@@ -610,6 +636,7 @@ class TestTrain:
|
||||
call_kwargs = mock_split_train_test.call_args[1]
|
||||
assert call_kwargs['shuffle'] is True
|
||||
|
||||
@patch.object(TrainingRepository, '_configure_datetime_index', lambda self, df: df)
|
||||
@patch('model_manager.utils.repository.training_repository.split_train_test')
|
||||
@patch('model_manager.utils.repository.training_repository.load_data')
|
||||
def test_train_with_different_train_size(
|
||||
@@ -640,6 +667,7 @@ class TestTrain:
|
||||
call_kwargs = mock_split_train_test.call_args[1]
|
||||
assert call_kwargs['train_size'] == 0.7
|
||||
|
||||
@patch.object(TrainingRepository, '_configure_datetime_index', lambda self, df: df)
|
||||
@patch('model_manager.utils.repository.training_repository.split_train_test')
|
||||
@patch('model_manager.utils.repository.training_repository.load_data')
|
||||
def test_train_raises_on_empty_data_after_transform(
|
||||
@@ -660,6 +688,7 @@ class TestTrain:
|
||||
with pytest.raises(ValueError, match='Data view is empty after transformation'):
|
||||
training_repo.train(sample_csv_data, sample_params)
|
||||
|
||||
@patch.object(TrainingRepository, '_configure_datetime_index', lambda self, df: df)
|
||||
@patch('model_manager.utils.repository.training_repository.split_train_test')
|
||||
@patch('model_manager.utils.repository.training_repository.load_data')
|
||||
def test_train_logs_success(
|
||||
@@ -696,6 +725,7 @@ class TestTrain:
|
||||
'Model trained successfully' in str(call) for call in mock_logger.info.call_args_list
|
||||
)
|
||||
|
||||
@patch.object(TrainingRepository, '_configure_datetime_index', lambda self, df: df)
|
||||
@patch('model_manager.utils.repository.training_repository.split_train_test')
|
||||
@patch('model_manager.utils.repository.training_repository.load_data')
|
||||
def test_train_with_custom_separators(
|
||||
@@ -726,6 +756,7 @@ class TestTrain:
|
||||
# Verify load_data was called with custom separators
|
||||
mock_load_data.assert_called_once_with(sample_csv_data, ';', ',')
|
||||
|
||||
@patch.object(TrainingRepository, '_configure_datetime_index', lambda self, df: df)
|
||||
@patch('model_manager.utils.repository.training_repository.split_train_test')
|
||||
@patch('model_manager.utils.repository.training_repository.load_data')
|
||||
def test_train_result_contains_all_fields(
|
||||
|
||||
Reference in New Issue
Block a user