mcli-framework 7.6.0__py3-none-any.whl → 7.6.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 (49) hide show
  1. mcli/app/commands_cmd.py +51 -39
  2. mcli/app/main.py +10 -2
  3. mcli/app/model_cmd.py +1 -1
  4. mcli/lib/custom_commands.py +4 -10
  5. mcli/ml/api/app.py +1 -5
  6. mcli/ml/dashboard/app.py +2 -2
  7. mcli/ml/dashboard/app_integrated.py +168 -116
  8. mcli/ml/dashboard/app_supabase.py +7 -3
  9. mcli/ml/dashboard/app_training.py +3 -6
  10. mcli/ml/dashboard/components/charts.py +74 -115
  11. mcli/ml/dashboard/components/metrics.py +24 -44
  12. mcli/ml/dashboard/components/tables.py +32 -40
  13. mcli/ml/dashboard/overview.py +102 -78
  14. mcli/ml/dashboard/pages/cicd.py +103 -56
  15. mcli/ml/dashboard/pages/debug_dependencies.py +35 -28
  16. mcli/ml/dashboard/pages/gravity_viz.py +374 -313
  17. mcli/ml/dashboard/pages/monte_carlo_predictions.py +50 -48
  18. mcli/ml/dashboard/pages/predictions_enhanced.py +396 -248
  19. mcli/ml/dashboard/pages/scrapers_and_logs.py +299 -273
  20. mcli/ml/dashboard/pages/test_portfolio.py +153 -121
  21. mcli/ml/dashboard/pages/trading.py +238 -169
  22. mcli/ml/dashboard/pages/workflows.py +129 -84
  23. mcli/ml/dashboard/streamlit_extras_utils.py +70 -79
  24. mcli/ml/dashboard/utils.py +24 -21
  25. mcli/ml/dashboard/warning_suppression.py +6 -4
  26. mcli/ml/database/session.py +16 -5
  27. mcli/ml/mlops/pipeline_orchestrator.py +1 -3
  28. mcli/ml/predictions/monte_carlo.py +6 -18
  29. mcli/ml/trading/alpaca_client.py +95 -96
  30. mcli/ml/trading/migrations.py +76 -40
  31. mcli/ml/trading/models.py +78 -60
  32. mcli/ml/trading/paper_trading.py +92 -74
  33. mcli/ml/trading/risk_management.py +106 -85
  34. mcli/ml/trading/trading_service.py +155 -110
  35. mcli/ml/training/train_model.py +1 -3
  36. mcli/self/self_cmd.py +71 -57
  37. mcli/workflow/daemon/daemon.py +2 -0
  38. mcli/workflow/model_service/openai_adapter.py +6 -2
  39. mcli/workflow/politician_trading/models.py +6 -2
  40. mcli/workflow/politician_trading/scrapers_corporate_registry.py +39 -88
  41. mcli/workflow/politician_trading/scrapers_free_sources.py +32 -39
  42. mcli/workflow/politician_trading/scrapers_third_party.py +21 -39
  43. mcli/workflow/politician_trading/seed_database.py +70 -89
  44. {mcli_framework-7.6.0.dist-info → mcli_framework-7.6.2.dist-info}/METADATA +1 -1
  45. {mcli_framework-7.6.0.dist-info → mcli_framework-7.6.2.dist-info}/RECORD +49 -49
  46. {mcli_framework-7.6.0.dist-info → mcli_framework-7.6.2.dist-info}/WHEEL +0 -0
  47. {mcli_framework-7.6.0.dist-info → mcli_framework-7.6.2.dist-info}/entry_points.txt +0 -0
  48. {mcli_framework-7.6.0.dist-info → mcli_framework-7.6.2.dist-info}/licenses/LICENSE +0 -0
  49. {mcli_framework-7.6.0.dist-info → mcli_framework-7.6.2.dist-info}/top_level.txt +0 -0
@@ -3,14 +3,15 @@ Gravity Anomaly Visualization Dashboard
3
3
  Correlates gravitational measurements with politician locations and trading activity
4
4
  """
5
5
 
6
- import streamlit as st
7
- import pandas as pd
8
- import plotly.graph_objects as go
9
- import plotly.express as px
6
+ import os
10
7
  from datetime import datetime, timedelta
11
- from typing import List, Dict, Optional, Tuple
8
+ from typing import Dict, List, Optional, Tuple
9
+
12
10
  import numpy as np
13
- import os
11
+ import pandas as pd
12
+ import plotly.express as px
13
+ import plotly.graph_objects as go
14
+ import streamlit as st
14
15
  from supabase import Client, create_client
15
16
 
16
17
  # Configure page
@@ -18,11 +19,12 @@ st.set_page_config(
18
19
  page_title="Gravity Anomaly Monitor",
19
20
  page_icon="🌍",
20
21
  layout="wide",
21
- initial_sidebar_state="expanded"
22
+ initial_sidebar_state="expanded",
22
23
  )
23
24
 
24
25
  # Custom CSS for better styling
25
- st.markdown("""
26
+ st.markdown(
27
+ """
26
28
  <style>
27
29
  .main-header {
28
30
  font-size: 2.5rem;
@@ -49,7 +51,9 @@ st.markdown("""
49
51
  font-weight: 600;
50
52
  }
51
53
  </style>
52
- """, unsafe_allow_html=True)
54
+ """,
55
+ unsafe_allow_html=True,
56
+ )
53
57
 
54
58
 
55
59
  @st.cache_resource
@@ -105,15 +109,19 @@ class GravityData:
105
109
  for _ in range(num_points)
