{"record":{"id":"d5da71d7d0c299ab","repo":"ZhuLinsen/daily_stock_analysis","slug":"self-name-stock-code-error-reason","errorCode":null,"errorMessage":"[{self.name}] {stock_code}: {error_reason}","messagePattern":"\\[\\{self\\.name\\}\\] \\{stock_code\\}: \\{error_reason\\}","errorType":"exception","errorClass":"DataFetchError","httpStatus":null,"severity":"error","filePath":"data_provider/base.py","lineNumber":528,"sourceCode":"            \n            # Step 4: 计算技术指标\n            df = self._calculate_indicators(df)\n\n            elapsed = time.time() - request_start\n            logger.info(\n                f\"[{self.name}] {stock_code} 获取成功: 范围={start_date} ~ {end_date}, \"\n                f\"rows={len(df)}, elapsed={elapsed:.2f}s\"\n            )\n            return df\n            \n        except Exception as e:\n            elapsed = time.time() - request_start\n            error_type, error_reason = summarize_exception(e)\n            logger.error(\n                f\"[{self.name}] {stock_code} 获取失败: 范围={start_date} ~ {end_date}, \"\n                f\"error_type={error_type}, elapsed={elapsed:.2f}s, reason={error_reason}\"\n            )\n            raise DataFetchError(f\"[{self.name}] {stock_code}: {error_reason}\") from e\n    \n    def _clean_data(self, df: pd.DataFrame) -> pd.DataFrame:\n        \"\"\"\n        数据清洗\n        \n        处理：\n        1. 确保日期列格式正确\n        2. 数值类型转换\n        3. 去除空值行\n        4. 按日期排序\n        \"\"\"\n        df = df.copy()\n        \n        # 确保日期列为 datetime 类型\n        if 'date' in df.columns:\n            df['date'] = pd.to_datetime(df['date'])\n        \n        # 数值列类型转换","sourceCodeStart":510,"sourceCodeEnd":546,"githubUrl":"https://github.com/ZhuLinsen/daily_stock_analysis/blob/5159bd72e8373d215492dff122acc9d389e219c9/data_provider/base.py#L510-L546","documentation":"The catch-all wrapper in BaseFetcher.get_daily_data: any exception escaping the pipeline (fetch, _normalize_data, _clean_data, _calculate_indicators) or re-raised from steps 1-3 is summarized via summarize_exception and re-raised as DataFetchError with the original as __cause__. The message '获取失败' with error_type/reason in the preceding log line is the diagnostic.","triggerScenarios":"Any unhandled exception inside _fetch_raw_data, _normalize_data, _clean_data, or _calculate_indicators: schema drift (missing columns), type coercion failures, indicator math errors on degenerate data (single row, all-NaN), or a lower-level DataFetchError/RateLimitError being re-wrapped.","commonSituations":"Provider renames/drops a column so normalization raises KeyError; technical indicator division by zero on flat data; network errors from the fetch step bubbling up with the elapsed-time context attached.","solutions":["Read the 'error_type=..., reason=...' in the logged '获取失败' line immediately before the raise — it names the root exception, not this wrapper.","Reproduce with a direct call to the failing step (e.g. fetcher._normalize_data(raw_df, code)) on the logged date range to isolate which pipeline stage threw.","Fix the root cause in the responsible step (column mapping, dtype coercion, indicator guard) rather than catching DataFetchError at the call site."],"exampleFix":null,"handlingStrategy":"fallback","validationCode":null,"typeGuard":null,"tryCatchPattern":"try:\n    df = manager.get_daily_data(code, ...)\nexcept DataFetchError as e:\n    logger.warning('daily fetch failed for %s: %s', code, e)\n    df = load_cached_daily(code)  # or skip / use next market source","preventionTips":["Always log the chained __cause__ — the root exception is one attribute away: e.__cause__.","Add contract tests asserting provider response schemas before normalizers run.","Keep per-step try boundaries in custom fetchers so failures carry the failing stage name."],"tags":["data-provider","wrapper","pipeline","python"],"backgroundTag":null,"analyzedSha":"5159bd72e8373d215492dff122acc9d389e219c9","analyzedAt":"2026-08-15T01:59:36.292Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}