Files
FinDataHub/Scripts
FinDataHub d6c55633e1 fix(data): 统一日期列格式为 YYYYMMDD(消除范围过滤静默丢 60% 行的缺陷)
缺陷(P0-6 的格式部分)
----------------------
同一列混存 `YYYY-MM-DD` 与 `YYYYMMDD` 两种格式,按标准 YYYYMMDD 写的
等值/范围过滤会**静默丢弃**另一种格式的行。实测:
  tushare_data.stock_daily(804 万行)2026 年共 94.6 万行,
  `BETWEEN '20260101' AND '20260930'` 只命中 37.6 万行 → 丢掉 60%,且不报错。
经全库扫描(39 个日期样字符列):6 个列混存、3 个列仅带横线、共 9 个列需迁移,
合计 **44,477,754 行**。

根因
----
采集脚本把 pandas 的 datetime64 列直接交给驱动,落库被渲染成 '2026-09-02';
另一些脚本显式 strftime('%Y%m%d')。混存由此产生。

改动
----
1. 写库层护栏(findatahub_core/db.py)
   - 新增 DATE_LIKE_COLUMNS 白名单 + normalize_date_columns():
     datetime64 → 格式化为 YYYYMMDD;字符串列仅替换匹配 YYYY-MM-DD 的值,
     其余(含 YYYYMMDD、NULL、空串)原样透传;浅拷贝输出,不改写入参。
   - upsert() / replace() 写前调用。放在共享基类可一次覆盖两个 fetcher
     与清洗层,比逐个修 20+ 个采集脚本可靠(新脚本也不会再引入)。
2. 源头修正(避免只依赖护栏)
   - akshare_fetcher/scripts/sync_daily_tasks.py(日更实际执行者)
   - akshare_fetcher/scripts/backfill_daily.py、fix_stock_daily.py
   三处 rename 后补 .dt.strftime('%Y%m%d'),并把比较口径同步改为 YYYYMMDD
   (原先用 'YYYY-MM-DD' 与 datetime64 比较,格式改变后会永远匹配不到)。
3. 迁移工具(Scripts/bin/)
   - audit_date_format.py:扫描三库全部日期样列,判定混存/仅带横线/已统一
   - migrate_date_format.py:--report / --backup / --execute / --verify / --only
     分批键选 ts_code(这些表都没有自增主键,ts_code 才是相关索引最左列;
     按 trade_date 分批会在扫描中改到分片列本身,可能重复访问或漏行)

数据迁移结果(44,477,754 行,按 ts_code 分批,逐批提交)
--------------------------------------------------------
  tushare_data.stock_daily           8,046,947  ✓
  tushare_data.stock_daily_basic     7,971,103  ✓
  tushare_data.stock_moneyflow       7,739,856  ✓
  tushare_data.margin_detail         5,174,771  ✓
  akshare_data.stock_daily          16,899,091  ✓
  akshare_data.index_daily              18,427  ✓
  akshare_data.stock_fundamental         5,501 ×2 列  ✓
  akshare_data._table_meta                  26  ✓
不变量:dashed=0 且 plain+empty=total(无行丢失)—— --verify 9/9 通过。
迁移前已留基线样本与逐年直方图至
  ~/Library/Application Support/FinDataHub/backups/datefmt-*

效果(实测)
------------
  tushare_data.stock_daily 2026 年:标准范围过滤命中 951,774 / 实际 951,774(100%)
  修复前:命中 376,056 / 实际 946,221(丢 60%)
  三库等值过滤均正常(tushare 20260916=5550、akshare 20260902=5547、clean=5550)
  迁移后重跑审计:同列混存 6→0,仅带横线 3→0,仅无横线 21→30

回归
----
core 121 passed(新增 13 条,含"upsert/replace 确实挂了归一化"的集成用例与
"不得就地改写入参"的契约用例,均做过证伪)、tushare 174、akshare 183、
Scripts 207、kuaixun 407 —— 合计 1092 passed。
gate.py 与 db.py 及三个采集脚本已同步至部署位。

仍未处理(同属 P0-6 的另一半)
------------------------------
**collation 不一致**:tushare_data 63 张表 316 个列为 utf8mb4_unicode_ci,
另两库为 utf8mb4_0900_ai_ci(akshare_data 自身还有 5 个列不一致)→ 跨库 UNION/JOIN
仍报 ERROR 1271。统一需重建 66 张表,且当前**没有数据库备份**,
故未在本次执行;临时可用显式 COLLATE 规避:
  ... ON a.trade_date = b.trade_date COLLATE utf8mb4_0900_ai_ci
2026-09-18 23:34:51 +08:00
..