---
title: 当前数据拉取任务总览
doc_type: business-inventory
domain: data-sync
keywords:
  - 数据拉取任务
  - 定时任务
  - provider
  - 同步任务
  - 补偿
summary: 盘点桌面端当前会主动访问外部数据源并把结果写回本地的任务、入口和补偿通道。
when_to_read:
  - 想确认系统现在到底会拉哪些外部数据
  - 想区分正式拉数任务、本地后处理任务和补偿入口
read_next:
  - ./notice-market-pipeline.md
  - ../operations/startup-sync-self-check.md
---
# 当前数据拉取任务总览

这篇文档只说明一件事：当前桌面端到底有哪些任务或入口会主动去拉外部数据，以及失败或漏跑后靠什么补齐。

这份清单只统计“会主动访问 provider / sidecar 并把结果写回本地”的任务，以及这些同步任务当前正式登记的补偿入口。

调度时间还有一个必须先记住的边界：

- 下面表格里的时间说的是仓库默认种子口径，不是某个用户当前机器上的绝对真值。
- 运行时真正会执行什么时间，始终以本地 SQLite 里的 `sys_scheduled_task.cron_expression` 为准；任务调度页允许用户修改大多数 cron。
- 后台 scheduler 启动后会每 60 秒扫描一次启用任务；如果某条任务相对 `last_run_at` 已经错过下一次 cron，它会在下一次扫描时立即补跑，而不是等到下一个整点才恢复。

明确不算在这份清单里的内容：

- 纯本地后处理任务，例如 `notice.t_date.backfill`、`notice.filter`、`notice.ai.analysis`、`notice.indicator.calc`
- 纯清理或维护任务，例如 `data.cleanup`
- 只读本地数据库的查询接口

如果你更关心公告同步后的过滤和分析编排，先看 [公告与市场任务链路](./notice-market-pipeline.md)。如果你更关心自动缺口巡查逻辑，直接看 [自动缺口巡查](../operations/startup-sync-self-check.md)。

## 一眼看完

```mermaid
flowchart LR
    A["startup.sync.self_check\n自动缺口巡查"] --> B["tushare.trade_calendar.sync"]
    A --> C["stock_basic_daily_basic / stk_factor_pro / limit_list_d / suspend_d\n最近 120 个开市日缺口补齐"]
    A --> D["cninfo.notice.sync\n最近 30 个自然日窗口补齐"]
    E["scheduler 定时任务"] --> B
    E --> F["cninfo.notice.type.sync"]
    E --> D
    E --> C
    E --> G["today.watch.quote.refresh"]
    E --> H["indicator.calc"]
```

## 1. 正式后台数据拉取任务

| 任务键 | 数据源 | 拉取内容 | 正式落点 | 默认触发 |
| --- | --- | --- | --- | --- |
| `tushare.trade_calendar.sync` | Tushare | A / H / U 交易日历 | `stock_trade_calendar`、`sync_status` | 每天 `06:05` |
| `tushare.stock_basic_daily_basic.sync` | Tushare `stock_basic` + `daily_basic` | 按最新已收盘交易日单日同步 A 股股票主数据和 `daily_basic` 市值标签 | `stock_basic_info`、`api_tushare_daily_basic`、`sync_status` | 每天 `08:00` 和 `16:00` |
| `cninfo.notice.sync` | CNInfo | 同步窗口内公告 raw 全量快照，以及通过业务准入的公告投影 | `api_cninfo_notice_raw`、`biz_notice_fact`、`biz_notice_analysis`、`sync_status` | 每天 `09:00` 和 `15:00` |
| `today.watch.quote.refresh` | Aktools sidecar / AkShare 口径，必要时回退同日日线快照 | 今日看盘股票池的当日行情快照 | `today_watch_quote_snapshot` | 工作日盘中每分钟 |
| `tushare.stk_factor_pro.sync` | Tushare `stk_factor_pro` | 按最新已收盘交易日单日同步 A 股正式日线事实，并同时保存 A 股正式技术指标原生字段 | `api_tushare_stk_factor_pro`、`sync_status` | 工作日 `19:00` |
| `tushare.limit_list_d.sync` | Tushare `limit_list_d` | 按最新已收盘交易日单日同步涨停事件 | `tushare_limit_list`、`sync_status` | 工作日 `19:10` |
| `tushare.suspend_d.sync` | Tushare `suspend_d` | 按最新已收盘交易日单日同步停牌事件 | `tushare_suspend_d`、`sync_status` | 工作日 `19:15` |
| `indicator.calc` | 当前有效日线 provider + Aktools sidecar | 观察池股票的主数据、provider 自持历史行情、实时快照，然后顺手重算指标 | `stock_basic_info`、`api_*` 行情表、`stock_market_snapshot`、`stock_indicator_*` | 工作日 `19:30` |
| `cninfo.notice.type.sync` | CNInfo | 官方公告类型码表 | `api_cninfo_notice_type_map`、`sync_status` | 每天 `16:05` |

