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.
- mcli/app/chat_cmd.py +42 -0
- mcli/app/commands_cmd.py +226 -0
- mcli/app/completion_cmd.py +216 -0
- mcli/app/completion_helpers.py +288 -0
- mcli/app/cron_test_cmd.py +697 -0
- mcli/app/logs_cmd.py +419 -0
- mcli/app/main.py +492 -0
- mcli/app/model/model.py +1060 -0
- mcli/app/model_cmd.py +227 -0
- mcli/app/redis_cmd.py +269 -0
- mcli/app/video/video.py +1114 -0
- mcli/app/visual_cmd.py +303 -0
- mcli/chat/chat.py +2409 -0
- mcli/chat/command_rag.py +514 -0
- mcli/chat/enhanced_chat.py +652 -0
- mcli/chat/system_controller.py +1010 -0
- mcli/chat/system_integration.py +1016 -0
- mcli/cli.py +25 -0
- mcli/config.toml +20 -0
- mcli/lib/api/api.py +586 -0
- mcli/lib/api/daemon_client.py +203 -0
- mcli/lib/api/daemon_client_local.py +44 -0
- mcli/lib/api/daemon_decorator.py +217 -0
- mcli/lib/api/mcli_decorators.py +1032 -0
- mcli/lib/auth/auth.py +85 -0
- mcli/lib/auth/aws_manager.py +85 -0
- mcli/lib/auth/azure_manager.py +91 -0
- mcli/lib/auth/credential_manager.py +192 -0
- mcli/lib/auth/gcp_manager.py +93 -0
- mcli/lib/auth/key_manager.py +117 -0
- mcli/lib/auth/mcli_manager.py +93 -0
- mcli/lib/auth/token_manager.py +75 -0
- mcli/lib/auth/token_util.py +1011 -0
- mcli/lib/config/config.py +47 -0
- mcli/lib/discovery/__init__.py +1 -0
- mcli/lib/discovery/command_discovery.py +274 -0
- mcli/lib/erd/erd.py +1345 -0
- mcli/lib/erd/generate_graph.py +453 -0
- mcli/lib/files/files.py +76 -0
- mcli/lib/fs/fs.py +109 -0
- mcli/lib/lib.py +29 -0
- mcli/lib/logger/logger.py +611 -0
- mcli/lib/performance/optimizer.py +409 -0
- mcli/lib/performance/rust_bridge.py +502 -0
- mcli/lib/performance/uvloop_config.py +154 -0
- mcli/lib/pickles/pickles.py +50 -0
- mcli/lib/search/cached_vectorizer.py +479 -0
- mcli/lib/services/data_pipeline.py +460 -0
- mcli/lib/services/lsh_client.py +441 -0
- mcli/lib/services/redis_service.py +387 -0
- mcli/lib/shell/shell.py +137 -0
- mcli/lib/toml/toml.py +33 -0
- mcli/lib/ui/styling.py +47 -0
- mcli/lib/ui/visual_effects.py +634 -0
- mcli/lib/watcher/watcher.py +185 -0
- mcli/ml/api/app.py +215 -0
- mcli/ml/api/middleware.py +224 -0
- mcli/ml/api/routers/admin_router.py +12 -0
- mcli/ml/api/routers/auth_router.py +244 -0
- mcli/ml/api/routers/backtest_router.py +12 -0
- mcli/ml/api/routers/data_router.py +12 -0
- mcli/ml/api/routers/model_router.py +302 -0
- mcli/ml/api/routers/monitoring_router.py +12 -0
- mcli/ml/api/routers/portfolio_router.py +12 -0
- mcli/ml/api/routers/prediction_router.py +267 -0
- mcli/ml/api/routers/trade_router.py +12 -0
- mcli/ml/api/routers/websocket_router.py +76 -0
- mcli/ml/api/schemas.py +64 -0
- mcli/ml/auth/auth_manager.py +425 -0
- mcli/ml/auth/models.py +154 -0
- mcli/ml/auth/permissions.py +302 -0
- mcli/ml/backtesting/backtest_engine.py +502 -0
- mcli/ml/backtesting/performance_metrics.py +393 -0
- mcli/ml/cache.py +400 -0
- mcli/ml/cli/main.py +398 -0
- mcli/ml/config/settings.py +394 -0
- mcli/ml/configs/dvc_config.py +230 -0
- mcli/ml/configs/mlflow_config.py +131 -0
- mcli/ml/configs/mlops_manager.py +293 -0
- mcli/ml/dashboard/app.py +532 -0
- mcli/ml/dashboard/app_integrated.py +738 -0
- mcli/ml/dashboard/app_supabase.py +560 -0
- mcli/ml/dashboard/app_training.py +615 -0
- mcli/ml/dashboard/cli.py +51 -0
- mcli/ml/data_ingestion/api_connectors.py +501 -0
- mcli/ml/data_ingestion/data_pipeline.py +567 -0
- mcli/ml/data_ingestion/stream_processor.py +512 -0
- mcli/ml/database/migrations/env.py +94 -0
- mcli/ml/database/models.py +667 -0
- mcli/ml/database/session.py +200 -0
- mcli/ml/experimentation/ab_testing.py +845 -0
- mcli/ml/features/ensemble_features.py +607 -0
- mcli/ml/features/political_features.py +676 -0
- mcli/ml/features/recommendation_engine.py +809 -0
- mcli/ml/features/stock_features.py +573 -0
- mcli/ml/features/test_feature_engineering.py +346 -0
- mcli/ml/logging.py +85 -0
- mcli/ml/mlops/data_versioning.py +518 -0
- mcli/ml/mlops/experiment_tracker.py +377 -0
- mcli/ml/mlops/model_serving.py +481 -0
- mcli/ml/mlops/pipeline_orchestrator.py +614 -0
- mcli/ml/models/base_models.py +324 -0
- mcli/ml/models/ensemble_models.py +675 -0
- mcli/ml/models/recommendation_models.py +474 -0
- mcli/ml/models/test_models.py +487 -0
- mcli/ml/monitoring/drift_detection.py +676 -0
- mcli/ml/monitoring/metrics.py +45 -0
- mcli/ml/optimization/portfolio_optimizer.py +834 -0
- mcli/ml/preprocessing/data_cleaners.py +451 -0
- mcli/ml/preprocessing/feature_extractors.py +491 -0
- mcli/ml/preprocessing/ml_pipeline.py +382 -0
- mcli/ml/preprocessing/politician_trading_preprocessor.py +569 -0
- mcli/ml/preprocessing/test_preprocessing.py +294 -0
- mcli/ml/scripts/populate_sample_data.py +200 -0
- mcli/ml/tasks.py +400 -0
- mcli/ml/tests/test_integration.py +429 -0
- mcli/ml/tests/test_training_dashboard.py +387 -0
- mcli/public/oi/oi.py +15 -0
- mcli/public/public.py +4 -0
- mcli/self/self_cmd.py +1246 -0
- mcli/workflow/daemon/api_daemon.py +800 -0
- mcli/workflow/daemon/async_command_database.py +681 -0
- mcli/workflow/daemon/async_process_manager.py +591 -0
- mcli/workflow/daemon/client.py +530 -0
- mcli/workflow/daemon/commands.py +1196 -0
- mcli/workflow/daemon/daemon.py +905 -0
- mcli/workflow/daemon/daemon_api.py +59 -0
- mcli/workflow/daemon/enhanced_daemon.py +571 -0
- mcli/workflow/daemon/process_cli.py +244 -0
- mcli/workflow/daemon/process_manager.py +439 -0
- mcli/workflow/daemon/test_daemon.py +275 -0
- mcli/workflow/dashboard/dashboard_cmd.py +113 -0
- mcli/workflow/docker/docker.py +0 -0
- mcli/workflow/file/file.py +100 -0
- mcli/workflow/gcloud/config.toml +21 -0
- mcli/workflow/gcloud/gcloud.py +58 -0
- mcli/workflow/git_commit/ai_service.py +328 -0
- mcli/workflow/git_commit/commands.py +430 -0
- mcli/workflow/lsh_integration.py +355 -0
- mcli/workflow/model_service/client.py +594 -0
- mcli/workflow/model_service/download_and_run_efficient_models.py +288 -0
- mcli/workflow/model_service/lightweight_embedder.py +397 -0
- mcli/workflow/model_service/lightweight_model_server.py +714 -0
- mcli/workflow/model_service/lightweight_test.py +241 -0
- mcli/workflow/model_service/model_service.py +1955 -0
- mcli/workflow/model_service/ollama_efficient_runner.py +425 -0
- mcli/workflow/model_service/pdf_processor.py +386 -0
- mcli/workflow/model_service/test_efficient_runner.py +234 -0
- mcli/workflow/model_service/test_example.py +315 -0
- mcli/workflow/model_service/test_integration.py +131 -0
- mcli/workflow/model_service/test_new_features.py +149 -0
- mcli/workflow/openai/openai.py +99 -0
- mcli/workflow/politician_trading/commands.py +1790 -0
- mcli/workflow/politician_trading/config.py +134 -0
- mcli/workflow/politician_trading/connectivity.py +490 -0
- mcli/workflow/politician_trading/data_sources.py +395 -0
- mcli/workflow/politician_trading/database.py +410 -0
- mcli/workflow/politician_trading/demo.py +248 -0
- mcli/workflow/politician_trading/models.py +165 -0
- mcli/workflow/politician_trading/monitoring.py +413 -0
- mcli/workflow/politician_trading/scrapers.py +966 -0
- mcli/workflow/politician_trading/scrapers_california.py +412 -0
- mcli/workflow/politician_trading/scrapers_eu.py +377 -0
- mcli/workflow/politician_trading/scrapers_uk.py +350 -0
- mcli/workflow/politician_trading/scrapers_us_states.py +438 -0
- mcli/workflow/politician_trading/supabase_functions.py +354 -0
- mcli/workflow/politician_trading/workflow.py +852 -0
- mcli/workflow/registry/registry.py +180 -0
- mcli/workflow/repo/repo.py +223 -0
- mcli/workflow/scheduler/commands.py +493 -0
- mcli/workflow/scheduler/cron_parser.py +238 -0
- mcli/workflow/scheduler/job.py +182 -0
- mcli/workflow/scheduler/monitor.py +139 -0
- mcli/workflow/scheduler/persistence.py +324 -0
- mcli/workflow/scheduler/scheduler.py +679 -0
- mcli/workflow/sync/sync_cmd.py +437 -0
- mcli/workflow/sync/test_cmd.py +314 -0
- mcli/workflow/videos/videos.py +242 -0
- mcli/workflow/wakatime/wakatime.py +11 -0
- mcli/workflow/workflow.py +37 -0
- mcli_framework-7.0.0.dist-info/METADATA +479 -0
- mcli_framework-7.0.0.dist-info/RECORD +186 -0
- mcli_framework-7.0.0.dist-info/WHEEL +5 -0
- mcli_framework-7.0.0.dist-info/entry_points.txt +7 -0
- mcli_framework-7.0.0.dist-info/licenses/LICENSE +21 -0
- 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()}
|