# CLAUDE-data.md — 数据层 + 数据库

## DataManager (`finance/data/data_manager.py`)

双数据源：Tushare（优先）→ AkShare（fallback）。DB 缓存优先。

```python
from data.data_manager import DataManager
dm = DataManager()
dm.init_db()  # 首次建表（幂等）
```

### 关键方法

```python
stocks = dm.get_stock_list()          # → 5524 只, DB 优先
daily  = dm.get_daily("000001.SZ")    # DB优先 → Tushare → AkShare
fina   = dm.get_financial("000001.SZ")  # 同花顺 + fallback
n      = dm.sync_daily("000001.SZ")   # 增量同步: latest>=today → 跳过
```

### 指数 vs 个股路由

`_try_fetch()` 自动检测 `is_index_code()`：
- `000001.SH` / `399001.SZ` → `fetch_index_daily()`（Tushare `index_daily` / AkShare `index_zh_a_hist`）
- `000001.SZ` / `600519.SH` → `fetch_daily()`（stock daily）

### 数据源 (`finance/data/sources/`)

- `akshare_source.py` — `AkShareSource`（个股+指数+财务）
- `tushare_source.py` — `TushareSource`（需 `TUSHARE_TOKEN`，`available=False` 自动跳过）

## 数据库 (`finance/database/`)

### 连接

```python
from database.connection import get_engine, test_connection
# get_engine() 自动检测断连 → 运行 autossh.sh → 重建引擎
```

### 表结构（mac_ 前缀）

| 表 | 内容 | 主键 |
|----|------|------|
| `mac_stock_basic` | A股列表, 5524只 | ts_code |
| `mac_stock_daily` | 日线 OHLCV | (ts_code, trade_date) |
| `mac_stock_financial` | 财务指标 | (ts_code, end_date) |
| `mac_report` | 报告持久化 | id |

### DAO (`finance/database/dao.py`)

- `_DAILY_COLS` / `_FINA_COLS` — 入库前字段筛选
- `get_latest_trade_date(ts_code)` → 增量判断
- `save_report()` / `query_reports()` → 报告管理

### 配置

```bash
# SSH 隧道
bash shared/script/autossh.sh
# host: 127.0.0.1:13306  user: myquant  database: myquant
```

## 缓存策略

- `sync_daily`: 已缓存且最新 → 0.04s 跳过
- 未缓存 → 提示 `agent_cli.py warmup`
- 每日增量只更新已缓存股票，未缓存统计跳过

## ⚠️ 已知 Bug 速查（避免重复踩坑）

### B1: `.env` 加载路径
- **症状**: Tushare available=False, QWEN_API_KEY 读不到
- **根因**: `load_dotenv()` 不传路径，从 CWD 找 .env，而非 `finance/`
- **修复**: `config/settings.py` 用 `Path(__file__).parent.parent / ".env"`
- **加固**: `tushare_source.py`, `qwen_client.py`, `news_source.py` 顶部 `import config.settings`

### B2: SSH 重连只生效一次
- **症状**: 第一次断连能恢复，第二次断连无法恢复
- **根因**: `_ssh_auto_attempted` 全局变量设 True 后永不重置
- **修复**: 移除该变量。`get_engine()` 每次断连都触发 `_reconnect_ssh()` → `dispose()` → 重建

### B3: 连接池半开连接
- **症状**: `_test_engine()` 返回 True 但实际查询报 `_read_bytes` 超时
- **根因**: 池中有活连接也有死连接，test 取到活的，查询取到死的
- **修复**: `pool_pre_ping=True` + `pool_recycle=600` + 断连时 `_engine.dispose()`

### B4: save_daily 主键冲突
- **症状**: `IntegrityError: Duplicate entry '000001.SZ-20260603'`
- **根因**: Tushare 返回的数据含已存在的日期，`append` 模式遇主键冲突
- **修复**: 写入前 `DELETE FROM mac_stock_daily WHERE ts_code IN (...) AND trade_date IN (...)`

### B5: 数据不更新排查清单
- 检查 SSH 隧道: `lsof -i :13306 | grep LISTEN`
- 检查 Tushare: `python -c "from data.sources.tushare_source import TushareSource; print(TushareSource().available)"`
- 检查最新数据: `from database.dao import get_latest_trade_date; print(get_latest_trade_date('000001.SZ'))`
- 手动触发 sync: `dm.sync_daily('000001.SZ')`
- 预期行为: T+1 数据产出，节假日无新数据属于正常