补充说明：

- `cninfo.notice.sync` 走的是完整 `notice_pipeline`，也就是“raw 公告同步 -> 业务公告投影 -> 交易日历校验 -> `t_date` 回填 -> `notice.filter`”；但真正会访问外部 provider 的只有最前面的公告抓取这一段。
- `cninfo.notice.sync` 默认按 `last_cursor + 1 -> 今日` 推进；如果上一次成功游标已经是今天，则本轮会只重扫今天，保证同一天内的新公告还能被补进来，而不会再固定回扫最近 7 天。
- `cninfo.notice.sync` 现在会直接读取 `sys_config.desktop.provider.cninfo.*` 的正式运行配置；其中地址已接入 Rust HTTP client，若当前设备显式关闭 `cninfo.enabled`，整条公告同步 pipeline 会直接跳过。历史 `desktop-market-config.json` 只在 SQLite 仍是默认值时参与一次性导入。
- `cninfo.notice.sync` 在 `cninfo.enabled=true` 时，要求 `cninfo.apiUrl`、`cninfo.username`、`cninfo.password` 同时就绪；缺任一字段时，`run_notice_sync_pipeline`、`startup.sync.self_check` 和 `cninfo-sync` CLI 都会直接 fail fast，不再静默跳过或回退到旧字段。
- 从旧 Java 后端迁移 CNInfo 配置时，不能把 `sys_config.cninfo.api_base_url=https://webapi.cninfo.com.cn` 直接覆盖当前桌面端 `cninfo.apiUrl`；当前桌面公告抓取仍然使用 `https://www.cninfo.com.cn/new/hisAnnouncement/query` 页面端地址。若要保留旧后端凭据，应把 `cninfo.client_id / cninfo.client_secret` 迁入当前设置页凭据字段，同时继续保留桌面端 `apiUrl=https://www.cninfo.com.cn`。
- `desktop_market_config` 现在把 `akshare / tushare / ifind / cninfo` 正式配置落到 SQLite `sys_config`；这些 `apiUrl` 不再由代码自动 seed 默认地址，地址留空就表示“当前未配置”，相关抓取链路会直接 fail fast，而不是回退到仓库内置 host。
- `today.watch.quote.refresh`、`refresh_spot_quotes` 和分时行情查询现在都要求 `akshare.apiUrl` 已在设置页明确保存；仅保留“启用 AkShare”而不填地址，不再自动指向本机 `127.0.0.1:8080`。
- Tushare 正式链路现在要求 `tushare.apiUrl + token` 同时就绪；只配 token 不配地址，也会在运行前被视为未配置。
- `api_cninfo_notice_raw` 现在承接同步窗口内的全量 raw 公告账本；`biz_notice_fact / biz_notice_analysis` 只承接通过业务准入的公告，不再默认与 raw 表保持一一对应。
- `indicator.calc` 虽然任务名看起来像“纯计算”，实际会先调用 `run_scheduled_sync()`，给观察池股票补主数据、历史数据和实时快照，再做指标计算。
- A 股技术指标正式真值现在只读 `api_tushare_stk_factor_pro` 的原生前复权指标，并叠加 `stock_indicator_ema` 的自定义 `EMA13 / EMA34 / EMA120`；`stock_tech_indicator_daily` 不再参与 A 股详情 / 行情正式查询。
- `stock_indicator_ema` 继续承接桌面端自定义 `EMA13 / EMA34 / EMA120`，并在统一指标读模型里覆盖同名字段。
- `api_tushare_daily` 已降级为退役对象，不再作为 A 股正式补缺目标，也不再参与正式 provider daily 查询口径。
- `stock_price_daily_standard / stock_price_daily` 已降级为 legacy 迁移债务；正式消费链改为“provider 自持表 + 统一查询层 + 显式 `source / sourceProvider`”。
- Rust 运行时不再把这条正式链路命名为 `standard daily` 或 `provider daily`；涉及 `api_tushare_stk_factor_pro` 时，统一明确写成“`stk_factor_pro` 单日快照”或“`api_tushare_stk_factor_pro` 单日数据集”。
- `today.watch.quote.refresh` 与 `refresh_spot_quotes` 不是同一个落点：前者写 `today_watch_quote_snapshot`，后者写通用的 `stock_market_snapshot`。
- `today.watch.quote.refresh` 优先尝试 live quote；如果 live provider 不可用，但本地已经具备同交易日完整正式日线，则允许写入 `source_provider=local:*` 的同日本地快照。
- `tushare.stk_factor_pro.sync` 成功后会立即补刷一次 `today.watch.quote.refresh`，确保盘中先写入的同日本地快照不会长期停留在半成品字段状态。
- 如果应用在某个 cron 时间点不在线，`scheduler` 恢复运行后会按“是否已经错过下一次调度”补跑一次；所以实际观察到的执行时间可能晚于文档里的默认时间点。

