Spaces:
Running on CPU Upgrade
Running on CPU Upgrade
| sidebar_position: 7 | |
| # FEC Campaign Finance Integration | |
| Track political contributions and campaign finance data using the **Federal Election Commission (FEC)** via **OpenFEC API** and **Bulk Downloads**. | |
| ## π― Overview | |
| The FEC integration enables tracking of: | |
| - Individual political contributions ($200+) | |
| - Federal candidates (House, Senate, President) | |
| - Political committees and PACs | |
| - Nonprofit leadership political giving | |
| - Donor networks in advocacy organizations | |
| - Campaign expenditures and disbursements | |
| **Two access methods:** | |
| 1. **OpenFEC API** - Real-time queries, filtered searches (API key required) | |
| 2. **Bulk Downloads** - Complete datasets, no rate limits (no API key needed) | |
| This data integrates with nonprofit and contacts data to reveal political influence patterns. | |
| ## π Get API Access | |
| ### OpenFEC API (Real-time Queries) | |
| **1. Sign Up for Free API Key** | |
| Visit: **https://api.data.gov/signup/** | |
| - Free tier: **1,000 requests/hour** | |
| - No credit card required | |
| - Instant activation | |
| **2. Set Environment Variable** | |
| ```bash | |
| # Add to your .env file | |
| echo 'FEC_API_KEY="your_api_key_here"' >> .env | |
| # Or export for current session | |
| export FEC_API_KEY="your_api_key_here" | |
| ``` | |
| Without an API key, you'll use `DEMO_KEY` (limited to 30 requests/hour). | |
| ### Bulk Downloads (Complete Datasets) | |
| **No API key required!** Download complete datasets directly: | |
| **Bulk Data Portal:** https://www.fec.gov/data/browse-data/?tab=bulk-data | |
| **Available datasets:** | |
| - **Contributions** (Schedule A) - All individual contributions $200+ | |
| - **Expenditures** (Schedule B) - All operating expenditures | |
| - **Candidate Master** - All federal candidates | |
| - **Committee Master** - All PACs and committees | |
| - **Campaign Finance Totals** - Summary by cycle | |
| **Format:** CSV, FEC format | |
| **Update Frequency:** Nightly | |
| **Historical Coverage:** 1980s to present | |
| **Rate Limits:** None (direct download) | |
| **When to use bulk downloads:** | |
| - Analyzing complete datasets (millions of records) | |
| - Historical analysis across election cycles | |
| - Offline analysis without API rate limits | |
| - Data warehousing and archival | |
| **When to use API:** | |
| - Real-time contribution lookups | |
| - Filtered searches (by name, employer, state) | |
| - Incremental updates | |
| - Web application queries | |
| ## π Gold Tables Created | |
| The FEC integration creates 4 gold tables per state: | |
| | Table | Description | Key Fields | | |
| |-------|-------------|------------| | |
| | **campaigns_candidates** | Federal candidates in state | candidate_id, name, party, office, district | | |
| | **campaigns_committees** | PACs and campaign committees | committee_id, name, type, party | | |
| | **campaigns_contributions** | Individual contributions $200+ | contributor_name, employer, amount, date, recipient | | |
| | **campaigns_nonprofit_donors** | Nonprofit leadership donations | ein, organization_name, contributor_name, amount | | |
| ## π Quick Start | |
| ### Run Demo Script | |
| ```bash | |
| cd /home/developer/projects/open-navigator | |
| source .venv/bin/activate | |
| # Basic demo (using DEMO_KEY) | |
| python examples/demo_fec_integration.py --state MA | |
| # With your API key | |
| export FEC_API_KEY="your_key_here" | |
| python examples/demo_fec_integration.py --state MA --cycle 2024 | |
| # Search for specific nonprofit donors | |
| python examples/demo_fec_integration.py --state MA --employer "Community Health" | |
| ``` | |
| ### Create Gold Tables | |
| ```bash | |
| # Create campaign finance tables for Massachusetts | |
| python pipeline/create_campaigns_gold_tables.py \ | |
| --state MA \ | |
| --cycle 2024 \ | |
| --max-contributions 10000 | |
| # With custom API key | |
| python pipeline/create_campaigns_gold_tables.py \ | |
| --state MA \ | |
| --api-key "your_key_here" | |
| ``` | |
| ## π Use Cases | |
| ### 1. Track Nonprofit Leadership Donations | |
| Identify politically active nonprofit officers and directors: | |
| ```python | |
| from pipeline.create_campaigns_gold_tables import CampaignsGoldTableCreator | |
| creator = CampaignsGoldTableCreator(state_code="MA") | |
| creator.create_all_campaigns_tables(cycle=2024) | |
| # Analyzes: | |
| # - Contributions where employer matches nonprofit name | |
| # - Contributions from known nonprofit officers/directors | |
| # - Political giving patterns in advocacy sector | |
| ``` | |
| **Output:** `data/gold/states/MA/campaigns_nonprofit_donors.parquet` | |
| ### 2. Map Donor Networks | |
| Find connections between organizations and political campaigns: | |
| ```python | |
| from discovery.fec_integration import OpenFECAPI | |
| api = OpenFECAPI(api_key="your_key") | |
| # Search for contributions from health sector | |
| result = api.search_individual_contributions( | |
| contributor_state="MA", | |
| contributor_employer="Health", | |
| min_amount=1000 | |
| ) | |
| for contrib in result['results']: | |
| print(f"{contrib['contributor_name']} ({contrib['contributor_employer']})") | |
| print(f" β ${contrib['contribution_receipt_amount']} to {contrib['committee_name']}") | |
| ``` | |
| ### 3. Analyze Political Influence on Policy | |
| Cross-reference campaign contributions with grant awards: | |
| ```python | |
| import pandas as pd | |
| # Load campaign contributions | |
| contributions = pd.read_parquet('data/gold/states/MA/campaigns_contributions.parquet') | |
| # Load grant data | |
| grants = pd.read_parquet('data/gold/states/MA/grants_revenue_sources.parquet') | |
| # Find organizations that both give politically and receive grants | |
| donor_orgs = contributions['contributor_employer'].unique() | |
| grant_recipients = grants['organization_name'].unique() | |
| overlap = set(donor_orgs) & set(grant_recipients) | |
| print(f"Organizations that both donate politically and receive grants: {len(overlap)}") | |
| ``` | |
| ## πΎ Using Bulk Downloads | |
| For large-scale analysis, use FEC bulk downloads instead of the API: | |
| ### Download Complete Datasets | |
| ```bash | |
| # Download candidate master file (all federal candidates) | |
| wget https://www.fec.gov/files/bulk-downloads/2024/cn24.zip | |
| unzip cn24.zip | |
| # Download committee master file (all PACs and committees) | |
| wget https://www.fec.gov/files/bulk-downloads/2024/cm24.zip | |
| unzip cm24.zip | |
| # Download individual contributions (Schedule A) | |
| # Warning: Very large file (100s of MB to GBs) | |
| wget https://www.fec.gov/files/bulk-downloads/2024/indiv24.zip | |
| unzip indiv24.zip | |
| ``` | |
| ### Process Bulk Data with Pandas | |
| ```python | |
| import pandas as pd | |
| from pathlib import Path | |
| # Load contributions for specific cycle | |
| contributions_file = Path("data/fec/bulk/indiv24.txt") | |
| df = pd.read_csv( | |
| contributions_file, | |
| sep="|", | |
| header=None, | |
| names=[ | |
| "CMTE_ID", "AMNDT_IND", "RPT_TP", "TRANSACTION_PGI", | |
| "IMAGE_NUM", "TRANSACTION_TP", "ENTITY_TP", "NAME", | |
| "CITY", "STATE", "ZIP_CODE", "EMPLOYER", "OCCUPATION", | |
| "TRANSACTION_DT", "TRANSACTION_AMT", "OTHER_ID", | |
| "TRAN_ID", "FILE_NUM", "MEMO_CD", "MEMO_TEXT", "SUB_ID" | |
| ], | |
| encoding="latin1", | |
| low_memory=False | |
| ) | |
| # Filter to Massachusetts contributors | |
| ma_contributions = df[df['STATE'] == 'MA'] | |
| # Filter to health sector | |
| health_donors = ma_contributions[ | |
| ma_contributions['EMPLOYER'].str.contains('Health|Hospital|Dental', case=False, na=False) | |
| ] | |
| print(f"Found {len(health_donors):,} health sector contributions from MA") | |
| print(f"Total amount: ${health_donors['TRANSACTION_AMT'].sum():,.2f}") | |
| # Save to parquet for faster future access | |
| health_donors.to_parquet('data/gold/states/MA/campaigns_contributions.parquet') | |
| ``` | |
| ### Bulk Download File Formats | |
| **Candidate Master File (cn.txt):** | |
| - Delimiter: Pipe (`|`) | |
| - Columns: `CAND_ID`, `CAND_NAME`, `CAND_PTY_AFFILIATION`, `CAND_ELECTION_YR`, `CAND_OFFICE_ST`, `CAND_OFFICE`, `CAND_OFFICE_DISTRICT`, etc. | |
| **Committee Master File (cm.txt):** | |
| - Delimiter: Pipe (`|`) | |
| - Columns: `CMTE_ID`, `CMTE_NM`, `TRES_NM`, `CMTE_CITY`, `CMTE_ST`, `CMTE_TP`, `CMTE_PTY_AFFILIATION`, etc. | |
| **Individual Contributions (indiv.txt):** | |
| - Delimiter: Pipe (`|`) | |
| - Columns: `CMTE_ID`, `NAME`, `CITY`, `STATE`, `ZIP_CODE`, `EMPLOYER`, `OCCUPATION`, `TRANSACTION_DT`, `TRANSACTION_AMT`, etc. | |
| - Contains all contributions $200+ as required by law | |
| ### Advantages of Bulk Downloads | |
| β **Complete data** - No pagination, no missing records | |
| β **No rate limits** - Download once, analyze forever | |
| β **Historical data** - Access to all election cycles | |
| β **Offline analysis** - No API dependency | |
| β **Faster processing** - Local files vs HTTP requests | |
| ### When to Use Each Method | |
| | Scenario | Method | Reason | | |
| |----------|--------|--------| | |
| | Find specific donor | API | Filtered search, fast lookup | | |
| | All MA contributions | Bulk Download | Complete dataset, no limits | | |
| | Real-time updates | API | Latest filings | | |
| | Historical analysis | Bulk Download | Multi-cycle coverage | | |
| | Web application | API | Dynamic queries | | |
| | Research/analysis | Bulk Download | Full data access | | |
| ## π οΈ API Reference | |
| ### Search Individual Contributions | |
| ```python | |
| from discovery.fec_integration import OpenFECAPI | |
| api = OpenFECAPI(api_key="your_key") | |
| # Search by contributor | |
| result = api.search_individual_contributions( | |
| contributor_name="Smith", | |
| contributor_state="MA", | |
| min_amount=200, | |
| min_date="2023-01-01", | |
| max_date="2024-12-31" | |
| ) | |
| # Search by employer | |
| result = api.search_individual_contributions( | |
| contributor_employer="Community Foundation", | |
| contributor_state="MA", | |
| min_amount=1000 | |
| ) | |
| ``` | |
| ### Search Candidates | |
| ```python | |
| # House candidates in Massachusetts | |
| result = api.search_candidates( | |
| state="MA", | |
| office="H", # H=House, S=Senate, P=President | |
| cycle=2024 | |
| ) | |
| # Senate candidates | |
| result = api.search_candidates( | |
| state="MA", | |
| office="S", | |
| party="DEM", # or "REP" | |
| cycle=2024 | |
| ) | |
| ``` | |
| ### Search Committees | |
| ```python | |
| # Find PACs in Massachusetts | |
| result = api.search_committees( | |
| state="MA", | |
| committee_type="N" # N=PAC, Q=Super PAC | |
| ) | |
| ``` | |
| ## π Data Model Integration | |
| ### Links to Nonprofit Data | |
| The `campaigns_nonprofit_donors` table links FEC data to nonprofits: | |
| ```sql | |
| -- Conceptual SQL (use pandas for actual queries) | |
| SELECT | |
| nd.organization_name, | |
| nd.contributor_name, | |
| nd.contributor_title, | |
| SUM(nd.contribution_amount) as total_contributions, | |
| COUNT(*) as num_contributions | |
| FROM campaigns_nonprofit_donors nd | |
| GROUP BY nd.organization_name, nd.contributor_name | |
| ORDER BY total_contributions DESC; | |
| ``` | |
| ### Links to Contacts | |
| Match contributions to nonprofit officers: | |
| ```python | |
| import pandas as pd | |
| # Load data | |
| officers = pd.read_parquet('data/gold/states/MA/contacts_nonprofit_officers.parquet') | |
| contributions = pd.read_parquet('data/gold/states/MA/campaigns_contributions.parquet') | |
| # Match by name | |
| merged = officers.merge( | |
| contributions, | |
| left_on='officer_name', | |
| right_on='contributor_name', | |
| how='inner' | |
| ) | |
| print(f"Found {len(merged)} contribution records from known nonprofit officers") | |
| ``` | |
| ## βοΈ Configuration | |
| ### Environment Variables | |
| ```bash | |
| # Required (or use DEMO_KEY) | |
| FEC_API_KEY="your_api_key_here" | |
| # Optional | |
| FEC_CACHE_DIR="data/cache/fec" # Where to cache bulk downloads | |
| ``` | |
| ### Rate Limits | |
| | API Key | Rate Limit | Notes | | |
| |---------|------------|-------| | |
| | DEMO_KEY | 30 requests/hour | Shared across all users | | |
| | Free API Key | 1,000 requests/hour | Recommended | | |
| | Bulk Download | No limit | 1-5 GB files, use for comprehensive data | | |
| ## π Advanced Usage | |
| ### Bulk Data Download | |
| For comprehensive historical analysis, download bulk files: | |
| ```python | |
| from discovery.fec_integration import FECBulkDataLoader | |
| loader = FECBulkDataLoader(cache_dir="data/cache/fec") | |
| # Download 2024 individual contributions (WARNING: 1-5 GB) | |
| zip_path = loader.download_individual_contributions(cycle="2024") | |
| # Parse and filter | |
| df = loader.parse_individual_contributions( | |
| zip_path=zip_path, | |
| state_filter="MA", | |
| employer_filter="Foundation", | |
| min_amount=200 | |
| ) | |
| print(f"Found {len(df):,} contributions from MA foundations") | |
| ``` | |
| ### Custom Analysis Pipeline | |
| Build custom analysis workflows: | |
| ```python | |
| from discovery.fec_integration import OpenFECAPI | |
| import pandas as pd | |
| api = OpenFECAPI(api_key="your_key") | |
| # 1. Get all House candidates in MA | |
| candidates_result = api.search_candidates(state="MA", office="H", cycle=2024) | |
| candidates = candidates_result['results'] | |
| # 2. For each candidate, get top donors | |
| for candidate in candidates: | |
| candidate_id = candidate['candidate_id'] | |
| # Get candidate committees | |
| committees_result = api.search_committees(candidate_id=candidate_id) | |
| # Analyze fundraising | |
| print(f"\n{candidate['name']} ({candidate['party']})") | |
| print(f" Committees: {len(committees_result['results'])}") | |
| ``` | |
| ## π Data Quality Notes | |
| ### Contribution Reporting Threshold | |
| - FEC requires reporting for contributions **$200 or more** | |
| - Smaller contributions are not itemized | |
| - Use `min_amount=200` for complete data | |
| ### Employer Field | |
| - Self-reported by contributor | |
| - May have inconsistent formatting | |
| - "Community Health Center" vs "Community Health Ctr" | |
| - Use fuzzy matching for nonprofit employer analysis | |
| ### Update Frequency | |
| - FEC data updated **daily** | |
| - Electronic filings appear within 24 hours | |
| - Paper filings may take 30+ days | |
| ## π Additional Resources | |
| - **OpenFEC API Docs:** https://api.open.fec.gov/developers/ | |
| - **FEC Bulk Data:** https://www.fec.gov/data/browse-data/?tab=bulk-data | |
| - **Campaign Finance Guide:** https://www.fec.gov/help-candidates-and-committees/ | |
| - **Data Dictionary:** https://www.fec.gov/campaign-finance-data/contributions-individuals-file-description/ | |
| ## π€ Related Integrations | |
| - [Nonprofit Discovery](nonprofit-discovery.md) - Match employers to EINs | |
| - [IRS BMF Data](irs-bmf-data.md) - Nonprofit organization data | |
| - [Gold Table Pipeline](../guides/gold-table-pipeline.md) - Full data model | |
| --- | |
| ## π‘ Pro Tips | |
| 1. **Start with API, use bulk for scale:** API is easier for exploration, bulk files for comprehensive analysis | |
| 2. **Cache aggressively:** Set up `data/cache/fec/` to avoid re-downloading | |
| 3. **Match on lowercase:** Employer names vary in capitalization | |
| 4. **Use year ranges:** Contributions span multiple years, use date filters | |
| 5. **Join with EINs:** Match FEC employer field to `nonprofits_organizations.ein` for verified linkage | |