Scorecard Methodology
Every Ohio legislator gets a weighted 0–100 score on LGBTQ+ equality. The score is built from four inputs, multiplied by event-stage weights, and resolved directly from the Supabase roll-call database so every number is traceable to a specific row of data.
Every legislator is scored across four dimensions. Floor votes carry the most weight because a recorded vote is the clearest measure of how a legislator behaves when the moment of decision arrives. Sponsorship is weighted up in v2 to reward members who drive the bills that protect our community, not just those who vote on them when someone else puts the question. Committee votes are tracked separately so members who stop bad bills in committee, or advance good bills out of committee, get credit.
As of v2.1, all four inputs are resolved from the Supabase
bills / roll_calls /
legislator_vote_exceptions tables rather than a
hand-maintained spreadsheet.
roll_calls table.bills catalog.
Inside each category, not every vote counts the same.
Higher-stakes events are weighted up; procedural or
amendment-only votes are weighted down. These multipliers live
in EVENT_WEIGHTS in /js/voting-records.js
and in the roll_calls.stage column comment in
Migration 4.
As of April 22, 2026 the scorecard is backed by Migration 4
(supabase/migrations/20260424000000_scorecard.sql),
which persists three tables the public scorecard reads over
the anon role:
bills — the catalog of tracked bills. Each
row carries an editorial stance of
anti, pro, or mixed. Stance drives
scoring direction: on an anti bill, a Y vote subtracts points
and an N adds them; on a pro bill the signs flip.
Mixed bills are tracked but never auto-scored.
roll_calls — one row per recorded
legislative action (committee, amend, pass, concur, override,
introduce). Each row records the chamber, stage, vote date,
tally, and the event weight that applies.
legislator_vote_exceptions — one row per
member who broke from their party-line default, or whose
NV / E was hand-recorded. The resolver falls back to the
party-line default for every other member, so only crossovers
need an exception row.
The final per-member impact is the product of three values:
stance direction (from
bills.stance), resolved vote
(exception row if one exists, otherwise the party-line
default), and event weight (from the stage
multiplier above). Legislators who were not seated at a roll
call’s vote date resolve to - and are
filtered out so only votes that actually mattered to that
member count against them. The dataset covers both the 135th
GA (HB 68, HB 8, SB 104, SB 1 135th, HB 602 135th) and the
136th GA (HB 249, SB 1, SB 34).
legislator_vote_exceptions row exists for this member and roll call, that row wins.- (ineligible; not scored).NV and are flagged for manual review.
Every roll_calls row carries a
verificationStatus flag. Migration 5 (daily
verification) extends this into the database schema itself;
until then the flag lives in the JS editorial layer.
LGBTQ+ equality cannot be separated from racial justice, reproductive freedom, voting rights, disability justice, and economic dignity. The same legislators who attack our community almost always attack the communities we belong to. Every legislator is evaluated across ten issue families that intersect with LGBTQ+ lives:
Cross-issue inconsistency flag: when a legislator’s subscore dispersion across these ten families exceeds 35 points, the member is strong on one side of the equality map and weak on an intersecting one. We surface that gap on the card instead of letting it hide inside a composite score.
When sources conflict, the higher-authority source wins.
Provisional roll_calls rows are reconciled against
the chamber journal of record before being promoted to
verified. Hand-recorded crossovers in
legislator_vote_exceptions always override the
party-line default.
The per-member weighted total from the four inputs above is
normalized to a 0–100 scale with
max(0, min(100, 50 + raw * 5)), then the grade is
applied from the table.
| Grade | Label | Score Range |
|---|---|---|
| A+ | Champion | 90 – 100 |
| A | Strong Ally | 73 – 89 |
| B | Supportive | 55 – 72 |
| C | Mixed Record | 40 – 54 |
| D | Unfriendly | 20 – 39 |
| F | Hostile | 0 – 19 |
public.roll_calls by Migration 4public.bills by Migration 4public.legislator_vote_exceptions