Campaign Finance methodology
Campaign Finance turns public campaign-finance filings into a plain-language funding profile for each member of Congress. This page documents exactly how every number is computed, so anyone can audit it. The design goal is bipartisan defensibility: objective, denominator-transparent figures with no values judgment baked into a score.
Source
- Federal: U.S. Federal Election Commission — OpenFEC API
/candidate/{id}/totals/(aggregate, public domain). One call per member; no raw individual-contributor rows are stored or displayed in v1. Each figure links to the exact underlying FEC query. - Member ↔ FEC crosswalk: the community-maintained
unitedstates/congress-legislatorsdataset. - Cycle selection: the member's most recent labeled election cycle. FEC returns per-cycle rows plus a null-cycle "full-election" summary row that duplicates them; Campaign Finance ignores the null-cycle row so no figure is ever attributed to the wrong year.
The verification floor (why you can trust the numbers)
Before any profile renders, computed receipts must reconcile against the FEC's own Form 3 summary total: receipts must equal the sum of its eight receipt categories (individual, party, other committee, candidate self-funding, transfers, loans, other receipts, offsets). If a category field is renamed or goes null (schema drift), the residual balloons and the build fails loudly rather than publishing a wrong number about a named person. On real data this residual is 0.00%.
What we compute
Every figure is labeled with its cycle, an "as-of" date, and its denominator caveat.
| Figure | Formula | Note |
|---|---|---|
| Direct-contribution share | (itemized + unitemized individual + PAC + party) ÷ (receipts − self-funding − loans) | Share of receipts that arrived as direct contributions vs. transfers and other receipts. A low value is a transfer-funded structure (leadership PACs / joint-fundraising committees), not a disclosure failure — transfers are themselves disclosed (see below). Scoped to the candidate's own committee; does not cover outside Super-PAC / 501(c)(4) spending (true "dark money" — a separate, later figure). |
| Small-donor share (unitemized floor) | unitemized (<$200) ÷ total individual $ | Reconciles exactly against FEC's individual total; shows "—" when a committee itemizes everything (limit below). Undercounts sub-$200 gifts that ActBlue/WinRed itemize. |
| Small-dollar by transaction size | size-0 (<$200) bucket ÷ Σ all schedule_a/by_size buckets | Counts each gift by its own amount, so it captures the small repeat donors the unitemized field hides. by_size sums raw Schedule A (does not reconcile to the net individual total), so the ratio is kept self-consistent — never divided by the net individual figure — and reads conservatively (its denominator carries the ≥$200 PAC/memo money). |
| PAC share | PAC ÷ (PAC + individual) | Neutral structure, not a judgment. |
| In-state share | itemized in-state $ ÷ itemized individual $ | Itemized-only denominator (unitemized carry no address). Ships once a real OpenFEC key is wired. |
| Donor concentration | top-20-donor-by-raw-name-string share | Exact and auditable; no fuzzy dedup. One raw Schedule A pull, current cycle only. Ships with a real key. |
The #1 bias guard: unitemized money is NOT dark money
Contributions under $200 are disclosed in aggregate — the FEC does not require itemization, but the money is fully reported. Campaign Finance counts unitemized dollars as accounted-for in the numerator of disclosure completeness, and never folds them into a "dark / undisclosed" bucket. Folding them in would systematically penalize small-dollar-heavy campaigns — the single largest source of partisan bias in a naïve completeness score. Self-funding and loans are tracked as their own labeled category, never merged into "individual" or "traceable."
Known limitation: small-donor share and all-itemizing committees
A contribution is itemized once a donor's cycle total passes $200 — but a committee may itemize every contribution regardless of size, and the major processors (ActBlue, WinRed) itemize each transaction by default. Such a committee reports $0 unitemized even when it has many sub-$200 donors; that money sits inside the itemized total. So unitemized ÷ individual understates grassroots support for all-itemizing committees, and a raw 0% would falsely read as "no small donors" — the same asymmetric-bias failure as the guard above, in reverse.
Campaign Finance shows "—" (not 0%) for this unitemized-floor metric whenever a committee reports $0 unitemized against nonzero itemized (confirmed: Whitehouse 2024 reports $0 unitemized at both the candidate and committee level — a real FEC record, not a data gap). Alongside it, a size-based measure now ships from the schedule_a/by_size aggregate (the under-$200 size bucket), which counts contributions by dollar size independent of itemization status — filling in a real small-dollar figure exactly where the unitemized floor reads "—" (Whitehouse: 22.8%).
One calibrated caveat, load-bearing enough to state plainly: schedule_a/by_size aggregates raw Schedule A line items, and for PAC/memo/earmark-heavy filers its bucket sum runs above the member's net individual total (Whitehouse: Σbuckets $4.25M vs net individual $2.70M, same 2-year window); it matches the net total only for clean single-conduit grassroots filers (Sanders exact, AOC ≈0.5%). It therefore does not reconcile to the individual-contribution floor — so the metric is kept self-consistent (size-0 bucket ÷ sum of all buckets) and is never divided by the net individual figure, which would cross a raw basis with a net one and misstate a named person. This size-based source superseded an earlier plan to read the split from /committee/{id}/totals/, which carries the same unitemized blind spot.
Percentile placement
A profile can carry one line placing the member's total receipts (the FEC receipts figure shown on the profile) among their peers. Peers are the other profiled members of the same chamber in the same election cycle: House and Senate totals run at different scales, and a partial-cycle total is not comparable to a full-cycle one, so neither is pooled.
- Minimum pool. The line is omitted entirely when the member has fewer than 20 such peers (members without a receipts figure don't count toward it). The sentence reads as a chamber-wide placement, which is only accurate when the pool resembles the chamber; at 20 peers, any one peer moves the placement by at most 5 points.
- How it is computed. The share of peers whose receipts are strictly lower than the member's. A tie counts as beating neither member, so two members with identical totals get the same placement.
- How it is stated. Only a member who out-raised every peer reads as having raised more than every other member of their chamber that cycle. Everyone else is stated as an approximate percentile, rounded and capped at 99 — so in a ~420-member House pool the second-highest total reads as the 99th percentile, not the top. A member tied for last reads as the 0th percentile, never as having raised less than every peer.
It is a factual placement, never a grade: no grade label, color band, or superlative is attached to it.
Sector / industry breakdown
The sector breakdown groups a member's itemized individual contributions by the employer and occupation each contributor reported.
- Data. Itemized individual Schedule A rows of the member's principal campaign committee (FEC designation P), for the same two-year cycle as the rest of the profile. When the candidate has no single principal committee, no breakdown is shown. The pull is bounded: rows are read largest first, up to 10 pages of 100 (about 1,000 rows). Each row's employer and occupation are reduced to a sector label as the row arrives and then discarded; only per-sector aggregates are stored or shown, never a contributor's name or row.
- Classification. A static keyword heuristic over the reported employer and occupation — not an industry-code crosswalk. Rules are checked in a fixed order and the first matching rule wins; keywords match their common word forms (bank, banker, banking), and a keyword phrase still matches when it is split across the two fields. The order is: Retired / not employed; an employer named Department (or Dept.) of Labor (Government); a real estate, property or oil and gas investor (Real estate, Energy); Finance / insurance; a union named as the employer — by the people it represents, by a well-known union's name or acronym (Labor / union); Legal; Healthcare; Technology; Real estate; Energy; Education; the word union, AFL-CIO or Teamsters in the employer, or the word labor anywhere (Labor / union); Agriculture; Media / entertainment; other government words (government, federal employee, state of, city of, county of; Government); Self-employed; and last, a union named only in the occupation (Labor / union). A contributor who reports being retired or not employed, or who gives retiree as their occupation or as the whole employer, is filed under Retired / not employed whatever else the row says. A government or self-employed contributor whose occupation names a sector checked earlier (a city attorney or a self-employed physician, for example) is filed under that sector, except at a department of labor, which is checked second.
- Notable rules. Credit unions, including ones named for their members or abbreviated FCU, are filed under Finance / insurance, which is checked before every union rule — as is a row whose employer or occupation contains another Finance / insurance word (capital, bank, insurance or financial, for example; a union's financial secretary). A union named as the employer is the contributor's industry and is filed under Labor / union ahead of the occupation-based sectors: a union named for the people it represents (a teachers' or nurses' union, a union of engineers, a workers' or employees' union), or a well-known union by name or by an acronym that names nothing else likely to appear there (SEIU, AFSCME, UAW, IBEW, Teamsters, AFL-CIO and the National Education Association, for example). Short acronyms that are also ordinary words or other organizations' names count only in an unambiguous form, such as with a local number, and a union's multi-word name only matches inside one field — except that a name truncated across the two, with the employer field ending in the occupation word (employer "Chicago Teachers", occupation "Union Organizer"), still matches. Benefit funds and training programs named for a union are filed with that union. A union named only in the occupation describes membership: the employer's sector wins (a nurse at a hospital who lists their union is filed under Healthcare), and the union decides the sector only when nothing else about the row matched. An employer field that names a city, state, county, hospital or university is treated as that employer even if it also names a union. Unions the rules don't recognize are filed by their other words and the reported occupation. A few company names that contain the word union (Western Union, Union Pacific) are kept out of Labor / union. An investor is filed under Real estate or Energy only when the row reads "real estate", "property" or "oil and gas" directly followed by investor, investment or investing; other investors are Finance / insurance.
- Unclassified. A row that matches no rule is counted under "unclassified", dollars included, rather than dropped, so unmatched dollars stay in the totals. Employer and occupation are free text, so some rows will be filed imperfectly.
- Privacy floor (k-anonymity). Sector size is counted in distinct donors, not transactions; contributor names are used only in memory for that count. Before counting, a name is lowercased, runs of spaces are collapsed to one and trimmed from the ends, and trailing periods, commas, semicolons and colons (with any spaces among them) are dropped, so names that differ only in those ways count once; other variants of one person's name (with and without a middle initial or a comma, or in a different word order) count as separate donors. A sector with fewer than 5 distinct donors (the pipeline's
DEFAULT_K) is folded into a single "All other (suppressed)" bucket, counting a donor who appears in several folded sectors once. If that combined bucket still has fewer than 5 distinct donors, the whole breakdown is withheld: a reader could otherwise subtract the visible sectors from the itemized total and recover the small group's exact amount. - Reconciliation. When the pull is complete (it did not reach the page limit), the displayed sectors, including the suppressed bucket, must sum to within 0.5% of the committee's own itemized-individual total for the cycle (
individual_itemized_contributionsfrom/committee/{id}/totals/). If they don't, the breakdown is omitted rather than shown on an unsound basis. A pull that reaches the limit is a known partial sample of the largest contributions and cannot reconcile by design; it is shown with a caveat that its sector totals are approximate.
Sectors are listed largest first, each with its dollar total and its share of the committee's itemized-individual total. Each stored breakdown records the classifier version that produced it; when the rules change, a breakdown from an earlier version is treated as stale and re-pulled on the next build that runs the contribution pulls. If that re-pull fails, a member whose stored data is reused keeps the earlier breakdown, any other member shows none for that build, and the pull is retried on the next such build.
What we deliberately do NOT do
- No blended composite score. Any weighting across figures encodes a values judgment and is the prime bias-attack surface. Campaign Finance ships one objective completeness number plus an unweighted profile — never a single ranked "grade."
- No donor-propensity / likelihood-to-give modeling. Modeling is scoped strictly to transparency, completeness, and diversity.
- The auto-generated "notable context" line is derived only from figures already shown on the page, is machine-labeled as such, and never imputes wrongdoing.
Versioning
Campaigns will optimize to whatever Campaign Finance measures (Goodhart's law), so the methodology is versioned; material changes are dated here. The Transparency Score/stamp (provisionally named "HillTrack") and outlier flags are held until after the November 2026 midterm — pre-election filings are incomplete and constantly amended exactly when demand peaks, and a scoring tool shipped mid-election compounds perception risk.
- 2026-10-09 — Sector classifier corrected and extended. Keywords now match their common word forms (financial, banker, farmer, healthcare, attorneys and similar); several had been written as bare stems that never matched a complete word, so those rows had been left "unclassified" or filed under a sector checked later. Credit unions and State Farm are now Finance / insurance and an employer named Department of Labor is Government; these name rules run ahead of the occupation-based sectors, so such rows move from Labor / union (credit unions, a department of labor), Agriculture (State Farm), "unclassified" (credit unions abbreviated FCU) or the sector their occupation named. Western Union and Union Pacific are kept out of Labor / union. A union named as the employer (SEIU, UAW, IBEW, AFSCME, a teachers' union and others) is now Labor / union ahead of the occupation-based sectors, so a union's own staff no longer land under Legal, Healthcare, Education or "unclassified". A union named only in the occupation now defers to the employer's sector, so some union members move out of Labor / union into their employer's sector (Healthcare, Education, Government and others). Retirees are filed as retired, and a sector's distinct-donor count no longer splits a name that differs only by a space before trailing punctuation. Reclassification moves dollars between sectors without changing the total, and can change which sectors are folded into "All other (suppressed)" or whether a breakdown clears the privacy floor. Stored breakdowns from the earlier classifier are re-pulled. See "Sector / industry breakdown" above.
- 2026-10-09 — Percentile placement now requires at least 20 same-chamber, same-cycle peers and is omitted below that. Appointed and special-election members can land in a cycle pool of only a few members, where a comparison against one or a handful of peers had read as a chamber-wide placement. The percentile is also capped at 99, so only a member who out-raised every peer reads as having raised more than every other member; plain rounding had put the second-highest total in a ~420-member House pool at the 100th percentile. See "Percentile placement" above.
- 2026-09-07 — Small-dollar-by-transaction-size measure (from
schedule_a/by_size) now ships as a second figure beside the unitemized floor, not a replacement for it. Calibration foundby_sizeis a raw Schedule A basis that does not reconcile to the net individual total, so the ratio is computed self-consistently (size-0 ÷ Σ buckets), never as size-0 ÷ net individual. See "Known limitation" above. - 2026-09-05 — Small-donor share now shows "—" instead of 0% when a committee itemizes every contribution (reports $0 unitemized against nonzero itemized). Planned v1.1 source for the metric changed from
/committee/{id}/totals/toschedule_a/by_size(the earlier plan shared the sameunitemizedblind spot). See "Known limitation" above. - 2026-09-05 — Renamed the completeness metric to "Direct-contribution share" and reframed its copy. A full-chamber build surfaced transfer-funded committees (e.g. leadership at ~18% under the old label) whose money is disclosed transfers, not hidden — the word "completeness" wrongly implied the remainder was undisclosed. The formula is unchanged; only the label and framing changed so a transfer-funded structure is not read as a transparency failure.