mcli-framework 7.1.1__py3-none-any.whl → 7.1.3__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/completion_cmd.py +59 -49
- mcli/app/completion_helpers.py +60 -138
- mcli/app/logs_cmd.py +6 -2
- mcli/app/main.py +17 -14
- mcli/app/model_cmd.py +19 -4
- mcli/chat/chat.py +3 -2
- mcli/lib/search/cached_vectorizer.py +1 -0
- mcli/lib/services/data_pipeline.py +12 -5
- mcli/lib/services/lsh_client.py +68 -57
- mcli/ml/api/app.py +28 -36
- mcli/ml/api/middleware.py +8 -16
- mcli/ml/api/routers/admin_router.py +3 -1
- mcli/ml/api/routers/auth_router.py +32 -56
- mcli/ml/api/routers/backtest_router.py +3 -1
- mcli/ml/api/routers/data_router.py +3 -1
- mcli/ml/api/routers/model_router.py +35 -74
- mcli/ml/api/routers/monitoring_router.py +3 -1
- mcli/ml/api/routers/portfolio_router.py +3 -1
- mcli/ml/api/routers/prediction_router.py +60 -65
- mcli/ml/api/routers/trade_router.py +6 -2
- mcli/ml/api/routers/websocket_router.py +12 -9
- mcli/ml/api/schemas.py +10 -2
- mcli/ml/auth/auth_manager.py +49 -114
- mcli/ml/auth/models.py +30 -15
- mcli/ml/auth/permissions.py +12 -19
- mcli/ml/backtesting/backtest_engine.py +134 -108
- mcli/ml/backtesting/performance_metrics.py +142 -108
- mcli/ml/cache.py +12 -18
- mcli/ml/cli/main.py +37 -23
- mcli/ml/config/settings.py +29 -12
- mcli/ml/dashboard/app.py +122 -130
- mcli/ml/dashboard/app_integrated.py +955 -154
- mcli/ml/dashboard/app_supabase.py +176 -108
- mcli/ml/dashboard/app_training.py +212 -206
- mcli/ml/dashboard/cli.py +14 -5
- mcli/ml/data_ingestion/api_connectors.py +51 -81
- mcli/ml/data_ingestion/data_pipeline.py +127 -125
- mcli/ml/data_ingestion/stream_processor.py +72 -80
- mcli/ml/database/migrations/env.py +3 -2
- mcli/ml/database/models.py +112 -79
- mcli/ml/database/session.py +6 -5
- mcli/ml/experimentation/ab_testing.py +149 -99
- mcli/ml/features/ensemble_features.py +9 -8
- mcli/ml/features/political_features.py +6 -5
- mcli/ml/features/recommendation_engine.py +15 -14
- mcli/ml/features/stock_features.py +7 -6
- mcli/ml/features/test_feature_engineering.py +8 -7
- mcli/ml/logging.py +10 -15
- mcli/ml/mlops/data_versioning.py +57 -64
- mcli/ml/mlops/experiment_tracker.py +49 -41
- mcli/ml/mlops/model_serving.py +59 -62
- mcli/ml/mlops/pipeline_orchestrator.py +203 -149
- mcli/ml/models/base_models.py +8 -7
- mcli/ml/models/ensemble_models.py +6 -5
- mcli/ml/models/recommendation_models.py +7 -6
- mcli/ml/models/test_models.py +18 -14
- mcli/ml/monitoring/drift_detection.py +95 -74
- mcli/ml/monitoring/metrics.py +10 -22
- mcli/ml/optimization/portfolio_optimizer.py +172 -132
- mcli/ml/predictions/prediction_engine.py +62 -50
- mcli/ml/preprocessing/data_cleaners.py +6 -5
- mcli/ml/preprocessing/feature_extractors.py +7 -6
- mcli/ml/preprocessing/ml_pipeline.py +3 -2
- mcli/ml/preprocessing/politician_trading_preprocessor.py +11 -10
- mcli/ml/preprocessing/test_preprocessing.py +4 -4
- mcli/ml/scripts/populate_sample_data.py +36 -16
- mcli/ml/tasks.py +82 -83
- mcli/ml/tests/test_integration.py +86 -76
- mcli/ml/tests/test_training_dashboard.py +169 -142
- mcli/mygroup/test_cmd.py +2 -1
- mcli/self/self_cmd.py +31 -16
- mcli/self/test_cmd.py +2 -1
- mcli/workflow/dashboard/dashboard_cmd.py +13 -6
- mcli/workflow/lsh_integration.py +46 -58
- mcli/workflow/politician_trading/commands.py +576 -427
- mcli/workflow/politician_trading/config.py +7 -7
- mcli/workflow/politician_trading/connectivity.py +35 -33
- mcli/workflow/politician_trading/data_sources.py +72 -71
- mcli/workflow/politician_trading/database.py +18 -16
- mcli/workflow/politician_trading/demo.py +4 -3
- mcli/workflow/politician_trading/models.py +5 -5
- mcli/workflow/politician_trading/monitoring.py +13 -13
- mcli/workflow/politician_trading/scrapers.py +332 -224
- mcli/workflow/politician_trading/scrapers_california.py +116 -94
- mcli/workflow/politician_trading/scrapers_eu.py +70 -71
- mcli/workflow/politician_trading/scrapers_uk.py +118 -90
- mcli/workflow/politician_trading/scrapers_us_states.py +125 -92
- mcli/workflow/politician_trading/workflow.py +98 -71
- {mcli_framework-7.1.1.dist-info → mcli_framework-7.1.3.dist-info}/METADATA +1 -1
- {mcli_framework-7.1.1.dist-info → mcli_framework-7.1.3.dist-info}/RECORD +94 -94
- {mcli_framework-7.1.1.dist-info → mcli_framework-7.1.3.dist-info}/WHEEL +0 -0
- {mcli_framework-7.1.1.dist-info → mcli_framework-7.1.3.dist-info}/entry_points.txt +0 -0
- {mcli_framework-7.1.1.dist-info → mcli_framework-7.1.3.dist-info}/licenses/LICENSE +0 -0
- {mcli_framework-7.1.1.dist-info → mcli_framework-7.1.3.dist-info}/top_level.txt +0 -0
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
"""Admin API routes"""
|
|
2
2
|
|
|
3
3
|
from fastapi import APIRouter, Depends
|
|
4
|
+
|
|
4
5
|
from mcli.ml.auth import get_current_active_user, require_role
|
|
5
6
|
from mcli.ml.database.models import User, UserRole
|
|
6
7
|
|
|
7
8
|
router = APIRouter()
|
|
8
9
|
|
|
10
|
+
|
|
9
11
|
@router.get("/users")
|
|
10
12
|
async def list_users(current_user: User = Depends(require_role(UserRole.ADMIN))):
|
|
11
13
|
"""List all users"""
|
|
12
|
-
return {"users": []}
|
|
14
|
+
return {"users": []}
|
|
@@ -3,24 +3,24 @@
|
|
|
3
3
|
from datetime import datetime
|
|
4
4
|
from typing import Optional
|
|
5
5
|
|
|
6
|
-
from fastapi import APIRouter, Depends, HTTPException,
|
|
7
|
-
from fastapi.security import
|
|
6
|
+
from fastapi import APIRouter, Depends, HTTPException, Request, status
|
|
7
|
+
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
|
|
8
8
|
from sqlalchemy.orm import Session
|
|
9
9
|
|
|
10
10
|
from mcli.ml.auth import (
|
|
11
11
|
AuthManager,
|
|
12
|
+
PasswordChange,
|
|
13
|
+
PasswordReset,
|
|
14
|
+
TokenResponse,
|
|
12
15
|
UserCreate,
|
|
13
16
|
UserLogin,
|
|
14
|
-
TokenResponse,
|
|
15
17
|
UserResponse,
|
|
16
|
-
PasswordChange,
|
|
17
|
-
PasswordReset,
|
|
18
|
-
get_current_user,
|
|
19
|
-
get_current_active_user,
|
|
20
18
|
check_rate_limit,
|
|
19
|
+
get_current_active_user,
|
|
20
|
+
get_current_user,
|
|
21
21
|
)
|
|
22
|
-
from mcli.ml.database.session import get_db
|
|
23
22
|
from mcli.ml.database.models import User
|
|
23
|
+
from mcli.ml.database.session import get_db
|
|
24
24
|
|
|
25
25
|
router = APIRouter()
|
|
26
26
|
auth_manager = AuthManager()
|
|
@@ -29,9 +29,7 @@ security = HTTPBearer()
|
|
|
29
29
|
|
|
30
30
|
@router.post("/register", response_model=UserResponse)
|
|
31
31
|
async def register(
|
|
32
|
-
user_data: UserCreate,
|
|
33
|
-
db: Session = Depends(get_db),
|
|
34
|
-
_: bool = Depends(check_rate_limit)
|
|
32
|
+
user_data: UserCreate, db: Session = Depends(get_db), _: bool = Depends(check_rate_limit)
|
|
35
33
|
):
|
|
36
34
|
"""Register a new user"""
|
|
37
35
|
try:
|
|
@@ -40,44 +38,32 @@ async def register(
|
|
|
40
38
|
except HTTPException:
|
|
41
39
|
raise
|
|
42
40
|
except Exception as e:
|
|
43
|
-
raise HTTPException(
|
|
44
|
-
status_code=status.HTTP_400_BAD_REQUEST,
|
|
45
|
-
detail=str(e)
|
|
46
|
-
)
|
|
41
|
+
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
|
|
47
42
|
|
|
48
43
|
|
|
49
44
|
@router.post("/login", response_model=TokenResponse)
|
|
50
45
|
async def login(
|
|
51
|
-
login_data: UserLogin,
|
|
52
|
-
db: Session = Depends(get_db),
|
|
53
|
-
_: bool = Depends(check_rate_limit)
|
|
46
|
+
login_data: UserLogin, db: Session = Depends(get_db), _: bool = Depends(check_rate_limit)
|
|
54
47
|
):
|
|
55
48
|
"""Login and receive access token"""
|
|
56
49
|
return await auth_manager.login(login_data, db)
|
|
57
50
|
|
|
58
51
|
|
|
59
52
|
@router.post("/refresh", response_model=TokenResponse)
|
|
60
|
-
async def refresh_token(
|
|
61
|
-
refresh_token: str,
|
|
62
|
-
db: Session = Depends(get_db)
|
|
63
|
-
):
|
|
53
|
+
async def refresh_token(refresh_token: str, db: Session = Depends(get_db)):
|
|
64
54
|
"""Refresh access token using refresh token"""
|
|
65
55
|
return await auth_manager.refresh_access_token(refresh_token, db)
|
|
66
56
|
|
|
67
57
|
|
|
68
58
|
@router.post("/logout")
|
|
69
|
-
async def logout(
|
|
70
|
-
current_user: User = Depends(get_current_active_user)
|
|
71
|
-
):
|
|
59
|
+
async def logout(current_user: User = Depends(get_current_active_user)):
|
|
72
60
|
"""Logout current user"""
|
|
73
61
|
# In a real implementation, you might want to blacklist the token
|
|
74
62
|
return {"message": "Successfully logged out"}
|
|
75
63
|
|
|
76
64
|
|
|
77
65
|
@router.get("/me", response_model=UserResponse)
|
|
78
|
-
async def get_current_user_info(
|
|
79
|
-
current_user: User = Depends(get_current_active_user)
|
|
80
|
-
):
|
|
66
|
+
async def get_current_user_info(current_user: User = Depends(get_current_active_user)):
|
|
81
67
|
"""Get current user information"""
|
|
82
68
|
return UserResponse.from_orm(current_user)
|
|
83
69
|
|
|
@@ -86,7 +72,7 @@ async def get_current_user_info(
|
|
|
86
72
|
async def update_current_user(
|
|
87
73
|
updates: dict,
|
|
88
74
|
current_user: User = Depends(get_current_active_user),
|
|
89
|
-
db: Session = Depends(get_db)
|
|
75
|
+
db: Session = Depends(get_db),
|
|
90
76
|
):
|
|
91
77
|
"""Update current user information"""
|
|
92
78
|
# Update allowed fields
|
|
@@ -104,17 +90,13 @@ async def update_current_user(
|
|
|
104
90
|
async def change_password(
|
|
105
91
|
password_data: PasswordChange,
|
|
106
92
|
current_user: User = Depends(get_current_active_user),
|
|
107
|
-
db: Session = Depends(get_db)
|
|
93
|
+
db: Session = Depends(get_db),
|
|
108
94
|
):
|
|
109
95
|
"""Change current user's password"""
|
|
110
96
|
# Verify current password
|
|
111
|
-
if not auth_manager.verify_password(
|
|
112
|
-
password_data.current_password,
|
|
113
|
-
current_user.password_hash
|
|
114
|
-
):
|
|
97
|
+
if not auth_manager.verify_password(password_data.current_password, current_user.password_hash):
|
|
115
98
|
raise HTTPException(
|
|
116
|
-
status_code=status.HTTP_400_BAD_REQUEST,
|
|
117
|
-
detail="Current password is incorrect"
|
|
99
|
+
status_code=status.HTTP_400_BAD_REQUEST, detail="Current password is incorrect"
|
|
118
100
|
)
|
|
119
101
|
|
|
120
102
|
# Update password
|
|
@@ -126,9 +108,7 @@ async def change_password(
|
|
|
126
108
|
|
|
127
109
|
@router.post("/reset-password")
|
|
128
110
|
async def reset_password_request(
|
|
129
|
-
reset_data: PasswordReset,
|
|
130
|
-
db: Session = Depends(get_db),
|
|
131
|
-
_: bool = Depends(check_rate_limit)
|
|
111
|
+
reset_data: PasswordReset, db: Session = Depends(get_db), _: bool = Depends(check_rate_limit)
|
|
132
112
|
):
|
|
133
113
|
"""Request password reset"""
|
|
134
114
|
# Find user by email
|
|
@@ -144,10 +124,7 @@ async def reset_password_request(
|
|
|
144
124
|
|
|
145
125
|
|
|
146
126
|
@router.post("/verify-email/{token}")
|
|
147
|
-
async def verify_email(
|
|
148
|
-
token: str,
|
|
149
|
-
db: Session = Depends(get_db)
|
|
150
|
-
):
|
|
127
|
+
async def verify_email(token: str, db: Session = Depends(get_db)):
|
|
151
128
|
"""Verify email address"""
|
|
152
129
|
# In a real implementation, verify the token and update user
|
|
153
130
|
return {"message": "Email verified successfully"}
|
|
@@ -155,8 +132,7 @@ async def verify_email(
|
|
|
155
132
|
|
|
156
133
|
@router.get("/sessions")
|
|
157
134
|
async def get_user_sessions(
|
|
158
|
-
current_user: User = Depends(get_current_active_user),
|
|
159
|
-
db: Session = Depends(get_db)
|
|
135
|
+
current_user: User = Depends(get_current_active_user), db: Session = Depends(get_db)
|
|
160
136
|
):
|
|
161
137
|
"""Get all active sessions for current user"""
|
|
162
138
|
# In a real implementation, return active sessions from database
|
|
@@ -167,7 +143,7 @@ async def get_user_sessions(
|
|
|
167
143
|
"ip_address": "127.0.0.1",
|
|
168
144
|
"user_agent": "Mozilla/5.0",
|
|
169
145
|
"created_at": datetime.utcnow().isoformat(),
|
|
170
|
-
"last_active": datetime.utcnow().isoformat()
|
|
146
|
+
"last_active": datetime.utcnow().isoformat(),
|
|
171
147
|
}
|
|
172
148
|
]
|
|
173
149
|
}
|
|
@@ -177,7 +153,7 @@ async def get_user_sessions(
|
|
|
177
153
|
async def revoke_session(
|
|
178
154
|
session_id: str,
|
|
179
155
|
current_user: User = Depends(get_current_active_user),
|
|
180
|
-
db: Session = Depends(get_db)
|
|
156
|
+
db: Session = Depends(get_db),
|
|
181
157
|
):
|
|
182
158
|
"""Revoke a specific session"""
|
|
183
159
|
# In a real implementation, revoke the session
|
|
@@ -189,7 +165,7 @@ async def create_api_key(
|
|
|
189
165
|
name: str,
|
|
190
166
|
expires_in_days: Optional[int] = None,
|
|
191
167
|
current_user: User = Depends(get_current_active_user),
|
|
192
|
-
db: Session = Depends(get_db)
|
|
168
|
+
db: Session = Depends(get_db),
|
|
193
169
|
):
|
|
194
170
|
"""Create a new API key"""
|
|
195
171
|
import secrets
|
|
@@ -209,15 +185,15 @@ async def create_api_key(
|
|
|
209
185
|
"name": name,
|
|
210
186
|
"created_at": datetime.utcnow().isoformat(),
|
|
211
187
|
"expires_at": (
|
|
212
|
-
datetime.utcnow() + timedelta(days=expires_in_days)
|
|
213
|
-
|
|
188
|
+
(datetime.utcnow() + timedelta(days=expires_in_days)).isoformat()
|
|
189
|
+
if expires_in_days
|
|
190
|
+
else None
|
|
191
|
+
),
|
|
214
192
|
}
|
|
215
193
|
|
|
216
194
|
|
|
217
195
|
@router.get("/api-keys")
|
|
218
|
-
async def list_api_keys(
|
|
219
|
-
current_user: User = Depends(get_current_active_user)
|
|
220
|
-
):
|
|
196
|
+
async def list_api_keys(current_user: User = Depends(get_current_active_user)):
|
|
221
197
|
"""List all API keys for current user"""
|
|
222
198
|
# In a real implementation, return API keys from database
|
|
223
199
|
return {
|
|
@@ -227,7 +203,7 @@ async def list_api_keys(
|
|
|
227
203
|
"name": "Production API",
|
|
228
204
|
"created_at": datetime.utcnow().isoformat(),
|
|
229
205
|
"last_used": datetime.utcnow().isoformat(),
|
|
230
|
-
"expires_at": None
|
|
206
|
+
"expires_at": None,
|
|
231
207
|
}
|
|
232
208
|
]
|
|
233
209
|
}
|
|
@@ -237,8 +213,8 @@ async def list_api_keys(
|
|
|
237
213
|
async def revoke_api_key(
|
|
238
214
|
key_id: str,
|
|
239
215
|
current_user: User = Depends(get_current_active_user),
|
|
240
|
-
db: Session = Depends(get_db)
|
|
216
|
+
db: Session = Depends(get_db),
|
|
241
217
|
):
|
|
242
218
|
"""Revoke an API key"""
|
|
243
219
|
# In a real implementation, revoke the API key
|
|
244
|
-
return {"message": f"API key {key_id} revoked successfully"}
|
|
220
|
+
return {"message": f"API key {key_id} revoked successfully"}
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
"""Backtesting API routes"""
|
|
2
2
|
|
|
3
3
|
from fastapi import APIRouter, Depends
|
|
4
|
+
|
|
4
5
|
from mcli.ml.auth import get_current_active_user
|
|
5
6
|
from mcli.ml.database.models import User
|
|
6
7
|
|
|
7
8
|
router = APIRouter()
|
|
8
9
|
|
|
10
|
+
|
|
9
11
|
@router.post("/run")
|
|
10
12
|
async def run_backtest(current_user: User = Depends(get_current_active_user)):
|
|
11
13
|
"""Run backtest"""
|
|
12
|
-
return {"status": "started"}
|
|
14
|
+
return {"status": "started"}
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
"""Data API routes"""
|
|
2
2
|
|
|
3
3
|
from fastapi import APIRouter, Depends
|
|
4
|
+
|
|
4
5
|
from mcli.ml.auth import get_current_active_user
|
|
5
6
|
from mcli.ml.database.models import User
|
|
6
7
|
|
|
7
8
|
router = APIRouter()
|
|
8
9
|
|
|
10
|
+
|
|
9
11
|
@router.get("/stocks/{ticker}")
|
|
10
12
|
async def get_stock_data(ticker: str, current_user: User = Depends(get_current_active_user)):
|
|
11
13
|
"""Get stock data"""
|
|
12
|
-
return {"ticker": ticker, "data": {}}
|
|
14
|
+
return {"ticker": ticker, "data": {}}
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
"""Model management API routes"""
|
|
2
2
|
|
|
3
|
-
from typing import List, Optional
|
|
4
3
|
from datetime import datetime
|
|
4
|
+
from typing import List, Optional
|
|
5
5
|
from uuid import UUID
|
|
6
6
|
|
|
7
|
-
from fastapi import APIRouter, Depends, HTTPException,
|
|
8
|
-
from sqlalchemy.orm import Session
|
|
7
|
+
from fastapi import APIRouter, Depends, File, HTTPException, Query, UploadFile, status
|
|
9
8
|
from sqlalchemy import select
|
|
9
|
+
from sqlalchemy.orm import Session
|
|
10
10
|
|
|
11
|
-
from mcli.ml.
|
|
12
|
-
from mcli.ml.
|
|
13
|
-
from mcli.ml.database.models import User, Model, ModelStatus, UserRole
|
|
14
|
-
from mcli.ml.api.schemas import ModelCreate, ModelResponse, ModelUpdate, ModelMetrics
|
|
11
|
+
from mcli.ml.api.schemas import ModelCreate, ModelMetrics, ModelResponse, ModelUpdate
|
|
12
|
+
from mcli.ml.auth import Permission, get_current_active_user, require_role
|
|
15
13
|
from mcli.ml.cache import cached
|
|
14
|
+
from mcli.ml.database.models import Model, ModelStatus, User, UserRole
|
|
15
|
+
from mcli.ml.database.session import get_async_db, get_db
|
|
16
16
|
from mcli.ml.tasks import train_model_task
|
|
17
17
|
|
|
18
18
|
router = APIRouter()
|
|
@@ -25,7 +25,7 @@ async def list_models(
|
|
|
25
25
|
limit: int = Query(100, le=1000),
|
|
26
26
|
status: Optional[ModelStatus] = None,
|
|
27
27
|
current_user: User = Depends(get_current_active_user),
|
|
28
|
-
db: Session = Depends(get_db)
|
|
28
|
+
db: Session = Depends(get_db),
|
|
29
29
|
):
|
|
30
30
|
"""List all available models"""
|
|
31
31
|
query = db.query(Model)
|
|
@@ -42,16 +42,13 @@ async def list_models(
|
|
|
42
42
|
async def get_model(
|
|
43
43
|
model_id: UUID,
|
|
44
44
|
current_user: User = Depends(get_current_active_user),
|
|
45
|
-
db: Session = Depends(get_db)
|
|
45
|
+
db: Session = Depends(get_db),
|
|
46
46
|
):
|
|
47
47
|
"""Get specific model details"""
|
|
48
48
|
model = db.query(Model).filter(Model.id == model_id).first()
|
|
49
49
|
|
|
50
50
|
if not model:
|
|
51
|
-
raise HTTPException(
|
|
52
|
-
status_code=status.HTTP_404_NOT_FOUND,
|
|
53
|
-
detail="Model not found"
|
|
54
|
-
)
|
|
51
|
+
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Model not found")
|
|
55
52
|
|
|
56
53
|
return ModelResponse.from_orm(model)
|
|
57
54
|
|
|
@@ -60,13 +57,11 @@ async def get_model(
|
|
|
60
57
|
async def create_model(
|
|
61
58
|
model_data: ModelCreate,
|
|
62
59
|
current_user: User = Depends(require_role(UserRole.ANALYST, UserRole.ADMIN)),
|
|
63
|
-
db: Session = Depends(get_db)
|
|
60
|
+
db: Session = Depends(get_db),
|
|
64
61
|
):
|
|
65
62
|
"""Create a new model"""
|
|
66
63
|
model = Model(
|
|
67
|
-
**model_data.dict(),
|
|
68
|
-
created_by=current_user.username,
|
|
69
|
-
status=ModelStatus.TRAINING
|
|
64
|
+
**model_data.dict(), created_by=current_user.username, status=ModelStatus.TRAINING
|
|
70
65
|
)
|
|
71
66
|
|
|
72
67
|
db.add(model)
|
|
@@ -84,16 +79,13 @@ async def update_model(
|
|
|
84
79
|
model_id: UUID,
|
|
85
80
|
updates: ModelUpdate,
|
|
86
81
|
current_user: User = Depends(require_role(UserRole.ANALYST, UserRole.ADMIN)),
|
|
87
|
-
db: Session = Depends(get_db)
|
|
82
|
+
db: Session = Depends(get_db),
|
|
88
83
|
):
|
|
89
84
|
"""Update model metadata"""
|
|
90
85
|
model = db.query(Model).filter(Model.id == model_id).first()
|
|
91
86
|
|
|
92
87
|
if not model:
|
|
93
|
-
raise HTTPException(
|
|
94
|
-
status_code=status.HTTP_404_NOT_FOUND,
|
|
95
|
-
detail="Model not found"
|
|
96
|
-
)
|
|
88
|
+
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Model not found")
|
|
97
89
|
|
|
98
90
|
for field, value in updates.dict(exclude_unset=True).items():
|
|
99
91
|
setattr(model, field, value)
|
|
@@ -110,21 +102,18 @@ async def deploy_model(
|
|
|
110
102
|
model_id: UUID,
|
|
111
103
|
endpoint: Optional[str] = None,
|
|
112
104
|
current_user: User = Depends(require_role(UserRole.ANALYST, UserRole.ADMIN)),
|
|
113
|
-
db: Session = Depends(get_db)
|
|
105
|
+
db: Session = Depends(get_db),
|
|
114
106
|
):
|
|
115
107
|
"""Deploy model to production"""
|
|
116
108
|
model = db.query(Model).filter(Model.id == model_id).first()
|
|
117
109
|
|
|
118
110
|
if not model:
|
|
119
|
-
raise HTTPException(
|
|
120
|
-
status_code=status.HTTP_404_NOT_FOUND,
|
|
121
|
-
detail="Model not found"
|
|
122
|
-
)
|
|
111
|
+
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Model not found")
|
|
123
112
|
|
|
124
113
|
if model.status != ModelStatus.TRAINED:
|
|
125
114
|
raise HTTPException(
|
|
126
115
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
127
|
-
detail="Model must be trained before deployment"
|
|
116
|
+
detail="Model must be trained before deployment",
|
|
128
117
|
)
|
|
129
118
|
|
|
130
119
|
# Deploy model (in real implementation, this would deploy to serving infrastructure)
|
|
@@ -134,26 +123,20 @@ async def deploy_model(
|
|
|
134
123
|
|
|
135
124
|
db.commit()
|
|
136
125
|
|
|
137
|
-
return {
|
|
138
|
-
"message": "Model deployed successfully",
|
|
139
|
-
"endpoint": model.deployment_endpoint
|
|
140
|
-
}
|
|
126
|
+
return {"message": "Model deployed successfully", "endpoint": model.deployment_endpoint}
|
|
141
127
|
|
|
142
128
|
|
|
143
129
|
@router.post("/{model_id}/archive")
|
|
144
130
|
async def archive_model(
|
|
145
131
|
model_id: UUID,
|
|
146
132
|
current_user: User = Depends(require_role(UserRole.ADMIN)),
|
|
147
|
-
db: Session = Depends(get_db)
|
|
133
|
+
db: Session = Depends(get_db),
|
|
148
134
|
):
|
|
149
135
|
"""Archive a model"""
|
|
150
136
|
model = db.query(Model).filter(Model.id == model_id).first()
|
|
151
137
|
|
|
152
138
|
if not model:
|
|
153
|
-
raise HTTPException(
|
|
154
|
-
status_code=status.HTTP_404_NOT_FOUND,
|
|
155
|
-
detail="Model not found"
|
|
156
|
-
)
|
|
139
|
+
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Model not found")
|
|
157
140
|
|
|
158
141
|
model.status = ModelStatus.ARCHIVED
|
|
159
142
|
db.commit()
|
|
@@ -166,16 +149,13 @@ async def archive_model(
|
|
|
166
149
|
async def get_model_metrics(
|
|
167
150
|
model_id: UUID,
|
|
168
151
|
current_user: User = Depends(get_current_active_user),
|
|
169
|
-
db: Session = Depends(get_db)
|
|
152
|
+
db: Session = Depends(get_db),
|
|
170
153
|
):
|
|
171
154
|
"""Get model performance metrics"""
|
|
172
155
|
model = db.query(Model).filter(Model.id == model_id).first()
|
|
173
156
|
|
|
174
157
|
if not model:
|
|
175
|
-
raise HTTPException(
|
|
176
|
-
status_code=status.HTTP_404_NOT_FOUND,
|
|
177
|
-
detail="Model not found"
|
|
178
|
-
)
|
|
158
|
+
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Model not found")
|
|
179
159
|
|
|
180
160
|
return ModelMetrics(
|
|
181
161
|
model_id=model.id,
|
|
@@ -185,7 +165,7 @@ async def get_model_metrics(
|
|
|
185
165
|
train_loss=model.train_loss,
|
|
186
166
|
val_loss=model.val_loss,
|
|
187
167
|
test_loss=model.test_loss,
|
|
188
|
-
additional_metrics=model.metrics or {}
|
|
168
|
+
additional_metrics=model.metrics or {},
|
|
189
169
|
)
|
|
190
170
|
|
|
191
171
|
|
|
@@ -194,16 +174,13 @@ async def retrain_model(
|
|
|
194
174
|
model_id: UUID,
|
|
195
175
|
hyperparameters: Optional[dict] = None,
|
|
196
176
|
current_user: User = Depends(require_role(UserRole.ANALYST, UserRole.ADMIN)),
|
|
197
|
-
db: Session = Depends(get_db)
|
|
177
|
+
db: Session = Depends(get_db),
|
|
198
178
|
):
|
|
199
179
|
"""Retrain an existing model"""
|
|
200
180
|
model = db.query(Model).filter(Model.id == model_id).first()
|
|
201
181
|
|
|
202
182
|
if not model:
|
|
203
|
-
raise HTTPException(
|
|
204
|
-
status_code=status.HTTP_404_NOT_FOUND,
|
|
205
|
-
detail="Model not found"
|
|
206
|
-
)
|
|
183
|
+
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Model not found")
|
|
207
184
|
|
|
208
185
|
# Update hyperparameters if provided
|
|
209
186
|
if hyperparameters:
|
|
@@ -223,16 +200,13 @@ async def upload_model_artifact(
|
|
|
223
200
|
model_id: UUID,
|
|
224
201
|
file: UploadFile = File(...),
|
|
225
202
|
current_user: User = Depends(require_role(UserRole.ANALYST, UserRole.ADMIN)),
|
|
226
|
-
db: Session = Depends(get_db)
|
|
203
|
+
db: Session = Depends(get_db),
|
|
227
204
|
):
|
|
228
205
|
"""Upload model artifact file"""
|
|
229
206
|
model = db.query(Model).filter(Model.id == model_id).first()
|
|
230
207
|
|
|
231
208
|
if not model:
|
|
232
|
-
raise HTTPException(
|
|
233
|
-
status_code=status.HTTP_404_NOT_FOUND,
|
|
234
|
-
detail="Model not found"
|
|
235
|
-
)
|
|
209
|
+
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Model not found")
|
|
236
210
|
|
|
237
211
|
# Save file (in real implementation, save to S3 or similar)
|
|
238
212
|
file_path = f"/models/{model_id}/{file.filename}"
|
|
@@ -241,31 +215,25 @@ async def upload_model_artifact(
|
|
|
241
215
|
model.model_path = file_path
|
|
242
216
|
db.commit()
|
|
243
217
|
|
|
244
|
-
return {
|
|
245
|
-
"message": "Model artifact uploaded successfully",
|
|
246
|
-
"path": file_path
|
|
247
|
-
}
|
|
218
|
+
return {"message": "Model artifact uploaded successfully", "path": file_path}
|
|
248
219
|
|
|
249
220
|
|
|
250
221
|
@router.delete("/{model_id}")
|
|
251
222
|
async def delete_model(
|
|
252
223
|
model_id: UUID,
|
|
253
224
|
current_user: User = Depends(require_role(UserRole.ADMIN)),
|
|
254
|
-
db: Session = Depends(get_db)
|
|
225
|
+
db: Session = Depends(get_db),
|
|
255
226
|
):
|
|
256
227
|
"""Delete a model"""
|
|
257
228
|
model = db.query(Model).filter(Model.id == model_id).first()
|
|
258
229
|
|
|
259
230
|
if not model:
|
|
260
|
-
raise HTTPException(
|
|
261
|
-
status_code=status.HTTP_404_NOT_FOUND,
|
|
262
|
-
detail="Model not found"
|
|
263
|
-
)
|
|
231
|
+
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Model not found")
|
|
264
232
|
|
|
265
233
|
if model.status == ModelStatus.DEPLOYED:
|
|
266
234
|
raise HTTPException(
|
|
267
235
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
268
|
-
detail="Cannot delete deployed model. Archive it first."
|
|
236
|
+
detail="Cannot delete deployed model. Archive it first.",
|
|
269
237
|
)
|
|
270
238
|
|
|
271
239
|
db.delete(model)
|
|
@@ -278,25 +246,18 @@ async def delete_model(
|
|
|
278
246
|
async def download_model(
|
|
279
247
|
model_id: UUID,
|
|
280
248
|
current_user: User = Depends(get_current_active_user),
|
|
281
|
-
db: Session = Depends(get_db)
|
|
249
|
+
db: Session = Depends(get_db),
|
|
282
250
|
):
|
|
283
251
|
"""Download model artifact"""
|
|
284
252
|
model = db.query(Model).filter(Model.id == model_id).first()
|
|
285
253
|
|
|
286
254
|
if not model:
|
|
287
|
-
raise HTTPException(
|
|
288
|
-
status_code=status.HTTP_404_NOT_FOUND,
|
|
289
|
-
detail="Model not found"
|
|
290
|
-
)
|
|
255
|
+
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Model not found")
|
|
291
256
|
|
|
292
257
|
if not model.model_path:
|
|
293
258
|
raise HTTPException(
|
|
294
|
-
status_code=status.HTTP_404_NOT_FOUND,
|
|
295
|
-
detail="Model artifact not available"
|
|
259
|
+
status_code=status.HTTP_404_NOT_FOUND, detail="Model artifact not available"
|
|
296
260
|
)
|
|
297
261
|
|
|
298
262
|
# 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
|
-
}
|
|
263
|
+
return {"download_url": f"https://storage.mcli-ml.com{model.model_path}", "expires_in": 3600}
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
"""Monitoring API routes"""
|
|
2
2
|
|
|
3
3
|
from fastapi import APIRouter, Depends
|
|
4
|
+
|
|
4
5
|
from mcli.ml.auth import get_current_active_user
|
|
5
6
|
from mcli.ml.database.models import User
|
|
6
7
|
|
|
7
8
|
router = APIRouter()
|
|
8
9
|
|
|
10
|
+
|
|
9
11
|
@router.get("/drift")
|
|
10
12
|
async def get_drift_status(current_user: User = Depends(get_current_active_user)):
|
|
11
13
|
"""Get drift monitoring status"""
|
|
12
|
-
return {"drift_detected": False}
|
|
14
|
+
return {"drift_detected": False}
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
"""Portfolio management API routes"""
|
|
2
2
|
|
|
3
3
|
from fastapi import APIRouter, Depends
|
|
4
|
+
|
|
4
5
|
from mcli.ml.auth import get_current_active_user
|
|
5
6
|
from mcli.ml.database.models import User
|
|
6
7
|
|
|
7
8
|
router = APIRouter()
|
|
8
9
|
|
|
10
|
+
|
|
9
11
|
@router.get("/")
|
|
10
12
|
async def list_portfolios(current_user: User = Depends(get_current_active_user)):
|
|
11
13
|
"""List user portfolios"""
|
|
12
|
-
return {"portfolios": []}
|
|
14
|
+
return {"portfolios": []}
|