106
110
  ]
107
111
 
108
- return pd.DataFrame({
109
- 'latitude': lat + lat_offsets,
110
- 'longitude': lon + lon_offsets,
111
- 'gravity_anomaly_mgal': anomalies,
112
- 'absolute_gravity_mgal': base_gravity + anomalies,
113
- 'measurement_time': timestamps,
114
- 'distance_km': distances,
115
- 'quality': np.random.choice(['high', 'medium', 'low'], num_points, p=[0.6, 0.3, 0.1])
116
- })
112
+ return pd.DataFrame(
113
+ {
114
+ "latitude": lat + lat_offsets,
115
+ "longitude": lon + lon_offsets,
116
+ "gravity_anomaly_mgal": anomalies,
117
+ "absolute_gravity_mgal": base_gravity + anomalies,
118
+ "measurement_time": timestamps,
119
+ "distance_km": distances,
120
+ "quality": np.random.choice(
121
+ ["high", "medium", "low"], num_points, p=[0.6, 0.3, 0.1]
122
+ ),
123
+ }
124
+ )
117
125
 
118
126
 
119
127
  class PoliticianLocations:
@@ -122,120 +130,134 @@ class PoliticianLocations:
122
130
  # Approximate coordinates for major cities (used as fallback)
123
131
  STATE_CAPITALS = {
124
132
  # 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),
133
+ "Alabama": (32.3668, -86.3000),
134
+ "California": (38.5816, -121.4944),
135
+ "Texas": (30.2672, -97.7431),
136
+ "New Jersey": (40.2206, -74.7597),
137
+ "Florida": (30.4383, -84.2807),
138
+ "New York": (42.6526, -73.7562),
139
+ "Pennsylvania": (40.2732, -76.8867),
140
+ "Illinois": (39.7817, -89.6501),
141
+ "Ohio": (39.9612, -82.9988),
142
+ "Georgia": (33.7490, -84.3880),
143
+ "Michigan": (42.7325, -84.5555),
144
+ "North Carolina": (35.7796, -78.6382),
145
+ "Virginia": (37.5407, -77.4360),
146
+ "Washington": (47.0379, -122.9007),
147
+ "Massachusetts": (42.3601, -71.0589),
140
148
  # UK
141
- 'United Kingdom': (51.5074, -0.1278), # London
142
- 'UK': (51.5074, -0.1278),
149
+ "United Kingdom": (51.5074, -0.1278), # London
150
+ "UK": (51.5074, -0.1278),
143
151
  # 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
152
+ "France": (48.8566, 2.3522), # Paris
153
+ "Germany": (52.5200, 13.4050), # Berlin
154
+ "Italy": (41.9028, 12.4964), # Rome
155
+ "Spain": (40.4168, -3.7038), # Madrid
156
+ "Poland": (52.2297, 21.0122), # Warsaw
157
+ "Netherlands": (52.3676, 4.9041), # Amsterdam
158
+ "Belgium": (50.8503, 4.3517), # Brussels
159
+ "Sweden": (59.3293, 18.0686), # Stockholm
160
+ "Austria": (48.2082, 16.3738), # Vienna
161
+ "Denmark": (55.6761, 12.5683), # Copenhagen
162
+ "Finland": (60.1699, 24.9384), # Helsinki
155
163
  }
156
164
 
157
165
  # Major city coordinates for district approximations
158
166
  MAJOR_CITIES = {
159
167
  # 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),
168
+ "San Francisco": (37.7749, -122.4194),
169
+ "Los Angeles": (34.0522, -118.2437),
170
+ "San Diego": (32.7157, -117.1611),
171
+ "Sacramento": (38.5816, -121.4944),
172
+ "San Jose": (37.3382, -121.8863),
165
173
  # 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),
174
+ "Houston": (29.7604, -97.7431),
175
+ "Dallas": (32.7767, -96.7970),
176
+ "Austin": (30.2672, -97.7431),
177
+ "San Antonio": (29.4241, -98.4936),
170
178
  # 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),
179
+ "New York City": (40.7128, -74.0060),
180
+ "Buffalo": (42.8864, -78.8784),
181
+ "Rochester": (43.1566, -77.6088),
182
+ "Albany": (42.6526, -73.7562),
175
183
  # 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),
184
+ "Miami": (25.7617, -80.1918),
185
+ "Tampa": (27.9506, -82.4572),
186
+ "Orlando": (28.5383, -81.3792),
187
+ "Jacksonville": (30.3322, -81.6557),
188
+ "Tallahassee": (30.4383, -84.2807),
181
189
  # Pennsylvania
182
- 'Philadelphia': (39.9526, -75.1652),
183
- 'Pittsburgh': (40.4406, -79.9959),
184
- 'Harrisburg': (40.2732, -76.8867),
190
+ "Philadelphia": (39.9526, -75.1652),
191
+ "Pittsburgh": (40.4406, -79.9959),
192
+ "Harrisburg": (40.2732, -76.8867),
185
193
  # Illinois
186
- 'Chicago': (41.8781, -87.6298),
187
- 'Springfield': (39.7817, -89.6501),
194
+ "Chicago": (41.8781, -87.6298),
195
+ "Springfield": (39.7817, -89.6501),
188
196
  # New Jersey
189
- 'Newark': (40.7357, -74.1724),
190
- 'Jersey City': (40.7178, -74.0431),
191
- 'Trenton': (40.2206, -74.7597),
197
+ "Newark": (40.7357, -74.1724),
198
+ "Jersey City": (40.7178, -74.0431),
199
+ "Trenton": (40.2206, -74.7597),
192
200
  # And more major cities as needed...
193
201
  }
