diff --git a/Cargo.lock b/Cargo.lock index b8e2707..7a559d4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,5 +1,11 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + [[package]] name = "autocfg" version = "1.0.1" @@ -18,6 +24,30 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" +[[package]] +name = "bstr" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90682c8d613ad3373e66de8c6411e0ae2ab2571e879d2efbf73558cc66f21279" +dependencies = [ + "lazy_static", + "memchr", + "regex-automata", + "serde", +] + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + [[package]] name = "clap" version = "3.0.0-beta.2" @@ -44,6 +74,49 @@ dependencies = [ "syn", ] +[[package]] +name = "crc32fast" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81156fece84ab6a9f2afdb109ce3ae577e42b1228441eded99bd77f627953b1a" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "csv" +version = "1.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22813a6dc45b335f9bade10bf7271dc477e81113e89eb251a0bc2a8a81c536e1" +dependencies = [ + "bstr", + "csv-core", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "csv-core" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b2466559f260f48ad25fe6317b3c8dac77b5bdb5763ac7d9d6103530663bc90" +dependencies = [ + "memchr", +] + +[[package]] +name = "flate2" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd3aec53de10fe96d7d8c565eb17f2c687bb5518a2ec453b5b1252964526abe0" +dependencies = [ + "cfg-if", + "crc32fast", + "libc", + "miniz_oxide", +] + [[package]] name = "git-version" version = "0.3.4" @@ -101,6 +174,12 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "itoa" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736" + [[package]] name = "lazy_static" version = "1.4.0" @@ -113,6 +192,22 @@ version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "789da6d93f1b866ffe175afc5322a4d76c038605a1c3319bb57b06967ca98a36" +[[package]] +name = "memchr" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b16bd47d9e329435e309c58469fe0791c2d0d1ba96ec0954152a5ae2b04387dc" + +[[package]] +name = "miniz_oxide" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" +dependencies = [ + "adler", + "autocfg", +] + [[package]] name = "num-traits" version = "0.2.14" @@ -127,6 +222,8 @@ name = "opentally" version = "0.1.0" dependencies = [ "clap", + "csv", + "flate2", "git-version", "num-traits", "rug", @@ -186,6 +283,15 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "regex-automata" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae1ded71d66a4a97f5e961fd0cb25a5f366a42a41570d16a763a69c092c26ae4" +dependencies = [ + "byteorder", +] + [[package]] name = "rug" version = "1.12.0" @@ -197,6 +303,18 @@ dependencies = [ "libc", ] +[[package]] +name = "ryu" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" + +[[package]] +name = "serde" +version = "1.0.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec7505abeacaec74ae4778d9d9328fe5a5d04253220a85c4ee022239fc996d03" + [[package]] name = "syn" version = "1.0.72" diff --git a/Cargo.toml b/Cargo.toml index 31bcef1..c58b054 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,6 +5,8 @@ authors = ["Lee Yingtong Li "] edition = "2018" [dependencies] +csv = "1.1.6" +flate2 = "1.0" git-version = "0.3.4" num-traits = "0.2" @@ -19,3 +21,6 @@ git = "https://github.com/clap-rs/clap" branch = "master" default-features = false features = ["std", "derive"] + +[profile.test] +opt-level = 3 diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..1ae4c63 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,21 @@ +/* OpenTally: Open-source election vote counting + * Copyright © 2021 Lee Yingtong Li (RunasSudo) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +pub mod election; +pub mod logger; +pub mod numbers; +pub mod stv; diff --git a/src/main.rs b/src/main.rs index 3e7af3c..06ef182 100644 --- a/src/main.rs +++ b/src/main.rs @@ -15,13 +15,9 @@ * along with this program. If not, see . */ -mod election; -mod logger; -mod numbers; -mod stv; - -use crate::election::{Candidate, CandidateState, CountCard, CountState, CountStateOrRef, Election, StageResult}; -use crate::numbers::{NativeFloat64, Number, Rational}; +use opentally::stv; +use opentally::election::{Candidate, CandidateState, CountCard, CountState, CountStateOrRef, Election, StageResult}; +use opentally::numbers::{NativeFloat64, Number, Rational}; use clap::{AppSettings, Clap}; use git_version::git_version; diff --git a/tests/aec.rs b/tests/aec.rs new file mode 100644 index 0000000..7b240bd --- /dev/null +++ b/tests/aec.rs @@ -0,0 +1,106 @@ +/* OpenTally: Open-source election vote counting + * Copyright © 2021 Lee Yingtong Li (RunasSudo) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +use opentally::election::{CandidateState, CountState, Election}; +use opentally::numbers::{Number, Rational}; +use opentally::stv; + +use csv::StringRecord; +use flate2::bufread::GzDecoder; + +use std::fs::File; +use std::io::{self, BufRead}; + +#[test] +fn aec_tas19_rational() { + // Read CSV file + let reader = csv::ReaderBuilder::new() + .has_headers(false) + .from_path("tests/data/aec-senate-formalpreferences-24310-TAS.csv") + .expect("IO Error"); + let records: Vec = reader.into_records().map(|r| r.expect("Syntax Error")).collect(); + + let mut candidates: Vec<&str> = records.iter().skip(2).map(|r| &r[0]).collect(); + // Remove exhausted/LBF rows + candidates.truncate(candidates.len() - 2); + + let stages: Vec = records.first().unwrap().iter().skip(1).step_by(2).map(|s| s.parse().unwrap()).collect(); + + // Decompress BLT + let file = File::open("tests/data/aec-senate-formalpreferences-24310-TAS.blt.gz").expect("IO Error"); + let file_reader = io::BufReader::new(file); + let gz_decoder = GzDecoder::new(file_reader); + let gz_reader = io::BufReader::new(gz_decoder); + let lines = gz_reader.lines(); + + // Read BLT + let election: Election = Election::from_blt(lines); + + // TODO: Validate candidate names + + // Initialise options + let stv_opts = stv::STVOptions { + round_votes: Some(0), + exclusion: "by_value", + }; + + // Initialise count state + let mut state = CountState::new(&election); + + // Distribute first preferences + stv::count_init(&mut state, &stv_opts); + let mut stage_num = 1; + + for (idx, stage) in stages.into_iter().enumerate() { + while stage_num < stage { + // Step through stages + // Assert count not yet done + assert_eq!(stv::count_one_stage(&mut state, &stv_opts), false); + stage_num += 1; + } + validate_stage(idx, &state, &records); + } +} + +fn validate_stage(idx: usize, state: &CountState, records: &Vec) { + // Validate candidate votes + let mut candidate_votes: Vec = records.iter().skip(2).map(|r| N::parse(&r[idx*2 + 1])).collect(); + // Remove exhausted/LBF rows + candidate_votes.truncate(candidate_votes.len() - 2); + + for (candidate, votes) in state.election.candidates.iter().zip(candidate_votes) { + let count_card = state.candidates.get(candidate).unwrap(); + assert!(count_card.votes == votes); + } + + // Validate candidate states + let mut candidate_states: Vec<&str> = records.iter().skip(2).map(|r| &r[idx*2 + 2]).collect(); + candidate_states.truncate(candidate_states.len() - 2); + + for (candidate, candidate_state) in state.election.candidates.iter().zip(candidate_states) { + let count_card = state.candidates.get(candidate).unwrap(); + if candidate_state == "" { + assert!(count_card.state == CandidateState::HOPEFUL); + } else if candidate_state == "EL" || candidate_state == "PEL" { + assert!(count_card.state == CandidateState::ELECTED); + } else if candidate_state == "EX" || candidate_state == "EXCLUDING" { + assert!(count_card.state == CandidateState::EXCLUDED); + } else { + panic!("Unknown state descriptor {}", candidate_state); + } + } +} diff --git a/tests/data/aec-senate-formalpreferences-24310-TAS.blt.gz b/tests/data/aec-senate-formalpreferences-24310-TAS.blt.gz new file mode 100644 index 0000000..541367c Binary files /dev/null and b/tests/data/aec-senate-formalpreferences-24310-TAS.blt.gz differ diff --git a/tests/data/aec-senate-formalpreferences-24310-TAS.csv b/tests/data/aec-senate-formalpreferences-24310-TAS.csv new file mode 100644 index 0000000..d003596 --- /dev/null +++ b/tests/data/aec-senate-formalpreferences-24310-TAS.csv @@ -0,0 +1,48 @@ +Stage:,1,,2,,3,,4,,8,,11,,15,,18,,21,,24,,28,,31,,33,,37,,40,,43,,46,,50,,53,,57,,61,,65,,69,,73,,75,,79,,83,,87,,91,,95,,99,,103,,107,,111,,115,,119,,123,,127,,128,,133,,138,,139, +Comment:,First preferences,,Surplus of COLBECK Richard,,Surplus of BROWN Carol,,Surplus of CHANDLER Claire,,Exclusion of KUCINA Steve,,Exclusion of WILLIAMS Glynn,,Exclusion of RABEY Matthew,,Exclusion of REYNOLDS Chris,,Exclusion of MACLAY Christopher Brian,,Exclusion of GUNNIS Craig,,Exclusion of HILDITCH Wendy,,Exclusion of FALZON Frank,,Exclusion of FLANNERY Francis,,Exclusion of TURNER Isobel Bertoz,,Exclusion of STREET Karen,,Exclusion of FRAME Nigel,,Exclusion of OWEN Matt,,Exclusion of SWANSON Kim,,Exclusion of DUNCAN Mark,,Exclusion of WILLIAMS David,,Exclusion of WILLIAMS Ray,,Exclusion of MARSH Simone,,Exclusion of LAMBERT Adam,,Exclusion of ROBERTS Wayne,,Exclusion of BECK Greg,,Exclusion of FLANAGAN Robert,,Exclusion of HUTCHINSON Helen,,Exclusion of JONES Michael,,Exclusion of SHORT John,,Exclusion of DUDLEY Todd William,,Exclusion of MEAD Clinton,,Exclusion of GARLAND Craig,,Exclusion of MAV Steve,,Exclusion of STRINGER Justin,,Exclusion of INFORMAL Alfred,,Exclusion of BEVIS Karen Louise,,Exclusion of MARTIN Steve,,Exclusion of BYFIELD Rebecca Anthea,,Surplus of McKIM Nick,,Exclusion of MORGAN Kevin,,Exclusion of DENISON Tanya,,Exclusion of SINGH Lisa, +STRINGER Justin,3695,,3709,,3710,,3710,,3710,,3710,,3710,,3710,,3711,,3711,,3712,,3713,,3713,,3714,,3716,,3805,,3805,,3807,,3807,,3807,,3818,,3819,,3821,,3822,,3852,,3852,,3856,,3972,,3982,,4090,,4176,,4196,,4259,,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX +FRAME Nigel,127,,129,,129,,129,,129,,129,,130,,130,,131,,131,,133,,133,,134,,135,,135,,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX +MARTIN Steve,3981,,4169,,4173,,4176,,4177,,4177,,4177,,4177,,4178,,4178,,4209,,4209,,4210,,4211,,4212,,4222,,4223,,4225,,4225,,4227,,4234,,4235,,4236,,4240,,4255,,4259,,4264,,4283,,4295,,4399,,4551,,4590,,4683,,6011,,6069,,6289,,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX +HILDITCH Wendy,60,,67,,68,,68,,68,,68,,68,,68,,69,,70,,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX +DUDLEY Todd William,1720,,1725,,1725,,1725,,1725,,1725,,1725,,1725,,1767,,1767,,1769,,1769,,1771,,1775,,1775,,1780,,1781,,1781,,1784,,1784,,1806,,1808,,1809,,1810,,1818,,1824,,1833,,1844,,1851,,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX +MACLAY Christopher Brian,63,,64,,64,,64,,64,,64,,64,,64,,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX +McKIM Nick,43212,,43282,,43345,,43347,,43347,,43348,,43349,,43350,,43353,,43353,,43354,,43355,,43362,,43368,,43369,,43370,,43379,,43379,,43387,,43389,,43406,,43451,,43453,,43458,,43467,,43487,,44314,,44318,,44376,,44907,,45000,,45942,,46070,,46275,,46911,,48797,,50009,,50530,PEL,50285,EL,50285,EL,50285,EL,50285,EL +HUTCHINSON Helen,665,,669,,673,,673,,674,,674,,674,,674,,676,,676,,684,,685,,687,,690,,690,,692,,694,,694,,697,,697,,701,,981,,983,,988,,989,,994,,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX +MARSH Simone,359,,359,,360,,360,,360,,360,,360,,361,,363,,363,,363,,363,,363,,365,,367,,369,,369,,369,,369,,370,,370,,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX +COLBECK Richard,106577,PEL,50285,EL,50285,EL,50285,EL,50285,EL,50285,EL,50285,EL,50285,EL,50285,EL,50285,EL,50285,EL,50285,EL,50285,EL,50285,EL,50285,EL,50285,EL,50285,EL,50285,EL,50285,EL,50285,EL,50285,EL,50285,EL,50285,EL,50285,EL,50285,EL,50285,EL,50285,EL,50285,EL,50285,EL,50285,EL,50285,EL,50285,EL,50285,EL,50285,EL,50285,EL,50285,EL,50285,EL,50285,EL,50285,EL,50285,EL,50285,EL,50285,EL +CHANDLER Claire,1687,,55996,PEL,55996,PEL,50285,EL,50285,EL,50285,EL,50285,EL,50285,EL,50285,EL,50285,EL,50285,EL,50285,EL,50285,EL,50285,EL,50285,EL,50285,EL,50285,EL,50285,EL,50285,EL,50285,EL,50285,EL,50285,EL,50285,EL,50285,EL,50285,EL,50285,EL,50285,EL,50285,EL,50285,EL,50285,EL,50285,EL,50285,EL,50285,EL,50285,EL,50285,EL,50285,EL,50285,EL,50285,EL,50285,EL,50285,EL,50285,EL,50285,EL +DENISON Tanya,2466,,3580,,3584,,9272,,9273,,9276,,9277,,9279,,9280,,9281,,9291,,9293,,9300,,9303,,9306,,9319,,9321,,9323,,9325,,9326,,9348,,9355,,9360,,9364,,9441,,9446,,9461,,9525,,9534,,9689,,10412,,10607,,11036,,12295,,12571,,13188,,16121,,17365,,17387,,19739,,0,EX,0,EX +BEVIS Karen Louise,4414,,4430,,4436,,4436,,4437,,4437,,4437,,4438,,4439,,4439,,4439,,4439,,4441,,4508,,4508,,4510,,4514,,4516,,4518,,4519,,4547,,4553,,4555,,4556,,4560,,4564,,4585,,4602,,4612,,4886,,5036,,5142,,5244,,5414,,6006,,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX +TURNER Isobel Bertoz,107,,109,,109,,109,,109,,109,,110,,110,,113,,113,,114,,115,,115,,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX +WILLIAMS Ray,313,,318,,322,,322,,331,,332,,333,,333,,335,,335,,335,,335,,335,,338,,338,,338,,339,,339,,339,,340,,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX +KUCINA Steve,16,,18,,18,,18,,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX +MEAD Clinton,2360,,2393,,2396,,2396,,2397,,2398,,2428,,2429,,2429,,2429,,2430,,2431,,2431,,2434,,2435,,2435,,2435,,2436,,2437,,2438,,2464,,2464,,2465,,2470,,2476,,2479,,2481,,2513,,2525,,2563,,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX +RABEY Matthew,40,,45,,48,,48,,48,,48,,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX +BROWN Carol,83829,PEL,83829,PEL,50285,EL,50285,EL,50285,EL,50285,EL,50285,EL,50285,EL,50285,EL,50285,EL,50285,EL,50285,EL,50285,EL,50285,EL,50285,EL,50285,EL,50285,EL,50285,EL,50285,EL,50285,EL,50285,EL,50285,EL,50285,EL,50285,EL,50285,EL,50285,EL,50285,EL,50285,EL,50285,EL,50285,EL,50285,EL,50285,EL,50285,EL,50285,EL,50285,EL,50285,EL,50285,EL,50285,EL,50285,EL,50285,EL,50285,EL,50285,EL +BILYK Catryna,1588,,1604,,34420,,34421,,34421,,34422,,34422,,34422,,34422,,34422,,34422,,34423,,34425,,34427,,34429,,34430,,34430,,34430,,34430,,34430,,34472,,34475,,34477,,34521,,34526,,34661,,34681,,34716,,35394,,35535,,35895,,36171,,36387,,36521,,37114,,37996,,38396,,39653,,39778,,41452,,45532,,64029,PEL +SHORT John,1217,,1224,,1394,,1394,,1397,,1397,,1397,,1398,,1398,,1398,,1398,,1398,,1399,,1401,,1401,,1402,,1404,,1404,,1404,,1404,,1407,,1408,,1408,,1478,,1483,,1661,,1667,,1667,,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX +SINGH Lisa,19984,,20137,,20432,,20435,,20435,,20435,,20435,,20437,,20438,,20438,,20439,,20439,,20440,,20442,,20444,,20445,,20445,,20446,,20451,,20454,,20461,,20473,,20478,,20576,,20585,,21026,,21067,,21070,,21815,,21856,,21861,,22329,,22559,,22580,,22624,,22717,,22817,,22930,,22986,,23078,,24132,,970,EXCLUDING +ROBERTS Wayne,366,,368,,398,,398,,398,,398,,400,,400,,400,,400,,400,,400,,400,,400,,400,,400,,400,,400,,400,,402,,403,,403,,404,,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX +FLANAGAN Robert,686,,690,,730,,730,,730,,730,,730,,730,,730,,730,,730,,730,,730,,730,,731,,731,,733,,733,,733,,733,,733,,733,,734,,864,,865,,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX +STEPHEN Matthew,11803,,11829,,11841,,11841,,11841,,11843,,11844,,11844,,11844,,11845,,11845,,11846,,11846,,11848,,11853,,11853,,11856,,11860,,11862,,11867,,11913,,11913,,12198,,12198,,12227,,12232,,12235,,12787,,12798,,12856,,13102,,13161,,13463,,13937,,14254,,14790,,15249,,16862,,16867,,20413,,25591,,25741, +LAMBERT Adam,356,,359,,363,,363,,363,,363,,364,,364,,364,,365,,366,,368,,368,,368,,368,,369,,370,,371,,371,,375,,377,,378,,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX +JONES Michael,1451,,1454,,1456,,1456,,1456,,1456,,1456,,1456,,1456,,1456,,1457,,1512,,1512,,1512,,1516,,1516,,1516,,1517,,1517,,1518,,1536,,1538,,1546,,1548,,1550,,1553,,1553,,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX +FALZON Frank,77,,78,,78,,78,,78,,78,,78,,79,,80,,81,,81,,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX +LAMBIE Jacqui,31292,,31488,,31532,,31535,,31535,,31563,,31565,,31604,,31604,,31606,,31609,,31617,,31625,,31628,,31650,,31653,,31657,,31659,,31666,,31674,,31706,,31709,,31754,,31773,,31835,,31866,,31881,,32122,,32186,,32312,,32582,,33554,,34856,,35070,,35723,,36442,,36973,,39356,,39378,,43653,,49400,,53389,PEL +WILLIAMS Glynn,39,,41,,41,,41,,41,,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX +REYNOLDS Chris,52,,52,,52,,52,,52,,54,,54,,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX +INFORMAL Alfred,4012,,4013,,4017,,4017,,4017,,4017,,4017,,4019,,4019,,4019,,4019,,4020,,4022,,4026,,4029,,4029,,4122,,4125,,4125,,4129,,4139,,4140,,4142,,4143,,4151,,4152,,4159,,4201,,4208,,4316,,4394,,4544,,4739,,4789,,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX +OWEN Matt,129,,129,,129,,129,,129,,129,,129,,130,,130,,131,,131,,131,,133,,135,,138,,138,,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX +MORGAN Kevin,9078,,9105,,9110,,9110,,9110,,9110,,9110,,9110,,9110,,9121,,9121,,9121,,9122,,9123,,9125,,9125,,9126,,9131,,9135,,9277,,9283,,9283,,9284,,9284,,9300,,9303,,9306,,9480,,9492,,9530,,9676,,9759,,10064,,10233,,10614,,10860,,11112,,12790,,12794,,0,EX,0,EX,0,EX +WILLIAMS David,138,,140,,140,,140,,140,,140,,140,,140,,140,,183,,183,,183,,184,,184,,187,,187,,188,,190,,190,,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX +GUNNIS Craig,65,,66,,66,,66,,66,,66,,66,,67,,68,,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX +GARLAND Craig,3475,,3495,,3503,,3503,,3503,,3503,,3504,,3504,,3504,,3504,,3504,,3505,,3509,,3509,,3509,,3509,,3512,,3516,,3654,,3656,,3662,,3664,,3665,,3665,,3682,,3687,,3694,,3705,,3712,,3743,,3750,,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX +DUNCAN Mark,174,,175,,175,,175,,175,,175,,175,,176,,176,,176,,176,,177,,178,,178,,179,,181,,183,,184,,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX +BYFIELD Rebecca Anthea,5971,,5981,,5985,,5985,,5985,,5985,,5985,,5985,,5985,,5987,,5987,,5987,,5992,,5993,,5998,,5998,,5998,,6129,,6131,,6138,,6156,,6156,,6160,,6162,,6194,,6198,,6199,,6382,,6391,,6439,,6577,,6825,,7430,,7560,,8671,,9286,,9456,,0,EX,0,EX,0,EX,0,EX,0,EX +SWANSON Kim,162,,163,,163,,163,,163,,163,,163,,163,,163,,164,,164,,165,,166,,167,,170,,170,,174,,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX +BECK Greg,408,,412,,412,,412,,412,,412,,413,,413,,413,,413,,413,,413,,415,,415,,434,,434,,434,,436,,437,,437,,437,,438,,439,,439,,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX +MAV Steve,3579,,3594,,3594,,3594,,3594,,3594,,3594,,3594,,3595,,3597,,3599,,3601,,3623,,3624,,3661,,3661,,3663,,3670,,3675,,3677,,3678,,3678,,3681,,3687,,3789,,3793,,3795,,3818,,3825,,3841,,3845,,3987,,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX +FLANNERY Francis,83,,83,,83,,83,,83,,83,,83,,83,,83,,83,,83,,83,,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX +STREET Karen,112,,113,,113,,113,,113,,113,,113,,113,,113,,113,,113,,114,,122,,122,,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX,0,EX +Exhausted,0,,0,,0,,0,,0,,0,,0,,0,,0,,0,,0,,0,,0,,0,,0,,1,,1,,1,,2,,2,,9,,9,,9,,9,,9,,11,,13,,37,,39,,73,,175,,224,,239,,344,,472,,663,,895,,1542,,1550,,2405,,6101,,6627, +Loss to fraction,0,,19,,35,,46,,47,,49,,54,,54,,54,,55,,60,,60,,60,,60,,60,,61,,61,,62,,62,,63,,67,,69,,72,,78,,79,,85,,89,,91,,98,,98,,101,,102,,104,,104,,104,,105,,105,,105,,108,,108,,92,,92, diff --git a/tests/data/aec-senate-formalpreferences-24310-TAS.ods b/tests/data/aec-senate-formalpreferences-24310-TAS.ods new file mode 100644 index 0000000..5187104 Binary files /dev/null and b/tests/data/aec-senate-formalpreferences-24310-TAS.ods differ