Commonwealth Index

74 days to 2026-11-03

Methods

How the scores are built

Written so a technically literate skeptic can rerun the pipeline and match every published number. The estimator does not know who built the site.

Reproduce this

Three commands from a clean machine. data/votes.csv is the adjudication input; vact export-web recomputes scores, empirical Bayes hyperparameters, and this page's worked example. No number on the scorecard is stored in the warehouse.1

git clone https://github.com/EAName/Congressional-Tracker.git
cd Congressional-Tracker && uv sync
./bin/vact votes validate && ./bin/vact audit symmetry && ./bin/vact export-web

1. Vote selection

The measured axis is small business climate: Positive = a YEA advances the small-business / affordability axis (lower input costs, easier access to capital, lighter compliance burden). Direction is not inferred from party. It is a per-vote valence: does a YEA advance that axis (+1) or oppose it (−1)?

A roll call enters a score only if all of the following hold:

  1. vote_category is in AMENDMENT, PASSAGE. Currently excluded: CLOTURE, MOTION_TO_RECOMMIT, NOMINATION, PROCEDURAL, SUSPENSION.
  2. An adjudicated valence of ±1 exists for that (vote_id, impact_tag). Un-adjudicated pairs (no valence row, or valence 0) are dropped. RULE-proposed valence is a proposal until a human promotes it.
  3. Special-rule resolutions (“Providing for consideration of …”) are excluded even when they land in PASSAGE. They set debate terms; they are not a policy position on the underlying bill.

“Procedural” here means the excluded categories plus those special-rule resolutions. It is a category filter, not a judgment that the vote was unimportant.

Adjudication lives in the public file data/votes.csv (unique key member_bioguide_id, rollcall_id, theme). Valence was exported from fact_vote_valence; the CSV is what scoring reads at runtime. Who adjudicated is the adjudicator column (HUMAN / RULE / LLM). Join key across the system is bioguide_id only. Corrections policy.

2. Scoring

Let n be the member's contested votes (YEA or NAY) on a theme, and k the count that advanced the axis. Absences (NOT_VOTING, PRESENT) do not enter k or n; they are reported separately as an absence rate. A cell is labeled sufficient when n3.

Cosponsorship is a second signal, not a second vote. Among adjudicated bills a member sponsored or cosponsored, k/n is the share that advance the axis, shrunk with the same beta-binomial estimator. Silence is not a Nay. Cosponsorship is cheap talk relative to a floor vote, so it is drawn as a hollow marker on the forest plot and is never averaged into the headline score.

p_raw = k / n
raw_score = 2 p_raw − 1          ∈ [−1, +1]
Wilson band = 2 · Wilson(k, n; z=1.96) − 1

Vote depths are small, so the published default is a beta-binomial empirical Bayes estimate, fit separately per (theme, party).2 Method: moments. Caucuses with fewer than 3 members who cast a contested vote use Beta(2, 2) and are flagged weakly_informative. Unanimous caucuses use a pooled prior with half-pseudocounts (degenerate) so the prior mean stays at the caucus edge instead of 0.5.

prior      Beta(α, β)     from the caucus (k_i, n_i)
posterior  Beta(α + k, β + n − k)
eb_score   = 2 · (α+k) / (α+β+n) − 1
cred band  = 2 · Beta-quantile_{0.025, 0.975} − 1

Hyperparameters in this build

These α, β values are written at export time from the live frame. They will change when data/votes.csv changes.

ThemePartyαβSourceEB centerMembers
Access to capitalDemocrat5.5000.500degenerate+0.835
Access to capitalRepublican0.5005.500degenerate-0.835
Federal contractingDemocrat2.0002.000weakly_informative+0.006
Federal contractingRepublican5.5000.500degenerate+0.835
Health costsDemocrat6.5000.500degenerate+0.866
Health costsRepublican0.5005.500degenerate-0.835
Input costsDemocrat0.1182.715moments-0.926
Input costsRepublican20.5000.500degenerate+0.955
Compliance and reportingDemocrat0.4116.007moments-0.876
Compliance and reportingRepublican22.7021.098moments+0.915
Taxes and creditsDemocrat1.2600.540moments+0.405
Taxes and creditsRepublican10.5000.500degenerate+0.915
WorkforceDemocrat0.50012.500degenerate-0.926
WorkforceRepublican10.5000.500degenerate+0.915

Worked example

Eugene Simon Vindman (Democrat, VA-7), theme Input costs. This cell had the largest |EB − raw| among sufficient cells in the current export.

  1. Counts: k = 1 advancing votes out of n = 4 contested, so p_raw = 1/4 = 0.2500.
  2. Raw signed score: 2 × 0.2500 − 1 = -0.50. Wilson 95% band [-0.91, +0.40].
  3. Caucus prior: Beta(0.1181, 2.7153), source moments, mean 0.0417 (signed -0.92).
  4. Posterior: Beta(1.1181, 5.7153) = Beta(0.1181+1, 2.7153+3). Mean 0.1636.
  5. Shrunk signed score: 2 × 0.1636 − 1 = -0.67. Credible 95% band [-0.99, -0.01]. Shrinkage -0.17 from the raw point.

3. Uncertainty

The 95% credible interval is the central 95% of the member's posterior Beta on the signed scale, given the fitted (theme, party) prior and that member's k, n. It is not a frequentist confidence interval, and it is not a statement about sampling from a superpopulation of bills. If the prior is wrong, the interval is wrong in the same direction.3

Interval overlap on the compare module is a visual heuristic. Non-overlap is not a hypothesis test, does not control Type I error, and is not adjusted for the number of pairwise looks on the page.

