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.

Files changed (94) hide show
  1. mcli/app/completion_cmd.py +59 -49
  2. mcli/app/completion_helpers.py +60 -138
  3. mcli/app/logs_cmd.py +46 -13
  4. mcli/app/main.py +17 -14
  5. mcli/app/model_cmd.py +19 -4
  6. mcli/chat/chat.py +3 -2
  7. mcli/lib/search/cached_vectorizer.py +1 -0
  8. mcli/lib/services/data_pipeline.py +12 -5
  9. mcli/lib/services/lsh_client.py +69 -58
  10. mcli/ml/api/app.py +28 -36
  11. mcli/ml/api/middleware.py +8 -16
  12. mcli/ml/api/routers/admin_router.py +3 -1
  13. mcli/ml/api/routers/auth_router.py +32 -56
  14. mcli/ml/api/routers/backtest_router.py +3 -1
  15. mcli/ml/api/routers/data_router.py +3 -1
  16. mcli/ml/api/routers/model_router.py +35 -74
  17. mcli/ml/api/routers/monitoring_router.py +3 -1
  18. mcli/ml/api/routers/portfolio_router.py +3 -1
  19. mcli/ml/api/routers/prediction_router.py +60 -65
  20. mcli/ml/api/routers/trade_router.py +6 -2
  21. mcli/ml/api/routers/websocket_router.py +12 -9
  22. mcli/ml/api/schemas.py +10 -2
  23. mcli/ml/auth/auth_manager.py +49 -114
  24. mcli/ml/auth/models.py +30 -15
  25. mcli/ml/auth/permissions.py +12 -19
  26. mcli/ml/backtesting/backtest_engine.py +134 -108
  27. mcli/ml/backtesting/performance_metrics.py +142 -108
  28. mcli/ml/cache.py +12 -18
  29. mcli/ml/cli/main.py +37 -23
  30. mcli/ml/config/settings.py +29 -12
  31. mcli/ml/dashboard/app.py +122 -130
  32. mcli/ml/dashboard/app_integrated.py +283 -152
  33. mcli/ml/dashboard/app_supabase.py +176 -108
  34. mcli/ml/dashboard/app_training.py +212 -206
  35. mcli/ml/dashboard/cli.py +14 -5
  36. mcli/ml/data_ingestion/api_connectors.py +51 -81
  37. mcli/ml/data_ingestion/data_pipeline.py +127 -125
  38. mcli/ml/data_ingestion/stream_processor.py +72 -80
  39. mcli/ml/database/migrations/env.py +3 -2
  40. mcli/ml/database/models.py +112 -79
  41. mcli/ml/database/session.py +6 -5
  42. mcli/ml/experimentation/ab_testing.py +149 -99
  43. mcli/ml/features/ensemble_features.py +9 -8
  44. mcli/ml/features/political_features.py +6 -5
  45. mcli/ml/features/recommendation_engine.py +15 -14
  46. mcli/ml/features/stock_features.py +7 -6
  47. mcli/ml/features/test_feature_engineering.py +8 -7
  48. mcli/ml/logging.py +10 -15
  49. mcli/ml/mlops/data_versioning.py +57 -64
  50. mcli/ml/mlops/experiment_tracker.py +49 -41
  51. mcli/ml/mlops/model_serving.py +59 -62
  52. mcli/ml/mlops/pipeline_orchestrator.py +203 -149
  53. mcli/ml/models/base_models.py +8 -7
  54. mcli/ml/models/ensemble_models.py +6 -5
  55. mcli/ml/models/recommendation_models.py +7 -6
  56. mcli/ml/models/test_models.py +18 -14
  57. mcli/ml/monitoring/drift_detection.py +95 -74
  58. mcli/ml/monitoring/metrics.py +10 -22
  59. mcli/ml/optimization/portfolio_optimizer.py +172 -132
  60. mcli/ml/predictions/prediction_engine.py +235 -0
  61. mcli/ml/preprocessing/data_cleaners.py +6 -5
  62. mcli/ml/preprocessing/feature_extractors.py +7 -6
  63. mcli/ml/preprocessing/ml_pipeline.py +3 -2
  64. mcli/ml/preprocessing/politician_trading_preprocessor.py +11 -10
  65. mcli/ml/preprocessing/test_preprocessing.py +4 -4
  66. mcli/ml/scripts/populate_sample_data.py +36 -16
  67. mcli/ml/tasks.py +82 -83
  68. mcli/ml/tests/test_integration.py +86 -76
  69. mcli/ml/tests/test_training_dashboard.py +169 -142
  70. mcli/mygroup/test_cmd.py +2 -1
  71. mcli/self/self_cmd.py +38 -18
  72. mcli/self/test_cmd.py +2 -1
  73. mcli/workflow/dashboard/dashboard_cmd.py +13 -6
  74. mcli/workflow/lsh_integration.py +46 -58
  75. mcli/workflow/politician_trading/commands.py +576 -427
  76. mcli/workflow/politician_trading/config.py +7 -7
  77. mcli/workflow/politician_trading/connectivity.py +35 -33
  78. mcli/workflow/politician_trading/data_sources.py +72 -71
  79. mcli/workflow/politician_trading/database.py +18 -16
  80. mcli/workflow/politician_trading/demo.py +4 -3
  81. mcli/workflow/politician_trading/models.py +5 -5
  82. mcli/workflow/politician_trading/monitoring.py +13 -13
  83. mcli/workflow/politician_trading/scrapers.py +332 -224
  84. mcli/workflow/politician_trading/scrapers_california.py +116 -94
  85. mcli/workflow/politician_trading/scrapers_eu.py +70 -71
  86. mcli/workflow/politician_trading/scrapers_uk.py +118 -90
  87. mcli/workflow/politician_trading/scrapers_us_states.py +125 -92
  88. mcli/workflow/politician_trading/workflow.py +98 -71
  89. {mcli_framework-7.1.0.dist-info → mcli_framework-7.1.2.dist-info}/METADATA +2 -2
  90. {mcli_framework-7.1.0.dist-info → mcli_framework-7.1.2.dist-info}/RECORD +94 -93
  91. {mcli_framework-7.1.0.dist-info → mcli_framework-7.1.2.dist-info}/WHEEL +0 -0
  92. {mcli_framework-7.1.0.dist-info → mcli_framework-7.1.2.dist-info}/entry_points.txt +0 -0
  93. {mcli_framework-7.1.0.dist-info → mcli_framework-7.1.2.dist-info}/licenses/LICENSE +0 -0
  94. {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
- """, unsafe_allow_html=True)
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("⚠️ Supabase credentials not found. Set SUPABASE_URL and SUPABASE_ANON_KEY environment variables.")
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 = client.table("trading_disclosures").select("*").order("disclosure_date", desc=True).limit(500).execute()
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 = client.table("ml_predictions").select("*").order("created_at", desc=True).limit(100).execute()
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 = client.table("data_pull_jobs").select("*").order("created_at", desc=True).limit(50).execute()
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
- '🚀 Dashboard Hub - View All'
156
- '</div></a>',
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
- '📊 Pipeline Jobs'
168
- '</div></a>',
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
- '🔄 Workflows'
175
- '</div></a>',
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
- '🏗️ CI/CD'
184
- '</div></a>',
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
- '🔍 Monitoring'
191
- '</div></a>',
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
- ["Overview", "Politicians", "Trading Disclosures", "ML Predictions", "Data Pull Jobs", "System Health"]
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("Sample politician:", politicians.iloc[0]['full_name'] if 'full_name' in politicians.columns else "No name")
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=f"{len(disclosures[pd.to_datetime(disclosures['disclosure_date']) > datetime.now() - timedelta(days=7)])} this week" if not disclosures.empty and 'disclosure_date' in disclosures else None
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 = len(jobs[jobs['status'] == 'completed']) if not jobs.empty and 'status' in jobs else 0
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 'transaction_type' in disclosures:
294
- type_counts = disclosures['transaction_type'].value_counts()
295
- fig = px.pie(values=type_counts.values, names=type_counts.index, title="Transaction Types")
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 'ticker_symbol' in disclosures:
303
- ticker_counts = disclosures['ticker_symbol'].value_counts().head(10)
304
- fig = px.bar(x=ticker_counts.values, y=ticker_counts.index, orientation='h', title="Most Traded Stocks")
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['party'].dropna().unique() if 'party' in politicians else [],
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['state'].dropna().unique() if 'state' in politicians else [],
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 'party' in filtered:
337
- filtered = filtered[filtered['party'].isin(party_filter)]
338
- if state_filter and 'state' in filtered:
339
- filtered = filtered[filtered['state'].isin(state_filter)]
340
- if active_only and 'is_active' in filtered:
341
- filtered = filtered[filtered['is_active'] == True]
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 'party' in filtered:
350
- party_dist = filtered['party'].value_counts()
351
- fig = px.pie(values=party_dist.values, names=party_dist.index, title="Party Distribution")
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 'state' in filtered:
355
- state_dist = filtered['state'].value_counts().head(10)
356
- fig = px.bar(x=state_dist.values, y=state_dist.index, orientation='h', title="Top States")
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 'disclosure_date' in disclosures:
371
- disclosures['disclosure_date'] = pd.to_datetime(disclosures['disclosure_date'])
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 = disclosures['transaction_type'].dropna().unique() if 'transaction_type' in disclosures else []
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 'ticker_symbol' in filtered:
390
- filtered = filtered[filtered['ticker_symbol'].str.contains(ticker_filter, na=False)]
391
- if transaction_filter != "All" and 'transaction_type' in filtered:
392
- filtered = filtered[filtered['transaction_type'] == transaction_filter]
393
- if len(date_range) == 2 and 'disclosure_date' in filtered:
394
- filtered = filtered[(filtered['disclosure_date'] >= pd.Timestamp(date_range[0])) &
395
- (filtered['disclosure_date'] <= pd.Timestamp(date_range[1]))]
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 'disclosure_date' in filtered and 'amount' in filtered:
406
- daily_volume = filtered.groupby(filtered['disclosure_date'].dt.date)['amount'].sum()
407
- fig = px.line(x=daily_volume.index, y=daily_volume.values, title="Trading Volume Over Time")
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 'politician_name' in filtered:
413
- top_traders = filtered['politician_name'].value_counts().head(10)
414
- fig = px.bar(x=top_traders.values, y=top_traders.index, orientation='h',
415
- title="Most Active Traders")
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 'confidence' in predictions:
432
- fig = px.histogram(predictions, x='confidence', title="Prediction Confidence Distribution")
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("No ML predictions available yet. The ML pipeline will generate predictions once sufficient data is collected.")
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['status'].value_counts() if 'status' in jobs else pd.Series()
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('completed', 0))
517
+ st.metric("Completed", status_counts.get("completed", 0))
452
518
  with col2:
453
- st.metric("Running", status_counts.get('running', 0))
519
+ st.metric("Running", status_counts.get("running", 0))
454
520
  with col3:
455
- st.metric("Failed", status_counts.get('failed', 0))
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 'created_at' in jobs:
462
- jobs['created_at'] = pd.to_datetime(jobs['created_at'])
463
- jobs['date'] = jobs['created_at'].dt.date
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(['date', 'status']).size().unstack(fill_value=0)
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(go.Scatter(
470
- x=daily_stats.index,
471
- y=daily_stats[status],
472
- mode='lines+markers',
473
- name=status,
474
- stackgroup='one'
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 'created_at' in disclosures:
507
- latest = pd.to_datetime(disclosures['created_at']).max()
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 'status' in jobs:
587
+ if not jobs.empty and "status" in jobs:
520
588
  recent_jobs = jobs.head(10)
521
- success_rate = (recent_jobs['status'] == 'completed').mean() * 100
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()