A simple API for ranked-choice voting in an election.
Ranked-choice Voting is a Flask app that serves API endpoints for ranked-choice voting, supporting features such as election creation, deletion and updating, vote casting, and result viewing. It supports election voting strategies for electing both a single winner and multiple winners.
The following voting strategies are supported by this API:
-
Instant Run-off Voting (IRV): A single candidate election method that elects the candidate that can obtain majority support (more than 50%). Voters rank candidates and are granted one vote. The candidate with the fewest votes is removed and this candidate's votes are transferred according to the voters 2nd preference (or 3rd etc).
-
Preferential Block Voting (PBV): A multiple candidate election method that elects candidates that can obtain majority support (more than 50%). PBV tends to elect uncontroversial candidates that agree with each other. Minority group often lose their representation.
-
Single Transferable Vote (STV): A multiple candidate election method that elects candidates based on proportional representation. Minority (and extreme) groups get representation if they have enough votes to elect a candidate. STV is therefore the preferred ranked-choice voting method for parliament elections and most multiple seat elections, but it's more complex than PBV.
Preferential block voting and Single transferable vote are the same as Instant-runoff voting when only one candidate is elected.
Instant-runoff voting and Preferential block voting are basically the same as exhaustive ballot, the preferred method in Rober's rules of order. The only difference is that in exhaustive ballot voters can adjust their preferences between each round (elimination or election of one candidate).
More details about each of these voting strategies can be found here.
The creation of elections is performed by sending either a GET
or POST
request.
A GET
request is the quickest way to set up a ranked-choice election but offers no customization options since all
fields take their default values. Create an election by appending a /
separated list of candidates to
the /addElection
endpoint.
curl --location --request GET 'https://localhost:5000/addElection/pancakes/waffles/ice-cream'
You can also create an election by sending a POST
request. This is the most flexible way to create an election but
requires you to specify the fields you wish to customize.
The request body is a JSON object with the following fields:
Field | Optional | Default | Description |
---|---|---|---|
_id |
Yes | Random | A custom ID for your election |
name |
Yes | The name of your election | |
description |
Yes | A short description of your election | |
start_time |
Yes | Current time in UTC | The timestamp at which your election starts. Ballots cast only after the start time will be counted |
end_time |
Yes | The timestamp at which your election ends. Ballots cast only before the end time will be counted. | |
voting_strategy |
Yes | instant_runoff |
A string indicating the voting strategy to use. Can be one of instant_runoff , preferential_block or single_transferable |
number_of_winners |
Yes | 1 |
An integer indicating the required number of winning candidates for your election |
update_ballot |
Yes | True |
A boolean value indicating whether a voter can update or delete their ballot |
anonymous |
Yes | False |
A boolean value indicating whether ballots cast in your election are publicly viewable |
candidates |
No | A list of unique strings where each element represents a candidate |
An example is provided below:
curl --location --request POST 'https://localhost:5000/addElection' \
--header 'Content-Type: application/json' \
--data-raw '{
"name": "Food ranking",
"description": "Rank your favorite foods!",
"candidates": ["pancakes", "ice-cream", "waffles"],
"anonymous": true
}'
Field | Description |
---|---|
status |
A boolean indicating whether the request succeeded or failed |
message |
A feedback on the action that was requested |
data |
A key-value map of your election's configuration, returned only if status returns true |
error |
The exception that occurred at the server, returned only if status returns false |
If your election creation request is successful, you will be able to access your election's _id
stored in
the data
field. Remember to use this ID when casting your votes and to access results.
You can view your election results by sending a GET
request to the /viewElection/_id
endpoint.
An example is provided below:
curl --location --request GET 'https://localhost:5000/viewElection/_id'
Field | Description |
---|---|
status |
A boolean indicating whether the request succeeded or failed |
message |
A feedback on the action that was requested |
data |
A key-value map of your election's data including configuration and votes cast, returned only if status returns true |
error |
The exception that occurred at the server, returned only if status returns false |
You can remove an election by sending a GET
request to the /removeElection/_id
endpoint. Note that this
action is irreversible and can only be performed by the person who created the election.
curl --location --request GET 'https://localhost:5000/removeElection/_id'
Field | Description |
---|---|
status |
A boolean indicating whether the request succeeded or failed |
message |
A feedback on the action that was requested |
data |
A key-value map of your election's data including configuration and votes cast, returned only if status returns true |
error |
The exception that occurred at the server, returned only if status returns false |
You can update an election by sending a POST
request to the /updateElection/_id
endpoint. Note that this
action can only be performed by the person who created the election.
The request body is a JSON object with the fields that you wish to update. These fields are the same as the ones described in the Create an Election section.
Note that updating the _id
field is not allowed. If you update the candidates
, number_of_winners
or voting_strategy
, all ballots cast in the election will be removed and the election will be reset.
curl --location --request POST 'https://localhost:5000/updateElection/_id'
--header 'Content-Type: application/json' \
--data-raw '{
"candidates": ["pancakes", "doughnuts", "waffles"],
"anonymous": true
}'
Field | Description |
---|---|
status |
A boolean indicating whether the request succeeded or failed |
message |
A feedback on the action that was requested |
data |
A key-value map of your election's data including configuration and votes cast, returned only if status returns true |
error |
The exception that occurred at the server, returned only if status returns false |
You can cast your votes by sending a GET
request to the /addVote/_id
endpoint. Append the URL with the
ordered candidates, separated with a /
. If you have already cast a vote, this action will update your
vote. An example is provided below:
curl --location --request GET 'https://localhost:5000/addVote/_id/pancakes/icecream/waffles'
Field | Description |
---|---|
status |
A boolean indicating whether the request succeeded or failed |
message |
A feedback on the action that was requested |
error |
The exception that occurred at the server, returned only if status returns false |
You can remove your vote by sending a GET
request to the /removeVote/_id
endpoint. Note that this action
will remove only your vote, not all votes.
curl --location --request GET 'https://localhost:5000/removeVote/_id'
Field | Description |
---|---|
status |
A boolean indicating whether the request succeeded or failed |
message |
A feedback on the action that was requested |
error |
The exception that occurred at the server, returned only if status returns false |
The easiest way to set up the app is to use Docker-Compose. Run the following command:
docker-compose --project-name ranked-choice-voting-api up -d
Alternatively, you can also use the following commands to set up the app:
-
Create a new Python3 environment and activate it
virutalenv ranked-choice-voting-api source ranked-choice-voting-api/bin/activate
-
Install the requirements
pip install -r requirements.txt
-
Modify the
.env
file if you wish to customize the app. The default values are:MONGO_URI=mongodb://mongo:27017/ # Change this to your MongoDB URI TTL_SECONDS=2592000 # time in seconds the election is persisted after it ends (default is 30 days) HOST="0.0.0.0" # Change this to your host PORT=5000 # Change this to your port
-
Run the app
python3 app/app.py
Anyone you wish to share the _id
of your election with can view your election and cast their votes. Additionally, if
the anonymous
field is set to False
, then anyone with the _id
can view the ballots cast as well. Otherwise, only
the person who created the election can view the ballots.
The app uses IP addresses to track different users. This is used to map a user to their ballot and is used to update votes (or prevent multiple votes). Every time you create an election or add a vote, your IP address is recorded and used to identify you.
This behavior depends on the update_votes
field. If this field is set to True
, then you can update or remove your
vote from the election.
This currently is not supported. If you would like to update your election details, please contact the administrators.
No data is being collected from you, except for your IP address. This is used to identify you and to prevent multiple votes.
No, the API cannot be completely linked with a bot. This is because the API uses IP addresses to identify users. If a bot sends requests to the API, it will always be treated as the same user (with the bot's IP address), and hence there will always be one voter, regardless of the number of people casting the vote through the bot.
However, the bot can be used to create elections, view election results, and construct a URL for a user to click and vote.