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.

Files changed (186) hide show
  1. mcli/app/chat_cmd.py +42 -0
  2. mcli/app/commands_cmd.py +226 -0
  3. mcli/app/completion_cmd.py +216 -0
  4. mcli/app/completion_helpers.py +288 -0
  5. mcli/app/cron_test_cmd.py +697 -0
  6. mcli/app/logs_cmd.py +419 -0
  7. mcli/app/main.py +492 -0
  8. mcli/app/model/model.py +1060 -0
  9. mcli/app/model_cmd.py +227 -0
  10. mcli/app/redis_cmd.py +269 -0
  11. mcli/app/video/video.py +1114 -0
  12. mcli/app/visual_cmd.py +303 -0
  13. mcli/chat/chat.py +2409 -0
  14. mcli/chat/command_rag.py +514 -0
  15. mcli/chat/enhanced_chat.py +652 -0
  16. mcli/chat/system_controller.py +1010 -0
  17. mcli/chat/system_integration.py +1016 -0
  18. mcli/cli.py +25 -0
  19. mcli/config.toml +20 -0
  20. mcli/lib/api/api.py +586 -0
  21. mcli/lib/api/daemon_client.py +203 -0
  22. mcli/lib/api/daemon_client_local.py +44 -0
  23. mcli/lib/api/daemon_decorator.py +217 -0
  24. mcli/lib/api/mcli_decorators.py +1032 -0
  25. mcli/lib/auth/auth.py +85 -0
  26. mcli/lib/auth/aws_manager.py +85 -0
  27. mcli/lib/auth/azure_manager.py +91 -0
  28. mcli/lib/auth/credential_manager.py +192 -0
  29. mcli/lib/auth/gcp_manager.py +93 -0
  30. mcli/lib/auth/key_manager.py +117 -0
  31. mcli/lib/auth/mcli_manager.py +93 -0
  32. mcli/lib/auth/token_manager.py +75 -0
  33. mcli/lib/auth/token_util.py +1011 -0
  34. mcli/lib/config/config.py +47 -0
  35. mcli/lib/discovery/__init__.py +1 -0
  36. mcli/lib/discovery/command_discovery.py +274 -0
  37. mcli/lib/erd/erd.py +1345 -0
  38. mcli/lib/erd/generate_graph.py +453 -0
  39. mcli/lib/files/files.py +76 -0
  40. mcli/lib/fs/fs.py +109 -0
  41. mcli/lib/lib.py +29 -0
  42. mcli/lib/logger/logger.py +611 -0
  43. mcli/lib/performance/optimizer.py +409 -0
  44. mcli/lib/performance/rust_bridge.py +502 -0
  45. mcli/lib/performance/uvloop_config.py +154 -0
  46. mcli/lib/pickles/pickles.py +50 -0
  47. mcli/lib/search/cached_vectorizer.py +479 -0
  48. mcli/lib/services/data_pipeline.py +460 -0
  49. mcli/lib/services/lsh_client.py +441 -0
  50. mcli/lib/services/redis_service.py +387 -0
  51. mcli/lib/shell/shell.py +137 -0
  52. mcli/lib/toml/toml.py +33 -0
  53. mcli/lib/ui/styling.py +47 -0
  54. mcli/lib/ui/visual_effects.py +634 -0
  55. mcli/lib/watcher/watcher.py +185 -0
  56. mcli/ml/api/app.py +215 -0
  57. mcli/ml/api/middleware.py +224 -0
  58. mcli/ml/api/routers/admin_router.py +12 -0
  59. mcli/ml/api/routers/auth_router.py +244 -0
  60. mcli/ml/api/routers/backtest_router.py +12 -0
  61. mcli/ml/api/routers/data_router.py +12 -0
  62. mcli/ml/api/routers/model_router.py +302 -0
  63. mcli/ml/api/routers/monitoring_router.py +12 -0
  64. mcli/ml/api/routers/portfolio_router.py +12 -0
  65. mcli/ml/api/routers/prediction_router.py +267 -0
  66. mcli/ml/api/routers/trade_router.py +12 -0
  67. mcli/ml/api/routers/websocket_router.py +76 -0
  68. mcli/ml/api/schemas.py +64 -0
  69. mcli/ml/auth/auth_manager.py +425 -0
  70. mcli/ml/auth/models.py +154 -0
  71. mcli/ml/auth/permissions.py +302 -0
  72. mcli/ml/backtesting/backtest_engine.py +502 -0
  73. mcli/ml/backtesting/performance_metrics.py +393 -0
  74. mcli/ml/cache.py +400 -0
  75. mcli/ml/cli/main.py +398 -0
  76. mcli/ml/config/settings.py +394 -0
  77. mcli/ml/configs/dvc_config.py +230 -0
  78. mcli/ml/configs/mlflow_config.py +131 -0
  79. mcli/ml/configs/mlops_manager.py +293 -0
  80. mcli/ml/dashboard/app.py +532 -0
  81. mcli/ml/dashboard/app_integrated.py +738 -0
  82. mcli/ml/dashboard/app_supabase.py +560 -0
  83. mcli/ml/dashboard/app_training.py +615 -0
  84. mcli/ml/dashboard/cli.py +51 -0
  85. mcli/ml/data_ingestion/api_connectors.py +501 -0
  86. mcli/ml/data_ingestion/data_pipeline.py +567 -0
  87. mcli/ml/data_ingestion/stream_processor.py +512 -0
  88. mcli/ml/database/migrations/env.py +94 -0
  89. mcli/ml/database/models.py +667 -0
  90. mcli/ml/database/session.py +200 -0
  91. mcli/ml/experimentation/ab_testing.py +845 -0
  92. mcli/ml/features/ensemble_features.py +607 -0
  93. mcli/ml/features/political_features.py +676 -0
  94. mcli/ml/features/recommendation_engine.py +809 -0
  95. mcli/ml/features/stock_features.py +573 -0
  96. mcli/ml/features/test_feature_engineering.py +346 -0
  97. mcli/ml/logging.py +85 -0
  98. mcli/ml/mlops/data_versioning.py +518 -0
  99. mcli/ml/mlops/experiment_tracker.py +377 -0
  100. mcli/ml/mlops/model_serving.py +481 -0
  101. mcli/ml/mlops/pipeline_orchestrator.py +614 -0
  102. mcli/ml/models/base_models.py +324 -0
  103. mcli/ml/models/ensemble_models.py +675 -0
  104. mcli/ml/models/recommendation_models.py +474 -0
  105. mcli/ml/models/test_models.py +487 -0
  106. mcli/ml/monitoring/drift_detection.py +676 -0
  107. mcli/ml/monitoring/metrics.py +45 -0
  108. mcli/ml/optimization/portfolio_optimizer.py +834 -0
  109. mcli/ml/preprocessing/data_cleaners.py +451 -0
  110. mcli/ml/preprocessing/feature_extractors.py +491 -0
  111. mcli/ml/preprocessing/ml_pipeline.py +382 -0
  112. mcli/ml/preprocessing/politician_trading_preprocessor.py +569 -0
  113. mcli/ml/preprocessing/test_preprocessing.py +294 -0
  114. mcli/ml/scripts/populate_sample_data.py +200 -0
  115. mcli/ml/tasks.py +400 -0
  116. mcli/ml/tests/test_integration.py +429 -0
  117. mcli/ml/tests/test_training_dashboard.py +387 -0
  118. mcli/public/oi/oi.py +15 -0
  119. mcli/public/public.py +4 -0
  120. mcli/self/self_cmd.py +1246 -0
  121. mcli/workflow/daemon/api_daemon.py +800 -0
  122. mcli/workflow/daemon/async_command_database.py +681 -0
  123. mcli/workflow/daemon/async_process_manager.py +591 -0
  124. mcli/workflow/daemon/client.py +530 -0
  125. mcli/workflow/daemon/commands.py +1196 -0
  126. mcli/workflow/daemon/daemon.py +905 -0
  127. mcli/workflow/daemon/daemon_api.py +59 -0
  128. mcli/workflow/daemon/enhanced_daemon.py +571 -0
  129. mcli/workflow/daemon/process_cli.py +244 -0
  130. mcli/workflow/daemon/process_manager.py +439 -0
  131. mcli/workflow/daemon/test_daemon.py +275 -0
  132. mcli/workflow/dashboard/dashboard_cmd.py +113 -0
  133. mcli/workflow/docker/docker.py +0 -0
  134. mcli/workflow/file/file.py +100 -0
  135. mcli/workflow/gcloud/config.toml +21 -0
  136. mcli/workflow/gcloud/gcloud.py +58 -0
  137. mcli/workflow/git_commit/ai_service.py +328 -0
  138. mcli/workflow/git_commit/commands.py +430 -0
  139. mcli/workflow/lsh_integration.py +355 -0
  140. mcli/workflow/model_service/client.py +594 -0
  141. mcli/workflow/model_service/download_and_run_efficient_models.py +288 -0
  142. mcli/workflow/model_service/lightweight_embedder.py +397 -0
  143. mcli/workflow/model_service/lightweight_model_server.py +714 -0
  144. mcli/workflow/model_service/lightweight_test.py +241 -0
  145. mcli/workflow/model_service/model_service.py +1955 -0
  146. mcli/workflow/model_service/ollama_efficient_runner.py +425 -0
  147. mcli/workflow/model_service/pdf_processor.py +386 -0
  148. mcli/workflow/model_service/test_efficient_runner.py +234 -0
  149. mcli/workflow/model_service/test_example.py +315 -0
  150. mcli/workflow/model_service/test_integration.py +131 -0
  151. mcli/workflow/model_service/test_new_features.py +149 -0
  152. mcli/workflow/openai/openai.py +99 -0
  153. mcli/workflow/politician_trading/commands.py +1790 -0
  154. mcli/workflow/politician_trading/config.py +134 -0
  155. mcli/workflow/politician_trading/connectivity.py +490 -0
  156. mcli/workflow/politician_trading/data_sources.py +395 -0
  157. mcli/workflow/politician_trading/database.py +410 -0
  158. mcli/workflow/politician_trading/demo.py +248 -0
  159. mcli/workflow/politician_trading/models.py +165 -0
  160. mcli/workflow/politician_trading/monitoring.py +413 -0
  161. mcli/workflow/politician_trading/scrapers.py +966 -0
  162. mcli/workflow/politician_trading/scrapers_california.py +412 -0
  163. mcli/workflow/politician_trading/scrapers_eu.py +377 -0
  164. mcli/workflow/politician_trading/scrapers_uk.py +350 -0
  165. mcli/workflow/politician_trading/scrapers_us_states.py +438 -0
  166. mcli/workflow/politician_trading/supabase_functions.py +354 -0
  167. mcli/workflow/politician_trading/workflow.py +852 -0
  168. mcli/workflow/registry/registry.py +180 -0
  169. mcli/workflow/repo/repo.py +223 -0
  170. mcli/workflow/scheduler/commands.py +493 -0
  171. mcli/workflow/scheduler/cron_parser.py +238 -0
  172. mcli/workflow/scheduler/job.py +182 -0
  173. mcli/workflow/scheduler/monitor.py +139 -0
  174. mcli/workflow/scheduler/persistence.py +324 -0
  175. mcli/workflow/scheduler/scheduler.py +679 -0
  176. mcli/workflow/sync/sync_cmd.py +437 -0
  177. mcli/workflow/sync/test_cmd.py +314 -0
  178. mcli/workflow/videos/videos.py +242 -0
  179. mcli/workflow/wakatime/wakatime.py +11 -0
  180. mcli/workflow/workflow.py +37 -0
  181. mcli_framework-7.0.0.dist-info/METADATA +479 -0
  182. mcli_framework-7.0.0.dist-info/RECORD +186 -0
  183. mcli_framework-7.0.0.dist-info/WHEEL +5 -0
  184. mcli_framework-7.0.0.dist-info/entry_points.txt +7 -0
  185. mcli_framework-7.0.0.dist-info/licenses/LICENSE +21 -0
  186. 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
@@ -0,0 +1,4 @@
1
+ from mcli.lib.logger.logger import get_logger
2
+
3
+ logger = get_logger()
4
+ logger.info("Loading mcli.public.public.py")