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.
- mcli/app/commands_cmd.py +51 -39
- mcli/app/main.py +10 -2
- mcli/app/model_cmd.py +1 -1
- mcli/lib/custom_commands.py +4 -10
- 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/self/self_cmd.py +71 -57
- mcli/workflow/daemon/daemon.py +2 -0
- mcli/workflow/model_service/openai_adapter.py +6 -2
- 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.6.0.dist-info → mcli_framework-7.6.2.dist-info}/METADATA +1 -1
- {mcli_framework-7.6.0.dist-info → mcli_framework-7.6.2.dist-info}/RECORD +49 -49
- {mcli_framework-7.6.0.dist-info → mcli_framework-7.6.2.dist-info}/WHEEL +0 -0
- {mcli_framework-7.6.0.dist-info → mcli_framework-7.6.2.dist-info}/entry_points.txt +0 -0
- {mcli_framework-7.6.0.dist-info → mcli_framework-7.6.2.dist-info}/licenses/LICENSE +0 -0
- {mcli_framework-7.6.0.dist-info → mcli_framework-7.6.2.dist-info}/top_level.txt +0 -0
|
@@ -7,17 +7,16 @@ This module contains scrapers for free, publicly available politician trading da
|
|
|
7
7
|
- SEC Edgar Insider Trading API
|
|
8
8
|
"""
|
|
9
9
|
|
|
10
|
+
import logging
|
|
10
11
|
import os
|
|
11
12
|
import time
|
|
12
13
|
from datetime import datetime, timedelta
|
|
13
14
|
from typing import Dict, List, Optional
|
|
14
|
-
import logging
|
|
15
15
|
|
|
16
16
|
import requests
|
|
17
17
|
|
|
18
18
|
from .models import Politician, TradingDisclosure
|
|
19
19
|
|
|
20
|
-
|
|
21
20
|
logger = logging.getLogger(__name__)
|
|
22
21
|
|
|
23
22
|
|
|
@@ -36,9 +35,7 @@ class SenateStockWatcherScraper:
|
|
|
36
35
|
|
|
37
36
|
def __init__(self):
|
|
38
37
|
self.session = requests.Session()
|
|
39
|
-
self.session.headers.update({
|
|
40
|
-
"User-Agent": "PoliticianTradingTracker/1.0"
|
|
41
|
-
})
|
|
38
|
+
self.session.headers.update({"User-Agent": "PoliticianTradingTracker/1.0"})
|
|
42
39
|
|
|
43
40
|
def fetch_all_transactions(self) -> List[Dict]:
|
|
44
41
|
"""
|
|
@@ -156,9 +153,7 @@ class SenateStockWatcherScraper:
|
|
|
156
153
|
return politicians
|
|
157
154
|
|
|
158
155
|
def convert_to_disclosures(
|
|
159
|
-
self,
|
|
160
|
-
transactions: List[Dict],
|
|
161
|
-
politician_lookup: Optional[Dict[str, str]] = None
|
|
156
|
+
self, transactions: List[Dict], politician_lookup: Optional[Dict[str, str]] = None
|
|
162
157
|
) -> List[TradingDisclosure]:
|
|
163
158
|
"""
|
|
164
159
|
Convert transaction data to TradingDisclosure objects
|
|
@@ -187,7 +182,11 @@ class SenateStockWatcherScraper:
|
|
|
187
182
|
transaction_date = datetime.now()
|
|
188
183
|
|
|
189
184
|
try:
|
|
190
|
-
disclosure_date =
|
|
185
|
+
disclosure_date = (
|
|
186
|
+
datetime.strptime(disclosure_date_str, "%m/%d/%Y")
|
|
187
|
+
if disclosure_date_str
|
|
188
|
+
else transaction_date
|
|
189
|
+
)
|
|
191
190
|
except ValueError:
|
|
192
191
|
disclosure_date = transaction_date
|
|
193
192
|
|
|
@@ -285,10 +284,7 @@ class FinnhubCongressionalAPI:
|
|
|
285
284
|
self.session = requests.Session()
|
|
286
285
|
|
|
287
286
|
def get_congressional_trading(
|
|
288
|
-
self,
|
|
289
|
-
symbol: str,
|
|
290
|
-
from_date: Optional[str] = None,
|
|
291
|
-
to_date: Optional[str] = None
|
|
287
|
+
self, symbol: str, from_date: Optional[str] = None, to_date: Optional[str] = None
|
|
292
288
|
) -> List[Dict]:
|
|
293
289
|
"""
|
|
294
290
|
Get congressional trading for a specific stock symbol
|
|
@@ -303,10 +299,7 @@ class FinnhubCongressionalAPI:
|
|
|
303
299
|
"""
|
|
304
300
|
try:
|
|
305
301
|
url = f"{self.BASE_URL}/stock/congressional-trading"
|
|
306
|
-
params = {
|
|
307
|
-
"symbol": symbol,
|
|
308
|
-
"token": self.api_key
|
|
309
|
-
}
|
|
302
|
+
params = {"symbol": symbol, "token": self.api_key}
|
|
310
303
|
|
|
311
304
|
if from_date:
|
|
312
305
|
params["from"] = from_date
|
|
@@ -349,11 +342,13 @@ class SECEdgarInsiderAPI:
|
|
|
349
342
|
def __init__(self):
|
|
350
343
|
self.session = requests.Session()
|
|
351
344
|
# SEC requires a User-Agent header
|
|
352
|
-
self.session.headers.update(
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
345
|
+
self.session.headers.update(
|
|
346
|
+
{
|
|
347
|
+
"User-Agent": "PoliticianTradingTracker/1.0 (contact@example.com)",
|
|
348
|
+
"Accept-Encoding": "gzip, deflate",
|
|
349
|
+
"Host": "data.sec.gov",
|
|
350
|
+
}
|
|
351
|
+
)
|
|
357
352
|
|
|
358
353
|
def get_company_submissions(self, cik: str) -> Dict:
|
|
359
354
|
"""
|
|
@@ -419,12 +414,18 @@ class SECEdgarInsiderAPI:
|
|
|
419
414
|
|
|
420
415
|
for i, form in enumerate(forms):
|
|
421
416
|
if form == "4": # Form 4 is insider transaction report
|
|
422
|
-
form4_transactions.append(
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
417
|
+
form4_transactions.append(
|
|
418
|
+
{
|
|
419
|
+
"accessionNumber": (
|
|
420
|
+
accession_numbers[i] if i < len(accession_numbers) else None
|
|
421
|
+
),
|
|
422
|
+
"filingDate": filing_dates[i] if i < len(filing_dates) else None,
|
|
423
|
+
"primaryDocument": (
|
|
424
|
+
primary_documents[i] if i < len(primary_documents) else None
|
|
425
|
+
),
|
|
426
|
+
"cik": cik,
|
|
427
|
+
}
|
|
428
|
+
)
|
|
428
429
|
|
|
429
430
|
logger.info(f"Found {len(form4_transactions)} Form 4 filings for CIK {cik}")
|
|
430
431
|
return form4_transactions
|
|
@@ -440,10 +441,7 @@ class FreeDataFetcher:
|
|
|
440
441
|
Unified interface for fetching politician trading data from free sources
|
|
441
442
|
"""
|
|
442
443
|
|
|
443
|
-
def __init__(
|
|
444
|
-
self,
|
|
445
|
-
finnhub_api_key: Optional[str] = None
|
|
446
|
-
):
|
|
444
|
+
def __init__(self, finnhub_api_key: Optional[str] = None):
|
|
447
445
|
"""
|
|
448
446
|
Initialize fetcher with optional API keys
|
|
449
447
|
|
|
@@ -461,9 +459,7 @@ class FreeDataFetcher:
|
|
|
461
459
|
logger.warning(f"Finnhub API not initialized: {e}")
|
|
462
460
|
|
|
463
461
|
def fetch_from_senate_watcher(
|
|
464
|
-
self,
|
|
465
|
-
recent_only: bool = False,
|
|
466
|
-
days: int = 90
|
|
462
|
+
self, recent_only: bool = False, days: int = 90
|
|
467
463
|
) -> Dict[str, List]:
|
|
468
464
|
"""
|
|
469
465
|
Fetch data from Senate Stock Watcher GitHub dataset
|
|
@@ -498,10 +494,7 @@ class FreeDataFetcher:
|
|
|
498
494
|
f"{len(disclosures)} disclosures from Senate Stock Watcher"
|
|
499
495
|
)
|
|
500
496
|
|
|
501
|
-
return {
|
|
502
|
-
"politicians": politicians,
|
|
503
|
-
"disclosures": disclosures
|
|
504
|
-
}
|
|
497
|
+
return {"politicians": politicians, "disclosures": disclosures}
|
|
505
498
|
|
|
506
499
|
|
|
507
500
|
# =============================================================================
|
|
@@ -9,18 +9,17 @@ politician trading activity:
|
|
|
9
9
|
- ProPublica Congress API
|
|
10
10
|
"""
|
|
11
11
|
|
|
12
|
+
import logging
|
|
12
13
|
import os
|
|
13
14
|
import time
|
|
14
15
|
from datetime import datetime, timedelta
|
|
15
16
|
from typing import Dict, List, Optional
|
|
16
|
-
import logging
|
|
17
17
|
|
|
18
18
|
import requests
|
|
19
19
|
from bs4 import BeautifulSoup
|
|
20
20
|
|
|
21
21
|
from .models import Politician, TradingDisclosure
|
|
22
22
|
|
|
23
|
-
|
|
24
23
|
logger = logging.getLogger(__name__)
|
|
25
24
|
|
|
26
25
|
|
|
@@ -36,9 +35,9 @@ class StockNearScraper:
|
|
|
36
35
|
|
|
37
36
|
def __init__(self):
|
|
38
37
|
self.session = requests.Session()
|
|
39
|
-
self.session.headers.update(
|
|
40
|
-
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36"
|
|
41
|
-
|
|
38
|
+
self.session.headers.update(
|
|
39
|
+
{"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36"}
|
|
40
|
+
)
|
|
42
41
|
|
|
43
42
|
def fetch_politicians_list(self) -> List[Dict]:
|
|
44
43
|
"""Fetch list of politicians tracked by StockNear"""
|
|
@@ -46,7 +45,7 @@ class StockNearScraper:
|
|
|
46
45
|
response = self.session.get(self.BASE_URL, timeout=30)
|
|
47
46
|
response.raise_for_status()
|
|
48
47
|
|
|
49
|
-
soup = BeautifulSoup(response.content,
|
|
48
|
+
soup = BeautifulSoup(response.content, "html.parser")
|
|
50
49
|
|
|
51
50
|
# StockNear loads data via JavaScript - would need Selenium or API access
|
|
52
51
|
# For now, return structure for manual data entry or API integration
|
|
@@ -80,18 +79,17 @@ class ProPublicaAPI:
|
|
|
80
79
|
def __init__(self, api_key: Optional[str] = None):
|
|
81
80
|
self.api_key = api_key or os.getenv("PROPUBLICA_API_KEY")
|
|
82
81
|
if not self.api_key:
|
|
83
|
-
raise ValueError(
|
|
82
|
+
raise ValueError(
|
|
83
|
+
"ProPublica API key required. Set PROPUBLICA_API_KEY environment variable."
|
|
84
|
+
)
|
|
84
85
|
|
|
85
86
|
self.session = requests.Session()
|
|
86
|
-
self.session.headers.update(
|
|
87
|
-
"X-API-Key": self.api_key,
|
|
88
|
-
|
|
89
|
-
})
|
|
87
|
+
self.session.headers.update(
|
|
88
|
+
{"X-API-Key": self.api_key, "User-Agent": "PoliticianTradingTracker/1.0"}
|
|
89
|
+
)
|
|
90
90
|
|
|
91
91
|
def get_member_financial_disclosures(
|
|
92
|
-
self,
|
|
93
|
-
member_id: str,
|
|
94
|
-
congress: int = 118 # 118th Congress (2023-2025)
|
|
92
|
+
self, member_id: str, congress: int = 118 # 118th Congress (2023-2025)
|
|
95
93
|
) -> List[Dict]:
|
|
96
94
|
"""
|
|
97
95
|
Get financial disclosures for a specific member of Congress
|
|
@@ -128,11 +126,7 @@ class ProPublicaAPI:
|
|
|
128
126
|
logger.error(f"Error fetching ProPublica financial disclosures: {e}")
|
|
129
127
|
return []
|
|
130
128
|
|
|
131
|
-
def get_recent_stock_transactions(
|
|
132
|
-
self,
|
|
133
|
-
congress: int = 118,
|
|
134
|
-
offset: int = 0
|
|
135
|
-
) -> List[Dict]:
|
|
129
|
+
def get_recent_stock_transactions(self, congress: int = 118, offset: int = 0) -> List[Dict]:
|
|
136
130
|
"""
|
|
137
131
|
Get recent stock transactions by members of Congress
|
|
138
132
|
|
|
@@ -144,7 +138,9 @@ class ProPublicaAPI:
|
|
|
144
138
|
List of stock transactions
|
|
145
139
|
"""
|
|
146
140
|
try:
|
|
147
|
-
url =
|
|
141
|
+
url = (
|
|
142
|
+
f"{self.BASE_URL}/{congress}/house/members/financial-disclosures/transactions.json"
|
|
143
|
+
)
|
|
148
144
|
params = {"offset": offset}
|
|
149
145
|
|
|
150
146
|
response = self.session.get(url, params=params, timeout=30)
|
|
@@ -160,9 +156,7 @@ class ProPublicaAPI:
|
|
|
160
156
|
return []
|
|
161
157
|
|
|
162
158
|
def list_current_members(
|
|
163
|
-
self,
|
|
164
|
-
chamber: str = "house", # "house" or "senate"
|
|
165
|
-
congress: int = 118
|
|
159
|
+
self, chamber: str = "house", congress: int = 118 # "house" or "senate"
|
|
166
160
|
) -> List[Dict]:
|
|
167
161
|
"""
|
|
168
162
|
Get list of current members of Congress
|
|
@@ -220,9 +214,7 @@ class ThirdPartyDataFetcher:
|
|
|
220
214
|
self.stocknear = StockNearScraper()
|
|
221
215
|
|
|
222
216
|
def fetch_from_propublica(
|
|
223
|
-
self,
|
|
224
|
-
fetch_members: bool = True,
|
|
225
|
-
fetch_transactions: bool = True
|
|
217
|
+
self, fetch_members: bool = True, fetch_transactions: bool = True
|
|
226
218
|
) -> Dict[str, List]:
|
|
227
219
|
"""
|
|
228
220
|
Fetch data from ProPublica Congress API
|
|
@@ -262,16 +254,9 @@ class ThirdPartyDataFetcher:
|
|
|
262
254
|
f"{len(disclosures)} disclosures from ProPublica"
|
|
263
255
|
)
|
|
264
256
|
|
|
265
|
-
return {
|
|
266
|
-
"politicians": politicians,
|
|
267
|
-
"disclosures": disclosures
|
|
268
|
-
}
|
|
257
|
+
return {"politicians": politicians, "disclosures": disclosures}
|
|
269
258
|
|
|
270
|
-
def _convert_propublica_members(
|
|
271
|
-
self,
|
|
272
|
-
members: List[Dict],
|
|
273
|
-
chamber: str
|
|
274
|
-
) -> List[Politician]:
|
|
259
|
+
def _convert_propublica_members(self, members: List[Dict], chamber: str) -> List[Politician]:
|
|
275
260
|
"""Convert ProPublica member data to Politician objects"""
|
|
276
261
|
politicians = []
|
|
277
262
|
|
|
@@ -294,10 +279,7 @@ class ThirdPartyDataFetcher:
|
|
|
294
279
|
|
|
295
280
|
return politicians
|
|
296
281
|
|
|
297
|
-
def _convert_propublica_transactions(
|
|
298
|
-
self,
|
|
299
|
-
transactions: List[Dict]
|
|
300
|
-
) -> List[TradingDisclosure]:
|
|
282
|
+
def _convert_propublica_transactions(self, transactions: List[Dict]) -> List[TradingDisclosure]:
|
|
301
283
|
"""Convert ProPublica transaction data to TradingDisclosure objects"""
|
|
302
284
|
disclosures = []
|
|
303
285
|
|
|
@@ -20,11 +20,12 @@ from pathlib import Path
|
|
|
20
20
|
from typing import Dict, List, Optional
|
|
21
21
|
from uuid import UUID
|
|
22
22
|
|
|
23
|
-
from supabase import
|
|
23
|
+
from supabase import Client, create_client
|
|
24
24
|
|
|
25
25
|
# Load environment variables from .env file
|
|
26
26
|
try:
|
|
27
27
|
from dotenv import load_dotenv
|
|
28
|
+
|
|
28
29
|
# Look for .env in project root
|
|
29
30
|
env_path = Path(__file__).parent.parent.parent.parent.parent / ".env"
|
|
30
31
|
if env_path.exists():
|
|
@@ -39,15 +40,11 @@ from .data_sources import ALL_DATA_SOURCES, AccessMethod, DataSource
|
|
|
39
40
|
from .models import Politician, TradingDisclosure
|
|
40
41
|
from .scrapers_free_sources import FreeDataFetcher
|
|
41
42
|
|
|
42
|
-
|
|
43
43
|
# Configure logging
|
|
44
44
|
logging.basicConfig(
|
|
45
45
|
level=logging.INFO,
|
|
46
46
|
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
|
|
47
|
-
handlers=[
|
|
48
|
-
logging.StreamHandler(sys.stdout),
|
|
49
|
-
logging.FileHandler("/tmp/seed_database.log")
|
|
50
|
-
]
|
|
47
|
+
handlers=[logging.StreamHandler(sys.stdout), logging.FileHandler("/tmp/seed_database.log")],
|
|
51
48
|
)
|
|
52
49
|
logger = logging.getLogger(__name__)
|
|
53
50
|
|
|
@@ -76,11 +73,7 @@ def get_supabase_client() -> Client:
|
|
|
76
73
|
# =============================================================================
|
|
77
74
|
|
|
78
75
|
|
|
79
|
-
def create_data_pull_job(
|
|
80
|
-
client: Client,
|
|
81
|
-
job_type: str,
|
|
82
|
-
config: Optional[Dict] = None
|
|
83
|
-
) -> UUID:
|
|
76
|
+
def create_data_pull_job(client: Client, job_type: str, config: Optional[Dict] = None) -> UUID:
|
|
84
77
|
"""
|
|
85
78
|
Create a new data pull job record
|
|
86
79
|
|
|
@@ -93,12 +86,18 @@ def create_data_pull_job(
|
|
|
93
86
|
Job ID
|
|
94
87
|
"""
|
|
95
88
|
try:
|
|
96
|
-
result =
|
|
97
|
-
"
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
89
|
+
result = (
|
|
90
|
+
client.table("data_pull_jobs")
|
|
91
|
+
.insert(
|
|
92
|
+
{
|
|
93
|
+
"job_type": job_type,
|
|
94
|
+
"status": "running",
|
|
95
|
+
"started_at": datetime.now().isoformat(),
|
|
96
|
+
"config_snapshot": config or {},
|
|
97
|
+
}
|
|
98
|
+
)
|
|
99
|
+
.execute()
|
|
100
|
+
)
|
|
102
101
|
|
|
103
102
|
job_id = result.data[0]["id"]
|
|
104
103
|
logger.info(f"Created data pull job: {job_id} (type: {job_type})")
|
|
@@ -114,7 +113,7 @@ def update_data_pull_job(
|
|
|
114
113
|
job_id: UUID,
|
|
115
114
|
status: str,
|
|
116
115
|
stats: Optional[Dict] = None,
|
|
117
|
-
error: Optional[str] = None
|
|
116
|
+
error: Optional[str] = None,
|
|
118
117
|
):
|
|
119
118
|
"""
|
|
120
119
|
Update data pull job with results
|
|
@@ -127,10 +126,7 @@ def update_data_pull_job(
|
|
|
127
126
|
error: Optional error message if failed
|
|
128
127
|
"""
|
|
129
128
|
try:
|
|
130
|
-
update_data = {
|
|
131
|
-
"status": status,
|
|
132
|
-
"completed_at": datetime.now().isoformat()
|
|
133
|
-
}
|
|
129
|
+
update_data = {"status": status, "completed_at": datetime.now().isoformat()}
|
|
134
130
|
|
|
135
131
|
if stats:
|
|
136
132
|
update_data.update(stats)
|
|
@@ -151,10 +147,7 @@ def update_data_pull_job(
|
|
|
151
147
|
# =============================================================================
|
|
152
148
|
|
|
153
149
|
|
|
154
|
-
def upsert_politicians(
|
|
155
|
-
client: Client,
|
|
156
|
-
politicians: List[Politician]
|
|
157
|
-
) -> Dict[str, UUID]:
|
|
150
|
+
def upsert_politicians(client: Client, politicians: List[Politician]) -> Dict[str, UUID]:
|
|
158
151
|
"""
|
|
159
152
|
Upsert politicians to database, returning mapping of bioguide_id -> UUID
|
|
160
153
|
|
|
@@ -186,20 +179,23 @@ def upsert_politicians(
|
|
|
186
179
|
# Try to find existing politician
|
|
187
180
|
if politician.bioguide_id:
|
|
188
181
|
# Query by bioguide_id if available
|
|
189
|
-
existing =
|
|
190
|
-
"
|
|
191
|
-
|
|
182
|
+
existing = (
|
|
183
|
+
client.table("politicians")
|
|
184
|
+
.select("id")
|
|
185
|
+
.eq("bioguide_id", politician.bioguide_id)
|
|
186
|
+
.execute()
|
|
187
|
+
)
|
|
192
188
|
else:
|
|
193
189
|
# Query by unique constraint fields (first_name, last_name, role, state_or_country)
|
|
194
|
-
existing =
|
|
195
|
-
"
|
|
196
|
-
|
|
197
|
-
"
|
|
198
|
-
|
|
199
|
-
"role", politician.role
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
)
|
|
190
|
+
existing = (
|
|
191
|
+
client.table("politicians")
|
|
192
|
+
.select("id")
|
|
193
|
+
.eq("first_name", politician.first_name)
|
|
194
|
+
.eq("last_name", politician.last_name)
|
|
195
|
+
.eq("role", politician.role)
|
|
196
|
+
.eq("state_or_country", politician.state_or_country)
|
|
197
|
+
.execute()
|
|
198
|
+
)
|
|
203
199
|
|
|
204
200
|
if existing.data:
|
|
205
201
|
# Update existing
|
|
@@ -223,7 +219,9 @@ def upsert_politicians(
|
|
|
223
219
|
logger.error(f"Error upserting politician {politician.full_name}: {e}")
|
|
224
220
|
continue
|
|
225
221
|
|
|
226
|
-
logger.info(
|
|
222
|
+
logger.info(
|
|
223
|
+
f"Upserted {len(politicians)} politicians ({new_count} new, {updated_count} updated)"
|
|
224
|
+
)
|
|
227
225
|
|
|
228
226
|
return politician_map
|
|
229
227
|
|
|
@@ -234,9 +232,7 @@ def upsert_politicians(
|
|
|
234
232
|
|
|
235
233
|
|
|
236
234
|
def upsert_trading_disclosures(
|
|
237
|
-
client: Client,
|
|
238
|
-
disclosures: List[TradingDisclosure],
|
|
239
|
-
politician_map: Dict[str, UUID]
|
|
235
|
+
client: Client, disclosures: List[TradingDisclosure], politician_map: Dict[str, UUID]
|
|
240
236
|
) -> Dict[str, int]:
|
|
241
237
|
"""
|
|
242
238
|
Upsert trading disclosures to database
|
|
@@ -283,17 +279,16 @@ def upsert_trading_disclosures(
|
|
|
283
279
|
}
|
|
284
280
|
|
|
285
281
|
# Check for existing disclosure (using unique constraint)
|
|
286
|
-
existing =
|
|
287
|
-
"
|
|
288
|
-
|
|
289
|
-
"
|
|
290
|
-
|
|
291
|
-
"asset_name", disclosure.asset_name
|
|
292
|
-
|
|
293
|
-
"
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
).execute()
|
|
282
|
+
existing = (
|
|
283
|
+
client.table("trading_disclosures")
|
|
284
|
+
.select("id")
|
|
285
|
+
.eq("politician_id", str(pol_id))
|
|
286
|
+
.eq("transaction_date", disclosure.transaction_date.isoformat())
|
|
287
|
+
.eq("asset_name", disclosure.asset_name)
|
|
288
|
+
.eq("transaction_type", disclosure.transaction_type)
|
|
289
|
+
.eq("disclosure_date", disclosure.disclosure_date.isoformat())
|
|
290
|
+
.execute()
|
|
291
|
+
)
|
|
297
292
|
|
|
298
293
|
if existing.data:
|
|
299
294
|
# Update existing
|
|
@@ -331,10 +326,7 @@ def upsert_trading_disclosures(
|
|
|
331
326
|
|
|
332
327
|
|
|
333
328
|
def seed_from_senate_watcher(
|
|
334
|
-
client: Client,
|
|
335
|
-
test_run: bool = False,
|
|
336
|
-
recent_only: bool = False,
|
|
337
|
-
days: int = 90
|
|
329
|
+
client: Client, test_run: bool = False, recent_only: bool = False, days: int = 90
|
|
338
330
|
) -> Dict[str, int]:
|
|
339
331
|
"""
|
|
340
332
|
Seed database from Senate Stock Watcher GitHub dataset
|
|
@@ -353,20 +345,16 @@ def seed_from_senate_watcher(
|
|
|
353
345
|
logger.info("=" * 80)
|
|
354
346
|
|
|
355
347
|
# Create job record
|
|
356
|
-
job_id = create_data_pull_job(
|
|
357
|
-
"recent_only": recent_only,
|
|
358
|
-
|
|
359
|
-
})
|
|
348
|
+
job_id = create_data_pull_job(
|
|
349
|
+
client, "senate_watcher_seed", {"recent_only": recent_only, "days": days}
|
|
350
|
+
)
|
|
360
351
|
|
|
361
352
|
try:
|
|
362
353
|
# Initialize fetcher
|
|
363
354
|
fetcher = FreeDataFetcher()
|
|
364
355
|
|
|
365
356
|
# Fetch data
|
|
366
|
-
data = fetcher.fetch_from_senate_watcher(
|
|
367
|
-
recent_only=recent_only,
|
|
368
|
-
days=days
|
|
369
|
-
)
|
|
357
|
+
data = fetcher.fetch_from_senate_watcher(recent_only=recent_only, days=days)
|
|
370
358
|
|
|
371
359
|
politicians = data["politicians"]
|
|
372
360
|
disclosures = data["disclosures"]
|
|
@@ -377,11 +365,16 @@ def seed_from_senate_watcher(
|
|
|
377
365
|
logger.info("TEST RUN - Not inserting to database")
|
|
378
366
|
logger.info(f"Sample politician: {politicians[0] if politicians else 'None'}")
|
|
379
367
|
logger.info(f"Sample disclosure: {disclosures[0] if disclosures else 'None'}")
|
|
380
|
-
update_data_pull_job(
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
"
|
|
384
|
-
|
|
368
|
+
update_data_pull_job(
|
|
369
|
+
client,
|
|
370
|
+
job_id,
|
|
371
|
+
"completed",
|
|
372
|
+
{
|
|
373
|
+
"records_found": len(politicians) + len(disclosures),
|
|
374
|
+
"records_new": 0,
|
|
375
|
+
"records_updated": 0,
|
|
376
|
+
},
|
|
377
|
+
)
|
|
385
378
|
return {"records_found": len(politicians) + len(disclosures)}
|
|
386
379
|
|
|
387
380
|
# Upsert politicians
|
|
@@ -401,10 +394,7 @@ def seed_from_senate_watcher(
|
|
|
401
394
|
raise
|
|
402
395
|
|
|
403
396
|
|
|
404
|
-
def seed_from_all_sources(
|
|
405
|
-
client: Client,
|
|
406
|
-
test_run: bool = False
|
|
407
|
-
) -> Dict[str, Dict[str, int]]:
|
|
397
|
+
def seed_from_all_sources(client: Client, test_run: bool = False) -> Dict[str, Dict[str, int]]:
|
|
408
398
|
"""
|
|
409
399
|
Seed database from all available sources
|
|
410
400
|
|
|
@@ -467,33 +457,27 @@ def main():
|
|
|
467
457
|
"--sources",
|
|
468
458
|
choices=["all", "senate", "finnhub", "sec-edgar"],
|
|
469
459
|
default="all",
|
|
470
|
-
help="Which data sources to seed from (default: all)"
|
|
460
|
+
help="Which data sources to seed from (default: all)",
|
|
471
461
|
)
|
|
472
462
|
|
|
473
463
|
parser.add_argument(
|
|
474
|
-
"--recent-only",
|
|
475
|
-
action="store_true",
|
|
476
|
-
help="Only fetch recent transactions (last 90 days)"
|
|
464
|
+
"--recent-only", action="store_true", help="Only fetch recent transactions (last 90 days)"
|
|
477
465
|
)
|
|
478
466
|
|
|
479
467
|
parser.add_argument(
|
|
480
468
|
"--days",
|
|
481
469
|
type=int,
|
|
482
470
|
default=90,
|
|
483
|
-
help="Number of days to look back when using --recent-only (default: 90)"
|
|
471
|
+
help="Number of days to look back when using --recent-only (default: 90)",
|
|
484
472
|
)
|
|
485
473
|
|
|
486
474
|
parser.add_argument(
|
|
487
475
|
"--test-run",
|
|
488
476
|
action="store_true",
|
|
489
|
-
help="Fetch data but don't insert to database (for testing)"
|
|
477
|
+
help="Fetch data but don't insert to database (for testing)",
|
|
490
478
|
)
|
|
491
479
|
|
|
492
|
-
parser.add_argument(
|
|
493
|
-
"--verbose",
|
|
494
|
-
action="store_true",
|
|
495
|
-
help="Enable verbose logging"
|
|
496
|
-
)
|
|
480
|
+
parser.add_argument("--verbose", action="store_true", help="Enable verbose logging")
|
|
497
481
|
|
|
498
482
|
args = parser.parse_args()
|
|
499
483
|
|
|
@@ -512,10 +496,7 @@ def main():
|
|
|
512
496
|
try:
|
|
513
497
|
if args.sources == "senate":
|
|
514
498
|
seed_from_senate_watcher(
|
|
515
|
-
client,
|
|
516
|
-
test_run=args.test_run,
|
|
517
|
-
recent_only=args.recent_only,
|
|
518
|
-
days=args.days
|
|
499
|
+
client, test_run=args.test_run, recent_only=args.recent_only, days=args.days
|
|
519
500
|
)
|
|
520
501
|
elif args.sources == "all":
|
|
521
502
|
seed_from_all_sources(client, args.test_run)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mcli-framework
|
|
3
|
-
Version: 7.6.
|
|
3
|
+
Version: 7.6.2
|
|
4
4
|
Summary: 🚀 High-performance CLI framework with Rust extensions, AI chat, and stunning visuals
|
|
5
5
|
Author-email: Luis Fernandez de la Vara <luis@lefv.io>
|
|
6
6
|
Maintainer-email: Luis Fernandez de la Vara <luis@lefv.io>
|