## 2. 自动补偿入口

`startup.sync.self_check` 不是普通 cron 定时任务，但它是当前最重要的自动缺口巡查入口。

当前口径：

- 启动时先执行 `startup.sync.self_check`，再启动 scheduler。
- 如果启动首轮失败，后台会只在当前进程内按 `1 / 3 / 10` 分钟退避最多再试 3 次；任一成功即停止。
- 进入稳定运行后，后台会每 `6` 小时自动巡查一次缺口。
- 行情链统一覆盖最近 `120` 个开市日，公告链统一覆盖最近 `30` 个自然日。
- 它复用现有正式同步能力，不额外发明新的拉数逻辑。
- 它仍出现在任务中心作为运行时管理任务，但不再提供手动补数入口，也不能被改成普通周期任务。

它当前可能触发的拉数动作如下：

| 启动入口 | 数据源 | 会补什么 | 落点 |
| --- | --- | --- | --- |
| `startup.sync.self_check -> sync_trade_calendar()` | Tushare | `A / H / U` 历史交易日历整窗补齐 | `stock_trade_calendar`、`sync_status` |
| `startup.sync.self_check -> sync_tushare_stock_basic_daily_basic_snapshot_for_trade_date()` | Tushare `stock_basic` + `daily_basic` | 在最近 `120` 个开市日窗口内补齐缺失的 `stock_basic + daily_basic` 单日快照 | `stock_basic_info`、`api_tushare_daily_basic`、`sync_status` |
| `startup.sync.self_check -> sync_tushare_stk_factor_pro_snapshot_for_trade_date()` | Tushare `stk_factor_pro` | 在最近 `120` 个开市日窗口内补齐缺失的 `stk_factor_pro` 单日快照；补齐后同轮重建对应窗口的 `stock_indicator_ema` | `api_tushare_stk_factor_pro`、`stock_indicator_ema`、`sync_status` |
| `startup.sync.self_check -> sync_tushare_limit_list_d_snapshot_for_trade_date()` | Tushare `limit_list_d` | 在最近 `120` 个开市日窗口内补齐缺失的 `limit_list_d` 单日快照 | `tushare_limit_list`、`sync_status` |
| `startup.sync.self_check -> sync_tushare_suspend_d_snapshot_for_trade_date()` | Tushare `suspend_d` | 在最近 `120` 个开市日窗口内补齐缺失的 `suspend_d` 单日快照 | `tushare_suspend_d`、`sync_status` |
| `startup.sync.self_check -> run_notice_sync_pipeline_for_window()` | CNInfo | 在最近 `30` 个自然日窗口内补齐公告；若游标已追平当前自然日，则同日重扫当前自然日 | `biz_notice_fact`、`biz_notice_analysis`、`api_cninfo_notice_raw`、`sync_status` |