194
202
 
195
203
  @staticmethod
196
- def get_location_for_politician(state_or_country: str, district: str, role: str) -> Tuple[float, float]:
204
+ def get_location_for_politician(
205
+ state_or_country: str, district: str, role: str
206
+ ) -> Tuple[float, float]:
197
207
  """
198
208
  Get lat/lon for a politician based on their state/district/role
199
209
  Returns: (latitude, longitude)
200
210
  """
201
211
  # 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']:
212
+ if district and state_or_country in [
213
+ "California",
214
+ "Texas",
215
+ "New York",
216
+ "Florida",
217
+ "Pennsylvania",
218
+ "Illinois",
219
+ "New Jersey",
220
+ ]:
203
221
  # Parse district like "CA-11" or "TX-02"
204
- if '-' in str(district):
205
- district_num = district.split('-')[-1]
222
+ if "-" in str(district):
223
+ district_num = district.split("-")[-1]
206
224
 
207
225
  # District-specific mapping (approximate major city in district)
208
226
  # This is a simplified approach - in production, use actual district boundaries
209
227
  district_locations = {
210
228
  # 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
229
+ "California": {
230
+ "11": (37.7749, -122.4194), # SF Bay Area
231
+ "12": (37.7749, -122.4194), # SF
232
+ "13": (37.3382, -121.8863), # Oakland/East Bay
233
+ "17": (37.3382, -121.8863), # San Jose area
234
+ "28": (34.0522, -118.2437), # LA
235
+ "43": (33.7701, -118.1937), # Long Beach
218
236
  },
219
237
  # 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
238
+ "Texas": {
239
+ "02": (29.7604, -97.7431), # Houston
240
+ "07": (29.7604, -97.7431), # Houston
241
+ "24": (32.7767, -96.7970), # Dallas
242
+ "21": (29.4241, -98.4936), # San Antonio
225
243
  },
226
244
  # 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
245
+ "New York": {
246
+ "12": (40.7128, -74.0060), # NYC Manhattan
247
+ "14": (40.7128, -74.0060), # NYC Bronx/Queens
248
+ "26": (42.8864, -78.8784), # Buffalo
231
249
  },
232
250
  # Add more as needed
233
251
  }
234
252
 
235
- if state_or_country in district_locations and district_num in district_locations[state_or_country]:
253
+ if (
254
+ state_or_country in district_locations
255
+ and district_num in district_locations[state_or_country]
256
+ ):
236
257
  # Add small random offset to prevent exact overlap
237
258
  base_lat, base_lon = district_locations[state_or_country][district_num]
238
259
  import random
260
+
239
261
  offset_lat = random.uniform(-0.1, 0.1)
240
262
  offset_lon = random.uniform(-0.1, 0.1)
241
263
  return (base_lat + offset_lat, base_lon + offset_lon)
@@ -245,6 +267,7 @@ class PoliticianLocations:
245
267
 
246
268
  # Add small random offset to prevent exact overlap for multiple politicians from same location
247
269
  import random
270
+
248
271
  offset_lat = random.uniform(-0.2, 0.2)
249
272
  offset_lon = random.uniform(-0.2, 0.2)
250
273
 
@@ -268,13 +291,21 @@ class PoliticianLocations:
268
291
 
269
292
  # Fetch trading disclosures to calculate volumes
270
293
  disclosures_response = client.table("trading_disclosures").select("*").execute()
271
- disclosures_df = pd.DataFrame(disclosures_response.data) if disclosures_response.data else pd.DataFrame()
294
+ disclosures_df = (
295
+ pd.DataFrame(disclosures_response.data)
296
+ if disclosures_response.data
297
+ else pd.DataFrame()
298
+ )
272
299
 
273
300
  # Calculate trading metrics per politician
274
301
  result_data = []
275
302
  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()
303
+ pol_id = pol.get("id")
304
+ pol_disclosures = (
305
+ disclosures_df[disclosures_df["politician_id"] == pol_id]
306
+ if not disclosures_df.empty
307
+ else pd.DataFrame()
308
+ )
278
309
 
279
310
  # Calculate metrics
280
311
  recent_trades = len(pol_disclosures)
@@ -284,29 +315,31 @@ class PoliticianLocations:
284
315
  if not pol_disclosures.empty:
285
316
  # Estimate volume from range midpoints
286
317
  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
318
+ min_amt = d.get("amount_range_min", 0) or 0
319
+ max_amt = d.get("amount_range_max", 0) or 0
289
320
  if min_amt and max_amt:
290
321
  total_volume += (min_amt + max_amt) / 2
291
- elif d.get('amount_exact'):
292
- total_volume += d['amount_exact']
322
+ elif d.get("amount_exact"):
323
+ total_volume += d["amount_exact"]
293
324
 
294
325
  # Get last trade date
295
- transaction_dates = pd.to_datetime(pol_disclosures['transaction_date'], errors='coerce')
326
+ transaction_dates = pd.to_datetime(
327
+ pol_disclosures["transaction_date"], errors="coerce"
328
+ )
296
329
  last_trade_date = transaction_dates.max()
297
330
 
298
331
  # 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', '')
332
+ state_or_country = pol.get("state_or_country", "")
333
+ district = pol.get("district", "")
334
+ role = pol.get("role", "")
302
335
  lat, lon = PoliticianLocations.get_location_for_politician(
303
336
  state_or_country, district, role
304
337
  )
305
338
 
306
339
  # 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()
