mcli-framework 7.1.0__py3-none-any.whl → 7.1.2__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 +46 -13
- 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 +69 -58
- 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 +283 -152
- 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 +235 -0
- 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 +38 -18
- 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.0.dist-info → mcli_framework-7.1.2.dist-info}/METADATA +2 -2
- {mcli_framework-7.1.0.dist-info → mcli_framework-7.1.2.dist-info}/RECORD +94 -93
- {mcli_framework-7.1.0.dist-info → mcli_framework-7.1.2.dist-info}/WHEEL +0 -0
- {mcli_framework-7.1.0.dist-info → mcli_framework-7.1.2.dist-info}/entry_points.txt +0 -0
- {mcli_framework-7.1.0.dist-info → mcli_framework-7.1.2.dist-info}/licenses/LICENSE +0 -0
- {mcli_framework-7.1.0.dist-info → mcli_framework-7.1.2.dist-info}/top_level.txt +0 -0
mcli/ml/mlops/model_serving.py
CHANGED
|
@@ -1,28 +1,29 @@
|
|
|
1
1
|
"""REST API for model serving"""
|
|
2
2
|
|
|
3
|
-
from fastapi import FastAPI, HTTPException, BackgroundTasks, File, UploadFile
|
|
4
|
-
from fastapi.responses import JSONResponse
|
|
5
|
-
from pydantic import BaseModel, Field
|
|
6
|
-
from typing import Dict, Any, Optional, List, Union
|
|
7
|
-
import torch
|
|
8
|
-
import numpy as np
|
|
9
|
-
import pandas as pd
|
|
10
|
-
from datetime import datetime
|
|
11
|
-
import logging
|
|
12
3
|
import asyncio
|
|
13
|
-
from pathlib import Path
|
|
14
4
|
import json
|
|
15
|
-
import
|
|
5
|
+
import logging
|
|
6
|
+
import os
|
|
7
|
+
import sys
|
|
16
8
|
from contextlib import asynccontextmanager
|
|
9
|
+
from datetime import datetime
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Any, Dict, List, Optional, Union
|
|
12
|
+
|
|
13
|
+
import numpy as np
|
|
14
|
+
import pandas as pd
|
|
15
|
+
import torch
|
|
16
|
+
import uvicorn
|
|
17
|
+
from fastapi import BackgroundTasks, FastAPI, File, HTTPException, UploadFile
|
|
18
|
+
from fastapi.responses import JSONResponse
|
|
19
|
+
from pydantic import BaseModel, Field
|
|
17
20
|
|
|
18
|
-
import sys
|
|
19
|
-
import os
|
|
20
21
|
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "../.."))
|
|
21
22
|
|
|
22
|
-
from ml.models.recommendation_models import StockRecommendationModel, PortfolioRecommendation
|
|
23
|
-
from ml.features.stock_features import StockRecommendationFeatures
|
|
24
|
-
from ml.features.political_features import PoliticalInfluenceFeatures
|
|
25
23
|
from ml.features.ensemble_features import EnsembleFeatureBuilder
|
|
24
|
+
from ml.features.political_features import PoliticalInfluenceFeatures
|
|
25
|
+
from ml.features.stock_features import StockRecommendationFeatures
|
|
26
|
+
from ml.models.recommendation_models import PortfolioRecommendation, StockRecommendationModel
|
|
26
27
|
|
|
27
28
|
logger = logging.getLogger(__name__)
|
|
28
29
|
|
|
@@ -30,6 +31,7 @@ logger = logging.getLogger(__name__)
|
|
|
30
31
|
# Pydantic models for API
|
|
31
32
|
class PredictionRequest(BaseModel):
|
|
32
33
|
"""Request model for predictions"""
|
|
34
|
+
|
|
33
35
|
trading_data: Dict[str, Any] = Field(..., description="Politician trading data")
|
|
34
36
|
stock_data: Optional[Dict[str, Any]] = Field(None, description="Stock price data")
|
|
35
37
|
tickers: List[str] = Field(..., description="Stock tickers to analyze")
|
|
@@ -38,6 +40,7 @@ class PredictionRequest(BaseModel):
|
|
|
38
40
|
|
|
39
41
|
class PredictionResponse(BaseModel):
|
|
40
42
|
"""Response model for predictions"""
|
|
43
|
+
|
|
41
44
|
recommendations: List[Dict[str, Any]]
|
|
42
45
|
timestamp: str
|
|
43
46
|
model_version: str
|
|
@@ -46,6 +49,7 @@ class PredictionResponse(BaseModel):
|
|
|
46
49
|
|
|
47
50
|
class HealthResponse(BaseModel):
|
|
48
51
|
"""Health check response"""
|
|
52
|
+
|
|
49
53
|
status: str
|
|
50
54
|
model_loaded: bool
|
|
51
55
|
model_version: Optional[str]
|
|
@@ -54,6 +58,7 @@ class HealthResponse(BaseModel):
|
|
|
54
58
|
|
|
55
59
|
class ModelMetricsResponse(BaseModel):
|
|
56
60
|
"""Model metrics response"""
|
|
61
|
+
|
|
57
62
|
accuracy: float
|
|
58
63
|
precision: float
|
|
59
64
|
recall: float
|
|
@@ -63,12 +68,14 @@ class ModelMetricsResponse(BaseModel):
|
|
|
63
68
|
|
|
64
69
|
class BatchPredictionRequest(BaseModel):
|
|
65
70
|
"""Batch prediction request"""
|
|
71
|
+
|
|
66
72
|
batch_id: str
|
|
67
73
|
data: List[PredictionRequest]
|
|
68
74
|
|
|
69
75
|
|
|
70
76
|
class BatchPredictionResponse(BaseModel):
|
|
71
77
|
"""Batch prediction response"""
|
|
78
|
+
|
|
72
79
|
batch_id: str
|
|
73
80
|
status: str
|
|
74
81
|
progress: float
|
|
@@ -85,12 +92,7 @@ class ModelEndpoint:
|
|
|
85
92
|
self.feature_extractors = {}
|
|
86
93
|
self.start_time = datetime.now()
|
|
87
94
|
self.prediction_cache = {}
|
|
88
|
-
self.metrics = {
|
|
89
|
-
"total_predictions": 0,
|
|
90
|
-
"avg_latency_ms": 0,
|
|
91
|
-
"cache_hits": 0,
|
|
92
|
-
"errors": 0
|
|
93
|
-
}
|
|
95
|
+
self.metrics = {"total_predictions": 0, "avg_latency_ms": 0, "cache_hits": 0, "errors": 0}
|
|
94
96
|
|
|
95
97
|
if model_path:
|
|
96
98
|
self.load_model(model_path)
|
|
@@ -102,14 +104,14 @@ class ModelEndpoint:
|
|
|
102
104
|
self.feature_extractors = {
|
|
103
105
|
"stock": StockRecommendationFeatures(),
|
|
104
106
|
"political": PoliticalInfluenceFeatures(),
|
|
105
|
-
"ensemble": EnsembleFeatureBuilder()
|
|
107
|
+
"ensemble": EnsembleFeatureBuilder(),
|
|
106
108
|
}
|
|
107
109
|
logger.info("Feature extractors initialized")
|
|
108
110
|
|
|
109
111
|
def load_model(self, model_path: str):
|
|
110
112
|
"""Load model from file"""
|
|
111
113
|
try:
|
|
112
|
-
checkpoint = torch.load(model_path, map_location=
|
|
114
|
+
checkpoint = torch.load(model_path, map_location="cpu")
|
|
113
115
|
|
|
114
116
|
# Reconstruct model (simplified - would need proper config)
|
|
115
117
|
from ml.models.ensemble_models import EnsembleConfig, ModelConfig
|
|
@@ -124,7 +126,7 @@ class ModelEndpoint:
|
|
|
124
126
|
learning_rate=0.001,
|
|
125
127
|
weight_decay=1e-4,
|
|
126
128
|
batch_size=32,
|
|
127
|
-
epochs=10
|
|
129
|
+
epochs=10,
|
|
128
130
|
)
|
|
129
131
|
]
|
|
130
132
|
|
|
@@ -136,24 +138,27 @@ class ModelEndpoint:
|
|
|
136
138
|
self.model = StockRecommendationModel(input_dim, recommendation_config)
|
|
137
139
|
|
|
138
140
|
# Load state
|
|
139
|
-
self.model.load_state_dict(checkpoint[
|
|
141
|
+
self.model.load_state_dict(checkpoint["model_state_dict"])
|
|
140
142
|
self.model.eval()
|
|
141
143
|
|
|
142
|
-
self.model_version = checkpoint.get(
|
|
144
|
+
self.model_version = checkpoint.get("version", "1.0.0")
|
|
143
145
|
logger.info(f"Model loaded from {model_path} (version: {self.model_version})")
|
|
144
146
|
|
|
145
147
|
except Exception as e:
|
|
146
148
|
logger.error(f"Failed to load model: {e}")
|
|
147
149
|
raise
|
|
148
150
|
|
|
149
|
-
def extract_features(
|
|
150
|
-
|
|
151
|
+
def extract_features(
|
|
152
|
+
self, trading_data: pd.DataFrame, stock_data: Optional[pd.DataFrame] = None
|
|
153
|
+
) -> np.ndarray:
|
|
151
154
|
"""Extract features from raw data"""
|
|
152
155
|
features = pd.DataFrame()
|
|
153
156
|
|
|
154
157
|
# Extract political features
|
|
155
158
|
if not trading_data.empty:
|
|
156
|
-
political_features = self.feature_extractors["political"].extract_influence_features(
|
|
159
|
+
political_features = self.feature_extractors["political"].extract_influence_features(
|
|
160
|
+
trading_data
|
|
161
|
+
)
|
|
157
162
|
features = pd.concat([features, political_features], axis=1)
|
|
158
163
|
|
|
159
164
|
# Extract stock features if available
|
|
@@ -196,9 +201,7 @@ class ModelEndpoint:
|
|
|
196
201
|
# Generate recommendations
|
|
197
202
|
if self.model:
|
|
198
203
|
recommendations = self.model.generate_recommendations(
|
|
199
|
-
features,
|
|
200
|
-
request.tickers,
|
|
201
|
-
market_data=stock_df
|
|
204
|
+
features, request.tickers, market_data=stock_df
|
|
202
205
|
)
|
|
203
206
|
else:
|
|
204
207
|
# Mock recommendations if no model loaded
|
|
@@ -216,7 +219,7 @@ class ModelEndpoint:
|
|
|
216
219
|
"entry_price": rec.entry_price,
|
|
217
220
|
"target_price": rec.target_price,
|
|
218
221
|
"stop_loss": rec.stop_loss,
|
|
219
|
-
"reason": rec.recommendation_reason
|
|
222
|
+
"reason": rec.recommendation_reason,
|
|
220
223
|
}
|
|
221
224
|
for rec in recommendations
|
|
222
225
|
]
|
|
@@ -227,15 +230,15 @@ class ModelEndpoint:
|
|
|
227
230
|
# Update metrics
|
|
228
231
|
self.metrics["total_predictions"] += 1
|
|
229
232
|
self.metrics["avg_latency_ms"] = (
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
)
|
|
233
|
+
self.metrics["avg_latency_ms"] * (self.metrics["total_predictions"] - 1)
|
|
234
|
+
+ processing_time
|
|
235
|
+
) / self.metrics["total_predictions"]
|
|
233
236
|
|
|
234
237
|
response = PredictionResponse(
|
|
235
238
|
recommendations=recommendations_dict,
|
|
236
239
|
timestamp=datetime.now().isoformat(),
|
|
237
240
|
model_version=self.model_version,
|
|
238
|
-
processing_time_ms=processing_time
|
|
241
|
+
processing_time_ms=processing_time,
|
|
239
242
|
)
|
|
240
243
|
|
|
241
244
|
# Cache response
|
|
@@ -267,7 +270,7 @@ class ModelEndpoint:
|
|
|
267
270
|
expected_return=np.random.normal(0.05, 0.15),
|
|
268
271
|
risk_adjusted_score=np.random.random(),
|
|
269
272
|
position_size=np.random.uniform(0.01, 0.1),
|
|
270
|
-
recommendation_reason="Mock recommendation for testing"
|
|
273
|
+
recommendation_reason="Mock recommendation for testing",
|
|
271
274
|
)
|
|
272
275
|
recommendations.append(rec)
|
|
273
276
|
return recommendations
|
|
@@ -280,7 +283,7 @@ class ModelEndpoint:
|
|
|
280
283
|
status="healthy",
|
|
281
284
|
model_loaded=self.model is not None,
|
|
282
285
|
model_version=self.model_version,
|
|
283
|
-
uptime_seconds=uptime
|
|
286
|
+
uptime_seconds=uptime,
|
|
284
287
|
)
|
|
285
288
|
|
|
286
289
|
def get_metrics(self) -> Dict[str, Any]:
|
|
@@ -289,7 +292,7 @@ class ModelEndpoint:
|
|
|
289
292
|
**self.metrics,
|
|
290
293
|
"model_version": self.model_version,
|
|
291
294
|
"uptime_seconds": (datetime.now() - self.start_time).total_seconds(),
|
|
292
|
-
"cache_size": len(self.prediction_cache)
|
|
295
|
+
"cache_size": len(self.prediction_cache),
|
|
293
296
|
}
|
|
294
297
|
|
|
295
298
|
|
|
@@ -310,7 +313,7 @@ class PredictionService:
|
|
|
310
313
|
"status": "processing",
|
|
311
314
|
"progress": 0.0,
|
|
312
315
|
"results": [],
|
|
313
|
-
"start_time": datetime.now()
|
|
316
|
+
"start_time": datetime.now(),
|
|
314
317
|
}
|
|
315
318
|
|
|
316
319
|
# Process each request
|
|
@@ -334,10 +337,7 @@ class PredictionService:
|
|
|
334
337
|
self.batch_jobs[batch_id]["end_time"] = datetime.now()
|
|
335
338
|
|
|
336
339
|
return BatchPredictionResponse(
|
|
337
|
-
batch_id=batch_id,
|
|
338
|
-
status="completed",
|
|
339
|
-
progress=1.0,
|
|
340
|
-
results=results
|
|
340
|
+
batch_id=batch_id, status="completed", progress=1.0, results=results
|
|
341
341
|
)
|
|
342
342
|
|
|
343
343
|
def get_batch_status(self, batch_id: str) -> BatchPredictionResponse:
|
|
@@ -351,7 +351,7 @@ class PredictionService:
|
|
|
351
351
|
batch_id=batch_id,
|
|
352
352
|
status=job["status"],
|
|
353
353
|
progress=job["progress"],
|
|
354
|
-
results=job.get("results")
|
|
354
|
+
results=job.get("results"),
|
|
355
355
|
)
|
|
356
356
|
|
|
357
357
|
|
|
@@ -378,7 +378,7 @@ class ModelServer:
|
|
|
378
378
|
title="Stock Recommendation API",
|
|
379
379
|
description="ML-powered stock recommendation system based on politician trading data",
|
|
380
380
|
version="1.0.0",
|
|
381
|
-
lifespan=self.lifespan
|
|
381
|
+
lifespan=self.lifespan,
|
|
382
382
|
)
|
|
383
383
|
|
|
384
384
|
# Health check
|
|
@@ -401,19 +401,14 @@ class ModelServer:
|
|
|
401
401
|
|
|
402
402
|
# Batch prediction
|
|
403
403
|
@app.post("/batch/predict", response_model=BatchPredictionResponse)
|
|
404
|
-
async def batch_predict(
|
|
405
|
-
|
|
404
|
+
async def batch_predict(
|
|
405
|
+
batch_request: BatchPredictionRequest, background_tasks: BackgroundTasks
|
|
406
|
+
):
|
|
406
407
|
"""Submit batch prediction job"""
|
|
407
|
-
background_tasks.add_task(
|
|
408
|
-
self.prediction_service.process_batch,
|
|
409
|
-
batch_request
|
|
410
|
-
)
|
|
408
|
+
background_tasks.add_task(self.prediction_service.process_batch, batch_request)
|
|
411
409
|
|
|
412
410
|
return BatchPredictionResponse(
|
|
413
|
-
batch_id=batch_request.batch_id,
|
|
414
|
-
status="submitted",
|
|
415
|
-
progress=0.0,
|
|
416
|
-
results=None
|
|
411
|
+
batch_id=batch_request.batch_id, status="submitted", progress=0.0, results=None
|
|
417
412
|
)
|
|
418
413
|
|
|
419
414
|
# Batch status
|
|
@@ -442,12 +437,14 @@ class ModelServer:
|
|
|
442
437
|
df = pd.read_csv(pd.io.common.BytesIO(content))
|
|
443
438
|
|
|
444
439
|
# Extract tickers
|
|
445
|
-
tickers =
|
|
440
|
+
tickers = (
|
|
441
|
+
df["ticker_cleaned"].unique().tolist() if "ticker_cleaned" in df.columns else []
|
|
442
|
+
)
|
|
446
443
|
|
|
447
444
|
# Create request
|
|
448
445
|
request = PredictionRequest(
|
|
449
|
-
trading_data=df.to_dict(orient=
|
|
450
|
-
tickers=tickers[:5] # Limit to 5 tickers
|
|
446
|
+
trading_data=df.to_dict(orient="records")[0] if len(df) > 0 else {},
|
|
447
|
+
tickers=tickers[:5], # Limit to 5 tickers
|
|
451
448
|
)
|
|
452
449
|
|
|
453
450
|
return await self.model_endpoint.predict(request)
|
|
@@ -478,4 +475,4 @@ def main():
|
|
|
478
475
|
|
|
479
476
|
|
|
480
477
|
if __name__ == "__main__":
|
|
481
|
-
main()
|
|
478
|
+
main()
|