Features Added: ============== 📧 EMAIL REPORTING SYSTEM: - EmailReporter: Send reports via SMTP (Gmail, SendGrid, custom) - ReportGenerator: Generate daily/weekly summaries with HTML/text formatting - Configurable via .env (SMTP_HOST, SMTP_PORT, etc.) - Scripts: send_daily_report.py, send_weekly_report.py 🤖 AUTOMATED RUNS: - automated_daily_run.sh: Full daily ETL pipeline + reporting - automated_weekly_run.sh: Weekly pattern analysis + reports - setup_cron.sh: Interactive cron job setup (5-minute setup) - Logs saved to ~/logs/ with automatic cleanup 🔍 HEALTH CHECKS: - health_check.py: System health monitoring - Checks: DB connection, data freshness, counts, recent alerts - JSON output for programmatic use - Exit codes for monitoring integration 🚀 CI/CD PIPELINE: - .github/workflows/ci.yml: Full CI/CD pipeline - GitHub Actions / Gitea Actions compatible - Jobs: lint & test, security scan, dependency scan, Docker build - PostgreSQL service for integration tests - 93 tests passing in CI 📚 COMPREHENSIVE DOCUMENTATION: - AUTOMATION_QUICKSTART.md: 5-minute email setup guide - docs/12_automation_and_reporting.md: Full automation guide - Updated README.md with automation links - Deployment → Production workflow guide 🛠️ IMPROVEMENTS: - All shell scripts made executable - Environment variable examples in .env.example - Report logs saved with timestamps - 30-day log retention with auto-cleanup - Health checks can be scheduled via cron WHAT THIS ENABLES: ================== After deployment, users can: 1. Set up automated daily/weekly email reports (5 min) 2. Receive HTML+text emails with: - New trades, market alerts, suspicious timing - Weekly patterns, rankings, repeat offenders 3. Monitor system health automatically 4. Run full CI/CD pipeline on every commit 5. Deploy with confidence (tests + security scans) USAGE: ====== # One-time setup (on deployed server) ./scripts/setup_cron.sh # Or manually send reports python scripts/send_daily_report.py --to user@example.com python scripts/send_weekly_report.py --to user@example.com # Check system health python scripts/health_check.py See AUTOMATION_QUICKSTART.md for full instructions. 93 tests passing | Full CI/CD | Email reports ready
181 lines
7.9 KiB
Python
Executable File
181 lines
7.9 KiB
Python
Executable File
#!/usr/bin/env python
|
|
"""
|
|
Fetch current members of Congress.
|
|
Creates a watchlist of active members.
|
|
"""
|
|
|
|
import json
|
|
import requests
|
|
from pathlib import Path
|
|
|
|
# Congress.gov API (no key required for basic info)
|
|
# or ProPublica Congress API (requires free key)
|
|
|
|
def fetch_from_propublica():
|
|
"""
|
|
Fetch from ProPublica Congress API.
|
|
Free API key: https://www.propublica.org/datastore/api/propublica-congress-api
|
|
"""
|
|
API_KEY = "YOUR_API_KEY" # Get free key from ProPublica
|
|
|
|
headers = {"X-API-Key": API_KEY}
|
|
|
|
# Get current Congress (118th = 2023-2025, 119th = 2025-2027)
|
|
members = []
|
|
|
|
# Senate
|
|
senate_url = "https://api.propublica.org/congress/v1/118/senate/members.json"
|
|
response = requests.get(senate_url, headers=headers)
|
|
if response.ok:
|
|
data = response.json()
|
|
for member in data['results'][0]['members']:
|
|
members.append({
|
|
'name': f"{member['first_name']} {member['last_name']}",
|
|
'chamber': 'Senate',
|
|
'party': member['party'],
|
|
'state': member['state'],
|
|
})
|
|
|
|
# House
|
|
house_url = "https://api.propublica.org/congress/v1/118/house/members.json"
|
|
response = requests.get(house_url, headers=headers)
|
|
if response.ok:
|
|
data = response.json()
|
|
for member in data['results'][0]['members']:
|
|
members.append({
|
|
'name': f"{member['first_name']} {member['last_name']}",
|
|
'chamber': 'House',
|
|
'party': member['party'],
|
|
'state': member['state'],
|
|
})
|
|
|
|
return members
|
|
|
|
|
|
def get_known_active_traders():
|
|
"""
|
|
Manually curated list of Congress members known for active trading.
|
|
Based on public reporting and analysis.
|
|
"""
|
|
return [
|
|
# === SENATE ===
|
|
{"name": "Tommy Tuberville", "chamber": "Senate", "party": "Republican", "state": "AL"},
|
|
{"name": "Rand Paul", "chamber": "Senate", "party": "Republican", "state": "KY"},
|
|
{"name": "Sheldon Whitehouse", "chamber": "Senate", "party": "Democrat", "state": "RI"},
|
|
{"name": "John Hickenlooper", "chamber": "Senate", "party": "Democrat", "state": "CO"},
|
|
{"name": "Steve Daines", "chamber": "Senate", "party": "Republican", "state": "MT"},
|
|
{"name": "Gary Peters", "chamber": "Senate", "party": "Democrat", "state": "MI"},
|
|
{"name": "Rick Scott", "chamber": "Senate", "party": "Republican", "state": "FL"},
|
|
{"name": "Mark Warner", "chamber": "Senate", "party": "Democrat", "state": "VA"},
|
|
{"name": "Dianne Feinstein", "chamber": "Senate", "party": "Democrat", "state": "CA"}, # Note: deceased
|
|
|
|
# === HOUSE ===
|
|
{"name": "Nancy Pelosi", "chamber": "House", "party": "Democrat", "state": "CA"},
|
|
{"name": "Brian Higgins", "chamber": "House", "party": "Democrat", "state": "NY"},
|
|
{"name": "Michael McCaul", "chamber": "House", "party": "Republican", "state": "TX"},
|
|
{"name": "Dan Crenshaw", "chamber": "House", "party": "Republican", "state": "TX"},
|
|
{"name": "Marjorie Taylor Greene", "chamber": "House", "party": "Republican", "state": "GA"},
|
|
{"name": "Josh Gottheimer", "chamber": "House", "party": "Democrat", "state": "NJ"},
|
|
{"name": "Ro Khanna", "chamber": "House", "party": "Democrat", "state": "CA"},
|
|
{"name": "Dean Phillips", "chamber": "House", "party": "Democrat", "state": "MN"},
|
|
{"name": "Virginia Foxx", "chamber": "House", "party": "Republican", "state": "NC"},
|
|
{"name": "Debbie Wasserman Schultz", "chamber": "House", "party": "Democrat", "state": "FL"},
|
|
{"name": "Pat Fallon", "chamber": "House", "party": "Republican", "state": "TX"},
|
|
{"name": "Kevin Hern", "chamber": "House", "party": "Republican", "state": "OK"},
|
|
{"name": "Mark Green", "chamber": "House", "party": "Republican", "state": "TN"},
|
|
{"name": "French Hill", "chamber": "House", "party": "Republican", "state": "AR"},
|
|
{"name": "John Curtis", "chamber": "House", "party": "Republican", "state": "UT"},
|
|
|
|
# === HIGH VOLUME TRADERS (Based on 2023-2024 reporting) ===
|
|
{"name": "Austin Scott", "chamber": "House", "party": "Republican", "state": "GA"},
|
|
{"name": "Nicole Malliotakis", "chamber": "House", "party": "Republican", "state": "NY"},
|
|
{"name": "Lois Frankel", "chamber": "House", "party": "Democrat", "state": "FL"},
|
|
{"name": "Earl Blumenauer", "chamber": "House", "party": "Democrat", "state": "OR"},
|
|
{"name": "Pete Sessions", "chamber": "House", "party": "Republican", "state": "TX"},
|
|
]
|
|
|
|
|
|
def save_watchlist(members, filename="watchlist.json"):
|
|
"""Save watchlist to file."""
|
|
output_path = Path(__file__).parent.parent / "config" / filename
|
|
output_path.parent.mkdir(exist_ok=True)
|
|
|
|
with open(output_path, 'w') as f:
|
|
json.dump(members, f, indent=2)
|
|
|
|
print(f"✅ Saved {len(members)} members to {output_path}")
|
|
return output_path
|
|
|
|
|
|
def load_watchlist(filename="watchlist.json"):
|
|
"""Load watchlist from file."""
|
|
config_path = Path(__file__).parent.parent / "config" / filename
|
|
|
|
if not config_path.exists():
|
|
print(f"⚠️ Watchlist not found at {config_path}")
|
|
print(" Creating default watchlist...")
|
|
members = get_known_active_traders()
|
|
save_watchlist(members, filename)
|
|
return members
|
|
|
|
with open(config_path) as f:
|
|
return json.load(f)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
import argparse
|
|
|
|
parser = argparse.ArgumentParser(description="Manage Congress member watchlist")
|
|
parser.add_argument("--create", action="store_true", help="Create default watchlist")
|
|
parser.add_argument("--list", action="store_true", help="List current watchlist")
|
|
parser.add_argument("--propublica", action="store_true", help="Fetch from ProPublica API")
|
|
|
|
args = parser.parse_args()
|
|
|
|
if args.propublica:
|
|
print("Fetching from ProPublica API...")
|
|
print("⚠️ You need to set API_KEY in the script first")
|
|
print(" Get free key: https://www.propublica.org/datastore/api/propublica-congress-api")
|
|
# members = fetch_from_propublica()
|
|
# save_watchlist(members)
|
|
elif args.create:
|
|
members = get_known_active_traders()
|
|
save_watchlist(members, "watchlist.json")
|
|
print(f"\n✅ Created watchlist with {len(members)} active traders")
|
|
elif args.list:
|
|
members = load_watchlist()
|
|
print(f"\n📋 Watchlist ({len(members)} members):\n")
|
|
|
|
# Group by chamber
|
|
senate = [m for m in members if m['chamber'] == 'Senate']
|
|
house = [m for m in members if m['chamber'] == 'House']
|
|
|
|
print("SENATE:")
|
|
for m in sorted(senate, key=lambda x: x['name']):
|
|
print(f" • {m['name']:30s} ({m['party']:1s}-{m['state']})")
|
|
|
|
print("\nHOUSE:")
|
|
for m in sorted(house, key=lambda x: x['name']):
|
|
print(f" • {m['name']:30s} ({m['party']:1s}-{m['state']})")
|
|
else:
|
|
# Default: show known active traders
|
|
members = get_known_active_traders()
|
|
print(f"\n📋 Known Active Traders ({len(members)} members):\n")
|
|
print("These are Congress members with historically high trading activity.\n")
|
|
|
|
senate = [m for m in members if m['chamber'] == 'Senate']
|
|
house = [m for m in members if m['chamber'] == 'House']
|
|
|
|
print("SENATE:")
|
|
for m in sorted(senate, key=lambda x: x['name']):
|
|
print(f" • {m['name']:30s} ({m['party']:1s}-{m['state']})")
|
|
|
|
print("\nHOUSE:")
|
|
for m in sorted(house, key=lambda x: x['name']):
|
|
print(f" • {m['name']:30s} ({m['party']:1s}-{m['state']})")
|
|
|
|
print("\n💡 To create watchlist file: python scripts/fetch_congress_members.py --create")
|
|
print("💡 To view saved watchlist: python scripts/fetch_congress_members.py --list")
|
|
|
|
|