mcli-framework 7.4.0__py3-none-any.whl → 7.5.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.

Files changed (86) hide show
  1. mcli/lib/auth/aws_manager.py +9 -64
  2. mcli/lib/auth/azure_manager.py +9 -64
  3. mcli/lib/auth/credential_manager.py +70 -1
  4. mcli/lib/auth/gcp_manager.py +11 -64
  5. mcli/ml/dashboard/app.py +6 -39
  6. mcli/ml/dashboard/app_integrated.py +23 -101
  7. mcli/ml/dashboard/app_supabase.py +8 -57
  8. mcli/ml/dashboard/app_training.py +9 -11
  9. mcli/ml/dashboard/common.py +167 -0
  10. mcli/ml/dashboard/pages/cicd.py +4 -4
  11. mcli/ml/dashboard/pages/debug_dependencies.py +99 -57
  12. mcli/ml/dashboard/pages/gravity_viz.py +265 -47
  13. mcli/ml/dashboard/pages/predictions_enhanced.py +4 -2
  14. mcli/ml/dashboard/pages/scrapers_and_logs.py +3 -3
  15. mcli/ml/dashboard/styles.py +55 -0
  16. mcli/self/self_cmd.py +12 -806
  17. {mcli_framework-7.4.0.dist-info → mcli_framework-7.5.0.dist-info}/METADATA +1 -3
  18. {mcli_framework-7.4.0.dist-info → mcli_framework-7.5.0.dist-info}/RECORD +23 -84
  19. mcli/__init__.py +0 -160
  20. mcli/__main__.py +0 -14
  21. mcli/app/__init__.py +0 -23
  22. mcli/app/model/__init__.py +0 -0
  23. mcli/app/video/__init__.py +0 -5
  24. mcli/chat/__init__.py +0 -34
  25. mcli/lib/__init__.py +0 -0
  26. mcli/lib/api/__init__.py +0 -0
  27. mcli/lib/auth/__init__.py +0 -1
  28. mcli/lib/config/__init__.py +0 -1
  29. mcli/lib/erd/__init__.py +0 -25
  30. mcli/lib/files/__init__.py +0 -0
  31. mcli/lib/fs/__init__.py +0 -1
  32. mcli/lib/logger/__init__.py +0 -3
  33. mcli/lib/performance/__init__.py +0 -17
  34. mcli/lib/pickles/__init__.py +0 -1
  35. mcli/lib/shell/__init__.py +0 -0
  36. mcli/lib/toml/__init__.py +0 -1
  37. mcli/lib/watcher/__init__.py +0 -0
  38. mcli/ml/__init__.py +0 -16
  39. mcli/ml/api/__init__.py +0 -30
  40. mcli/ml/api/routers/__init__.py +0 -27
  41. mcli/ml/auth/__init__.py +0 -45
  42. mcli/ml/backtesting/__init__.py +0 -39
  43. mcli/ml/cli/__init__.py +0 -5
  44. mcli/ml/config/__init__.py +0 -33
  45. mcli/ml/configs/__init__.py +0 -16
  46. mcli/ml/dashboard/__init__.py +0 -12
  47. mcli/ml/dashboard/components/__init__.py +0 -7
  48. mcli/ml/dashboard/pages/__init__.py +0 -6
  49. mcli/ml/data_ingestion/__init__.py +0 -39
  50. mcli/ml/database/__init__.py +0 -47
  51. mcli/ml/experimentation/__init__.py +0 -29
  52. mcli/ml/features/__init__.py +0 -39
  53. mcli/ml/mlops/__init__.py +0 -33
  54. mcli/ml/models/__init__.py +0 -94
  55. mcli/ml/monitoring/__init__.py +0 -25
  56. mcli/ml/optimization/__init__.py +0 -27
  57. mcli/ml/predictions/__init__.py +0 -5
  58. mcli/ml/preprocessing/__init__.py +0 -28
  59. mcli/ml/scripts/__init__.py +0 -1
  60. mcli/ml/trading/__init__.py +0 -66
  61. mcli/ml/training/__init__.py +0 -10
  62. mcli/mygroup/__init__.py +0 -3
  63. mcli/public/__init__.py +0 -1
  64. mcli/public/commands/__init__.py +0 -2
  65. mcli/self/__init__.py +0 -3
  66. mcli/workflow/__init__.py +0 -0
  67. mcli/workflow/daemon/__init__.py +0 -15
  68. mcli/workflow/dashboard/__init__.py +0 -5
  69. mcli/workflow/docker/__init__.py +0 -0
  70. mcli/workflow/file/__init__.py +0 -0
  71. mcli/workflow/gcloud/__init__.py +0 -1
  72. mcli/workflow/git_commit/__init__.py +0 -0
  73. mcli/workflow/interview/__init__.py +0 -0
  74. mcli/workflow/politician_trading/__init__.py +0 -4
  75. mcli/workflow/registry/__init__.py +0 -0
  76. mcli/workflow/repo/__init__.py +0 -0
  77. mcli/workflow/scheduler/__init__.py +0 -25
  78. mcli/workflow/search/__init__.py +0 -0
  79. mcli/workflow/sync/__init__.py +0 -5
  80. mcli/workflow/videos/__init__.py +0 -1
  81. mcli/workflow/wakatime/__init__.py +0 -80
  82. /mcli/ml/dashboard/{pages/overview.py → overview.py} +0 -0
  83. {mcli_framework-7.4.0.dist-info → mcli_framework-7.5.0.dist-info}/WHEEL +0 -0
  84. {mcli_framework-7.4.0.dist-info → mcli_framework-7.5.0.dist-info}/entry_points.txt +0 -0
  85. {mcli_framework-7.4.0.dist-info → mcli_framework-7.5.0.dist-info}/licenses/LICENSE +0 -0
  86. {mcli_framework-7.4.0.dist-info → mcli_framework-7.5.0.dist-info}/top_level.txt +0 -0
