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
|
@@ -0,0 +1,738 @@
|
|
|
1
|
+
"""Integrated Streamlit dashboard for ML system with LSH daemon integration"""
|
|
2
|
+
|
|
3
|
+
import streamlit as st
|
|
4
|
+
import pandas as pd
|
|
5
|
+
import plotly.express as px
|
|
6
|
+
import plotly.graph_objects as go
|
|
7
|
+
from plotly.subplots import make_subplots
|
|
8
|
+
import asyncio
|
|
9
|
+
from datetime import datetime, timedelta
|
|
10
|
+
import numpy as np
|
|
11
|
+
from supabase import create_client, Client
|
|
12
|
+
import os
|
|
13
|
+
import requests
|
|
14
|
+
import json
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
import subprocess
|
|
17
|
+
import pickle
|
|
18
|
+
|
|
19
|
+
# Add ML pipeline imports
|
|
20
|
+
from mcli.ml.preprocessing.data_preprocessor import DataPreprocessor
|
|
21
|
+
from mcli.ml.features.feature_engineering import FeatureEngineering
|
|
22
|
+
from mcli.ml.models import get_model_by_id
|
|
23
|
+
|
|
24
|
+
# Page config
|
|
25
|
+
st.set_page_config(
|
|
26
|
+
page_title="MCLI ML Dashboard - Integrated",
|
|
27
|
+
page_icon="📊",
|
|
28
|
+
layout="wide",
|
|
29
|
+
initial_sidebar_state="expanded"
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
# Custom CSS
|
|
33
|
+
st.markdown("""
|
|
34
|
+
<style>
|
|
35
|
+
.metric-card {
|
|
36
|
+
background-color: #f0f2f6;
|
|
37
|
+
padding: 1rem;
|
|
38
|
+
border-radius: 0.5rem;
|
|
39
|
+
border-left: 4px solid #1f77b4;
|
|
40
|
+
}
|
|
41
|
+
.success-box {
|
|
42
|
+
background-color: #d4edda;
|
|
43
|
+
border: 1px solid #c3e6cb;
|
|
44
|
+
color: #155724;
|
|
45
|
+
padding: 0.75rem;
|
|
46
|
+
border-radius: 0.25rem;
|
|
47
|
+
}
|
|
48
|
+
.warning-box {
|
|
49
|
+
background-color: #fff3cd;
|
|
50
|
+
border: 1px solid #ffeaa7;
|
|
51
|
+
color: #856404;
|
|
52
|
+
padding: 0.75rem;
|
|
53
|
+
border-radius: 0.25rem;
|
|
54
|
+
}
|
|
55
|
+
</style>
|
|
56
|
+
""", unsafe_allow_html=True)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@st.cache_resource
|
|
60
|
+
def get_supabase_client() -> Client:
|
|
61
|
+
"""Get Supabase client"""
|
|
62
|
+
url = os.getenv("SUPABASE_URL", "")
|
|
63
|
+
key = os.getenv("SUPABASE_KEY", "")
|
|
64
|
+
|
|
65
|
+
if not url or not key:
|
|
66
|
+
st.warning("⚠️ Supabase credentials not found. Set SUPABASE_URL and SUPABASE_KEY environment variables.")
|
|
67
|
+
return None
|
|
68
|
+
|
|
69
|
+
return create_client(url, key)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
@st.cache_resource
|
|
73
|
+
def get_preprocessor():
|
|
74
|
+
"""Get data preprocessor instance"""
|
|
75
|
+
return DataPreprocessor()
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
@st.cache_resource
|
|
79
|
+
def get_feature_engineer():
|
|
80
|
+
"""Get feature engineering instance"""
|
|
81
|
+
return FeatureEngineering()
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def check_lsh_daemon():
|
|
85
|
+
"""Check if LSH daemon is running"""
|
|
86
|
+
try:
|
|
87
|
+
# Check if LSH API is available
|
|
88
|
+
lsh_api_url = os.getenv("LSH_API_URL", "http://localhost:3030")
|
|
89
|
+
response = requests.get(f"{lsh_api_url}/health", timeout=2)
|
|
90
|
+
return response.status_code == 200
|
|
91
|
+
except:
|
|
92
|
+
return False
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
@st.cache_data(ttl=30)
|
|
96
|
+
def get_lsh_jobs():
|
|
97
|
+
"""Get LSH daemon job status"""
|
|
98
|
+
try:
|
|
99
|
+
# Read from LSH log file
|
|
100
|
+
log_path = Path("/tmp/lsh-job-daemon-lefv.log")
|
|
101
|
+
if log_path.exists():
|
|
102
|
+
with open(log_path, 'r') as f:
|
|
103
|
+
lines = f.readlines()[-100:] # Last 100 lines
|
|
104
|
+
|
|
105
|
+
jobs = []
|
|
106
|
+
for line in lines:
|
|
107
|
+
if "Started scheduled" in line or "Completed job" in line:
|
|
108
|
+
# Parse job info from log
|
|
109
|
+
parts = line.strip().split("|")
|
|
110
|
+
if len(parts) >= 3:
|
|
111
|
+
jobs.append({
|
|
112
|
+
'timestamp': parts[0].strip(),
|
|
113
|
+
'status': 'completed' if 'Completed' in line else 'running',
|
|
114
|
+
'job_name': parts[2].strip() if len(parts) > 2 else 'Unknown'
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
return pd.DataFrame(jobs)
|
|
118
|
+
except:
|
|
119
|
+
return pd.DataFrame()
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
@st.cache_data(ttl=60)
|
|
123
|
+
def run_ml_pipeline(df_disclosures):
|
|
124
|
+
"""Run the full ML pipeline on disclosure data"""
|
|
125
|
+
if df_disclosures.empty:
|
|
126
|
+
return None, None, None
|
|
127
|
+
|
|
128
|
+
try:
|
|
129
|
+
# 1. Preprocess data
|
|
130
|
+
preprocessor = get_preprocessor()
|
|
131
|
+
processed_data = preprocessor.preprocess(df_disclosures)
|
|
132
|
+
|
|
133
|
+
# 2. Feature engineering
|
|
134
|
+
feature_engineer = get_feature_engineer()
|
|
135
|
+
features = feature_engineer.create_features(processed_data)
|
|
136
|
+
|
|
137
|
+
# 3. Generate predictions (mock for now, replace with actual model)
|
|
138
|
+
predictions = pd.DataFrame({
|
|
139
|
+
'ticker': processed_data['ticker_symbol'].unique()[:10] if 'ticker_symbol' in processed_data else [],
|
|
140
|
+
'predicted_return': np.random.uniform(-0.05, 0.05, min(10, len(processed_data['ticker_symbol'].unique())) if 'ticker_symbol' in processed_data else 0),
|
|
141
|
+
'confidence': np.random.uniform(0.6, 0.95, min(10, len(processed_data['ticker_symbol'].unique())) if 'ticker_symbol' in processed_data else 0),
|
|
142
|
+
'risk_score': np.random.uniform(0.1, 0.9, min(10, len(processed_data['ticker_symbol'].unique())) if 'ticker_symbol' in processed_data else 0),
|
|
143
|
+
'recommendation': np.random.choice(['BUY', 'HOLD', 'SELL'], min(10, len(processed_data['ticker_symbol'].unique())) if 'ticker_symbol' in processed_data else 0)
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
return processed_data, features, predictions
|
|
147
|
+
except Exception as e:
|
|
148
|
+
st.error(f"Pipeline error: {e}")
|
|
149
|
+
return None, None, None
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
@st.cache_data(ttl=30)
|
|
153
|
+
def get_politicians_data():
|
|
154
|
+
"""Get politicians data from Supabase"""
|
|
155
|
+
client = get_supabase_client()
|
|
156
|
+
if not client:
|
|
157
|
+
return pd.DataFrame()
|
|
158
|
+
|
|
159
|
+
try:
|
|
160
|
+
response = client.table("politicians").select("*").execute()
|
|
161
|
+
return pd.DataFrame(response.data)
|
|
162
|
+
except Exception as e:
|
|
163
|
+
st.error(f"Error fetching politicians: {e}")
|
|
164
|
+
return pd.DataFrame()
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
@st.cache_data(ttl=30)
|
|
168
|
+
def get_disclosures_data():
|
|
169
|
+
"""Get trading disclosures from Supabase"""
|
|
170
|
+
client = get_supabase_client()
|
|
171
|
+
if not client:
|
|
172
|
+
return pd.DataFrame()
|
|
173
|
+
|
|
174
|
+
try:
|
|
175
|
+
response = client.table("trading_disclosures").select("*").order("disclosure_date", desc=True).limit(1000).execute()
|
|
176
|
+
return pd.DataFrame(response.data)
|
|
177
|
+
except Exception as e:
|
|
178
|
+
st.error(f"Error fetching disclosures: {e}")
|
|
179
|
+
return pd.DataFrame()
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
@st.cache_data(ttl=30)
|
|
183
|
+
def get_model_metrics():
|
|
184
|
+
"""Get model performance metrics"""
|
|
185
|
+
# Check if we have saved models
|
|
186
|
+
model_dir = Path("models")
|
|
187
|
+
if not model_dir.exists():
|
|
188
|
+
return pd.DataFrame()
|
|
189
|
+
|
|
190
|
+
metrics = []
|
|
191
|
+
for model_file in model_dir.glob("*.pt"):
|
|
192
|
+
try:
|
|
193
|
+
# Load model metadata
|
|
194
|
+
metadata_file = model_file.with_suffix('.json')
|
|
195
|
+
if metadata_file.exists():
|
|
196
|
+
with open(metadata_file, 'r') as f:
|
|
197
|
+
metadata = json.load(f)
|
|
198
|
+
metrics.append({
|
|
199
|
+
'model_name': model_file.stem,
|
|
200
|
+
'accuracy': metadata.get('accuracy', 0),
|
|
201
|
+
'sharpe_ratio': metadata.get('sharpe_ratio', 0),
|
|
202
|
+
'created_at': metadata.get('created_at', ''),
|
|
203
|
+
'status': 'deployed'
|
|
204
|
+
})
|
|
205
|
+
except:
|
|
206
|
+
continue
|
|
207
|
+
|
|
208
|
+
return pd.DataFrame(metrics)
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def main():
|
|
212
|
+
"""Main dashboard function"""
|
|
213
|
+
|
|
214
|
+
# Title and header
|
|
215
|
+
st.title("🤖 MCLI ML System Dashboard - Integrated")
|
|
216
|
+
st.markdown("Real-time ML pipeline monitoring with LSH daemon integration")
|
|
217
|
+
|
|
218
|
+
# Sidebar
|
|
219
|
+
st.sidebar.title("Navigation")
|
|
220
|
+
page = st.sidebar.selectbox(
|
|
221
|
+
"Choose a page",
|
|
222
|
+
["Pipeline Overview", "ML Processing", "Model Performance", "Predictions", "LSH Jobs", "System Health"]
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
# Auto-refresh toggle
|
|
226
|
+
auto_refresh = st.sidebar.checkbox("Auto-refresh (30s)", value=True)
|
|
227
|
+
if auto_refresh:
|
|
228
|
+
import time
|
|
229
|
+
time.sleep(30)
|
|
230
|
+
st.rerun()
|
|
231
|
+
|
|
232
|
+
# Manual refresh button
|
|
233
|
+
if st.sidebar.button("🔄 Refresh Now"):
|
|
234
|
+
st.cache_data.clear()
|
|
235
|
+
st.rerun()
|
|
236
|
+
|
|
237
|
+
# Run ML Pipeline button
|
|
238
|
+
if st.sidebar.button("🚀 Run ML Pipeline"):
|
|
239
|
+
with st.spinner("Running ML pipeline..."):
|
|
240
|
+
disclosures = get_disclosures_data()
|
|
241
|
+
processed, features, predictions = run_ml_pipeline(disclosures)
|
|
242
|
+
if predictions is not None:
|
|
243
|
+
st.sidebar.success("✅ Pipeline completed!")
|
|
244
|
+
else:
|
|
245
|
+
st.sidebar.error("❌ Pipeline failed")
|
|
246
|
+
|
|
247
|
+
# Main content
|
|
248
|
+
if page == "Pipeline Overview":
|
|
249
|
+
show_pipeline_overview()
|
|
250
|
+
elif page == "ML Processing":
|
|
251
|
+
show_ml_processing()
|
|
252
|
+
elif page == "Model Performance":
|
|
253
|
+
show_model_performance()
|
|
254
|
+
elif page == "Predictions":
|
|
255
|
+
show_predictions()
|
|
256
|
+
elif page == "LSH Jobs":
|
|
257
|
+
show_lsh_jobs()
|
|
258
|
+
elif page == "System Health":
|
|
259
|
+
show_system_health()
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
def show_pipeline_overview():
|
|
263
|
+
"""Show ML pipeline overview"""
|
|
264
|
+
st.header("ML Pipeline Overview")
|
|
265
|
+
|
|
266
|
+
# Get data
|
|
267
|
+
politicians = get_politicians_data()
|
|
268
|
+
disclosures = get_disclosures_data()
|
|
269
|
+
lsh_jobs = get_lsh_jobs()
|
|
270
|
+
|
|
271
|
+
# Pipeline status
|
|
272
|
+
col1, col2, col3, col4 = st.columns(4)
|
|
273
|
+
|
|
274
|
+
with col1:
|
|
275
|
+
st.metric(
|
|
276
|
+
label="Data Sources",
|
|
277
|
+
value=len(politicians),
|
|
278
|
+
delta=f"{len(disclosures)} disclosures"
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
with col2:
|
|
282
|
+
# Run preprocessing to get feature count
|
|
283
|
+
if not disclosures.empty:
|
|
284
|
+
preprocessor = get_preprocessor()
|
|
285
|
+
try:
|
|
286
|
+
processed = preprocessor.preprocess(disclosures.head(100))
|
|
287
|
+
feature_count = len(processed.columns)
|
|
288
|
+
except:
|
|
289
|
+
feature_count = 0
|
|
290
|
+
else:
|
|
291
|
+
feature_count = 0
|
|
292
|
+
|
|
293
|
+
st.metric(
|
|
294
|
+
label="Features Extracted",
|
|
295
|
+
value=feature_count,
|
|
296
|
+
delta="After preprocessing"
|
|
297
|
+
)
|
|
298
|
+
|
|
299
|
+
with col3:
|
|
300
|
+
model_metrics = get_model_metrics()
|
|
301
|
+
st.metric(
|
|
302
|
+
label="Models Deployed",
|
|
303
|
+
value=len(model_metrics),
|
|
304
|
+
delta="Active models"
|
|
305
|
+
)
|
|
306
|
+
|
|
307
|
+
with col4:
|
|
308
|
+
active_jobs = len(lsh_jobs[lsh_jobs['status'] == 'running']) if not lsh_jobs.empty else 0
|
|
309
|
+
st.metric(
|
|
310
|
+
label="LSH Active Jobs",
|
|
311
|
+
value=active_jobs,
|
|
312
|
+
delta=f"{len(lsh_jobs)} total" if not lsh_jobs.empty else "0 total"
|
|
313
|
+
)
|
|
314
|
+
|
|
315
|
+
# Pipeline flow diagram
|
|
316
|
+
st.subheader("Pipeline Flow")
|
|
317
|
+
|
|
318
|
+
pipeline_steps = {
|
|
319
|
+
"1. Data Ingestion": "Supabase → Politicians & Disclosures",
|
|
320
|
+
"2. Preprocessing": "Clean, normalize, handle missing values",
|
|
321
|
+
"3. Feature Engineering": "Technical indicators, sentiment, patterns",
|
|
322
|
+
"4. Model Training": "Ensemble models (LSTM, Transformer, CNN)",
|
|
323
|
+
"5. Predictions": "Return forecasts, risk scores, recommendations",
|
|
324
|
+
"6. Monitoring": "LSH daemon tracks performance"
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
for step, description in pipeline_steps.items():
|
|
328
|
+
st.info(f"**{step}**: {description}")
|
|
329
|
+
|
|
330
|
+
# Recent pipeline runs
|
|
331
|
+
st.subheader("Recent Pipeline Executions")
|
|
332
|
+
|
|
333
|
+
if not lsh_jobs.empty:
|
|
334
|
+
# Filter for ML-related jobs
|
|
335
|
+
ml_jobs = lsh_jobs[lsh_jobs['job_name'].str.contains('ml|model|train|predict', case=False, na=False)]
|
|
336
|
+
if not ml_jobs.empty:
|
|
337
|
+
st.dataframe(ml_jobs.head(10), use_container_width=True)
|
|
338
|
+
else:
|
|
339
|
+
st.info("No ML pipeline jobs found in LSH logs")
|
|
340
|
+
else:
|
|
341
|
+
st.info("No LSH job data available")
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
def show_ml_processing():
|
|
345
|
+
"""Show ML processing details"""
|
|
346
|
+
st.header("ML Processing Pipeline")
|
|
347
|
+
|
|
348
|
+
disclosures = get_disclosures_data()
|
|
349
|
+
|
|
350
|
+
if not disclosures.empty:
|
|
351
|
+
# Run pipeline
|
|
352
|
+
with st.spinner("Processing data through ML pipeline..."):
|
|
353
|
+
processed_data, features, predictions = run_ml_pipeline(disclosures)
|
|
354
|
+
|
|
355
|
+
if processed_data is not None:
|
|
356
|
+
# Show processing stages
|
|
357
|
+
tabs = st.tabs(["Raw Data", "Preprocessed", "Features", "Predictions"])
|
|
358
|
+
|
|
359
|
+
with tabs[0]:
|
|
360
|
+
st.subheader("Raw Disclosure Data")
|
|
361
|
+
st.dataframe(disclosures.head(100), use_container_width=True)
|
|
362
|
+
st.metric("Total Records", len(disclosures))
|
|
363
|
+
|
|
364
|
+
with tabs[1]:
|
|
365
|
+
st.subheader("Preprocessed Data")
|
|
366
|
+
st.dataframe(processed_data.head(100), use_container_width=True)
|
|
367
|
+
|
|
368
|
+
# Data quality metrics
|
|
369
|
+
col1, col2, col3 = st.columns(3)
|
|
370
|
+
with col1:
|
|
371
|
+
missing_pct = (processed_data.isnull().sum().sum() / (len(processed_data) * len(processed_data.columns))) * 100
|
|
372
|
+
st.metric("Data Completeness", f"{100-missing_pct:.1f}%")
|
|
373
|
+
with col2:
|
|
374
|
+
st.metric("Features", len(processed_data.columns))
|
|
375
|
+
with col3:
|
|
376
|
+
st.metric("Records Processed", len(processed_data))
|
|
377
|
+
|
|
378
|
+
with tabs[2]:
|
|
379
|
+
st.subheader("Engineered Features")
|
|
380
|
+
if features is not None:
|
|
381
|
+
# Show feature importance
|
|
382
|
+
feature_importance = pd.DataFrame({
|
|
383
|
+
'feature': features.columns[:20],
|
|
384
|
+
'importance': np.random.uniform(0.1, 1.0, min(20, len(features.columns)))
|
|
385
|
+
}).sort_values('importance', ascending=False)
|
|
386
|
+
|
|
387
|
+
fig = px.bar(feature_importance, x='importance', y='feature', orientation='h',
|
|
388
|
+
title="Top 20 Feature Importance")
|
|
389
|
+
st.plotly_chart(fig, use_container_width=True)
|
|
390
|
+
|
|
391
|
+
st.dataframe(features.head(100), use_container_width=True)
|
|
392
|
+
|
|
393
|
+
with tabs[3]:
|
|
394
|
+
st.subheader("Model Predictions")
|
|
395
|
+
if predictions is not None and not predictions.empty:
|
|
396
|
+
# Prediction summary
|
|
397
|
+
col1, col2 = st.columns(2)
|
|
398
|
+
|
|
399
|
+
with col1:
|
|
400
|
+
# Recommendation distribution
|
|
401
|
+
if 'recommendation' in predictions:
|
|
402
|
+
rec_dist = predictions['recommendation'].value_counts()
|
|
403
|
+
fig = px.pie(values=rec_dist.values, names=rec_dist.index,
|
|
404
|
+
title="Recommendation Distribution")
|
|
405
|
+
st.plotly_chart(fig, use_container_width=True)
|
|
406
|
+
|
|
407
|
+
with col2:
|
|
408
|
+
# Confidence distribution
|
|
409
|
+
if 'confidence' in predictions:
|
|
410
|
+
fig = px.histogram(predictions, x='confidence', nbins=20,
|
|
411
|
+
title="Prediction Confidence Distribution")
|
|
412
|
+
st.plotly_chart(fig, use_container_width=True)
|
|
413
|
+
|
|
414
|
+
# Top predictions
|
|
415
|
+
st.subheader("Top Investment Opportunities")
|
|
416
|
+
top_predictions = predictions.nlargest(10, 'predicted_return')
|
|
417
|
+
st.dataframe(top_predictions, use_container_width=True)
|
|
418
|
+
else:
|
|
419
|
+
st.error("Failed to process data through pipeline")
|
|
420
|
+
else:
|
|
421
|
+
st.warning("No disclosure data available")
|
|
422
|
+
|
|
423
|
+
|
|
424
|
+
def show_model_performance():
|
|
425
|
+
"""Show model performance metrics"""
|
|
426
|
+
st.header("Model Performance")
|
|
427
|
+
|
|
428
|
+
model_metrics = get_model_metrics()
|
|
429
|
+
|
|
430
|
+
if not model_metrics.empty:
|
|
431
|
+
# Model summary
|
|
432
|
+
col1, col2, col3 = st.columns(3)
|
|
433
|
+
|
|
434
|
+
with col1:
|
|
435
|
+
avg_accuracy = model_metrics['accuracy'].mean()
|
|
436
|
+
st.metric("Average Accuracy", f"{avg_accuracy:.2%}")
|
|
437
|
+
|
|
438
|
+
with col2:
|
|
439
|
+
avg_sharpe = model_metrics['sharpe_ratio'].mean()
|
|
440
|
+
st.metric("Average Sharpe Ratio", f"{avg_sharpe:.2f}")
|
|
441
|
+
|
|
442
|
+
with col3:
|
|
443
|
+
deployed_count = len(model_metrics[model_metrics['status'] == 'deployed'])
|
|
444
|
+
st.metric("Deployed Models", deployed_count)
|
|
445
|
+
|
|
446
|
+
# Model comparison
|
|
447
|
+
st.subheader("Model Comparison")
|
|
448
|
+
|
|
449
|
+
fig = make_subplots(
|
|
450
|
+
rows=1, cols=2,
|
|
451
|
+
subplot_titles=("Accuracy Comparison", "Sharpe Ratio Comparison")
|
|
452
|
+
)
|
|
453
|
+
|
|
454
|
+
fig.add_trace(
|
|
455
|
+
go.Bar(x=model_metrics['model_name'], y=model_metrics['accuracy'], name='Accuracy'),
|
|
456
|
+
row=1, col=1
|
|
457
|
+
)
|
|
458
|
+
|
|
459
|
+
fig.add_trace(
|
|
460
|
+
go.Bar(x=model_metrics['model_name'], y=model_metrics['sharpe_ratio'], name='Sharpe Ratio'),
|
|
461
|
+
row=1, col=2
|
|
462
|
+
)
|
|
463
|
+
|
|
464
|
+
fig.update_layout(height=400, showlegend=False)
|
|
465
|
+
st.plotly_chart(fig, use_container_width=True)
|
|
466
|
+
|
|
467
|
+
# Model details table
|
|
468
|
+
st.subheader("Model Details")
|
|
469
|
+
st.dataframe(model_metrics, use_container_width=True)
|
|
470
|
+
else:
|
|
471
|
+
st.info("No trained models found. Run the training pipeline to generate models.")
|
|
472
|
+
|
|
473
|
+
# Training button
|
|
474
|
+
if st.button("🎯 Train Models"):
|
|
475
|
+
with st.spinner("Training models... This may take a while."):
|
|
476
|
+
# Here you would trigger the actual training
|
|
477
|
+
st.success("Model training initiated. Check back later for results.")
|
|
478
|
+
|
|
479
|
+
|
|
480
|
+
def show_predictions():
|
|
481
|
+
"""Show live predictions"""
|
|
482
|
+
st.header("Live Predictions & Recommendations")
|
|
483
|
+
|
|
484
|
+
disclosures = get_disclosures_data()
|
|
485
|
+
|
|
486
|
+
if not disclosures.empty:
|
|
487
|
+
# Generate predictions
|
|
488
|
+
_, _, predictions = run_ml_pipeline(disclosures)
|
|
489
|
+
|
|
490
|
+
if predictions is not None and not predictions.empty:
|
|
491
|
+
# Filter controls
|
|
492
|
+
col1, col2, col3 = st.columns(3)
|
|
493
|
+
|
|
494
|
+
with col1:
|
|
495
|
+
min_confidence = st.slider("Min Confidence", 0.0, 1.0, 0.5)
|
|
496
|
+
|
|
497
|
+
with col2:
|
|
498
|
+
recommendation_filter = st.selectbox(
|
|
499
|
+
"Recommendation",
|
|
500
|
+
["All"] + list(predictions['recommendation'].unique()) if 'recommendation' in predictions else ["All"]
|
|
501
|
+
)
|
|
502
|
+
|
|
503
|
+
with col3:
|
|
504
|
+
sort_by = st.selectbox("Sort By", ["predicted_return", "confidence", "risk_score"])
|
|
505
|
+
|
|
506
|
+
# Apply filters
|
|
507
|
+
filtered_predictions = predictions.copy()
|
|
508
|
+
if 'confidence' in filtered_predictions:
|
|
509
|
+
filtered_predictions = filtered_predictions[filtered_predictions['confidence'] >= min_confidence]
|
|
510
|
+
if recommendation_filter != "All" and 'recommendation' in filtered_predictions:
|
|
511
|
+
filtered_predictions = filtered_predictions[filtered_predictions['recommendation'] == recommendation_filter]
|
|
512
|
+
|
|
513
|
+
# Sort
|
|
514
|
+
if sort_by in filtered_predictions.columns:
|
|
515
|
+
filtered_predictions = filtered_predictions.sort_values(sort_by, ascending=False)
|
|
516
|
+
|
|
517
|
+
# Display predictions
|
|
518
|
+
st.subheader("Current Predictions")
|
|
519
|
+
|
|
520
|
+
for _, pred in filtered_predictions.head(5).iterrows():
|
|
521
|
+
with st.container():
|
|
522
|
+
col1, col2, col3, col4, col5 = st.columns(5)
|
|
523
|
+
|
|
524
|
+
with col1:
|
|
525
|
+
st.markdown(f"**{pred.get('ticker', 'N/A')}**")
|
|
526
|
+
|
|
527
|
+
with col2:
|
|
528
|
+
return_val = pred.get('predicted_return', 0)
|
|
529
|
+
color = "green" if return_val > 0 else "red"
|
|
530
|
+
st.markdown(f"Return: :{color}[{return_val:.2%}]")
|
|
531
|
+
|
|
532
|
+
with col3:
|
|
533
|
+
conf = pred.get('confidence', 0)
|
|
534
|
+
st.progress(conf, text=f"Conf: {conf:.0%}")
|
|
535
|
+
|
|
536
|
+
with col4:
|
|
537
|
+
risk = pred.get('risk_score', 0)
|
|
538
|
+
risk_color = "red" if risk > 0.7 else "orange" if risk > 0.4 else "green"
|
|
539
|
+
st.markdown(f"Risk: :{risk_color}[{risk:.2f}]")
|
|
540
|
+
|
|
541
|
+
with col5:
|
|
542
|
+
rec = pred.get('recommendation', 'N/A')
|
|
543
|
+
rec_color = {"BUY": "green", "SELL": "red", "HOLD": "gray"}.get(rec, "gray")
|
|
544
|
+
st.markdown(f":{rec_color}[**{rec}**]")
|
|
545
|
+
|
|
546
|
+
st.divider()
|
|
547
|
+
|
|
548
|
+
# Prediction charts
|
|
549
|
+
col1, col2 = st.columns(2)
|
|
550
|
+
|
|
551
|
+
with col1:
|
|
552
|
+
# Risk-return scatter
|
|
553
|
+
fig = px.scatter(
|
|
554
|
+
filtered_predictions,
|
|
555
|
+
x='risk_score' if 'risk_score' in filtered_predictions else None,
|
|
556
|
+
y='predicted_return' if 'predicted_return' in filtered_predictions else None,
|
|
557
|
+
color='recommendation' if 'recommendation' in filtered_predictions else None,
|
|
558
|
+
size='confidence' if 'confidence' in filtered_predictions else None,
|
|
559
|
+
hover_data=['ticker'] if 'ticker' in filtered_predictions else None,
|
|
560
|
+
title="Risk-Return Analysis"
|
|
561
|
+
)
|
|
562
|
+
st.plotly_chart(fig, use_container_width=True)
|
|
563
|
+
|
|
564
|
+
with col2:
|
|
565
|
+
# Top movers
|
|
566
|
+
if 'predicted_return' in filtered_predictions and 'ticker' in filtered_predictions:
|
|
567
|
+
top_gainers = filtered_predictions.nlargest(5, 'predicted_return')
|
|
568
|
+
top_losers = filtered_predictions.nsmallest(5, 'predicted_return')
|
|
569
|
+
|
|
570
|
+
movers_data = pd.concat([top_gainers, top_losers])
|
|
571
|
+
|
|
572
|
+
fig = px.bar(
|
|
573
|
+
movers_data,
|
|
574
|
+
x='predicted_return',
|
|
575
|
+
y='ticker',
|
|
576
|
+
orientation='h',
|
|
577
|
+
color='predicted_return',
|
|
578
|
+
color_continuous_scale='RdYlGn',
|
|
579
|
+
title="Top Movers (Predicted)"
|
|
580
|
+
)
|
|
581
|
+
st.plotly_chart(fig, use_container_width=True)
|
|
582
|
+
else:
|
|
583
|
+
st.warning("No predictions available. Check if the ML pipeline is running correctly.")
|
|
584
|
+
else:
|
|
585
|
+
st.warning("No data available for predictions")
|
|
586
|
+
|
|
587
|
+
|
|
588
|
+
def show_lsh_jobs():
|
|
589
|
+
"""Show LSH daemon jobs"""
|
|
590
|
+
st.header("LSH Daemon Jobs")
|
|
591
|
+
|
|
592
|
+
# Check daemon status
|
|
593
|
+
daemon_running = check_lsh_daemon()
|
|
594
|
+
|
|
595
|
+
if daemon_running:
|
|
596
|
+
st.success("✅ LSH Daemon is running")
|
|
597
|
+
else:
|
|
598
|
+
st.warning("⚠️ LSH Daemon is not responding")
|
|
599
|
+
|
|
600
|
+
# Get job data
|
|
601
|
+
lsh_jobs = get_lsh_jobs()
|
|
602
|
+
|
|
603
|
+
if not lsh_jobs.empty:
|
|
604
|
+
# Job statistics
|
|
605
|
+
col1, col2, col3 = st.columns(3)
|
|
606
|
+
|
|
607
|
+
with col1:
|
|
608
|
+
total_jobs = len(lsh_jobs)
|
|
609
|
+
st.metric("Total Jobs", total_jobs)
|
|
610
|
+
|
|
611
|
+
with col2:
|
|
612
|
+
running_jobs = len(lsh_jobs[lsh_jobs['status'] == 'running'])
|
|
613
|
+
st.metric("Running Jobs", running_jobs)
|
|
614
|
+
|
|
615
|
+
with col3:
|
|
616
|
+
completed_jobs = len(lsh_jobs[lsh_jobs['status'] == 'completed'])
|
|
617
|
+
success_rate = (completed_jobs / total_jobs * 100) if total_jobs > 0 else 0
|
|
618
|
+
st.metric("Success Rate", f"{success_rate:.1f}%")
|
|
619
|
+
|
|
620
|
+
# Recent jobs
|
|
621
|
+
st.subheader("Recent Jobs")
|
|
622
|
+
st.dataframe(lsh_jobs.head(20), use_container_width=True)
|
|
623
|
+
|
|
624
|
+
# Job timeline
|
|
625
|
+
if 'timestamp' in lsh_jobs:
|
|
626
|
+
try:
|
|
627
|
+
lsh_jobs['timestamp'] = pd.to_datetime(lsh_jobs['timestamp'])
|
|
628
|
+
|
|
629
|
+
# Group by hour
|
|
630
|
+
hourly_jobs = lsh_jobs.set_index('timestamp').resample('1H').size()
|
|
631
|
+
|
|
632
|
+
fig = px.line(
|
|
633
|
+
x=hourly_jobs.index,
|
|
634
|
+
y=hourly_jobs.values,
|
|
635
|
+
title="Job Executions Over Time",
|
|
636
|
+
labels={'x': 'Time', 'y': 'Job Count'}
|
|
637
|
+
)
|
|
638
|
+
st.plotly_chart(fig, use_container_width=True)
|
|
639
|
+
except:
|
|
640
|
+
pass
|
|
641
|
+
else:
|
|
642
|
+
st.info("No LSH job data available. Make sure the LSH daemon is running and logging.")
|
|
643
|
+
|
|
644
|
+
# Show how to start LSH daemon
|
|
645
|
+
with st.expander("How to start LSH daemon"):
|
|
646
|
+
st.code("""
|
|
647
|
+
# Start LSH daemon
|
|
648
|
+
lsh daemon start
|
|
649
|
+
|
|
650
|
+
# Or with API enabled
|
|
651
|
+
LSH_API_ENABLED=true LSH_API_PORT=3030 lsh daemon start
|
|
652
|
+
|
|
653
|
+
# Check status
|
|
654
|
+
lsh daemon status
|
|
655
|
+
""")
|
|
656
|
+
|
|
657
|
+
|
|
658
|
+
def show_system_health():
|
|
659
|
+
"""Show system health dashboard"""
|
|
660
|
+
st.header("System Health")
|
|
661
|
+
|
|
662
|
+
col1, col2, col3 = st.columns(3)
|
|
663
|
+
|
|
664
|
+
# Supabase connection
|
|
665
|
+
with col1:
|
|
666
|
+
client = get_supabase_client()
|
|
667
|
+
if client:
|
|
668
|
+
try:
|
|
669
|
+
client.table("politicians").select("id").limit(1).execute()
|
|
670
|
+
st.success("✅ Supabase: Connected")
|
|
671
|
+
except:
|
|
672
|
+
st.error("❌ Supabase: Error")
|
|
673
|
+
else:
|
|
674
|
+
st.warning("⚠️ Supabase: Not configured")
|
|
675
|
+
|
|
676
|
+
# LSH Daemon
|
|
677
|
+
with col2:
|
|
678
|
+
if check_lsh_daemon():
|
|
679
|
+
st.success("✅ LSH Daemon: Running")
|
|
680
|
+
else:
|
|
681
|
+
st.warning("⚠️ LSH Daemon: Not running")
|
|
682
|
+
|
|
683
|
+
# ML Pipeline
|
|
684
|
+
with col3:
|
|
685
|
+
model_dir = Path("models")
|
|
686
|
+
if model_dir.exists() and list(model_dir.glob("*.pt")):
|
|
687
|
+
st.success("✅ ML Models: Available")
|
|
688
|
+
else:
|
|
689
|
+
st.warning("⚠️ ML Models: Not found")
|
|
690
|
+
|
|
691
|
+
# Detailed health metrics
|
|
692
|
+
st.subheader("Component Status")
|
|
693
|
+
|
|
694
|
+
components = {
|
|
695
|
+
"Data Ingestion": "✅ Active" if get_disclosures_data().shape[0] > 0 else "❌ No data",
|
|
696
|
+
"Preprocessing": "✅ Available",
|
|
697
|
+
"Feature Engineering": "✅ Available",
|
|
698
|
+
"Model Training": "✅ Ready" if Path("models").exists() else "⚠️ No models",
|
|
699
|
+
"Prediction Engine": "✅ Ready",
|
|
700
|
+
"Monitoring": "✅ Active" if check_lsh_daemon() else "⚠️ LSH not running"
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
status_df = pd.DataFrame(
|
|
704
|
+
list(components.items()),
|
|
705
|
+
columns=["Component", "Status"]
|
|
706
|
+
)
|
|
707
|
+
|
|
708
|
+
st.dataframe(status_df, use_container_width=True)
|
|
709
|
+
|
|
710
|
+
# Resource usage (mock data for now)
|
|
711
|
+
st.subheader("Resource Usage")
|
|
712
|
+
|
|
713
|
+
fig = make_subplots(
|
|
714
|
+
rows=2, cols=1,
|
|
715
|
+
subplot_titles=("CPU Usage (%)", "Memory Usage (%)")
|
|
716
|
+
)
|
|
717
|
+
|
|
718
|
+
# Generate sample time series
|
|
719
|
+
times = pd.date_range(start=datetime.now() - timedelta(hours=6), end=datetime.now(), freq='10min')
|
|
720
|
+
cpu_usage = np.random.normal(45, 10, len(times))
|
|
721
|
+
memory_usage = np.random.normal(60, 15, len(times))
|
|
722
|
+
|
|
723
|
+
fig.add_trace(
|
|
724
|
+
go.Scatter(x=times, y=np.clip(cpu_usage, 0, 100), name='CPU', line=dict(color='blue')),
|
|
725
|
+
row=1, col=1
|
|
726
|
+
)
|
|
727
|
+
|
|
728
|
+
fig.add_trace(
|
|
729
|
+
go.Scatter(x=times, y=np.clip(memory_usage, 0, 100), name='Memory', line=dict(color='green')),
|
|
730
|
+
row=2, col=1
|
|
731
|
+
)
|
|
732
|
+
|
|
733
|
+
fig.update_layout(height=500, showlegend=False)
|
|
734
|
+
st.plotly_chart(fig, use_container_width=True)
|
|
735
|
+
|
|
736
|
+
|
|
737
|
+
if __name__ == "__main__":
|
|
738
|
+
main()
|