A static site for publishing Single Transferable Vote (STV) election reports.
- Web UI: SvelteKit (Svelte 5) app in
src/that renders published reports - Data: SQLite database (
data.sqlite3) stores election data
# Install dependencies
bun install
# Initialize the database (creates empty tables)
bun run init-db
# Start dev server
bun run dev
# Open http://localhost:5173bun run dev: start SvelteKit dev serverbun run build: build static site tobuild/directorybun run preview: preview the built site locallybun run check: run Svelte type checking
bun run init-db: initialize empty SQLite database with schemabun run load-scotland: load Scotland 2022 and 2024 council election databun run load-portland: load Portland OR November 2024 election databun run load-albany: load Albany CA November 2024 election 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
- Processes images in parallel (default: 5 concurrent, set
bun install
bun run build
# output: build/The data.sqlite3 database contains these tables:
- reports: Election contest metadata (jurisdiction, date, office, winner, etc.)
- candidates: Candidates for each contest with vote totals
- rounds: Tabulation rounds for each contest
- allocations: Vote allocations within each round
- transfers: Vote transfers between eliminated candidates
You can add election data using SQL or by creating a load script similar to scripts/load-report.js in the approval-vote project:
import { Database } from "bun:sqlite";
const db = new Database("data.sqlite3");
// Insert a report
const result = db
.prepare(
`
INSERT INTO reports (name, date, jurisdictionPath, electionPath, office, ...)
VALUES (?, ?, ?, ?, ?, ...)
`,
)
.run(...values);
const reportId = result.lastInsertRowid;
// Insert candidates
db.prepare(
`
INSERT INTO candidates (report_id, candidate_index, name, firstRoundVotes, ...)
VALUES (?, ?, ?, ?, ...)
`,
).run(reportId, ...candidateValues);
// Insert rounds, allocations, transfers...src/: SvelteKit app (Svelte 5 components, routes, API endpoints)src/lib/: Shared library codesrc/lib/server/: Server-only code (database access)src/lib/report_types.ts: TypeScript type definitions
src/routes/: SvelteKit routessrc/components/: Svelte components
static/: static assets copied to buildstatic/share/: Generated card images for social media sharing
scripts/: Utility scriptsdata.sqlite3: SQLite database with election databuild/: static site build output (gitignored)
Deploys are handled by GitHub Pages via .github/workflows/deploy.yml:
- On push to
main/master, CI installs dependencies, builds, and publishesbuild/to Pages
Scotland council election data (raw-data/scotland/2022/) is made available under the CC-BY-SA 4.0 license. Attribution: @gerrymulvenna, containing candidate data provided by Democracy Club.
The official National Results Reporting Portal provides the 2024 preference profiles in raw-data/uk/scotland/2024/.
Portland, OR election data (raw-data/us/us-portland-or/) consists of Cast Vote Records (CVR) from the November 2024 General Election. Data source: Multnomah County Elections Division. Includes contests for Mayor, Auditor, and four Councilor districts.
Albany election data (raw-data/us/albany-ca/2024/) contains contest-scoped NIST SP 1500-103 JSON cast-vote records from the Alameda County Registrar of Voters. It includes the three-seat City Council and two-seat Board of Education proportional ranked-choice voting contests.
Minneapolis election data (raw-data/us/minneapolis-mn/2025/) contains official cast-vote records from the City of Minneapolis Elections & Voter Services. It includes the three-seat Park & Recreation Commissioner At Large contest and the two-seat Board of Estimate and Taxation contest.
Website content and generated reports may be freely distributed with attribution under CC-BY.