Skip to content

Commit

Permalink
edit text to be accurate in bottoms-up rcv
Browse files Browse the repository at this point in the history
  • Loading branch information
artoonie committed Aug 24, 2023
1 parent 027bb95 commit e83a648
Show file tree
Hide file tree
Showing 4 changed files with 50 additions and 13 deletions.
2 changes: 1 addition & 1 deletion testData/expected-video-script.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
Ranked Choice Voting Election Results\n\n\nCity of Eastpointe, Macomb County, MI
In this Proportional, Multi-Winner Ranked Choice Voting election, there were 5 rounds, after which Harvey Curley and Larry Edwards were elected. Here's what happened in each round.
In this Multi-Winner Ranked Choice Voting election, there were 5 rounds, after which Harvey Curley and Larry Edwards were elected. Here's what happened in each round.
In the first round, Larry Edwards received the most votes.
In the second round, People who voted for Write-In had their votes transferred to their next choice. Write-In had the fewest votes and was eliminated. Harvey Curley reached the threshold of 134 votes and was elected.
In the third round, Harvey Curley received the most votes. Harvey Curley had more than enough votes to win, so to ensure no vote is wasted, 10 surplus votes were redistributed to other candidates.
Expand Down
23 changes: 17 additions & 6 deletions visualizer/descriptors/roundDescriber.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,20 +147,31 @@ def _describe_winners_this_round(self, roundNum):
""" e.g. "Foo reached the threshold of X and was elected. "
Returns empty string if there wasn't a winner. """
rounds = self.graph.summarize().rounds
winners = rounds[roundNum].winnerNames
winnerNames = rounds[roundNum].winnerNames
winnerItems = rounds[roundNum].winnerItems

# Note: each event shows just one winner. If there are multiple winners,
# there will be multiple sentences in the main vis. (Not true in the video...)
# So, set numWinners to 1, not len(winners)
event = textForWinnerUtils.as_event(self.config, 1)

if self.graph.threshold is not None:
thresholdString = intify(self.graph.threshold)
whatHappened = "{name} reached the threshold of "\
f"{thresholdString} votes and {event}. "
# Did all candidates pass the threshold?
candidates = self.graph.summarize().candidates
thresh = self.graph.threshold
allOverThreshold = all(candidates[item].totalVotesPerRound[roundNum] > thresh
for item in winnerItems)

if allOverThreshold:
thresholdString = intify(self.graph.threshold)
whatHappened = "{name} reached the threshold of "\
f"{thresholdString} votes and {event}. "
else:
whatHappened = "{name} is among the top vote-getters " \
f" and {event}. "
else:
whatHappened = "{name} " + event + ". "
return self._describe_list_of_names(winners, " won", whatHappened)
return self._describe_list_of_names(winnerNames, " won", whatHappened)

@classmethod
def _describe_first_round(cls, roundNum):
Expand Down Expand Up @@ -215,7 +226,7 @@ def describe_initial_summary(self, isForVideo):
self.summarizeAsParagraph = originalSummarizeAsParagraph

if len(winners) > 1:
electionType = "Proportional, Multi-Winner Ranked Choice Voting election"
electionType = "Multi-Winner Ranked Choice Voting election"
else:
electionType = "Ranked Choice Voting election"

Expand Down
16 changes: 10 additions & 6 deletions visualizer/graph/graphSummary.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,13 @@ def __init__(self, graph):
if node.isWinner:
# Only count winner the first time they win
if item not in alreadyWonInPreviousRound:
rounds[currRound].add_winner(item.name)
rounds[currRound].add_winner(item)
alreadyWonInPreviousRound.append(item)
if node.isEliminated:
# Eliminate the next round: in the sankey representation,
# eliminated candidates are shown on the previous round
# so they don't ever show zero-vote bars. Account for that.
rounds[currRound + 1].add_eliminated(item.name)
rounds[currRound + 1].add_eliminated(item)

# Create linksByNode
linksByTargetNode = {}
Expand All @@ -64,6 +64,8 @@ class RoundInfo:

def __init__(self, round_i):
self.round_i = round_i
self.eliminatedItems = []
self.winnerItems = []
self.eliminatedNames = []
self.winnerNames = []
self.totalActiveVotes = 0 # The total number of active ballots this round
Expand All @@ -72,13 +74,15 @@ def key(self):
""" Returns the "key" for this round (just the round number) """
return self.round_i

def add_eliminated(self, name):
def add_eliminated(self, item):
""" Adds the name to the list of names eliminated this round """
self.eliminatedNames.append(name)
self.eliminatedItems.append(item)
self.eliminatedNames.append(item.name)

def add_winner(self, name):
def add_winner(self, item):
""" Adds the name to the list of names elected this round """
self.winnerNames.append(name)
self.winnerItems.append(item)
self.winnerNames.append(item.name)

def add_votes(self, candidateItem, numVotes):
""" Notes that the given Candidate received numVotes votes - unless they're
Expand Down
22 changes: 22 additions & 0 deletions visualizer/tests/testFaq.py
Original file line number Diff line number Diff line change
Expand Up @@ -252,3 +252,25 @@ def test_text_for_winner_in_summaries(self):
for desc in descList:
assert isinstance(desc['description'], str)
self.assertNotIn(searchFor, desc['description'])

# final round shouldn't have any threshold logic
desc = allRoundsDesc[-1][-1]
self.assertNotIn("is among the top vote-getters", desc['description'])
self.assertNotIn("reached the threshold", desc['description'])

def test_winner_didnt_meet_threshold(self):
"""
If a winner doesn't meet the threshold, don't claim they did
"""
tf = TestHelpers.modify_json_with(filenames.THREE_ROUND,
lambda d: d['config'].update({'threshold': 9999}))
with open(tf.name, 'r', encoding='utf-8') as f:
graph = make_graph_with_file(f, False)

describer = Describer(graph, self.config, False)
allRoundsDesc = describer.describe_all_rounds()

# final round shouldn't just say they were elected, not that they reached the threshold
desc = allRoundsDesc[-1][-1]
self.assertIn("is among the top vote-getters", desc['description'])
self.assertNotIn("reached the threshold", desc['description'])

0 comments on commit e83a648

Please sign in to comment.