forked from yvv/decision
Lot U2 : settings.reading dit « au-delà du quorum » comme N1.1 et formula.reading (fr, en, es) ; l'anglais en un seul vocabulaire (for et against, jamais yes et no ; favour partout) ; le commentaire de threshold.rs écrit juste (la part la plus haute au quorum, puis qui baisse jusqu'à M), et un test qui le tient
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -6,9 +6,12 @@
|
||||
//! ```
|
||||
//!
|
||||
//! 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:
|
||||
//! W is the size of the electorate, T the votes cast (for + against). The share of
|
||||
//! *for* asked is judged on T' voters, never fewer than the quorum: it is at its
|
||||
//! highest at the quorum (the share of the level at T' = Q), then falls as turnout
|
||||
//! rises above it, down to the simple majority M when the whole electorate votes.
|
||||
//! Below the quorum it does not rise any further: the voters missing count as
|
||||
//! against. 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
|
||||
@@ -234,6 +237,28 @@ mod tests {
|
||||
assert_eq!(e.missing, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_share_is_highest_at_the_quorum_then_falls_to_the_majority() {
|
||||
// What the head of this module says (lot U2, review of M6c): judged on T' voters, the
|
||||
// share of *for* is at its highest at the quorum, falls as turnout rises, and is M when
|
||||
// the whole electorate votes; below the quorum it stays that of the quorum.
|
||||
let (w, q) = (5745, 12);
|
||||
let p = standard(q);
|
||||
let share = |cast: u32| required_ratio(w, judged_voters(cast, q), &p);
|
||||
for cast in 0..q {
|
||||
assert!((share(cast) - share(q)).abs() < 1e-12, "below the quorum, at {cast}");
|
||||
}
|
||||
let mut last = share(q);
|
||||
for cast in (q + 1..w).step_by(7).chain([w]) {
|
||||
let now = share(cast);
|
||||
assert!(now < last, "falls at {cast}: {now} after {last}");
|
||||
last = now;
|
||||
}
|
||||
assert!((share(w) - 0.5).abs() < 1e-12, "M at full turnout");
|
||||
// Never « near-unanimity »: at this level, 85 % of the quorum.
|
||||
assert!(share(q) > 0.8 && share(q) < 0.9, "at the quorum: {}", share(q));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parity_with_the_typescript_reference() {
|
||||
// Values computed by maquette/frontend/app/engine/threshold.ts (C = 0).
|
||||
|
||||
+16
-16
@@ -209,8 +209,8 @@ export const en: Messages = {
|
||||
'title.proposed.other': '{n} titles proposed',
|
||||
|
||||
'tally.short': '{for} for · {against} against',
|
||||
'tally.missing.one': '{n} more vote in favor needed.',
|
||||
'tally.missing.other': '{n} more votes in favor needed.',
|
||||
'tally.missing.one': '{n} more vote in favour needed.',
|
||||
'tally.missing.other': '{n} more votes in favour needed.',
|
||||
'tally.reached': 'Threshold reached.',
|
||||
'tally.level': 'Level: {pct}% of the threshold',
|
||||
'inertia.name.1': 'very low',
|
||||
@@ -242,15 +242,15 @@ export const en: Messages = {
|
||||
'vote.simulated.mark': 'simulated vote',
|
||||
'vote.others': 'Other versions',
|
||||
'vote.others.count': 'Other versions ({n})',
|
||||
'vote.need': '{yes} yes to pass · {no} no to block',
|
||||
'vote.need.quorum': '{yes} yes to pass · the quorum blocks it',
|
||||
'vote.need.first.one': '{yes} yes out of {quorum} voter to pass',
|
||||
'vote.need.first.other': '{yes} yes out of {quorum} voters to pass',
|
||||
'vote.need.short': 'passes at {yes} yes · blocked at {no} no',
|
||||
'vote.need.short.quorum': 'passes at {yes} yes',
|
||||
'vote.need.short.first': '{yes} yes out of {quorum}',
|
||||
'vote.need.title.one': 'Quorum: {n} voter. Below it, the missing voters count as “no”.',
|
||||
'vote.need.title.other': 'Quorum: {n} voters. Below it, the missing voters count as “no”.',
|
||||
'vote.need': '{yes} for to pass · {no} against to block',
|
||||
'vote.need.quorum': '{yes} for to pass · the quorum blocks it',
|
||||
'vote.need.first.one': '{yes} for out of {quorum} voter to pass',
|
||||
'vote.need.first.other': '{yes} for out of {quorum} voters to pass',
|
||||
'vote.need.short': 'passes at {yes} for · blocked at {no} against',
|
||||
'vote.need.short.quorum': 'passes at {yes} for',
|
||||
'vote.need.short.first': '{yes} for out of {quorum}',
|
||||
'vote.need.title.one': 'Quorum: {n} voter. Below it, the missing voters count as against.',
|
||||
'vote.need.title.other': 'Quorum: {n} voters. Below it, the missing voters count as against.',
|
||||
'vote.retracted': 'Vote withdrawn.',
|
||||
'vote.stale': 'The text has changed in substance since you voted: your vote no longer counts until you reconfirm it.',
|
||||
'vote.reconfirm': 'Reconfirm: {choice}',
|
||||
@@ -295,7 +295,7 @@ export const en: Messages = {
|
||||
'quorum.heading.other': 'Quorum · {n} voters',
|
||||
'quorum.default.one': 'By default, that of its level: {n} voter.',
|
||||
'quorum.default.other': 'By default, that of its level: {n} voters.',
|
||||
'quorum.explain': 'You set the quorum of this article as long as nobody has voted. Below it, the missing voters count as “no”.',
|
||||
'quorum.explain': 'You set the quorum of this article as long as nobody has voted. Below it, the missing voters count as against.',
|
||||
'quorum.field': 'Quorum of the article',
|
||||
'quorum.set': 'Set this quorum',
|
||||
'quorum.reset': 'Back to the default ({n})',
|
||||
@@ -400,7 +400,7 @@ export const en: Messages = {
|
||||
'ballots.verify.unreadable': 'This is not a readable vote receipt.',
|
||||
|
||||
'inertia.title': 'Inertia',
|
||||
'inertia.explain': 'The higher the inertia, the more votes it takes to change this text. Below the quorum, the missing voters count as “no”; beyond it, the share of “yes” asked for falls as turnout rises.',
|
||||
'inertia.explain': 'The higher the inertia, the more votes it takes to change this text. Below the quorum, the missing voters count as against; beyond it, the share of votes in favour asked for falls as turnout rises.',
|
||||
'inertia.change': 'Propose an inertia change',
|
||||
'inertia.change.to': 'New inertia',
|
||||
'inertia.change.note': "A change of inertia is itself voted at the article's inertia: {level}.",
|
||||
@@ -411,7 +411,7 @@ export const en: Messages = {
|
||||
'inertia.history.amendment': 'Went from {from} to {to} on {date}: starting setting of the text.',
|
||||
|
||||
'formula.title': 'The threshold formula',
|
||||
'formula.intro': 'The number of votes in favor required, R, depends on turnout:',
|
||||
'formula.intro': 'The number of votes in favour required, R, depends on turnout:',
|
||||
'formula.W': 'W — electorate: {n} ({source})',
|
||||
'formula.W.wot': 'members of the Ğ1 web of trust',
|
||||
'formula.W.group': 'group members',
|
||||
@@ -420,7 +420,7 @@ export const en: Messages = {
|
||||
'formula.G': 'G — gradient: {n}',
|
||||
'formula.B': 'B — base: {n}',
|
||||
'formula.Q': 'Q — quorum: {n}',
|
||||
'formula.R': 'R — current threshold: {n} votes in favor',
|
||||
'formula.R': 'R — current threshold: {n} votes in favour',
|
||||
'formula.reading': 'Below the quorum Q, the missing voters count as against. Beyond it, the share of votes in favour asked for, highest at the quorum, falls as turnout rises; when everyone votes, majority M is enough.',
|
||||
'common.pct': '{n}%',
|
||||
'settings.title': 'Formula settings',
|
||||
@@ -447,7 +447,7 @@ export const en: Messages = {
|
||||
'settings.required.version.other': "Votes for: at {n} voters, this version's turnout.",
|
||||
'settings.required.usual.one': 'Votes for: at {n} voter, as usual.',
|
||||
'settings.required.usual.other': 'Votes for: at {n} voters, as usual.',
|
||||
'settings.reading': 'At each level, the group asks for its share of votes in favour when it votes as usual; the more voters, the closer it comes to M. Below the quorum of the level, the missing voters count as against.',
|
||||
'settings.reading': 'At each level, the group asks for its share of votes in favour when it votes as usual; beyond the quorum, the more voters, the closer it comes to M. Below the quorum of the level, the missing voters count as against.',
|
||||
|
||||
'history.title': 'Corrections',
|
||||
'history.initial': 'initial text',
|
||||
|
||||
@@ -447,7 +447,7 @@ export const es: Messages = {
|
||||
'settings.required.version.other': 'Votos a favor: con {n} votantes, la participación de esta versión.',
|
||||
'settings.required.usual.one': 'Votos a favor: con {n} votante, como de costumbre.',
|
||||
'settings.required.usual.other': 'Votos a favor: con {n} votantes, como de costumbre.',
|
||||
'settings.reading': 'En cada nivel, el grupo pide su parte de votos a favor cuando vota como de costumbre; cuantos más votantes, más se acerca a M. Por debajo del quórum del nivel, los votantes que faltan cuentan como «no».',
|
||||
'settings.reading': 'En cada nivel, el grupo pide su parte de votos a favor cuando vota como de costumbre; más allá del quórum, cuantos más votantes, más se acerca a M. Por debajo del quórum del nivel, los votantes que faltan cuentan como «no».',
|
||||
|
||||
'history.title': 'Correcciones',
|
||||
'history.initial': 'texto inicial',
|
||||
|
||||
@@ -451,7 +451,7 @@ export const fr = {
|
||||
'settings.required.version.other': 'Voix pour : à {n} votants, la participation de cette version.',
|
||||
'settings.required.usual.one': "Voix pour : à {n} votant, comme d'habitude.",
|
||||
'settings.required.usual.other': "Voix pour : à {n} votants, comme d'habitude.",
|
||||
'settings.reading': "À chaque cran, le groupe demande sa part de « pour » quand il vote comme d'habitude ; plus il y a de votants, plus elle se rapproche de M. Sous le quorum du cran, les votants qui manquent comptent comme « non ».",
|
||||
'settings.reading': "À chaque cran, le groupe demande sa part de « pour » quand il vote comme d'habitude ; au-delà du quorum, plus il y a de votants, plus elle se rapproche de M. Sous le quorum du cran, les votants qui manquent comptent comme « non ».",
|
||||
|
||||
'history.title': 'Corrections',
|
||||
'history.initial': 'texte initial',
|
||||
|
||||
@@ -69,8 +69,10 @@ describe('the reading of the formula', () => {
|
||||
describe('English: one vocabulary', () => {
|
||||
it('« Pour » and « Contre » are for and against, never yes and no', () => {
|
||||
for (const [k, v] of Object.entries(en)) {
|
||||
expect(v, k).not.toMatch(/\byes\b/i);
|
||||
expect(v, k).not.toMatch(/“no”|\{no\} no\b/);
|
||||
// What is read, the placeholders ({yes}, {no}: the numbers) left out.
|
||||
const shown = v.replace(/\{\w+\}/g, '#');
|
||||
expect(shown, k).not.toMatch(/\byes\b/i);
|
||||
expect(shown, k).not.toMatch(/“no”|# no\b/);
|
||||
}
|
||||
});
|
||||
it('one spelling, the one that dominates: favour', () => {
|
||||
|
||||
Reference in New Issue
Block a user