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:
107
backend/app/engine/mode_params.py
Normal file
107
backend/app/engine/mode_params.py
Normal file
@@ -0,0 +1,107 @@
|
||||
"""Parse mode-parameter strings into structured dicts.
|
||||
|
||||
A mode-params string encodes voting formula parameters in a compact format.
|
||||
Example: ``"D30M50B.1G.2T.1"``
|
||||
|
||||
Supported codes:
|
||||
D = duration_days (int)
|
||||
M = majority_pct (int, 0-100)
|
||||
B = base_exponent (float)
|
||||
G = gradient_exponent (float)
|
||||
C = constant_base (float)
|
||||
S = smith_exponent (float)
|
||||
T = techcomm_exponent (float)
|
||||
N = ratio_multiplier (float)
|
||||
R = ratio_mode (bool, 0 or 1)
|
||||
|
||||
Values may start with a dot for decimals < 1, e.g. ``B.1`` means base_exponent=0.1.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
|
||||
|
||||
# Ordered list of recognised codes and their target keys + types
|
||||
_CODES: dict[str, tuple[str, type]] = {
|
||||
"D": ("duration_days", int),
|
||||
"M": ("majority_pct", int),
|
||||
"B": ("base_exponent", float),
|
||||
"G": ("gradient_exponent", float),
|
||||
"C": ("constant_base", float),
|
||||
"S": ("smith_exponent", float),
|
||||
"T": ("techcomm_exponent", float),
|
||||
"N": ("ratio_multiplier", float),
|
||||
"R": ("is_ratio_mode", bool),
|
||||
}
|
||||
|
||||
# Regex: a single uppercase letter followed by a numeric value (int or float,
|
||||
# possibly starting with '.' for values like .1 meaning 0.1)
|
||||
_PARAM_RE = re.compile(r"([A-Z])(\d*\.?\d+)")
|
||||
|
||||
|
||||
def parse_mode_params(params_str: str) -> dict:
|
||||
"""Parse a mode-params string into a parameter dict.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
params_str:
|
||||
Compact parameter string, e.g. ``"D30M50B.1G.2T.1"``.
|
||||
|
||||
Returns
|
||||
-------
|
||||
dict
|
||||
Keys present depend on codes found in the string. Defaults are
|
||||
applied for any code not present::
|
||||
|
||||
{
|
||||
"duration_days": 30,
|
||||
"majority_pct": 50,
|
||||
"base_exponent": 0.1,
|
||||
"gradient_exponent": 0.2,
|
||||
"constant_base": 0.0,
|
||||
"smith_exponent": None,
|
||||
"techcomm_exponent": None,
|
||||
"ratio_multiplier": None,
|
||||
"is_ratio_mode": False,
|
||||
}
|
||||
|
||||
Raises
|
||||
------
|
||||
ValueError
|
||||
If an unrecognised code letter is found.
|
||||
"""
|
||||
defaults: dict = {
|
||||
"duration_days": 30,
|
||||
"majority_pct": 50,
|
||||
"base_exponent": 0.1,
|
||||
"gradient_exponent": 0.2,
|
||||
"constant_base": 0.0,
|
||||
"smith_exponent": None,
|
||||
"techcomm_exponent": None,
|
||||
"ratio_multiplier": None,
|
||||
"is_ratio_mode": False,
|
||||
}
|
||||
|
||||
if not params_str or not params_str.strip():
|
||||
return dict(defaults)
|
||||
|
||||
result = dict(defaults)
|
||||
|
||||
for match in _PARAM_RE.finditer(params_str):
|
||||
code = match.group(1)
|
||||
raw_value = match.group(2)
|
||||
|
||||
if code not in _CODES:
|
||||
raise ValueError(f"Code de parametre inconnu : '{code}'")
|
||||
|
||||
key, target_type = _CODES[code]
|
||||
|
||||
if target_type is int:
|
||||
result[key] = int(float(raw_value))
|
||||
elif target_type is float:
|
||||
result[key] = float(raw_value)
|
||||
elif target_type is bool:
|
||||
result[key] = float(raw_value) != 0.0
|
||||
|
||||
return result
|
||||
Reference in New Issue
Block a user