@@ -10,6 +10,8 @@ import plotly.express as px
10
10
  from datetime import datetime, timedelta
11
11
  from typing import List, Dict, Optional, Tuple
12
12
  import numpy as np
13
+ import os
14
+ from supabase import Client, create_client
13
15
 
14
16
  # Configure page
15
17
  st.set_page_config(
@@ -50,6 +52,25 @@ st.markdown("""
50
52
  """, unsafe_allow_html=True)
51
53
 
52
54
 
55
+ @st.cache_resource
56
+ def get_supabase_client() -> Optional[Client]:
57
+ """Get Supabase client with Streamlit Cloud secrets support"""
58
+ try:
59
+ url = st.secrets.get("SUPABASE_URL", "")
60
+ key = st.secrets.get("SUPABASE_KEY", "") or st.secrets.get("SUPABASE_SERVICE_ROLE_KEY", "")
61
+ except (AttributeError, FileNotFoundError):
62
+ url = os.getenv("SUPABASE_URL", "")
63
+ key = os.getenv("SUPABASE_KEY", "") or os.getenv("SUPABASE_SERVICE_ROLE_KEY", "")
64
+
65
+ if not url or not key:
66
+ return None
67
+
68
+ try:
69
+ return create_client(url, key)
70
+ except Exception:
71
+ return None
72
+
73
+
53
74
  class GravityData:
54
75
  """Simulates gravity measurement data (in production, would fetch from real sensors/APIs)"""
55
76
 
@@ -98,12 +119,236 @@ class GravityData:
98
119
  class PoliticianLocations:
99
120
  """Manages politician location and trading data"""
100
121
 
122
+ # Approximate coordinates for major cities (used as fallback)
123
+ STATE_CAPITALS = {
124
+ # US States (capital cities)
125
+ 'Alabama': (32.3668, -86.3000),
126
+ 'California': (38.5816, -121.4944),
127
+ 'Texas': (30.2672, -97.7431),
128
+ 'New Jersey': (40.2206, -74.7597),
129
+ 'Florida': (30.4383, -84.2807),
130
+ 'New York': (42.6526, -73.7562),
131
+ 'Pennsylvania': (40.2732, -76.8867),
132
+ 'Illinois': (39.7817, -89.6501),
133
+ 'Ohio': (39.9612, -82.9988),
134
+ 'Georgia': (33.7490, -84.3880),
135
+ 'Michigan': (42.7325, -84.5555),
136
+ 'North Carolina': (35.7796, -78.6382),
137
+ 'Virginia': (37.5407, -77.4360),
138
+ 'Washington': (47.0379, -122.9007),
139
+ 'Massachusetts': (42.3601, -71.0589),
140
+ # UK
141
+ 'United Kingdom': (51.5074, -0.1278), # London
142
+ 'UK': (51.5074, -0.1278),
143
+ # EU Countries (capitals)
144
+ 'France': (48.8566, 2.3522), # Paris
145
+ 'Germany': (52.5200, 13.4050), # Berlin
146
+ 'Italy': (41.9028, 12.4964), # Rome
147
+ 'Spain': (40.4168, -3.7038), # Madrid
148
+ 'Poland': (52.2297, 21.0122), # Warsaw
149
+ 'Netherlands': (52.3676, 4.9041), # Amsterdam
150
+ 'Belgium': (50.8503, 4.3517), # Brussels
151
+ 'Sweden': (59.3293, 18.0686), # Stockholm
152
+ 'Austria': (48.2082, 16.3738), # Vienna
153
+ 'Denmark': (55.6761, 12.5683), # Copenhagen
154
+ 'Finland': (60.1699, 24.9384), # Helsinki
155
+ }
156
+
157
+ # Major city coordinates for district approximations
158
+ MAJOR_CITIES = {
159
+ # California
160
+ 'San Francisco': (37.7749, -122.4194),
161
+ 'Los Angeles': (34.0522, -118.2437),
162
+ 'San Diego': (32.7157, -117.1611),
163
+ 'Sacramento': (38.5816, -121.4944),
164
+ 'San Jose': (37.3382, -121.8863),
165
+ # Texas
166
+ 'Houston': (29.7604, -97.7431),
167
+ 'Dallas': (32.7767, -96.7970),
168
+ 'Austin': (30.2672, -97.7431),
169
+ 'San Antonio': (29.4241, -98.4936),
170
+ # New York
171
+ 'New York City': (40.7128, -74.0060),
172
+ 'Buffalo': (42.8864, -78.8784),
173
+ 'Rochester': (43.1566, -77.6088),
174
+ 'Albany': (42.6526, -73.7562),
175
+ # Florida
176
+ 'Miami': (25.7617, -80.1918),
177
+ 'Tampa': (27.9506, -82.4572),
178
+ 'Orlando': (28.5383, -81.3792),
179
+ 'Jacksonville': (30.3322, -81.6557),
180
+ 'Tallahassee': (30.4383, -84.2807),
181
+ # Pennsylvania
182
+ 'Philadelphia': (39.9526, -75.1652),
183
+ 'Pittsburgh': (40.4406, -79.9959),
184
+ 'Harrisburg': (40.2732, -76.8867),
185
+ # Illinois
186
+ 'Chicago': (41.8781, -87.6298),
187
+ 'Springfield': (39.7817, -89.6501),
188
+ # New Jersey
189
+ 'Newark': (40.7357, -74.1724),
190
+ 'Jersey City': (40.7178, -74.0431),
191
+ 'Trenton': (40.2206, -74.7597),
192
+ # And more major cities as needed...
193
+ }
194
+
101
195
  @staticmethod
102
- def get_sample_politicians() -> pd.DataFrame:
196
+ def get_location_for_politician(state_or_country: str, district: str, role: str) -> Tuple[float, float]:
103
197
  """
104
- Sample politician data with locations
105
- In production: integrate with mcli's politician_trading database
198
+ Get lat/lon for a politician based on their state/district/role
199
+ Returns: (latitude, longitude)
106
200
  """
201
+ # For US Congress members with districts, try to use district-specific locations
202
+ if district and state_or_country in ['California', 'Texas', 'New York', 'Florida', 'Pennsylvania', 'Illinois', 'New Jersey']:
203
+ # Parse district like "CA-11" or "TX-02"
204
+ if '-' in str(district):
205
+ district_num = district.split('-')[-1]
206
+
207
+ # District-specific mapping (approximate major city in district)
208
+ # This is a simplified approach - in production, use actual district boundaries
209
+ district_locations = {
210
+ # California districts (examples)
211
+ 'California': {
212
+ '11': (37.7749, -122.4194), # SF Bay Area
213
+ '12': (37.7749, -122.4194), # SF
214
+ '13': (37.3382, -121.8863), # Oakland/East Bay
215
+ '17': (37.3382, -121.8863), # San Jose area
216
+ '28': (34.0522, -118.2437), # LA
217
+ '43': (33.7701, -118.1937), # Long Beach
218
+ },
219
+ # Texas districts
220
+ 'Texas': {
221
+ '02': (29.7604, -97.7431), # Houston
222
+ '07': (29.7604, -97.7431), # Houston
223
+ '24': (32.7767, -96.7970), # Dallas
224
+ '21': (29.4241, -98.4936), # San Antonio
225
+ },
226
+ # New York districts
227
+ 'New York': {
228
+ '12': (40.7128, -74.0060), # NYC Manhattan
229
+ '14': (40.7128, -74.0060), # NYC Bronx/Queens
230
+ '26': (42.8864, -78.8784), # Buffalo
231
+ },
232
+ # Add more as needed
233
+ }
234
+
235
+ if state_or_country in district_locations and district_num in district_locations[state_or_country]:
236
+ # Add small random offset to prevent exact overlap
237
+ base_lat, base_lon = district_locations[state_or_country][district_num]
238
+ import random
239
+ offset_lat = random.uniform(-0.1, 0.1)
240
+ offset_lon = random.uniform(-0.1, 0.1)
241
+ return (base_lat + offset_lat, base_lon + offset_lon)
242
+
243
+ # For senators or politicians without district mapping, use state capital with small offset
244
+ base_coords = PoliticianLocations.STATE_CAPITALS.get(state_or_country, (38.9072, -77.0369))
245
+
246
+ # Add small random offset to prevent exact overlap for multiple politicians from same location
247
+ import random
248
+ offset_lat = random.uniform(-0.2, 0.2)
249
+ offset_lon = random.uniform(-0.2, 0.2)
250
+
251
+ return (base_coords[0] + offset_lat, base_coords[1] + offset_lon)
252
+
253
+ @staticmethod
254
+ @st.cache_data(ttl=60)
255
+ def get_politicians_from_db() -> pd.DataFrame:
256
+ """Fetch politicians with trading data from database"""
257
+ client = get_supabase_client()
258
+ if not client:
259
+ return PoliticianLocations.get_fallback_politicians()
260
+
261
+ try:
262
+ # Fetch politicians
263
+ politicians_response = client.table("politicians").select("*").execute()
264
+ if not politicians_response.data:
265
+ return PoliticianLocations.get_fallback_politicians()
266
+
267
+ politicians_df = pd.DataFrame(politicians_response.data)
268
+
269
+ # Fetch trading disclosures to calculate volumes
270
+ disclosures_response = client.table("trading_disclosures").select("*").execute()
271
+ disclosures_df = pd.DataFrame(disclosures_response.data) if disclosures_response.data else pd.DataFrame()
272
+
273
+ # Calculate trading metrics per politician
274
+ result_data = []
275
+ for _, pol in politicians_df.iterrows():
276
+ pol_id = pol.get('id')
277
+ pol_disclosures = disclosures_df[disclosures_df['politician_id'] == pol_id] if not disclosures_df.empty else pd.DataFrame()
278
+
279
+ # Calculate metrics
280
+ recent_trades = len(pol_disclosures)
281
+ total_volume = 0
282
+ last_trade_date = None
283
+
284
+ if not pol_disclosures.empty:
285
+ # Estimate volume from range midpoints
286
+ for _, d in pol_disclosures.iterrows():
287
+ min_amt = d.get('amount_range_min', 0) or 0
288
+ max_amt = d.get('amount_range_max', 0) or 0
289
+ if min_amt and max_amt:
290
+ total_volume += (min_amt + max_amt) / 2
291
+ elif d.get('amount_exact'):
292
+ total_volume += d['amount_exact']
293
+
294
+ # Get last trade date
295
+ transaction_dates = pd.to_datetime(pol_disclosures['transaction_date'], errors='coerce')
296
+ last_trade_date = transaction_dates.max()
297
+
298
+ # Get location based on state/district/role
299
+ state_or_country = pol.get('state_or_country', '')
300
+ district = pol.get('district', '')
301
+ role = pol.get('role', '')
302
+ lat, lon = PoliticianLocations.get_location_for_politician(
303
+ state_or_country, district, role
304
+ )
305
+
306
+ # Build politician record - prefer first+last name over full_name if available
307
+ first_name = pol.get('first_name', '').strip()
308
+ last_name = pol.get('last_name', '').strip()
309
+ full_name = pol.get('full_name', '').strip()
310
+
311
+ # Use first_name + last_name if both available, otherwise use full_name
312
+ if first_name and last_name:
313
+ display_name = f"{first_name} {last_name}"
314
+ elif full_name:
315
+ display_name = full_name
316
+ elif first_name:
317
+ display_name = first_name
318
+ elif last_name:
319
+ display_name = last_name
320
+ else:
321
+ display_name = f"Politician {pol_id[:8]}" # Fallback to ID
322
+
323
+ result_data.append({
324
+ 'name': display_name,
325
+ 'role': pol.get('role', 'Unknown'),
326
+ 'state': state_or_country,
327
+ 'district': pol.get('district'),
328
+ 'party': pol.get('party', 'Unknown'),
329
+ 'lat': lat,
330
+ 'lon': lon,
331
+ 'recent_trades': recent_trades,
332
+ 'total_trade_volume': total_volume,
333
+ 'last_trade_date': last_trade_date if pd.notna(last_trade_date) else datetime(2025, 1, 1),
334
+ })
335
+
336
+ result_df = pd.DataFrame(result_data)
337
+ # Filter out politicians with no trading data
338
+ result_df = result_df[result_df['recent_trades'] > 0]
339
+
340
+ if result_df.empty:
341
+ return PoliticianLocations.get_fallback_politicians()
342
+
343
+ return result_df
344
+
345
+ except Exception as e:
346
+ st.warning(f"Could not fetch politicians from database: {e}")
347
+ return PoliticianLocations.get_fallback_politicians()
348
+
349
+ @staticmethod
350
+ def get_fallback_politicians() -> pd.DataFrame:
351
+ """Fallback sample data if database is unavailable"""
107
352
  politicians = [
108
353
  {
109
354
  'name': 'Nancy Pelosi',
@@ -111,7 +356,7 @@ class PoliticianLocations:
111
356
  'state': 'California',
112
357
  'district': 'CA-11',
113
358
  'party': 'Democrat',
114
- 'lat': 37.7749, # San Francisco
359
+ 'lat': 37.7749,
115
360
  'lon': -122.4194,
116
361
  'recent_trades': 15,
117
362
  'total_trade_volume': 5_000_000,
@@ -123,50 +368,13 @@ class PoliticianLocations:
123
368
  'state': 'Alabama',
124
369
  'district': None,
125
370
  'party': 'Republican',
126
- 'lat': 32.3668, # Montgomery
371
+ 'lat': 32.3668,
127
372
  'lon': -86.3000,
128
373
  'recent_trades': 23,
129
374
  'total_trade_volume': 3_200_000,
130
375
  'last_trade_date': datetime(2025, 10, 3),
131
376
  },
132
- {
133
- 'name': 'Josh Gottheimer',
134
- 'role': 'US House Representative',
135
- 'state': 'New Jersey',
136
- 'district': 'NJ-05',
137
- 'party': 'Democrat',
138
- 'lat': 40.9168, # Ridgewood
139
- 'lon': -74.1168,
140
- 'recent_trades': 12,
141
- 'total_trade_volume': 2_800_000,
142
- 'last_trade_date': datetime(2025, 10, 7),
143
- },
144
- {
145
- 'name': 'Dan Crenshaw',
146
- 'role': 'US House Representative',
147
- 'state': 'Texas',
148
- 'district': 'TX-02',
149
- 'party': 'Republican',
150
- 'lat': 29.7604, # Houston
151
- 'lon': -95.3698,
152
- 'recent_trades': 18,
153
- 'total_trade_volume': 4_100_000,
154
- 'last_trade_date': datetime(2025, 10, 6),
155
- },
156
- {
157
- 'name': 'Ro Khanna',
158
- 'role': 'US House Representative',
159
- 'state': 'California',
160
- 'district': 'CA-17',
161
- 'party': 'Democrat',
162
- 'lat': 37.3861, # San Jose
163
- 'lon': -121.8947,
164
- 'recent_trades': 8,
165
- 'total_trade_volume': 1_500_000,
166
- 'last_trade_date': datetime(2025, 10, 4),
167
- },
168
377
  ]
169
-
170
378
  return pd.DataFrame(politicians)
171
379
 
172
380
 
@@ -386,8 +594,11 @@ def main():
386
594
  # Sidebar
387
595
  st.sidebar.header("⚙️ Configuration")
388
596
 
389
- # Load politician data
390
- politicians_df = PoliticianLocations.get_sample_politicians()
597
+ # Load politician data from database
598
+ politicians_df = PoliticianLocations.get_politicians_from_db()
599
+
600
+ # Show data info
601
+ st.sidebar.info(f"📊 Loaded {len(politicians_df)} politicians with trading data")
391
602
 
392
603
  # Politician selection
393
604
  selected_politician = st.sidebar.selectbox(
@@ -411,9 +622,10 @@ def main():
411
622
  "Minimum Trade Volume ($)",
412
623
  min_value=0,
413
624
  max_value=10_000_000,
414
- value=1_000_000,
415
- step=500_000,
416
- format="%d"
625
+ value=0, # Changed from 1,000,000 to 0 to show all politicians by default
626
+ step=100_000,
627
+ format="%d",
628
+ help="Filter politicians by minimum trade volume. Set to 0 to see all."
417
629
  )
418
630
 
419
631
  # Filter politicians by trade volume
@@ -421,6 +633,12 @@ def main():
421
633
  politicians_df['total_trade_volume'] >= min_trade_volume
422
634
  ]
