SPB Git forge

spb/uqo-chat

Public
14commits 1branches 0releases
1.4 MBsize
maindefault branch
17 days agolast push
Python 64.6% TypeScript 33.7% CSS 0.8%
2.8 KB · 82 lines python
Raw Blame History
1"""Tolerant value coercion for tool arguments (models send '185 000 $', 'Oui', '12 %', …)."""23from __future__ import annotations45import re6from typing import Any78_NUM_RE = re.compile(r"^[+-]?\d+(?:[.,]\d+)?$")9TRUE_WORDS = {"oui", "yes", "vrai", "true", "o", "y", "x", "✓"}10FALSE_WORDS = {"non", "no", "faux", "false", "n", "-", "—", ""}111213def num(value: Any, default: float | None = None) -> float | None:14    """Parse a number from int/float/str (French formats, currency, percent). None if impossible.1516    '185 000 $' → 185000 ; '12,5 %' → 0.125 ; '+3 %' → 0.03 ; '-5 000' → -5000 ; 'Oui' → None.17    """18    if value is None or isinstance(value, bool):19        return default20    if isinstance(value, (int, float)):21        return float(value)22    if not isinstance(value, str):23        return default24    s = value.strip().replace(" ", "").replace(" ", "").replace(" ", "")25    if not s or s.startswith("="):26        return default27    pct = s.endswith("%")28    s = re.sub(r"/?(?:[$€£%]|CAD|USD|pi²|pi2|m²|m2|mois|ans?\b)", "", s, flags=re.I).strip()29    s = s.replace("'", "")30    if s.count(",") == 1 and s.count(".") == 0:31        s = s.replace(",", ".")32    elif s.count(",") >= 1 and s.count(".") == 1:33        s = s.replace(",", "")34    if not _NUM_RE.match(s):35        return default36    v = float(s)37    return v / 100.0 if pct else v383940def pct(value: Any, default: float | None = None) -> float | None:41    """Percent as a ratio. 3 → 0.03 (if |v| > 1), '3 %' → 0.03, 0.03 → 0.03."""42    v = num(value, None)43    if v is None:44        return default45    if isinstance(value, str) and value.strip().endswith("%"):46        return v47    return v / 100.0 if abs(v) > 1 else v484950def boolish(value: Any) -> bool | None:51    if isinstance(value, bool):52        return value53    if isinstance(value, (int, float)):54        return bool(value)55    if isinstance(value, str):56        s = value.strip().lower()57        if s in TRUE_WORDS:58            return True59        if s in FALSE_WORDS:60            return False61    return None626364def cell(value: Any) -> Any:65    """Value safe for openpyxl: numbers parsed, formulas kept, everything else → text."""66    if value is None or isinstance(value, (int, float, bool)):67        return value68    if isinstance(value, str):69        s = value.strip()70        if s.startswith("="):71            return s72        n = num(s, None)73        stripped = re.sub(r"(pi²|pi2|m²|m2|CAD|USD|ans?\b|mois)", "", s, flags=re.I)74        if n is not None and not re.search(r"[A-Za-z]{2,}", stripped):75            return n76        return s77    if isinstance(value, dict):78        return ", ".join(f"{k}: {v}" for k, v in value.items())[:250]79    if isinstance(value, (list, tuple)):80        return ", ".join(str(v) for v in value)[:250]81    return str(value)82