diff --git a/.github/workflows/generate_sample_data.yml b/.github/workflows/generate_sample_data.yml new file mode 100644 index 0000000..c8c7d18 --- /dev/null +++ b/.github/workflows/generate_sample_data.yml @@ -0,0 +1,37 @@ +name: Generate Sample Data + +on: + workflow_dispatch: + pull_request: + paths: + - ".github/workflows/generate_sample_data.yml" + - "Makefile" + - "scripts/generate_sample_data.py" + - "data/sample/manifests/**" + +env: + SAMPLE_DATA_PYTHON_VERSION: 3.9 + +jobs: + generate: + name: Generate Sample Data + runs-on: ubuntu-latest + steps: + - name: Checkout Code + uses: actions/checkout@v5 + - name: Set up Python ${{ env.SAMPLE_DATA_PYTHON_VERSION }} + uses: actions/setup-python@v6 + with: + python-version: ${{ env.SAMPLE_DATA_PYTHON_VERSION }} + - name: Install sample data generator dependencies + run: python -m pip install electionguard==1.4.0 dacite==1.6.0 hypothesis + - name: Package Sample Data + run: make release-package-sample-data PYTHON=python + - name: Publish Sample Data + uses: actions/upload-artifact@v4 + with: + name: sample-data + path: | + sample-data.zip + sample-data.tar.gz + overwrite: true diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index bd8d0fa..496c497 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -7,6 +7,7 @@ on: env: PYTHON_VERSION: 3.14.0 + SAMPLE_DATA_PYTHON_VERSION: 3.9 jobs: publish: @@ -28,11 +29,19 @@ jobs: run: pipenv install - name: Deploy run: pipenv run make deploy - - name: Zip Sample Data - run: pipenv run make release-zip-sample-data + - name: Set up Python ${{ env.SAMPLE_DATA_PYTHON_VERSION }} for sample data + uses: actions/setup-python@v6 + with: + python-version: ${{ env.SAMPLE_DATA_PYTHON_VERSION }} + - name: Install sample data generator dependencies + run: python -m pip install electionguard==1.4.0 dacite==1.6.0 hypothesis + - name: Package Sample Data + run: make release-package-sample-data PYTHON=python - name: Publish Sample Data uses: actions/upload-artifact@v4 with: name: sample-data - path: sample-data.zip + path: | + sample-data.zip + sample-data.tar.gz overwrite: true diff --git a/.gitignore b/.gitignore index dda71d5..6c6da0d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ node_modules/ .DS_Store -site/ \ No newline at end of file +site/ +data/sample/generated/ +sample-data.zip +sample-data.tar.gz diff --git a/Makefile b/Makefile index 631b7b3..a7986eb 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,10 @@ -.PHONY: all install lint serve build deploy +.PHONY: all install lint serve build deploy generate-sample-data release-package-sample-data release-zip-sample-data release-tar-sample-data + +PYTHON ?= python3 +SAMPLE_DATA_VERSION ?= 0.95.0 +SAMPLE_DATA_OUTPUT ?= data/sample/generated +SAMPLE_BALLOT_COUNT ?= 5 +SAMPLE_BALLOT_SPOIL_RATE ?= 50 all: install serve @@ -23,6 +29,16 @@ labels: github-label-sync --labels .github/labels.yml Election-Tech-Initiative/electionguard-api-python github-label-sync --labels .github/labels.yml Election-Tech-Initiative/electionguard-ui +generate-sample-data: + @echo 🔁 GENERATE SAMPLE DATA + $(PYTHON) scripts/generate_sample_data.py --clean --private-data --version $(SAMPLE_DATA_VERSION) --output-dir $(SAMPLE_DATA_OUTPUT) --number-of-ballots $(SAMPLE_BALLOT_COUNT) --spoil-rate $(SAMPLE_BALLOT_SPOIL_RATE) + +release-package-sample-data: generate-sample-data release-zip-sample-data release-tar-sample-data + release-zip-sample-data: @echo 📁 ZIP SAMPLE DATA - zip -r sample-data.zip data + zip -r sample-data.zip $(SAMPLE_DATA_OUTPUT) + +release-tar-sample-data: + @echo 📁 TAR SAMPLE DATA + tar -czf sample-data.tar.gz $(SAMPLE_DATA_OUTPUT) diff --git a/data/sample/manifests/0.95.0/full.json b/data/sample/manifests/0.95.0/full.json new file mode 100644 index 0000000..aeeff24 --- /dev/null +++ b/data/sample/manifests/0.95.0/full.json @@ -0,0 +1,420 @@ +{ + "election_scope_id": "jefferson-county-primary", + "spec_version": "v0.95", + "type": "primary", + "start_date": "2020-03-01T08:00:00-05:00", + "end_date": "2020-03-01T20:00:00-05:00", + "geopolitical_units": [ + { + "object_id": "jefferson-county", + "name": "Jefferson County", + "type": "county", + "contact_information": { + "address_line": [ + "1234 Samuel Adams Way", + "Jefferson, Hamilton 999999" + ], + "email": [ + { + "annotation": "inquiries", + "value": "inquiries@jefferson.hamilton.state.gov" + } + ], + "phone": [ + { + "annotation": "domestic", + "value": "123-456-7890" + } + ], + "name": "Jefferson County Clerk" + } + }, + { + "object_id": "harrison-township", + "name": "Harrison Township", + "type": "township", + "contact_information": { + "address_line": [ + "1234 Thorton Drive", + "Harrison, Hamilton 999999" + ], + "email": [ + { + "annotation": "inquiries", + "value": "inquiries@harrison.hamilton.state.gov" + } + ], + "phone": [ + { + "annotation": "domestic", + "value": "123-456-7890" + } + ], + "name": "Harrison Town Hall" + } + }, + { + "object_id": "harrison-township-precinct-east", + "name": "Harrison Township Precinct", + "type": "township", + "contact_information": { + "address_line": [ + "1234 Thorton Drive", + "Harrison, Hamilton 999999" + ], + "email": [ + { + "annotation": "inquiries", + "value": "inquiries@harrison.hamilton.state.gov" + } + ], + "phone": [ + { + "annotation": "domestic", + "value": "123-456-7890" + } + ], + "name": "Harrison Town Hall" + } + }, + { + "object_id": "rutledge-elementary", + "name": "Rutledge Elementary School district", + "type": "school", + "contact_information": { + "address_line": [ + "1234 Wolcott Parkway", + "Harrison, Hamilton 999999" + ], + "email": [ + { + "annotation": "inquiries", + "value": "inquiries@harrison.hamilton.state.gov" + } + ], + "phone": [ + { + "annotation": "domestic", + "value": "123-456-7890" + } + ], + "name": "Rutledge Elementary School" + } + } + ], + "parties": [ + { + "object_id": "whig", + "name": { + "text": [ + { + "value": "Whig Party", + "language": "en" + } + ] + }, + "abbreviation": "WHI", + "color": "AAAAAA", + "logo_uri": "http://some/path/to/whig.svg" + }, + { + "object_id": "federalist", + "name": { + "text": [ + { + "value": "Federalist Party", + "language": "en" + } + ] + }, + "abbreviation": "FED", + "color": "CCCCCC", + "logo_uri": "http://some/path/to/federalist.svg" + }, + { + "object_id": "democratic-republican", + "name": { + "text": [ + { + "value": "Democratic Republican Party", + "language": "en" + } + ] + }, + "abbreviation": "DEMREP", + "color": "EEEEEE", + "logo_uri": "http://some/path/to/democratic-repulbican.svg" + } + ], + "candidates": [ + { + "object_id": "benjamin-franklin", + "name": { + "text": [ + { + "value": "Benjamin Franklin", + "language": "en" + } + ] + }, + "party_id": "whig", + "image_uri": null, + "is_write_in": null + }, + { + "object_id": "john-adams", + "name": { + "text": [ + { + "value": "John Adams", + "language": "en" + } + ] + }, + "party_id": "federalist", + "image_uri": null, + "is_write_in": null + }, + { + "object_id": "john-hancock", + "name": { + "text": [ + { + "value": "John Hancock", + "language": "en" + } + ] + }, + "party_id": "democratic-republican", + "image_uri": null, + "is_write_in": null + }, + { + "object_id": "write-in", + "name": { + "text": [ + { + "value": "Write In Candidate", + "language": "en" + }, + { + "value": "Escribir en la candidata", + "language": "es" + } + ] + }, + "party_id": null, + "image_uri": null, + "is_write_in": true + }, + { + "object_id": "referendum-pineapple-affirmative", + "name": { + "text": [ + { + "value": "Pineapple should be banned on pizza", + "language": "en" + } + ] + }, + "party_id": null, + "image_uri": null, + "is_write_in": null + }, + { + "object_id": "referendum-pineapple-negative", + "name": { + "text": [ + { + "value": "Pineapple should not be banned on pizza", + "language": "en" + } + ] + }, + "party_id": null, + "image_uri": null, + "is_write_in": null + } + ], + "contests": [ + { + "object_id": "justice-supreme-court", + "sequence_order": 0, + "electoral_district_id": "jefferson-county", + "vote_variation": "n_of_m", + "number_elected": 2, + "votes_allowed": 2, + "name": "Justice of the Supreme Court", + "ballot_selections": [ + { + "object_id": "john-adams-selection", + "sequence_order": 0, + "candidate_id": "john-adams" + }, + { + "object_id": "benjamin-franklin-selection", + "sequence_order": 1, + "candidate_id": "benjamin-franklin" + }, + { + "object_id": "john-hancock-selection", + "sequence_order": 2, + "candidate_id": "john-hancock" + }, + { + "object_id": "write-in-selection", + "sequence_order": 3, + "candidate_id": "write-in" + } + ], + "ballot_title": { + "text": [ + { + "value": "Justice of the Supreme Court", + "language": "en" + }, + { + "value": "Juez de la corte suprema", + "language": "es" + } + ] + }, + "ballot_subtitle": { + "text": [ + { + "value": "Please choose up to two candidates", + "language": "en" + }, + { + "value": "Uno", + "language": "es" + } + ] + } + }, + { + "object_id": "referendum-pineapple", + "sequence_order": 1, + "electoral_district_id": "harrison-township", + "vote_variation": "one_of_m", + "number_elected": 1, + "votes_allowed": 1, + "name": "The Pineapple Question", + "ballot_selections": [ + { + "object_id": "referendum-pineapple-affirmative-selection", + "sequence_order": 0, + "candidate_id": "referendum-pineapple-affirmative" + }, + { + "object_id": "referendum-pineapple-negative-selection", + "sequence_order": 1, + "candidate_id": "referendum-pineapple-negative" + } + ], + "ballot_title": { + "text": [ + { + "value": "Should pineapple be banned on pizza?", + "language": "en" + }, + { + "value": "\u00bfDeber\u00eda prohibirse la pi\u00f1a en la pizza?", + "language": "es" + } + ] + }, + "ballot_subtitle": { + "text": [ + { + "value": "The township considers this issue to be very important", + "language": "en" + }, + { + "value": "El municipio considera que esta cuesti\u00f3n es muy importante", + "language": "es" + } + ] + } + } + ], + "ballot_styles": [ + { + "object_id": "jefferson-county-ballot-style", + "geopolitical_unit_ids": [ + "jefferson-county" + ], + "party_ids": null, + "image_uri": null + }, + { + "object_id": "harrison-township-ballot-style", + "geopolitical_unit_ids": [ + "jefferson-county", + "harrison-township" + ], + "party_ids": null, + "image_uri": null + }, + { + "object_id": "harrison-township-precinct-east-ballot-style", + "geopolitical_unit_ids": [ + "jefferson-county", + "harrison-township", + "harrison-township-precinct-east", + "rutledge-elementary" + ], + "party_ids": null, + "image_uri": null + }, + { + "object_id": "rutledge-elementary-ballot-style", + "geopolitical_unit_ids": [ + "jefferson-county", + "harrison-township", + "rutledge-elementary" + ], + "party_ids": null, + "image_uri": null + } + ], + "name": { + "text": [ + { + "value": "Jefferson County Spring Primary", + "language": "en" + }, + { + "value": "Primaria de primavera del condado de Jefferson", + "language": "es" + } + ] + }, + "contact_information": { + "address_line": [ + "1234 Paul Revere Run", + "Jefferson, Hamilton 999999" + ], + "email": [ + { + "annotation": "press", + "value": "inquiries@hamilton.state.gov" + }, + { + "annotation": "federal", + "value": "commissioner@hamilton.state.gov" + } + ], + "phone": [ + { + "annotation": "domestic", + "value": "123-456-7890" + }, + { + "annotation": "international", + "value": "+1-123-456-7890" + } + ], + "name": "Hamilton State Election Commission" + } +} \ No newline at end of file diff --git a/data/sample/manifests/0.95.0/hamilton-general.json b/data/sample/manifests/0.95.0/hamilton-general.json new file mode 100644 index 0000000..a676b1a --- /dev/null +++ b/data/sample/manifests/0.95.0/hamilton-general.json @@ -0,0 +1,2005 @@ +{ + "election_scope_id": "hamilton-county-general-election", + "spec_version": "v0.95", + "type": "general", + "start_date": "2020-03-01T08:00:00-05:00", + "end_date": "2020-03-01T20:00:00-05:00", + "geopolitical_units": [ + { + "object_id": "hamilton-county", + "name": "Hamilton County", + "type": "county", + "contact_information": { + "address_line": [ + "1234 Samuel Adams Way", + "Hamilton, Ozark 99999" + ], + "email": [ + { + "annotation": "inquiries", + "value": "inquiries@hamiltoncounty.gov" + } + ], + "phone": [ + { + "annotation": "domestic", + "value": "123-456-7890" + } + ], + "name": "Hamilton County Clerk" + } + }, + { + "object_id": "congress-district-5", + "name": "Congressional District 5", + "type": "congressional", + "contact_information": { + "address_line": [ + "1234 Somerville Gateway", + "Medford, Ozark 999999" + ], + "email": [ + { + "annotation": "inquiries", + "value": "inquiries@congressional-district-5.gov" + } + ], + "phone": [ + { + "annotation": "domestic", + "value": "123-456-7890" + } + ], + "name": "Medford Town Hall" + } + }, + { + "object_id": "congress-district-7", + "name": "Congressional District 7", + "type": "congressional", + "contact_information": { + "address_line": [ + "1234 Somerville Gateway", + "Arlington, Ozark 999999" + ], + "email": [ + { + "annotation": "inquiries", + "value": "inquiries@congressional-district-7.gov" + } + ], + "phone": [ + { + "annotation": "domestic", + "value": "123-456-7890" + } + ], + "name": "Arlington Town Hall" + } + }, + { + "object_id": "lacroix-township-precinct-1", + "name": "LaCroix Township Precinct 1", + "type": "precinct", + "contact_information": { + "address_line": [ + "1234 Thorton Drive", + "LaCroix, Ozark 99999" + ], + "email": [ + { + "annotation": "inquiries", + "value": "inquiries@lacrox.gov" + } + ], + "phone": [ + { + "annotation": "domestic", + "value": "123-456-7890" + } + ], + "name": "LaCroix Town Hall" + } + }, + { + "object_id": "lacroix-exeter-utility-district", + "name": "Exeter Utility District", + "type": "utility", + "contact_information": { + "address_line": [ + "1234 Watt Drive", + "LaCroix, Ozark 99999" + ], + "email": [ + { + "annotation": "inquiries", + "value": "inquiries@exeter-utility.com" + } + ], + "phone": [ + { + "annotation": "domestic", + "value": "123-456-7890" + } + ], + "name": "Exeter Utility District coordinator" + } + }, + { + "object_id": "arlington-township-precinct-1", + "name": "Arlington Township Precinct 1", + "type": "precinct", + "contact_information": { + "address_line": [ + "1234 Pahk Avenue", + "Arlinton, Ozark 99999" + ], + "email": [ + { + "annotation": "inquiries", + "value": "inquiries@arlington-township.gov" + } + ], + "phone": [ + { + "annotation": "domestic", + "value": "123-456-7890" + } + ], + "name": "Arlington Town Hall" + } + }, + { + "object_id": "pismo-beach-school-district-precinct-1", + "name": "Pismo Beach School District Precinct 1", + "type": "school", + "contact_information": { + "address_line": [ + "1234 Pismo Beach Elementary", + "Arlington, Ozark 99999" + ], + "email": [ + { + "annotation": "inquiries", + "value": "inquiries@pismo-beach-school.edu" + } + ], + "phone": [ + { + "annotation": "domestic", + "value": "123-456-7890" + } + ], + "name": "Pismo Beah Elementary" + } + }, + { + "object_id": "somerset-school-district-precinct-1", + "name": "Somerset School District", + "type": "school", + "contact_information": { + "address_line": [ + "1234 Somerset Avenue", + "Arlinton, Ozark 99999" + ], + "email": [ + { + "annotation": "inquiries", + "value": "inquiries@somerset-elementary.edu" + } + ], + "phone": [ + { + "annotation": "domestic", + "value": "123-456-7890" + } + ], + "name": "Someset Elementary" + } + }, + { + "object_id": "harris-township", + "name": "Harris Township", + "type": "township", + "contact_information": { + "address_line": [ + "1234 Pahk Avenue", + "Harris, Ozark 99999" + ], + "email": [ + { + "annotation": "inquiries", + "value": "inquiries@harris-township.gov" + } + ], + "phone": [ + { + "annotation": "domestic", + "value": "123-456-7890" + } + ], + "name": "harris Town Hall" + } + } + ], + "parties": [ + { + "object_id": "whig", + "name": { + "text": [ + { + "value": "Whig Party", + "language": "en" + } + ] + }, + "abbreviation": "WHI", + "color": "AAAAAA", + "logo_uri": "http://some/path/to/whig.svg" + }, + { + "object_id": "federalist", + "name": { + "text": [ + { + "value": "Federalist Party", + "language": "en" + } + ] + }, + "abbreviation": "FED", + "color": "BBBBBB", + "logo_uri": "http://some/path/to/federalist.svg" + }, + { + "object_id": "peoples", + "name": { + "text": [ + { + "value": "People's Party", + "language": "en" + } + ] + }, + "abbreviation": "PPL", + "color": "CCCCCC", + "logo_uri": "http://some/path/to/people-s.svg" + }, + { + "object_id": "liberty", + "name": { + "text": [ + { + "value": "Liberty Party", + "language": "en" + } + ] + }, + "abbreviation": "LIB", + "color": "DDDDDD", + "logo_uri": "http://some/path/to/liberty.svg" + }, + { + "object_id": "constitution", + "name": { + "text": [ + { + "value": "Constitution Party", + "language": "en" + } + ] + }, + "abbreviation": "CONST", + "color": "EEEEEE", + "logo_uri": "http://some/path/to/democratic-repulbican.svg" + }, + { + "object_id": "labor", + "name": { + "text": [ + { + "value": "Labor Party", + "language": "en" + } + ] + }, + "abbreviation": "LBR", + "color": "FFFFFF", + "logo_uri": "http://some/path/to/laobr.svg" + }, + { + "object_id": "independent", + "name": { + "text": [ + { + "value": "Independent", + "language": "en" + } + ] + }, + "abbreviation": "IND", + "color": "000000", + "logo_uri": "http://some/path/to/independent.svg" + } + ], + "candidates": [ + { + "object_id": "barchi-hallaren", + "name": { + "text": [ + { + "value": "Joseph Barchi and Joseph Hallaren", + "language": "en" + } + ] + }, + "party_id": "whig", + "image_uri": null, + "is_write_in": null + }, + { + "object_id": "cramer-vuocolo", + "name": { + "text": [ + { + "value": "Adam Cramer and Greg Vuocolo", + "language": "en" + } + ] + }, + "party_id": "federalist", + "image_uri": null, + "is_write_in": null + }, + { + "object_id": "court-blumhardt", + "name": { + "text": [ + { + "value": "Daniel Court and Amy Blumhardt", + "language": "en" + } + ] + }, + "party_id": "peoples", + "image_uri": null, + "is_write_in": null + }, + { + "object_id": "boone-lian", + "name": { + "text": [ + { + "value": "Alvin Boone and James Lian", + "language": "en" + } + ] + }, + "party_id": "liberty", + "image_uri": null, + "is_write_in": null + }, + { + "object_id": "hildebrand-garritty", + "name": { + "text": [ + { + "value": "Ashley Hildebrand-McDougall and James Garritty", + "language": "en" + } + ] + }, + "party_id": "constitution", + "image_uri": null, + "is_write_in": null + }, + { + "object_id": "patterson-lariviere", + "name": { + "text": [ + { + "value": "Martin Patterson and Clay Lariviere", + "language": "en" + } + ] + }, + "party_id": "labor", + "image_uri": null, + "is_write_in": null + }, + { + "object_id": "franz", + "name": { + "text": [ + { + "value": "Charlene Franz", + "language": "en" + } + ] + }, + "party_id": "federalist", + "image_uri": null, + "is_write_in": null + }, + { + "object_id": "harris", + "name": { + "text": [ + { + "value": "Gerald Harris", + "language": "en" + } + ] + }, + "party_id": "peoples", + "image_uri": null, + "is_write_in": null + }, + { + "object_id": "bargmann", + "name": { + "text": [ + { + "value": "Linda Bargmann", + "language": "en" + } + ] + }, + "party_id": "constitution", + "image_uri": null, + "is_write_in": null + }, + { + "object_id": "abcock", + "name": { + "text": [ + { + "value": "Barbara Abcock", + "language": "en" + } + ] + }, + "party_id": "liberty", + "image_uri": null, + "is_write_in": null + }, + { + "object_id": "steel-loy", + "name": { + "text": [ + { + "value": "Carrie Steel-Loy", + "language": "en" + } + ] + }, + "party_id": "labor", + "image_uri": null, + "is_write_in": null + }, + { + "object_id": "sharp", + "name": { + "text": [ + { + "value": "Frederick Sharp", + "language": "en" + } + ] + }, + "party_id": "constitution", + "image_uri": null, + "is_write_in": null + }, + { + "object_id": "wallace", + "name": { + "text": [ + { + "value": "Alex Wallace", + "language": "en" + } + ] + }, + "party_id": "independent", + "image_uri": null, + "is_write_in": null + }, + { + "object_id": "williams", + "name": { + "text": [ + { + "value": "Barbara Williams", + "language": "en" + } + ] + }, + "party_id": "peoples", + "image_uri": null, + "is_write_in": null + }, + { + "object_id": "sharp-althea", + "name": { + "text": [ + { + "value": "Althea Sharp", + "language": "en" + } + ] + }, + "party_id": "whig", + "image_uri": null, + "is_write_in": null + }, + { + "object_id": "alpern", + "name": { + "text": [ + { + "value": "Douglas Alpern", + "language": "en" + } + ] + }, + "party_id": "federalist", + "image_uri": null, + "is_write_in": null + }, + { + "object_id": "windbeck", + "name": { + "text": [ + { + "value": "Ann Windbeck", + "language": "en" + } + ] + }, + "party_id": "peoples", + "image_uri": null, + "is_write_in": null + }, + { + "object_id": "greher", + "name": { + "text": [ + { + "value": "Mike Greher", + "language": "en" + } + ] + }, + "party_id": "constitution", + "image_uri": null, + "is_write_in": null + }, + { + "object_id": "alexander", + "name": { + "text": [ + { + "value": "Patricia Alexander", + "language": "en" + } + ] + }, + "party_id": "whig", + "image_uri": null, + "is_write_in": null + }, + { + "object_id": "mitchell", + "name": { + "text": [ + { + "value": "Kenneth Mitchell", + "language": "en" + } + ] + }, + "party_id": "federalist", + "image_uri": null, + "is_write_in": null + }, + { + "object_id": "lee", + "name": { + "text": [ + { + "value": "Stan Lee", + "language": "en" + } + ] + }, + "party_id": "independent", + "image_uri": null, + "is_write_in": null + }, + { + "object_id": "ash", + "name": { + "text": [ + { + "value": "Henry Ash", + "language": "en" + } + ] + }, + "party_id": "liberty", + "image_uri": null, + "is_write_in": null + }, + { + "object_id": "kennedy", + "name": { + "text": [ + { + "value": "Karen Kennedy", + "language": "en" + } + ] + }, + "party_id": "independent", + "image_uri": null, + "is_write_in": null + }, + { + "object_id": "jackson", + "name": { + "text": [ + { + "value": "Van Jackson", + "language": "en" + } + ] + }, + "party_id": "labor", + "image_uri": null, + "is_write_in": null + }, + { + "object_id": "brown", + "name": { + "text": [ + { + "value": "Debbie Brown", + "language": "en" + } + ] + }, + "party_id": "peoples", + "image_uri": null, + "is_write_in": null + }, + { + "object_id": "teller", + "name": { + "text": [ + { + "value": "Joseph Teller", + "language": "en" + } + ] + }, + "party_id": "peoples", + "image_uri": null, + "is_write_in": null + }, + { + "object_id": "ward", + "name": { + "text": [ + { + "value": "Greg Ward", + "language": "en" + } + ] + }, + "party_id": "independent", + "image_uri": null, + "is_write_in": null + }, + { + "object_id": "murphy", + "name": { + "text": [ + { + "value": "Lou Murphy", + "language": "en" + } + ] + }, + "party_id": "federalist", + "image_uri": null, + "is_write_in": null + }, + { + "object_id": "newman", + "name": { + "text": [ + { + "value": "Jane Newman", + "language": "en" + } + ] + }, + "party_id": "whig", + "image_uri": null, + "is_write_in": null + }, + { + "object_id": "callanann", + "name": { + "text": [ + { + "value": "Jack Callanann", + "language": "en" + } + ] + }, + "party_id": "labor", + "image_uri": null, + "is_write_in": null + }, + { + "object_id": "york", + "name": { + "text": [ + { + "value": "Esther York", + "language": "en" + } + ] + }, + "party_id": "labor", + "image_uri": null, + "is_write_in": null + }, + { + "object_id": "chandler", + "name": { + "text": [ + { + "value": "Glenn Chandler", + "language": "en" + } + ] + }, + "party_id": "labor", + "image_uri": null, + "is_write_in": null + }, + { + "object_id": "solis", + "name": { + "text": [ + { + "value": "Andrea Solis", + "language": "en" + } + ] + }, + "party_id": "labor", + "image_uri": null, + "is_write_in": null + }, + { + "object_id": "keller", + "name": { + "text": [ + { + "value": "Amos Keller", + "language": "en" + } + ] + }, + "party_id": "constitution", + "image_uri": null, + "is_write_in": null + }, + { + "object_id": "rangel", + "name": { + "text": [ + { + "value": "Davitra Rangel", + "language": "en" + } + ] + }, + "party_id": "peoples", + "image_uri": null, + "is_write_in": null + }, + { + "object_id": "argent", + "name": { + "text": [ + { + "value": "Camille Argent", + "language": "en" + } + ] + }, + "party_id": "liberty", + "image_uri": null, + "is_write_in": null + }, + { + "object_id": "witherspoon-smithson", + "name": { + "text": [ + { + "value": "Chloe Witherspoon-Smithson", + "language": "en" + } + ] + }, + "party_id": "independent", + "image_uri": null, + "is_write_in": null + }, + { + "object_id": "bainbridge", + "name": { + "text": [ + { + "value": "Clayton Bainbridge", + "language": "en" + } + ] + }, + "party_id": "peoples", + "image_uri": null, + "is_write_in": null + }, + { + "object_id": "hennessey", + "name": { + "text": [ + { + "value": "Charlene Hennessey", + "language": "en" + } + ] + }, + "party_id": "whig", + "image_uri": null, + "is_write_in": null + }, + { + "object_id": "savoy", + "name": { + "text": [ + { + "value": "Eric Savoy", + "language": "en" + } + ] + }, + "party_id": "labor", + "image_uri": null, + "is_write_in": null + }, + { + "object_id": "tawa", + "name": { + "text": [ + { + "value": "Susan Tawa", + "language": "en" + } + ] + }, + "party_id": "constitution", + "image_uri": null, + "is_write_in": null + }, + { + "object_id": "tawa-mary", + "name": { + "text": [ + { + "value": "Mary Tawa", + "language": "en" + } + ] + }, + "party_id": "independent", + "image_uri": null, + "is_write_in": null + }, + { + "object_id": "altman", + "name": { + "text": [ + { + "value": "Valarie Altman", + "language": "en" + } + ] + }, + "party_id": "peoples", + "image_uri": null, + "is_write_in": null + }, + { + "object_id": "moore", + "name": { + "text": [ + { + "value": "Helen Moore", + "language": "en" + } + ] + }, + "party_id": null, + "image_uri": null, + "is_write_in": null + }, + { + "object_id": "white", + "name": { + "text": [ + { + "value": "John White", + "language": "en" + } + ] + }, + "party_id": null, + "image_uri": null, + "is_write_in": null + }, + { + "object_id": "smallberries", + "name": { + "text": [ + { + "value": "John Smallberries", + "language": "en" + } + ] + }, + "party_id": null, + "image_uri": null, + "is_write_in": null + }, + { + "object_id": "warfin", + "name": { + "text": [ + { + "value": "John Warfin", + "language": "en" + } + ] + }, + "party_id": null, + "image_uri": null, + "is_write_in": null + }, + { + "object_id": "norberg", + "name": { + "text": [ + { + "value": "Chris Norberg", + "language": "en" + } + ] + }, + "party_id": null, + "image_uri": null, + "is_write_in": null + }, + { + "object_id": "parks", + "name": { + "text": [ + { + "value": "Abigail Parks", + "language": "en" + } + ] + }, + "party_id": null, + "image_uri": null, + "is_write_in": null + }, + { + "object_id": "savannah", + "name": { + "text": [ + { + "value": "Harmony Savannah", + "language": "en" + } + ] + }, + "party_id": null, + "image_uri": null, + "is_write_in": null + }, + { + "object_id": "summers", + "name": { + "text": [ + { + "value": "Buffy Summers", + "language": "en" + } + ] + }, + "party_id": null, + "image_uri": null, + "is_write_in": null + }, + { + "object_id": "chase", + "name": { + "text": [ + { + "value": "Cordelia Chase", + "language": "en" + } + ] + }, + "party_id": null, + "image_uri": null, + "is_write_in": null + }, + { + "object_id": "osborne", + "name": { + "text": [ + { + "value": "Daniel Osborne", + "language": "en" + } + ] + }, + "party_id": null, + "image_uri": null, + "is_write_in": null + }, + { + "object_id": "rosenberg", + "name": { + "text": [ + { + "value": "Willow Rosenberg", + "language": "en" + } + ] + }, + "party_id": null, + "image_uri": null, + "is_write_in": null + }, + { + "object_id": "head", + "name": { + "text": [ + { + "value": "Anthony Stewart Head", + "language": "en" + } + ] + }, + "party_id": null, + "image_uri": null, + "is_write_in": null + }, + { + "object_id": "marsters", + "name": { + "text": [ + { + "value": "James Marsters", + "language": "en" + } + ] + }, + "party_id": null, + "image_uri": null, + "is_write_in": null + }, + { + "object_id": "write-in-1", + "name": { + "text": [ + { + "value": "Write In Candidate", + "language": "en" + }, + { + "value": "Escribir en la candidata", + "language": "es" + } + ] + }, + "party_id": null, + "image_uri": null, + "is_write_in": true + }, + { + "object_id": "write-in-2", + "name": { + "text": [ + { + "value": "Write In Candidate", + "language": "en" + }, + { + "value": "Escribir en la candidata", + "language": "es" + } + ] + }, + "party_id": null, + "image_uri": null, + "is_write_in": true + }, + { + "object_id": "write-in-3", + "name": { + "text": [ + { + "value": "Write In Candidate", + "language": "en" + }, + { + "value": "Escribir en la candidata", + "language": "es" + } + ] + }, + "party_id": null, + "image_uri": null, + "is_write_in": true + }, + { + "object_id": "ozark-chief-justice-retain-demergue-affirmative", + "name": { + "text": [ + { + "value": "Retain", + "language": "en" + }, + { + "value": "Conservar", + "language": "es" + } + ] + }, + "party_id": null, + "image_uri": null, + "is_write_in": null + }, + { + "object_id": "ozark-chief-justice-retain-demergue-negative", + "name": { + "text": [ + { + "value": "Reject", + "language": "en" + }, + { + "value": "Rechazar", + "language": "es" + } + ] + }, + "party_id": null, + "image_uri": null, + "is_write_in": null + }, + { + "object_id": "exeter-utility-district-referendum-affirmative", + "name": { + "text": [ + { + "value": "Yes", + "language": "en" + } + ] + }, + "party_id": null, + "image_uri": null, + "is_write_in": null + }, + { + "object_id": "exeter-utility-district-referendum-negative", + "name": { + "text": [ + { + "value": "No", + "language": "en" + } + ] + }, + "party_id": null, + "image_uri": null, + "is_write_in": null + } + ], + "contests": [ + { + "object_id": "president-vice-president-contest", + "sequence_order": 0, + "electoral_district_id": "hamilton-county", + "vote_variation": "one_of_m", + "number_elected": 1, + "votes_allowed": 1, + "name": "President and Vice President of the United States", + "ballot_selections": [ + { + "object_id": "barchi-hallaren-selection", + "sequence_order": 0, + "candidate_id": "barchi-hallaren" + }, + { + "object_id": "cramer-vuocolo-selection", + "sequence_order": 1, + "candidate_id": "cramer-vuocolo" + }, + { + "object_id": "court-blumhardt-selection", + "sequence_order": 2, + "candidate_id": "court-blumhardt" + }, + { + "object_id": "boone-lian-selection", + "sequence_order": 3, + "candidate_id": "boone-lian" + }, + { + "object_id": "hildebrand-garritty-selection", + "sequence_order": 4, + "candidate_id": "hildebrand-garritty" + }, + { + "object_id": "patterson-lariviere-selection", + "sequence_order": 5, + "candidate_id": "patterson-lariviere" + }, + { + "object_id": "write-in-selection-president", + "sequence_order": 6, + "candidate_id": "write-in" + } + ], + "ballot_title": { + "text": [ + { + "value": "President and Vice President of the United States", + "language": "en" + }, + { + "value": "Presidente y Vicepresidente de los Estados Unidos", + "language": "es" + } + ] + }, + "ballot_subtitle": { + "text": [ + { + "value": "Vote for one", + "language": "en" + }, + { + "value": "Votar por uno", + "language": "es" + } + ] + } + }, + { + "object_id": "ozark-governor", + "sequence_order": 1, + "electoral_district_id": "hamilton-county", + "vote_variation": "one_of_m", + "number_elected": 1, + "votes_allowed": 1, + "name": "Governor of the Commonwealth of Ozark", + "ballot_selections": [ + { + "object_id": "franz-selection", + "sequence_order": 0, + "candidate_id": "franz" + }, + { + "object_id": "harris-selection", + "sequence_order": 1, + "candidate_id": "harris" + }, + { + "object_id": "bargmann-selection", + "sequence_order": 2, + "candidate_id": "bargmann" + }, + { + "object_id": "abcock-selection", + "sequence_order": 3, + "candidate_id": "abcock" + }, + { + "object_id": "steel-loy-selection", + "sequence_order": 4, + "candidate_id": "steel-loy" + }, + { + "object_id": "sharp-selection", + "sequence_order": 5, + "candidate_id": "sharp" + }, + { + "object_id": "walace-selection", + "sequence_order": 6, + "candidate_id": "wallace" + }, + { + "object_id": "williams-selection", + "sequence_order": 7, + "candidate_id": "williams" + }, + { + "object_id": "alpern-selection", + "sequence_order": 9, + "candidate_id": "alpern" + }, + { + "object_id": "windbeck-selection", + "sequence_order": 10, + "candidate_id": "windbeck" + }, + { + "object_id": "sharp-althea-selection", + "sequence_order": 11, + "candidate_id": "sharp-althea" + }, + { + "object_id": "greher-selection", + "sequence_order": 12, + "candidate_id": "greher" + }, + { + "object_id": "alexander-selection", + "sequence_order": 13, + "candidate_id": "alexander" + }, + { + "object_id": "mitchell-selection", + "sequence_order": 14, + "candidate_id": "mitchell" + }, + { + "object_id": "lee-selection", + "sequence_order": 15, + "candidate_id": "lee" + }, + { + "object_id": "ash-selection", + "sequence_order": 16, + "candidate_id": "ash" + }, + { + "object_id": "kennedy-selection", + "sequence_order": 17, + "candidate_id": "kennedy" + }, + { + "object_id": "jackson-selection", + "sequence_order": 18, + "candidate_id": "jackson" + }, + { + "object_id": "brown-selection", + "sequence_order": 19, + "candidate_id": "brown" + }, + { + "object_id": "teller-selection", + "sequence_order": 20, + "candidate_id": "teller" + }, + { + "object_id": "ward-selection", + "sequence_order": 21, + "candidate_id": "ward" + }, + { + "object_id": "murphy-selection", + "sequence_order": 22, + "candidate_id": "murphy" + }, + { + "object_id": "newman-selection", + "sequence_order": 23, + "candidate_id": "newman" + }, + { + "object_id": "callanann-selection", + "sequence_order": 24, + "candidate_id": "callanann" + }, + { + "object_id": "york-selection", + "sequence_order": 25, + "candidate_id": "york" + }, + { + "object_id": "chandler-selection", + "sequence_order": 26, + "candidate_id": "chandler" + }, + { + "object_id": "write-in-selection-governor", + "sequence_order": 27, + "candidate_id": "write-in" + } + ], + "ballot_title": { + "text": [ + { + "value": "Governor of the Commonwealth of Ozark", + "language": "en" + }, + { + "value": "Gobernador de la Mancomunidad de Ozark", + "language": "es" + } + ] + }, + "ballot_subtitle": { + "text": [ + { + "value": "Vote for one", + "language": "en" + }, + { + "value": "Votar por uno", + "language": "es" + } + ] + } + }, + { + "object_id": "congress-district-5-contest", + "sequence_order": 2, + "electoral_district_id": "congress-district-5", + "vote_variation": "one_of_m", + "number_elected": 1, + "votes_allowed": 1, + "name": "Congressional District 5", + "ballot_selections": [ + { + "object_id": "soliz-selection", + "sequence_order": 0, + "candidate_id": "soliz" + }, + { + "object_id": "keller-selection", + "sequence_order": 1, + "candidate_id": "keller" + }, + { + "object_id": "rangel-selection", + "sequence_order": 2, + "candidate_id": "rengel" + }, + { + "object_id": "argent-selection", + "sequence_order": 3, + "candidate_id": "argent" + }, + { + "object_id": "witherspoon-smithson-selection", + "sequence_order": 4, + "candidate_id": "witherspoon-smithson" + }, + { + "object_id": "write-in-selection-us-congress-district-5", + "sequence_order": 5, + "candidate_id": "write-in" + } + ], + "ballot_title": { + "text": [ + { + "value": "House of Representatives Congressional District 5", + "language": "en" + }, + { + "value": "C\u00e1mara de Representantes de Distrito 5 del Congreso de Ozark", + "language": "es" + } + ] + }, + "ballot_subtitle": { + "text": [ + { + "value": "Vote for one", + "language": "en" + }, + { + "value": "Votar por uno", + "language": "es" + } + ] + } + }, + { + "object_id": "congress-district-7-contest", + "sequence_order": 3, + "electoral_district_id": "congress-district-7", + "vote_variation": "one_of_m", + "number_elected": 1, + "votes_allowed": 1, + "name": "Congressional District 7", + "ballot_selections": [ + { + "object_id": "bainbridge-selection", + "sequence_order": 0, + "candidate_id": "bainbridge" + }, + { + "object_id": "hennessey-selection", + "sequence_order": 1, + "candidate_id": "hennessey" + }, + { + "object_id": "savoy-selection", + "sequence_order": 2, + "candidate_id": "savoy" + }, + { + "object_id": "tawa-selection", + "sequence_order": 3, + "candidate_id": "tawa" + }, + { + "object_id": "tawa-mary-selection", + "sequence_order": 4, + "candidate_id": "tawa-mary" + }, + { + "object_id": "write-in-selection-us-congress-district-7", + "sequence_order": 5, + "candidate_id": "write-in" + } + ], + "ballot_title": { + "text": [ + { + "value": "House of Representatives Ozark Congressional District 7", + "language": "en" + }, + { + "value": "C\u00e1mara de Representantes de Distrito 7 del Congreso de Ozark", + "language": "es" + } + ] + }, + "ballot_subtitle": { + "text": [ + { + "value": "Vote for one", + "language": "en" + }, + { + "value": "Votar por uno", + "language": "es" + } + ] + } + }, + { + "object_id": "pismo-beach-school-board-contest", + "sequence_order": 4, + "electoral_district_id": "pismo-beach-school-district-precinct-1", + "vote_variation": "n_of_m", + "number_elected": 3, + "votes_allowed": 3, + "name": "Pismo Beach School Board", + "ballot_selections": [ + { + "object_id": "moore-selection", + "sequence_order": 0, + "candidate_id": "moore" + }, + { + "object_id": "white-selection", + "sequence_order": 1, + "candidate_id": "white" + }, + { + "object_id": "smallberries-selection", + "sequence_order": 2, + "candidate_id": "smallberries" + }, + { + "object_id": "warfin-selection", + "sequence_order": 3, + "candidate_id": "warfin" + }, + { + "object_id": "norberg-selection", + "sequence_order": 4, + "candidate_id": "norberg" + }, + { + "object_id": "parks-selection", + "sequence_order": 5, + "candidate_id": "parks" + }, + { + "object_id": "savannah-selection", + "sequence_order": 6, + "candidate_id": "savannah" + }, + { + "object_id": "write-in-selection-1-pismo-beach-school-board", + "sequence_order": 7, + "candidate_id": "write-in-1" + }, + { + "object_id": "write-in-selection-2-pismo-beach-school-board", + "sequence_order": 8, + "candidate_id": "write-in-2" + }, + { + "object_id": "write-in-selection-3-pismo-beach-school-board", + "sequence_order": 9, + "candidate_id": "write-in-3" + } + ], + "ballot_title": { + "text": [ + { + "value": "Pismo Beach School Board", + "language": "en" + }, + { + "value": "Junta Escolar de Pismo Beach", + "language": "es" + } + ] + }, + "ballot_subtitle": { + "text": [ + { + "value": "Vote for up to 3", + "language": "en" + }, + { + "value": "Vote por hasta 3", + "language": "es" + } + ] + } + }, + { + "object_id": "somerset-school-board-contest", + "sequence_order": 5, + "electoral_district_id": "somerset-school-district-precinct-1", + "vote_variation": "n_of_m", + "number_elected": 2, + "votes_allowed": 2, + "name": "Somerset School Board", + "ballot_selections": [ + { + "object_id": "summers-selection", + "sequence_order": 0, + "candidate_id": "summers" + }, + { + "object_id": "chase-selection", + "sequence_order": 1, + "candidate_id": "chase" + }, + { + "object_id": "osborne-selection", + "sequence_order": 2, + "candidate_id": "osborne" + }, + { + "object_id": "rosenberg-selection", + "sequence_order": 3, + "candidate_id": "rosenberg" + }, + { + "object_id": "head-selection", + "sequence_order": 4, + "candidate_id": "head" + }, + { + "object_id": "marsters-selection", + "sequence_order": 5, + "candidate_id": "marsters" + }, + { + "object_id": "write-in-selection-1-somerset-school-board", + "sequence_order": 6, + "candidate_id": "write-in-1" + }, + { + "object_id": "write-in-selection-2-somerset-school-board", + "sequence_order": 7, + "candidate_id": "write-in-2" + } + ], + "ballot_title": { + "text": [ + { + "value": "Pismo Beach School Board", + "language": "en" + }, + { + "value": "Junta Escolar de Somerset", + "language": "es" + } + ] + }, + "ballot_subtitle": { + "text": [ + { + "value": "Vote for up to 2", + "language": "en" + }, + { + "value": "Vote por hasta 2", + "language": "es" + } + ] + } + }, + { + "object_id": "arlington-chief-justice-retain-demergue", + "sequence_order": 6, + "electoral_district_id": "arlington-township-precinct-1", + "vote_variation": "one_of_m", + "number_elected": 1, + "votes_allowed": 1, + "name": "Retain Robert Demergue as Chief Justice?", + "ballot_selections": [ + { + "object_id": "ozark-chief-justice-retain-demergue-affirmative-selection", + "sequence_order": 0, + "candidate_id": "ozark-chief-justice-retain-demergue-affirmative" + }, + { + "object_id": "ozark-chief-justice-retain-demergue-negative-selection", + "sequence_order": 1, + "candidate_id": "ozark-chief-justice-retain-demergue-negative" + } + ], + "ballot_title": { + "text": [ + { + "value": "Retain Robert Demergue as Chief Justice?", + "language": "en" + }, + { + "value": "\u00bfRetener a Robert Demergue como Presidente del Tribunal Supremo?", + "language": "es" + } + ] + }, + "ballot_subtitle": { + "text": [ + { + "value": "Choose 'Accept' or 'Reject'", + "language": "en" + }, + { + "value": "Elija 'Aceptar' o 'Rechazar'", + "language": "es" + } + ] + } + }, + { + "object_id": "exeter-utility-district-referendum-contest", + "sequence_order": 7, + "electoral_district_id": "lacroix-exeter-utility-district", + "vote_variation": "one_of_m", + "number_elected": 1, + "votes_allowed": 1, + "name": "Capital Projects Levy", + "ballot_selections": [ + { + "object_id": "exeter-utility-district-referendum-affirmative-selection", + "sequence_order": 0, + "candidate_id": "exeter-utility-district-referendum-affirmative" + }, + { + "object_id": "exeter-utility-district-referendum-selection", + "sequence_order": 1, + "candidate_id": "exeter-utility-district-referendum-negative" + } + ], + "ballot_title": { + "text": [ + { + "value": "Levy Lift to Maintain Public Safety and Other Core Utility Services", + "language": "en" + }, + { + "value": "Levy Lift para Mantener la Seguridad P\u00fablica y Otros Servicios B\u00e1sicos", + "language": "es" + } + ] + }, + "ballot_subtitle": { + "text": [ + { + "value": "Should this Proposition be approved?", + "language": "en" + }, + { + "value": "Uno", + "language": "es" + } + ] + } + } + ], + "ballot_styles": [ + { + "object_id": "congress-district-7-hamilton-county", + "geopolitical_unit_ids": [ + "hamilton-county", + "congress-district-7" + ], + "party_ids": null, + "image_uri": null + }, + { + "object_id": "congress-district-7-lacroix", + "geopolitical_unit_ids": [ + "hamilton-county", + "congress-district-7", + "lacroix-township-precinct-1" + ], + "party_ids": null, + "image_uri": null + }, + { + "object_id": "congress-district-7-lacroix-exeter", + "geopolitical_unit_ids": [ + "hamilton-county", + "congress-district-7", + "lacroix-township-precinct-1", + "lacroix-exeter-utility-district" + ], + "party_ids": null, + "image_uri": null + }, + { + "object_id": "congress-district-7-arlington", + "geopolitical_unit_ids": [ + "hamilton-county", + "congress-district-7", + "arlington-township-precinct-1" + ], + "party_ids": null, + "image_uri": null + }, + { + "object_id": "congress-district-7-arlington-pismo-beach", + "geopolitical_unit_ids": [ + "hamilton-county", + "congress-district-7", + "arlington-township-precinct-1", + "pismo-beach-school-district-precinct-1" + ], + "party_ids": null, + "image_uri": null + }, + { + "object_id": "congress-district-7-arlington-somerset", + "geopolitical_unit_ids": [ + "hamilton-county", + "congress-district-7", + "arlington-township-precinct-1", + "somerset-school-district-precinct-1" + ], + "party_ids": null, + "image_uri": null + }, + { + "object_id": "congress-district-5-hamilton-county", + "geopolitical_unit_ids": [ + "hamilton-county", + "congress-district-5" + ], + "party_ids": null, + "image_uri": null + }, + { + "object_id": "congress-district-5-lacroix", + "geopolitical_unit_ids": [ + "hamilton-county", + "congress-district-5", + "lacroix-township-precinct-1" + ], + "party_ids": null, + "image_uri": null + }, + { + "object_id": "congress-district-5-harris", + "geopolitical_unit_ids": [ + "hamilton-county", + "congress-district-5", + "harris-township" + ], + "party_ids": null, + "image_uri": null + }, + { + "object_id": "congress-district-5-arlington-pismo-beach", + "geopolitical_unit_ids": [ + "hamilton-county", + "congress-district-5", + "arlington-township-precinct-1", + "pismo-beach-school-district-precinct-1" + ], + "party_ids": null, + "image_uri": null + }, + { + "object_id": "congress-district-5-arlington-somerset", + "geopolitical_unit_ids": [ + "hamilton-county", + "congress-district-5", + "arlington-township-precinct-1", + "somerset-school-district-precinct-1" + ], + "party_ids": null, + "image_uri": null + } + ], + "name": { + "text": [ + { + "value": "Hamiltion County General Election", + "language": "en" + }, + { + "value": "Elecci\u00f3n general del condado de Hamilton", + "language": "es" + } + ] + }, + "contact_information": { + "address_line": [ + "1234 Paul Revere Run", + "Hamilton, Ozark 99999" + ], + "email": [ + { + "annotation": "press", + "value": "inquiries@hamilton.state.gov" + }, + { + "annotation": "federal", + "value": "commissioner@hamilton.state.gov" + } + ], + "phone": [ + { + "annotation": "domestic", + "value": "123-456-7890" + }, + { + "annotation": "international", + "value": "+1-123-456-7890" + } + ], + "name": "Hamilton State Election Commission" + } +} \ No newline at end of file diff --git a/data/sample/manifests/0.95.0/minimal.json b/data/sample/manifests/0.95.0/minimal.json new file mode 100644 index 0000000..f1f7352 --- /dev/null +++ b/data/sample/manifests/0.95.0/minimal.json @@ -0,0 +1,94 @@ +{ + "election_scope_id": "franklin-minimal-referendum-manifest", + "spec_version": "v0.95", + "type": "general", + "start_date": "2020-03-01T08:00:00-05:00", + "end_date": "2020-03-03T19:00:00-05:00", + "geopolitical_units": [ + { + "object_id": "franklin-county", + "name": "Franklin County", + "type": "municipality", + "contact_information": null + } + ], + "parties": [ + { + "object_id": "N/A", + "name": { + "text": [] + }, + "abbreviation": null, + "color": null, + "logo_uri": null + } + ], + "candidates": [ + { + "object_id": "referendum-pineapple-affirmative", + "name": { + "text": [] + }, + "party_id": null, + "image_uri": null, + "is_write_in": null + }, + { + "object_id": "referendum-pineapple-negative", + "name": { + "text": [] + }, + "party_id": null, + "image_uri": null, + "is_write_in": null + } + ], + "contests": [ + { + "object_id": "referendum-pineapple", + "sequence_order": 0, + "electoral_district_id": "franklin-county", + "vote_variation": "one_of_m", + "number_elected": 1, + "votes_allowed": 1, + "name": "Referendum for Banning Pineapple on Pizza", + "ballot_selections": [ + { + "object_id": "referendum-pineapple-affirmative-selection", + "sequence_order": 0, + "candidate_id": "referendum-pineapple-affirmative" + }, + { + "object_id": "referendum-pineapple-negative-selection", + "sequence_order": 1, + "candidate_id": "referendum-pineapple-negative" + } + ], + "ballot_title": null, + "ballot_subtitle": null + } + ], + "ballot_styles": [ + { + "object_id": "ballot-style-01", + "geopolitical_unit_ids": [ + "franklin-county" + ], + "party_ids": null, + "image_uri": null + } + ], + "name": { + "text": [ + { + "value": "Franklin County Minimal General Election March 2020", + "language": "en" + }, + { + "value": "Elecciones generales m\u00ednimas del condado de Franklin marzo de 2020", + "language": "es" + } + ] + }, + "contact_information": null +} \ No newline at end of file diff --git a/data/sample/manifests/0.95.0/small.json b/data/sample/manifests/0.95.0/small.json new file mode 100644 index 0000000..7f838b5 --- /dev/null +++ b/data/sample/manifests/0.95.0/small.json @@ -0,0 +1,237 @@ +{ + "election_scope_id": "franklin-county-general-march2020", + "spec_version": "v0.95", + "type": "general", + "start_date": "2020-03-01T08:00:00-05:00", + "end_date": "2020-03-03T19:00:00-05:00", + "geopolitical_units": [ + { + "object_id": "franklin-county", + "name": "Franklin County", + "type": "county", + "contact_information": null + }, + { + "object_id": "ozark-school-district", + "name": "Ozark School District in Franklin County", + "type": "school", + "contact_information": null + } + ], + "parties": [ + { + "object_id": "party-whig", + "name": { + "text": [] + }, + "abbreviation": "WHI", + "color": null, + "logo_uri": null + }, + { + "object_id": "party-federalist", + "name": { + "text": [] + }, + "abbreviation": "FED", + "color": null, + "logo_uri": null + }, + { + "object_id": "party-democratic-republican", + "name": { + "text": [] + }, + "abbreviation": "DR", + "color": null, + "logo_uri": null + } + ], + "candidates": [ + { + "object_id": "benjamin-franklin", + "name": { + "text": [] + }, + "party_id": "party-whig", + "image_uri": null, + "is_write_in": null + }, + { + "object_id": "john-adams", + "name": { + "text": [] + }, + "party_id": "party-federalist", + "image_uri": null, + "is_write_in": null + }, + { + "object_id": "john-hancock", + "name": { + "text": [] + }, + "party_id": "party-democratic-republican", + "image_uri": null, + "is_write_in": null + }, + { + "object_id": "samuel-adams", + "name": { + "text": [] + }, + "party_id": "party-democratic-republican", + "image_uri": null, + "is_write_in": null + }, + { + "object_id": "thomas-jefferson", + "name": { + "text": [] + }, + "party_id": "party-democratic-republican", + "image_uri": null, + "is_write_in": null + }, + { + "object_id": "write-in", + "name": { + "text": [] + }, + "party_id": null, + "image_uri": null, + "is_write_in": true + }, + { + "object_id": "referendum-pineapple-affirmative", + "name": { + "text": [] + }, + "party_id": null, + "image_uri": null, + "is_write_in": null + }, + { + "object_id": "referendum-pineapple-negative", + "name": { + "text": [] + }, + "party_id": null, + "image_uri": null, + "is_write_in": null + } + ], + "contests": [ + { + "object_id": "congressional-race-01", + "sequence_order": 0, + "electoral_district_id": "franklin-county", + "vote_variation": "one_of_m", + "number_elected": 1, + "votes_allowed": 1, + "name": "Representative to US Congress", + "ballot_selections": [ + { + "object_id": "john-hancock-selection", + "sequence_order": 0, + "candidate_id": "john-hancock" + }, + { + "object_id": "benjamin-franklin-selection", + "sequence_order": 1, + "candidate_id": "benjamin-franklin" + }, + { + "object_id": "write-in-selection", + "sequence_order": 2, + "candidate_id": "write-in" + } + ], + "ballot_title": null, + "ballot_subtitle": null + }, + { + "object_id": "referendum-pineapple", + "sequence_order": 1, + "electoral_district_id": "franklin-county", + "vote_variation": "one_of_m", + "number_elected": 1, + "votes_allowed": 1, + "name": "The Pineapple Question", + "ballot_selections": [ + { + "object_id": "referendum-pineapple-affirmative-selection", + "sequence_order": 0, + "candidate_id": "referendum-pineapple-affirmative" + }, + { + "object_id": "referendum-pineapple-negative-selection", + "sequence_order": 1, + "candidate_id": "referendum-pineapple-negative" + } + ], + "ballot_title": null, + "ballot_subtitle": null + }, + { + "object_id": "ozark-school-board-race", + "sequence_order": 2, + "electoral_district_id": "ozark-school-district", + "vote_variation": "n_of_m", + "number_elected": 2, + "votes_allowed": 2, + "name": "Ozark School Board", + "ballot_selections": [ + { + "object_id": "john-adams-selection", + "sequence_order": 0, + "candidate_id": "john-adams" + }, + { + "object_id": "samuel-adams-selection", + "sequence_order": 1, + "candidate_id": "samuel-adams" + }, + { + "object_id": "thomas-jefferson-selection", + "sequence_order": 2, + "candidate_id": "thomas-jefferson" + } + ], + "ballot_title": null, + "ballot_subtitle": null + } + ], + "ballot_styles": [ + { + "object_id": "franklin-county-no-schooldistrict", + "geopolitical_unit_ids": [ + "franklin-county" + ], + "party_ids": null, + "image_uri": null + }, + { + "object_id": "franklin-county-ozark-schools", + "geopolitical_unit_ids": [ + "franklin-county", + "ozark-school-district" + ], + "party_ids": null, + "image_uri": null + } + ], + "name": { + "text": [ + { + "value": "Franklin County Small General Election March 2020", + "language": "en" + }, + { + "value": "Peque\u00f1as elecciones generales del condado de Franklin marzo de 2020", + "language": "es" + } + ] + }, + "contact_information": null +} \ No newline at end of file diff --git a/data/sample/manifests/1.0.0-preview-1/hamilton-general.json b/data/sample/manifests/1.0.0-preview-1/hamilton-general.json new file mode 100644 index 0000000..a4a09ce --- /dev/null +++ b/data/sample/manifests/1.0.0-preview-1/hamilton-general.json @@ -0,0 +1,420 @@ +{ + "election_scope_id": "jefferson-county-primary", + "spec_version": "v0.95", + "type": "primary", + "start_date": "2020-03-01T08:00:00-05:00", + "end_date": "2020-03-01T20:00:00-05:00", + "geopolitical_units": [ + { + "object_id": "jefferson-county", + "name": "Jefferson County", + "type": "county", + "contact_information": { + "address_line": [ + "1234 Samuel Adams Way", + "Jefferson, Hamilton 999999" + ], + "email": [ + { + "annotation": "inquiries", + "value": "inquiries@jefferson.hamilton.state.gov" + } + ], + "phone": [ + { + "annotation": "domestic", + "value": "123-456-7890" + } + ], + "name": "Jefferson County Clerk" + } + }, + { + "object_id": "harrison-township", + "name": "Harrison Township", + "type": "township", + "contact_information": { + "address_line": [ + "1234 Thorton Drive", + "Harrison, Hamilton 999999" + ], + "email": [ + { + "annotation": "inquiries", + "value": "inquiries@harrison.hamilton.state.gov" + } + ], + "phone": [ + { + "annotation": "domestic", + "value": "123-456-7890" + } + ], + "name": "Harrison Town Hall" + } + }, + { + "object_id": "harrison-township-precinct-east", + "name": "Harrison Township Precinct", + "type": "township", + "contact_information": { + "address_line": [ + "1234 Thorton Drive", + "Harrison, Hamilton 999999" + ], + "email": [ + { + "annotation": "inquiries", + "value": "inquiries@harrison.hamilton.state.gov" + } + ], + "phone": [ + { + "annotation": "domestic", + "value": "123-456-7890" + } + ], + "name": "Harrison Town Hall" + } + }, + { + "object_id": "rutledge-elementary", + "name": "Rutledge Elementary School district", + "type": "school", + "contact_information": { + "address_line": [ + "1234 Wolcott Parkway", + "Harrison, Hamilton 999999" + ], + "email": [ + { + "annotation": "inquiries", + "value": "inquiries@harrison.hamilton.state.gov" + } + ], + "phone": [ + { + "annotation": "domestic", + "value": "123-456-7890" + } + ], + "name": "Rutledge Elementary School" + } + } + ], + "parties": [ + { + "object_id": "whig", + "name": { + "text": [ + { + "value": "Whig Party", + "language": "en" + } + ] + }, + "abbreviation": "WHI", + "color": "AAAAAA", + "logo_uri": "http://some/path/to/whig.svg" + }, + { + "object_id": "federalist", + "name": { + "text": [ + { + "value": "Federalist Party", + "language": "en" + } + ] + }, + "abbreviation": "FED", + "color": "CCCCCC", + "logo_uri": "http://some/path/to/federalist.svg" + }, + { + "object_id": "democratic-republican", + "name": { + "text": [ + { + "value": "Democratic Republican Party", + "language": "en" + } + ] + }, + "abbreviation": "DEMREP", + "color": "EEEEEE", + "logo_uri": "http://some/path/to/democratic-repulbican.svg" + } + ], + "candidates": [ + { + "object_id": "benjamin-franklin", + "name": { + "text": [ + { + "value": "Benjamin Franklin", + "language": "en" + } + ] + }, + "party_id": "whig", + "image_uri": null, + "is_write_in": null + }, + { + "object_id": "john-adams", + "name": { + "text": [ + { + "value": "John Adams", + "language": "en" + } + ] + }, + "party_id": "federalist", + "image_uri": null, + "is_write_in": null + }, + { + "object_id": "john-hancock", + "name": { + "text": [ + { + "value": "John Hancock", + "language": "en" + } + ] + }, + "party_id": "democratic-republican", + "image_uri": null, + "is_write_in": null + }, + { + "object_id": "write-in", + "name": { + "text": [ + { + "value": "Write In Candidate", + "language": "en" + }, + { + "value": "Escribir en la candidata", + "language": "es" + } + ] + }, + "party_id": null, + "image_uri": null, + "is_write_in": true + }, + { + "object_id": "referendum-pineapple-affirmative", + "name": { + "text": [ + { + "value": "Pineapple should be banned on pizza", + "language": "en" + } + ] + }, + "party_id": null, + "image_uri": null, + "is_write_in": null + }, + { + "object_id": "referendum-pineapple-negative", + "name": { + "text": [ + { + "value": "Pineapple should not be banned on pizza", + "language": "en" + } + ] + }, + "party_id": null, + "image_uri": null, + "is_write_in": null + } + ], + "contests": [ + { + "object_id": "justice-supreme-court", + "sequence_order": 1, + "electoral_district_id": "jefferson-county", + "vote_variation": "n_of_m", + "number_elected": 2, + "votes_allowed": 2, + "name": "Justice of the Supreme Court", + "ballot_selections": [ + { + "object_id": "john-adams-selection", + "sequence_order": 1, + "candidate_id": "john-adams" + }, + { + "object_id": "benjamin-franklin-selection", + "sequence_order": 2, + "candidate_id": "benjamin-franklin" + }, + { + "object_id": "john-hancock-selection", + "sequence_order": 3, + "candidate_id": "john-hancock" + }, + { + "object_id": "write-in-selection", + "sequence_order": 4, + "candidate_id": "write-in" + } + ], + "ballot_title": { + "text": [ + { + "value": "Justice of the Supreme Court", + "language": "en" + }, + { + "value": "Juez de la corte suprema", + "language": "es" + } + ] + }, + "ballot_subtitle": { + "text": [ + { + "value": "Please choose up to two candidates", + "language": "en" + }, + { + "value": "Uno", + "language": "es" + } + ] + } + }, + { + "object_id": "referendum-pineapple", + "sequence_order": 2, + "electoral_district_id": "harrison-township", + "vote_variation": "one_of_m", + "number_elected": 1, + "votes_allowed": 1, + "name": "The Pineapple Question", + "ballot_selections": [ + { + "object_id": "referendum-pineapple-affirmative-selection", + "sequence_order": 1, + "candidate_id": "referendum-pineapple-affirmative" + }, + { + "object_id": "referendum-pineapple-negative-selection", + "sequence_order": 2, + "candidate_id": "referendum-pineapple-negative" + } + ], + "ballot_title": { + "text": [ + { + "value": "Should pineapple be banned on pizza?", + "language": "en" + }, + { + "value": "\u00bfDeber\u00eda prohibirse la pi\u00f1a en la pizza?", + "language": "es" + } + ] + }, + "ballot_subtitle": { + "text": [ + { + "value": "The township considers this issue to be very important", + "language": "en" + }, + { + "value": "El municipio considera que esta cuesti\u00f3n es muy importante", + "language": "es" + } + ] + } + } + ], + "ballot_styles": [ + { + "object_id": "jefferson-county-ballot-style", + "geopolitical_unit_ids": [ + "jefferson-county" + ], + "party_ids": null, + "image_uri": null + }, + { + "object_id": "harrison-township-ballot-style", + "geopolitical_unit_ids": [ + "jefferson-county", + "harrison-township" + ], + "party_ids": null, + "image_uri": null + }, + { + "object_id": "harrison-township-precinct-east-ballot-style", + "geopolitical_unit_ids": [ + "jefferson-county", + "harrison-township", + "harrison-township-precinct-east", + "rutledge-elementary" + ], + "party_ids": null, + "image_uri": null + }, + { + "object_id": "rutledge-elementary-ballot-style", + "geopolitical_unit_ids": [ + "jefferson-county", + "harrison-township", + "rutledge-elementary" + ], + "party_ids": null, + "image_uri": null + } + ], + "name": { + "text": [ + { + "value": "Jefferson County Spring Primary", + "language": "en" + }, + { + "value": "Primaria de primavera del condado de Jefferson", + "language": "es" + } + ] + }, + "contact_information": { + "address_line": [ + "1234 Paul Revere Run", + "Jefferson, Hamilton 999999" + ], + "email": [ + { + "annotation": "press", + "value": "inquiries@hamilton.state.gov" + }, + { + "annotation": "federal", + "value": "commissioner@hamilton.state.gov" + } + ], + "phone": [ + { + "annotation": "domestic", + "value": "123-456-7890" + }, + { + "annotation": "international", + "value": "+1-123-456-7890" + } + ], + "name": "Hamilton State Election Commission" + } +} \ No newline at end of file diff --git a/data/sample/manifests/1.91/jefferson-county-primary.json b/data/sample/manifests/1.91/jefferson-county-primary.json new file mode 100644 index 0000000..9f274b3 --- /dev/null +++ b/data/sample/manifests/1.91/jefferson-county-primary.json @@ -0,0 +1 @@ +{"ballot_styles":[{"geopolitical_unit_ids":["jefferson-county"],"object_id":"jefferson-county-ballot-style"},{"geopolitical_unit_ids":["jefferson-county","harrison-township"],"object_id":"harrison-township-ballot-style"},{"geopolitical_unit_ids":["jefferson-county","harrison-township","harrison-township-precinct-east","rutledge-elementary"],"object_id":"harrison-township-precinct-east-ballot-style"},{"geopolitical_unit_ids":["jefferson-county","harrison-township","rutledge-elementary"],"object_id":"rutledge-elementary-ballot-style"}],"candidates":[{"name":{"text":[{"language":"en","value":"Benjamin Franklin"}]},"object_id":"benjamin-franklin","party_id":"whig"},{"name":{"text":[{"language":"en","value":"John Adams"}]},"object_id":"john-adams","party_id":"federalist"},{"name":{"text":[{"language":"en","value":"John Hancock"}]},"object_id":"john-hancock","party_id":"democratic-republican"},{"is_write_in":true,"name":{"text":[{"language":"en","value":"Write In Candidate"},{"language":"es","value":"Escribir en la candidata"}]},"object_id":"write-in"},{"name":{"text":[{"language":"en","value":"Pineapple should be banned on pizza"}]},"object_id":"referendum-pineapple-affirmative"},{"name":{"text":[{"language":"en","value":"Pineapple should not be banned on pizza"}]},"object_id":"referendum-pineapple-negative"}],"contact_information":{"address_line":["1234 Paul Revere Run","Jefferson, Hamilton 999999"],"email":[{"annotation":"press","value":"inquiries@hamilton.state.gov"},{"annotation":"federal","value":"commissioner@hamilton.state.gov"}],"name":"Hamilton State Election Commission","phone":[{"annotation":"domestic","value":"123-456-7890"},{"annotation":"international","value":"+1-123-456-7890"}]},"contests":[{"ballot_selections":[{"candidate_id":"john-adams","object_id":"john-adams-selection","sequence_order":1},{"candidate_id":"benjamin-franklin","object_id":"benjamin-franklin-selection","sequence_order":2},{"candidate_id":"john-hancock","object_id":"john-hancock-selection","sequence_order":3},{"candidate_id":"write-in","object_id":"write-in-selection","sequence_order":4}],"ballot_subtitle":{"text":[{"language":"en","value":"Please choose up to two candidates"},{"language":"es","value":"Uno"}]},"ballot_title":{"text":[{"language":"en","value":"Justice of the Supreme Court"},{"language":"es","value":"Juez de la corte suprema"}]},"electoral_district_id":"jefferson-county","name":"Justice of the Supreme Court","number_elected":2,"object_id":"justice-supreme-court","sequence_order":1,"vote_variation":"n_of_m","votes_allowed":2},{"ballot_selections":[{"candidate_id":"referendum-pineapple-affirmative","object_id":"referendum-pineapple-affirmative-selection","sequence_order":1},{"candidate_id":"referendum-pineapple-negative","object_id":"referendum-pineapple-negative-selection","sequence_order":2}],"ballot_subtitle":{"text":[{"language":"en","value":"The township considers this issue to be very important"},{"language":"es","value":"El municipio considera que esta cuestión es muy importante"}]},"ballot_title":{"text":[{"language":"en","value":"Should pineapple be banned on pizza?"},{"language":"es","value":"¿Debería prohibirse la piña en la pizza?"}]},"electoral_district_id":"harrison-township","name":"The Pineapple Question","number_elected":1,"object_id":"referendum-pineapple","sequence_order":2,"vote_variation":"one_of_m","votes_allowed":1}],"election_scope_id":"jefferson-county-primary","end_date":"2020-03-02T01:00:00Z","geopolitical_units":[{"contact_information":{"address_line":["1234 Samuel Adams Way","Jefferson, Hamilton 999999"],"email":[{"annotation":"inquiries","value":"inquiries@jefferson.hamilton.state.gov"}],"name":"Jefferson County Clerk","phone":[{"annotation":"domestic","value":"123-456-7890"}]},"name":"Jefferson County","object_id":"jefferson-county","type":"county"},{"contact_information":{"address_line":["1234 Thorton Drive","Harrison, Hamilton 999999"],"email":[{"annotation":"inquiries","value":"inquiries@harrison.hamilton.state.gov"}],"name":"Harrison Town Hall","phone":[{"annotation":"domestic","value":"123-456-7890"}]},"name":"Harrison Township","object_id":"harrison-township","type":"township"},{"contact_information":{"address_line":["1234 Thorton Drive","Harrison, Hamilton 999999"],"email":[{"annotation":"inquiries","value":"inquiries@harrison.hamilton.state.gov"}],"name":"Harrison Town Hall","phone":[{"annotation":"domestic","value":"123-456-7890"}]},"name":"Harrison Township Precinct","object_id":"harrison-township-precinct-east","type":"township"},{"contact_information":{"address_line":["1234 Wolcott Parkway","Harrison, Hamilton 999999"],"email":[{"annotation":"inquiries","value":"inquiries@harrison.hamilton.state.gov"}],"name":"Rutledge Elementary School","phone":[{"annotation":"domestic","value":"123-456-7890"}]},"name":"Rutledge Elementary School district","object_id":"rutledge-elementary","type":"school"}],"name":{"text":[{"language":"en","value":"Jefferson County Spring Primary"},{"language":"es","value":"Primaria de primavera del condado de Jefferson"}]},"parties":[{"abbreviation":"WHI","color":"AAAAAA","logo_uri":"http://some/path/to/whig.svg","name":{"text":[{"language":"en","value":"Whig Party"}]},"object_id":"whig"},{"abbreviation":"FED","color":"CCCCCC","logo_uri":"http://some/path/to/federalist.svg","name":{"text":[{"language":"en","value":"Federalist Party"}]},"object_id":"federalist"},{"abbreviation":"DEMREP","color":"EEEEEE","logo_uri":"http://some/path/to/democratic-repulbican.svg","name":{"text":[{"language":"en","value":"Democratic Republican Party"}]},"object_id":"democratic-republican"}],"spec_version":"1.0","start_date":"2020-03-01T13:00:00Z","type":"primary"} \ No newline at end of file diff --git a/docs/develop/Sample_Data.md b/docs/develop/Sample_Data.md index f70865f..ee842a8 100644 --- a/docs/develop/Sample_Data.md +++ b/docs/develop/Sample_Data.md @@ -1,6 +1,7 @@ # Sample Data To better understand the data formats used by ElectionGuard, sample data is provided here to guide new developers. +The sample manifests are kept in this repository and generated sample data is published with the release artifacts. ## Four Sample Elections @@ -18,6 +19,9 @@ We have provided sample data for four different elections: ## Data ![Version 0.95][badge-0.95] +Generated sample data is built from the manifests in the repository during the release workflow. +Download the release artifact named `sample-data` to inspect the generated election records, submitted ballots, and private test data. + ### Election Manifests The [Election Manifest][manifest-building] contains all the details of the election (such as contests, candidates, selections, ballot styles, etc.) and is required at the start of an ElectionGuard election. @@ -41,12 +45,21 @@ The Election Record are the files required at the end of the election to verify [badge-0.95]: https://img.shields.io/badge/🗳%20ElectionGuard-v0.95-green -[hamilton-election-manifest]: https://github.com/microsoft/electionguard/blob/main/data/1.0.0-preview-1/sample/hamilton-general/election_record/manifest.json -[hamilton-election-ballot]: https://github.com/microsoft/electionguard/blob/main/data/1.0.0-preview-1/sample/hamilton-general/election_record/submitted_ballots -[hamilton-election-record]: https://github.com/microsoft/electionguard/blob/main/data/1.0.0-preview-1/sample/hamilton-general/election_record -[hamilton-election-private]: https://github.com/microsoft/electionguard/blob/main/data/1.0.0-preview-1/sample/hamilton-general/private +[minimal-election-manifest]: https://github.com/microsoft/electionguard/blob/main/data/sample/manifests/0.95.0/minimal.json +[small-election-manifest]: https://github.com/microsoft/electionguard/blob/main/data/sample/manifests/0.95.0/small.json +[full-election-manifest]: https://github.com/microsoft/electionguard/blob/main/data/sample/manifests/0.95.0/full.json +[hamilton-election-manifest]: https://github.com/microsoft/electionguard/blob/main/data/sample/manifests/0.95.0/hamilton-general.json +[minimal-election-ballot]: https://github.com/microsoft/electionguard/releases +[small-election-ballot]: https://github.com/microsoft/electionguard/releases +[full-election-ballot]: https://github.com/microsoft/electionguard/releases +[hamilton-election-ballot]: https://github.com/microsoft/electionguard/releases +[minimal-election-record]: https://github.com/microsoft/electionguard/releases +[small-election-record]: https://github.com/microsoft/electionguard/releases +[full-election-record]: https://github.com/microsoft/electionguard/releases +[hamilton-election-record]: https://github.com/microsoft/electionguard/releases +[hamilton-election-private]: https://github.com/microsoft/electionguard/releases [election-manifest-docs]: https://www.electionguard.vote/guide/Election_Manifest/#introducing-hamilton-county-oz [manifest-building]: ../concepts/Manifest_Building.md ---8<-- "includes/abbreviations.md" \ No newline at end of file +--8<-- "includes/abbreviations.md" diff --git a/scripts/generate_sample_data.py b/scripts/generate_sample_data.py new file mode 100644 index 0000000..a252db2 --- /dev/null +++ b/scripts/generate_sample_data.py @@ -0,0 +1,229 @@ +#!/usr/bin/env python3 +"""Generate ElectionGuard sample data from checked-in sample manifests.""" + +from __future__ import annotations + +import argparse +import logging +import os +import shutil +import sys +from pathlib import Path +from tempfile import TemporaryDirectory +from typing import Iterable, List + + +SUPPORTED_MANIFESTS = ("minimal", "small", "full", "hamilton-general") +DEFAULT_VERSION = "0.95.0" +DEFAULT_MANIFEST_DIR = Path("data/sample/manifests") +DEFAULT_OUTPUT_DIR = Path("data/sample/generated") + + +def parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser( + description="Generate sample data from the sample manifests.", + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + ) + parser.add_argument( + "--manifest-dir", + type=Path, + default=DEFAULT_MANIFEST_DIR, + help="Directory containing manifests grouped by version.", + ) + parser.add_argument( + "--output-dir", + type=Path, + default=DEFAULT_OUTPUT_DIR, + help="Directory where generated sample data is written.", + ) + parser.add_argument( + "--version", + default=DEFAULT_VERSION, + help="Sample manifest version to generate.", + ) + parser.add_argument( + "--manifest", + action="append", + choices=SUPPORTED_MANIFESTS, + help="Manifest name to generate. Can be provided more than once.", + ) + parser.add_argument( + "--number-of-ballots", + type=int, + default=5, + help="Number of ballots to generate per manifest.", + ) + parser.add_argument( + "--spoil-rate", + type=int, + default=50, + help="Approximate percentage of ballots to spoil instead of cast.", + ) + parser.add_argument( + "--all-guardians", + action="store_true", + help="Use all guardians during decryption.", + ) + parser.add_argument( + "--private-data", + action="store_true", + help="Include private test data such as plaintext ballots and guardian keys.", + ) + parser.add_argument( + "--clean", + action="store_true", + help="Remove the output version directory before generating.", + ) + parser.add_argument( + "--verbose", + action="store_true", + help="Show verbose output from the ElectionGuard generator.", + ) + return parser.parse_args() + + +def fail(message: str) -> None: + print(f"error: {message}", file=sys.stderr) + raise SystemExit(1) + + +def import_generator(): + try: + from electionguard.manifest import Manifest + from electionguard.serialize import from_file + from electionguard_tools.factories.election_factory import ElectionFactory + from electionguard_tools.scripts.sample_generator import ( + ElectionSampleDataGenerator, + ) + except ImportError as exc: + fail( + "sample generation requires electionguard-python. " + "Install it with " + "`python -m pip install electionguard==1.4.0 dacite==1.6.0 hypothesis`. " + f"Original import error: {exc}" + ) + + return ElectionFactory, ElectionSampleDataGenerator, Manifest, from_file + + +def build_manifest_directory_factory(election_factory_class, manifest_class, from_file): + class ManifestDirectoryElectionFactory(election_factory_class): + def __init__(self, manifest_data_dir: Path) -> None: + super().__init__() + self.manifest_data_dir = manifest_data_dir + + def get_manifest_from_filename(self, filename: str): + return from_file(manifest_class, str(self.manifest_data_dir / filename)) + + return ManifestDirectoryElectionFactory + + +def copy_manifest_sources( + manifest_dir: Path, version: str, manifest_names: Iterable[str], package_data_dir: Path +) -> List[str]: + copied: List[str] = [] + version_dir = manifest_dir / version + if not version_dir.exists(): + fail(f"manifest version directory does not exist: {version_dir}") + + for manifest_name in manifest_names: + source = version_dir / f"{manifest_name}.json" + if not source.exists(): + fail(f"sample manifest does not exist: {source}") + + shutil.copyfile(source, package_data_dir / f"manifest-{manifest_name}.json") + copied.append(manifest_name) + + return copied + + +def generate_manifest( + generator_class, + election_factory_class, + manifest_data_dir: Path, + output_root: Path, + version: str, + manifest_name: str, + number_of_ballots: int, + spoil_rate: int, + all_guardians: bool, + private_data: bool, +) -> None: + destination = output_root / version / manifest_name + if destination.exists(): + shutil.rmtree(destination) + destination.mkdir(parents=True) + + with TemporaryDirectory() as work_dir_name: + work_dir = Path(work_dir_name) + previous_cwd = Path.cwd() + try: + os.chdir(work_dir) + generator = generator_class() + generator.election_factory = election_factory_class(manifest_data_dir) + generator.generate( + number_of_ballots=number_of_ballots, + spoil_rate=spoil_rate, + use_all_guardians=all_guardians, + use_private_data=private_data, + sample_manifest=manifest_name, + ) + finally: + os.chdir(previous_cwd) + + for generated_dir_name in ("election_record", "election_private_data"): + generated_dir = work_dir / generated_dir_name + if generated_dir.exists(): + shutil.copytree(generated_dir, destination / generated_dir_name) + + +def main() -> None: + args = parse_args() + if args.spoil_rate < 0 or args.spoil_rate > 100: + fail("--spoil-rate must be between 0 and 100") + if args.number_of_ballots < 1: + fail("--number-of-ballots must be at least 1") + if not args.verbose: + logging.disable(logging.INFO) + + manifest_names = args.manifest or list(SUPPORTED_MANIFESTS) + output_dir = args.output_dir + version_output_dir = output_dir / args.version + + if args.clean and version_output_dir.exists(): + shutil.rmtree(version_output_dir) + + ( + election_factory_class, + generator_class, + manifest_class, + from_file, + ) = import_generator() + + with TemporaryDirectory() as data_dir_name: + data_dir = Path(data_dir_name) + copied = copy_manifest_sources( + args.manifest_dir, args.version, manifest_names, data_dir + ) + manifest_directory_factory = build_manifest_directory_factory( + election_factory_class, manifest_class, from_file + ) + + for manifest_name in copied: + print(f"Generating {args.version}/{manifest_name}") + generate_manifest( + generator_class, + manifest_directory_factory, + data_dir, + output_dir, + args.version, + manifest_name, + args.number_of_ballots, + args.spoil_rate, + args.all_guardians, + args.private_data, + ) + + +if __name__ == "__main__": + main()