Files
xiaobai-review/xiaobai-datahub
011ecc0d1a feat(HEL-546): 数据中枢 A/B/C 三方向落地为可用页面
- 基于第七版稳定基线(be647bb)重构 admin 前端为模块化架构:
  tokens.css(设计token唯一来源) + shared.css(公共组件) + core.js(状态/API/事件总线/轮询/路由)
  + main.js(入口) + layouts/{flowline,ledger,strata}.{css,js}(三方向独立布局)
  + layouts/shared.js(三方向共用数据视图与操作绑定)
- 三个页面(A装配线/B值班台账/C地层剖面)通过 ?layout= 查询参数独立可达、可刷新、可前进后退导航,
  共享登录态、真实后端数据(overview/sources/jobs/batches/datasets/audit)、错误处理与主题
- 六大板块(总览/数据源/调度任务/盘后发布/数据集/审计)在三个方向均可查看与操作(探测/触发/回滚/补数)
- 修正健康状态语义:CircuitBreaker closed/half_open/open 与适配器 ok/error/empty/unconfigured/unknown
  统一归一化为 ok/warn/error/unconfigured/unknown 供三方向一致展示
- 事件驱动动效:A 水平接力光梭、B 行级高亮+实时调用跑马灯、C 纵向贯穿光点+分层标记;
  统一使用 transform/opacity/WAAPI,避免 transition:all,支持 prefers-reduced-motion 静态降级
- 修复响应式布局在 1024/1280 断点因 CSS Grid 默认 min-width:auto 被内部宽表格撑爆轨道的问题
- 修复布局切换/前进后退时残留 setTimeout 回调在 unmount 后访问 null root 导致的报错(mounted 标志位+
  safeTimeout+统一清理定时器)
- 自测:Playwright 全断点(1440/1280/1024)x 双主题矩阵截图、90 次连续布局切换+前进后退压力测试无报错、
  探测/触发/回滚danger操作流程验证、reduced-motion 验证;后端 pytest 全量 133 用例通过,无回归
- 未改动:登录/鉴权契约、后端 API、'问天'冻结区、生产数据/权限

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: multica-agent <github@multica.ai>
2026-09-13 16:28:50 +08:00
..

xiaobai-datahub

独立行情数据中枢(HEL-382 / P0)。与 xiaobai-review 同仓库、不同容器、不共享数据库文件。 本阶段不部署现网;只提供可本地运行、可自测的底座和盘后正式数据链路。

