# 端到端成功记录：数据源切换 + 代码修复验证

**时间**：2026-09-14 21:49:12 → 22:07:50（18 分 38 秒）
**结果**：`run_daily_update.cmd` **退出码 0**，M1 → M2 → 看板生成 三阶段全部成功
**runId**：`85c6f179088640ea82cd98df8353c76e`

## 授权下的代码修复（用户明确授权）

### 修改文件

`src\fundamental_mvp\inventory.py`，函数 `audit_legacy_csv`（第 53 行起）

### 修改内容

```python
def audit_legacy_csv(data_dir: Path) -> pd.DataFrame:
    records = [_csv_edge(path) for path in sorted(data_dir.glob("*.csv")) if not path.name.startswith("idx_")]
    if not records:
        # No legacy CSVs present. Return an EMPTY frame that still carries the full
        # schema: build_security_master merges this on "source_code", and a column-less
        # DataFrame raises KeyError: 'source_code'. An empty frame also makes
        # has_legacy_csv evaluate to False for every stock, which is correct.
        return pd.DataFrame({
            "source_code": pd.Series(dtype="object"),
            "legacy_csv_rows": pd.Series(dtype="int64"),
            "legacy_csv_first_date": pd.Series(dtype="object"),
            "legacy_csv_last_date": pd.Series(dtype="object"),
            "legacy_csv_header": pd.Series(dtype="object"),
            "legacy_csv_size": pd.Series(dtype="int64"),
        })
    return pd.DataFrame(records)
```

原代码 `return pd.DataFrame(records)` 在 `records == []` 时返回**零列** DataFrame，
第 145 行 `master.merge(legacy, on="source_code", how="left")` 随即 `KeyError: 'source_code'`。

选择「空表 + 完整 schema」而非「哨兵行」，语义更正确：
`has_legacy_csv` 对每只股票都为 `False`，如实反映「没有历史数据」。

### 修复前后对比

| | 修复前 | 修复后 |
| --- | --- | --- |
| `audit_legacy_csv` 返回 | `(0, 0)` 零列 | `(0, 6)` 完整 schema |
| 第 145 行 merge | `KeyError: 'source_code'` | 通过 |
| M1 结果 | `exit=1`，920.6s 后崩溃 | **`exit=0`，95.2s** |

## 验收结果（提示词第 4 节，10/10 通过）

```
state                          = success          ✅
version == version.json.version= 85c6f179…        ✅
runId 一致                      = 85c6f179…        ✅
stage / stageIndex / stageCount= 更新完成 / 3 / 3   ✅
version.json 三字段             = generatedAt/asOf/factorAsOf 均在 ✅
index.html                     = 997,539 字节      ✅
market_data_cutoff             = 2026-09-14        ✅
market_data_business_days_stale= 0                 ✅
basic_pool_eligible            = 4027              ✅
hotspot_build.candidates       = 657               ✅
price_metadata.latest_trade_date = 2026-09-14      ✅
M2 factor_rows / stocks        = 298,878 / 5,211   ✅
M2 factor_periods / oos / train= 61 / 37 / 24      ✅
M2 latest_weights              = 存在              ✅
logPath 指向存在的日志          = True              ✅
```

**关键变化**：`asOf` 由 `2026-09-11` 推进到 **`2026-09-14`**，
`market_data_business_days_stale` = **0**（正常交易日滞后应为 ≤2）。

## 公网验证

```
https://premium-manufacturing-equal-load.trycloudflare.com/reports/dashboard/version.json
→ HTTP 200, runId=85c6f179088640ea82cd98df8353c76e, asOf=2026-09-14

/reports/dashboard/index.html → HTTP 200, 997,539 字节
```

看板页面每分钟轮询 `version.json`，发现新版本会提示「发现新数据」，
点击后载入，不打断当前阅读。

## 数据源切换的最终结论

**通达信客户端方案完全可行**，无需官方静态 zip：

- 客户端「盘后数据下载」一次性生成全部 5 项依赖：
  `vipdoc\sh|sz\lday\*.day`、`vipdoc\cw\gpcw*.dat`、`T0002\hq_cache\gbbq`、
  `tdxhy.cfg`、`shs.tnf`/`szs.tnf`
- `--check` → `ready: true`
- 日线覆盖 2021-08-02 ~ 2026-09-14，5218 只股票，607 万行
- 复权链路正常：`adjustment_source = tdx_gbbq_category_1`

## 仍需注意

1. **日线历史深度只有约 5 年**（2021-08-02 起），项目原有数据可回溯到 1991/1999。
   不影响 M1（只需 250 个交易日），但 M2 因子回测的历史深度比原机器浅
   （`factor_periods=61`、`training_periods=24`）。
   若需要更长历史，可用官方 `hsjday.zip`（523.8MB，含 1991 年起全量）补种。
2. **「定时自动下载日线」是否真的在 17:00 触发尚未验证**（需等下一个交易日）。
   若未触发，改用 `deploy\tdx_download.ps1` + Windows 计划任务兜底。
3. 日更计划任务（`install_daily_task.ps1`）尚未注册。
