mcli-framework 7.1.0__py3-none-any.whl → 7.1.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of mcli-framework might be problematic. Click here for more details.
- mcli/app/completion_cmd.py +59 -49
- mcli/app/completion_helpers.py +60 -138
- mcli/app/logs_cmd.py +46 -13
- 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 +69 -58
- 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 +283 -152
- 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 +235 -0
- 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 +38 -18
- 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.0.dist-info → mcli_framework-7.1.2.dist-info}/METADATA +2 -2
- {mcli_framework-7.1.0.dist-info → mcli_framework-7.1.2.dist-info}/RECORD +94 -93
- {mcli_framework-7.1.0.dist-info → mcli_framework-7.1.2.dist-info}/WHEEL +0 -0
- {mcli_framework-7.1.0.dist-info → mcli_framework-7.1.2.dist-info}/entry_points.txt +0 -0
- {mcli_framework-7.1.0.dist-info → mcli_framework-7.1.2.dist-info}/licenses/LICENSE +0 -0
- {mcli_framework-7.1.0.dist-info → mcli_framework-7.1.2.dist-info}/top_level.txt +0 -0
|
@@ -1,24 +1,22 @@
|
|
|
1
1
|
"""Streamlit dashboard for ML system monitoring - Supabase version"""
|
|
2
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
3
|
import asyncio
|
|
9
|
-
from datetime import datetime, timedelta
|
|
10
|
-
import numpy as np
|
|
11
|
-
from supabase import create_client, Client
|
|
12
4
|
import os
|
|
5
|
+
from datetime import datetime, timedelta
|
|
13
6
|
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
import numpy as np
|
|
9
|
+
import pandas as pd
|
|
10
|
+
import plotly.express as px
|
|
11
|
+
import plotly.graph_objects as go
|
|
12
|
+
import streamlit as st
|
|
14
13
|
from dotenv import load_dotenv
|
|
14
|
+
from plotly.subplots import make_subplots
|
|
15
|
+
from supabase import Client, create_client
|
|
15
16
|
|
|
16
17
|
# Page config must come first
|
|
17
18
|
st.set_page_config(
|
|
18
|
-
page_title="MCLI ML Dashboard",
|
|
19
|
-
page_icon="📊",
|
|
20
|
-
layout="wide",
|
|
21
|
-
initial_sidebar_state="expanded"
|
|
19
|
+
page_title="MCLI ML Dashboard", page_icon="📊", layout="wide", initial_sidebar_state="expanded"
|
|
22
20
|
)
|
|
23
21
|
|
|
24
22
|
# Load environment variables from supabase/.env.local
|
|
@@ -27,7 +25,8 @@ if env_path.exists():
|
|
|
27
25
|
load_dotenv(env_path)
|
|
28
26
|
|
|
29
27
|
# Custom CSS
|
|
30
|
-
st.markdown(
|
|
28
|
+
st.markdown(
|
|
29
|
+
"""
|
|
31
30
|
<style>
|
|
32
31
|
.metric-card {
|
|
33
32
|
background-color: #f0f2f6;
|
|
@@ -36,7 +35,9 @@ st.markdown("""
|
|
|
36
35
|
border-left: 4px solid #1f77b4;
|
|
37
36
|
}
|
|
38
37
|
</style>
|
|
39
|
-
""",
|
|
38
|
+
""",
|
|
39
|
+
unsafe_allow_html=True,
|
|
40
|
+
)
|
|
40
41
|
|
|
41
42
|
|
|
42
43
|
@st.cache_resource
|
|
@@ -47,7 +48,9 @@ def get_supabase_client() -> Client:
|
|
|
47
48
|
key = os.getenv("SUPABASE_KEY", "") or os.getenv("SUPABASE_ANON_KEY", "")
|
|
48
49
|
|
|
49
50
|
if not url or not key:
|
|
50
|
-
st.warning(
|
|
51
|
+
st.warning(
|
|
52
|
+
"⚠️ Supabase credentials not found. Set SUPABASE_URL and SUPABASE_ANON_KEY environment variables."
|
|
53
|
+
)
|
|
51
54
|
return None
|
|
52
55
|
|
|
53
56
|
return create_client(url, key)
|
|
@@ -81,7 +84,13 @@ def get_disclosures_data():
|
|
|
81
84
|
|
|
82
85
|
try:
|
|
83
86
|
# Get recent disclosures
|
|
84
|
-
response =
|
|
87
|
+
response = (
|
|
88
|
+
client.table("trading_disclosures")
|
|
89
|
+
.select("*")
|
|
90
|
+
.order("disclosure_date", desc=True)
|
|
91
|
+
.limit(500)
|
|
92
|
+
.execute()
|
|
93
|
+
)
|
|
85
94
|
return pd.DataFrame(response.data)
|
|
86
95
|
except Exception as e:
|
|
87
96
|
st.error(f"Error fetching disclosures: {e}")
|
|
@@ -97,7 +106,13 @@ def get_predictions_data():
|
|
|
97
106
|
|
|
98
107
|
try:
|
|
99
108
|
# Try to get predictions if table exists
|
|
100
|
-
response =
|
|
109
|
+
response = (
|
|
110
|
+
client.table("ml_predictions")
|
|
111
|
+
.select("*")
|
|
112
|
+
.order("created_at", desc=True)
|
|
113
|
+
.limit(100)
|
|
114
|
+
.execute()
|
|
115
|
+
)
|
|
101
116
|
return pd.DataFrame(response.data)
|
|
102
117
|
except:
|
|
103
118
|
# Table might not exist yet
|
|
@@ -128,7 +143,13 @@ def get_jobs_data():
|
|
|
128
143
|
return pd.DataFrame()
|
|
129
144
|
|
|
130
145
|
try:
|
|
131
|
-
response =
|
|
146
|
+
response = (
|
|
147
|
+
client.table("data_pull_jobs")
|
|
148
|
+
.select("*")
|
|
149
|
+
.order("created_at", desc=True)
|
|
150
|
+
.limit(50)
|
|
151
|
+
.execute()
|
|
152
|
+
)
|
|
132
153
|
return pd.DataFrame(response.data)
|
|
133
154
|
except Exception as e:
|
|
134
155
|
st.error(f"Error fetching jobs: {e}")
|
|
@@ -152,9 +173,9 @@ def main():
|
|
|
152
173
|
st.markdown(
|
|
153
174
|
'<a href="file:///Users/lefv/repos/lsh/dashboard-hub.html" target="_blank" style="text-decoration: none;">'
|
|
154
175
|
'<div style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 10px; border-radius: 8px; text-align: center; margin-bottom: 15px; font-weight: 600;">'
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
unsafe_allow_html=True
|
|
176
|
+
"🚀 Dashboard Hub - View All"
|
|
177
|
+
"</div></a>",
|
|
178
|
+
unsafe_allow_html=True,
|
|
158
179
|
)
|
|
159
180
|
|
|
160
181
|
st.sidebar.subheader("🔗 Direct Links")
|
|
@@ -164,32 +185,32 @@ def main():
|
|
|
164
185
|
st.markdown(
|
|
165
186
|
'<a href="http://localhost:3034/dashboard/" target="_blank" style="text-decoration: none;">'
|
|
166
187
|
'<div style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 8px; border-radius: 6px; text-align: center; margin-bottom: 8px;">'
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
unsafe_allow_html=True
|
|
188
|
+
"📊 Pipeline Jobs"
|
|
189
|
+
"</div></a>",
|
|
190
|
+
unsafe_allow_html=True,
|
|
170
191
|
)
|
|
171
192
|
st.markdown(
|
|
172
193
|
'<a href="http://localhost:3034/dashboard/workflow.html" target="_blank" style="text-decoration: none;">'
|
|
173
194
|
'<div style="background: linear-gradient(135deg, #48bb78 0%, #38a169 100%); color: white; padding: 8px; border-radius: 6px; text-align: center; margin-bottom: 8px;">'
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
unsafe_allow_html=True
|
|
195
|
+
"🔄 Workflows"
|
|
196
|
+
"</div></a>",
|
|
197
|
+
unsafe_allow_html=True,
|
|
177
198
|
)
|
|
178
199
|
|
|
179
200
|
with col2:
|
|
180
201
|
st.markdown(
|
|
181
202
|
'<a href="http://localhost:3033/dashboard/" target="_blank" style="text-decoration: none;">'
|
|
182
203
|
'<div style="background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%); color: white; padding: 8px; border-radius: 6px; text-align: center; margin-bottom: 8px;">'
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
unsafe_allow_html=True
|
|
204
|
+
"🏗️ CI/CD"
|
|
205
|
+
"</div></a>",
|
|
206
|
+
unsafe_allow_html=True,
|
|
186
207
|
)
|
|
187
208
|
st.markdown(
|
|
188
209
|
'<a href="http://localhost:3035/api/health" target="_blank" style="text-decoration: none;">'
|
|
189
210
|
'<div style="background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%); color: white; padding: 8px; border-radius: 6px; text-align: center; margin-bottom: 8px;">'
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
unsafe_allow_html=True
|
|
211
|
+
"🔍 Monitoring"
|
|
212
|
+
"</div></a>",
|
|
213
|
+
unsafe_allow_html=True,
|
|
193
214
|
)
|
|
194
215
|
|
|
195
216
|
st.sidebar.markdown("---")
|
|
@@ -205,13 +226,21 @@ def main():
|
|
|
205
226
|
st.error("❌ Not connected to Supabase")
|
|
206
227
|
page = st.sidebar.selectbox(
|
|
207
228
|
"Choose a page",
|
|
208
|
-
[
|
|
229
|
+
[
|
|
230
|
+
"Overview",
|
|
231
|
+
"Politicians",
|
|
232
|
+
"Trading Disclosures",
|
|
233
|
+
"ML Predictions",
|
|
234
|
+
"Data Pull Jobs",
|
|
235
|
+
"System Health",
|
|
236
|
+
],
|
|
209
237
|
)
|
|
210
238
|
|
|
211
239
|
# Auto-refresh toggle
|
|
212
240
|
auto_refresh = st.sidebar.checkbox("Auto-refresh (30s)", value=True)
|
|
213
241
|
if auto_refresh:
|
|
214
242
|
import time
|
|
243
|
+
|
|
215
244
|
time.sleep(30)
|
|
216
245
|
st.rerun()
|
|
217
246
|
|
|
@@ -252,7 +281,10 @@ def show_overview():
|
|
|
252
281
|
|
|
253
282
|
# Also show sample data for debugging
|
|
254
283
|
if not politicians.empty:
|
|
255
|
-
st.sidebar.write(
|
|
284
|
+
st.sidebar.write(
|
|
285
|
+
"Sample politician:",
|
|
286
|
+
politicians.iloc[0]["full_name"] if "full_name" in politicians.columns else "No name",
|
|
287
|
+
)
|
|
256
288
|
|
|
257
289
|
# Display key metrics
|
|
258
290
|
col1, col2, col3, col4 = st.columns(4)
|
|
@@ -261,28 +293,31 @@ def show_overview():
|
|
|
261
293
|
st.metric(
|
|
262
294
|
label="Politicians Tracked",
|
|
263
295
|
value=len(politicians) if not politicians.empty else 0,
|
|
264
|
-
delta=None # Simplified to avoid errors
|
|
296
|
+
delta=None, # Simplified to avoid errors
|
|
265
297
|
)
|
|
266
298
|
|
|
267
299
|
with col2:
|
|
268
300
|
st.metric(
|
|
269
301
|
label="Total Disclosures",
|
|
270
302
|
value=len(disclosures),
|
|
271
|
-
delta=
|
|
303
|
+
delta=(
|
|
304
|
+
f"{len(disclosures[pd.to_datetime(disclosures['disclosure_date']) > datetime.now() - timedelta(days=7)])} this week"
|
|
305
|
+
if not disclosures.empty and "disclosure_date" in disclosures
|
|
306
|
+
else None
|
|
307
|
+
),
|
|
272
308
|
)
|
|
273
309
|
|
|
274
310
|
with col3:
|
|
275
|
-
st.metric(
|
|
276
|
-
label="ML Predictions",
|
|
277
|
-
value=len(predictions) if not predictions.empty else "0"
|
|
278
|
-
)
|
|
311
|
+
st.metric(label="ML Predictions", value=len(predictions) if not predictions.empty else "0")
|
|
279
312
|
|
|
280
313
|
with col4:
|
|
281
|
-
successful_jobs =
|
|
314
|
+
successful_jobs = (
|
|
315
|
+
len(jobs[jobs["status"] == "completed"]) if not jobs.empty and "status" in jobs else 0
|
|
316
|
+
)
|
|
282
317
|
total_jobs = len(jobs) if not jobs.empty else 0
|
|
283
318
|
st.metric(
|
|
284
319
|
label="Job Success Rate",
|
|
285
|
-
value=f"{(successful_jobs/total_jobs*100):.1f}%" if total_jobs > 0 else "N/A"
|
|
320
|
+
value=f"{(successful_jobs/total_jobs*100):.1f}%" if total_jobs > 0 else "N/A",
|
|
286
321
|
)
|
|
287
322
|
|
|
288
323
|
# Charts
|
|
@@ -290,18 +325,25 @@ def show_overview():
|
|
|
290
325
|
|
|
291
326
|
with col1:
|
|
292
327
|
st.subheader("Disclosure Types")
|
|
293
|
-
if not disclosures.empty and
|
|
294
|
-
type_counts = disclosures[
|
|
295
|
-
fig = px.pie(
|
|
328
|
+
if not disclosures.empty and "transaction_type" in disclosures:
|
|
329
|
+
type_counts = disclosures["transaction_type"].value_counts()
|
|
330
|
+
fig = px.pie(
|
|
331
|
+
values=type_counts.values, names=type_counts.index, title="Transaction Types"
|
|
332
|
+
)
|
|
296
333
|
st.plotly_chart(fig, use_container_width=True)
|
|
297
334
|
else:
|
|
298
335
|
st.info("No disclosure data available")
|
|
299
336
|
|
|
300
337
|
with col2:
|
|
301
338
|
st.subheader("Top Traded Tickers")
|
|
302
|
-
if not disclosures.empty and
|
|
303
|
-
ticker_counts = disclosures[
|
|
304
|
-
fig = px.bar(
|
|
339
|
+
if not disclosures.empty and "ticker_symbol" in disclosures:
|
|
340
|
+
ticker_counts = disclosures["ticker_symbol"].value_counts().head(10)
|
|
341
|
+
fig = px.bar(
|
|
342
|
+
x=ticker_counts.values,
|
|
343
|
+
y=ticker_counts.index,
|
|
344
|
+
orientation="h",
|
|
345
|
+
title="Most Traded Stocks",
|
|
346
|
+
)
|
|
305
347
|
st.plotly_chart(fig, use_container_width=True)
|
|
306
348
|
else:
|
|
307
349
|
st.info("No ticker data available")
|
|
@@ -319,26 +361,26 @@ def show_politicians():
|
|
|
319
361
|
with col1:
|
|
320
362
|
party_filter = st.multiselect(
|
|
321
363
|
"Party",
|
|
322
|
-
options=politicians[
|
|
323
|
-
default=[]
|
|
364
|
+
options=politicians["party"].dropna().unique() if "party" in politicians else [],
|
|
365
|
+
default=[],
|
|
324
366
|
)
|
|
325
367
|
with col2:
|
|
326
368
|
state_filter = st.multiselect(
|
|
327
369
|
"State",
|
|
328
|
-
options=politicians[
|
|
329
|
-
default=[]
|
|
370
|
+
options=politicians["state"].dropna().unique() if "state" in politicians else [],
|
|
371
|
+
default=[],
|
|
330
372
|
)
|
|
331
373
|
with col3:
|
|
332
374
|
active_only = st.checkbox("Active Only", value=True)
|
|
333
375
|
|
|
334
376
|
# Apply filters
|
|
335
377
|
filtered = politicians.copy()
|
|
336
|
-
if party_filter and
|
|
337
|
-
filtered = filtered[filtered[
|
|
338
|
-
if state_filter and
|
|
339
|
-
filtered = filtered[filtered[
|
|
340
|
-
if active_only and
|
|
341
|
-
filtered = filtered[filtered[
|
|
378
|
+
if party_filter and "party" in filtered:
|
|
379
|
+
filtered = filtered[filtered["party"].isin(party_filter)]
|
|
380
|
+
if state_filter and "state" in filtered:
|
|
381
|
+
filtered = filtered[filtered["state"].isin(state_filter)]
|
|
382
|
+
if active_only and "is_active" in filtered:
|
|
383
|
+
filtered = filtered[filtered["is_active"] == True]
|
|
342
384
|
|
|
343
385
|
# Display data
|
|
344
386
|
st.dataframe(filtered, use_container_width=True)
|
|
@@ -346,14 +388,18 @@ def show_politicians():
|
|
|
346
388
|
# Stats
|
|
347
389
|
col1, col2 = st.columns(2)
|
|
348
390
|
with col1:
|
|
349
|
-
if
|
|
350
|
-
party_dist = filtered[
|
|
351
|
-
fig = px.pie(
|
|
391
|
+
if "party" in filtered:
|
|
392
|
+
party_dist = filtered["party"].value_counts()
|
|
393
|
+
fig = px.pie(
|
|
394
|
+
values=party_dist.values, names=party_dist.index, title="Party Distribution"
|
|
395
|
+
)
|
|
352
396
|
st.plotly_chart(fig, use_container_width=True)
|
|
353
397
|
with col2:
|
|
354
|
-
if
|
|
355
|
-
state_dist = filtered[
|
|
356
|
-
fig = px.bar(
|
|
398
|
+
if "state" in filtered:
|
|
399
|
+
state_dist = filtered["state"].value_counts().head(10)
|
|
400
|
+
fig = px.bar(
|
|
401
|
+
x=state_dist.values, y=state_dist.index, orientation="h", title="Top States"
|
|
402
|
+
)
|
|
357
403
|
st.plotly_chart(fig, use_container_width=True)
|
|
358
404
|
else:
|
|
359
405
|
st.warning("No politician data available")
|
|
@@ -367,32 +413,38 @@ def show_disclosures():
|
|
|
367
413
|
|
|
368
414
|
if not disclosures.empty:
|
|
369
415
|
# Convert dates
|
|
370
|
-
if
|
|
371
|
-
disclosures[
|
|
416
|
+
if "disclosure_date" in disclosures:
|
|
417
|
+
disclosures["disclosure_date"] = pd.to_datetime(disclosures["disclosure_date"])
|
|
372
418
|
|
|
373
419
|
# Filters
|
|
374
420
|
col1, col2, col3 = st.columns(3)
|
|
375
421
|
with col1:
|
|
376
422
|
ticker_filter = st.text_input("Ticker Symbol", "").upper()
|
|
377
423
|
with col2:
|
|
378
|
-
transaction_types =
|
|
424
|
+
transaction_types = (
|
|
425
|
+
disclosures["transaction_type"].dropna().unique()
|
|
426
|
+
if "transaction_type" in disclosures
|
|
427
|
+
else []
|
|
428
|
+
)
|
|
379
429
|
transaction_filter = st.selectbox("Transaction Type", ["All"] + list(transaction_types))
|
|
380
430
|
with col3:
|
|
381
431
|
date_range = st.date_input(
|
|
382
432
|
"Date Range",
|
|
383
433
|
value=(datetime.now() - timedelta(days=30), datetime.now()),
|
|
384
|
-
max_value=datetime.now()
|
|
434
|
+
max_value=datetime.now(),
|
|
385
435
|
)
|
|
386
436
|
|
|
387
437
|
# Apply filters
|
|
388
438
|
filtered = disclosures.copy()
|
|
389
|
-
if ticker_filter and
|
|
390
|
-
filtered = filtered[filtered[
|
|
391
|
-
if transaction_filter != "All" and
|
|
392
|
-
filtered = filtered[filtered[
|
|
393
|
-
if len(date_range) == 2 and
|
|
394
|
-
filtered = filtered[
|
|
395
|
-
|
|
439
|
+
if ticker_filter and "ticker_symbol" in filtered:
|
|
440
|
+
filtered = filtered[filtered["ticker_symbol"].str.contains(ticker_filter, na=False)]
|
|
441
|
+
if transaction_filter != "All" and "transaction_type" in filtered:
|
|
442
|
+
filtered = filtered[filtered["transaction_type"] == transaction_filter]
|
|
443
|
+
if len(date_range) == 2 and "disclosure_date" in filtered:
|
|
444
|
+
filtered = filtered[
|
|
445
|
+
(filtered["disclosure_date"] >= pd.Timestamp(date_range[0]))
|
|
446
|
+
& (filtered["disclosure_date"] <= pd.Timestamp(date_range[1]))
|
|
447
|
+
]
|
|
396
448
|
|
|
397
449
|
# Display data
|
|
398
450
|
st.dataframe(filtered, use_container_width=True)
|
|
@@ -402,17 +454,27 @@ def show_disclosures():
|
|
|
402
454
|
col1, col2 = st.columns(2)
|
|
403
455
|
with col1:
|
|
404
456
|
# Volume over time
|
|
405
|
-
if
|
|
406
|
-
daily_volume = filtered.groupby(filtered[
|
|
407
|
-
|
|
457
|
+
if "disclosure_date" in filtered and "amount" in filtered:
|
|
458
|
+
daily_volume = filtered.groupby(filtered["disclosure_date"].dt.date)[
|
|
459
|
+
"amount"
|
|
460
|
+
].sum()
|
|
461
|
+
fig = px.line(
|
|
462
|
+
x=daily_volume.index,
|
|
463
|
+
y=daily_volume.values,
|
|
464
|
+
title="Trading Volume Over Time",
|
|
465
|
+
)
|
|
408
466
|
st.plotly_chart(fig, use_container_width=True)
|
|
409
467
|
|
|
410
468
|
with col2:
|
|
411
469
|
# Top politicians by trading
|
|
412
|
-
if
|
|
413
|
-
top_traders = filtered[
|
|
414
|
-
fig = px.bar(
|
|
415
|
-
|
|
470
|
+
if "politician_name" in filtered:
|
|
471
|
+
top_traders = filtered["politician_name"].value_counts().head(10)
|
|
472
|
+
fig = px.bar(
|
|
473
|
+
x=top_traders.values,
|
|
474
|
+
y=top_traders.index,
|
|
475
|
+
orientation="h",
|
|
476
|
+
title="Most Active Traders",
|
|
477
|
+
)
|
|
416
478
|
st.plotly_chart(fig, use_container_width=True)
|
|
417
479
|
else:
|
|
418
480
|
st.warning("No disclosure data available")
|
|
@@ -428,11 +490,15 @@ def show_predictions():
|
|
|
428
490
|
st.dataframe(predictions, use_container_width=True)
|
|
429
491
|
|
|
430
492
|
# Add prediction analysis charts if we have data
|
|
431
|
-
if
|
|
432
|
-
fig = px.histogram(
|
|
493
|
+
if "confidence" in predictions:
|
|
494
|
+
fig = px.histogram(
|
|
495
|
+
predictions, x="confidence", title="Prediction Confidence Distribution"
|
|
496
|
+
)
|
|
433
497
|
st.plotly_chart(fig, use_container_width=True)
|
|
434
498
|
else:
|
|
435
|
-
st.info(
|
|
499
|
+
st.info(
|
|
500
|
+
"No ML predictions available yet. The ML pipeline will generate predictions once sufficient data is collected."
|
|
501
|
+
)
|
|
436
502
|
|
|
437
503
|
|
|
438
504
|
def show_jobs():
|
|
@@ -445,34 +511,36 @@ def show_jobs():
|
|
|
445
511
|
# Status overview
|
|
446
512
|
col1, col2, col3 = st.columns(3)
|
|
447
513
|
|
|
448
|
-
status_counts = jobs[
|
|
514
|
+
status_counts = jobs["status"].value_counts() if "status" in jobs else pd.Series()
|
|
449
515
|
|
|
450
516
|
with col1:
|
|
451
|
-
st.metric("Completed", status_counts.get(
|
|
517
|
+
st.metric("Completed", status_counts.get("completed", 0))
|
|
452
518
|
with col2:
|
|
453
|
-
st.metric("Running", status_counts.get(
|
|
519
|
+
st.metric("Running", status_counts.get("running", 0))
|
|
454
520
|
with col3:
|
|
455
|
-
st.metric("Failed", status_counts.get(
|
|
521
|
+
st.metric("Failed", status_counts.get("failed", 0))
|
|
456
522
|
|
|
457
523
|
# Jobs table
|
|
458
524
|
st.dataframe(jobs, use_container_width=True)
|
|
459
525
|
|
|
460
526
|
# Success rate over time
|
|
461
|
-
if
|
|
462
|
-
jobs[
|
|
463
|
-
jobs[
|
|
527
|
+
if "created_at" in jobs:
|
|
528
|
+
jobs["created_at"] = pd.to_datetime(jobs["created_at"])
|
|
529
|
+
jobs["date"] = jobs["created_at"].dt.date
|
|
464
530
|
|
|
465
|
-
daily_stats = jobs.groupby([
|
|
531
|
+
daily_stats = jobs.groupby(["date", "status"]).size().unstack(fill_value=0)
|
|
466
532
|
fig = go.Figure()
|
|
467
533
|
|
|
468
534
|
for status in daily_stats.columns:
|
|
469
|
-
fig.add_trace(
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
535
|
+
fig.add_trace(
|
|
536
|
+
go.Scatter(
|
|
537
|
+
x=daily_stats.index,
|
|
538
|
+
y=daily_stats[status],
|
|
539
|
+
mode="lines+markers",
|
|
540
|
+
name=status,
|
|
541
|
+
stackgroup="one",
|
|
542
|
+
)
|
|
543
|
+
)
|
|
476
544
|
|
|
477
545
|
fig.update_layout(title="Job Status Over Time", xaxis_title="Date", yaxis_title="Count")
|
|
478
546
|
st.plotly_chart(fig, use_container_width=True)
|
|
@@ -503,8 +571,8 @@ def show_system_health():
|
|
|
503
571
|
with col2:
|
|
504
572
|
# Check data freshness
|
|
505
573
|
disclosures = get_disclosures_data()
|
|
506
|
-
if not disclosures.empty and
|
|
507
|
-
latest = pd.to_datetime(disclosures[
|
|
574
|
+
if not disclosures.empty and "created_at" in disclosures:
|
|
575
|
+
latest = pd.to_datetime(disclosures["created_at"]).max()
|
|
508
576
|
hours_ago = (datetime.now() - latest).total_seconds() / 3600
|
|
509
577
|
if hours_ago < 24:
|
|
510
578
|
st.success(f"✅ Data: Fresh ({hours_ago:.1f}h old)")
|
|
@@ -516,9 +584,9 @@ def show_system_health():
|
|
|
516
584
|
with col3:
|
|
517
585
|
# Check job health
|
|
518
586
|
jobs = get_jobs_data()
|
|
519
|
-
if not jobs.empty and
|
|
587
|
+
if not jobs.empty and "status" in jobs:
|
|
520
588
|
recent_jobs = jobs.head(10)
|
|
521
|
-
success_rate = (recent_jobs[
|
|
589
|
+
success_rate = (recent_jobs["status"] == "completed").mean() * 100
|
|
522
590
|
if success_rate > 80:
|
|
523
591
|
st.success(f"✅ Jobs: {success_rate:.0f}% success")
|
|
524
592
|
elif success_rate > 50:
|
|
@@ -541,8 +609,8 @@ def show_system_health():
|
|
|
541
609
|
len(politicians),
|
|
542
610
|
len(disclosures),
|
|
543
611
|
len(predictions),
|
|
544
|
-
len(jobs) if not jobs.empty else 0
|
|
545
|
-
]
|
|
612
|
+
len(jobs) if not jobs.empty else 0,
|
|
613
|
+
],
|
|
546
614
|
}
|
|
547
615
|
|
|
548
616
|
stats_df = pd.DataFrame(stats_data)
|
|
@@ -557,4 +625,4 @@ def show_system_health():
|
|
|
557
625
|
|
|
558
626
|
|
|
559
627
|
if __name__ == "__main__":
|
|
560
|
-
main()
|
|
628
|
+
main()
|