{"record":{"id":"3875c465239b504c","repo":"HKUDS/Vibe-Trading","slug":"invalid-date-for-tushare-fallback-value-r","errorCode":null,"errorMessage":"invalid date for tushare fallback: {value!r}","messagePattern":"invalid date for tushare fallback: (.+?)","errorType":"validation","errorClass":"TushareFallbackUnavailable","httpStatus":null,"severity":"error","filePath":"agent/src/tools/tushare_fallbacks.py","lineNumber":49,"sourceCode":"\n\ndef _records(frame: Any) -> list[dict[str, Any]]:\n    if frame is None:\n        return []\n    if bool(getattr(frame, \"empty\", False)):\n        return []\n    if hasattr(frame, \"to_dict\"):\n        rows = frame.to_dict(\"records\")\n        return [row for row in rows if isinstance(row, dict)]\n    if isinstance(frame, list):\n        return [row for row in frame if isinstance(row, dict)]\n    return []\n\n\ndef _compact_date(value: str) -> str:\n    digits = str(value).strip().replace(\"-\", \"\")\n    if len(digits) != 8 or not digits.isdigit():\n        raise TushareFallbackUnavailable(f\"invalid date for tushare fallback: {value!r}\")\n    return digits\n\n\ndef _dashed_date(value: Any) -> str | None:\n    if value is None:\n        return None\n    digits = str(value).strip().replace(\"-\", \"\")\n    if len(digits) == 8 and digits.isdigit():\n        return f\"{digits[:4]}-{digits[4:6]}-{digits[6:]}\"\n    return str(value)[:10] if value else None\n\n\ndef _date_window(days: int) -> tuple[str, str]:\n    end = date.today()\n    # Market holidays/weekends mean calendar days need slack to recover the\n    # requested number of trading rows.\n    start = end - timedelta(days=max(days * 3, 10))\n    return start.strftime(\"%Y%m%d\"), end.strftime(\"%Y%m%d\")","sourceCodeStart":31,"sourceCodeEnd":67,"githubUrl":"https://github.com/HKUDS/Vibe-Trading/blob/80ffdda44c5c4db0dd84d70e051cca591cea67df/agent/src/tools/tushare_fallbacks.py#L31-L67","documentation":"_compact_date normalizes a date string for Tushare's YYYYMMDD API format; it strips dashes then requires exactly 8 digits. It raises TushareFallbackUnavailable when the value doesn't reduce to 8 digits — for example '2024/01/05' (slashes survive), '2024-1-5' (only 6 digits), or arbitrary text like 'latest'.","triggerScenarios":"fetch_dragon_tiger (and any caller of _compact_date) passing a date like '2024/06/01', '2024060', '202406011', 'today', or None coerced to the string 'None'. Only plain 'YYYY-MM-DD' or 'YYYYMMDD' forms pass.","commonSituations":"Dates sourced from user input or another API with a different format (slashes, abbreviated months); empty string defaults; a date already normalized by a different layer producing a non-8-digit result; timezone/locale-specific formatting.","solutions":["Normalize the date to 'YYYY-MM-DD' or 'YYYYMMDD' before calling fetch_dragon_tiger","Validate/parse user-supplied dates at the boundary (e.g. datetime.date.isoformat())","Default missing dates to a computed trade date instead of passing raw input through","Catch TushareFallbackUnavailable and surface a clear 'expected YYYY-MM-DD' message"],"exampleFix":"# before\nfetch_dragon_tiger(trade_date=\"2024/06/01\")\n\n# after\nfrom datetime import datetime\ndate = datetime.strptime(\"2024/06/01\", \"%Y/%m/%d\").strftime(\"%Y-%m-%d\")\nfetch_dragon_tiger(trade_date=date)","handlingStrategy":"validation","validationCode":"import re\n\ndef is_valid_trade_date(value: str) -> bool:\n    digits = str(value).strip().replace('-', '')\n    return bool(re.fullmatch(r'\\d{8}', digits))\n\nassert is_valid_trade_date(trade_date), f'bad date: {trade_date!r}'","typeGuard":"def is_iso_date(value: str) -> bool:\n    \"\"\"Type-level guard for 'YYYY-MM-DD' strings accepted by the fallback.\"\"\"\n    import re\n    return isinstance(value, str) and bool(re.fullmatch(r'\\d{4}-\\d{2}-\\d{2}|\\d{8}', value))","tryCatchPattern":"try:\n    rows = fetch_dragon_tiger(trade_date=raw)\nexcept TushareFallbackUnavailable as exc:\n    if 'invalid date' in str(exc):\n        raise ValueError(f'expected YYYY-MM-DD, got {raw!r}') from exc\n    raise","preventionTips":["Normalize dates once at the boundary: datetime.strptime(...).strftime('%Y-%m-%d')","Never pass user input straight through to date parameters","Centralize date validation in one helper reused across fetchers"],"tags":["tushare","date-format","validation","fallback"],"backgroundTag":"date-parse-failed","analyzedSha":"80ffdda44c5c4db0dd84d70e051cca591cea67df","analyzedAt":"2026-08-28T12:46:38.989Z","schemaVersion":2},"datasetVersion":"2026-08-28T16:17:29.566Z"}