340
+ first_name = pol.get("first_name", "").strip()
341
+ last_name = pol.get("last_name", "").strip()
342
+ full_name = pol.get("full_name", "").strip()
310
343
 
311
344
  # Use first_name + last_name if both available, otherwise use full_name
312
345
  if first_name and last_name:
@@ -320,22 +353,26 @@ class PoliticianLocations:
320
353
  else:
321
354
  display_name = f"Politician {pol_id[:8]}" # Fallback to ID
322
355
 
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
- })
356
+ result_data.append(
357
+ {
358
+ "name": display_name,
359
+ "role": pol.get("role", "Unknown"),
360
+ "state": state_or_country,
361
+ "district": pol.get("district"),
362
+ "party": pol.get("party", "Unknown"),
363
+ "lat": lat,
364
+ "lon": lon,
365
+ "recent_trades": recent_trades,
366
+ "total_trade_volume": total_volume,
367
+ "last_trade_date": (
368
+ last_trade_date if pd.notna(last_trade_date) else datetime(2025, 1, 1)
369
+ ),
370
+ }
371
+ )
335
372
 
336
373
  result_df = pd.DataFrame(result_data)
337
374
  # Filter out politicians with no trading data
338
- result_df = result_df[result_df['recent_trades'] > 0]
375
+ result_df = result_df[result_df["recent_trades"] > 0]
339
376
 
340
377
  if result_df.empty:
341
378
  return PoliticianLocations.get_fallback_politicians()
@@ -351,115 +388,118 @@ class PoliticianLocations:
351
388
  """Fallback sample data if database is unavailable"""
352
389
  politicians = [
353
390
  {
354
- 'name': 'Nancy Pelosi',
355
- 'role': 'US House Representative',
356
- 'state': 'California',
357
- 'district': 'CA-11',
358
- 'party': 'Democrat',
359
- 'lat': 37.7749,
360
- 'lon': -122.4194,
361
- 'recent_trades': 15,
362
- 'total_trade_volume': 5_000_000,
363
- 'last_trade_date': datetime(2025, 10, 5),
391
+ "name": "Nancy Pelosi",
392
+ "role": "US House Representative",
393
+ "state": "California",
394
+ "district": "CA-11",
395
+ "party": "Democrat",
396
+ "lat": 37.7749,
397
+ "lon": -122.4194,
398
+ "recent_trades": 15,
399
+ "total_trade_volume": 5_000_000,
400
+ "last_trade_date": datetime(2025, 10, 5),
364
401
  },
365
402
  {
366
- 'name': 'Tommy Tuberville',
367
- 'role': 'US Senator',
368
- 'state': 'Alabama',
369
- 'district': None,
370
- 'party': 'Republican',
371
- 'lat': 32.3668,
372
- 'lon': -86.3000,
373
- 'recent_trades': 23,
374
- 'total_trade_volume': 3_200_000,
375
- 'last_trade_date': datetime(2025, 10, 3),
403
+ "name": "Tommy Tuberville",
404
+ "role": "US Senator",
405
+ "state": "Alabama",
406
+ "district": None,
407
+ "party": "Republican",
408
+ "lat": 32.3668,
409
+ "lon": -86.3000,
410
+ "recent_trades": 23,
411
+ "total_trade_volume": 3_200_000,
412
+ "last_trade_date": datetime(2025, 10, 3),
376
413
  },
377
414
  ]
378
415
  return pd.DataFrame(politicians)
379
416
 
380
417
 
381
- def create_gravity_map(politicians_df: pd.DataFrame, selected_politician: Optional[str] = None) -> go.Figure:
418
+ def create_gravity_map(
419
+ politicians_df: pd.DataFrame, selected_politician: Optional[str] = None
420
+ ) -> go.Figure:
382
421
  """Create interactive map showing politician locations and gravity anomalies"""
383
422
 
384
423
  fig = go.Figure()
385
424
 
386
425
  # Add politician markers
387
426
  for _, pol in politicians_df.iterrows():
388
- is_selected = pol['name'] == selected_politician
427
+ is_selected = pol["name"] == selected_politician
389
428
 
390
- fig.add_trace(go.Scattergeo(
391
- lon=[pol['lon']],
392
- lat=[pol['lat']],
393
- mode='markers+text',
394
- marker=dict(
395
- size=20 if is_selected else 12,
396
- color='red' if is_selected else 'blue',
397
- symbol='star',
398
- line=dict(width=2, color='white')
399
- ),
400
- text=pol['name'],
401
- textposition='top center',
402
- name=pol['name'],
403
- hovertemplate=(
404
- f"<b>{pol['name']}</b><br>"
405
- f"Role: {pol['role']}<br>"
406
- f"State: {pol['state']}<br>"
407
- f"Recent Trades: {pol['recent_trades']}<br>"
408
- f"Trade Volume: ${pol['total_trade_volume']:,.0f}<br>"
409
- "<extra></extra>"
410
- ),
411
- ))
429
+ fig.add_trace(
430
+ go.Scattergeo(
431
+ lon=[pol["lon"]],
432
+ lat=[pol["lat"]],
433
+ mode="markers+text",
434
+ marker=dict(
435
+ size=20 if is_selected else 12,
436
+ color="red" if is_selected else "blue",
437
+ symbol="star",
438
+ line=dict(width=2, color="white"),
439
+ ),
440
+ text=pol["name"],
441
+ textposition="top center",
442
+ name=pol["name"],
443
+ hovertemplate=(
444
+ f"<b>{pol['name']}</b><br>"
445
+ f"Role: {pol['role']}<br>"
446
+ f"State: {pol['state']}<br>"
447
+ f"Recent Trades: {pol['recent_trades']}<br>"
448
+ f"Trade Volume: ${pol['total_trade_volume']:,.0f}<br>"
449
+ "<extra></extra>"
450
+ ),
451
+ )
452
+ )
412
453
 
