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
mcli/ml/database/models.py
CHANGED
|
@@ -1,46 +1,59 @@
|
|
|
1
1
|
"""Database models for ML system"""
|
|
2
2
|
|
|
3
3
|
from datetime import datetime
|
|
4
|
-
from typing import Optional, Dict, Any, List
|
|
5
4
|
from enum import Enum as PyEnum
|
|
5
|
+
from typing import Any, Dict, List, Optional
|
|
6
6
|
from uuid import uuid4
|
|
7
7
|
|
|
8
8
|
from sqlalchemy import (
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
JSON,
|
|
10
|
+
BigInteger,
|
|
11
|
+
Boolean,
|
|
12
|
+
CheckConstraint,
|
|
13
|
+
Column,
|
|
14
|
+
DateTime,
|
|
15
|
+
Enum,
|
|
16
|
+
Float,
|
|
17
|
+
ForeignKey,
|
|
18
|
+
Index,
|
|
19
|
+
Integer,
|
|
20
|
+
String,
|
|
21
|
+
Table,
|
|
22
|
+
Text,
|
|
23
|
+
UniqueConstraint,
|
|
24
|
+
event,
|
|
12
25
|
)
|
|
13
|
-
from sqlalchemy.
|
|
14
|
-
from sqlalchemy.dialects.postgresql import UUID, ARRAY
|
|
26
|
+
from sqlalchemy.dialects.postgresql import ARRAY, UUID
|
|
15
27
|
from sqlalchemy.ext.hybrid import hybrid_property
|
|
28
|
+
from sqlalchemy.orm import declarative_base, relationship, validates
|
|
16
29
|
from sqlalchemy.sql import func
|
|
17
30
|
|
|
18
|
-
|
|
19
31
|
Base = declarative_base()
|
|
20
32
|
|
|
21
33
|
|
|
22
34
|
# Association tables for many-to-many relationships
|
|
23
35
|
portfolio_stocks = Table(
|
|
24
|
-
|
|
36
|
+
"portfolio_stocks",
|
|
25
37
|
Base.metadata,
|
|
26
|
-
Column(
|
|
27
|
-
Column(
|
|
28
|
-
Column(
|
|
29
|
-
Column(
|
|
30
|
-
Column(
|
|
31
|
-
Column(
|
|
38
|
+
Column("portfolio_id", UUID(as_uuid=True), ForeignKey("portfolios.id")),
|
|
39
|
+
Column("stock_ticker", String, ForeignKey("stock_data.ticker")),
|
|
40
|
+
Column("weight", Float, nullable=False),
|
|
41
|
+
Column("shares", Integer, default=0),
|
|
42
|
+
Column("entry_price", Float),
|
|
43
|
+
Column("created_at", DateTime, default=datetime.utcnow),
|
|
32
44
|
)
|
|
33
45
|
|
|
34
46
|
experiment_models = Table(
|
|
35
|
-
|
|
47
|
+
"experiment_models",
|
|
36
48
|
Base.metadata,
|
|
37
|
-
Column(
|
|
38
|
-
Column(
|
|
49
|
+
Column("experiment_id", UUID(as_uuid=True), ForeignKey("experiments.id")),
|
|
50
|
+
Column("model_id", UUID(as_uuid=True), ForeignKey("models.id")),
|
|
39
51
|
)
|
|
40
52
|
|
|
41
53
|
|
|
42
54
|
class UserRole(PyEnum):
|
|
43
55
|
"""User role enumeration"""
|
|
56
|
+
|
|
44
57
|
ADMIN = "admin"
|
|
45
58
|
USER = "user"
|
|
46
59
|
ANALYST = "analyst"
|
|
@@ -49,6 +62,7 @@ class UserRole(PyEnum):
|
|
|
49
62
|
|
|
50
63
|
class ModelStatus(PyEnum):
|
|
51
64
|
"""Model status enumeration"""
|
|
65
|
+
|
|
52
66
|
TRAINING = "training"
|
|
53
67
|
TRAINED = "trained"
|
|
54
68
|
DEPLOYED = "deployed"
|
|
@@ -58,6 +72,7 @@ class ModelStatus(PyEnum):
|
|
|
58
72
|
|
|
59
73
|
class TradeType(PyEnum):
|
|
60
74
|
"""Trade type enumeration"""
|
|
75
|
+
|
|
61
76
|
BUY = "buy"
|
|
62
77
|
SELL = "sell"
|
|
63
78
|
OPTION = "option"
|
|
@@ -65,6 +80,7 @@ class TradeType(PyEnum):
|
|
|
65
80
|
|
|
66
81
|
class AlertType(PyEnum):
|
|
67
82
|
"""Alert type enumeration"""
|
|
83
|
+
|
|
68
84
|
POLITICIAN_TRADE = "politician_trade"
|
|
69
85
|
PRICE_MOVEMENT = "price_movement"
|
|
70
86
|
VOLUME_SPIKE = "volume_spike"
|
|
@@ -74,7 +90,8 @@ class AlertType(PyEnum):
|
|
|
74
90
|
|
|
75
91
|
class User(Base):
|
|
76
92
|
"""User model for authentication and authorization"""
|
|
77
|
-
|
|
93
|
+
|
|
94
|
+
__tablename__ = "users"
|
|
78
95
|
|
|
79
96
|
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid4)
|
|
80
97
|
username = Column(String(50), unique=True, nullable=False, index=True)
|
|
@@ -102,22 +119,26 @@ class User(Base):
|
|
|
102
119
|
|
|
103
120
|
# Constraints
|
|
104
121
|
__table_args__ = (
|
|
105
|
-
CheckConstraint(
|
|
106
|
-
|
|
122
|
+
CheckConstraint(
|
|
123
|
+
"email ~* '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Z|a-z]{2,}$'", name="valid_email"
|
|
124
|
+
),
|
|
125
|
+
Index("idx_user_active", "is_active"),
|
|
107
126
|
)
|
|
108
127
|
|
|
109
|
-
@validates(
|
|
128
|
+
@validates("email")
|
|
110
129
|
def validate_email(self, key, email):
|
|
111
130
|
"""Validate email format"""
|
|
112
131
|
import re
|
|
113
|
-
|
|
132
|
+
|
|
133
|
+
if not re.match(r"^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}$", email):
|
|
114
134
|
raise ValueError("Invalid email format")
|
|
115
135
|
return email.lower()
|
|
116
136
|
|
|
117
137
|
|
|
118
138
|
class Politician(Base):
|
|
119
139
|
"""Politician information"""
|
|
120
|
-
|
|
140
|
+
|
|
141
|
+
__tablename__ = "politicians"
|
|
121
142
|
|
|
122
143
|
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid4)
|
|
123
144
|
name = Column(String(100), nullable=False)
|
|
@@ -142,17 +163,18 @@ class Politician(Base):
|
|
|
142
163
|
|
|
143
164
|
# Indexes
|
|
144
165
|
__table_args__ = (
|
|
145
|
-
Index(
|
|
146
|
-
Index(
|
|
166
|
+
Index("idx_politician_active", "active"),
|
|
167
|
+
Index("idx_politician_party_state", "party", "state"),
|
|
147
168
|
)
|
|
148
169
|
|
|
149
170
|
|
|
150
171
|
class Trade(Base):
|
|
151
172
|
"""Political trading records"""
|
|
152
|
-
|
|
173
|
+
|
|
174
|
+
__tablename__ = "trades"
|
|
153
175
|
|
|
154
176
|
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid4)
|
|
155
|
-
politician_id = Column(UUID(as_uuid=True), ForeignKey(
|
|
177
|
+
politician_id = Column(UUID(as_uuid=True), ForeignKey("politicians.id"), nullable=False)
|
|
156
178
|
|
|
157
179
|
ticker = Column(String(10), nullable=False, index=True)
|
|
158
180
|
trade_type = Column(Enum(TradeType), nullable=False)
|
|
@@ -175,10 +197,11 @@ class Trade(Base):
|
|
|
175
197
|
|
|
176
198
|
# Indexes and constraints
|
|
177
199
|
__table_args__ = (
|
|
178
|
-
Index(
|
|
179
|
-
Index(
|
|
180
|
-
UniqueConstraint(
|
|
181
|
-
|
|
200
|
+
Index("idx_trade_ticker_date", "ticker", "disclosure_date"),
|
|
201
|
+
Index("idx_trade_politician_date", "politician_id", "disclosure_date"),
|
|
202
|
+
UniqueConstraint(
|
|
203
|
+
"politician_id", "ticker", "disclosure_date", "trade_type", name="unique_trade"
|
|
204
|
+
),
|
|
182
205
|
)
|
|
183
206
|
|
|
184
207
|
@hybrid_property
|
|
@@ -191,7 +214,8 @@ class Trade(Base):
|
|
|
191
214
|
|
|
192
215
|
class StockData(Base):
|
|
193
216
|
"""Stock market data"""
|
|
194
|
-
|
|
217
|
+
|
|
218
|
+
__tablename__ = "stock_data"
|
|
195
219
|
|
|
196
220
|
ticker = Column(String(10), primary_key=True)
|
|
197
221
|
company_name = Column(String(200))
|
|
@@ -234,20 +258,21 @@ class StockData(Base):
|
|
|
234
258
|
|
|
235
259
|
# Indexes
|
|
236
260
|
__table_args__ = (
|
|
237
|
-
Index(
|
|
238
|
-
Index(
|
|
261
|
+
Index("idx_stock_sector_cap", "sector", "market_cap"),
|
|
262
|
+
Index("idx_stock_updated", "last_updated"),
|
|
239
263
|
)
|
|
240
264
|
|
|
241
265
|
|
|
242
266
|
class Prediction(Base):
|
|
243
267
|
"""Model predictions"""
|
|
244
|
-
|
|
268
|
+
|
|
269
|
+
__tablename__ = "predictions"
|
|
245
270
|
|
|
246
271
|
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid4)
|
|
247
|
-
model_id = Column(UUID(as_uuid=True), ForeignKey(
|
|
248
|
-
user_id = Column(UUID(as_uuid=True), ForeignKey(
|
|
272
|
+
model_id = Column(UUID(as_uuid=True), ForeignKey("models.id"), nullable=False)
|
|
273
|
+
user_id = Column(UUID(as_uuid=True), ForeignKey("users.id"))
|
|
249
274
|
|
|
250
|
-
ticker = Column(String(10), ForeignKey(
|
|
275
|
+
ticker = Column(String(10), ForeignKey("stock_data.ticker"), nullable=False, index=True)
|
|
251
276
|
|
|
252
277
|
# Prediction details
|
|
253
278
|
prediction_date = Column(DateTime, default=datetime.utcnow, nullable=False, index=True)
|
|
@@ -281,18 +306,19 @@ class Prediction(Base):
|
|
|
281
306
|
|
|
282
307
|
# Indexes
|
|
283
308
|
__table_args__ = (
|
|
284
|
-
Index(
|
|
285
|
-
Index(
|
|
286
|
-
Index(
|
|
309
|
+
Index("idx_prediction_date_ticker", "prediction_date", "ticker"),
|
|
310
|
+
Index("idx_prediction_model_date", "model_id", "prediction_date"),
|
|
311
|
+
Index("idx_prediction_confidence", "confidence_score"),
|
|
287
312
|
)
|
|
288
313
|
|
|
289
314
|
|
|
290
315
|
class Portfolio(Base):
|
|
291
316
|
"""User portfolios"""
|
|
292
|
-
|
|
317
|
+
|
|
318
|
+
__tablename__ = "portfolios"
|
|
293
319
|
|
|
294
320
|
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid4)
|
|
295
|
-
user_id = Column(UUID(as_uuid=True), ForeignKey(
|
|
321
|
+
user_id = Column(UUID(as_uuid=True), ForeignKey("users.id"), nullable=False)
|
|
296
322
|
|
|
297
323
|
name = Column(String(100), nullable=False)
|
|
298
324
|
description = Column(Text)
|
|
@@ -326,28 +352,29 @@ class Portfolio(Base):
|
|
|
326
352
|
|
|
327
353
|
# Constraints
|
|
328
354
|
__table_args__ = (
|
|
329
|
-
UniqueConstraint(
|
|
330
|
-
Index(
|
|
355
|
+
UniqueConstraint("user_id", "name", name="unique_user_portfolio"),
|
|
356
|
+
Index("idx_portfolio_active", "is_active"),
|
|
331
357
|
)
|
|
332
358
|
|
|
333
359
|
|
|
334
360
|
class Alert(Base):
|
|
335
361
|
"""User alerts and notifications"""
|
|
336
|
-
|
|
362
|
+
|
|
363
|
+
__tablename__ = "alerts"
|
|
337
364
|
|
|
338
365
|
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid4)
|
|
339
|
-
user_id = Column(UUID(as_uuid=True), ForeignKey(
|
|
366
|
+
user_id = Column(UUID(as_uuid=True), ForeignKey("users.id"), nullable=False)
|
|
340
367
|
|
|
341
368
|
alert_type = Column(Enum(AlertType), nullable=False, index=True)
|
|
342
|
-
severity = Column(String(10), default=
|
|
369
|
+
severity = Column(String(10), default="info") # info, warning, critical
|
|
343
370
|
|
|
344
371
|
title = Column(String(200), nullable=False)
|
|
345
372
|
message = Column(Text, nullable=False)
|
|
346
373
|
|
|
347
374
|
# Related entities
|
|
348
375
|
ticker = Column(String(10), index=True)
|
|
349
|
-
politician_id = Column(UUID(as_uuid=True), ForeignKey(
|
|
350
|
-
portfolio_id = Column(UUID(as_uuid=True), ForeignKey(
|
|
376
|
+
politician_id = Column(UUID(as_uuid=True), ForeignKey("politicians.id"))
|
|
377
|
+
portfolio_id = Column(UUID(as_uuid=True), ForeignKey("portfolios.id"))
|
|
351
378
|
|
|
352
379
|
is_read = Column(Boolean, default=False, index=True)
|
|
353
380
|
is_sent = Column(Boolean, default=False)
|
|
@@ -363,18 +390,19 @@ class Alert(Base):
|
|
|
363
390
|
|
|
364
391
|
# Indexes
|
|
365
392
|
__table_args__ = (
|
|
366
|
-
Index(
|
|
367
|
-
Index(
|
|
393
|
+
Index("idx_alert_user_unread", "user_id", "is_read"),
|
|
394
|
+
Index("idx_alert_created", "created_at"),
|
|
368
395
|
)
|
|
369
396
|
|
|
370
397
|
|
|
371
398
|
class BacktestResult(Base):
|
|
372
399
|
"""Backtesting results"""
|
|
373
|
-
|
|
400
|
+
|
|
401
|
+
__tablename__ = "backtest_results"
|
|
374
402
|
|
|
375
403
|
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid4)
|
|
376
|
-
model_id = Column(UUID(as_uuid=True), ForeignKey(
|
|
377
|
-
portfolio_id = Column(UUID(as_uuid=True), ForeignKey(
|
|
404
|
+
model_id = Column(UUID(as_uuid=True), ForeignKey("models.id"), nullable=False)
|
|
405
|
+
portfolio_id = Column(UUID(as_uuid=True), ForeignKey("portfolios.id"))
|
|
378
406
|
|
|
379
407
|
start_date = Column(DateTime, nullable=False)
|
|
380
408
|
end_date = Column(DateTime, nullable=False)
|
|
@@ -414,14 +442,15 @@ class BacktestResult(Base):
|
|
|
414
442
|
|
|
415
443
|
# Indexes
|
|
416
444
|
__table_args__ = (
|
|
417
|
-
Index(
|
|
418
|
-
Index(
|
|
445
|
+
Index("idx_backtest_model", "model_id"),
|
|
446
|
+
Index("idx_backtest_sharpe", "sharpe_ratio"),
|
|
419
447
|
)
|
|
420
448
|
|
|
421
449
|
|
|
422
450
|
class Experiment(Base):
|
|
423
451
|
"""ML experiments tracking"""
|
|
424
|
-
|
|
452
|
+
|
|
453
|
+
__tablename__ = "experiments"
|
|
425
454
|
|
|
426
455
|
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid4)
|
|
427
456
|
name = Column(String(100), nullable=False, index=True)
|
|
@@ -440,7 +469,7 @@ class Experiment(Base):
|
|
|
440
469
|
test_metrics = Column(JSON)
|
|
441
470
|
|
|
442
471
|
# Status
|
|
443
|
-
status = Column(String(20), default=
|
|
472
|
+
status = Column(String(20), default="running") # running, completed, failed
|
|
444
473
|
|
|
445
474
|
# Timing
|
|
446
475
|
started_at = Column(DateTime, default=datetime.utcnow)
|
|
@@ -461,21 +490,22 @@ class Experiment(Base):
|
|
|
461
490
|
|
|
462
491
|
# Indexes
|
|
463
492
|
__table_args__ = (
|
|
464
|
-
Index(
|
|
465
|
-
Index(
|
|
493
|
+
Index("idx_experiment_status", "status"),
|
|
494
|
+
Index("idx_experiment_created", "created_at"),
|
|
466
495
|
)
|
|
467
496
|
|
|
468
497
|
|
|
469
498
|
class Model(Base):
|
|
470
499
|
"""ML models registry"""
|
|
471
|
-
|
|
500
|
+
|
|
501
|
+
__tablename__ = "models"
|
|
472
502
|
|
|
473
503
|
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid4)
|
|
474
504
|
name = Column(String(100), nullable=False, index=True)
|
|
475
505
|
version = Column(String(20), nullable=False)
|
|
476
506
|
|
|
477
507
|
model_type = Column(String(50), nullable=False) # ensemble, lstm, transformer, etc.
|
|
478
|
-
framework = Column(String(20), default=
|
|
508
|
+
framework = Column(String(20), default="pytorch") # pytorch, tensorflow, sklearn
|
|
479
509
|
|
|
480
510
|
# MLflow integration
|
|
481
511
|
mlflow_model_uri = Column(String(500))
|
|
@@ -541,18 +571,19 @@ class Model(Base):
|
|
|
541
571
|
|
|
542
572
|
# Constraints and indexes
|
|
543
573
|
__table_args__ = (
|
|
544
|
-
UniqueConstraint(
|
|
545
|
-
Index(
|
|
546
|
-
Index(
|
|
574
|
+
UniqueConstraint("name", "version", name="unique_model_version"),
|
|
575
|
+
Index("idx_model_status", "status"),
|
|
576
|
+
Index("idx_model_created", "created_at"),
|
|
547
577
|
)
|
|
548
578
|
|
|
549
579
|
|
|
550
580
|
class FeatureSet(Base):
|
|
551
581
|
"""Feature sets for models"""
|
|
552
|
-
|
|
582
|
+
|
|
583
|
+
__tablename__ = "feature_sets"
|
|
553
584
|
|
|
554
585
|
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid4)
|
|
555
|
-
model_id = Column(UUID(as_uuid=True), ForeignKey(
|
|
586
|
+
model_id = Column(UUID(as_uuid=True), ForeignKey("models.id"), nullable=False)
|
|
556
587
|
|
|
557
588
|
name = Column(String(100), nullable=False)
|
|
558
589
|
version = Column(String(20), nullable=False)
|
|
@@ -580,14 +611,15 @@ class FeatureSet(Base):
|
|
|
580
611
|
|
|
581
612
|
# Constraints
|
|
582
613
|
__table_args__ = (
|
|
583
|
-
UniqueConstraint(
|
|
584
|
-
Index(
|
|
614
|
+
UniqueConstraint("model_id", "name", "version", name="unique_feature_set"),
|
|
615
|
+
Index("idx_feature_set_model", "model_id"),
|
|
585
616
|
)
|
|
586
617
|
|
|
587
618
|
|
|
588
619
|
class DataVersion(Base):
|
|
589
620
|
"""Data versioning for DVC"""
|
|
590
|
-
|
|
621
|
+
|
|
622
|
+
__tablename__ = "data_versions"
|
|
591
623
|
|
|
592
624
|
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid4)
|
|
593
625
|
|
|
@@ -618,27 +650,29 @@ class DataVersion(Base):
|
|
|
618
650
|
|
|
619
651
|
# Constraints
|
|
620
652
|
__table_args__ = (
|
|
621
|
-
UniqueConstraint(
|
|
622
|
-
Index(
|
|
653
|
+
UniqueConstraint("dataset_name", "version", name="unique_data_version"),
|
|
654
|
+
Index("idx_data_version_created", "created_at"),
|
|
623
655
|
)
|
|
624
656
|
|
|
625
657
|
|
|
626
658
|
# Database events and triggers
|
|
627
|
-
@event.listens_for(User,
|
|
659
|
+
@event.listens_for(User, "before_insert")
|
|
628
660
|
def generate_api_key(mapper, connection, target):
|
|
629
661
|
"""Generate API key for new users"""
|
|
630
662
|
if not target.api_key:
|
|
631
663
|
import secrets
|
|
664
|
+
|
|
632
665
|
target.api_key = secrets.token_urlsafe(32)
|
|
633
666
|
target.api_key_created_at = datetime.utcnow()
|
|
634
667
|
|
|
635
668
|
|
|
636
|
-
@event.listens_for(Portfolio,
|
|
669
|
+
@event.listens_for(Portfolio, "before_update")
|
|
637
670
|
def update_portfolio_metrics(mapper, connection, target):
|
|
638
671
|
"""Update portfolio performance metrics"""
|
|
639
672
|
if target.current_value and target.initial_capital:
|
|
640
|
-
target.total_return = (
|
|
641
|
-
|
|
673
|
+
target.total_return = (
|
|
674
|
+
(target.current_value - target.initial_capital) / target.initial_capital
|
|
675
|
+
) * 100
|
|
642
676
|
|
|
643
677
|
|
|
644
678
|
# Create indexes for better query performance
|
|
@@ -652,7 +686,6 @@ def create_indexes(engine):
|
|
|
652
686
|
"CREATE INDEX IF NOT EXISTS idx_prediction_latest ON predictions(ticker, prediction_date DESC)",
|
|
653
687
|
"CREATE INDEX IF NOT EXISTS idx_portfolio_performance ON portfolios(user_id, total_return DESC)",
|
|
654
688
|
"CREATE INDEX IF NOT EXISTS idx_alert_priority ON alerts(user_id, is_read, severity, created_at DESC)",
|
|
655
|
-
|
|
656
689
|
# Full text search indexes (PostgreSQL specific)
|
|
657
690
|
"CREATE INDEX IF NOT EXISTS idx_politician_name_search ON politicians USING gin(to_tsvector('english', name))",
|
|
658
691
|
"CREATE INDEX IF NOT EXISTS idx_stock_company_search ON stock_data USING gin(to_tsvector('english', company_name))",
|
|
@@ -664,4 +697,4 @@ def create_indexes(engine):
|
|
|
664
697
|
conn.execute(text(index_sql))
|
|
665
698
|
conn.commit()
|
|
666
699
|
except Exception as e:
|
|
667
|
-
print(f"Warning: Could not create index: {e}")
|
|
700
|
+
print(f"Warning: Could not create index: {e}")
|
mcli/ml/database/session.py
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
"""Database session management"""
|
|
2
2
|
|
|
3
|
+
from contextlib import asynccontextmanager, contextmanager
|
|
3
4
|
from typing import AsyncGenerator, Generator
|
|
4
|
-
from contextlib import contextmanager, asynccontextmanager
|
|
5
5
|
|
|
6
6
|
from sqlalchemy import create_engine
|
|
7
|
-
from sqlalchemy.ext.asyncio import AsyncSession,
|
|
8
|
-
from sqlalchemy.orm import
|
|
7
|
+
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
|
|
8
|
+
from sqlalchemy.orm import Session, sessionmaker
|
|
9
9
|
from sqlalchemy.pool import NullPool, StaticPool
|
|
10
10
|
|
|
11
11
|
from mcli.ml.config import settings
|
|
12
|
-
from .models import Base
|
|
13
12
|
|
|
13
|
+
from .models import Base
|
|
14
14
|
|
|
15
15
|
# Synchronous database setup
|
|
16
16
|
engine = create_engine(
|
|
@@ -125,6 +125,7 @@ def init_db() -> None:
|
|
|
125
125
|
|
|
126
126
|
# Create additional indexes
|
|
127
127
|
from .models import create_indexes
|
|
128
|
+
|
|
128
129
|
create_indexes(engine)
|
|
129
130
|
|
|
130
131
|
|
|
@@ -197,4 +198,4 @@ async def bulk_update(model_class, data: list, key_field: str = "id") -> None:
|
|
|
197
198
|
.where(getattr(model_class, key_field) == key_value)
|
|
198
199
|
.values(**item)
|
|
199
200
|
)
|
|
200
|
-
await session.commit()
|
|
201
|
+
await session.commit()
|