Converters

Interface

All converters conform to this interface.

Interface and exceptions for all converters

class conversions.base.Converter

Bases: ABC

Interface for converters

convert_to_ut(data)

Parses the file and returns the parsed data

Parameters:

data – A File object, filename, or json data. Not all converters support JSON.

Returns:

The Universal Tabulator representation of this data. Call convert_to_ut_and_validate() to guarantee that it matches the Universal Tabulator schema.

Raises:
  • CouldNotConvertException – If the conversion could not complete

  • CouldNotOpenFileException – If the file couldn’t be opened

convert_to_ut_and_validate(filename_or_fileobj)

Calls convert_to_ut(), then validates it with the Universal Tabulator schema.

Parameters:

filename – A File object or filename

Returns:

Guaranteed-valid Universal Tabulator data

Raises:

CouldNotConvertException – If the conversion could not complete

convert_to_ut_without_exceptions(data)

See convert_to_ut(). This is the workhorse, without exceptions. To debug. call this in convert_to_ut_and_validate()

classmethod postprocess_remove_last_round_elimination(data)

When there are two candidates left, Dominion marks the loser among them as “eliminated”, whereas the URCVT format does not. Updates data to remove any last-round eliminations

classmethod postprocess_use_standard_irv_threshold(data)

Set the threshold based on (last round active votes) / (num winners + 1)

Exceptions

Some of the possible exceptions that may be thrown during conversion

Interface and exceptions for all converters

exception conversions.base.CouldNotConvertException

Bases: Exception

Raised when an unexpected error prevented conversion. This could be anything from an invalid input file, to unsupported data, to a bug in the software.

Automatic Converter

Allows you to provide any file and it will try to figure out its type and convert it.

Attempts to convert any file to the standard format. If you know the file format, you should not use this - it will loop through all schemas which will be needlessly slow.

class conversions.automatic.AutomaticConverter

Bases: Converter

Interface for converters

_convert_file_object_to_ut(file_object)

Just like func:~convert_to_ut, but only accepting a file object

Internal developer documentation

The remainder of this documentation is about the internal representation of classes. It is intended for developers of RCV Formats, rather than its users.

Transferless format helpers

Helper base class for any file format which does not explicitly spell out how votes are transferred between candidates.

Interface and exceptions for all converters

class conversions.base.GenericGuessAtTransferConverter

Bases: Converter

If there are multiple candidates transferring their votes, we cannot know which candidates contributed to which transfers. Our best-effort guess is that they distributed their votes equally to all candidates.

This base class lets you fill out partial data, leaving out tallyResults, and it will guess at the tallyResults for you.

Note that it always knows the correct tally results if only one candidate is eliminated or elected in each round - the guessing only happens when multiple candidates are transferring their votes.

classmethod _compute_vote_deltas_from_tally(tally_this_round, tally_next_round)

Returns the vote deltas between this round and next round. The two tally arguments must match the tally format in the Universal Tabulator format.

Parameters:
  • tally_this_round – A dict mapping candidate names to number of votes this round

  • tally_next_round – A dict mapping candidate names to number of votes next round, ensuring that any eliminated candidates are NOT present next round, NOT that they just have zero votes.

Returns:

A dict mapping candidate names in this round to how their votes changed between this round and the next. Includes positive numbers (gained votes) and negative numbers (lost votes via elimination or surplus transfer)

classmethod _weights_for_each_transfer(eliminated_names, elected_names, vote_delta)
Parameters:
  • eliminated_names – the names of each eliminated candidate

  • elected_names – the names of each elected candidate

  • vote_delta – a dict mapping candidate name to vote difference between this round and the next round

Returns:

a dict mapping a transferring candidate’s name to the weight they contributed to the overall transfer. In most cases, there will only be one candidate in transferring_candidates and the weight will be a simple 1.0.

classmethod compute_vote_deltas_for_round(ut_rounds_tally_only, round_i)

Gathers some data and passes it on to _compute_vote_deltas_from_tally()

Parameters:
  • ut_rounds_tally_only – Incomplete Universal Tabulator ‘results’ structure, containing only ‘tally’ but not ‘tallyResults’. The tallies must be numbers, not strings.

  • round_i – Computes delta between this round and the next

Returns:

Value from _compute_vote_deltas_from_tally()

classmethod guess_at_tally_results(eliminated_names, elected_names, vote_delta, allow_guessing=True)

Computes the tallyResult, the difference between this round and the next See the description of @_compute_tally_results to understand why, in the case of multiple winners, the best we can do is guess at the transfers here.

Parameters:
  • eliminated_names – the names of each eliminated candidate

  • elected_names – the names of each elected candidate

  • vote_delta – a dict mapping candidate name to vote difference between this round and the next round.

  • allow_guessing – Allow guessing of transfer data during batch elimination

Returns:

The contents of the tallyResults dict

Opavote to Universal Tabulator

Reads an ElectionBuddy CSV results file, writes to the standard format

class conversions.opavote.OpavoteConverter

Bases: GenericGuessAtTransferConverter

Reads an opavote-formatted JSON file.

_convert_file_object_to_ut(file_object)

Just like func:~convert_to_ut, but only accepting a file object

classmethod _fill_in_tallyresults(rounds, candidate_names, ut_rounds)

Fill out rounds[‘tallyResults’] based on rounds[‘tally’]

classmethod _get_elected_names(rounds, candidate_names, round_i)
Parameters:
  • candidate_names – in-order names

  • rounds – rounds data, direct from the Opavote format

  • round_i – the current round

Returns:

list of names that were elected

