"""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