Methodology
This page is generated from
config/pipeline.json — the same file the pipeline reads when it
computes the numbers. It cannot describe a method the build is not running,
because the build fails if the two disagree.
Pro-rata attribution, frozen at the expenditure date
For an independent expenditure made by a committee on a given date:
The ratio is frozen at the expenditure_date, stored on the
row, and never recomputed. Where that date is missing, the
dissemination_date is used.
- The ratio is computed once, stored on the row, and never recomputed. A later contribution does not change a past attribution.
- An independent expenditure dated before the donor's first contribution to that committee gets ratio 0.0 and keeps it permanently. This is the intended result, not a defect.
- A candidate's figures move only when new expenditures are filed or a filer amends history — never because the donor wrote a new check.
Support and oppose
Support and oppose are carried separately end to end and are never summed at candidate level.
A combined figure would describe a candidate as the object of a single quantity of spending, which misstates what the filings say. Summing is legitimate only at donor level, because there it describes the donor.
The published candidate schema contains no field whose name includes 'total' or 'net'. validate.py asserts this.
Duplicate filings
Schedule E rows where most_recent is false are excluded before attribution.
Independent expenditures reported on a 24- or 48-hour notice are re-reported on the committee's periodic report, and amended filings restate rows that were already filed. Counting every returned row would double-count the same spending. The excluded rows stay in data/raw/ and their count is published on each donor's figures.
This removes duplicate records of one expenditure. It never alters, nets, or reconciles the amounts themselves.
Memo rows
Schedule A rows carrying a memo code are excluded from both the numerator and the denominator.
A memo row itemizes money already counted by another row — an earmarked or bundled contribution broken out for disclosure. The FEC excludes them from a committee's receipt total for exactly this reason. Counting them inflates the denominator, which shrinks every ratio and understates the donor.
The same filter runs on the donor's own contributions. A row filed twice would otherwise inflate the numerator instead.
Itemized receipts are a subset of total receipts, so the itemized share cannot exceed 1. Counting memo rows produced 1.0325 for one committee. That assertion now gates the build.
Naming candidates
Expenditures are rolled up by candidate_id where the filing carries one, and by normalized candidate name where it does not.
The FEC returns Schedule E rows naming a candidate but carrying a null candidate_id, typically for candidates without a registered committee at the time of filing. Keying on candidate_id alone would drop that spending from the published tables without any visible trace.
validate.py asserts that every attributed dollar in the expenditure rows appears in the candidate rollup.
What these numbers are wrong about
Ratios are biased upward
The denominator counts itemized receipts only. Contributions under $200 are not itemized, so the denominator is understated and every ratio is therefore overstated. The gap is measured per committee as receipt_coverage, comparing itemized receipts against the committee totals endpoint. It is disclosed, never corrected.
501(c)(4) spending is structurally invisible
Money routed through a 501(c)(4) is not disclosed to the FEC at donor level and cannot appear here at all. Nothing on this site should be read as a complete account of a donor's political spending. The unattributed share of expenditures is published as a headline figure on every donor page.
Most committee spending is not attributed to any one donor
A donor is attributed the share of a committee's spending that their contributions funded. The remainder was funded by other contributors and is reported as not attributed, not as unknown.
No model writes a figure
No language model writes, edits, or repairs a published figure. Code, parsers, configuration and presentation are fair game. Output is not. If a number is wrong the bug is in the pipeline; the pipeline gets fixed and recomputes.
The only claim this site makes is that you can run the script yourself and get these numbers. An agent with write access to output makes that claim false in a way no reader could detect.
Which jurisdictions are included
A state is admitted only if it meets all four conditions. Every state checked is published in the coverage table with the condition it failed and the date checked.
- Itemized contributions to the committee are disclosed.
- Itemized independent expenditures name the target candidate with a support or oppose indicator.
- Data is available in bulk or via an API, not only through a paginated search form.
- Dates are precise enough to stamp the ratio at the expenditure date.
Every jurisdiction checked, and what it failed.
Assertions that gate the build
These run on every build. Any failure stops the deploy, leaving the previously published site in place rather than replacing it with numbers that did not pass.
every expenditure carries a source documentevery ratio in [0,1]no unresolved row flagsattributed never exceeds grosssupport/oppose indicator is S or Ono candidate record carries a merged totalcandidate rollup reconciles to expenditure rowsattributed share <= 1itemized share does not exceed reported receiptsmethod matches configno superseded expenditure rows were attributed