How much vote depth is needed to reliably separate two members 0.25 apart on the signed scale? Simulation, not a slogan: two independent binomials with true signed scores +0.00 and +0.25 (p = 0.5 and 0.6250), equal n, 95% credible intervals, 2000 draws per n, 80% power defined as P(intervals disjoint).

  • Under the weakly-informative fallback Beta(2, 2): no n ≤ 400 reached 80% power (power at n=400 was 0.779; shrinkage toward a shared prior makes a 0.25 gap harder to declare than a Wilson calculation suggests).
  • Under this build's worked-example prior Beta(0.118, 2.715), moments: no n ≤ 400 reached 80% power (power at n=400 was 0.779). A peaked caucus prior can demand more votes to declare a 0.25 gap, because both members are pulled toward the same center.

Most published cells in this tracker have n in the single digits. A 0.25 gap is generally not identifiable at current depth. That is why the bands are the claim, not the point.

4. Symmetry audit and falsification

Downstream scoring is party-blind arithmetic. Bias can enter when roll calls are chosen or when axis direction is coded. This build pre-registers inclusion in VOTE_INCLUSION_SPEC.md and publishes every excluded pair in data/votes_excluded.csv.

Spec version vote-inclusion-v1.0. Blind-coded share: 100% of roll-call × theme units not blind-coded. Any tripped threshold is a signal to re-open adjudication, not proof of bias.

Excluded roll calls by reason

ReasonCount
NEAR_UNANIMOUS1
NO_IMPACT_TAG338
PROCEDURAL_CATEGORY1067
RULE_RESOLUTION63
UNADJUDICATED_DIRECTION33

Caucus majority advancing the coded axis

ThemeDem shareRep shareGap (pp)
Access to capital100%0%100
Federal contracting0%100%-100
Health costs100%0%100
Input costs0%100%-100
Compliance and reporting0%92%-92.3
Taxes and credits50%100%-50
Workforce0%100%-100

Falsification thresholds

  • caucus_advancing_gap: >|threshold| percentage-point gap between parties' caucus-majority advancing share on the same theme — tripped in this build. Re-open adjudication for themes that trip; log reason in this spec's changelog.
  • n_depth_median_gap (threshold 2): >|threshold| vote-depth median between parties — ok. Audit scoreable filter and theme assignment for thin caucus evidence.
  • ci_width_gap_at_matched_n (threshold 0.08): >|threshold| credible-interval width gap at the same n — tripped in this build. Investigate estimator parity (should be party-blind arithmetic).
  • exclusion_rate_gap_pp (threshold 10): >|threshold| pp gap in exclusion rate by sponsor party leadership — tripped in this build. Review votes_excluded.csv and inclusion keywords.
  • coded_blind_false_share_pp (threshold 5): >|threshold| of adjudications not blind-coded — tripped in this build. Route new rows through valence_review_queue before commit.

5. Historical challenger records

Challengers with prior House service (Elaine Luria, 116th–117th; Tom Perriello, 111th) are scored with the same pipeline as incumbents: identical themes, adjudication schema, and empirical Bayes shrinkage. Shannon Taylor has no federal voting record; VA-1 shows incumbent-only comparison.

Historical rows carry a congress_era tag. Caucus priors for shrinkage are fit from the Democratic caucus in that Congress, not from the current Virginia delegation. Head-to-head modules always show this caption (it cannot be disabled):

Scored on votes from the [era] Congress; themes matched by adjudication, not identical bills. Cross-era comparison is indicative, not exact.

Review queues: data/historical_rollcall_review.csv (roll-call theme proposals) and data/votes_historical_candidates.csv (full caucus member rows). Run vact historical propose after backfilling 111th / 116th / 117th House roll calls.

6. Known limitations

  • Small n. Sufficiency is n ≥ 3. Several themes still sit on the weakly-informative or degenerate prior because the caucus sample is thin or unanimous.
  • Theme coverage. Only impact tags with adjudicated valence appear. A member can look extreme on a theme that has two votes and ordinary on one that has twelve.
  • No consequence weights. A minibus and a narrow amendment count the same. Bill importance is not in the likelihood.
  • Single-state scope. The scorecard is the Virginia delegation. Senate roll calls include 100 members in the warehouse; scoring still joins VA legislators only.
  • Authorship. Operators build and maintain the pipeline; see the About page. Valence is a political judgment. The math above is the estimator, not a claim of neutrality about which bills belong on the axis.
  • Map version. District attributes are keyed to map version 2021 unless an export says otherwise. Mixing 2021 voting geography with 2026 targeting attributes votes to the wrong electorate.

7. Changelog

Auto-generated from git log of data/votes.csv, src/vact/analysis/scoring.py, src/vact/analysis/estimators.py, and config/scoring.yaml.

DateCommitSubject
2026-08-19828fd34Add challenger historical scoring and head-to-head race module.
2026-08-191b3ba9cAdd methodology, time series, IRT, and cosponsorship surfaces.
2026-08-19343d3ddAdd per-caucus empirical Bayes shrinkage for signed scores.
2026-08-19348b066Add versioned votes.csv as the scoring adjudication layer.
2026-08-04a8f82e8Add signed scoring frame and within-party deviation report.

Notes

  1. Signed scores are never written to DuckDB. Only valence (the political input) is persisted. See AGENTS.md §8.
  2. A single national prior would shrink Democrats toward Republicans. The (theme, party) cell is the grouping on purpose.
  3. Equal-tailed posterior quantiles, mapped through 2x−1. They are not highest-density intervals, and they inherit whatever misfit the method-of-moments (or MLE) prior has.