added AEC formality checking rules
This commit is contained in:
parent
e403ab3b82
commit
cb02be7d8e
|
|
@ -1,11 +1,6 @@
|
|||
use itertools::Itertools;
|
||||
use crate::header::Header;
|
||||
|
||||
#[derive(Copy,Clone,Debug,PartialEq)]
|
||||
enum BallotType {
|
||||
Party,
|
||||
Candidate,
|
||||
}
|
||||
use crate::{header::Header, stats::Stats};
|
||||
|
||||
|
||||
#[derive(Debug)]
|
||||
|
|
@ -14,44 +9,43 @@ pub struct Ballot {
|
|||
weight: f64,
|
||||
}
|
||||
|
||||
impl Ballot {
|
||||
pub fn parse(row: quick_csv::Row, header: &Header) -> Result<Ballot, Box<dyn std::error::Error>> {
|
||||
let cols = row.columns()?.skip(6).collect_vec();
|
||||
let mut votes: Vec<Option<usize>>;
|
||||
let ty: BallotType;
|
||||
const CANDIDATE_MIN: usize = 6;
|
||||
|
||||
let col_filter = |(index, place): (usize, &str)| match place.parse::<usize>() {
|
||||
Ok(v) => Some((index, v - 1)),
|
||||
impl Ballot {
|
||||
pub fn parse(row: quick_csv::Row, header: &Header, stats: &mut Stats) -> Result<Ballot, Box<dyn std::error::Error>> {
|
||||
let mut cols = row.columns()?.skip(6);
|
||||
|
||||
let place_filter = |(index, place): (usize, &str)| match place.parse::<i32>() {
|
||||
Ok(place) => Some((place, index)),
|
||||
Err(_) => None,
|
||||
};
|
||||
|
||||
// below the line votes
|
||||
if cols.len() > header.parties.len() {
|
||||
ty = BallotType::Candidate;
|
||||
votes = vec![None; header.candidates.len()];
|
||||
for (col_id, place) in cols.iter().copied().skip(header.parties.len()).take(header.candidates.len()).enumerate().filter_map(col_filter) {
|
||||
votes[place] = Some(col_id);
|
||||
}
|
||||
let mut votes_party = cols.by_ref().take(header.parties.len()).enumerate().filter_map(place_filter).collect_vec();
|
||||
let mut votes_candidate = cols.take(header.candidates.len()).enumerate().filter_map(place_filter).collect_vec();
|
||||
let votes;
|
||||
|
||||
if votes_candidate.len() > 0 && votes_party.len() > 0 {
|
||||
stats.both += 1;
|
||||
}
|
||||
|
||||
if votes_candidate.len() >= CANDIDATE_MIN.min(header.candidates.len()) {
|
||||
votes_candidate.sort_by(|a, b| i32::cmp(&a.0, &b.0));
|
||||
votes = votes_candidate.into_iter().map(|(_,id)| id).collect_vec();
|
||||
stats.candidate += 1;
|
||||
}
|
||||
|
||||
else if votes_party.len() > 0 {
|
||||
votes_party.sort_by(|a, b| i32::cmp(&a.0, &b.0));
|
||||
votes = votes_party.into_iter().map(|(_,id)| header.parties[id].member_ids.iter().copied()).flatten().collect_vec();
|
||||
stats.party += 1;
|
||||
}
|
||||
|
||||
// above the line votes
|
||||
else {
|
||||
ty = BallotType::Party;
|
||||
votes = vec![None; header.parties.len()];
|
||||
for (col_id, place) in cols.iter().copied().take(header.parties.len()).enumerate().filter_map(col_filter) {
|
||||
votes[place] = Some(col_id);
|
||||
}
|
||||
return Err("ballot is informal".into());
|
||||
}
|
||||
|
||||
let votes = match ty {
|
||||
BallotType::Candidate => votes.iter().filter_map(|&v| v).collect(),
|
||||
BallotType::Party => votes.iter().filter_map(|&v| v).map(|id| header.parties[id].member_ids.iter().copied()).flatten().collect(),
|
||||
};
|
||||
|
||||
Ok(Ballot {
|
||||
weight: 1.0,
|
||||
votes,
|
||||
})
|
||||
stats.total += 1;
|
||||
Ok(Ballot { weight: 1.0, votes })
|
||||
}
|
||||
pub fn get_weight(&self) -> f64 {
|
||||
self.weight
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use std::rc::Rc;
|
||||
use itertools::Itertools;
|
||||
|
||||
use crate::{ballot::Ballot, header::Header, util::ScoreItem};
|
||||
use crate::{ballot::Ballot, header::Header, stats::Stats, util::ScoreItem};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Event {
|
||||
|
|
@ -13,6 +13,7 @@ pub enum Event {
|
|||
pub struct Counter {
|
||||
pub header: Rc<Header>,
|
||||
pub ballots: Vec<Ballot>,
|
||||
pub stats: Stats,
|
||||
winners_left: usize,
|
||||
enabled: Vec<bool>,
|
||||
quota: f64,
|
||||
|
|
@ -22,13 +23,14 @@ impl Counter {
|
|||
pub fn new<T: std::io::BufRead>(mut csv: quick_csv::Csv<T>, winners: usize) -> Result<Self, Box<dyn std::error::Error>> {
|
||||
let header = Rc::new(Header::parse(csv.next().ok_or("csv header missing")??, winners)?);
|
||||
let mut ballots = Vec::new();
|
||||
let mut stats = Stats::new();
|
||||
|
||||
if winners > header.candidates.len() {
|
||||
return Err("winners can't be smaller than the candidates list".into());
|
||||
}
|
||||
|
||||
for row in csv {
|
||||
ballots.push(Ballot::parse(row?, &header)?);
|
||||
ballots.push(Ballot::parse(row?, &header, &mut stats)?);
|
||||
}
|
||||
|
||||
let enabled = vec![true; header.candidates.len()];
|
||||
|
|
@ -38,6 +40,7 @@ impl Counter {
|
|||
Ok(Counter {
|
||||
header,
|
||||
ballots,
|
||||
stats,
|
||||
winners_left,
|
||||
enabled,
|
||||
quota,
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ pub mod candidate;
|
|||
pub mod ballot;
|
||||
pub mod header;
|
||||
pub mod party;
|
||||
pub mod stats;
|
||||
mod counter;
|
||||
|
||||
fn main() {
|
||||
|
|
@ -27,7 +28,11 @@ fn main() {
|
|||
eprintln!("Parties: {}", header.parties.len());
|
||||
eprintln!("Candidates: {}", header.candidates.len());
|
||||
eprintln!("Vacancies: {}", winner_count);
|
||||
eprintln!("Ballots: {}", counter.ballots.len());
|
||||
eprintln!("Ballots:");
|
||||
eprintln!(" Total: {}", counter.stats.total);
|
||||
eprintln!(" Above: {}, {}", counter.stats.party, Percent(counter.stats.party.into(), counter.stats.total.into()));
|
||||
eprintln!(" Below: {}, {}", counter.stats.candidate, Percent(counter.stats.candidate.into(), counter.stats.total.into()));
|
||||
eprintln!(" Both: {}, {}", counter.stats.both, Percent(counter.stats.both.into(), counter.stats.total.into()));
|
||||
eprintln!("Quota: {}, {}", counter.get_quota(), Percent(counter.get_quota(), total));
|
||||
eprintln!();
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,22 @@
|
|||
use std::fmt::Debug;
|
||||
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Stats {
|
||||
pub total: u32,
|
||||
pub candidate: u32,
|
||||
pub party: u32,
|
||||
pub both: u32,
|
||||
}
|
||||
|
||||
impl Stats {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
total: 0,
|
||||
party: 0,
|
||||
candidate: 0,
|
||||
both: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
use std::fmt::Display;
|
||||
use std::fmt::{Debug, Display};
|
||||
|
||||
|
||||
pub struct Percent(pub f64, pub f64);
|
||||
|
|
@ -9,6 +9,11 @@ impl Display for Percent {
|
|||
write!(f, "{:.3}%", self.0 / self.1 * 100.0)
|
||||
}
|
||||
}
|
||||
impl Debug for Percent {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "Percent({}, {:.3}%)", self.0, self.0 / self.1 * 100.0)
|
||||
}
|
||||
}
|
||||
impl Display for PercentDiff {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let v = self.0 / self.1 * 100.0;
|
||||
|
|
@ -18,4 +23,9 @@ impl Display for PercentDiff {
|
|||
}, v.abs())
|
||||
}
|
||||
}
|
||||
impl Debug for PercentDiff {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "Percent({}, {:.3}%)", self.0, self.0 / self.1 * 100.0)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue