Sprint 1 : scaffolding complet de Glibredecision
Plateforme de decisions collectives pour Duniter/G1. Backend FastAPI async + PostgreSQL (14 tables, 8 routers, 6 services, moteur de vote avec formule d'inertie WoT/Smith/TechComm). Frontend Nuxt 4 + Nuxt UI v3 + Pinia (9 pages, 5 stores). Infrastructure Docker + Woodpecker CI + Traefik. Documentation technique et utilisateur (15 fichiers). Seed : Licence G1, Engagement Forgeron v2.0.0, 4 protocoles de vote. 30 tests unitaires (formules, mode params, vote nuance) -- tous verts. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
85
backend/app/engine/threshold.py
Normal file
85
backend/app/engine/threshold.py
Normal file
@@ -0,0 +1,85 @@
|
||||
"""WoT members threshold formula for binary votes.
|
||||
|
||||
Core formula:
|
||||
Result = C + B^W + (M + (1-M) * (1 - (T/W)^G)) * max(0, T - C)
|
||||
|
||||
Where:
|
||||
C = constant_base
|
||||
B = base_exponent
|
||||
W = wot_size (corpus of eligible voters)
|
||||
T = total_votes (for + against)
|
||||
M = majority_ratio (majority_pct / 100)
|
||||
G = gradient_exponent
|
||||
|
||||
Inertia behaviour:
|
||||
- Low participation (T << W) -> near-unanimity required
|
||||
- High participation (T -> W) -> simple majority M suffices
|
||||
|
||||
Reference test case:
|
||||
wot_size=7224, votes_for=97, votes_against=23 (total=120)
|
||||
params M50 B.1 G.2 => threshold=94, adopted (97 >= 94)
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import math
|
||||
|
||||
|
||||
def wot_threshold(
|
||||
wot_size: int,
|
||||
total_votes: int,
|
||||
majority_pct: int = 50,
|
||||
base_exponent: float = 0.1,
|
||||
gradient_exponent: float = 0.2,
|
||||
constant_base: float = 0.0,
|
||||
) -> int:
|
||||
"""Compute the minimum number of *for* votes required for adoption.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
wot_size:
|
||||
Size of the eligible voter corpus (WoT members).
|
||||
total_votes:
|
||||
Number of votes cast (for + against).
|
||||
majority_pct:
|
||||
Majority percentage (0-100). 50 = simple majority at full participation.
|
||||
base_exponent:
|
||||
B in the formula. ``B^W`` contributes a vanishingly small offset
|
||||
when W is large (0 < B < 1).
|
||||
gradient_exponent:
|
||||
G controls how fast the required super-majority decays toward M as
|
||||
participation increases.
|
||||
constant_base:
|
||||
C, a fixed additive floor on the threshold.
|
||||
|
||||
Returns
|
||||
-------
|
||||
int
|
||||
The ceiling of the computed threshold. A vote passes when
|
||||
``votes_for >= wot_threshold(...)``.
|
||||
"""
|
||||
if wot_size <= 0:
|
||||
raise ValueError("wot_size doit etre strictement positif")
|
||||
if total_votes < 0:
|
||||
raise ValueError("total_votes ne peut pas etre negatif")
|
||||
if not (0 <= majority_pct <= 100):
|
||||
raise ValueError("majority_pct doit etre entre 0 et 100")
|
||||
|
||||
C = constant_base
|
||||
B = base_exponent
|
||||
W = wot_size
|
||||
T = total_votes
|
||||
M = majority_pct / 100.0
|
||||
G = gradient_exponent
|
||||
|
||||
# Guard: if no votes, threshold is at least ceil(C + B^W)
|
||||
if T == 0:
|
||||
return math.ceil(C + B ** W)
|
||||
|
||||
# Core formula
|
||||
participation_ratio = T / W
|
||||
inertia_factor = 1.0 - participation_ratio ** G
|
||||
required_ratio = M + (1.0 - M) * inertia_factor
|
||||
result = C + B ** W + required_ratio * max(0.0, T - C)
|
||||
|
||||
return math.ceil(result)
|
||||
Reference in New Issue
Block a user