Skip to content

Commit

Permalink
Ranked vote stats (#77)
Browse files Browse the repository at this point in the history
  • Loading branch information
leomanza authored Feb 4, 2025
1 parent 564e310 commit 5561640
Show file tree
Hide file tree
Showing 5 changed files with 43 additions and 16 deletions.
2 changes: 1 addition & 1 deletion server/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion server/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "mina-ocv"
version = "0.14.1"
version = "0.14.2"
edition = "2021"

[dependencies]
Expand Down
15 changes: 7 additions & 8 deletions server/src/ocv.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ use rust_decimal::Decimal;
use serde::Serialize;

use crate::{
Archive, Ledger, Network, Proposal, RankedVote, ReleaseStage, RoundStats, Vote, VoteRules, VoteWithWeight,
VotingResult, Wrapper, ranked_vote::run_simple_election, util::Caches,
Archive, ElectionResult, ElectionStats, Ledger, Network, Proposal, RankedVote, ReleaseStage, Vote, VoteRules,
VoteWithWeight, Wrapper, ranked_vote::run_simple_election, util::Caches,
};

#[derive(Clone)]
Expand Down Expand Up @@ -284,13 +284,12 @@ impl Ocv {
let vote_rules = VoteRules::default();
let election = run_simple_election(&votes, &vote_rules);
let voting_result = match election {
Ok(result) => result, // If the election is successful, take the VotingResult.
Ok(result) => result, // If the election is successful, take the ElectionResult.
Err(error) => {
eprintln!("Election failed with error: {:?}", error);
VotingResult {
ElectionResult {
winners: Some(vec![]), // Default to no winners
threshold: 0, // Set threshold to 0 or another sensible default
round_stats: vec![], // Default to empty round statistics
stats: vec![], // Default to empty round statistics
}
}
};
Expand All @@ -299,7 +298,7 @@ impl Ocv {
round_id,
total_votes: votes.len(),
winners: voting_result.winners.unwrap_or_else(Vec::new),
round_stats: voting_result.round_stats,
stats: voting_result.stats,
votes: ranked_votes.into_values().collect(),
})
}
Expand Down Expand Up @@ -352,6 +351,6 @@ pub struct GetMinaRankedVoteResponse {
round_id: usize,
total_votes: usize,
winners: Vec<String>,
round_stats: Vec<RoundStats>,
stats: Vec<ElectionStats>,
votes: Vec<RankedVote>,
}
21 changes: 15 additions & 6 deletions server/src/ranked_vote.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ use serde::{Deserialize, Serialize};
use tracing::log::{debug, info};

use crate::{
Ballot, BallotChoice, Builder, Candidate, DuplicateCandidateMode, EliminationAlgorithm, EliminationStats,
MaxSkippedRank, OverVoteRule, RoundStats, TieBreakMode, VoteRules, VotingErrors, VotingResult, Wrapper,
archive::FetchTransactionResult, vote::BlockStatus,
Ballot, BallotChoice, Builder, Candidate, DuplicateCandidateMode, ElectionResult, ElectionStats,
EliminationAlgorithm, EliminationStats, MaxSkippedRank, OverVoteRule, RoundStats, TieBreakMode, VoteRules,
VotingErrors, VotingResult, Wrapper, archive::FetchTransactionResult, vote::BlockStatus,
};

// **** Private structures ****
Expand Down Expand Up @@ -254,9 +254,13 @@ struct RoundResult {
/// Multi-winner proportional election using the instant-runoff voting
/// algorithm. Runs single-winner elections until the required number
/// of winners is reached or no remaining candidates are left.
pub fn run_election(builder: &Builder) -> Result<VotingResult, VotingErrors> {
pub fn run_election(builder: &Builder) -> Result<ElectionResult, VotingErrors> {
let mut winners: Vec<String> = Vec::new();
let mut remaining_candidates = builder._candidates.to_owned().unwrap_or_default();
let mut all_round_stats: Vec<ElectionStats> = Vec::new();

let mut spot_position = 0; // Track ranking spot

while winners.len() < builder._rules.max_rankings_allowed.unwrap_or(usize::MAX as u32) as usize
&& !remaining_candidates.is_empty()
{
Expand All @@ -266,19 +270,24 @@ pub fn run_election(builder: &Builder) -> Result<VotingResult, VotingErrors> {
if let Some(mut elected_winners) = result.winners {
winners.append(&mut elected_winners); // Flatten the Option<Vec<String>> into Vec<String>
remaining_candidates.retain(|c| !winners.contains(&c.name)); // Remove elected candidates
// Increment ranking spot for next selection
spot_position += elected_winners.len() as u32;
let election_stats = ElectionStats { spot_position, round_stats: result.round_stats };

all_round_stats.push(election_stats);
}
}
Err(error) => {
eprintln!("Election failed with error: {:?}", error);
}
};
}
Ok(VotingResult { threshold: 0, winners: Some(winners), round_stats: vec![] })
Ok(ElectionResult { winners: Some(winners), stats: all_round_stats })
}

/// Runs an election (simple interface) using the instant-runoff voting
/// algorithm.
pub fn run_simple_election(votes: &[Vec<&str>], rules: &VoteRules) -> Result<VotingResult, VotingErrors> {
pub fn run_simple_election(votes: &[Vec<&str>], rules: &VoteRules) -> Result<ElectionResult, VotingErrors> {
let mut builder = Builder::new(rules)?;

{
Expand Down
19 changes: 19 additions & 0 deletions server/src/ranked_vote_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,25 @@ pub struct EliminationStats {
pub exhausted: u64,
}

/// Election Statistics.
#[derive(Eq, PartialEq, Debug, Clone, Serialize, Deserialize)]
pub struct ElectionStats {
/// The position of the current election spot in the ranking process, starting
/// from 1.
pub spot_position: u32,
/// The statistics for each spot
pub round_stats: Vec<RoundStats>,
}

/// Election result.
#[derive(Eq, PartialEq, Debug, Clone)]
pub struct ElectionResult {
/// The winner(s) of this election, if any.
pub winners: Option<Vec<String>>,
/// The statistics for each round.
pub stats: Vec<ElectionStats>,
}

/// Statistics for one round
#[derive(Eq, PartialEq, Debug, Clone, Serialize, Deserialize)]
pub struct RoundStats {
Expand Down

0 comments on commit 5561640

Please sign in to comment.