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>
96 lines
2.5 KiB
Python
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,
|
|
}
|