423
635
 
636
+ # Show filter results
637
+ if len(filtered_politicians) < len(politicians_df):
638
+ st.sidebar.warning(f"⚠️ Filter reduced to {len(filtered_politicians)} politicians (from {len(politicians_df)})")
639
+ else:
640
+ st.sidebar.success(f"✅ Showing all {len(filtered_politicians)} politicians")
641
+
424
642
  # Main content
425
643
  if selected_politician != 'All':
426
644
  # Single politician view
@@ -310,7 +310,8 @@ def show_active_predictions(predictions_df: pd.DataFrame):
310
310
  with st.container():
311
311
  cols = st.columns([2, 1, 1, 1])
312
312
  with cols[0]:
313
- st.markdown(f"**{row['ticker']}** - {row['sector']}")
313
+ st.markdown(f"### 📈 {row['ticker']}")
314
+ st.caption(f"{row['sector']}")
314
315
  with cols[1]:
315
316
  st.metric("Return", f"{row['predicted_return']*100:+.1f}%")
316
317
  with cols[2]:
@@ -327,7 +328,8 @@ def show_active_predictions(predictions_df: pd.DataFrame):
327
328
  with st.container():
328
329
  cols = st.columns([2, 1, 1, 1])
329
330
  with cols[0]:
330
- st.markdown(f"**{row['ticker']}** - {row['sector']}")
331
+ st.markdown(f"### 📈 {row['ticker']}")
332
+ st.caption(f"{row['sector']}")
331
333
  with cols[1]:
332
334
  st.metric("Return", f"{row['predicted_return']*100:+.1f}%")
333
335
  with cols[2]:
@@ -736,10 +736,10 @@ def run_senate_watcher_scraper(
736
736
  st.markdown("#### Recent Trading Disclosures")
737
737
  disc_df = pd.DataFrame([{
738
738
  "Date": d.transaction_date.strftime("%Y-%m-%d") if hasattr(d.transaction_date, 'strftime') else str(d.transaction_date),
739
- "Politician": d.politician_bioguide_id,
740
- "Type": d.transaction_type,
739
+ "Ticker": d.asset_ticker or "—",
741
740
  "Asset": d.asset_name[:50],
742
- "Ticker": d.asset_ticker or "",
741
+ "Type": d.transaction_type,
742
+ "Politician": d.politician_bioguide_id,
743
743
  "Min": f"${d.amount_range_min:,.0f}" if d.amount_range_min else "",
744
744
  "Max": f"${d.amount_range_max:,.0f}" if d.amount_range_max else ""
745
745
  } for d in disclosures[:100]]) # Limit to 100 for display
@@ -0,0 +1,55 @@
1
+ """
2
+ Shared CSS styles for MCLI ML Dashboards.
3
+
4
+ This module centralizes all CSS styling to avoid duplication across dashboard files.
5
+ """
6
+
7
+ # Standard dashboard CSS used across all dashboard variants
8
+ DASHBOARD_CSS = """
9
+ <style>
10
+ .metric-card {
11
+ background-color: #f0f2f6;
12
+ padding: 1rem;
13
+ border-radius: 0.5rem;
14
+ border-left: 4px solid #1f77b4;
15
+ }
16
+ .alert-success {
17
+ background-color: #d4edda;
18
+ border: 1px solid #c3e6cb;
19
+ color: #155724;
20
+ padding: 0.75rem;
21
+ border-radius: 0.25rem;
22
+ }
23
+ .alert-warning {
24
+ background-color: #fff3cd;
25
+ border: 1px solid #ffeaa7;
26
+ color: #856404;
27
+ padding: 0.75rem;
28
+ border-radius: 0.25rem;
29
+ }
30
+ .alert-danger {
31
+ background-color: #f8d7da;
32
+ border: 1px solid #f5c6cb;
33
+ color: #721c24;
34
+ padding: 0.75rem;
35
+ border-radius: 0.25rem;
36
+ }
37
+ </style>
38
+ """
39
+
40
+
41
+ def apply_dashboard_styles():
42
+ """
43
+ Apply standard dashboard CSS styles.
44
+
45
+ Call this function once in your dashboard to apply the standard styling.
46
+
47
+ Example:
48
+ import streamlit as st
49
+ from mcli.ml.dashboard.styles import apply_dashboard_styles
50
+
51
+ apply_dashboard_styles()
52
+ """
53
+ import streamlit as st
54
+
55
+ st.markdown(DASHBOARD_CSS, unsafe_allow_html=True)