Skip to content

Commit

Permalink
Use u32 for party & candidate numbers (#985)
Browse files Browse the repository at this point in the history
Co-authored-by: Ellen <[email protected]>
Co-authored-by: Marlon <[email protected]>
  • Loading branch information
3 people authored Feb 11, 2025
1 parent facaf67 commit ce5b730
Show file tree
Hide file tree
Showing 4 changed files with 54 additions and 25 deletions.
4 changes: 2 additions & 2 deletions backend/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -2089,12 +2089,12 @@
"properties": {
"meets_surplus_threshold": {
"type": "boolean",
"description": "Whether this group met the threshold for surplus seat assigment"
"description": "Whether this group met the threshold for surplus seat assignment"
},
"pg_number": {
"type": "integer",
"format": "int32",
"description": "Political group number for which this assigment applies",
"description": "Political group number for which this assignment applies",
"minimum": 0
},
"rest_seats": {
Expand Down
34 changes: 21 additions & 13 deletions backend/src/apportionment/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use serde::{Deserialize, Serialize};
use tracing::{debug, info};
use utoipa::ToSchema;

use crate::election::PGNumber;
use crate::{data_entry::PoliticalGroupVotes, summary::ElectionSummary};

pub use self::{api::*, fraction::*};
Expand All @@ -24,13 +25,14 @@ pub struct ApportionmentResult {
/// Contains information about the final assignment of seats for a specific political group.
#[derive(Debug, PartialEq, Serialize, Deserialize, ToSchema)]
pub struct PoliticalGroupSeatAssignment {
/// Political group number for which this assigment applies
pg_number: u8,
/// Political group number for which this assignment applies
#[schema(value_type = u32)]
pg_number: PGNumber,
/// The number of votes cast for this group
votes_cast: u64,
/// The surplus votes that were not used to get whole seats assigned to this political group
surplus_votes: Fraction,
/// Whether this group met the threshold for surplus seat assigment
/// Whether this group met the threshold for surplus seat assignment
meets_surplus_threshold: bool,
/// The number of whole seats assigned to this group
whole_seats: u64,
Expand Down Expand Up @@ -59,7 +61,8 @@ impl From<PoliticalGroupStanding> for PoliticalGroupSeatAssignment {
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, ToSchema)]
pub struct PoliticalGroupStanding {
/// Political group number for which this standing applies
pg_number: u8,
#[schema(value_type = u32)]
pg_number: PGNumber,
/// The number of votes cast for this group
votes_cast: u64,
/// The surplus of votes that was not used to get whole seats (does not have to be a whole number of votes)
Expand Down Expand Up @@ -312,7 +315,7 @@ fn allocate_remainder(
}

/// Assign the next remainder seat, and return which group that seat was assigned to.
/// This assigment is done according to the rules for elections with 19 seats or more.
/// This assignment is done according to the rules for elections with 19 seats or more.
fn step_allocate_remainder_using_highest_averages(
standing: &[PoliticalGroupStanding],
remaining_seats: u64,
Expand Down Expand Up @@ -360,7 +363,7 @@ fn political_groups_qualifying_for_unique_highest_average<'a>(
}

/// Assign the next remainder seat, and return which group that seat was assigned to.
/// This assigment is done according to the rules for elections with less than 19 seats.
/// This assignment is done according to the rules for elections with less than 19 seats.
fn step_allocate_remainder_using_highest_surplus(
assigned_seats: &[PoliticalGroupStanding],
remaining_seats: u64,
Expand Down Expand Up @@ -427,7 +430,7 @@ pub enum AssignedSeat {

impl AssignedSeat {
/// Get the political group number for the group this step has assigned a seat
fn political_group_number(&self) -> u8 {
fn political_group_number(&self) -> PGNumber {
match self {
AssignedSeat::HighestAverage(highest_average) => highest_average.selected_pg_number,
AssignedSeat::HighestSurplus(highest_surplus) => highest_surplus.selected_pg_number,
Expand All @@ -449,9 +452,11 @@ impl AssignedSeat {
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, ToSchema)]
pub struct HighestAverageAssignedSeat {
/// The political group that was selected for this seat has this political group number
selected_pg_number: u8,
#[schema(value_type = u32)]
selected_pg_number: PGNumber,
/// The list from which the political group was selected, all of them having the same votes per seat
pg_options: Vec<u8>,
#[schema(value_type = Vec<u32>)]
pg_options: Vec<PGNumber>,
/// This is the votes per seat achieved by the selected political group
votes_per_seat: Fraction,
}
Expand All @@ -460,9 +465,11 @@ pub struct HighestAverageAssignedSeat {
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, ToSchema)]
pub struct HighestSurplusAssignedSeat {
/// The political group that was selected for this seat has this political group number
selected_pg_number: u8,
#[schema(value_type = u32)]
selected_pg_number: PGNumber,
/// The list from which the political group was selected, all of them having the same number of surplus votes
pg_options: Vec<u8>,
#[schema(value_type = Vec<u32>)]
pg_options: Vec<PGNumber>,
/// The number of surplus votes achieved by the selected political group
surplus_votes: Fraction,
}
Expand All @@ -474,7 +481,7 @@ pub enum ApportionmentError {
}

/// Create a vector containing just the political group numbers from an iterator of the current standing
fn political_group_numbers(standing: &[&PoliticalGroupStanding]) -> Vec<u8> {
fn political_group_numbers(standing: &[&PoliticalGroupStanding]) -> Vec<PGNumber> {
standing.iter().map(|s| s.pg_number).collect()
}

Expand All @@ -493,6 +500,7 @@ mod tests {
get_total_seats_from_apportionment_result, seat_allocation, ApportionmentError,
},
data_entry::{Count, PoliticalGroupVotes, VotersCounts, VotesCounts},
election::PGNumber,
summary::{ElectionSummary, SummaryDifferencesCounts},
};
use test_log::test;
Expand All @@ -502,7 +510,7 @@ mod tests {
let mut political_group_votes: Vec<PoliticalGroupVotes> = vec![];
for (index, votes) in pg_votes.iter().enumerate() {
political_group_votes.push(PoliticalGroupVotes::from_test_data_auto(
u8::try_from(index + 1).unwrap(),
PGNumber::try_from(index + 1).unwrap(),
*votes,
&[],
))
Expand Down
27 changes: 21 additions & 6 deletions backend/src/data_entry/structs.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
use crate::{data_entry::status::DataEntryStatus, error::ErrorReference, APIError};
use crate::{
data_entry::status::DataEntryStatus,
election::{CandidateNumber, PGNumber},
error::ErrorReference,
APIError,
};
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use sqlx::{types::Json, FromRow};
Expand Down Expand Up @@ -154,7 +159,8 @@ impl DifferencesCounts {

#[derive(Serialize, Deserialize, ToSchema, Clone, Debug, Default, PartialEq, Eq, Hash)]
pub struct PoliticalGroupVotes {
pub number: u8,
#[schema(value_type = u32)]
pub number: PGNumber,
#[schema(value_type = u32)]
pub total: Count,
pub candidate_votes: Vec<CandidateVotes>,
Expand Down Expand Up @@ -193,7 +199,11 @@ impl PoliticalGroupVotes {

/// Create `PoliticalGroupVotes` from test data.
#[cfg(test)]
pub fn from_test_data(number: u8, total_count: Count, candidate_votes: &[(u8, Count)]) -> Self {
pub fn from_test_data(
number: PGNumber,
total_count: Count,
candidate_votes: &[(CandidateNumber, Count)],
) -> Self {
PoliticalGroupVotes {
number,
total: total_count,
Expand All @@ -209,22 +219,27 @@ impl PoliticalGroupVotes {

/// Create `PoliticalGroupVotes` from test data with candidate numbers automatically generated starting from 1.
#[cfg(test)]
pub fn from_test_data_auto(number: u8, total_count: Count, candidate_votes: &[Count]) -> Self {
pub fn from_test_data_auto(
number: PGNumber,
total_count: Count,
candidate_votes: &[Count],
) -> Self {
Self::from_test_data(
number,
total_count,
&candidate_votes
.iter()
.enumerate()
.map(|(i, votes)| (u8::try_from(i).unwrap() + 1, *votes))
.map(|(i, votes)| (CandidateNumber::try_from(i + 1).unwrap(), *votes))
.collect::<Vec<_>>(),
)
}
}

#[derive(Serialize, Deserialize, ToSchema, Clone, Debug, PartialEq, Eq, Hash)]
pub struct CandidateVotes {
pub number: u8,
#[schema(value_type = u32)]
pub number: CandidateNumber,
#[schema(value_type = u32)]
pub votes: Count,
}
Expand Down
14 changes: 10 additions & 4 deletions backend/src/election/structs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,18 +60,24 @@ pub enum ElectionStatus {
DataEntryFinished,
}

pub type PGNumber = u32;

/// Political group with its candidates
#[derive(Serialize, Deserialize, ToSchema, Clone, Debug, PartialEq, Eq, Hash)]
pub struct PoliticalGroup {
pub number: u8,
#[schema(value_type = u32)]
pub number: PGNumber,
pub name: String,
pub candidates: Vec<Candidate>,
}

pub type CandidateNumber = u32;

/// Candidate
#[derive(Serialize, Deserialize, ToSchema, Clone, Debug, PartialEq, Eq, Hash)]
pub struct Candidate {
pub number: u8,
#[schema(value_type = u32)]
pub number: CandidateNumber,
pub initials: String,
#[serde(skip_serializing_if = "Option::is_none")]
#[schema(nullable = false)]
Expand Down Expand Up @@ -108,12 +114,12 @@ pub(crate) mod tests {
/// Create a test election with some political groups.
/// The number of political groups is the length of the `political_groups_candidates` slice.
/// The number of candidates in each political group is equal to the value in the slice at that index.
pub fn election_fixture(political_groups_candidates: &[u8]) -> Election {
pub fn election_fixture(political_groups_candidates: &[u32]) -> Election {
let political_groups = political_groups_candidates
.iter()
.enumerate()
.map(|(i, &candidates)| PoliticalGroup {
number: u8::try_from(i + 1).unwrap(),
number: u32::try_from(i + 1).unwrap(),
name: format!("Political group {}", i + 1),
candidates: (0..candidates)
.map(|j| Candidate {
Expand Down

0 comments on commit ce5b730

Please sign in to comment.