classmethod _get_eliminated_names(rounds, candidate_names, round_i)

Opavote format places losses on the round after they happen, whereas the Universal Tabulator format places it on the previous round. This function looks at the next round for its data. A corellary is that there are no eliminations on the first round, which is what I believe to always be the case anyway.

Parameters:
  • rounds – rounds data, direct from the Opavote format

  • candidate_names – in-order names

  • round_i – the current round (we’ll look at round_i+1)

Returns:

list of names that were eliminated

classmethod _votes_on_round(candidate_i, rounds, round_i)

Number of votes the corresponding candidate had on round_i

ElectionBuddy to Universal Tabulator

Reads an ElectionBuddy CSV results file, writes to the standard format

class conversions.electionbuddy.ElectionBuddyConverter

Bases: GenericGuessAtTransferConverter

Reads an electionbuddy-formatted CSV file. Note that this use a generic text file reader, not a CSV reader, because it’s not really a CSV file - it has parts of it that are, but it also has miscellaneous title lines.

_convert_file_object_to_ut(file_object)

Just like func:~convert_to_ut, but only accepting a file object

classmethod _fill_in_tallyresults_from_tally(rounds, ut_rounds)

Fills in tallyResults in ut_rounds

classmethod _get_elected_names(rounds, round_i, already_elected_set)
Returns:

a list of names of candidates elected on this round

classmethod _get_round_data_without_tallyresults(rounds)

Generates the ut_rounds data without tallyResults

classmethod _get_threshold(csv_data)

Returns the threshold for the overall election, which doesn’t make a lot of sense for dynamic-threshold multiwinner elections, but we mark it as just the last threshold in the file if the file contains thresholds.

classmethod _threshold_for_round(rounds, round_i)

If the threshold is not provided in each round, assume it is half of the number of voters present in the last round. This means that if the thresold is not provided, we assume it is a single-winner election (unless there is an exact 50/50 tie) TODO: This should probably be handled by a migration function, since ElectionBuddy no longer outputs data without thresholds.

Dominion to Universal Tabulator

Reads an Dominion TXT results file, used by Alaska since they have consistently failed to publish the easier-to-read Dominion JSON file.

class conversions.dominion_txt.DominionTxtConverter

Bases: GenericGuessAtTransferConverter

Parses the dominion file format as exemplified in /testdata/inputs/dominion.txt

_convert_file_object_to_ut(file_object)

Just like func:~convert_to_ut, but only accepting a file object

classmethod _get_transfer_data(line)

These lines have cells 20-22 which looks like, including the quotes: 20: “From” 21: “To” 22: Num Votes

returns None if not relevant

classmethod _name_strip(name)

Strips quotes first, then spaces

classmethod _who_is_eliminated_or_elected(elim_or_elect_text, line)

These lines have box 17 which looks like, including the quotes: “Palin, Sarah is eliminated because the candidate was not elected in the last round.”

returns None if nobody was

Reads an Dominion XLSX results file, writes to the standard format

class conversions.dominion_xlsx.DominionXlsxConverter

Bases: GenericGuessAtTransferConverter

Parses the dominion file format as exemplified in /testdata/inputs/dominion-json These are .xlsx files

class RoundInfo

Bases: object

Data for parsing a round: Because columns are hapharzadly merged, there is no straightforward mapping from round number to the corresponding column. This little struct keeps track of it for us.

class RowConstants

Bases: object

Data for the row number that various items are on

classmethod _count_num_header_rows(sheet)

The number of header rows can vary. Looks for the first left-aligned row, which is row 12 or 13 in all data we’ve seen.

_find_row_of_inactive_ballots(sheet, num_candidates)

Returns row number labeled “Non-Transferrable Total”

_try_to_find_row_of_threshold(sheet, inactive_row)

Returns row number labeled “Threshold”

find_rows_after_summary_table(sheet, num_candidates)

Fills in values for rows after the summary table, which requires needing to know the number of candidates

find_rows_before_summary_table(workbook)

Fills in values for rows before the summary table

_convert_file_object_to_ut(file_object)

Just like func:~convert_to_ut, but only accepting a file object

classmethod _is_elected_color(color)

Is this cell colored greed, noting it is elected? Note: only call this if you’ve checked that the cell is filled solid. It may be the case that the cell is red, but not filled, in which case it appears white and is not eliminated.

classmethod _is_eliminated_color(color)

Is this cell colored red for elimination? Note: only call this if you’ve checked that the cell is filled solid. It may be the case that the cell is red, but not filled, in which case it appears white and is not eliminated.

_parse_candidates()

Grabs the list of candidate names from the summary table

_parse_config()

Returns the URCV config format

_parse_rounds()

Rounds are curious - they are separated by a varying number of columns, usually 3 except for the first few, where the initial columns have been merged seemingly randomly (but in actually: twice in round 1, once in round 2)

Therefore, this returns a pair of (Round, Column #) to get the column for the number of votes in each Round

_parse_tally_for_round_at_column(col, eliminated_names)

Creates a ‘tally’ and ‘tallyResults’ struct for the given round

_postprocess_set_threshold_from_spreadsheet(data, sheet)

The threshold is always listed on the table of per-round info We don’t guess here - if we can’t find it, we leave it blank.

Reads an Dominion XML file containing many contests.

class conversions.dominion_multi_converter.DominionMultiConverter

Bases: object

Parses the dominion first-round-only file format as exemplified in testdata/inputs/dominion-multi-converter.xml, which contains many elections.

classmethod explode_to_files(file_object)

Given the XML format with multiple elections, explodes into many files, and returns a dictionary of titles to NamedTemporaryFiles.