{"record":{"id":"2d8783f014687f78","repo":"HKUDS/Vibe-Trading","slug":"text-r-uses-a-single-comma-and-could-be-either","errorCode":null,"errorMessage":"{text!r} uses a single comma and could be either {text.replace(',', '')} (comma groups thousands) or {text.replace(',', '.')} (comma is the decimal separator); pass decimal_separator='.' or decimal_separator=',' to say which","messagePattern":"(.+?) uses a single comma and could be either (.+?) \\(comma groups thousands\\) or (.+?) \\(comma is the decimal separator\\); pass decimal_separator='\\.' or decimal_separator=',' to say which","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"agent/src/entities/ingest.py","lineNumber":168,"sourceCode":"        ValueError: If the value uses a single comma whose role cannot be\n            determined -- ``\"1,234\"`` is 1234 in a US export and 1.234 in a\n            European one, and the data cannot say which.\n    \"\"\"\n    if decimal_separator is not None:\n        grouping = \",\" if decimal_separator == \".\" else \".\"\n        return text.replace(grouping, \"\").replace(decimal_separator, \".\")\n\n    has_dot = \".\" in text\n    has_comma = \",\" in text\n    if has_dot and has_comma:\n        # The rightmost separator is the decimal one; the other groups digits.\n        if text.rindex(\".\") > text.rindex(\",\"):\n            return text.replace(\",\", \"\")\n        return text.replace(\".\", \"\").replace(\",\", \".\")\n    if has_comma:\n        if text.count(\",\") > 1:\n            return text.replace(\",\", \"\")  # 1,234,567 cannot be a decimal comma\n        raise ValueError(\n            f\"{text!r} uses a single comma and could be either \"\n            f\"{text.replace(',', '')} (comma groups thousands) or \"\n            f\"{text.replace(',', '.')} (comma is the decimal separator); pass \"\n            \"decimal_separator='.' or decimal_separator=',' to say which\"\n        )\n    return text\n\n\ndef _parse_amount(\n    raw: str,\n    path: Path,\n    row_number: int,\n    decimal_separator: str | None = None,\n) -> float:\n    \"\"\"Parse a numeric amount from a file field.\n\n    Handles currency symbols, digit grouping, and the accounting convention\n    where parentheses or a trailing minus denote a negative number, e.g.","sourceCodeStart":150,"sourceCodeEnd":186,"githubUrl":"https://github.com/HKUDS/Vibe-Trading/blob/80ffdda44c5c4db0dd84d70e051cca591cea67df/agent/src/entities/ingest.py#L150-L186","documentation":"Raised (as ValueError from _to_plain_number) when a numeric cell contains exactly one comma and no dot, making the number ambiguous: '1,234' could be 1234 (thousands grouping) or 1.234 (decimal comma). The library refuses to guess because either interpretation yields a plausible but different number; the caller must disambiguate via decimal_separator.","triggerScenarios":"load_cashflows / _parse_amount encounters a cell like '1,234' or '12,5' and decimal_separator is None. Values with multiple commas ('1,234,567') or with both separators are resolved automatically; only the single-comma/no-dot case raises.","commonSituations":"European exports with decimal commas ('1234,5' with one digit after) that look like thousands groups; US exports with a single thousands comma ('1,234'); mixed-locale CSVs loaded without configuration.","solutions":["Inspect the raw value cited in the message and determine whether the comma groups thousands or marks decimals","Pass decimal_separator='.' if commas group thousands, or decimal_separator=',' if the comma is the decimal separator","For per-file locales, load each file with its own decimal_separator rather than a global setting","If the file mixes conventions, normalize the source data first"],"exampleFix":"# before\nflows = load_cashflows('eu_export.csv')\n# after\nflows = load_cashflows('eu_export.csv', decimal_separator=',')","handlingStrategy":"validation","validationCode":"import re\n\ndef detect_ambiguous(cell: str) -> bool:\n    return cell.count(',') == 1 and '.' not in cell\n\nambiguous = [r for r in rows if detect_ambiguous(r['amount'])]\nif ambiguous:\n    raise SystemExit('ambiguous decimals; choose decimal_separator explicitly')","typeGuard":"def is_unambiguous_number(text: str) -> bool:\n    t = text.strip()\n    return not (t.count(',') == 1 and '.' not in t)","tryCatchPattern":"try:\n    load_cashflows(p)\nexcept CashFlowIngestError as e:\n    if 'could be either' in str(e):\n        load_cashflows(p, decimal_separator=decide_by_locale_of(p))","preventionTips":["Always pass decimal_separator for non-ISO exports","Standardize exports to one locale before ingestion","Log the source system's locale alongside the file path"],"tags":["csv","number-parsing","locale","ambiguity"],"backgroundTag":"locale-number-format-ambiguity","analyzedSha":"80ffdda44c5c4db0dd84d70e051cca591cea67df","analyzedAt":"2026-08-28T12:46:38.989Z","schemaVersion":2},"datasetVersion":"2026-08-28T16:17:29.566Z"}