mcli-framework 7.1.1__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.

Files changed (94) hide show
  1. mcli/app/completion_cmd.py +59 -49
  2. mcli/app/completion_helpers.py +60 -138
  3. mcli/app/logs_cmd.py +6 -2
  4. mcli/app/main.py +17 -14
  5. mcli/app/model_cmd.py +19 -4
  6. mcli/chat/chat.py +3 -2
  7. mcli/lib/search/cached_vectorizer.py +1 -0
  8. mcli/lib/services/data_pipeline.py +12 -5
  9. mcli/lib/services/lsh_client.py +68 -57
  10. mcli/ml/api/app.py +28 -36
  11. mcli/ml/api/middleware.py +8 -16
  12. mcli/ml/api/routers/admin_router.py +3 -1
  13. mcli/ml/api/routers/auth_router.py +32 -56
  14. mcli/ml/api/routers/backtest_router.py +3 -1
  15. mcli/ml/api/routers/data_router.py +3 -1
  16. mcli/ml/api/routers/model_router.py +35 -74
  17. mcli/ml/api/routers/monitoring_router.py +3 -1
  18. mcli/ml/api/routers/portfolio_router.py +3 -1
  19. mcli/ml/api/routers/prediction_router.py +60 -65
  20. mcli/ml/api/routers/trade_router.py +6 -2
  21. mcli/ml/api/routers/websocket_router.py +12 -9
  22. mcli/ml/api/schemas.py +10 -2
  23. mcli/ml/auth/auth_manager.py +49 -114
  24. mcli/ml/auth/models.py +30 -15
  25. mcli/ml/auth/permissions.py +12 -19
  26. mcli/ml/backtesting/backtest_engine.py +134 -108
  27. mcli/ml/backtesting/performance_metrics.py +142 -108
  28. mcli/ml/cache.py +12 -18
  29. mcli/ml/cli/main.py +37 -23
  30. mcli/ml/config/settings.py +29 -12
  31. mcli/ml/dashboard/app.py +122 -130
  32. mcli/ml/dashboard/app_integrated.py +216 -150
  33. mcli/ml/dashboard/app_supabase.py +176 -108
  34. mcli/ml/dashboard/app_training.py +212 -206
  35. mcli/ml/dashboard/cli.py +14 -5
  36. mcli/ml/data_ingestion/api_connectors.py +51 -81
  37. mcli/ml/data_ingestion/data_pipeline.py +127 -125
  38. mcli/ml/data_ingestion/stream_processor.py +72 -80
  39. mcli/ml/database/migrations/env.py +3 -2
  40. mcli/ml/database/models.py +112 -79
  41. mcli/ml/database/session.py +6 -5
  42. mcli/ml/experimentation/ab_testing.py +149 -99
  43. mcli/ml/features/ensemble_features.py +9 -8
  44. mcli/ml/features/political_features.py +6 -5
  45. mcli/ml/features/recommendation_engine.py +15 -14
  46. mcli/ml/features/stock_features.py +7 -6
  47. mcli/ml/features/test_feature_engineering.py +8 -7
  48. mcli/ml/logging.py +10 -15
  49. mcli/ml/mlops/data_versioning.py +57 -64
  50. mcli/ml/mlops/experiment_tracker.py +49 -41
  51. mcli/ml/mlops/model_serving.py +59 -62
  52. mcli/ml/mlops/pipeline_orchestrator.py +203 -149
  53. mcli/ml/models/base_models.py +8 -7
  54. mcli/ml/models/ensemble_models.py +6 -5
  55. mcli/ml/models/recommendation_models.py +7 -6
  56. mcli/ml/models/test_models.py +18 -14
  57. mcli/ml/monitoring/drift_detection.py +95 -74
  58. mcli/ml/monitoring/metrics.py +10 -22
  59. mcli/ml/optimization/portfolio_optimizer.py +172 -132
  60. mcli/ml/predictions/prediction_engine.py +62 -50
  61. mcli/ml/preprocessing/data_cleaners.py +6 -5
  62. mcli/ml/preprocessing/feature_extractors.py +7 -6
  63. mcli/ml/preprocessing/ml_pipeline.py +3 -2
  64. mcli/ml/preprocessing/politician_trading_preprocessor.py +11 -10
  65. mcli/ml/preprocessing/test_preprocessing.py +4 -4
  66. mcli/ml/scripts/populate_sample_data.py +36 -16
  67. mcli/ml/tasks.py +82 -83
  68. mcli/ml/tests/test_integration.py +86 -76
  69. mcli/ml/tests/test_training_dashboard.py +169 -142
  70. mcli/mygroup/test_cmd.py +2 -1
  71. mcli/self/self_cmd.py +31 -16
  72. mcli/self/test_cmd.py +2 -1
  73. mcli/workflow/dashboard/dashboard_cmd.py +13 -6
  74. mcli/workflow/lsh_integration.py +46 -58
  75. mcli/workflow/politician_trading/commands.py +576 -427
  76. mcli/workflow/politician_trading/config.py +7 -7
  77. mcli/workflow/politician_trading/connectivity.py +35 -33
  78. mcli/workflow/politician_trading/data_sources.py +72 -71
  79. mcli/workflow/politician_trading/database.py +18 -16
  80. mcli/workflow/politician_trading/demo.py +4 -3
  81. mcli/workflow/politician_trading/models.py +5 -5
  82. mcli/workflow/politician_trading/monitoring.py +13 -13
  83. mcli/workflow/politician_trading/scrapers.py +332 -224
  84. mcli/workflow/politician_trading/scrapers_california.py +116 -94
  85. mcli/workflow/politician_trading/scrapers_eu.py +70 -71
  86. mcli/workflow/politician_trading/scrapers_uk.py +118 -90
  87. mcli/workflow/politician_trading/scrapers_us_states.py +125 -92
  88. mcli/workflow/politician_trading/workflow.py +98 -71
  89. {mcli_framework-7.1.1.dist-info → mcli_framework-7.1.2.dist-info}/METADATA +1 -1
  90. {mcli_framework-7.1.1.dist-info → mcli_framework-7.1.2.dist-info}/RECORD +94 -94
  91. {mcli_framework-7.1.1.dist-info → mcli_framework-7.1.2.dist-info}/WHEEL +0 -0
  92. {mcli_framework-7.1.1.dist-info → mcli_framework-7.1.2.dist-info}/entry_points.txt +0 -0
  93. {mcli_framework-7.1.1.dist-info → mcli_framework-7.1.2.dist-info}/licenses/LICENSE +0 -0
  94. {mcli_framework-7.1.1.dist-info → mcli_framework-7.1.2.dist-info}/top_level.txt +0 -0
@@ -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 uvicorn
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='cpu')
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['model_state_dict'])
141
+ self.model.load_state_dict(checkpoint["model_state_dict"])
140
142
  self.model.eval()
141
143
 
142
- self.model_version = checkpoint.get('version', '1.0.0')
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(self, trading_data: pd.DataFrame,
150
- stock_data: Optional[pd.DataFrame] = None) -> np.ndarray:
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(trading_data)
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
- (self.metrics["avg_latency_ms"] * (self.metrics["total_predictions"] - 1) + processing_time)
231
- / self.metrics["total_predictions"]
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(batch_request: BatchPredictionRequest,
405
- background_tasks: BackgroundTasks):
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 = df['ticker_cleaned'].unique().tolist() if 'ticker_cleaned' in df.columns else []
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='records')[0] if len(df) > 0 else {},
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()