forked from yvv/decision
Lot S, phase 2 : le quorum dans le moteur (ADR 0013), vu rouge puis vert
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>
This commit is contained in:
@@ -19,7 +19,8 @@
|
||||
//! - what the document would read if the well-rated, or all, proposals passed
|
||||
//! (`preview`);
|
||||
//! - which formula a proposal is judged by (`params_for`): the level of
|
||||
//! inertia of its unit, on the scale of its group (`inertia::Scale`).
|
||||
//! inertia of its unit and its quorum, on the scale of its document
|
||||
//! (`inertia::Scale`).
|
||||
|
||||
use crate::inertia::{InertiaLevel, Scale};
|
||||
use crate::threshold::{FormulaParams, Tally, ThresholdError, evaluate};
|
||||
@@ -40,6 +41,10 @@ pub struct UnitState {
|
||||
pub id: String,
|
||||
pub position: f64,
|
||||
pub inertia: InertiaLevel,
|
||||
/// Its own quorum, set by the author of its first version before the first vote
|
||||
/// (ADR 0013); `None`: the quorum of its level.
|
||||
#[serde(default)]
|
||||
pub quorum: Option<u32>,
|
||||
pub in_force: Option<VersionText>,
|
||||
}
|
||||
|
||||
@@ -84,8 +89,11 @@ pub fn voting_level(unit: &UnitState, kind: ProposalKind) -> InertiaLevel {
|
||||
}
|
||||
}
|
||||
|
||||
/// The formula a proposal is judged by: the level it is voted at, and the quorum of its
|
||||
/// unit — its own, or that level's (a change of inertia inherits both).
|
||||
pub fn params_for(unit: &UnitState, kind: ProposalKind, scale: &Scale) -> FormulaParams {
|
||||
scale.params(voting_level(unit, kind))
|
||||
let level = voting_level(unit, kind);
|
||||
scale.params(level, scale.quorum(level, unit.quorum))
|
||||
}
|
||||
|
||||
/// A proposal that changes nothing cannot enter into force.
|
||||
@@ -264,6 +272,7 @@ mod tests {
|
||||
id: id.into(),
|
||||
position,
|
||||
inertia: InertiaLevel::DEFAULT,
|
||||
quorum: None,
|
||||
in_force: in_force.map(|b| VersionText {
|
||||
id: format!("{id}-v"),
|
||||
title: id.into(),
|
||||
@@ -286,8 +295,9 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
/// Monnaie libre before any median: W 5745, C 50, the reference p0 = 2C/W.
|
||||
const SCALE: Scale = Scale { electorate: 5745, floor: 50.0, participation: 100.0 / 5745.0 };
|
||||
/// Monnaie libre before any median: W 5745, the reference p0 = 2 × 50 / W, the quorums
|
||||
/// 12 · 12 · 50 · 50 · 50 (ADR 0013).
|
||||
const SCALE: Scale = Scale { electorate: 5745, participation: 100.0 / 5745.0, quorums: [12, 12, 50, 50, 50] };
|
||||
|
||||
fn level(k: u8) -> InertiaLevel {
|
||||
InertiaLevel::new(k).expect("1 to 5")
|
||||
@@ -301,12 +311,18 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_floor_holds_then_releases() {
|
||||
fn the_quorum_holds_then_releases() {
|
||||
// Level 3, quorum 50: under it the voters missing count as against, 39 « pour » pass.
|
||||
let units = [unit("E3", 7.0, None)];
|
||||
let few = [prop("p1", "E3", ProposalKind::Text, "connaître", 49, 0)];
|
||||
let few = [prop("p1", "E3", ProposalKind::Text, "connaître", 38, 0)];
|
||||
assert!(adoptions(&units, &few, &SCALE).expect("valid").is_empty());
|
||||
let enough = [prop("p1", "E3", ProposalKind::Text, "connaître", 50, 0)];
|
||||
let enough = [prop("p1", "E3", ProposalKind::Text, "connaître", 39, 0)];
|
||||
assert_eq!(adoptions(&units, &enough, &SCALE).expect("valid"), vec!["p1".to_string()]);
|
||||
// Its own quorum, lower, set by its author: the same curve on fewer voters.
|
||||
let mut own = unit("E3", 7.0, None);
|
||||
own.quorum = Some(12);
|
||||
let lower = [prop("p1", "E3", ProposalKind::Text, "connaître", 11, 0)];
|
||||
assert_eq!(adoptions(&[own], &lower, &SCALE).expect("valid"), vec!["p1".to_string()]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -339,7 +355,10 @@ mod tests {
|
||||
assert_eq!(voting_level(&u, kind), level(4), "inherits the level of the article");
|
||||
assert_eq!(params_for(&u, kind, &SCALE), params_for(&u, ProposalKind::Text, &SCALE));
|
||||
}
|
||||
assert_eq!(params_for(&u, ProposalKind::Text, &SCALE).majority_pct, 60.0);
|
||||
let p = params_for(&u, ProposalKind::Text, &SCALE);
|
||||
assert_eq!((p.majority_pct, p.quorum), (60.0, 50), "the level, and so its quorum");
|
||||
u.quorum = Some(20);
|
||||
assert_eq!(params_for(&u, ProposalKind::Inertia(level(1)), &SCALE).quorum, 20, "and the article's own");
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -358,7 +377,7 @@ mod tests {
|
||||
// The seed a minima has no vote: the first rival that reaches its threshold wins.
|
||||
let rival = [prop("r", "E1", ProposalKind::Text, "unicité reformulée", 50, 0)];
|
||||
assert_eq!(adoptions(&units, &rival, &SCALE).expect("valid"), vec!["r".to_string()]);
|
||||
// A version in force by vote, margin 60 − 58 = 2: a rival at 55 (margin 55 − 54 = 1) waits.
|
||||
// A version in force by vote, margin 60 − 47 = 13: a rival at 55 (margin 55 − 43 = 12) waits.
|
||||
if let Some(v) = units[0].in_force.as_mut() {
|
||||
v.tally = Tally { for_votes: 60, against: 0 };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user