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:
Yvv
2026-11-03 11:25:36 +01:00
co-authored by Claude Opus 5.5
parent b39f7678b0
commit b0e849b9cb
5 changed files with 50 additions and 23 deletions
+28 -3
View File
@@ -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
View File
@@ -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',
+1 -1
View File
@@ -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',
+1 -1
View File
@@ -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',
+4 -2
View File
@@ -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', () => {