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
mcli/ml/tasks.py ADDED
@@ -0,0 +1,400 @@
1
+ """Celery background tasks for ML system"""
2
+
3
+ from celery import Celery, Task
4
+ from celery.schedules import crontab
5
+ from datetime import datetime, timedelta
6
+ import asyncio
7
+ from typing import Dict, Any
8
+
9
+ from mcli.ml.config import settings
10
+ from mcli.ml.logging import get_logger
11
+
12
+ logger = get_logger(__name__)
13
+
14
+ # Create Celery app
15
+ celery_app = Celery(
16
+ 'mcli_ml',
17
+ broker=settings.redis.url,
18
+ backend=settings.redis.url,
19
+ include=['mcli.ml.tasks']
20
+ )
21
+
22
+ # Celery configuration
23
+ celery_app.conf.update(
24
+ task_serializer='json',
25
+ accept_content=['json'],
26
+ result_serializer='json',
27
+ timezone='UTC',
28
+ enable_utc=True,
29
+ task_track_started=True,
30
+ task_time_limit=3600, # 1 hour
31
+ task_soft_time_limit=3300, # 55 minutes
32
+ worker_prefetch_multiplier=1,
33
+ worker_max_tasks_per_child=100,
34
+ )
35
+
36
+ # Schedule periodic tasks
37
+ celery_app.conf.beat_schedule = {
38
+ 'update-stock-data': {
39
+ 'task': 'mcli.ml.tasks.update_stock_data_task',
40
+ 'schedule': crontab(minute='*/15'), # Every 15 minutes
41
+ },
42
+ 'retrain-models': {
43
+ 'task': 'mcli.ml.tasks.retrain_models_task',
44
+ 'schedule': crontab(hour=2, minute=0), # Daily at 2 AM
45
+ },
46
+ 'check-model-drift': {
47
+ 'task': 'mcli.ml.tasks.check_model_drift_task',
48
+ 'schedule': crontab(minute=0), # Every hour
49
+ },
50
+ 'cleanup-old-predictions': {
51
+ 'task': 'mcli.ml.tasks.cleanup_predictions_task',
52
+ 'schedule': crontab(hour=3, minute=0), # Daily at 3 AM
53
+ },
54
+ 'generate-daily-report': {
55
+ 'task': 'mcli.ml.tasks.generate_daily_report_task',
56
+ 'schedule': crontab(hour=6, minute=0), # Daily at 6 AM
57
+ },
58
+ 'fetch-politician-trades': {
59
+ 'task': 'mcli.ml.tasks.fetch_politician_trades_task',
60
+ 'schedule': crontab(minute='*/30'), # Every 30 minutes
61
+ },
62
+ }
63
+
64
+
65
+ class MLTask(Task):
66
+ """Base task with error handling"""
67
+
68
+ def on_failure(self, exc, task_id, args, kwargs, einfo):
69
+ """Log task failure"""
70
+ logger.error(f"Task {self.name} failed: {exc}", exc_info=True)
71
+
72
+ def on_retry(self, exc, task_id, args, kwargs, einfo):
73
+ """Log task retry"""
74
+ logger.warning(f"Task {self.name} retrying: {exc}")
75
+
76
+ def on_success(self, retval, task_id, args, kwargs):
77
+ """Log task success"""
78
+ logger.info(f"Task {self.name} completed successfully")
79
+
80
+
81
+ @celery_app.task(base=MLTask, bind=True, max_retries=3)
82
+ def train_model_task(self, model_id: str, retrain: bool = False) -> Dict[str, Any]:
83
+ """Train or retrain a model"""
84
+ try:
85
+ logger.info(f"Starting training for model {model_id}")
86
+
87
+ from mcli.ml.mlops.pipeline_orchestrator import MLPipeline, PipelineConfig
88
+ from mcli.ml.database.session import SessionLocal
89
+ from mcli.ml.database.models import Model, ModelStatus
90
+
91
+ # Get model from database
92
+ db = SessionLocal()
93
+ model = db.query(Model).filter(Model.id == model_id).first()
94
+
95
+ if not model:
96
+ raise ValueError(f"Model {model_id} not found")
97
+
98
+ # Update status
99
+ model.status = ModelStatus.TRAINING
100
+ db.commit()
101
+
102
+ # Configure and run pipeline
103
+ config = PipelineConfig(
104
+ experiment_name=f"model_{model_id}",
105
+ enable_mlflow=True
106
+ )
107
+
108
+ pipeline = MLPipeline(config)
109
+
110
+ # Run training asynchronously
111
+ loop = asyncio.new_event_loop()
112
+ asyncio.set_event_loop(loop)
113
+ result = loop.run_until_complete(pipeline.run_async())
114
+
115
+ # Update model with results
116
+ model.status = ModelStatus.TRAINED
117
+ model.train_accuracy = result.get('train_accuracy')
118
+ model.val_accuracy = result.get('val_accuracy')
119
+ model.test_accuracy = result.get('test_accuracy')
120
+ model.metrics = result
121
+
122
+ db.commit()
123
+ db.close()
124
+
125
+ logger.info(f"Training completed for model {model_id}")
126
+ return {"model_id": model_id, "status": "completed", "metrics": result}
127
+
128
+ except Exception as e:
129
+ logger.error(f"Training failed for model {model_id}: {e}")
130
+ self.retry(countdown=60, exc=e)
131
+
132
+
133
+ @celery_app.task(base=MLTask, bind=True)
134
+ def update_stock_data_task(self, ticker: str = None) -> Dict[str, Any]:
135
+ """Update stock data from external APIs"""
136
+ try:
137
+ logger.info(f"Updating stock data{f' for {ticker}' if ticker else ''}")
138
+
139
+ from mcli.ml.data_ingestion.api_connectors import YahooFinanceConnector
140
+ from mcli.ml.database.session import SessionLocal
141
+ from mcli.ml.database.models import StockData
142
+
143
+ connector = YahooFinanceConnector()
144
+ db = SessionLocal()
145
+
146
+ if ticker:
147
+ tickers = [ticker]
148
+ else:
149
+ # Get all tracked tickers
150
+ tickers = db.query(StockData.ticker).distinct().limit(100).all()
151
+ tickers = [t[0] for t in tickers]
152
+
153
+ updated_count = 0
154
+ for ticker in tickers:
155
+ try:
156
+ # Fetch latest data
157
+ data = asyncio.run(connector.fetch_stock_data(ticker))
158
+
159
+ # Update database
160
+ stock = db.query(StockData).filter(StockData.ticker == ticker).first()
161
+ if not stock:
162
+ stock = StockData(ticker=ticker)
163
+ db.add(stock)
164
+
165
+ stock.current_price = data.get('price')
166
+ stock.volume = data.get('volume')
167
+ stock.change_1d = data.get('change_1d')
168
+ stock.last_updated = datetime.utcnow()
169
+
170
+ updated_count += 1
171
+
172
+ except Exception as e:
173
+ logger.error(f"Failed to update {ticker}: {e}")
174
+
175
+ db.commit()
176
+ db.close()
177
+
178
+ logger.info(f"Updated {updated_count} stocks")
179
+ return {"updated": updated_count, "total": len(tickers)}
180
+
181
+ except Exception as e:
182
+ logger.error(f"Stock data update failed: {e}")
183
+ raise
184
+
185
+
186
+ @celery_app.task(base=MLTask)
187
+ def check_model_drift_task() -> Dict[str, Any]:
188
+ """Check for model drift"""
189
+ try:
190
+ logger.info("Checking for model drift")
191
+
192
+ from mcli.ml.monitoring.drift_detection import ModelMonitor
193
+ from mcli.ml.database.session import SessionLocal
194
+ from mcli.ml.database.models import Model, ModelStatus
195
+
196
+ db = SessionLocal()
197
+ deployed_models = db.query(Model).filter(
198
+ Model.status == ModelStatus.DEPLOYED
199
+ ).all()
200
+
201
+ drift_detected = []
202
+ for model in deployed_models:
203
+ monitor = ModelMonitor(str(model.id))
204
+
205
+ # Check drift (simplified)
206
+ if monitor.check_drift():
207
+ drift_detected.append(str(model.id))
208
+ logger.warning(f"Drift detected in model {model.id}")
209
+
210
+ db.close()
211
+
212
+ return {
213
+ "checked": len(deployed_models),
214
+ "drift_detected": len(drift_detected),
215
+ "models_with_drift": drift_detected
216
+ }
217
+
218
+ except Exception as e:
219
+ logger.error(f"Drift check failed: {e}")
220
+ raise
221
+
222
+
223
+ @celery_app.task(base=MLTask)
224
+ def cleanup_predictions_task() -> Dict[str, Any]:
225
+ """Clean up old predictions"""
226
+ try:
227
+ logger.info("Cleaning up old predictions")
228
+
229
+ from mcli.ml.database.session import SessionLocal
230
+ from mcli.ml.database.models import Prediction
231
+
232
+ db = SessionLocal()
233
+
234
+ # Delete predictions older than 90 days
235
+ cutoff_date = datetime.utcnow() - timedelta(days=90)
236
+ deleted = db.query(Prediction).filter(
237
+ Prediction.created_at < cutoff_date
238
+ ).delete()
239
+
240
+ db.commit()
241
+ db.close()
242
+
243
+ logger.info(f"Deleted {deleted} old predictions")
244
+ return {"deleted": deleted}
245
+
246
+ except Exception as e:
247
+ logger.error(f"Cleanup failed: {e}")
248
+ raise
249
+
250
+
251
+ @celery_app.task(base=MLTask)
252
+ def retrain_models_task() -> Dict[str, Any]:
253
+ """Retrain models on schedule"""
254
+ try:
255
+ logger.info("Starting scheduled model retraining")
256
+
257
+ from mcli.ml.database.session import SessionLocal
258
+ from mcli.ml.database.models import Model, ModelStatus
259
+
260
+ db = SessionLocal()
261
+
262
+ # Get models that need retraining
263
+ models_to_retrain = db.query(Model).filter(
264
+ Model.status == ModelStatus.DEPLOYED,
265
+ Model.updated_at < datetime.utcnow() - timedelta(days=7)
266
+ ).all()
267
+
268
+ retrained = []
269
+ for model in models_to_retrain:
270
+ # Trigger retraining
271
+ train_model_task.delay(str(model.id), retrain=True)
272
+ retrained.append(str(model.id))
273
+
274
+ db.close()
275
+
276
+ logger.info(f"Triggered retraining for {len(retrained)} models")
277
+ return {"retrained": retrained}
278
+
279
+ except Exception as e:
280
+ logger.error(f"Retraining failed: {e}")
281
+ raise
282
+
283
+
284
+ @celery_app.task(base=MLTask)
285
+ def generate_daily_report_task() -> Dict[str, Any]:
286
+ """Generate daily performance report"""
287
+ try:
288
+ logger.info("Generating daily report")
289
+
290
+ from mcli.ml.database.session import SessionLocal
291
+ from mcli.ml.database.models import Prediction, Portfolio, User
292
+
293
+ db = SessionLocal()
294
+
295
+ # Gather statistics
296
+ total_predictions = db.query(Prediction).filter(
297
+ Prediction.prediction_date >= datetime.utcnow() - timedelta(days=1)
298
+ ).count()
299
+
300
+ active_portfolios = db.query(Portfolio).filter(
301
+ Portfolio.is_active == True
302
+ ).count()
303
+
304
+ active_users = db.query(User).filter(
305
+ User.last_login_at >= datetime.utcnow() - timedelta(days=1)
306
+ ).count()
307
+
308
+ db.close()
309
+
310
+ report = {
311
+ "date": datetime.utcnow().date().isoformat(),
312
+ "predictions_24h": total_predictions,
313
+ "active_portfolios": active_portfolios,
314
+ "active_users_24h": active_users,
315
+ "generated_at": datetime.utcnow().isoformat()
316
+ }
317
+
318
+ # In real implementation, send email or save to storage
319
+ logger.info(f"Daily report generated: {report}")
320
+
321
+ return report
322
+
323
+ except Exception as e:
324
+ logger.error(f"Report generation failed: {e}")
325
+ raise
326
+
327
+
328
+ @celery_app.task(base=MLTask)
329
+ def fetch_politician_trades_task() -> Dict[str, Any]:
330
+ """Fetch latest politician trades"""
331
+ try:
332
+ logger.info("Fetching politician trades")
333
+
334
+ from mcli.ml.data_ingestion.api_connectors import CongressionalTradingConnector
335
+ from mcli.ml.database.session import SessionLocal
336
+ from mcli.ml.database.models import Trade, Politician
337
+
338
+ connector = CongressionalTradingConnector()
339
+ db = SessionLocal()
340
+
341
+ # Fetch recent trades
342
+ trades_data = asyncio.run(connector.fetch_recent_trades())
343
+
344
+ new_trades = 0
345
+ for trade_info in trades_data:
346
+ # Check if trade exists
347
+ existing = db.query(Trade).filter(
348
+ Trade.politician_id == trade_info['politician_id'],
349
+ Trade.ticker == trade_info['ticker'],
350
+ Trade.disclosure_date == trade_info['disclosure_date']
351
+ ).first()
352
+
353
+ if not existing:
354
+ trade = Trade(**trade_info)
355
+ db.add(trade)
356
+ new_trades += 1
357
+
358
+ db.commit()
359
+ db.close()
360
+
361
+ logger.info(f"Added {new_trades} new politician trades")
362
+ return {"new_trades": new_trades, "total_processed": len(trades_data)}
363
+
364
+ except Exception as e:
365
+ logger.error(f"Failed to fetch politician trades: {e}")
366
+ raise
367
+
368
+
369
+ @celery_app.task(base=MLTask, bind=True)
370
+ def process_batch_predictions_task(self, predictions: list) -> Dict[str, Any]:
371
+ """Process batch predictions asynchronously"""
372
+ try:
373
+ logger.info(f"Processing batch of {len(predictions)} predictions")
374
+
375
+ from mcli.ml.models import get_model_by_id
376
+ import numpy as np
377
+
378
+ results = []
379
+ for pred in predictions:
380
+ model = asyncio.run(get_model_by_id(pred['model_id']))
381
+ features = np.array(pred['features']).reshape(1, -1)
382
+ result = model.predict(features)
383
+ results.append({
384
+ 'ticker': pred['ticker'],
385
+ 'prediction': float(result[0])
386
+ })
387
+
388
+ logger.info(f"Batch predictions completed")
389
+ return {"predictions": results}
390
+
391
+ except Exception as e:
392
+ logger.error(f"Batch prediction failed: {e}")
393
+ self.retry(countdown=30, exc=e)
394
+
395
+
396
+ # Worker health check
397
+ @celery_app.task(name='health_check')
398
+ def health_check():
399
+ """Health check for Celery worker"""
400
+ return {"status": "healthy", "timestamp": datetime.utcnow().isoformat()}