Source code for smith_utils.numeric.refinement

[docs] def parse_numeric_value(val: str | float | None, sep: str = ",", decimal: str = ".", relaxed: bool = False) -> float | str: if val is None: return 0.0 s = str(val).strip() is_negative = False if s.startswith("-"): is_negative = True s = s[1:].strip() elif s.startswith("(") and s.endswith(")"): is_negative = True s = s[1:-1].strip() # Split by decimal to validate groups parts = s.split(decimal) if len(parts) > 2: if relaxed: return val raise ValueError(f"Invalid numeric value: '{val}'") # Validate integer part groups if 'sep' is used int_part = parts[0] if sep in int_part: groups = int_part.split(sep) # All groups except the first must be exactly 3 digits # This is a common rule for thousand separators if any(len(g) != 3 for g in groups[1:]): if relaxed: return val raise ValueError(f"Invalid numeric value: '{val}'") s_cleaned = s.replace(sep, "") if decimal != ".": s_cleaned = s_cleaned.replace(decimal, ".") try: # Check if separator appears after decimal if sep in s and decimal in s and s.rfind(sep) > s.find(decimal): raise ValueError num = float(s_cleaned) # Check if it was purely digits + decimal if not s_cleaned.replace(".", "", 1).isdigit(): raise ValueError return -num if is_negative else num except ValueError: if relaxed: return val raise ValueError(f"Invalid numeric value: '{val}'")
[docs] def parse_currency_value(val: str | float | None, sep: str = ",", decimal: str = ".", relaxed: bool = False) -> float | str: return parse_numeric_value(val, sep=sep, decimal=decimal, relaxed=relaxed)