做什么

  • SQLite WAL datahub.db,容器名 xiaobai-datahub,端口 8766
  • Tushare 盘后正式数据:交易日历、股票主档、daily、daily_basic、adj_factor、index_daily、moneyflow、stk_auction、limit_list_d、ths_hot/dc_hot、hm_detail、ths_daily/dc_index/sw_daily
  • 盘中观察(provisional):东财/腾讯指数报价、个股最新价、全市场快照、分时点(/v1/quotes/latest 不传 codes 即全市场,/v1/indexes/quotes /v1/intraday/points);永不写入 eod_* 正式表
  • 暂存 → 校验 → 整批原子发布 → 可回滚
  • /v1 稳定接口(X-Datahub-Token
  • /admin/ 最小管理后台(总览 / 数据源 / 调度 / 发布 / 数据集 / 审计)
  • 同花顺/选股宝/AKShare/iFinD 适配器位仍预留;东财/腾讯已接入盘中观察

单位口径(相对现站)

现站 xiaobai-review 按 Tushare 原始单位入库、展示时再换算。中枢在归一化层一次换算:

字段 Tushare / 现站 中枢 canonical
daily.amount / index_daily.amount 千元 元(×1000
daily.vol / index_daily.vol 股(×100
moneyflow.*_amount 万元 元(×1e4
daily_basic.total_mv / circ_mv 万元 元(×1e4
stk_auction.amount

差异为口径升级,golden 测试按上表对照,不为 0 的字段都有说明。

本地启动(不走 Docker

cd xiaobai-datahub
python -m venv .venv && .venv/bin/pip install -r requirements.txt
cp .env.example .env
# 填入 DATAHUB_ENCRYPTION_KEY / DATAHUB_TOKEN / DATAHUB_ADMIN_PASSWORD / TUSHARE_TOKEN
# 生成 Fernet 密钥:
# python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"
.venv/bin/python server.py --host 127.0.0.1 --port 8766

Docker(独立 compose,不改现网 review 服务)

cd xiaobai-datahub
cp .env.example .env   # 填密钥
mkdir -p data
docker compose build
docker compose up -d

仓库根目录另有 compose.datahub.yaml,供总工以后与现有 compose.yaml 叠加部署,本卡不执行现网 up

自测

cd xiaobai-datahub
python -m unittest discover -s tests -v

不调用真实 Tushare;用内存/临时库和假适配器。

历史回补

交易日历默认从 20160101 拉到今天后 30 天;盘前 precheck 与手动回补都走同一 UPSERT,可重复执行。

网站实际使用的指数(上证、深成、创业板、沪深300)按交易日增量发布,默认覆盖 260 个交易日(大于现有 90 天窗口,并覆盖智能选股基准回看)。已发布日期默认跳过。

cd xiaobai-datahub
python -m datahub history-backfill
# 可选:--calendar-start 20160101 --index-days 260 --force

管理后台也可手动跑 history_backfill 任务,或 POST /admin/api/backfilldataset=history、确认词 history:full

区间接口在 meta.coverage / meta.incomplete 标明覆盖是否完整;网站只读接入把不完整区间视为不可用并回旧链路。个股日 K 的 90 天区间查询依赖已核实,本阶段不回补全市场历史。

估值字段级质量门

hub-quality.config.jsonfield_gates 按数据集配置关键字段:非空率下限(支持按字段覆盖,如 dv_ttm 合法高空值)、非有限值比例上限、以及相对上一已发布批次的非空率塌陷保护。字段大面积为空的批次会被拒绝发布、保留上一份正常正式数据,失败原因逐字段写入 batches.error / quality_json。被拒后数据集仍视为缺失,盘后自动重试(HEL-435 机制)会继续尝试直到成功或截止。配置对任意数据集生效,不写死单日或单字段。

整批原子发布(release group

盘后发布/重发(eod_a、eod_retry、eod-refresh、跨数据集重发)不再逐数据集各自切换,而是走整批原子可见机制:

  • 一致性边界:日 K、估值、资金流、竞价同属 A 组整批;指数日 K 为 B 组;当日股票主档快照随 A 组一同切换(主档 stock_master 的 UPSERT 与快照发布同一事务,不会出现主档先行/滞后)。
  • 流程:组内全部成员先在暂存表完成拉取、字段质量门、覆盖检查和跨数据集交叉校验(cross_gates 配置 ts_code 覆盖重叠率下限),全部达标后才在一个 SQLite 事务里复制正式表并翻转全部 publications 指针。
  • 任一成员失败(拉取失败、质量门拒绝、交叉校验不过、切换事务中断)→ 整批不切换,对外继续提供上一份完整正式版本,失败原因写入 batches.erroraudit_logaction=release-group),等待晚间自动重试。
  • 读取侧任何时刻只会看到"旧完整版本"或"新完整版本":发布指针在单事务内统一翻转,容器重启/事务中断自动回滚,不暴露字段残缺或跨数据集混合版本。
  • 幂等:仅当一致性边界内全部成员都已发布时才整组跳过;边界内任有缺失则整组重暂存后统一切换,避免旧批次与新批次混在同一次重发中。重复执行、并发重试不会在完整边界已就绪时生成重复批次(调度器另有 EOD 互斥锁)。

股票主档每日刷新与发布

交易日 20:00 与 23:10stocks_refresh_times 可配)自动刷新股票主档并发布版本化快照(eod_stocks + publications.dataset='stocks'),覆盖当日新上市、证券简称变化和上市首日 N/C 前缀摘除;无变化则跳过,重复执行幂等。/v1/stocks 从最新已发布快照提供数据并带 batch_id / published_at/v1/datasets/status 同步展示 stocks 状态。

cd xiaobai-datahub
python -m datahub stocks-refresh            # 手动触发;--force 无变化也重发

资金流历史回补

网站会沿真实调用链查最近若干交易日的 moneyflow(个股详情任意日期点查 + 智能选股最近 5 个交易日),默认回补最近 60 个交易日(moneyflow_history_trading_days 可配,已发布日期自动跳过)。点查未覆盖的历史日期返回 DATASET_NOT_PUBLISHED 并附 available_from / available_to(低于下界时 reason=history_not_backfilled),网站据此明确回退旧链路,不会静默拿到半截数据。

cd xiaobai-datahub
python -m datahub moneyflow-backfill        # --trading-days 60 --end-date --force 可选

盘后补跑与强制重发

cd xiaobai-datahub
python -m datahub eod-refresh --trade-date 20260904                  # 补不完整的 A/B 边界
python -m datahub eod-refresh --trade-date 20260904 --force --dataset valuation
# --force 按一致性边界整组重发:valuation/daily/moneyflow/auction/stocks → A 组;
# index_daily → B 组。不可再单独切换某一个正式数据集。

管理后台「补数」对盘后正式数据集同样走 force_republish_boundary,不会绕过 A/B 整批边界。

估值发布后复核与自动追补

Tushare daily_basic 会在盘后继续改当日字段。HEL-423 在 2026-09-07 观察到:中枢 17:10 发布 003021.SZ turnover_rate=1.356521:05 上游/旧链路已是 1.3572;其余 7 类观察对象当日一致。日 K、资金流、竞价、指数没有同类晚间修订证据,股票主档已有 20:00/23:10 刷新,因此默认只复核估值,不盲目全量重拉。

窗口(可配):交易日 20:0023:20,每 30 分钟一次轻量比对(对齐网站 21:00 / 23:30 观察)。只拉取 daily_basic,按网站真实请求字段精确比较,无误差豁免。

  • 无变化:不产生新批次,状态「已追平」。
  • 发现修订:重新走字段质量门、覆盖检查和 A 组整批原子发布;读者全程只能看到上一完整版本或新完整版本。
  • 上游空 / 接口失败 / 不完整 / 质量门拒绝:保留上一完整版本,状态「复核失败」。
  • 23:20 截止后停止当晚复核;下一自然日盘前对上一交易日再做一次安全追赶。
  • eod_a / eod_retry 共用互斥锁;容器重启会在窗口内立即补一次。

备份

每日 00:40 任务把 datahub.db 备份到 data/backups/(保留 14 份)。也可手动:

python -c "from pathlib import Path; from datahub.db import HubDB; HubDB(Path('data/datahub.db')).backup_to(Path('data/backups/manual.db'))"

安全

  • 密钥只以 configured / 末4位 / 更新时间 出现在后台,不进日志、不进 /v1
  • HTTP 解析失败只记录“请求不是合法 JSON”,不把请求正文、密码或 Token 写入容器日志
  • 回滚、补数需重新输入密码 + 确认词
  • 容器非 rootuid 10002)、read_only、cap_drop ALL