mcli-framework 7.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of mcli-framework might be problematic. Click here for more details.
- mcli/app/chat_cmd.py +42 -0
- mcli/app/commands_cmd.py +226 -0
- mcli/app/completion_cmd.py +216 -0
- mcli/app/completion_helpers.py +288 -0
- mcli/app/cron_test_cmd.py +697 -0
- mcli/app/logs_cmd.py +419 -0
- mcli/app/main.py +492 -0
- mcli/app/model/model.py +1060 -0
- mcli/app/model_cmd.py +227 -0
- mcli/app/redis_cmd.py +269 -0
- mcli/app/video/video.py +1114 -0
- mcli/app/visual_cmd.py +303 -0
- mcli/chat/chat.py +2409 -0
- mcli/chat/command_rag.py +514 -0
- mcli/chat/enhanced_chat.py +652 -0
- mcli/chat/system_controller.py +1010 -0
- mcli/chat/system_integration.py +1016 -0
- mcli/cli.py +25 -0
- mcli/config.toml +20 -0
- mcli/lib/api/api.py +586 -0
- mcli/lib/api/daemon_client.py +203 -0
- mcli/lib/api/daemon_client_local.py +44 -0
- mcli/lib/api/daemon_decorator.py +217 -0
- mcli/lib/api/mcli_decorators.py +1032 -0
- mcli/lib/auth/auth.py +85 -0
- mcli/lib/auth/aws_manager.py +85 -0
- mcli/lib/auth/azure_manager.py +91 -0
- mcli/lib/auth/credential_manager.py +192 -0
- mcli/lib/auth/gcp_manager.py +93 -0
- mcli/lib/auth/key_manager.py +117 -0
- mcli/lib/auth/mcli_manager.py +93 -0
- mcli/lib/auth/token_manager.py +75 -0
- mcli/lib/auth/token_util.py +1011 -0
- mcli/lib/config/config.py +47 -0
- mcli/lib/discovery/__init__.py +1 -0
- mcli/lib/discovery/command_discovery.py +274 -0
- mcli/lib/erd/erd.py +1345 -0
- mcli/lib/erd/generate_graph.py +453 -0
- mcli/lib/files/files.py +76 -0
- mcli/lib/fs/fs.py +109 -0
- mcli/lib/lib.py +29 -0
- mcli/lib/logger/logger.py +611 -0
- mcli/lib/performance/optimizer.py +409 -0
- mcli/lib/performance/rust_bridge.py +502 -0
- mcli/lib/performance/uvloop_config.py +154 -0
- mcli/lib/pickles/pickles.py +50 -0
- mcli/lib/search/cached_vectorizer.py +479 -0
- mcli/lib/services/data_pipeline.py +460 -0
- mcli/lib/services/lsh_client.py +441 -0
- mcli/lib/services/redis_service.py +387 -0
- mcli/lib/shell/shell.py +137 -0
- mcli/lib/toml/toml.py +33 -0
- mcli/lib/ui/styling.py +47 -0
- mcli/lib/ui/visual_effects.py +634 -0
- mcli/lib/watcher/watcher.py +185 -0
- mcli/ml/api/app.py +215 -0
- mcli/ml/api/middleware.py +224 -0
- mcli/ml/api/routers/admin_router.py +12 -0
- mcli/ml/api/routers/auth_router.py +244 -0
- mcli/ml/api/routers/backtest_router.py +12 -0
- mcli/ml/api/routers/data_router.py +12 -0
- mcli/ml/api/routers/model_router.py +302 -0
- mcli/ml/api/routers/monitoring_router.py +12 -0
- mcli/ml/api/routers/portfolio_router.py +12 -0
- mcli/ml/api/routers/prediction_router.py +267 -0
- mcli/ml/api/routers/trade_router.py +12 -0
- mcli/ml/api/routers/websocket_router.py +76 -0
- mcli/ml/api/schemas.py +64 -0
- mcli/ml/auth/auth_manager.py +425 -0
- mcli/ml/auth/models.py +154 -0
- mcli/ml/auth/permissions.py +302 -0
- mcli/ml/backtesting/backtest_engine.py +502 -0
- mcli/ml/backtesting/performance_metrics.py +393 -0
- mcli/ml/cache.py +400 -0
- mcli/ml/cli/main.py +398 -0
- mcli/ml/config/settings.py +394 -0
- mcli/ml/configs/dvc_config.py +230 -0
- mcli/ml/configs/mlflow_config.py +131 -0
- mcli/ml/configs/mlops_manager.py +293 -0
- mcli/ml/dashboard/app.py +532 -0
- mcli/ml/dashboard/app_integrated.py +738 -0
- mcli/ml/dashboard/app_supabase.py +560 -0
- mcli/ml/dashboard/app_training.py +615 -0
- mcli/ml/dashboard/cli.py +51 -0
- mcli/ml/data_ingestion/api_connectors.py +501 -0
- mcli/ml/data_ingestion/data_pipeline.py +567 -0
- mcli/ml/data_ingestion/stream_processor.py +512 -0
- mcli/ml/database/migrations/env.py +94 -0
- mcli/ml/database/models.py +667 -0
- mcli/ml/database/session.py +200 -0
- mcli/ml/experimentation/ab_testing.py +845 -0
- mcli/ml/features/ensemble_features.py +607 -0
- mcli/ml/features/political_features.py +676 -0
- mcli/ml/features/recommendation_engine.py +809 -0
- mcli/ml/features/stock_features.py +573 -0
- mcli/ml/features/test_feature_engineering.py +346 -0
- mcli/ml/logging.py +85 -0
- mcli/ml/mlops/data_versioning.py +518 -0
- mcli/ml/mlops/experiment_tracker.py +377 -0
- mcli/ml/mlops/model_serving.py +481 -0
- mcli/ml/mlops/pipeline_orchestrator.py +614 -0
- mcli/ml/models/base_models.py +324 -0
- mcli/ml/models/ensemble_models.py +675 -0
- mcli/ml/models/recommendation_models.py +474 -0
- mcli/ml/models/test_models.py +487 -0
- mcli/ml/monitoring/drift_detection.py +676 -0
- mcli/ml/monitoring/metrics.py +45 -0
- mcli/ml/optimization/portfolio_optimizer.py +834 -0
- mcli/ml/preprocessing/data_cleaners.py +451 -0
- mcli/ml/preprocessing/feature_extractors.py +491 -0
- mcli/ml/preprocessing/ml_pipeline.py +382 -0
- mcli/ml/preprocessing/politician_trading_preprocessor.py +569 -0
- mcli/ml/preprocessing/test_preprocessing.py +294 -0
- mcli/ml/scripts/populate_sample_data.py +200 -0
- mcli/ml/tasks.py +400 -0
- mcli/ml/tests/test_integration.py +429 -0
- mcli/ml/tests/test_training_dashboard.py +387 -0
- mcli/public/oi/oi.py +15 -0
- mcli/public/public.py +4 -0
- mcli/self/self_cmd.py +1246 -0
- mcli/workflow/daemon/api_daemon.py +800 -0
- mcli/workflow/daemon/async_command_database.py +681 -0
- mcli/workflow/daemon/async_process_manager.py +591 -0
- mcli/workflow/daemon/client.py +530 -0
- mcli/workflow/daemon/commands.py +1196 -0
- mcli/workflow/daemon/daemon.py +905 -0
- mcli/workflow/daemon/daemon_api.py +59 -0
- mcli/workflow/daemon/enhanced_daemon.py +571 -0
- mcli/workflow/daemon/process_cli.py +244 -0
- mcli/workflow/daemon/process_manager.py +439 -0
- mcli/workflow/daemon/test_daemon.py +275 -0
- mcli/workflow/dashboard/dashboard_cmd.py +113 -0
- mcli/workflow/docker/docker.py +0 -0
- mcli/workflow/file/file.py +100 -0
- mcli/workflow/gcloud/config.toml +21 -0
- mcli/workflow/gcloud/gcloud.py +58 -0
- mcli/workflow/git_commit/ai_service.py +328 -0
- mcli/workflow/git_commit/commands.py +430 -0
- mcli/workflow/lsh_integration.py +355 -0
- mcli/workflow/model_service/client.py +594 -0
- mcli/workflow/model_service/download_and_run_efficient_models.py +288 -0
- mcli/workflow/model_service/lightweight_embedder.py +397 -0
- mcli/workflow/model_service/lightweight_model_server.py +714 -0
- mcli/workflow/model_service/lightweight_test.py +241 -0
- mcli/workflow/model_service/model_service.py +1955 -0
- mcli/workflow/model_service/ollama_efficient_runner.py +425 -0
- mcli/workflow/model_service/pdf_processor.py +386 -0
- mcli/workflow/model_service/test_efficient_runner.py +234 -0
- mcli/workflow/model_service/test_example.py +315 -0
- mcli/workflow/model_service/test_integration.py +131 -0
- mcli/workflow/model_service/test_new_features.py +149 -0
- mcli/workflow/openai/openai.py +99 -0
- mcli/workflow/politician_trading/commands.py +1790 -0
- mcli/workflow/politician_trading/config.py +134 -0
- mcli/workflow/politician_trading/connectivity.py +490 -0
- mcli/workflow/politician_trading/data_sources.py +395 -0
- mcli/workflow/politician_trading/database.py +410 -0
- mcli/workflow/politician_trading/demo.py +248 -0
- mcli/workflow/politician_trading/models.py +165 -0
- mcli/workflow/politician_trading/monitoring.py +413 -0
- mcli/workflow/politician_trading/scrapers.py +966 -0
- mcli/workflow/politician_trading/scrapers_california.py +412 -0
- mcli/workflow/politician_trading/scrapers_eu.py +377 -0
- mcli/workflow/politician_trading/scrapers_uk.py +350 -0
- mcli/workflow/politician_trading/scrapers_us_states.py +438 -0
- mcli/workflow/politician_trading/supabase_functions.py +354 -0
- mcli/workflow/politician_trading/workflow.py +852 -0
- mcli/workflow/registry/registry.py +180 -0
- mcli/workflow/repo/repo.py +223 -0
- mcli/workflow/scheduler/commands.py +493 -0
- mcli/workflow/scheduler/cron_parser.py +238 -0
- mcli/workflow/scheduler/job.py +182 -0
- mcli/workflow/scheduler/monitor.py +139 -0
- mcli/workflow/scheduler/persistence.py +324 -0
- mcli/workflow/scheduler/scheduler.py +679 -0
- mcli/workflow/sync/sync_cmd.py +437 -0
- mcli/workflow/sync/test_cmd.py +314 -0
- mcli/workflow/videos/videos.py +242 -0
- mcli/workflow/wakatime/wakatime.py +11 -0
- mcli/workflow/workflow.py +37 -0
- mcli_framework-7.0.0.dist-info/METADATA +479 -0
- mcli_framework-7.0.0.dist-info/RECORD +186 -0
- mcli_framework-7.0.0.dist-info/WHEEL +5 -0
- mcli_framework-7.0.0.dist-info/entry_points.txt +7 -0
- mcli_framework-7.0.0.dist-info/licenses/LICENSE +21 -0
- mcli_framework-7.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,387 @@
|
|
|
1
|
+
"""Unit tests for training dashboard functionality"""
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
import pandas as pd
|
|
5
|
+
import numpy as np
|
|
6
|
+
from datetime import datetime, timedelta
|
|
7
|
+
from unittest.mock import Mock, patch, MagicMock
|
|
8
|
+
|
|
9
|
+
from mcli.ml.database.models import Model, ModelStatus, Experiment
|
|
10
|
+
from sqlalchemy.orm import Session
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class TestTrainingDashboard:
|
|
14
|
+
"""Test suite for training dashboard functions"""
|
|
15
|
+
|
|
16
|
+
@pytest.fixture
|
|
17
|
+
def mock_db_session(self):
|
|
18
|
+
"""Create mock database session"""
|
|
19
|
+
session = MagicMock(spec=Session)
|
|
20
|
+
return session
|
|
21
|
+
|
|
22
|
+
@pytest.fixture
|
|
23
|
+
def sample_models(self):
|
|
24
|
+
"""Create sample model data"""
|
|
25
|
+
models = []
|
|
26
|
+
|
|
27
|
+
# Bitcoin-style model comparison data
|
|
28
|
+
model_configs = [
|
|
29
|
+
{
|
|
30
|
+
'name': 'Random Forest',
|
|
31
|
+
'type': 'random_forest',
|
|
32
|
+
'test_rmse': 150.5,
|
|
33
|
+
'test_mae': 120.3,
|
|
34
|
+
'test_r2': 0.85,
|
|
35
|
+
'mape': 5.5
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
'name': 'Gradient Boosting',
|
|
39
|
+
'type': 'gradient_boosting',
|
|
40
|
+
'test_rmse': 155.2,
|
|
41
|
+
'test_mae': 125.8,
|
|
42
|
+
'test_r2': 0.83,
|
|
43
|
+
'mape': 6.2
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
'name': 'Linear Regression',
|
|
47
|
+
'type': 'linear_regression',
|
|
48
|
+
'test_rmse': 180.0,
|
|
49
|
+
'test_mae': 145.0,
|
|
50
|
+
'test_r2': 0.75,
|
|
51
|
+
'mape': 8.5
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
'name': 'Ridge Regression',
|
|
55
|
+
'type': 'ridge',
|
|
56
|
+
'test_rmse': 175.5,
|
|
57
|
+
'test_mae': 140.2,
|
|
58
|
+
'test_r2': 0.78,
|
|
59
|
+
'mape': 7.8
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
'name': 'Lasso Regression',
|
|
63
|
+
'type': 'lasso',
|
|
64
|
+
'test_rmse': 178.0,
|
|
65
|
+
'test_mae': 142.5,
|
|
66
|
+
'test_r2': 0.76,
|
|
67
|
+
'mape': 8.1
|
|
68
|
+
}
|
|
69
|
+
]
|
|
70
|
+
|
|
71
|
+
for i, config in enumerate(model_configs):
|
|
72
|
+
model = Mock(spec=Model)
|
|
73
|
+
model.id = f'model-{i}'
|
|
74
|
+
model.name = config['name']
|
|
75
|
+
model.version = '1.0.0'
|
|
76
|
+
model.model_type = config['type']
|
|
77
|
+
model.status = ModelStatus.DEPLOYED if i < 2 else ModelStatus.TRAINED
|
|
78
|
+
|
|
79
|
+
model.train_accuracy = 0.90 + np.random.uniform(-0.05, 0.05)
|
|
80
|
+
model.val_accuracy = 0.88 + np.random.uniform(-0.05, 0.05)
|
|
81
|
+
model.test_accuracy = 0.85 + np.random.uniform(-0.05, 0.05)
|
|
82
|
+
|
|
83
|
+
model.train_loss = 0.15 + np.random.uniform(-0.05, 0.05)
|
|
84
|
+
model.val_loss = 0.18 + np.random.uniform(-0.05, 0.05)
|
|
85
|
+
model.test_loss = 0.20 + np.random.uniform(-0.05, 0.05)
|
|
86
|
+
|
|
87
|
+
# Bitcoin-style metrics
|
|
88
|
+
model.test_rmse = config['test_rmse']
|
|
89
|
+
model.test_mae = config['test_mae']
|
|
90
|
+
model.test_r2 = config['test_r2']
|
|
91
|
+
|
|
92
|
+
model.metrics = {
|
|
93
|
+
'rmse': config['test_rmse'],
|
|
94
|
+
'mae': config['test_mae'],
|
|
95
|
+
'r2': config['test_r2'],
|
|
96
|
+
'mape': config['mape']
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
# Feature names
|
|
100
|
+
model.feature_names = [
|
|
101
|
+
'lag_1', 'lag_7', 'lag_30',
|
|
102
|
+
'ma_7', 'ma_14', 'ma_30',
|
|
103
|
+
'volatility_7', 'volatility_14',
|
|
104
|
+
'price_change_1', 'price_change_7'
|
|
105
|
+
]
|
|
106
|
+
|
|
107
|
+
model.created_at = datetime.utcnow() - timedelta(days=i)
|
|
108
|
+
model.updated_at = datetime.utcnow()
|
|
109
|
+
|
|
110
|
+
models.append(model)
|
|
111
|
+
|
|
112
|
+
return models
|
|
113
|
+
|
|
114
|
+
@pytest.fixture
|
|
115
|
+
def sample_experiments(self):
|
|
116
|
+
"""Create sample experiment data"""
|
|
117
|
+
experiments = []
|
|
118
|
+
|
|
119
|
+
for i in range(10):
|
|
120
|
+
exp = Mock(spec=Experiment)
|
|
121
|
+
exp.id = f'exp-{i}'
|
|
122
|
+
exp.name = f'Experiment {i}'
|
|
123
|
+
exp.status = 'completed' if i < 7 else ('running' if i < 9 else 'failed')
|
|
124
|
+
exp.started_at = datetime.utcnow() - timedelta(hours=i*2)
|
|
125
|
+
exp.completed_at = datetime.utcnow() - timedelta(hours=i*2-1) if exp.status == 'completed' else None
|
|
126
|
+
exp.duration_seconds = 3600 if exp.status == 'completed' else None
|
|
127
|
+
|
|
128
|
+
exp.hyperparameters = {
|
|
129
|
+
'learning_rate': 0.01,
|
|
130
|
+
'n_estimators': 100,
|
|
131
|
+
'max_depth': 10
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
exp.train_metrics = {'loss': 0.15, 'accuracy': 0.90}
|
|
135
|
+
exp.val_metrics = {'loss': 0.18, 'accuracy': 0.88}
|
|
136
|
+
exp.test_metrics = {'loss': 0.20, 'accuracy': 0.85}
|
|
137
|
+
|
|
138
|
+
experiments.append(exp)
|
|
139
|
+
|
|
140
|
+
return experiments
|
|
141
|
+
|
|
142
|
+
def test_model_comparison_metrics(self, sample_models):
|
|
143
|
+
"""Test model comparison metrics calculation"""
|
|
144
|
+
# Convert to DataFrame as the dashboard would
|
|
145
|
+
df = pd.DataFrame([
|
|
146
|
+
{
|
|
147
|
+
'name': m.name,
|
|
148
|
+
'test_rmse': m.test_rmse,
|
|
149
|
+
'test_mae': m.test_mae,
|
|
150
|
+
'test_r2': m.test_r2,
|
|
151
|
+
'mape': m.metrics['mape']
|
|
152
|
+
}
|
|
153
|
+
for m in sample_models
|
|
154
|
+
])
|
|
155
|
+
|
|
156
|
+
# Test ranking by RMSE
|
|
157
|
+
sorted_by_rmse = df.sort_values('test_rmse')
|
|
158
|
+
assert sorted_by_rmse.iloc[0]['name'] == 'Random Forest'
|
|
159
|
+
assert sorted_by_rmse.iloc[0]['test_rmse'] < 155
|
|
160
|
+
|
|
161
|
+
# Test ranking by R²
|
|
162
|
+
sorted_by_r2 = df.sort_values('test_r2', ascending=False)
|
|
163
|
+
assert sorted_by_r2.iloc[0]['test_r2'] > 0.8
|
|
164
|
+
|
|
165
|
+
# Test ranking by MAE
|
|
166
|
+
sorted_by_mae = df.sort_values('test_mae')
|
|
167
|
+
assert sorted_by_mae.iloc[0]['test_mae'] < 125
|
|
168
|
+
|
|
169
|
+
def test_model_performance_aggregation(self, sample_models):
|
|
170
|
+
"""Test aggregation of model performance"""
|
|
171
|
+
metrics = {
|
|
172
|
+
'total_models': len(sample_models),
|
|
173
|
+
'deployed_models': sum(1 for m in sample_models if m.status == ModelStatus.DEPLOYED),
|
|
174
|
+
'avg_rmse': np.mean([m.test_rmse for m in sample_models]),
|
|
175
|
+
'avg_r2': np.mean([m.test_r2 for m in sample_models]),
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
assert metrics['total_models'] == 5
|
|
179
|
+
assert metrics['deployed_models'] == 2
|
|
180
|
+
assert 150 < metrics['avg_rmse'] < 180
|
|
181
|
+
assert 0.75 < metrics['avg_r2'] < 0.85
|
|
182
|
+
|
|
183
|
+
def test_feature_importance_calculation(self, sample_models):
|
|
184
|
+
"""Test feature importance extraction and ranking"""
|
|
185
|
+
model = sample_models[0]
|
|
186
|
+
|
|
187
|
+
# Simulate feature importance
|
|
188
|
+
importance = np.random.dirichlet(np.ones(len(model.feature_names)))
|
|
189
|
+
feature_df = pd.DataFrame({
|
|
190
|
+
'feature': model.feature_names,
|
|
191
|
+
'importance': importance
|
|
192
|
+
}).sort_values('importance', ascending=False)
|
|
193
|
+
|
|
194
|
+
# Test that importances sum to 1
|
|
195
|
+
assert np.isclose(feature_df['importance'].sum(), 1.0)
|
|
196
|
+
|
|
197
|
+
# Test top features
|
|
198
|
+
top_5 = feature_df.head(5)
|
|
199
|
+
assert len(top_5) == 5
|
|
200
|
+
assert all(top_5['importance'] > 0)
|
|
201
|
+
|
|
202
|
+
def test_residuals_analysis(self):
|
|
203
|
+
"""Test residual analysis calculations"""
|
|
204
|
+
# Generate sample predictions and actuals
|
|
205
|
+
np.random.seed(42)
|
|
206
|
+
n = 500
|
|
207
|
+
|
|
208
|
+
actual = np.random.normal(100, 20, n)
|
|
209
|
+
predicted = actual + np.random.normal(0, 5, n)
|
|
210
|
+
residuals = actual - predicted
|
|
211
|
+
|
|
212
|
+
# Test residual statistics
|
|
213
|
+
mean_residual = np.mean(residuals)
|
|
214
|
+
std_residual = np.std(residuals)
|
|
215
|
+
max_abs_residual = np.max(np.abs(residuals))
|
|
216
|
+
|
|
217
|
+
assert abs(mean_residual) < 1 # Should be close to 0
|
|
218
|
+
assert std_residual > 0
|
|
219
|
+
assert max_abs_residual > std_residual
|
|
220
|
+
|
|
221
|
+
# Test normality (using simple statistics)
|
|
222
|
+
from scipy import stats
|
|
223
|
+
_, p_value = stats.normaltest(residuals)
|
|
224
|
+
# With random data, should generally pass normality test
|
|
225
|
+
assert 0 <= p_value <= 1
|
|
226
|
+
|
|
227
|
+
def test_cross_validation_metrics(self):
|
|
228
|
+
"""Test cross-validation metrics calculation"""
|
|
229
|
+
# Simulate CV scores
|
|
230
|
+
cv_scores = [0.80, 0.82, 0.78, 0.85, 0.79]
|
|
231
|
+
|
|
232
|
+
cv_mean = np.mean(cv_scores)
|
|
233
|
+
cv_std = np.std(cv_scores)
|
|
234
|
+
|
|
235
|
+
assert 0.75 < cv_mean < 0.85
|
|
236
|
+
assert cv_std < 0.05
|
|
237
|
+
|
|
238
|
+
# Test that std is reasonable (not too high)
|
|
239
|
+
assert cv_std / cv_mean < 0.1 # Coefficient of variation < 10%
|
|
240
|
+
|
|
241
|
+
def test_training_duration_analysis(self, sample_experiments):
|
|
242
|
+
"""Test training duration analysis"""
|
|
243
|
+
completed = [exp for exp in sample_experiments if exp.status == 'completed']
|
|
244
|
+
|
|
245
|
+
durations = [exp.duration_seconds for exp in completed]
|
|
246
|
+
avg_duration = np.mean(durations)
|
|
247
|
+
max_duration = np.max(durations)
|
|
248
|
+
min_duration = np.min(durations)
|
|
249
|
+
|
|
250
|
+
assert all(d > 0 for d in durations)
|
|
251
|
+
assert min_duration <= avg_duration <= max_duration
|
|
252
|
+
|
|
253
|
+
def test_model_comparison_ranking(self, sample_models):
|
|
254
|
+
"""Test ranking models by multiple metrics"""
|
|
255
|
+
df = pd.DataFrame([
|
|
256
|
+
{
|
|
257
|
+
'name': m.name,
|
|
258
|
+
'test_rmse': m.test_rmse,
|
|
259
|
+
'test_mae': m.test_mae,
|
|
260
|
+
'test_r2': m.test_r2,
|
|
261
|
+
}
|
|
262
|
+
for m in sample_models
|
|
263
|
+
])
|
|
264
|
+
|
|
265
|
+
# Rank by RMSE (lower is better)
|
|
266
|
+
df['rank_rmse'] = df['test_rmse'].rank()
|
|
267
|
+
|
|
268
|
+
# Rank by R² (higher is better)
|
|
269
|
+
df['rank_r2'] = df['test_r2'].rank(ascending=False)
|
|
270
|
+
|
|
271
|
+
# Composite rank
|
|
272
|
+
df['composite_rank'] = (df['rank_rmse'] + df['rank_r2']) / 2
|
|
273
|
+
|
|
274
|
+
best_overall = df.loc[df['composite_rank'].idxmin()]
|
|
275
|
+
|
|
276
|
+
# Random Forest should be among the best
|
|
277
|
+
assert best_overall['test_r2'] > 0.8
|
|
278
|
+
assert best_overall['test_rmse'] < 160
|
|
279
|
+
|
|
280
|
+
def test_feature_categorization(self):
|
|
281
|
+
"""Test feature categorization (lag, MA, volatility, etc.)"""
|
|
282
|
+
features = [
|
|
283
|
+
'lag_1', 'lag_7', 'lag_30',
|
|
284
|
+
'ma_7', 'ma_14', 'sma_30', 'ema_20',
|
|
285
|
+
'volatility_7', 'volatility_14', 'std_30',
|
|
286
|
+
'price_change_1', 'pct_change_7',
|
|
287
|
+
'rsi_14', 'macd', 'bollinger_upper'
|
|
288
|
+
]
|
|
289
|
+
|
|
290
|
+
categories = {
|
|
291
|
+
'Lag Features': [f for f in features if 'lag' in f.lower()],
|
|
292
|
+
'Moving Averages': [f for f in features if any(x in f.lower() for x in ['ma', 'sma', 'ema'])],
|
|
293
|
+
'Volatility': [f for f in features if any(x in f.lower() for x in ['volatility', 'std'])],
|
|
294
|
+
'Price Changes': [f for f in features if 'change' in f.lower() or 'pct' in f.lower()],
|
|
295
|
+
'Technical': [f for f in features if any(x in f.lower() for x in ['rsi', 'macd', 'bollinger'])]
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
assert len(categories['Lag Features']) == 3
|
|
299
|
+
assert len(categories['Moving Averages']) == 4
|
|
300
|
+
assert len(categories['Volatility']) == 3
|
|
301
|
+
assert len(categories['Price Changes']) == 2
|
|
302
|
+
assert len(categories['Technical']) == 3
|
|
303
|
+
|
|
304
|
+
def test_mape_calculation(self):
|
|
305
|
+
"""Test Mean Absolute Percentage Error calculation"""
|
|
306
|
+
actual = np.array([100, 200, 150, 300, 250])
|
|
307
|
+
predicted = np.array([105, 195, 160, 295, 245])
|
|
308
|
+
|
|
309
|
+
mape = np.mean(np.abs((actual - predicted) / actual)) * 100
|
|
310
|
+
|
|
311
|
+
assert 0 <= mape <= 100
|
|
312
|
+
assert mape < 10 # Should be reasonably low for good predictions
|
|
313
|
+
|
|
314
|
+
def test_error_metrics_comparison(self):
|
|
315
|
+
"""Test that RMSE >= MAE for any predictions"""
|
|
316
|
+
# This is a mathematical property: RMSE is always >= MAE
|
|
317
|
+
|
|
318
|
+
errors = np.array([5, 3, 8, 2, 10])
|
|
319
|
+
|
|
320
|
+
mae = np.mean(np.abs(errors))
|
|
321
|
+
rmse = np.sqrt(np.mean(errors ** 2))
|
|
322
|
+
|
|
323
|
+
assert rmse >= mae
|
|
324
|
+
|
|
325
|
+
def test_r2_score_properties(self):
|
|
326
|
+
"""Test R² score properties"""
|
|
327
|
+
# Perfect predictions
|
|
328
|
+
y_true = np.array([1, 2, 3, 4, 5])
|
|
329
|
+
y_pred = np.array([1, 2, 3, 4, 5])
|
|
330
|
+
|
|
331
|
+
ss_res = np.sum((y_true - y_pred) ** 2)
|
|
332
|
+
ss_tot = np.sum((y_true - np.mean(y_true)) ** 2)
|
|
333
|
+
r2 = 1 - (ss_res / ss_tot)
|
|
334
|
+
|
|
335
|
+
assert np.isclose(r2, 1.0)
|
|
336
|
+
|
|
337
|
+
# Random predictions
|
|
338
|
+
y_pred_random = np.array([2, 3, 1, 5, 4])
|
|
339
|
+
ss_res_random = np.sum((y_true - y_pred_random) ** 2)
|
|
340
|
+
r2_random = 1 - (ss_res_random / ss_tot)
|
|
341
|
+
|
|
342
|
+
assert r2_random < 1.0
|
|
343
|
+
|
|
344
|
+
def test_experiment_status_distribution(self, sample_experiments):
|
|
345
|
+
"""Test experiment status distribution"""
|
|
346
|
+
status_counts = {}
|
|
347
|
+
for exp in sample_experiments:
|
|
348
|
+
status_counts[exp.status] = status_counts.get(exp.status, 0) + 1
|
|
349
|
+
|
|
350
|
+
assert status_counts['completed'] == 7
|
|
351
|
+
assert status_counts['running'] == 2
|
|
352
|
+
assert status_counts['failed'] == 1
|
|
353
|
+
assert sum(status_counts.values()) == 10
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
class TestModelVersioning:
|
|
357
|
+
"""Test model versioning functionality"""
|
|
358
|
+
|
|
359
|
+
def test_version_comparison(self):
|
|
360
|
+
"""Test semantic version comparison"""
|
|
361
|
+
versions = ['1.0.0', '1.1.0', '1.0.1', '2.0.0', '1.2.0']
|
|
362
|
+
|
|
363
|
+
# Parse and sort versions
|
|
364
|
+
parsed = [tuple(map(int, v.split('.'))) for v in versions]
|
|
365
|
+
sorted_versions = sorted(parsed)
|
|
366
|
+
|
|
367
|
+
assert sorted_versions[0] == (1, 0, 0)
|
|
368
|
+
assert sorted_versions[-1] == (2, 0, 0)
|
|
369
|
+
|
|
370
|
+
def test_model_deployment_tracking(self):
|
|
371
|
+
"""Test tracking which models are deployed"""
|
|
372
|
+
models = [
|
|
373
|
+
{'name': 'model-a', 'version': '1.0.0', 'deployed': True},
|
|
374
|
+
{'name': 'model-a', 'version': '1.1.0', 'deployed': False},
|
|
375
|
+
{'name': 'model-b', 'version': '1.0.0', 'deployed': True},
|
|
376
|
+
]
|
|
377
|
+
|
|
378
|
+
deployed = [m for m in models if m['deployed']]
|
|
379
|
+
assert len(deployed) == 2
|
|
380
|
+
|
|
381
|
+
# Test that only one version of each model is deployed
|
|
382
|
+
deployed_names = [m['name'] for m in deployed]
|
|
383
|
+
assert len(deployed_names) == len(set(deployed_names))
|
|
384
|
+
|
|
385
|
+
|
|
386
|
+
if __name__ == '__main__':
|
|
387
|
+
pytest.main([__file__, '-v'])
|
mcli/public/oi/oi.py
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import click
|
|
2
|
+
|
|
3
|
+
from mcli.lib.shell.shell import get_shell_script_path, shell_exec
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@click.group(name="oi")
|
|
7
|
+
def oi():
|
|
8
|
+
"""Create an alpha tunnel using the instance"""
|
|
9
|
+
scripts_path = get_shell_script_path("oi", __name__)
|
|
10
|
+
shell_exec(scripts_path, "oi")
|
|
11
|
+
pass
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
if __name__ == "__main__":
|
|
15
|
+
oi()
|
mcli/public/public.py
ADDED