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,244 @@
1
+ """Authentication API routes"""
2
+
3
+ from datetime import datetime
4
+ from typing import Optional
5
+
6
+ from fastapi import APIRouter, Depends, HTTPException, status, Request
7
+ from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
8
+ from sqlalchemy.orm import Session
9
+
10
+ from mcli.ml.auth import (
11
+ AuthManager,
12
+ UserCreate,
13
+ UserLogin,
14
+ TokenResponse,
15
+ UserResponse,
16
+ PasswordChange,
17
+ PasswordReset,
18
+ get_current_user,
19
+ get_current_active_user,
20
+ check_rate_limit,
21
+ )
22
+ from mcli.ml.database.session import get_db
23
+ from mcli.ml.database.models import User
24
+
25
+ router = APIRouter()
26
+ auth_manager = AuthManager()
27
+ security = HTTPBearer()
28
+
29
+
30
+ @router.post("/register", response_model=UserResponse)
31
+ async def register(
32
+ user_data: UserCreate,
33
+ db: Session = Depends(get_db),
34
+ _: bool = Depends(check_rate_limit)
35
+ ):
36
+ """Register a new user"""
37
+ try:
38
+ user = await auth_manager.register_user(user_data, db)
39
+ return UserResponse.from_orm(user)
40
+ except HTTPException:
41
+ raise
42
+ except Exception as e:
43
+ raise HTTPException(
44
+ status_code=status.HTTP_400_BAD_REQUEST,
45
+ detail=str(e)
46
+ )
47
+
48
+
49
+ @router.post("/login", response_model=TokenResponse)
50
+ async def login(
51
+ login_data: UserLogin,
52
+ db: Session = Depends(get_db),
53
+ _: bool = Depends(check_rate_limit)
54
+ ):
55
+ """Login and receive access token"""
56
+ return await auth_manager.login(login_data, db)
57
+
58
+
59
+ @router.post("/refresh", response_model=TokenResponse)
60
+ async def refresh_token(
61
+ refresh_token: str,
62
+ db: Session = Depends(get_db)
63
+ ):
64
+ """Refresh access token using refresh token"""
65
+ return await auth_manager.refresh_access_token(refresh_token, db)
66
+
67
+
68
+ @router.post("/logout")
69
+ async def logout(
70
+ current_user: User = Depends(get_current_active_user)
71
+ ):
72
+ """Logout current user"""
73
+ # In a real implementation, you might want to blacklist the token
74
+ return {"message": "Successfully logged out"}
75
+
76
+
77
+ @router.get("/me", response_model=UserResponse)
78
+ async def get_current_user_info(
79
+ current_user: User = Depends(get_current_active_user)
80
+ ):
81
+ """Get current user information"""
82
+ return UserResponse.from_orm(current_user)
83
+
84
+
85
+ @router.put("/me", response_model=UserResponse)
86
+ async def update_current_user(
87
+ updates: dict,
88
+ current_user: User = Depends(get_current_active_user),
89
+ db: Session = Depends(get_db)
90
+ ):
91
+ """Update current user information"""
92
+ # Update allowed fields
93
+ allowed_fields = ["first_name", "last_name", "email"]
94
+ for field in allowed_fields:
95
+ if field in updates:
96
+ setattr(current_user, field, updates[field])
97
+
98
+ db.commit()
99
+ db.refresh(current_user)
100
+ return UserResponse.from_orm(current_user)
101
+
102
+
103
+ @router.post("/change-password")
104
+ async def change_password(
105
+ password_data: PasswordChange,
106
+ current_user: User = Depends(get_current_active_user),
107
+ db: Session = Depends(get_db)
108
+ ):
109
+ """Change current user's password"""
110
+ # Verify current password
111
+ if not auth_manager.verify_password(
112
+ password_data.current_password,
113
+ current_user.password_hash
114
+ ):
115
+ raise HTTPException(
116
+ status_code=status.HTTP_400_BAD_REQUEST,
117
+ detail="Current password is incorrect"
118
+ )
119
+
120
+ # Update password
121
+ current_user.password_hash = auth_manager.hash_password(password_data.new_password)
122
+ db.commit()
123
+
124
+ return {"message": "Password changed successfully"}
125
+
126
+
127
+ @router.post("/reset-password")
128
+ async def reset_password_request(
129
+ reset_data: PasswordReset,
130
+ db: Session = Depends(get_db),
131
+ _: bool = Depends(check_rate_limit)
132
+ ):
133
+ """Request password reset"""
134
+ # Find user by email
135
+ user = db.query(User).filter(User.email == reset_data.email).first()
136
+
137
+ if not user:
138
+ # Don't reveal if email exists
139
+ return {"message": "If the email exists, a reset link has been sent"}
140
+
141
+ # In a real implementation, send email with reset token
142
+ # For now, just return success
143
+ return {"message": "If the email exists, a reset link has been sent"}
144
+
145
+
146
+ @router.post("/verify-email/{token}")
147
+ async def verify_email(
148
+ token: str,
149
+ db: Session = Depends(get_db)
150
+ ):
151
+ """Verify email address"""
152
+ # In a real implementation, verify the token and update user
153
+ return {"message": "Email verified successfully"}
154
+
155
+
156
+ @router.get("/sessions")
157
+ async def get_user_sessions(
158
+ current_user: User = Depends(get_current_active_user),
159
+ db: Session = Depends(get_db)
160
+ ):
161
+ """Get all active sessions for current user"""
162
+ # In a real implementation, return active sessions from database
163
+ return {
164
+ "sessions": [
165
+ {
166
+ "id": "session-1",
167
+ "ip_address": "127.0.0.1",
168
+ "user_agent": "Mozilla/5.0",
169
+ "created_at": datetime.utcnow().isoformat(),
170
+ "last_active": datetime.utcnow().isoformat()
171
+ }
172
+ ]
173
+ }
174
+
175
+
176
+ @router.delete("/sessions/{session_id}")
177
+ async def revoke_session(
178
+ session_id: str,
179
+ current_user: User = Depends(get_current_active_user),
180
+ db: Session = Depends(get_db)
181
+ ):
182
+ """Revoke a specific session"""
183
+ # In a real implementation, revoke the session
184
+ return {"message": f"Session {session_id} revoked successfully"}
185
+
186
+
187
+ @router.post("/api-key")
188
+ async def create_api_key(
189
+ name: str,
190
+ expires_in_days: Optional[int] = None,
191
+ current_user: User = Depends(get_current_active_user),
192
+ db: Session = Depends(get_db)
193
+ ):
194
+ """Create a new API key"""
195
+ import secrets
196
+ from datetime import timedelta
197
+
198
+ api_key = secrets.token_urlsafe(32)
199
+
200
+ # Store API key in database (hashed)
201
+ # In real implementation, store this properly
202
+ current_user.api_key = api_key
203
+ current_user.api_key_created_at = datetime.utcnow()
204
+
205
+ db.commit()
206
+
207
+ return {
208
+ "api_key": api_key,
209
+ "name": name,
210
+ "created_at": datetime.utcnow().isoformat(),
211
+ "expires_at": (
212
+ datetime.utcnow() + timedelta(days=expires_in_days)
213
+ ).isoformat() if expires_in_days else None
214
+ }
215
+
216
+
217
+ @router.get("/api-keys")
218
+ async def list_api_keys(
219
+ current_user: User = Depends(get_current_active_user)
220
+ ):
221
+ """List all API keys for current user"""
222
+ # In a real implementation, return API keys from database
223
+ return {
224
+ "api_keys": [
225
+ {
226
+ "id": "key-1",
227
+ "name": "Production API",
228
+ "created_at": datetime.utcnow().isoformat(),
229
+ "last_used": datetime.utcnow().isoformat(),
230
+ "expires_at": None
231
+ }
232
+ ]
233
+ }
234
+
235
+
236
+ @router.delete("/api-keys/{key_id}")
237
+ async def revoke_api_key(
238
+ key_id: str,
239
+ current_user: User = Depends(get_current_active_user),
240
+ db: Session = Depends(get_db)
241
+ ):
242
+ """Revoke an API key"""
243
+ # In a real implementation, revoke the API key
244
+ return {"message": f"API key {key_id} revoked successfully"}
@@ -0,0 +1,12 @@
1
+ """Backtesting API routes"""
2
+
3
+ from fastapi import APIRouter, Depends
4
+ from mcli.ml.auth import get_current_active_user
5
+ from mcli.ml.database.models import User
6
+
7
+ router = APIRouter()
8
+
9
+ @router.post("/run")
10
+ async def run_backtest(current_user: User = Depends(get_current_active_user)):
11
+ """Run backtest"""
12
+ return {"status": "started"}
@@ -0,0 +1,12 @@
1
+ """Data API routes"""
2
+
3
+ from fastapi import APIRouter, Depends
4
+ from mcli.ml.auth import get_current_active_user
5
+ from mcli.ml.database.models import User
6
+
7
+ router = APIRouter()
8
+
9
+ @router.get("/stocks/{ticker}")
10
+ async def get_stock_data(ticker: str, current_user: User = Depends(get_current_active_user)):
11
+ """Get stock data"""
12
+ return {"ticker": ticker, "data": {}}
@@ -0,0 +1,302 @@
1
+ """Model management API routes"""
2
+
3
+ from typing import List, Optional
4
+ from datetime import datetime
5
+ from uuid import UUID
6
+
7
+ from fastapi import APIRouter, Depends, HTTPException, status, UploadFile, File, Query
8
+ from sqlalchemy.orm import Session
9
+ from sqlalchemy import select
10
+
11
+ from mcli.ml.auth import get_current_active_user, require_role, Permission
12
+ from mcli.ml.database.session import get_db, get_async_db
13
+ from mcli.ml.database.models import User, Model, ModelStatus, UserRole
14
+ from mcli.ml.api.schemas import ModelCreate, ModelResponse, ModelUpdate, ModelMetrics
15
+ from mcli.ml.cache import cached
16
+ from mcli.ml.tasks import train_model_task
17
+
18
+ router = APIRouter()
19
+
20
+
21
+ @router.get("/", response_model=List[ModelResponse])
22
+ @cached(expire=300)
23
+ async def list_models(
24
+ skip: int = Query(0, ge=0),
25
+ limit: int = Query(100, le=1000),
26
+ status: Optional[ModelStatus] = None,
27
+ current_user: User = Depends(get_current_active_user),
28
+ db: Session = Depends(get_db)
29
+ ):
30
+ """List all available models"""
31
+ query = db.query(Model)
32
+
33
+ if status:
34
+ query = query.filter(Model.status == status)
35
+
36
+ models = query.offset(skip).limit(limit).all()
37
+ return [ModelResponse.from_orm(m) for m in models]
38
+
39
+
40
+ @router.get("/{model_id}", response_model=ModelResponse)
41
+ @cached(expire=60)
42
+ async def get_model(
43
+ model_id: UUID,
44
+ current_user: User = Depends(get_current_active_user),
45
+ db: Session = Depends(get_db)
46
+ ):
47
+ """Get specific model details"""
48
+ model = db.query(Model).filter(Model.id == model_id).first()
49
+
50
+ if not model:
51
+ raise HTTPException(
52
+ status_code=status.HTTP_404_NOT_FOUND,
53
+ detail="Model not found"
54
+ )
55
+
56
+ return ModelResponse.from_orm(model)
57
+
58
+
59
+ @router.post("/", response_model=ModelResponse)
60
+ async def create_model(
61
+ model_data: ModelCreate,
62
+ current_user: User = Depends(require_role(UserRole.ANALYST, UserRole.ADMIN)),
63
+ db: Session = Depends(get_db)
64
+ ):
65
+ """Create a new model"""
66
+ model = Model(
67
+ **model_data.dict(),
68
+ created_by=current_user.username,
69
+ status=ModelStatus.TRAINING
70
+ )
71
+
72
+ db.add(model)
73
+ db.commit()
74
+ db.refresh(model)
75
+
76
+ # Trigger training task
77
+ train_model_task.delay(str(model.id))
78
+
79
+ return ModelResponse.from_orm(model)
80
+
81
+
82
+ @router.put("/{model_id}", response_model=ModelResponse)
83
+ async def update_model(
84
+ model_id: UUID,
85
+ updates: ModelUpdate,
86
+ current_user: User = Depends(require_role(UserRole.ANALYST, UserRole.ADMIN)),
87
+ db: Session = Depends(get_db)
88
+ ):
89
+ """Update model metadata"""
90
+ model = db.query(Model).filter(Model.id == model_id).first()
91
+
92
+ if not model:
93
+ raise HTTPException(
94
+ status_code=status.HTTP_404_NOT_FOUND,
95
+ detail="Model not found"
96
+ )
97
+
98
+ for field, value in updates.dict(exclude_unset=True).items():
99
+ setattr(model, field, value)
100
+
101
+ model.updated_at = datetime.utcnow()
102
+ db.commit()
103
+ db.refresh(model)
104
+
105
+ return ModelResponse.from_orm(model)
106
+
107
+
108
+ @router.post("/{model_id}/deploy")
109
+ async def deploy_model(
110
+ model_id: UUID,
111
+ endpoint: Optional[str] = None,
112
+ current_user: User = Depends(require_role(UserRole.ANALYST, UserRole.ADMIN)),
113
+ db: Session = Depends(get_db)
114
+ ):
115
+ """Deploy model to production"""
116
+ model = db.query(Model).filter(Model.id == model_id).first()
117
+
118
+ if not model:
119
+ raise HTTPException(
120
+ status_code=status.HTTP_404_NOT_FOUND,
121
+ detail="Model not found"
122
+ )
123
+
124
+ if model.status != ModelStatus.TRAINED:
125
+ raise HTTPException(
126
+ status_code=status.HTTP_400_BAD_REQUEST,
127
+ detail="Model must be trained before deployment"
128
+ )
129
+
130
+ # Deploy model (in real implementation, this would deploy to serving infrastructure)
131
+ model.status = ModelStatus.DEPLOYED
132
+ model.deployed_at = datetime.utcnow()
133
+ model.deployment_endpoint = endpoint or f"https://api.mcli-ml.com/models/{model_id}/predict"
134
+
135
+ db.commit()
136
+
137
+ return {
138
+ "message": "Model deployed successfully",
139
+ "endpoint": model.deployment_endpoint
140
+ }
141
+
142
+
143
+ @router.post("/{model_id}/archive")
144
+ async def archive_model(
145
+ model_id: UUID,
146
+ current_user: User = Depends(require_role(UserRole.ADMIN)),
147
+ db: Session = Depends(get_db)
148
+ ):
149
+ """Archive a model"""
150
+ model = db.query(Model).filter(Model.id == model_id).first()
151
+
152
+ if not model:
153
+ raise HTTPException(
154
+ status_code=status.HTTP_404_NOT_FOUND,
155
+ detail="Model not found"
156
+ )
157
+
158
+ model.status = ModelStatus.ARCHIVED
159
+ db.commit()
160
+
161
+ return {"message": "Model archived successfully"}
162
+
163
+
164
+ @router.get("/{model_id}/metrics", response_model=ModelMetrics)
165
+ @cached(expire=60)
166
+ async def get_model_metrics(
167
+ model_id: UUID,
168
+ current_user: User = Depends(get_current_active_user),
169
+ db: Session = Depends(get_db)
170
+ ):
171
+ """Get model performance metrics"""
172
+ model = db.query(Model).filter(Model.id == model_id).first()
173
+
174
+ if not model:
175
+ raise HTTPException(
176
+ status_code=status.HTTP_404_NOT_FOUND,
177
+ detail="Model not found"
178
+ )
179
+
180
+ return ModelMetrics(
181
+ model_id=model.id,
182
+ train_accuracy=model.train_accuracy,
183
+ val_accuracy=model.val_accuracy,
184
+ test_accuracy=model.test_accuracy,
185
+ train_loss=model.train_loss,
186
+ val_loss=model.val_loss,
187
+ test_loss=model.test_loss,
188
+ additional_metrics=model.metrics or {}
189
+ )
190
+
191
+
192
+ @router.post("/{model_id}/retrain")
193
+ async def retrain_model(
194
+ model_id: UUID,
195
+ hyperparameters: Optional[dict] = None,
196
+ current_user: User = Depends(require_role(UserRole.ANALYST, UserRole.ADMIN)),
197
+ db: Session = Depends(get_db)
198
+ ):
199
+ """Retrain an existing model"""
200
+ model = db.query(Model).filter(Model.id == model_id).first()
201
+
202
+ if not model:
203
+ raise HTTPException(
204
+ status_code=status.HTTP_404_NOT_FOUND,
205
+ detail="Model not found"
206
+ )
207
+
208
+ # Update hyperparameters if provided
209
+ if hyperparameters:
210
+ model.hyperparameters = hyperparameters
211
+
212
+ model.status = ModelStatus.TRAINING
213
+ db.commit()
214
+
215
+ # Trigger retraining task
216
+ train_model_task.delay(str(model.id), retrain=True)
217
+
218
+ return {"message": "Model retraining started", "model_id": str(model.id)}
219
+
220
+
221
+ @router.post("/{model_id}/upload")
222
+ async def upload_model_artifact(
223
+ model_id: UUID,
224
+ file: UploadFile = File(...),
225
+ current_user: User = Depends(require_role(UserRole.ANALYST, UserRole.ADMIN)),
226
+ db: Session = Depends(get_db)
227
+ ):
228
+ """Upload model artifact file"""
229
+ model = db.query(Model).filter(Model.id == model_id).first()
230
+
231
+ if not model:
232
+ raise HTTPException(
233
+ status_code=status.HTTP_404_NOT_FOUND,
234
+ detail="Model not found"
235
+ )
236
+
237
+ # Save file (in real implementation, save to S3 or similar)
238
+ file_path = f"/models/{model_id}/{file.filename}"
239
+
240
+ # Update model path
241
+ model.model_path = file_path
242
+ db.commit()
243
+
244
+ return {
245
+ "message": "Model artifact uploaded successfully",
246
+ "path": file_path
247
+ }
248
+
249
+
250
+ @router.delete("/{model_id}")
251
+ async def delete_model(
252
+ model_id: UUID,
253
+ current_user: User = Depends(require_role(UserRole.ADMIN)),
254
+ db: Session = Depends(get_db)
255
+ ):
256
+ """Delete a model"""
257
+ model = db.query(Model).filter(Model.id == model_id).first()
258
+
259
+ if not model:
260
+ raise HTTPException(
261
+ status_code=status.HTTP_404_NOT_FOUND,
262
+ detail="Model not found"
263
+ )
264
+
265
+ if model.status == ModelStatus.DEPLOYED:
266
+ raise HTTPException(
267
+ status_code=status.HTTP_400_BAD_REQUEST,
268
+ detail="Cannot delete deployed model. Archive it first."
269
+ )
270
+
271
+ db.delete(model)
272
+ db.commit()
273
+
274
+ return {"message": "Model deleted successfully"}
275
+
276
+
277
+ @router.get("/{model_id}/download")
278
+ async def download_model(
279
+ model_id: UUID,
280
+ current_user: User = Depends(get_current_active_user),
281
+ db: Session = Depends(get_db)
282
+ ):
283
+ """Download model artifact"""
284
+ model = db.query(Model).filter(Model.id == model_id).first()
285
+
286
+ if not model:
287
+ raise HTTPException(
288
+ status_code=status.HTTP_404_NOT_FOUND,
289
+ detail="Model not found"
290
+ )
291
+
292
+ if not model.model_path:
293
+ raise HTTPException(
294
+ status_code=status.HTTP_404_NOT_FOUND,
295
+ detail="Model artifact not available"
296
+ )
297
+
298
+ # In real implementation, return file from storage
299
+ return {
300
+ "download_url": f"https://storage.mcli-ml.com{model.model_path}",
301
+ "expires_in": 3600
302
+ }
@@ -0,0 +1,12 @@
1
+ """Monitoring API routes"""
2
+
3
+ from fastapi import APIRouter, Depends
4
+ from mcli.ml.auth import get_current_active_user
5
+ from mcli.ml.database.models import User
6
+
7
+ router = APIRouter()
8
+
9
+ @router.get("/drift")
10
+ async def get_drift_status(current_user: User = Depends(get_current_active_user)):
11
+ """Get drift monitoring status"""
12
+ return {"drift_detected": False}
@@ -0,0 +1,12 @@
1
+ """Portfolio management API routes"""
2
+
3
+ from fastapi import APIRouter, Depends
4
+ from mcli.ml.auth import get_current_active_user
5
+ from mcli.ml.database.models import User
6
+
7
+ router = APIRouter()
8
+
9
+ @router.get("/")
10
+ async def list_portfolios(current_user: User = Depends(get_current_active_user)):
11
+ """List user portfolios"""
12
+ return {"portfolios": []}