Conversation
With two or more ranked (IRV/STV) races on one ballot, the CSV export's
overvote_rank and has_duplicate_rank columns collided: the header emitted
the same unprefixed label and key once per ranked race, and the row
builder wrote every race's values into the same two keys, so the last
ranked race's values were repeated under every race's columns. An
auditor reading a multi-race export got wrong per-race data with no
warning (e.g. a duplicate rank in one race reported as TRUE in all of
them, and a real overvote in an earlier race silently erased).
Fix: scope the two columns per race, exactly the way the candidate
columns already are — keys always carry the race_id, and labels take the
`${race.title}!!` prefix under the same condition the candidate labels
use (election.races.length > 1). Single-race exports remain
byte-identical for existing consumers.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
✅ Deploy Preview for bettervoting ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
|
Warning Review limit reached
Next review available in: 52 minutes Limit details: You’ve used the included review currently available. You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. How can I continue?Wait for the limit to reset, then comment An organization admin can change what happens after included review limits in Billing. How do review limits work?CodeRabbit enforces per-developer PR review limits within each organization. For paid Pro and Pro+ reviews, CodeRabbit uses a developer's included PR review attempts over the past 7 days to set the current hourly allowance. At typical activity levels, the full plan allowance applies. Higher sustained activity can lower the allowance until earlier attempts leave the 7-day window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Description
In the ballot-data CSV export (
BallotDataExport.tsx), the per-candidate columns are race-prefixed (Race!!Candidate), but the ranked-ballot bookkeeping columnsovervote_rankandhas_duplicate_rankwere not. With two or more IRV/STV races on one ballot, the header emitted the same unprefixed label and key once per ranked race, and the row builder wrote every race's values into the same two keys — so the last ranked race's values were repeated under every race's columns. An auditor reading a multi-race export got wrong per-race data with no warning.Concretely, for a two-race election (Mayor = IRV, Council = STV) where ballot
b1has an overvote at rank 2 in Mayor and ballotb2has a duplicate rank in Council:Before (Mayor's columns silently show Council's values; b1's real overvote is erased, b2 is falsely flagged with a duplicate in Mayor):
After:
The fix, and the compatibility choice
The two columns are now scoped per race exactly the way the candidate columns already are:
${race.race_id}-overvote_rank), mirroring the candidate keys (${race.race_id}-${candidate_id}) — keys are internal, so this has no effect on the file itself.${race.title}!!prefix under the same condition the candidate labels use:election.races.length > 1. I considered prefixing only when the election has more than one ranked race (maximally conservative for existing consumers), but followed the codebase's own convention instead: the candidate columns key their prefixing on the total race count, and a mixed election (one ranked race among several) already prefixes every candidate column, so leaving two bare bookkeeping columns floating among prefixed ones is the inconsistent — and ambiguous — reading. Multi-race exports of these two columns were wrong or ambiguous before, so no correct consumer behavior is being broken there.Verification
The frontend package has no test runner configured, so I verified with a standalone harness that replicates the export's header/row assembly for the two-race election above, before and after. Assertions (all passing): headers are unique; b1's Mayor
overvote_rank= 2; b1's Councilhas_duplicate_rank= FALSE; b2's Mayorovervote_rankblank andhas_duplicate_rank= FALSE; b2's Councilhas_duplicate_rank= TRUE; and a single-race election's export is byte-identical before vs after.npx tsc --noEmitpasses inpackages/frontend.Also checked for in-repo consumers of this export: the CVR importer (
UploadElections.tsx/cvrParsers.tsx) reads a differentrank1,rank2,…column format, not this file, and no docs or tests assert the old header layout.Screenshots / Videos (frontend only)
No visual change — the export file contents change as shown in the before/after headers above.
Related Issues
Part of the ballot-data export bug list in #1556. Note for docs: the exporting-data help page written under #1545 deliberately avoided asserting that
overvote_rank/has_duplicate_rankare per-race scoped because of this bug — once this merges, that page can be tightened to state the per-race scoping outright.🤖 Generated with Claude Code