A static site and data pipeline for publishing ranked-choice voting (RCV) election reports.
- Web UI: SvelteKit (Svelte 5) app in
src/that renders reports from a SQLite database - Data pipeline: TypeScript/Bun scripts in
scripts/that parse raw ballot data, run RCV tabulation, and write results to SQLite
- Bun (latest version)
- Git LFS for downloading election data archives
macOS:
brew install git-lfs
git lfs installLinux:
sudo apt-get install git-lfs
git lfs installSee GIT-LFS-SETUP.md for detailed instructions.
# Clone repository (Git LFS will automatically download archives)
git clone https://github.com/ranked-vote/ranked.vote.git
cd ranked.vote
# Extract election data archives to working directory
bun run report:extract
# This creates raw-data/ from the compressed archives/
# Time: ~5-10 minutes for 12 GB of dataCaution
The committed Alameda County November 2024 raw-data archive is truncated.
Its published reports remain available, but they are aggregate outputs and
cannot reconstruct the missing cast vote records. Consequently,
bun run report:extract currently reports that archive as invalid and exits
nonzero after extracting other valid archives. Replacing it requires the
complete final Alameda County CVR export; the available preliminary export is
not equivalent to the data used for the published reports.
# Install dependencies
bun install
# Start dev server
bun run dev
# Open http://localhost:3000The app reads report data from report_pipeline/reports.sqlite3 via the RANKED_VOTE_DB environment variable (set automatically in the dev and build scripts).
If you only want to view existing reports without raw data:
bun install
bun run dev
# open http://localhost:3000bun run dev: start SvelteKit dev server (withRANKED_VOTE_DBset automatically)bun run build: build static site tobuild/directorybun run preview: preview the built site locallybun run check: run Svelte type checking
bun run report(orbun scripts/preprocess.ts): full pipeline — reads raw data, parses ballots, normalizes, tabulates, and writes results to SQLitebun run report:extract: extract election data from archives toraw-data/
bun run generate-images: generate share images (automatically starts/stops dev server if needed)- Processes images in parallel (default: 5 concurrent, set
CONCURRENCYenv var to adjust) - Skips unchanged images (only regenerates when source data is newer than PNG)
- Processes images in parallel (default: 5 concurrent, set
- Card image validation is included in the test suite (
bun test)
bun install
bun run build
# output: build/The build script automatically sets RANKED_VOTE_DB to report_pipeline/reports.sqlite3.
Deploys are handled by GitHub Pages via .github/workflows/deploy-rcv-report.yml:
- On push to
main/master, CI installs dependencies, builds, and publishesbuild/to Pages
report_pipeline/
├── archives/ # Compressed data (committed to git via LFS)
│ └── us/ca/alameda/2024/11/
│ └── nov-05-general.tar.xz
├── election-metadata/ # Election configuration JSON (committed to git)
│ └── us/ca/alameda.json
├── raw-data/ # Uncompressed working data (gitignored)
│ └── us/ca/alameda/2024/11/
│ └── nov-05-general/
│ ├── CvrExport_*.json
│ └── *Manifest.json
└── reports.sqlite3 # Generated report database (committed to git)
-
Add data to
raw-data/cd report_pipeline mkdir -p raw-data/us/ca/alameda/2025/06 cp -r /path/to/new-data raw-data/us/ca/alameda/2025/06/ -
Add or update election metadata in
election-metadata/(e.g.us/ca/alameda.json) -
Run the pipeline
# From project root: bun scripts/preprocess.ts -
Compress for git
cd report_pipeline ./compress-to-archives.sh # Creates archives/ from raw-data/ (~33:1 compression)
-
Commit archives and database (not raw-data)
cd .. git add report_pipeline/archives/us/ca/alameda/2025/06/ git add report_pipeline/election-metadata/us/ca/alameda.json git add report_pipeline/reports.sqlite3 git add static/share/us/ca/alameda/2025/06/ git commit -m "Add Alameda June 2025 election" git push
See DATA-WORKFLOW.md for complete documentation.
src/: SvelteKit app (Svelte 5 components, routes, server-side DB access)src/lib/server/db.ts: SQLite database access layer
scripts/: TypeScript pipeline and utilitiesscripts/preprocess.ts: Full pipeline (raw data → SQLite)scripts/pipeline/formats/: Format readers (NIST SP 1500, NYC, Maine, etc.)scripts/pipeline/normalizers/: Ballot normalizers (simple, Maine, NYC)scripts/tabulate-rcv.ts: RCV tabulation enginescripts/compute-rcv-analysis.ts: Analysis (pairwise, Condorcet, Smith set, etc.)scripts/init-database.ts: SQLite schema
static/: static assets copied to buildstatic/share/: Generated card images for social media sharing (committed)
report_pipeline/: Election data and configurationelection-metadata/: Election configuration JSON files (committed)archives/: Compressed election data (git LFS, committed)raw-data/: Uncompressed working data (gitignored)reports.sqlite3: SQLite database with all report data (committed)
tests/: Pipeline and data validation testsbuild/: static site build output (gitignored)
- GIT-LFS-SETUP.md - Complete Git LFS setup and troubleshooting
- DATA-WORKFLOW.md - Data management workflow
- report_pipeline/README.md - Pipeline details and format reference
# First time: Extract election data
bun run report:extract
# View reports in browser
bun install && bun run dev
# Run the full pipeline (raw data → SQLite)
bun scripts/preprocess.ts
# Generate/update share images
bun run generate-images
# Run tests
bun test
# Add new election data
cd report_pipeline
cp -r /source raw-data/us/ca/alameda/2025/06/
# Edit election-metadata/us/ca/alameda.json
cd ..
bun scripts/preprocess.ts # Generate reports
bun run generate-images # Generate share images
cd report_pipeline
./compress-to-archives.sh
cd ..
git add report_pipeline/archives/ report_pipeline/reports.sqlite3 static/share/"Pointer file" errors:
- You need Git LFS installed:
brew install git-lfs && git lfs install - Pull LFS files:
git lfs pull
"No such file" in raw-data/:
- Extract archives:
bun run report:extract
Slow clone:
- Archives are large (~360 MB). Be patient or use:
GIT_LFS_SKIP_SMUDGE=1 git clone ...
See GIT-LFS-SETUP.md for more help.
Website content and generated reports may be freely distributed with attribution under CC-BY.