当前已知边界：

- 它不会默认触发 `notice.ai.analysis`。
- 它会在“只补了市场底座、没有补公告窗口”时，额外重跑一次 `notice.filter`，但这一步不算新的拉数动作。
- 它会对自身依赖的布尔配置和已成功同步的 CNInfo 游标做 fail-fast 校验，避免坏配置或坏游标被静默吞掉。
- 它与常规 `cninfo.notice.sync` 现在共用同一套游标窗口规则：`last_cursor + 1 -> 目标日`，游标追平目标日时单日重扫，失败时保留旧游标而不回退到 bootstrap。
- 它虽然会出现在任务中心，但仍不登记为普通 cron 调度任务；控制面只提供运行可观测性，不提供手动触发或周期编辑能力。
- 它现在是唯一的自动历史缺口修复入口；桌面运行时不再保留每 5 分钟检查一次最近交易日的后台自动补偿循环，启动期恢复也只限当前进程这一个退避窗口。
- `2026-03-29` 起，启动补偿与正式 cron 统一复用 `trade_calendar_utils::resolve_latest_completed_trade_date_for_datetime()` 解析目标交易日，A / HK / US 的收盘时段正式改为读取 `sys_config` 的 `market.trading.hours.a / hk / us`，初始化默认值分别是 `15:00 / 16:00 / 16:00`，并继续保持“只有严格晚于收盘才推进”的边界。
- `market.trading.hours.*` 现在是公告 `trade_date` 与“最新已收盘交易日”推导的单一配置源；如果缺失或格式非法，`notice_sync`、`notice_ai`、`scheduler`、`startup.sync.self_check` 和依赖同一 helper 的消费链会直接 fail fast，不再回退到代码内硬编码时段。

## 3. 手动拉取 / 补数入口

当前正式产品口径已不再提供数据补录的手动入口。

| 入口 | 当前状态 | 说明 |
| --- | --- | --- |
| 任务调度页 / 前端 `runTaskNow(taskKey)` | 只保留非数据同步类任务 | 数据同步类任务已从手动执行口径移除 |
| 普通设置页同步入口 / 前端 `startHistorySync()` | 已从桌面产品契约移除 | 历史行情补齐改为自动缺口巡查 |
| Tauri `sync_stock_data()` / `sync_trade_calendar()` / `refresh_spot_quotes()` | 已从桌面产品契约移除 | 数据补录改由自动缺口巡查与正式 cron 负责 |
| CLI `task-runner --task-key ...` | 不再作为桌面产品能力的一部分 | 仅保留为内部工具，且数据同步类任务不再走手动执行口径 |

补充说明：

- `2026-04-03` 起，普通页面查询与前端受控动作不再触发任何补录或远端刷新；详情、行情、交易日历与快照查询统一只读本地正式结果。
- `2026-04-04` 起，普通用户前端和 CLI 不再保留数据补录的手动恢复路径；数据只在自动缺口巡查和正式调度任务里拉取。

## 4. 当前补偿与恢复口径

当前同步链路的补偿不是单一机制，而是按场景拆开：

| 场景 | 真实机制 | 当前覆盖 | 明确不是什么 |
| --- | --- | --- | --- |
| 常规定时任务漏跑或普通失败 | `scheduler -> run_task_once()` 在下一个 cron 窗口自然重跑 | 所有登记在 `sys_scheduled_task` 的正式任务 | 不是按错误类型排队的细粒度重试队列 |
| 最近窗口内的历史缺口 | `startup.sync.self_check` | 交易日历、A 股主数据 / 日线 / 涨停 / 停牌最近 `120` 个开市日窗口、CNInfo 最近 `30` 个自然日窗口，以及必要时的 `notice.filter` 重跑；若启动首轮失败，当前进程内再按 `1 / 3 / 10` 分钟退避限次重试，之后每 `30` 分钟自动巡查一次 | 不补盘中实时行情，也不扫描未登记的数据域 |
| 公告底座稍后才补齐 | `notice.filter` 的 `09:20 / 15:20 / 19:20` 定时重跑，或自动缺口巡查补底座后的即时重跑 | 把之前因其他缺口或历史 `SKIPPED` 停住的公告继续推进；`2026-03-29` 起，涨停 / 停牌底座缺口本身不再生成新的 `SKIPPED` | 不是新的 provider 拉数 |
| 运维修复或定向补数 | 已下线为正式能力 | 正式产品不再提供数据补录的人工恢复路径 | 不是后台自动恢复 |

