mcli-framework 7.5.1__py3-none-any.whl → 7.6.1__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/commands_cmd.py +51 -39
- mcli/app/completion_helpers.py +4 -13
- mcli/app/main.py +21 -25
- mcli/app/model_cmd.py +119 -9
- mcli/lib/custom_commands.py +16 -11
- mcli/ml/api/app.py +1 -5
- mcli/ml/dashboard/app.py +2 -2
- mcli/ml/dashboard/app_integrated.py +168 -116
- mcli/ml/dashboard/app_supabase.py +7 -3
- mcli/ml/dashboard/app_training.py +3 -6
- mcli/ml/dashboard/components/charts.py +74 -115
- mcli/ml/dashboard/components/metrics.py +24 -44
- mcli/ml/dashboard/components/tables.py +32 -40
- mcli/ml/dashboard/overview.py +102 -78
- mcli/ml/dashboard/pages/cicd.py +103 -56
- mcli/ml/dashboard/pages/debug_dependencies.py +35 -28
- mcli/ml/dashboard/pages/gravity_viz.py +374 -313
- mcli/ml/dashboard/pages/monte_carlo_predictions.py +50 -48
- mcli/ml/dashboard/pages/predictions_enhanced.py +396 -248
- mcli/ml/dashboard/pages/scrapers_and_logs.py +299 -273
- mcli/ml/dashboard/pages/test_portfolio.py +153 -121
- mcli/ml/dashboard/pages/trading.py +238 -169
- mcli/ml/dashboard/pages/workflows.py +129 -84
- mcli/ml/dashboard/streamlit_extras_utils.py +70 -79
- mcli/ml/dashboard/utils.py +24 -21
- mcli/ml/dashboard/warning_suppression.py +6 -4
- mcli/ml/database/session.py +16 -5
- mcli/ml/mlops/pipeline_orchestrator.py +1 -3
- mcli/ml/predictions/monte_carlo.py +6 -18
- mcli/ml/trading/alpaca_client.py +95 -96
- mcli/ml/trading/migrations.py +76 -40
- mcli/ml/trading/models.py +78 -60
- mcli/ml/trading/paper_trading.py +92 -74
- mcli/ml/trading/risk_management.py +106 -85
- mcli/ml/trading/trading_service.py +155 -110
- mcli/ml/training/train_model.py +1 -3
- mcli/{app → self}/completion_cmd.py +6 -6
- mcli/self/self_cmd.py +100 -57
- mcli/test/test_cmd.py +30 -0
- mcli/workflow/daemon/daemon.py +2 -0
- mcli/workflow/model_service/openai_adapter.py +347 -0
- mcli/workflow/politician_trading/models.py +6 -2
- mcli/workflow/politician_trading/scrapers_corporate_registry.py +39 -88
- mcli/workflow/politician_trading/scrapers_free_sources.py +32 -39
- mcli/workflow/politician_trading/scrapers_third_party.py +21 -39
- mcli/workflow/politician_trading/seed_database.py +70 -89
- {mcli_framework-7.5.1.dist-info → mcli_framework-7.6.1.dist-info}/METADATA +1 -1
- {mcli_framework-7.5.1.dist-info → mcli_framework-7.6.1.dist-info}/RECORD +56 -54
- /mcli/{app → self}/logs_cmd.py +0 -0
- /mcli/{app → self}/redis_cmd.py +0 -0
- /mcli/{app → self}/visual_cmd.py +0 -0
- /mcli/{app → test}/cron_test_cmd.py +0 -0
- {mcli_framework-7.5.1.dist-info → mcli_framework-7.6.1.dist-info}/WHEEL +0 -0
- {mcli_framework-7.5.1.dist-info → mcli_framework-7.6.1.dist-info}/entry_points.txt +0 -0
- {mcli_framework-7.5.1.dist-info → mcli_framework-7.6.1.dist-info}/licenses/LICENSE +0 -0
- {mcli_framework-7.5.1.dist-info → mcli_framework-7.6.1.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
|
|
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
|
|
8
|
+
from typing import Dict, List, Optional, Tuple
|
|
9
|
+
|
|
12
10
|
import numpy as np
|
|
13
|
-
import
|
|
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
|
-
""",
|
|
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
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
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
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
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
|
-
|
|
142
|
-
|
|
149
|
+
"United Kingdom": (51.5074, -0.1278), # London
|
|
150
|
+
"UK": (51.5074, -0.1278),
|
|
143
151
|
# EU Countries (capitals)
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
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
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
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
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
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
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
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
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
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
|
-
|
|
183
|
-
|
|
184
|
-
|
|
190
|
+
"Philadelphia": (39.9526, -75.1652),
|
|
191
|
+
"Pittsburgh": (40.4406, -79.9959),
|
|
192
|
+
"Harrisburg": (40.2732, -76.8867),
|
|
185
193
|
# Illinois
|
|
186
|
-
|
|
187
|
-
|
|
194
|
+
"Chicago": (41.8781, -87.6298),
|
|
195
|
+
"Springfield": (39.7817, -89.6501),
|
|
188
196
|
# New Jersey
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
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(
|
|
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 [
|
|
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
|
|
205
|
-
district_num = district.split(
|
|
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
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
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
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
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
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
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
|
|
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 =
|
|
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(
|
|
277
|
-
pol_disclosures =
|
|
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(
|
|
288
|
-
max_amt = d.get(
|
|
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(
|
|
292
|
-
total_volume += d[
|
|
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(
|
|
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(
|
|
300
|
-
district = pol.get(
|
|
301
|
-
role = pol.get(
|
|
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(
|
|
308
|
-
last_name = pol.get(
|
|
309
|
-
full_name = pol.get(
|
|
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
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
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[
|
|
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
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
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
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
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(
|
|
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[
|
|
427
|
+
is_selected = pol["name"] == selected_politician
|
|
389
428
|
|
|
390
|
-
fig.add_trace(
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
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[
|
|
456
|
+
gravity_data = GravityData.generate_gravity_anomalies(pol["lat"], pol["lon"])
|
|
416
457
|
|
|
417
458
|
# Color by anomaly strength
|
|
418
|
-
colors = gravity_data[
|
|
419
|
-
|
|
420
|
-
fig.add_trace(
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
title="Gravity<br>Anomaly<br>(mGal)",
|
|
432
|
-
|
|
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
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
"
|
|
442
|
-
)
|
|
443
|
-
|
|
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=
|
|
488
|
+
projection_type="natural earth",
|
|
449
489
|
showcountries=True,
|
|
450
|
-
countrycolor=
|
|
490
|
+
countrycolor="lightgray",
|
|
451
491
|
showland=True,
|
|
452
|
-
landcolor=
|
|
492
|
+
landcolor="white",
|
|
453
493
|
showocean=True,
|
|
454
|
-
oceancolor=
|
|
494
|
+
oceancolor="lightblue",
|
|
455
495
|
coastlinewidth=1,
|
|
456
496
|
)
|
|
457
497
|
|
|
458
498
|
fig.update_layout(
|
|
459
|
-
title=
|
|
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(
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
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[
|
|
484
|
-
center_lon = gravity_df[
|
|
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
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
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(
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
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=
|
|
532
|
-
xaxis_title=
|
|
533
|
-
yaxis_title=
|
|
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(
|
|
589
|
+
gravity_df = gravity_df.sort_values("measurement_time")
|
|
546
590
|
|
|
547
591
|
fig = go.Figure()
|
|
548
592
|
|
|
549
|
-
fig.add_trace(
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
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
|
|
574
|
-
xaxis_title=
|
|
575
|
-
yaxis_title=
|
|
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=
|
|
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(
|
|
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 !=
|
|
685
|
+
if selected_politician != "All":
|
|
644
686
|
# Single politician view
|
|
645
|
-
pol = politicians_df[politicians_df[
|
|
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[
|
|
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[
|
|
666
|
-
max_anomaly = gravity_data[
|
|
667
|
-
alert_class =
|
|
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
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
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(
|
|
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[
|
|
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
|
-
|
|
751
|
-
|
|
752
|
-
|
|
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(
|
|
760
|
-
x=
|
|
761
|
-
y=
|
|
762
|
-
orientation=
|
|
763
|
-
color=
|
|
764
|
-
color_discrete_map={
|
|
765
|
-
labels={
|
|
766
|
-
title=
|
|
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__":
|