Files
decision/backend/app/engine/nuanced_vote.py
Yvv 25437f24e3 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>
2026-02-28 12:46:11 +01:00

96 lines
2.5 KiB
Python

"""Six-level nuanced vote evaluation.
Levels:
0 - CONTRE
1 - PAS DU TOUT
2 - PAS D'ACCORD
3 - NEUTRE
4 - D'ACCORD
5 - TOUT A FAIT
Adoption rule:
The sum of votes at levels 3 + 4 + 5 must be >= threshold_pct% of total votes.
A minimum number of participants is also required.
"""
from __future__ import annotations
LEVEL_LABELS: dict[int, str] = {
0: "CONTRE",
1: "PAS DU TOUT",
2: "PAS D'ACCORD",
3: "NEUTRE",
4: "D'ACCORD",
5: "TOUT A FAIT",
}
NUM_LEVELS = 6
def evaluate_nuanced(
votes: list[int],
threshold_pct: int = 80,
min_participants: int = 59,
) -> dict:
"""Evaluate a nuanced vote from a list of individual vote levels.
Parameters
----------
votes:
List of vote levels (each 0-5). One entry per voter.
threshold_pct:
Minimum percentage of positive votes (levels 3-5) out of total
for adoption.
min_participants:
Minimum number of participants required for validity.
Returns
-------
dict
{
"total": int,
"per_level_counts": {0: int, 1: int, ..., 5: int},
"positive_count": int, # levels 3 + 4 + 5
"positive_pct": float, # 0.0 - 100.0
"threshold_met": bool,
"min_participants_met": bool,
"adopted": bool,
}
Raises
------
ValueError
If any vote value is outside the 0-5 range.
"""
# Validate vote levels
for v in votes:
if v < 0 or v > 5:
raise ValueError(
f"Niveau de vote invalide : {v}. Les niveaux valides sont 0-5."
)
total = len(votes)
per_level_counts: dict[int, int] = {level: 0 for level in range(NUM_LEVELS)}
for v in votes:
per_level_counts[v] += 1
# Positive = levels 3 (NEUTRE), 4 (D'ACCORD), 5 (TOUT A FAIT)
positive_count = per_level_counts[3] + per_level_counts[4] + per_level_counts[5]
positive_pct = (positive_count / total * 100.0) if total > 0 else 0.0
threshold_met = positive_pct >= threshold_pct
min_participants_met = total >= min_participants
adopted = threshold_met and min_participants_met
return {
"total": total,
"per_level_counts": per_level_counts,
"positive_count": positive_count,
"positive_pct": round(positive_pct, 2),
"threshold_met": threshold_met,
"min_participants_met": min_participants_met,
"adopted": adopted,
}