当前状态留痕也要单独看：

- `sync_status` 负责记录 `last_success_at`、`last_attempt_at`、`last_error` 和 `retry_count`，它是同步状态账本，不是失败任务队列。
- 当前正式口径是：如果某条同步任务曾经成功，后续失败只更新 `last_attempt_at / last_error / retry_count`，不会抹掉已有 `last_success_at` 或已有游标；这样“已有底座但最近一次刷新失败”和“从未成功”不会再被混成同一种状态。
- 但 `sync_status` 已不再单独等于“可消费”：`stock_basic + daily_basic` 单日快照与 `stk_factor_pro` 单日快照还必须看到对应交易日的真实业务数据，`limit_list_d / suspend_d` 单日快照则要求“成功标记或事件表已有该交易日行”至少命中其一，三者共同构成底座 ready 真值。
- `cninfo.notice.sync` 失败时会保留上一次成功游标 `last_cursor` 并累加 `retry_count`，避免一次失败把下一轮补偿误判成 bootstrap。
- 其他行情 / 码表同步任务失败时，也统一沿用同一套账本语义，通过 `sync_status` 和 `sync_task_log` 留痕，等待下一轮定时、启动补数或手动入口来恢复；当前没有面向这些同步任务的统一 `sys_failed_operation` 自动重试队列。

## 5. 原后端统计 / 重试评估结论

这一轮也把“要不要吸收原后端增量同步统计和重试思路”定成了正式边界。

原后端确实有两类更重的能力：

- `OutboxDispatchService` / `ChannelRetryConsumer`：围绕 outbox 事件分发的失败重试、指数退避和 dead-letter。
- `OutboxObservabilityService` / `OutboxMetricsSnapshotTask`：围绕事件 backlog、失败率和 P95 延迟的快照指标。

当前桌面端结论是不继续迁入这套 outbox 控制面，原因只有两个：

- 当前同步任务还是单机 scheduler 直接调用 provider，不存在单独的事件总线、多消费者分发或跨进程 worker。
- 当前真正需要保证的是“缺口能补、失败可见、自动可恢复”，这已经由 `sync_status`、`sync_task_log`、`startup.sync.self_check` 和正式单日 cron 共同覆盖。

换句话说：

- 桌面端保留“账本式同步状态 + 自动缺口巡查 + 正式单日 cron”的恢复模型。
- 桌面端当前不额外引入 `ChannelRetry` 事件、outbox backlog 指标快照或 dead-letter 队列；自动恢复继续收口在“自动缺口巡查 + 正式单日 cron”两类入口里。
- 只有在后续同步任务真的演进成“多消费者 / 多进程 / 事件分发”模型时，才重新评估这套原后端 outbox 统计和重试设计。

## 6. 已登记但当前不算正式拉取的任务

当前 `sys_scheduled_task` 里还登记了一个看起来像拉数、但实际上还没接入正式 provider 的任务：

| 任务键 | 当前状态 | 原因 |
| --- | --- | --- |
| `hkex.notice.sync` | 已登记但默认不算正式拉取任务 | 配置开关默认关闭；即使打开，当前 handler 也只会返回“adapter not enabled in current MVP” |

这意味着：

- 现在不能把 `hkex.notice.sync` 计入“当前已经可用的数据拉取任务”。
- 如果后续要正式接入，需要同时更新这篇文档、调度说明和功能测试文档。

## 7. 不属于数据拉取任务的本地后处理任务

下面这些任务经常和拉数链路一起出现，但它们当前不直接访问外部 provider：

- `notice.t_date.backfill`
- `notice.filter`
- `notice.ai.analysis`
- `notice.indicator.calc`
- `data.cleanup`

如果后续有人要排查“为什么任务跑了却没有新拉数”，先确认自己看到的是不是这几类本地后处理任务。
