{"record":{"id":"c852b61566b30e1d","repo":"ZhuLinsen/daily_stock_analysis","slug":"raw-code","errorCode":null,"errorMessage":"无法识别港股代码 {raw_code}","messagePattern":"无法识别港股代码 (.+?)","errorType":"exception","errorClass":"DataFetchError","httpStatus":null,"severity":"error","filePath":"data_provider/tushare_fetcher.py","lineNumber":459,"sourceCode":"\n    def _convert_hk_stock_code_for_tushare(self, stock_code: str) -> str:\n        \"\"\"\n        将用户输入转为 Tushare Pro 接口所需的 ts_code（含港股 nnnnn.HK）。\n\n        - 非港股：委托 _convert_stock_code（A 股 / ETF / 北交所等）。\n        - 港股：从 HK00700、00700、00700.HK 等形式归一为 5 位数字 + .HK。\n        \"\"\"\n        raw_code = stock_code.strip()\n        if _is_hk_market(raw_code):\n            if \".\" in raw_code:\n                ts_code = raw_code.upper()\n                if ts_code.endswith(\".SS\"):\n                    return f\"{ts_code[:-3]}.SH\"\n                if ts_code.endswith(\".HK\"):\n                    return ts_code\n            digits = re.sub(r\"\\D\", \"\", raw_code)\n            if not digits:\n                raise DataFetchError(f\"无法识别港股代码 {raw_code}\")\n            code = digits[-5:].rjust(5, \"0\")\n            return f\"{code}.HK\"\n        return self._convert_stock_code(stock_code)\n\n    @retry(\n        stop=stop_after_attempt(3),\n        wait=wait_exponential(multiplier=1, min=2, max=30),\n        retry=retry_if_exception_type((ConnectionError, TimeoutError)),\n        before_sleep=before_sleep_log(logger, logging.WARNING),\n    )\n    def _fetch_raw_data(self, stock_code: str, start_date: str, end_date: str) -> pd.DataFrame:\n        \"\"\"\n        从 Tushare 获取原始数据\n        \n        根据代码类型选择不同接口：\n        - 普通股票：daily()\n        - ETF 基金：fund_daily()\n        ","sourceCodeStart":441,"sourceCodeEnd":477,"githubUrl":"https://github.com/ZhuLinsen/daily_stock_analysis/blob/5159bd72e8373d215492dff122acc9d389e219c9/data_provider/tushare_fetcher.py#L441-L477","documentation":"DataFetchError raised in the HK branch of TushareFetcher code normalization when an HK-market code contains no digits after stripping non-digit characters (re.sub(r'\\D', '', raw_code) is empty). The fetcher needs a numeric HK code to build the 5-digit .HK ts_code, so codes like 'hk', '.HK', or 'hk.XY' are rejected.","triggerScenarios":"Passing a code matched by _is_hk_market that has no digits at all, e.g. 'hk', 'HK.', '.hk', or codes whose only content is a market prefix/suffix.","commonSituations":"Upstream parsing splitting 'hk00700' into prefix and empty body; watchlist typos; programmatic construction of codes producing prefix-only strings; malformed user input in bot commands.","solutions":["Fix the upstream code construction so HK codes always carry the numeric part (e.g. '00700', 'hk00700', '00700.HK').","Validate user/watchlist input with a regex like ^\\D*(\\d{1,5})\\D*$ before it reaches the fetcher.","Reject or quarantine malformed codes at ingestion instead of letting them hit the provider layer."],"exampleFix":"# before\nraw = \"hk.\"                       # matched as HK market, no digits -> DataFetchError\ncode = tushare_fetcher._convert_stock_code(raw)\n\n# after\nimport re\nm = re.search(r\"(\\d{1,6})\", raw)\nif m:\n    code = m.group(1)[-5:].rjust(5, \"0\") + \".HK\"   # safe to pass on\nelse:\n    raise ValueError(f\"invalid HK code: {raw!r}\")  # fail at ingestion with clear input error","handlingStrategy":"validation","validationCode":"import re\n\ndef valid_hk_code(raw: str) -> bool:\n    \"\"\"HK codes must contain at least one digit to be normalizable.\"\"\"\n    return bool(re.search(r\"\\d\", raw))\n\nif _is_hk_market(raw) and not valid_hk_code(raw):\n    raise ValueError(f\"malformed HK code: {raw!r}\")  # reject at ingestion","typeGuard":"import re\n\ndef has_hk_digits(code: str) -> bool:\n    \"\"\"True when an HK-market code carries a numeric part.\"\"\"\n    return bool(re.search(r\"\\d\", code))","tryCatchPattern":"try:\n    ts_code = tushare_fetcher._convert_stock_code(raw)\nexcept DataFetchError as e:\n    if \"无法识别港股代码\" in str(e):\n        raise ValueError(f\"bad HK code from upstream: {raw!r}\") from e  # surface as input error\n    raise","preventionTips":["Validate watchlist/bot input codes with a digit-containing regex before provider calls.","Fix upstream code parsing that can emit prefix-only strings like 'hk.' or '.HK'.","Reject malformed codes at ingestion with a clear message instead of deep provider errors."],"tags":["tushare","hk-stocks","code-normalization","validation"],"backgroundTag":null,"analyzedSha":"5159bd72e8373d215492dff122acc9d389e219c9","analyzedAt":"2026-08-15T01:59:36.292Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}