413
454
  # Add gravity measurement points if politician is selected
414
455
  if is_selected:
415
- gravity_data = GravityData.generate_gravity_anomalies(pol['lat'], pol['lon'])
456
+ gravity_data = GravityData.generate_gravity_anomalies(pol["lat"], pol["lon"])
416
457
 
417
458
  # Color by anomaly strength
418
- colors = gravity_data['gravity_anomaly_mgal']
419
-
420
- fig.add_trace(go.Scattergeo(
421
- lon=gravity_data['longitude'],
422
- lat=gravity_data['latitude'],
423
- mode='markers',
424
- marker=dict(
425
- size=8,
426
- color=colors,
427
- colorscale='RdYlGn_r',
428
- cmin=-50,
429
- cmax=50,
430
- colorbar=dict(
431
- title="Gravity<br>Anomaly<br>(mGal)",
432
- x=1.1
459
+ colors = gravity_data["gravity_anomaly_mgal"]
460
+
461
+ fig.add_trace(
462
+ go.Scattergeo(
463
+ lon=gravity_data["longitude"],
464
+ lat=gravity_data["latitude"],
465
+ mode="markers",
466
+ marker=dict(
467
+ size=8,
468
+ color=colors,
469
+ colorscale="RdYlGn_r",
470
+ cmin=-50,
471
+ cmax=50,
472
+ colorbar=dict(title="Gravity<br>Anomaly<br>(mGal)", x=1.1),
473
+ showscale=True,
433
474
  ),
434
- showscale=True,
435
- ),
436
- name='Gravity Measurements',
437
- hovertemplate=(
438
- "Anomaly: %{marker.color:.2f} mGal<br>"
439
- "Distance: %{customdata[0]:.1f} km<br>"
440
- "Time: %{customdata[1]}<br>"
441
- "<extra></extra>"
442
- ),
443
- customdata=gravity_data[['distance_km', 'measurement_time']].values
444
- ))
475
+ name="Gravity Measurements",
476
+ hovertemplate=(
477
+ "Anomaly: %{marker.color:.2f} mGal<br>"
478
+ "Distance: %{customdata[0]:.1f} km<br>"
479
+ "Time: %{customdata[1]}<br>"
480
+ "<extra></extra>"
481
+ ),
482
+ customdata=gravity_data[["distance_km", "measurement_time"]].values,
483
+ )
484
+ )
445
485
 
446
486
  # Update layout
447
487
  fig.update_geos(
448
- projection_type='natural earth',
488
+ projection_type="natural earth",
449
489
  showcountries=True,
450
- countrycolor='lightgray',
490
+ countrycolor="lightgray",
451
491
  showland=True,
452
- landcolor='white',
492
+ landcolor="white",
453
493
  showocean=True,
454
- oceancolor='lightblue',
494
+ oceancolor="lightblue",
455
495
  coastlinewidth=1,
456
496
  )
457
497
 
458
498
  fig.update_layout(
459
- title='Politician Locations & Gravity Anomalies',
499
+ title="Politician Locations & Gravity Anomalies",
460
500
  height=600,
461
501
  showlegend=False,
462
- margin=dict(l=0, r=0, t=40, b=0)
502
+ margin=dict(l=0, r=0, t=40, b=0),
463
503
  )
464
504
 
465
505
  return fig
@@ -468,29 +508,28 @@ def create_gravity_map(politicians_df: pd.DataFrame, selected_politician: Option
468
508
  def create_gravity_heatmap(gravity_df: pd.DataFrame) -> go.Figure:
469
509
  """Create heatmap of gravity measurements over time"""
470
510
 
471
- fig = go.Figure(data=go.Densitymapbox(
472
- lat=gravity_df['latitude'],
473
- lon=gravity_df['longitude'],
474
- z=gravity_df['gravity_anomaly_mgal'],
475
- radius=20,
476
- colorscale='RdYlGn_r',
477
- zmin=-50,
478
- zmax=50,
479
- hovertemplate='Anomaly: %{z:.2f} mGal<extra></extra>',
480
- ))
511
+ fig = go.Figure(
512
+ data=go.Densitymapbox(
513
+ lat=gravity_df["latitude"],
514
+ lon=gravity_df["longitude"],
515
+ z=gravity_df["gravity_anomaly_mgal"],
516
+ radius=20,
517
+ colorscale="RdYlGn_r",
518
+ zmin=-50,
519
+ zmax=50,
520
+ hovertemplate="Anomaly: %{z:.2f} mGal<extra></extra>",
521
+ )
522
+ )
481
523
 
482
524
  # Calculate center point
483
- center_lat = gravity_df['latitude'].mean()
484
- center_lon = gravity_df['longitude'].mean()
525
+ center_lat = gravity_df["latitude"].mean()
526
+ center_lon = gravity_df["longitude"].mean()
485
527
 
486
528
  fig.update_layout(
487
529
  mapbox_style="open-street-map",
488
- mapbox=dict(
489
- center=dict(lat=center_lat, lon=center_lon),
490
- zoom=8
491
- ),
530
+ mapbox=dict(center=dict(lat=center_lat, lon=center_lon), zoom=8),
492
531
  margin=dict(l=0, r=0, t=0, b=0),
493
- height=400
532
+ height=400,
494
533
  )
495
534
 
496
535
  return fig
@@ -501,38 +540,43 @@ def create_correlation_chart(politician_df: pd.DataFrame, gravity_df: pd.DataFra
501
540
 
502
541
  # Aggregate gravity data by time period
503
542
  gravity_stats = {
504
- 'max_anomaly': gravity_df['gravity_anomaly_mgal'].max(),
505
- 'mean_anomaly': gravity_df['gravity_anomaly_mgal'].mean(),
506
- 'std_anomaly': gravity_df['gravity_anomaly_mgal'].std(),
507
- 'num_measurements': len(gravity_df),
543
+ "max_anomaly": gravity_df["gravity_anomaly_mgal"].max(),
544
+ "mean_anomaly": gravity_df["gravity_anomaly_mgal"].mean(),
545
+ "std_anomaly": gravity_df["gravity_anomaly_mgal"].std(),
546
+ "num_measurements": len(gravity_df),
508
547
  }
509
548
 
510
549
  fig = go.Figure()
511
550
 
512
- fig.add_trace(go.Bar(
513
- x=['Max Anomaly', 'Mean Anomaly', 'Std Dev', 'Measurements'],
514
- y=[
515
- gravity_stats['max_anomaly'],
516
- gravity_stats['mean_anomaly'],
517
- gravity_stats['std_anomaly'],
518
- gravity_stats['num_measurements'] / 10 # Scale for visibility
519
- ],
520
- marker_color=['red', 'orange', 'yellow', 'green'],
521
- text=[f"{v:.2f}" for v in [
522
- gravity_stats['max_anomaly'],
523
- gravity_stats['mean_anomaly'],
524
- gravity_stats['std_anomaly'],
525
- gravity_stats['num_measurements'] / 10
526
- ]],
527
- textposition='auto',
528
- ))
551
+ fig.add_trace(
552
+ go.Bar(
553
+ x=["Max Anomaly", "Mean Anomaly", "Std Dev", "Measurements"],
554
+ y=[
555
+ gravity_stats["max_anomaly"],
556
+ gravity_stats["mean_anomaly"],
557
+ gravity_stats["std_anomaly"],
558
+ gravity_stats["num_measurements"] / 10, # Scale for visibility
559
+ ],
560
+ marker_color=["red", "orange", "yellow", "green"],
561
+ text=[
562
+ f"{v:.2f}"
563
+ for v in [
564
+ gravity_stats["max_anomaly"],
565
+ gravity_stats["mean_anomaly"],
566
+ gravity_stats["std_anomaly"],
567
+ gravity_stats["num_measurements"] / 10,
568
+ ]
569
+ ],
570
+ textposition="auto",
571
+ )
572
+ )
529
573
 
530
574
  fig.update_layout(
531
- title='Gravity Anomaly Statistics',
532
- xaxis_title='Metric',
533
- yaxis_title='Value',
575
+ title="Gravity Anomaly Statistics",
576
+ xaxis_title="Metric",
577
+ yaxis_title="Value",
534
578
  height=300,
535
- showlegend=False
579
+ showlegend=False,
536
580
  )
537
581
 
538
582
  return fig
@@ -542,40 +586,38 @@ def create_timeline_chart(gravity_df: pd.DataFrame, politician_name: str) -> go.
542
586
  """Create timeline showing gravity measurements over time"""
543
587
 
544
588
  # Sort by time
545
- gravity_df = gravity_df.sort_values('measurement_time')
589
+ gravity_df = gravity_df.sort_values("measurement_time")
546
590
 
547
591
  fig = go.Figure()
548
592
 
549
- fig.add_trace(go.Scatter(
550
- x=gravity_df['measurement_time'],
551
- y=gravity_df['gravity_anomaly_mgal'],
552
- mode='markers+lines',
553
- marker=dict(
554
- size=8,
555
- color=gravity_df['gravity_anomaly_mgal'],
556
- colorscale='RdYlGn_r',
557
- showscale=False,
558
- line=dict(width=1, color='white')
559
- ),
560
- line=dict(width=1, color='gray', dash='dot'),
561
- name='Gravity Anomaly',
562
- hovertemplate=(
563
- 'Time: %{x}<br>'
564
- 'Anomaly: %{y:.2f} mGal<br>'
565
- '<extra></extra>'
593
+ fig.add_trace(
594
+ go.Scatter(
595
+ x=gravity_df["measurement_time"],
596
+ y=gravity_df["gravity_anomaly_mgal"],
597
+ mode="markers+lines",
598
+ marker=dict(
599
+ size=8,
600
+ color=gravity_df["gravity_anomaly_mgal"],
601
+ colorscale="RdYlGn_r",
602
+ showscale=False,
603
+ line=dict(width=1, color="white"),
604
+ ),
605
+ line=dict(width=1, color="gray", dash="dot"),
606
+ name="Gravity Anomaly",
607
+ hovertemplate=("Time: %{x}<br>" "Anomaly: %{y:.2f} mGal<br>" "<extra></extra>"),
566
608
  )
567
- ))
609
+ )
568
610
 
569
611
  # Add zero line
570
612
  fig.add_hline(y=0, line_dash="dash", line_color="gray", opacity=0.5)
571
613
 
572
614
  fig.update_layout(
573
- title=f'Gravity Measurements Over Time - {politician_name}',
574
- xaxis_title='Time',
575
- yaxis_title='Gravity Anomaly (mGal)',
615
+ title=f"Gravity Measurements Over Time - {politician_name}",
616
+ xaxis_title="Time",
617
+ yaxis_title="Gravity Anomaly (mGal)",
576
618
  height=350,
577
619
  showlegend=False,
578
- hovermode='closest'
620
+ hovermode="closest",
579
621
  )
580
622
 
581
623
  return fig
@@ -586,10 +628,12 @@ def main():
586
628
 
587
629
  # Header
588
630
  st.markdown('<h1 class="main-header">🌍 Gravity Anomaly Monitor</h1>', unsafe_allow_html=True)
589
- st.markdown("""
631
+ st.markdown(
632
+ """
590
633
  Monitor gravitational anomalies near politician locations and correlate with trading activity.
591
634
  Data sources: GRACE satellites, ground-based gravimeters, and geological surveys.
592
- """)
635
+ """
636
+ )
593
637
 
594
638
  # Sidebar
595
639
  st.sidebar.header("⚙️ Configuration")
@@ -602,9 +646,7 @@ def main():
602
646
 
603
647
  # Politician selection
604
648
  selected_politician = st.sidebar.selectbox(
605
- "Select Politician",
606
- options=['All'] + politicians_df['name'].tolist(),
607
- index=0
649
+ "Select Politician", options=["All"] + politicians_df["name"].tolist(), index=0
608
650
  )
609
651
 
610
652
  # Filters
@@ -615,7 +657,7 @@ def main():
615
657
  min_value=1,
616
658
  max_value=30,
617
659
  value=7,
618
- help="Number of days of historical data to display"
660
+ help="Number of days of historical data to display",
619
661
  )
620
662
 
621
663
  min_trade_volume = st.sidebar.number_input(
@@ -625,24 +667,24 @@ def main():
625
667
  value=0, # Changed from 1,000,000 to 0 to show all politicians by default
626
668
  step=100_000,
627
669
  format="%d",
628
- help="Filter politicians by minimum trade volume. Set to 0 to see all."
670
+ help="Filter politicians by minimum trade volume. Set to 0 to see all.",
629
671
  )
630
672
 
631
673
  # Filter politicians by trade volume
632
- filtered_politicians = politicians_df[
633
- politicians_df['total_trade_volume'] >= min_trade_volume
634
- ]
674
+ filtered_politicians = politicians_df[politicians_df["total_trade_volume"] >= min_trade_volume]
635
675
 
636
676
  # Show filter results
637
677
  if len(filtered_politicians) < len(politicians_df):
638
- st.sidebar.warning(f"⚠️ Filter reduced to {len(filtered_politicians)} politicians (from {len(politicians_df)})")
678
+ st.sidebar.warning(
679
+ f"⚠️ Filter reduced to {len(filtered_politicians)} politicians (from {len(politicians_df)})"
680
+ )
639
681
  else:
640
682
  st.sidebar.success(f"✅ Showing all {len(filtered_politicians)} politicians")
641
683
 
642
684
  # Main content
643
- if selected_politician != 'All':
685
+ if selected_politician != "All":
644
686
  # Single politician view
645
- pol = politicians_df[politicians_df['name'] == selected_politician].iloc[0]
687
+ pol = politicians_df[politicians_df["name"] == selected_politician].iloc[0]
646
688
 
647
689
  # Metrics row
648
690
  col1, col2, col3, col4 = st.columns(4)
@@ -650,32 +692,32 @@ def main():
650
692
  with col1:
651
693
  st.metric(
652
694
  label="Recent Trades",
653
- value=pol['recent_trades'],
654
- delta=f"Last: {(datetime.now() - pol['last_trade_date']).days}d ago"
695
+ value=pol["recent_trades"],
696
+ delta=f"Last: {(datetime.now() - pol['last_trade_date']).days}d ago",
655
697
  )
656
698
 
657
699
  with col2:
658
- st.metric(
659
- label="Trade Volume",
660
- value=f"${pol['total_trade_volume']:,.0f}",
661
- delta=None
662
- )
700
+ st.metric(label="Trade Volume", value=f"${pol['total_trade_volume']:,.0f}", delta=None)
663
701
 
664
702
  with col3:
665
- gravity_data = GravityData.generate_gravity_anomalies(pol['lat'], pol['lon'])
666
- max_anomaly = gravity_data['gravity_anomaly_mgal'].max()
667
- alert_class = 'alert-high' if max_anomaly > 40 else 'alert-medium' if max_anomaly > 20 else 'alert-low'
703
+ gravity_data = GravityData.generate_gravity_anomalies(pol["lat"], pol["lon"])
704
+ max_anomaly = gravity_data["gravity_anomaly_mgal"].max()
705
+ alert_class = (
706
+ "alert-high"
707
+ if max_anomaly > 40
708
+ else "alert-medium" if max_anomaly > 20 else "alert-low"
709
+ )
668
710
  st.metric(
669
711
  label="Max Gravity Anomaly",
670
712
  value=f"{max_anomaly:.2f} mGal",
671
- help="Unusually high anomalies may indicate geological features or data quality issues"
713
+ help="Unusually high anomalies may indicate geological features or data quality issues",
672
714
  )
673
715
 
674
716
  with col4:
675
717
  st.metric(
676
718
  label="Measurements",
677
719
  value=len(gravity_data),
678
- delta=f"{len(gravity_data[gravity_data['quality'] == 'high'])} high quality"
720
+ delta=f"{len(gravity_data[gravity_data['quality'] == 'high'])} high quality",
679
721
  )
680
722
 
681
723
  # Tabs for different visualizations
@@ -685,38 +727,46 @@ def main():
685
727
  st.plotly_chart(
686
728
  create_gravity_map(filtered_politicians, selected_politician),
687
729
  config={"displayModeBar": True},
688
- use_container_width=True
730
+ use_container_width=True,
689
731
  )
690
732
 
691
733
  with tab2:
692
734
  st.plotly_chart(
693
735
  create_gravity_heatmap(gravity_data),
694
736
  config={"displayModeBar": True},
695
- use_container_width=True
737
+ use_container_width=True,
696
738
  )
697
739
 
698
740
  # Data table
699
741
  st.subheader("Measurement Data")
700
742
  st.dataframe(
701
- gravity_data[[
702
- 'latitude', 'longitude', 'gravity_anomaly_mgal',
703
- 'distance_km', 'quality', 'measurement_time'
704
- ]].sort_values('gravity_anomaly_mgal', ascending=False).head(10),
705
- use_container_width=True
743
+ gravity_data[
744
+ [
745
+ "latitude",
746
+ "longitude",
747
+ "gravity_anomaly_mgal",
748
+ "distance_km",
749
+ "quality",
750
+ "measurement_time",
751
+ ]
752
+ ]
753
+ .sort_values("gravity_anomaly_mgal", ascending=False)
754
+ .head(10),
755
+ use_container_width=True,
706
756
  )
707
757
 
708
758
  with tab3:
709
759
  st.plotly_chart(
710
760
  create_timeline_chart(gravity_data, selected_politician),
711
761
  config={"displayModeBar": True},
712
- use_container_width=True
762
+ use_container_width=True,
713
763
  )
714
764
 
715
765
  with tab4:
716
766
  st.plotly_chart(
717
767
  create_correlation_chart(filtered_politicians, gravity_data),
718
768
  config={"displayModeBar": True},
719
- use_container_width=True
769
+ use_container_width=True,
720
770
  )
721
771
 
722
772
  # Additional stats
@@ -724,14 +774,16 @@ def main():
724
774
 
725
775
  with col1:
726
776
  st.subheader("Gravity Statistics")
727
- st.write(f"**Mean Anomaly:** {gravity_data['gravity_anomaly_mgal'].mean():.2f} mGal")
777
+ st.write(
778
+ f"**Mean Anomaly:** {gravity_data['gravity_anomaly_mgal'].mean():.2f} mGal"
779
+ )
728
780
  st.write(f"**Std Dev:** {gravity_data['gravity_anomaly_mgal'].std():.2f} mGal")
729
781
  st.write(f"**Min:** {gravity_data['gravity_anomaly_mgal'].min():.2f} mGal")
730
782
  st.write(f"**Max:** {gravity_data['gravity_anomaly_mgal'].max():.2f} mGal")
731
783
 
732
784
  with col2:
733
785
  st.subheader("Data Quality")
734
- quality_counts = gravity_data['quality'].value_counts()
786
+ quality_counts = gravity_data["quality"].value_counts()
735
787
  st.bar_chart(quality_counts)
736
788
 
737
789
  else:
@@ -741,42 +793,51 @@ def main():
741
793
  st.plotly_chart(
742
794
  create_gravity_map(filtered_politicians),
743
795
  config={"displayModeBar": True},
744
- use_container_width=True
796
+ use_container_width=True,
745
797
  )
746
798
 
747
799
  # Summary table
748
800
  st.subheader("Trading Activity Summary")
749
- summary_df = filtered_politicians[[
750
- 'name', 'role', 'state', 'party',
751
- 'recent_trades', 'total_trade_volume', 'last_trade_date'
752
- ]].sort_values('total_trade_volume', ascending=False)
801
+ summary_df = filtered_politicians[
802
+ [
803
+ "name",
804
+ "role",
805
+ "state",
806
+ "party",
807
+ "recent_trades",
808
+ "total_trade_volume",
809
+ "last_trade_date",
810
+ ]
811
+ ].sort_values("total_trade_volume", ascending=False)
753
812
 
754
813
  st.dataframe(summary_df, use_container_width=True)
755
814
 
756
815
  # Trading volume chart
757
816
  st.subheader("Trade Volume Comparison")
758
817
  fig = px.bar(
759
- filtered_politicians.sort_values('total_trade_volume', ascending=True),
760
- x='total_trade_volume',
761
- y='name',
762
- orientation='h',
763
- color='party',
764
- color_discrete_map={'Democrat': 'blue', 'Republican': 'red'},
765
- labels={'total_trade_volume': 'Total Trade Volume ($)', 'name': 'Politician'},
766
- title='Trade Volume by Politician'
818
+ filtered_politicians.sort_values("total_trade_volume", ascending=True),
819
+ x="total_trade_volume",
820
+ y="name",
821
+ orientation="h",
822
+ color="party",
823
+ color_discrete_map={"Democrat": "blue", "Republican": "red"},
824
+ labels={"total_trade_volume": "Total Trade Volume ($)", "name": "Politician"},
825
+ title="Trade Volume by Politician",
767
826
  )
768
827
  st.plotly_chart(fig, config={"displayModeBar": True}, use_container_width=True)
769
828
 
770
829
  # Footer
771
830
  st.markdown("---")
772
- st.markdown("""
831
+ st.markdown(
832
+ """
773
833
  **Data Sources:**
774
834
  - Gravity: GRACE satellites, ground gravimeters, geological surveys
775
835
  - Trading: mcli politician trading database
776
836
  - Locations: Official government records
777
837
 
778
838
  **Note:** This is a demonstration. In production, integrate with real-time data sources.
779
- """)
839
+ """
840
+ )
780
841
 
781
842
 
782
843
  if __name__ == "__main__":