forked from yvv/decision
Le quorum Q (votants = pour + contre) remplace le C additif : T' = max(T, Q), la version passe si pour ≥ ⌈B^W + ratio(T')·T'⌉, ratio la courbe des cinq crans (M, G calé à la participation de référence, inchangé). La règle d'Yvv du 03/11 (« Ils comptent comme non ») est seule dans threshold::judged_voters. Pour l'écran : yes_needed (le total de pour qui passe avec les contre d'aujourd'hui) et no_needed (le total de contre qui tient les pour d'aujourd'hui, 0 si le quorum suffit) ; passe ⇔ pour ≥ yesNeeded ⇔ contre < noNeeded, éprouvé sur une grille. well_rated : la part tient sur ceux qui sont venus. Scale porte les quorums par cran, UnitState le quorum propre d'un article ; p0 reste 2·vote_floor/W. Vu rouge : sans la règle (T' = T), 9 tests échouent ; avec l'autre lecture d'un quorum (un minimum de votants, les absents non comptés), 8 échouent, dont « un contre n'aide jamais ». Chiffres de contrôle (W 5 707, p0 = 100/W) tenus : 9, 39, 46 au quorum ; 7, 8, 9, 9, 10 à dix votants. Le serveur suit au commit suivant. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
414 lines
18 KiB
Rust
414 lines
18 KiB
Rust
//! Inertial threshold for binary votes — the Ğ1 "g1vote" formula, judged on a quorum.
|
||
//!
|
||
//! ```text
|
||
//! T' = max(T, Q)
|
||
//! R = B^W + (M + (1 − M)(1 − (T'/W)^G)) · T'
|
||
//! ```
|
||
//!
|
||
//! R is the minimum number of *for* votes; a version passes when `for ≥ ⌈R⌉`.
|
||
//! W is the size of the electorate, T the votes cast (for + against). At low
|
||
//! participation the formula asks for near-unanimity, at full participation the
|
||
//! simple majority M suffices. Q is the **quorum** (ADR 0013, Yvv, 2026-11-02:
|
||
//! « ce n'est pas un nombre de vote pour a priori, c'est plutôt un quorum, à partir
|
||
//! de ce nombre, alors il faut la bonne proportion attendue par l'indice ») — and
|
||
//! below it the voters missing count as against (`judged_voters`). It replaces the
|
||
//! floor C of *for* votes added in front of the formula (ADR 0002, 0007).
|
||
//!
|
||
//! Parity with `maquette/frontend/app/engine/threshold.ts` at Q = 0 (the C = 0 of
|
||
//! the maquette), with two deliberate guards — the participation ratio is capped
|
||
//! at 1, so a stale W can never lower the bar below M; and a count that lands on a
|
||
//! whole number stays on it (`settle`), so a rounding error never adds a vote.
|
||
//! Which M, G and Q a text is judged by: `inertia` (five levels, their quorums).
|
||
|
||
use serde::{Deserialize, Serialize};
|
||
|
||
/// The letters of the formula, named.
|
||
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
|
||
pub struct FormulaParams {
|
||
/// M, in percent (0–100).
|
||
pub majority_pct: f64,
|
||
/// G.
|
||
pub gradient: f64,
|
||
/// B.
|
||
pub base: f64,
|
||
/// Q — the quorum: the voters (for + against) from which the share of the inertia
|
||
/// applies; below it, the voters missing count as against (`judged_voters`).
|
||
pub quorum: u32,
|
||
}
|
||
|
||
impl FormulaParams {
|
||
pub const DEFAULT_BASE: f64 = 0.1;
|
||
}
|
||
|
||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||
pub enum ThresholdError {
|
||
EmptyElectorate,
|
||
MajorityOutOfRange,
|
||
}
|
||
|
||
/// T' — the voters a version is judged on. **The rule of the quorum, here alone.**
|
||
///
|
||
/// Yvv, 2026-11-03, to « Sous le quorum, les votants qui manquent comptent-ils comme des
|
||
/// « non » ? » : « Ils comptent comme non ». Below the quorum Q, the voters who did not
|
||
/// come count as against: the share of the inertia is asked of max(T, Q) voters. At level
|
||
/// 2, quorum 12, 9 « pour » pass with 3 « contre » at most; Yvv's own example, 60 % and
|
||
/// Q = 10, takes 6 « pour ». So an « against » never helps a version pass — completing
|
||
/// the quorum with a « non » adds nothing the missing voters did not already count.
|
||
pub fn judged_voters(cast: u32, quorum: u32) -> u32 {
|
||
cast.max(quorum)
|
||
}
|
||
|
||
/// The share of *for* votes asked of `voters` voters (T' when judging a version).
|
||
pub fn required_ratio(electorate: u32, voters: u32, params: &FormulaParams) -> f64 {
|
||
let m = params.majority_pct / 100.0;
|
||
let participation = (f64::from(voters) / f64::from(electorate.max(1))).min(1.0);
|
||
m + (1.0 - m) * (1.0 - participation.powf(params.gradient))
|
||
}
|
||
|
||
/// ⌈R⌉ — the minimum number of *for* votes for adoption, at `cast` votes cast.
|
||
pub fn wot_threshold(electorate: u32, cast: u32, params: &FormulaParams) -> Result<u32, ThresholdError> {
|
||
if electorate == 0 {
|
||
return Err(ThresholdError::EmptyElectorate);
|
||
}
|
||
if !(0.0..=100.0).contains(¶ms.majority_pct) {
|
||
return Err(ThresholdError::MajorityOutOfRange);
|
||
}
|
||
let voters = judged_voters(cast, params.quorum);
|
||
let offset = params.base.powf(f64::from(electorate));
|
||
let raw = if voters == 0 {
|
||
offset
|
||
} else {
|
||
offset + settle(required_ratio(electorate, voters, params) * f64::from(voters))
|
||
};
|
||
// `raw` is finite and non-negative for any valid input; saturate rather than wrap.
|
||
Ok(clamp_ceil(raw))
|
||
}
|
||
|
||
/// Does a version with this tally pass? At least one vote cast, and its *for* reach
|
||
/// ⌈R⌉ on the voters it is judged on (`judged_voters`).
|
||
pub fn passes(tally: Tally, electorate: u32, params: &FormulaParams) -> Result<bool, ThresholdError> {
|
||
let cast = tally.cast();
|
||
Ok(cast > 0 && tally.for_votes >= wot_threshold(electorate, cast, params)?)
|
||
}
|
||
|
||
/// A count of votes that lands on a whole number stays on it: a rounding error
|
||
/// of the logarithms (a billionth of a vote) must never raise a threshold by
|
||
/// one. At the usual participation, 90 % of 70 votes is exactly 63.
|
||
fn settle(votes: f64) -> f64 {
|
||
let whole = votes.round();
|
||
if (votes - whole).abs() < 1e-9 { whole } else { votes }
|
||
}
|
||
|
||
fn clamp_ceil(raw: f64) -> u32 {
|
||
let ceiled = raw.ceil();
|
||
if ceiled <= 0.0 {
|
||
0
|
||
} else if ceiled >= f64::from(u32::MAX) {
|
||
u32::MAX
|
||
} else {
|
||
// Exact: `ceiled` is an integer in (0, u32::MAX).
|
||
ceiled as u32
|
||
}
|
||
}
|
||
|
||
/// Votes counted on one proposal.
|
||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
|
||
pub struct Tally {
|
||
#[serde(rename = "for")]
|
||
pub for_votes: u32,
|
||
pub against: u32,
|
||
}
|
||
|
||
impl Tally {
|
||
pub fn cast(&self) -> u32 {
|
||
self.for_votes.saturating_add(self.against)
|
||
}
|
||
}
|
||
|
||
/// Where a proposal stands, in words a screen can say.
|
||
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub struct Evaluation {
|
||
pub cast: u32,
|
||
/// Q, the quorum it is judged on.
|
||
pub quorum: u32,
|
||
/// ⌈R⌉ at the current participation (on T' = max(T, Q) voters).
|
||
pub required: u32,
|
||
/// `passes`: `for ≥ ⌈R⌉` with at least one vote cast.
|
||
pub reached: bool,
|
||
/// *for* votes still missing with the *against* of now: `yes_needed − for` (0 once reached).
|
||
pub missing: u32,
|
||
/// The share of *for* the inertia asks of T' voters.
|
||
pub required_ratio: f64,
|
||
/// « Bien cotée » : it holds the share its inertia asks of the T voters who came,
|
||
/// it only lacks voters (the quorum ignored). Used by the "if the well-rated pass" preview.
|
||
pub well_rated: bool,
|
||
/// The total of *for* that passes with the *against* cast now.
|
||
pub yes_needed: u32,
|
||
/// The total of *against* that holds the *for* cast now in check: 0 when the quorum
|
||
/// already holds them (they do not pass even without any *against*).
|
||
pub no_needed: u32,
|
||
}
|
||
|
||
/// The number of votes searched for never goes past this: the five levels always find
|
||
/// theirs well before (`yes_needed`, `no_needed`).
|
||
const SEARCH_LIMIT: u32 = u32::MAX / 4;
|
||
|
||
/// The smallest `n` in `0..` for which `holds(n)`, `holds` being monotone (false, then
|
||
/// true); `u32::MAX` if it never holds up to `SEARCH_LIMIT`.
|
||
fn first(mut holds: impl FnMut(u32) -> Result<bool, ThresholdError>) -> Result<u32, ThresholdError> {
|
||
if holds(0)? {
|
||
return Ok(0);
|
||
}
|
||
let (mut lo, mut hi) = (0u32, 1u32);
|
||
while !holds(hi)? {
|
||
if hi >= SEARCH_LIMIT {
|
||
return Ok(u32::MAX);
|
||
}
|
||
lo = hi;
|
||
hi = hi.saturating_mul(2).min(SEARCH_LIMIT);
|
||
}
|
||
// holds(lo) is false, holds(hi) true.
|
||
while hi - lo > 1 {
|
||
let mid = lo + (hi - lo) / 2;
|
||
if holds(mid)? { hi = mid } else { lo = mid }
|
||
}
|
||
Ok(hi)
|
||
}
|
||
|
||
/// The total of *for* that passes with `against` votes against. A *for* more never
|
||
/// hurts (`passes` is monotone): every total from this one on passes.
|
||
pub fn yes_needed(against: u32, electorate: u32, params: &FormulaParams) -> Result<u32, ThresholdError> {
|
||
first(|y| passes(Tally { for_votes: y, against }, electorate, params))
|
||
}
|
||
|
||
/// The total of *against* that holds `for_votes` in check; 0 when they do not pass even
|
||
/// without any *against* (the quorum holds them). An *against* more never helps: every
|
||
/// total from this one on holds them.
|
||
pub fn no_needed(for_votes: u32, electorate: u32, params: &FormulaParams) -> Result<u32, ThresholdError> {
|
||
first(|n| passes(Tally { for_votes, against: n }, electorate, params).map(|p| !p))
|
||
}
|
||
|
||
pub fn evaluate(tally: Tally, electorate: u32, params: &FormulaParams) -> Result<Evaluation, ThresholdError> {
|
||
let cast = tally.cast();
|
||
let required = wot_threshold(electorate, cast, params)?;
|
||
let reached = passes(tally, electorate, params)?;
|
||
let without_quorum = FormulaParams { quorum: 0, ..*params };
|
||
let required_without_quorum = wot_threshold(electorate, cast, &without_quorum)?;
|
||
let yes = yes_needed(tally.against, electorate, params)?;
|
||
Ok(Evaluation {
|
||
cast,
|
||
quorum: params.quorum,
|
||
required,
|
||
reached,
|
||
missing: if reached { 0 } else { yes.saturating_sub(tally.for_votes) },
|
||
required_ratio: required_ratio(electorate, judged_voters(cast, params.quorum), params),
|
||
well_rated: cast > 0 && tally.for_votes >= required_without_quorum,
|
||
yes_needed: yes,
|
||
no_needed: no_needed(tally.for_votes, electorate, params)?,
|
||
})
|
||
}
|
||
|
||
#[cfg(test)]
|
||
mod tests {
|
||
use super::*;
|
||
|
||
fn g1vote(m: f64, g: f64) -> FormulaParams {
|
||
FormulaParams { majority_pct: m, gradient: g, base: 0.1, quorum: 0 }
|
||
}
|
||
|
||
/// The letters of the « standard » inertia before the five levels: M 50 %, G 0.2.
|
||
fn standard(quorum: u32) -> FormulaParams {
|
||
FormulaParams { quorum, ..g1vote(50.0, 0.2) }
|
||
}
|
||
|
||
#[test]
|
||
fn reference_case_of_the_smith_vote() {
|
||
// Engagement forgeron v2.0.0: W 7224, 97 for, 23 against, M50 B.1 G.2 → 94.
|
||
// The formula's own reference, letters given, without a quorum (Q = 0, the C = 0
|
||
// of the maquette): the five levels choose M, G and Q (ADR 0007, 0013).
|
||
let p = g1vote(50.0, 0.2);
|
||
assert_eq!(wot_threshold(7224, 120, &p), Ok(94));
|
||
let e = evaluate(Tally { for_votes: 97, against: 23 }, 7224, &p).expect("valid");
|
||
assert!(e.reached);
|
||
assert_eq!(e.missing, 0);
|
||
}
|
||
|
||
#[test]
|
||
fn parity_with_the_typescript_reference() {
|
||
// Values computed by maquette/frontend/app/engine/threshold.ts (C = 0).
|
||
let p = g1vote(50.0, 0.2);
|
||
assert_eq!(wot_threshold(7224, 0, &p), Ok(0));
|
||
assert_eq!(wot_threshold(7224, 1, &p), Ok(1));
|
||
assert_eq!(wot_threshold(100, 100, &p), Ok(50));
|
||
assert_eq!(wot_threshold(5, 5, &p), Ok(3));
|
||
let high = g1vote(60.0, 0.4);
|
||
assert_eq!(wot_threshold(7224, 120, &high), Ok(111));
|
||
}
|
||
|
||
#[test]
|
||
fn yvv_s_example_sixty_percent_and_a_quorum_of_ten() {
|
||
// Yvv, 2026-11-02: « Si c'est 60% et c=10, alors il faut 6 voix pour et ça passe. »
|
||
// A share of 60 % whatever the participation (G = 0), a large web (B^W vanishes).
|
||
let p = FormulaParams { majority_pct: 60.0, gradient: 0.0, base: 0.1, quorum: 10 };
|
||
// G = 0: (T/W)^0 = 1, the share is M, 60 %.
|
||
assert!((required_ratio(7224, 10, &p) - 0.6).abs() < 1e-12);
|
||
for against in 0..=4 {
|
||
let t = Tally { for_votes: 6, against };
|
||
assert_eq!(passes(t, 7224, &p), Ok(true), "6 pour, {against} contre");
|
||
}
|
||
assert_eq!(passes(Tally { for_votes: 5, against: 0 }, 7224, &p), Ok(false), "5 of the 10 counted");
|
||
assert_eq!(wot_threshold(7224, 6, &p), Ok(6));
|
||
// Beyond the quorum, the share of all who came: 6 pour and 5 contre are 11 voters.
|
||
assert_eq!(passes(Tally { for_votes: 6, against: 5 }, 7224, &p), Ok(false));
|
||
assert_eq!(passes(Tally { for_votes: 7, against: 4 }, 7224, &p), Ok(true));
|
||
}
|
||
|
||
#[test]
|
||
fn under_the_quorum_the_voters_missing_count_as_against() {
|
||
// Yvv, 2026-11-03: « Ils comptent comme non ». T' = max(T, Q).
|
||
assert_eq!(
|
||
(judged_voters(0, 12), judged_voters(9, 12), judged_voters(12, 12), judged_voters(30, 12)),
|
||
(12, 12, 12, 30)
|
||
);
|
||
let p = standard(50);
|
||
// One « pour » alone is judged on 50 voters; so are 1 « pour » and 49 « contre ».
|
||
assert_eq!(wot_threshold(5745, 1, &p), wot_threshold(5745, 50, &p));
|
||
assert!(!passes(Tally { for_votes: 1, against: 0 }, 5745, &p).expect("valid"));
|
||
}
|
||
|
||
#[test]
|
||
fn an_against_never_helps_a_version_pass() {
|
||
// Without the rule, an « against » that completes the quorum could make a text pass.
|
||
for (m, g, q, w) in [(50.0, 0.1, 12, 5707), (55.0, 0.15, 50, 5707), (66.0, 0.3, 50, 5745), (50.0, 0.6, 1, 2)] {
|
||
let p = FormulaParams { majority_pct: m, gradient: g, base: 0.1, quorum: q };
|
||
for y in 0..80 {
|
||
let mut passed = passes(Tally { for_votes: y, against: 0 }, w, &p).expect("valid");
|
||
for n in 1..80 {
|
||
let now = passes(Tally { for_votes: y, against: n }, w, &p).expect("valid");
|
||
assert!(passed || !now, "M {m} Q {q} W {w}: {y} pour, {n} contre passes, {} contre did not", n - 1);
|
||
passed = now;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
#[test]
|
||
fn one_vote_never_passes_with_a_quorum_of_two_or_more() {
|
||
// The invariant of Monnaie libre (quorum 2 at least): one voice alone never passes.
|
||
for q in [2, 12, 50] {
|
||
for g in [0.05, 0.1, 0.3, 1.77] {
|
||
let p = FormulaParams { majority_pct: 50.0, gradient: g, base: 0.1, quorum: q };
|
||
let e = evaluate(Tally { for_votes: 1, against: 0 }, 5745, &p).expect("valid");
|
||
assert!(!e.reached, "Q {q}, G {g}");
|
||
assert!(e.well_rated, "unanimous, it only lacks voters");
|
||
}
|
||
}
|
||
// Without a quorum, the trap of a permanent vote: a lone « pour » meets the inertial bar.
|
||
assert!(evaluate(Tally { for_votes: 1, against: 0 }, 5745, &standard(0)).expect("valid").reached);
|
||
}
|
||
|
||
#[test]
|
||
fn yes_needed_and_no_needed_say_where_the_line_is() {
|
||
// At level 2 on the scale of Monnaie libre (G 0.10274, W 5707), quorum 12.
|
||
let p = FormulaParams { majority_pct: 50.0, gradient: 0.102_742, base: 0.1, quorum: 12 };
|
||
let e = evaluate(Tally::default(), 5707, &p).expect("valid");
|
||
assert_eq!((e.quorum, e.required, e.yes_needed, e.missing, e.no_needed), (12, 9, 9, 9, 0));
|
||
// 9 « pour »: they pass with 3 « contre » at most; the 4th holds them.
|
||
let e = evaluate(Tally { for_votes: 9, against: 0 }, 5707, &p).expect("valid");
|
||
assert!(e.reached);
|
||
assert_eq!((e.yes_needed, e.no_needed, e.missing), (9, 4, 0));
|
||
// With a 4th « contre », 14 voters at least: 11 « pour » are asked (10,2 rounded up).
|
||
let e = evaluate(Tally { for_votes: 9, against: 4 }, 5707, &p).expect("valid");
|
||
assert!(!e.reached);
|
||
assert_eq!(e.yes_needed, 11);
|
||
assert_eq!(e.missing, 2);
|
||
// 8 « pour »: the quorum already holds them, no « contre » is needed.
|
||
let e = evaluate(Tally { for_votes: 8, against: 0 }, 5707, &p).expect("valid");
|
||
assert_eq!((e.reached, e.no_needed, e.missing), (false, 0, 1));
|
||
}
|
||
|
||
#[test]
|
||
fn passes_means_yes_needed_reached_means_no_needed_not_reached() {
|
||
// On a grid: levels (M, G as at the reference participation and beyond), quorums,
|
||
// electorates, « pour », « contre ». passes ⇔ for ≥ yesNeeded(against) ⇔ against < noNeeded(for).
|
||
let levels = [(50.0, 0.055), (50.0, 0.103), (55.0, 0.145), (60.0, 0.212), (66.0, 0.303), (66.0, 1.77)];
|
||
for (m, g) in levels {
|
||
for q in [0, 1, 2, 12, 50] {
|
||
for w in [1, 2, 16, 40, 500, 5707] {
|
||
let p = FormulaParams { majority_pct: m, gradient: g, base: 0.1, quorum: q };
|
||
let yes: Vec<u32> = (0..70).map(|n| yes_needed(n, w, &p).expect("valid")).collect();
|
||
for y in 0..70 {
|
||
let no = no_needed(y, w, &p).expect("valid");
|
||
for n in 0..70 {
|
||
let t = Tally { for_votes: y, against: n };
|
||
let ok = passes(t, w, &p).expect("valid");
|
||
assert_eq!(ok, y >= yes[n as usize], "M {m} G {g} Q {q} W {w}: {y} pour {n} contre");
|
||
assert_eq!(ok, n < no, "M {m} G {g} Q {q} W {w}: {y} pour {n} contre, noNeeded {no}");
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
#[test]
|
||
fn well_rated_holds_the_share_on_those_who_came() {
|
||
let p = standard(50);
|
||
// 30 « pour », 2 « contre »: the share holds on 32 voters, the quorum is not there.
|
||
let e = evaluate(Tally { for_votes: 30, against: 2 }, 5745, &p).expect("valid");
|
||
assert!(!e.reached && e.well_rated);
|
||
// 10 « pour », 10 « contre »: the share does not hold.
|
||
assert!(!evaluate(Tally { for_votes: 10, against: 10 }, 5745, &p).expect("valid").well_rated);
|
||
}
|
||
|
||
#[test]
|
||
fn no_vote_is_never_reached() {
|
||
for q in [0, 1, 12] {
|
||
let p = FormulaParams { quorum: q, ..g1vote(50.0, 0.1) };
|
||
let e = evaluate(Tally::default(), 3, &p).expect("valid");
|
||
assert!(!e.reached);
|
||
assert!(!e.well_rated);
|
||
}
|
||
}
|
||
|
||
#[test]
|
||
fn the_test_group_passes_with_one_voice_at_a_quorum_of_one() {
|
||
// Two people, quorum 1 (Yvv, 2026-11-02: the test group stays at 1 everywhere): one
|
||
// « pour » of one voter holds any share; a « contre » beside it does not.
|
||
let p = standard(1);
|
||
assert!(evaluate(Tally { for_votes: 1, against: 0 }, 2, &p).expect("valid").reached);
|
||
assert!(!evaluate(Tally { for_votes: 1, against: 1 }, 2, &p).expect("valid").reached);
|
||
assert!(evaluate(Tally { for_votes: 2, against: 0 }, 2, &p).expect("valid").reached);
|
||
}
|
||
|
||
#[test]
|
||
fn stale_electorate_cannot_lower_the_bar_below_majority() {
|
||
let p = g1vote(50.0, 0.2);
|
||
// 30 votes cast for an electorate believed to be 10: the reference
|
||
// would ask 12 (ratio below M), the cap keeps the simple majority:
|
||
// ⌈B^W + 0.5 × 30⌉ = 16, the tiny B^W offset rounding up.
|
||
let r = wot_threshold(10, 30, &p).expect("valid");
|
||
assert_eq!(r, 16);
|
||
}
|
||
|
||
#[test]
|
||
fn a_whole_number_of_votes_never_gains_one_by_rounding() {
|
||
// 100 members, everyone votes, M 55 %: 55 % of 100 = 55. In floating point 0.55 × 100
|
||
// is 55.000000000000007, which would ask for 56 (B^W = 10⁻¹⁰⁰ adds nothing).
|
||
let p = FormulaParams { majority_pct: 55.0, gradient: 0.3, base: 0.1, quorum: 1 };
|
||
assert_eq!(wot_threshold(100, 100, &p), Ok(55));
|
||
// A genuine fraction still rounds up.
|
||
assert_eq!(wot_threshold(101, 99, &FormulaParams { quorum: 0, ..p }), Ok(55));
|
||
}
|
||
|
||
#[test]
|
||
fn errors() {
|
||
let p = g1vote(50.0, 0.2);
|
||
assert_eq!(wot_threshold(0, 1, &p), Err(ThresholdError::EmptyElectorate));
|
||
let bad = g1vote(120.0, 0.2);
|
||
assert_eq!(wot_threshold(10, 1, &bad), Err(ThresholdError::MajorityOutOfRange));
|
||
assert_eq!(yes_needed(0, 0, &p), Err(ThresholdError::EmptyElectorate));
|
||
}
|
||
}
|