From dd04933d6359b6fd742c5b720dfc88b9f6d3998d Mon Sep 17 00:00:00 2001 From: yuxuanhui Date: Tue, 11 Aug 2026 13:32:21 +0800 Subject: [PATCH] =?UTF-8?q?chore(task):=20=E5=BD=92=E6=A1=A3=2008-10=20?= =?UTF-8?q?=E4=B8=8E=2008-11=20=E4=BB=BB=E5=8A=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../check.jsonl | 0 .../design.md | 0 .../implement.jsonl | 0 .../implement.md | 0 .../prd.md | 0 .../task.json | 4 +- .../check.jsonl | 6 + .../design.md | 160 ++++++++++++++++++ .../implement.jsonl | 6 + .../implement.md | 39 +++++ .../prd.md | 84 +++++++++ .../legacy-sync-and-current-bottlenecks.md | 56 ++++++ .../task.json | 30 ++++ .../check.jsonl | 5 + .../design.md | 37 ++++ .../implement.jsonl | 5 + .../implement.md | 13 ++ .../08-11-market-integrity-check-api/prd.md | 38 +++++ .../task.json | 26 +++ .../check.jsonl | 5 + .../design.md | 26 +++ .../implement.jsonl | 5 + .../implement.md | 10 ++ .../08-11-market-integrity-check-web/prd.md | 35 ++++ .../task.json | 26 +++ .../check.jsonl | 5 + .../design.md | 43 +++++ .../implement.jsonl | 5 + .../implement.md | 14 ++ .../prd.md | 40 +++++ .../task.json | 26 +++ .trellis/workspace/yuxuanhui/index.md | 7 +- .trellis/workspace/yuxuanhui/journal-1.md | 37 ++++ 33 files changed, 788 insertions(+), 5 deletions(-) rename .trellis/tasks/{ => archive/2026-08}/08-10-selection-results-filter-state-ui/check.jsonl (100%) rename .trellis/tasks/{ => archive/2026-08}/08-10-selection-results-filter-state-ui/design.md (100%) rename .trellis/tasks/{ => archive/2026-08}/08-10-selection-results-filter-state-ui/implement.jsonl (100%) rename .trellis/tasks/{ => archive/2026-08}/08-10-selection-results-filter-state-ui/implement.md (100%) rename .trellis/tasks/{ => archive/2026-08}/08-10-selection-results-filter-state-ui/prd.md (100%) rename .trellis/tasks/{ => archive/2026-08}/08-10-selection-results-filter-state-ui/task.json (90%) create mode 100644 .trellis/tasks/archive/2026-08/08-11-market-data-sync-performance-audit/check.jsonl create mode 100644 .trellis/tasks/archive/2026-08/08-11-market-data-sync-performance-audit/design.md create mode 100644 .trellis/tasks/archive/2026-08/08-11-market-data-sync-performance-audit/implement.jsonl create mode 100644 .trellis/tasks/archive/2026-08/08-11-market-data-sync-performance-audit/implement.md create mode 100644 .trellis/tasks/archive/2026-08/08-11-market-data-sync-performance-audit/prd.md create mode 100644 .trellis/tasks/archive/2026-08/08-11-market-data-sync-performance-audit/research/legacy-sync-and-current-bottlenecks.md create mode 100644 .trellis/tasks/archive/2026-08/08-11-market-data-sync-performance-audit/task.json create mode 100644 .trellis/tasks/archive/2026-08/08-11-market-integrity-check-api/check.jsonl create mode 100644 .trellis/tasks/archive/2026-08/08-11-market-integrity-check-api/design.md create mode 100644 .trellis/tasks/archive/2026-08/08-11-market-integrity-check-api/implement.jsonl create mode 100644 .trellis/tasks/archive/2026-08/08-11-market-integrity-check-api/implement.md create mode 100644 .trellis/tasks/archive/2026-08/08-11-market-integrity-check-api/prd.md create mode 100644 .trellis/tasks/archive/2026-08/08-11-market-integrity-check-api/task.json create mode 100644 .trellis/tasks/archive/2026-08/08-11-market-integrity-check-web/check.jsonl create mode 100644 .trellis/tasks/archive/2026-08/08-11-market-integrity-check-web/design.md create mode 100644 .trellis/tasks/archive/2026-08/08-11-market-integrity-check-web/implement.jsonl create mode 100644 .trellis/tasks/archive/2026-08/08-11-market-integrity-check-web/implement.md create mode 100644 .trellis/tasks/archive/2026-08/08-11-market-integrity-check-web/prd.md create mode 100644 .trellis/tasks/archive/2026-08/08-11-market-integrity-check-web/task.json create mode 100644 .trellis/tasks/archive/2026-08/08-11-market-sync-concurrency-storage/check.jsonl create mode 100644 .trellis/tasks/archive/2026-08/08-11-market-sync-concurrency-storage/design.md create mode 100644 .trellis/tasks/archive/2026-08/08-11-market-sync-concurrency-storage/implement.jsonl create mode 100644 .trellis/tasks/archive/2026-08/08-11-market-sync-concurrency-storage/implement.md create mode 100644 .trellis/tasks/archive/2026-08/08-11-market-sync-concurrency-storage/prd.md create mode 100644 .trellis/tasks/archive/2026-08/08-11-market-sync-concurrency-storage/task.json diff --git a/.trellis/tasks/08-10-selection-results-filter-state-ui/check.jsonl b/.trellis/tasks/archive/2026-08/08-10-selection-results-filter-state-ui/check.jsonl similarity index 100% rename from .trellis/tasks/08-10-selection-results-filter-state-ui/check.jsonl rename to .trellis/tasks/archive/2026-08/08-10-selection-results-filter-state-ui/check.jsonl diff --git a/.trellis/tasks/08-10-selection-results-filter-state-ui/design.md b/.trellis/tasks/archive/2026-08/08-10-selection-results-filter-state-ui/design.md similarity index 100% rename from .trellis/tasks/08-10-selection-results-filter-state-ui/design.md rename to .trellis/tasks/archive/2026-08/08-10-selection-results-filter-state-ui/design.md diff --git a/.trellis/tasks/08-10-selection-results-filter-state-ui/implement.jsonl b/.trellis/tasks/archive/2026-08/08-10-selection-results-filter-state-ui/implement.jsonl similarity index 100% rename from .trellis/tasks/08-10-selection-results-filter-state-ui/implement.jsonl rename to .trellis/tasks/archive/2026-08/08-10-selection-results-filter-state-ui/implement.jsonl diff --git a/.trellis/tasks/08-10-selection-results-filter-state-ui/implement.md b/.trellis/tasks/archive/2026-08/08-10-selection-results-filter-state-ui/implement.md similarity index 100% rename from .trellis/tasks/08-10-selection-results-filter-state-ui/implement.md rename to .trellis/tasks/archive/2026-08/08-10-selection-results-filter-state-ui/implement.md diff --git a/.trellis/tasks/08-10-selection-results-filter-state-ui/prd.md b/.trellis/tasks/archive/2026-08/08-10-selection-results-filter-state-ui/prd.md similarity index 100% rename from .trellis/tasks/08-10-selection-results-filter-state-ui/prd.md rename to .trellis/tasks/archive/2026-08/08-10-selection-results-filter-state-ui/prd.md diff --git a/.trellis/tasks/08-10-selection-results-filter-state-ui/task.json b/.trellis/tasks/archive/2026-08/08-10-selection-results-filter-state-ui/task.json similarity index 90% rename from .trellis/tasks/08-10-selection-results-filter-state-ui/task.json rename to .trellis/tasks/archive/2026-08/08-10-selection-results-filter-state-ui/task.json index e35ad83..8e87a16 100644 --- a/.trellis/tasks/08-10-selection-results-filter-state-ui/task.json +++ b/.trellis/tasks/archive/2026-08/08-10-selection-results-filter-state-ui/task.json @@ -3,7 +3,7 @@ "name": "selection-results-filter-state-ui", "title": "迭代选股结果页筛选与状态入口", "description": "", - "status": "in_progress", + "status": "completed", "dev_type": null, "scope": null, "package": null, @@ -11,7 +11,7 @@ "creator": "yuxuanhui", "assignee": "yuxuanhui", "createdAt": "2026-08-10", - "completedAt": null, + "completedAt": "2026-08-11", "branch": null, "base_branch": "main", "worktree_path": null, diff --git a/.trellis/tasks/archive/2026-08/08-11-market-data-sync-performance-audit/check.jsonl b/.trellis/tasks/archive/2026-08/08-11-market-data-sync-performance-audit/check.jsonl new file mode 100644 index 0000000..530f2d3 --- /dev/null +++ b/.trellis/tasks/archive/2026-08/08-11-market-data-sync-performance-audit/check.jsonl @@ -0,0 +1,6 @@ +{"file":".trellis/spec/backend/market-data-sync.md","reason":"检查并发、完整性审计和现有同步契约的集成一致性。"} +{"file":".trellis/spec/backend/quality-guidelines.md","reason":"检查后端 lint、类型、测试、迁移和集成证据。"} +{"file":".trellis/spec/frontend/quality-guidelines.md","reason":"检查前端格式、lint、类型、测试和构建证据。"} +{"file":"docs/adr/0003-postgresql-as-market-data-store.md","reason":"检查完整性报告没有改变 PostgreSQL 事实源地位。"} +{"file":"docs/adr/0004-tushare-six-year-snapshot-sync.md","reason":"检查并发仍保持事务/CSV 发布、失败隔离和滚动边界。"} +{"file":".trellis/tasks/08-11-market-data-sync-performance-audit/prd.md","reason":"执行父任务最终 acceptance criteria 对照。"} diff --git a/.trellis/tasks/archive/2026-08/08-11-market-data-sync-performance-audit/design.md b/.trellis/tasks/archive/2026-08/08-11-market-data-sync-performance-audit/design.md new file mode 100644 index 0000000..ff44a85 --- /dev/null +++ b/.trellis/tasks/archive/2026-08/08-11-market-data-sync-performance-audit/design.md @@ -0,0 +1,160 @@ +# 市场数据同步性能与完整性检查设计 + +## 1. 目标与边界 + +本设计保留现有“逐股获取六年 qfq 完整快照”的业务口径,通过 8 路受控并发和 PostgreSQL +连接/写入批量化缩短日常同步。另提供不访问 Tushare、不修改市场事实的手动完整性检查。 + +不引入按交易日增量下载、自动完整检查、自动修复或通用任务队列。选股仍只消费已完成且 +覆盖率达标的市场同步批次。 + +## 2. 任务拆分与依赖 + +父任务只负责需求、跨子任务契约和最终集成,不直接实现产品代码。 + +1. `08-11-market-sync-concurrency-storage` + - 交付 8 worker、共享频控、连接池、批量 item 写入和集合覆盖率。 + - 无子任务依赖,必须最先完成。 +2. `08-11-market-integrity-check-api` + - 依赖第 1 项提供的池化 PostgreSQL 基础设施和 advisory lock 语义。 + - 交付检查领域模型、迁移、只读比较、HTTP 触发和轮询。 +3. `08-11-market-integrity-check-web` + - 依赖第 2 项冻结 HTTP 字段和状态值。 + - 交付 `/sync` 页面、导航、触发、轮询和报告展示。 + +## 3. 同步执行模块 + +### 3.1 外部接口 + +`SyncMarketData.execute(command) -> SyncBatchSummary` 保持不变。并发数、请求协调器和仓储 +由组合根注入,CLI 调用方不需要了解线程、频控或连接池细节。 + +### 3.2 单股 worker + +把当前会修改共享 `failures` / `totals` 的 `_process_bar` 改为返回不可变 +`SyncItemOutcome`:股票代码、状态、`WriteResult`、fingerprint 和可选安全错误。 + +每个 worker 内仍严格执行: + +1. 获取该股票六年 qfq; +2. 读取旧正式 CSV 并比较重叠指纹; +3. 写临时 CSV; +4. 在独立 PostgreSQL 事务中幂等 upsert; +5. 数据库提交后原子发布 CSV; +6. 返回 outcome。 + +主线程通过 `as_completed` 聚合 outcome、更新进度和每 100 项批量写入同步审计。单股异常只 +生成失败 outcome;未发布的临时文件必须清理。结果顺序不影响计数或最终状态。 + +### 3.3 请求协调器 + +请求协调器是 Tushare 适配器内部的深模块,接口只暴露“执行一个真实供应商调用”。实现隐藏: + +- 正常请求保持最多 8 路并发,不用一个全局固定间隔把真实调用重新串行化; +- 保留现有可配置的单股请求间隔作为温和节流; +- “访问频繁/请稍后/超过频率/too many requests/429/403”等频控分类; +- 频控时共享 `cooldown_until`,默认按 60、120、180 秒增长并设置上限; +- 非频控临时错误沿用有界重试和普通退避; +- 注入 monotonic clock / wait 实现确定性单元测试; +- 日志只包含方法名、尝试次数和等待时间,不包含 token 或供应商原始响应。 + +为保留 Tushare qfq 计算,继续调用 `ts.pro_bar(api=coordinated_client, adj="qfq", ...)`。 +`coordinated_client.daily` 与 `coordinated_client.adj_factor` 在原始异常仍可见的位置经过请求 +协调器;`pro_bar` 内部重试降为一次,避免隐藏重试绕开全局频控。 + +### 3.4 PostgreSQL + +- 增加 `psycopg_pool.ConnectionPool` 依赖,池最大连接数与 worker 数匹配并留出控制连接。 +- CLI 用上下文管理器打开/关闭池;HTTP 进程使用按数据库 URL 缓存的池并在进程退出时关闭。 +- worker 的 bar upsert 从池中借一个连接并保持单股票事务,不在线程间共享 connection。 +- 主线程调用 `record_items(batch_id, outcomes)` 分批 upsert `market_sync_item`。 +- 仓储新增 `count_valid_stocks(target_trade_date)`,用一条集合 SQL 计算 active 股票中同时存在 + bar 和 daily_basic 的数量;删除同步编排对逐股 `has_*` 的依赖。 +- advisory lock 的连接在整个批次期间保持借出,不和 worker 连接混用。 + +## 4. 完整性检查模块 + +### 4.1 语义 + +“完整性检查”验证 PostgreSQL 事实与已发布 CSV 快照是否一致,并验证 CSV 可以按现有领域规则 +解析。它不判断 Tushare 是否已发布某日数据,也不能发现 PostgreSQL 和 CSV 同时缺少但供应商 +实际存在的数据。 + +检查范围: + +- 当前 active 股票主数据与 `stock-basic/current.csv`; +- 最近成功同步窗口内的每股 qfq bar 与 `bars/.csv`; +- 同一窗口内 PostgreSQL daily_basic 与 `daily-basic//.csv`; +- 缺失、多余、无法解析、重复键、字段内容不一致和窗口越界。 + +### 4.2 独立持久化模型 + +新增表而不复用 `market_sync_batch`: + +- `market_integrity_check` + - `id`, `status`, `window_start`, `window_end` + - `target_count`, `checked_count`, `issue_count` + - `error_type`, `error_message`, `created_at`, `finished_at` +- `market_integrity_issue` + - `check_id`, `item_kind`, `item_key`, `issue_type`, `message`, `created_at` + - 复合主键包含可稳定区分同一对象多个问题的序号或 issue key。 + +状态固定为:`running`、`passed`、`issues_found`、`failed`。`issues_found` 表示检查完整执行但数据 +存在问题,不等于检查任务执行失败。 + +### 4.3 只读比较 + +`RunMarketIntegrityCheck` 是外部应用接口: + +- `prepare() -> IntegrityCheckRun` 原子创建 running 记录;已有 running 时返回冲突。 +- `execute(check_id) -> None` 获取与同步相同的 advisory lock,执行比较并收敛终态。 +- `get(check_id, page, page_size)` 和 `get_latest(...)` 提供轮询读模型。 + +PostgreSQL adapter 使用有序 server-side cursor,按股票或交易日流式产生一组记录;CSV adapter +按同样 key 顺序读取。应用模块做 merge comparison,一次只保留一个股票或一个交易日的数据, +不把约 700 万行全量装入内存。 + +检查持有与同步相同的 advisory lock,保证 DB 与正式 CSV 在比较期间不会被同步修改。它只向 +检查表写进度和问题;市场事实表和正式 CSV 不发生写入。 + +### 4.4 HTTP + +路由归属 market_data presentation,并由 `/api/v1/market-data` 挂载: + +- `POST /integrity-checks` → `202 Accepted`,返回 check id、status、窗口; +- `GET /integrity-checks/latest` → 最近一次检查或 `no_data`; +- `GET /integrity-checks/{check_id}?page=&page_size=` → 进度、终态和分页问题。 + +HTTP 使用仓库已有的 FastAPI `BackgroundTasks` 模式。后台任务出现未捕获异常时必须把检查记录 +收敛为 `failed`。进程重启可能中断 in-process task;下一次触发允许显式把失去 advisory lock +且超过超时阈值的 running 记录标记为 failed,避免永久阻塞。该恢复只修改检查元数据。 + +## 5. Web 模块 + +启用现有“同步任务”导航并新增 `/sync` 路由,使用独立 `features/sync` 垂直切片: + +- API 类型与函数; +- 最新检查 query、单次检查 polling query、触发 mutation; +- 页面展示检查说明、报告只读提示、最近窗口、进度、状态和分页问题; +- running 时禁用重复触发并按固定间隔轮询;终态停止轮询并刷新 latest; +- `409` 显示已有检查正在运行,`503` 显示存储不可用,其他错误保留可重试入口。 + +页面不把服务器状态复制到 Zustand。问题明细最少显示对象类型、key、问题类型和安全说明。 + +## 6. 兼容、部署与回滚 + +- 新迁移为 `0003_market_integrity_checks`,只新增检查表/索引,不修改事实表。 +- 配置默认 worker 从当前未生效的 4 调整为 8;新增频控退避配置时同步 `.env.example`、 + Compose 和市场数据运维文档。 +- CLI 参数和退出码保持兼容;旧批次和旧 CSV 无需迁移。 +- 回滚应用版本时新增检查表可暂留;数据库 downgrade 只删除检查表,不触碰市场事实。 +- 若并发上线后供应商频控持续恶化,可通过环境变量把 worker 降为 1,无需回滚代码。 + +## 7. 验证策略 + +- 确定性并发测试证明 active worker 不超过配置值,结果汇总不依赖完成顺序。 +- fake clock/condition 测试证明频控触发共享冷却,普通错误不冻结其他 worker。 +- PostgreSQL 集成测试验证池化并发 upsert、批量 item、集合覆盖率和事务回滚。 +- 完整性检查 fixture 覆盖 passed、缺 CSV、缺 DB、内容不一致、非法 CSV、检查冲突和批次异常。 +- HTTP 测试锁定 202、409、分页与状态契约;Web 测试锁定触发、轮询停止、报告只读提示和错误态。 +- 无网络 5002 股票调度基准锁定线程与仓储调用数量;生产约 30 分钟目标以部署日志验收。 diff --git a/.trellis/tasks/archive/2026-08/08-11-market-data-sync-performance-audit/implement.jsonl b/.trellis/tasks/archive/2026-08/08-11-market-data-sync-performance-audit/implement.jsonl new file mode 100644 index 0000000..c0a207a --- /dev/null +++ b/.trellis/tasks/archive/2026-08/08-11-market-data-sync-performance-audit/implement.jsonl @@ -0,0 +1,6 @@ +{"file":".trellis/spec/backend/market-data-sync.md","reason":"父任务集成时保持 qfq、事务/CSV 顺序、覆盖率和 CLI 契约。"} +{"file":".trellis/spec/backend/http-api-contracts.md","reason":"集成完整性检查 HTTP 与 Web 同源契约。"} +{"file":".trellis/spec/frontend/hook-guidelines.md","reason":"集成页面触发、轮询和终态停止行为。"} +{"file":"docs/adr/0004-tushare-six-year-snapshot-sync.md","reason":"检查三个子任务没有绕过六年快照和失败隔离决策。"} +{"file":".trellis/tasks/08-11-market-data-sync-performance-audit/research/legacy-sync-and-current-bottlenecks.md","reason":"提供生产基线、旧项目证据与已确认技术选择。"} +{"file":".trellis/tasks/08-11-market-data-sync-performance-audit/design.md","reason":"提供任务依赖、跨层接口、状态和回滚设计。"} diff --git a/.trellis/tasks/archive/2026-08/08-11-market-data-sync-performance-audit/implement.md b/.trellis/tasks/archive/2026-08/08-11-market-data-sync-performance-audit/implement.md new file mode 100644 index 0000000..5beefe4 --- /dev/null +++ b/.trellis/tasks/archive/2026-08/08-11-market-data-sync-performance-audit/implement.md @@ -0,0 +1,39 @@ +# 父任务执行计划 + +父任务不直接修改产品代码;按以下顺序启动、检查并集成子任务。 + +## 1. 子任务顺序 + +1. 完成 `08-11-market-sync-concurrency-storage`。 + - 先锁定并发、频控、数据库池和批量接口。 + - 完成后运行后端全量质量门禁和无网络基准。 +2. 完成 `08-11-market-integrity-check-api`。 + - 以前一子任务的连接池与 advisory lock 契约为依赖。 + - 完成迁移、只读检查、后台任务和 HTTP 契约后运行后端全量门禁。 +3. 完成 `08-11-market-integrity-check-web`。 + - 只消费已冻结的 HTTP 模型和状态值。 + - 完成导航、路由、触发/轮询和问题展示后运行前端全量门禁。 + +## 2. 集成检查 + +- 检查三个子任务的字段、状态和路径完全一致。 +- 模拟同步持锁时触发检查,以及检查持锁时启动同步,确认不会并发修改/读取快照。 +- 确认检查批次不影响首页最近同步、selection 数据源选择或 CLI 退出码。 +- 确认运行检查时不会构造或调用 Tushare adapter。 +- 运行迁移 upgrade/downgrade(测试数据库可用时)并核对 schema metadata。 +- 运行根目录 `./dev.sh check` 与 `./dev.sh test`。 +- 检查四种 Compose config。 + +## 3. 生产验收与回退 + +- 首次部署先执行迁移,再用少量股票/测试环境验证 8 worker 和共享频控日志。 +- 生产正常批次记录总耗时、频控次数、累计冷却时间、失败数和覆盖率;目标约 30 分钟。 +- 如频控导致失败率上升,通过 `ZHIXING_MARKET_DATA_MAX_WORKERS` 降低并发。 +- 完整性检查先在非交易时段触发;确认只产生检查表写入,不改变事实表行数或 CSV mtime。 + +## 4. 完成门禁 + +- 三个子任务 acceptance criteria 全部有实际验证证据。 +- 父 PRD 中所有 acceptance criteria 可映射到子任务测试或生产验收项。 +- 完成 `trellis-check` 后再评估是否有经用户批准、值得提升到 `.trellis/spec/` 的规则。 +- 不自动 commit、push、archive;这些动作仍需用户明确授权。 diff --git a/.trellis/tasks/archive/2026-08/08-11-market-data-sync-performance-audit/prd.md b/.trellis/tasks/archive/2026-08/08-11-market-data-sync-performance-audit/prd.md new file mode 100644 index 0000000..aed78de --- /dev/null +++ b/.trellis/tasks/archive/2026-08/08-11-market-data-sync-performance-audit/prd.md @@ -0,0 +1,84 @@ +# 优化市场数据同步并增加完整校验入口 + +## Goal + +把当前接近四小时的市场数据同步缩短到可接受范围,同时保留六年 qfq +历史修订检查、单股票失败隔离、PostgreSQL/CSV 发布一致性和选股覆盖率契约。 +日常同步由外部调度器执行;耗时较长的完整检查改为用户在 Web 页面中主动触发并查看进度与结果。 + +## Background + +- 2026-08-10 生产批次处理 5002 只股票,总耗时 13339.3 秒;行情阶段后半段稳定在 + 2.525–2.820 秒/只,属于逐股固定成本。 +- 当前实现串行调用 5002 次 `pro_bar(..., adj="qfq")`。本机 Tushare 1.4.29 + 源码表明每次 qfq `pro_bar` 至少调用一次 `daily` 和一次 `adj_factor`。 +- `Settings.market_data_max_workers` 当前默认 4,但未被同步用例或 CLI 使用。 +- 旧项目 `../zgnb-project` 使用 `ThreadPoolExecutor(max_workers=8)` 逐股拉取六年 + `pro_bar`;识别频控错误后按 60/120/180 秒退避,普通错误按 5/10/15 秒退避。 + 它是八路逐股并发,不是按交易日一次请求全市场。 +- 当前 PostgreSQL 适配器在单股 upsert、单股审计记录和覆盖率存在性检查中频繁创建 + 新连接;bar 阶段完成后仍耗时约 110.2 秒才完成批次。 +- 当前 Web 导航已有未启用的“同步任务”入口,后端已有 selection 的 + `202 Accepted + BackgroundTasks + polling` 长任务模式,但生产服务重启会中断进程内任务。 + +## Requirements + +### R1. 八路受控并发 + +- 需要同步单股票行情时默认使用 8 个 worker,并允许通过 + `ZHIXING_MARKET_DATA_MAX_WORKERS` 配置。 +- worker 必须保留单股票事务、临时 CSV、数据库提交后原子发布和单股票失败隔离语义。 +- Tushare 频控由所有 worker 协同处理,不能让一个 worker 进入长退避时其他 worker + 继续无界冲击接口;重试次数、退避和最终失败必须可观测且不泄露凭据。 +- 同一环境仍只允许一个市场数据同步或完整检查批次持有 advisory lock。 + +### R2. 日常同步与完整检查分离 + +- 外部调度器继续触发日常同步,日常同步不依赖浏览器或 FastAPI 生命周期定时器。 +- 日常同步继续按旧项目的数据口径,对全部目标股票逐股获取六年 qfq 完整快照;性能优化来自 + 8 路受控并发,而不是改成按目标交易日增量拉取。 +- 本任务不引入 `adj_factor` 持久化或按复权因子变化选择性重建历史的增量方案。 +- PostgreSQL/CSV 完整性检查不由 cron 自动触发,只允许用户从 HTTP/Web 主动发起;该检查 + 不调用 Tushare,也不重新下载行情。 +- 完整检查需要持久化批次、目标/已检查/问题计数和安全错误,并提供轮询查询契约。 +- 用户可在“同步任务”页面触发完整检查;运行中禁止重复触发,并持续展示进度和最终结果。 +- 完整检查只报告问题:除检查批次和问题明细外,不修改股票主数据、行情、估值或 CSV;发现的 + 异常由后续日常同步修复。 + +### R3. PostgreSQL 批量化 + +- 同一个批次复用有限数量的数据库连接,避免每只股票为 upsert、审计记录和覆盖率检查 + 分别建立新连接。 +- 同步 item 审计记录支持批量写入,同时保留失败股票的可定位记录。 +- 覆盖率通过集合 SQL 一次计算,不逐股票执行 `has_bar` / `has_daily_basic`。 +- 并发写入不得破坏 `(ts_code, trade_date)` 幂等约束或批次汇总计数。 + +### R4. 兼容性 + +- 保留现有 CLI 成功/部分成功/失败退出码、覆盖率阈值和失败重试语义。 +- 保留六年滚动窗口、qfq 唯一口径、PostgreSQL 事实源和 CSV 恢复快照边界。 +- 日常同步结果仍能作为选股批次的数据来源,完整检查不得让正在使用的最近成功批次 + 短暂变成不可用。 + +## Acceptance Criteria + +- [ ] 默认并发数为 8,配置覆盖有效;并发测试能证明最多只有配置数量的单股任务同时执行。 +- [ ] 模拟 Tushare 频控时,所有 worker 服从共享冷却窗口,随后成功恢复或在重试预算耗尽后记录失败。 +- [ ] 单股票失败不发布其临时 CSV,不回滚其他成功股票,重复执行保持幂等。 +- [ ] 日常同步与手动完整检查的职责符合最终确认的数据口径。 +- [ ] `POST` 完整检查返回可轮询的批次标识;重复触发返回冲突;查询端点展示进度、状态和错误。 +- [ ] “同步任务”页面可触发完整检查并展示无记录、运行、通过、发现问题、执行失败状态。 +- [ ] 完整检查不调用 Tushare,不修改市场事实表或正式 CSV;有问题时只持久化安全报告。 +- [ ] 覆盖率计算不再产生逐股票查询;审计记录使用批量写入;连接创建数量不随股票数线性增长。 +- [ ] 5002 只股票的无网络性能测试/受控基准锁定并发调度和数据库调用数量;生产目标为正常频控条件下 + 完整下载约 30 分钟,结果需通过部署后日志验证,不能用本地 mock 代替生产结论。 +- [ ] 后端 Ruff、Pyright、pytest,前端格式、lint、typecheck、Vitest 和构建全部通过。 + +## Out of Scope + +- 不新增分钟级或实时行情。 +- 不改变选股公式、覆盖率阈值业务含义或六年保留边界。 +- 不自动安排周期性完整检查。 +- 不把日常同步改造成按交易日/复权因子增量拉取。 +- 不在完整检查中自动修复、重试或重新下载异常对象。 +- 不在本任务中引入与市场数据无关的通用任务平台。 diff --git a/.trellis/tasks/archive/2026-08/08-11-market-data-sync-performance-audit/research/legacy-sync-and-current-bottlenecks.md b/.trellis/tasks/archive/2026-08/08-11-market-data-sync-performance-audit/research/legacy-sync-and-current-bottlenecks.md new file mode 100644 index 0000000..642ce56 --- /dev/null +++ b/.trellis/tasks/archive/2026-08/08-11-market-data-sync-performance-audit/research/legacy-sync-and-current-bottlenecks.md @@ -0,0 +1,56 @@ +# 旧项目并发模式与当前性能证据 + +## 生产基线 + +- 批次 `7ca4a247-ddb8-4038-a19f-856421433a6a` 在 2026-08-10 处理 5002 只股票, + 总耗时 13339.3 秒。 +- 从 1800 到 5002 的日志计算得到平均 2.674 秒/只,各区段 2.525–2.820 秒/只, + 说明主要成本随股票数线性增长。 +- bar 阶段完成到批次完成还有 110.2 秒;当前代码在此期间按股票执行两次存在性查询。 +- 4 worker 的理想线性下限约 55.1 分钟,8 worker 的理想线性下限约 27.6 分钟。 + +## 当前实现 + +- `application/sync.py:237` 使用普通 `for` 串行调用 `_process_bar`。 +- `bootstrap/config.py:21` 定义 `market_data_max_workers`,但没有调用方使用。 +- `infrastructure/tushare.py:109-119` 每股调用一次六年 qfq `pro_bar`;本机 + Tushare 1.4.29 的 `pro_bar` 内部为 qfq 分别调用 `daily` 和 `adj_factor`。 +- `infrastructure/tushare.py:153-170` 只在外层请求成功后等待固定间隔;并发 worker + 之间没有共享冷却窗口。 +- Tushare 1.4.29 的 `pro_bar` 会在内部捕获原始异常、打印消息并最终抛出通用 + `IOError("ERROR.")`。如果只在 `pro_bar` 外层分类错误,将丢失“访问频繁/429”等信号。 +- `infrastructure/postgres.py` 在 `upsert_bars`、`record_item` 和每次 `_exists` 中创建连接。 +- `application/sync.py:273-278` 为覆盖率逐股票调用 `has_bar` 和 `has_daily_basic`。 +- 100 只股票、每只 1500 行的本机 CSV 探针中,读取、比较、重写和发布平均 + 0.0399 秒/只,属于次要成本。 + +## 旧项目 `../zgnb-project` + +- `application/pipeline.py:104-159` 默认 `workers=8`,使用 + `ThreadPoolExecutor` 和 `as_completed` 逐股并发。 +- `infrastructure/data_source/tushare_adapter.py:134-172` 每股仍请求六年 qfq + `pro_bar`,不是按交易日一次请求全市场。 +- 旧实现识别“访问频繁、请稍后、超过频率、too many requests、429、403”;匹配后按 + 60/120/180 秒退避,普通错误按 5/10/15 秒退避。 +- 因为 `pro_bar` 会把原始异常转换为 `IOError("ERROR.")`,旧实现的外层频控分类不能 + 稳定看到原始供应商消息;新实现需要在实际 `client.daily` / `client.adj_factor` + 调用处协调频控,同时继续复用 `pro_bar` 的 qfq 计算。 +- 旧项目 `/data/check` 只用 000001 探测 Tushare 当日是否出数,不检查本地 PostgreSQL/CSV, + 因而只能参考交互入口,不能直接迁移为本任务的完整性检查。 + +## 当前技术选择 + +- 用户选择保留旧项目的数据口径:日常仍逐股请求六年 qfq,默认 8 worker。 +- 用户明确 Tushare 主要限制是调用次数;本任务不改成按交易日增量方案。 +- 用户选择完整性检查只报告,不自动修复,也不调用 Tushare。 +- Psycopg 3 官方文档确认同步 `ConnectionPool` 可由多个线程共享; + `pool.connection()` 归还连接时自动提交或回滚,池应显式关闭或注册进程退出清理。 + +## 设计影响 + +- 单股 worker 必须返回不可变结果,由主线程汇总;不能并发修改共享 list/counter。 +- Tushare 真实请求需要一个进程内共享的请求协调器:全局最小请求间隔、频控冷却窗口、 + 有界重试和可测试时钟。 +- PostgreSQL 写路径使用大小受限的连接池;同步 item 由主线程分批落库,覆盖率改为集合 SQL。 +- 完整性检查使用独立检查批次表,避免 `mode=check` 污染“最近同步批次”和选股资格查询。 +- PostgreSQL 和 CSV 在同一 advisory lock 下以只读方式流式比较,避免一次加载全市场六年数据。 diff --git a/.trellis/tasks/archive/2026-08/08-11-market-data-sync-performance-audit/task.json b/.trellis/tasks/archive/2026-08/08-11-market-data-sync-performance-audit/task.json new file mode 100644 index 0000000..fb85ac2 --- /dev/null +++ b/.trellis/tasks/archive/2026-08/08-11-market-data-sync-performance-audit/task.json @@ -0,0 +1,30 @@ +{ + "id": "market-data-sync-performance-audit", + "name": "market-data-sync-performance-audit", + "title": "优化市场数据同步并增加完整校验入口", + "description": "将逐股六年 qfq 同步改为 8 路受控并发并批量化 PostgreSQL,同时增加用户主动触发的只读 PostgreSQL/CSV 完整性检查页面。", + "status": "completed", + "dev_type": null, + "scope": null, + "package": null, + "priority": "P2", + "creator": "yuxuanhui", + "assignee": "yuxuanhui", + "createdAt": "2026-08-11", + "completedAt": "2026-08-11", + "branch": null, + "base_branch": "main", + "worktree_path": null, + "commit": null, + "pr_url": null, + "subtasks": [], + "children": [ + "08-11-market-sync-concurrency-storage", + "08-11-market-integrity-check-api", + "08-11-market-integrity-check-web" + ], + "parent": null, + "relatedFiles": [], + "notes": "", + "meta": {} +} \ No newline at end of file diff --git a/.trellis/tasks/archive/2026-08/08-11-market-integrity-check-api/check.jsonl b/.trellis/tasks/archive/2026-08/08-11-market-integrity-check-api/check.jsonl new file mode 100644 index 0000000..abd1a43 --- /dev/null +++ b/.trellis/tasks/archive/2026-08/08-11-market-integrity-check-api/check.jsonl @@ -0,0 +1,5 @@ +{"file":".trellis/spec/backend/market-data-sync.md","reason":"检查只读审计没有破坏市场事实、CSV 或同步批次语义。"} +{"file":".trellis/spec/backend/http-api-contracts.md","reason":"检查路由挂载、模型、状态码和同源路径。"} +{"file":".trellis/spec/backend/quality-guidelines.md","reason":"检查迁移、类型、测试和禁止模式。"} +{"file":".trellis/tasks/08-11-market-integrity-check-api/prd.md","reason":"逐条核对报告范围、只读性、冲突、分页和流式验收。"} +{"file":".trellis/tasks/08-11-market-data-sync-performance-audit/design.md","reason":"检查没有污染首页/selection,并与并发子任务正确集成。"} diff --git a/.trellis/tasks/archive/2026-08/08-11-market-integrity-check-api/design.md b/.trellis/tasks/archive/2026-08/08-11-market-integrity-check-api/design.md new file mode 100644 index 0000000..6637b92 --- /dev/null +++ b/.trellis/tasks/archive/2026-08/08-11-market-integrity-check-api/design.md @@ -0,0 +1,37 @@ +# 市场数据完整性检查 API 设计 + +## 深模块接口 + +`RunMarketIntegrityCheck` 对 HTTP 暴露 prepare/execute/query;调用方不需要知道流式 cursor、CSV +布局、比较算法或检查表写入细节。真实 PostgreSQL/CSV adapters 与内存测试 adapters 落在既有 ports seam。 + +## 数据模型 + +新增 `IntegrityCheckRun`、`IntegrityIssue` 和分页结果。迁移 `0003_market_integrity_checks` 创建: + +- `market_integrity_check`:运行状态、窗口、target/checked/issue 计数、安全批次错误和时间; +- `market_integrity_issue`:check id、稳定 issue key、item kind/key、issue type、安全 message; +- running/created_at 和 issue check id 索引。 + +检查表不参与首页 overview 或 selection source 查询。 + +## 比较算法 + +在同一 advisory lock 中: + +1. 读取最近已完成同步窗口和 active stock master; +2. 比较 stock CSV; +3. PostgreSQL 按 `(ts_code, trade_date)` 流式读取 bars,与逐股 CSV 归并比较; +4. PostgreSQL 按 `(trade_date, ts_code)` 流式读取 daily_basic,与逐日 CSV 归并比较; +5. 每完成一组更新 checked_count,问题分批写入; +6. 无问题为 passed,有问题为 issues_found,批次异常为 failed。 + +事实读取使用稳定快照且不执行 DML。CSV adapter 仅打开正式文件,不创建临时文件、不调用 publish。 + +## HTTP 与恢复 + +挂载 `/api/v1/market-data/integrity-checks`。POST 原子 claim 后通过 BackgroundTasks 执行;已有 running +返回 409。查询端点返回 Pydantic 模型并限制 page size。 + +prepare 时可把超过配置阈值且已不持有 advisory lock 的旧 running 标记为 failed;不能仅按页面刷新 +时间误杀仍在运行的任务。后台 execute 最外层保证异常落库。 diff --git a/.trellis/tasks/archive/2026-08/08-11-market-integrity-check-api/implement.jsonl b/.trellis/tasks/archive/2026-08/08-11-market-integrity-check-api/implement.jsonl new file mode 100644 index 0000000..a2aec01 --- /dev/null +++ b/.trellis/tasks/archive/2026-08/08-11-market-integrity-check-api/implement.jsonl @@ -0,0 +1,5 @@ +{"file":".trellis/spec/backend/market-data-sync.md","reason":"检查必须理解 PostgreSQL/CSV 快照布局、窗口和 advisory lock 契约。"} +{"file":".trellis/spec/backend/http-api-contracts.md","reason":"实现 market_data 业务路由、Pydantic 响应和 FastAPI 错误映射。"} +{"file":".trellis/spec/backend/error-handling.md","reason":"保证批次/问题错误安全、可识别且不吞异常。"} +{"file":".trellis/tasks/08-11-market-data-sync-performance-audit/design.md","reason":"提供独立检查表、流式比较、HTTP 状态和只报告边界。"} +{"file":".trellis/tasks/08-11-market-integrity-check-api/design.md","reason":"定义本子任务的领域接口、比较算法、迁移和恢复行为。"} diff --git a/.trellis/tasks/archive/2026-08/08-11-market-integrity-check-api/implement.md b/.trellis/tasks/archive/2026-08/08-11-market-integrity-check-api/implement.md new file mode 100644 index 0000000..5a7fea2 --- /dev/null +++ b/.trellis/tasks/archive/2026-08/08-11-market-integrity-check-api/implement.md @@ -0,0 +1,13 @@ +# 实施计划 + +1. 增加领域状态、run/issue/分页模型和 ports,先写 passed/issues/failed 应用测试。 +2. 增加 `0003_market_integrity_checks` 迁移并同步 `infrastructure/schema.py`。 +3. 扩充只读 CSV 能力:stock、bar、daily_basic 的列举/读取,不复用写入路径产生副作用。 +4. 实现 PostgreSQL 流式 snapshot reader 和 integrity check store,复用前置任务连接池。 +5. 实现分组 merge comparison、问题批量落库、进度与终态收敛。 +6. 实现同 advisory lock 冲突和陈旧 running 恢复。 +7. 增加 market_data HTTP presentation、router 挂载、202/查询/错误映射测试。 +8. 把经确认的只读检查契约列为 `.trellis/spec/` 更新候选;未经用户批准不直接提升。 +9. 运行 Ruff、Pyright、pytest;配置数据库时运行迁移与完整性集成测试。 + +回滚点:迁移只新增检查表;应用回滚不影响事实表,downgrade 可独立删除检查表。 diff --git a/.trellis/tasks/archive/2026-08/08-11-market-integrity-check-api/prd.md b/.trellis/tasks/archive/2026-08/08-11-market-integrity-check-api/prd.md new file mode 100644 index 0000000..a2013bc --- /dev/null +++ b/.trellis/tasks/archive/2026-08/08-11-market-integrity-check-api/prd.md @@ -0,0 +1,38 @@ +# 实现市场数据完整性检查 API + +## Goal + +提供用户主动触发、可轮询的 PostgreSQL/CSV 完整性检查;检查不访问 Tushare、不修改市场事实, +只持久化检查进度与安全问题报告。 + +## Dependencies + +- 依赖 `08-11-market-sync-concurrency-storage` 的连接池和共享 advisory lock 契约完成。 + +## Requirements + +- 检查 current stock master、六年 qfq bars 和六年 daily_basic 的 PostgreSQL/CSV 一致性。 +- 报告缺失、多余、解析失败、重复键、内容不一致和窗口越界;不把正常停牌误报为缺历史日期。 +- 使用流式/分组比较,不能一次加载全市场约 700 万行。 +- 使用独立检查表和状态 `running/passed/issues_found/failed`,不复用同步批次。 +- 检查与同步使用同一 advisory lock;已有检查运行时禁止重复触发。 +- `POST` 返回 202,`GET latest` 和 `GET by id` 返回进度、终态及分页问题。 +- 后台异常和失去 worker 的陈旧 running 记录必须能收敛为 failed。 +- 除检查表外,执行前后市场事实表内容和正式 CSV 必须完全不变。 + +## Acceptance Criteria + +- [ ] 一致 fixture 得到 `passed`;各类不一致得到 `issues_found` 和稳定问题类型。 +- [ ] CSV 解析失败被报告且不会中断其余对象检查。 +- [ ] 检查只使用本地 DB/CSV adapters,测试能断言 Tushare port 未被构造或调用。 +- [ ] 大数据 fixture/迭代器测试证明比较按股票/交易日分组流式消费。 +- [ ] 同步持锁时检查安全失败,检查持锁时同步不能进入临界区。 +- [ ] HTTP 测试覆盖 202、409、404、503、latest/no_data、轮询进度和问题分页。 +- [ ] 迁移 upgrade/downgrade 和 schema metadata 一致;首页/selection 查询不选择检查记录。 +- [ ] 后端 Ruff、Pyright、pytest 全部通过。 + +## Out of Scope + +- 不检查 Tushare 当日是否出数。 +- 不自动修复、重试或重新下载。 +- 不引入外部队列;沿用当前进程内 BackgroundTasks。 diff --git a/.trellis/tasks/archive/2026-08/08-11-market-integrity-check-api/task.json b/.trellis/tasks/archive/2026-08/08-11-market-integrity-check-api/task.json new file mode 100644 index 0000000..0c6469c --- /dev/null +++ b/.trellis/tasks/archive/2026-08/08-11-market-integrity-check-api/task.json @@ -0,0 +1,26 @@ +{ + "id": "market-integrity-check-api", + "name": "market-integrity-check-api", + "title": "实现市场数据完整性检查 API", + "description": "实现不访问 Tushare、不修改市场事实的 PostgreSQL/CSV 完整性检查批次和 HTTP 轮询接口。", + "status": "completed", + "dev_type": null, + "scope": null, + "package": null, + "priority": "P2", + "creator": "yuxuanhui", + "assignee": "yuxuanhui", + "createdAt": "2026-08-11", + "completedAt": "2026-08-11", + "branch": null, + "base_branch": "main", + "worktree_path": null, + "commit": null, + "pr_url": null, + "subtasks": [], + "children": [], + "parent": "08-11-market-data-sync-performance-audit", + "relatedFiles": [], + "notes": "", + "meta": {} +} \ No newline at end of file diff --git a/.trellis/tasks/archive/2026-08/08-11-market-integrity-check-web/check.jsonl b/.trellis/tasks/archive/2026-08/08-11-market-integrity-check-web/check.jsonl new file mode 100644 index 0000000..2191c54 --- /dev/null +++ b/.trellis/tasks/archive/2026-08/08-11-market-integrity-check-web/check.jsonl @@ -0,0 +1,5 @@ +{"file":".trellis/spec/frontend/hook-guidelines.md","reason":"检查请求适配、query key、取消和 polling 终止逻辑。"} +{"file":".trellis/spec/frontend/state-management.md","reason":"检查没有复制服务器状态或引入不必要全局状态。"} +{"file":".trellis/spec/frontend/quality-guidelines.md","reason":"检查格式、lint、typecheck、Vitest 和 build 证据。"} +{"file":".trellis/tasks/08-11-market-integrity-check-web/prd.md","reason":"逐条核对导航、触发、轮询、恢复、报告和错误态。"} +{"file":".trellis/tasks/08-11-market-data-sync-performance-audit/design.md","reason":"检查前端与已冻结 HTTP 契约及只报告边界一致。"} diff --git a/.trellis/tasks/archive/2026-08/08-11-market-integrity-check-web/design.md b/.trellis/tasks/archive/2026-08/08-11-market-integrity-check-web/design.md new file mode 100644 index 0000000..fe67558 --- /dev/null +++ b/.trellis/tasks/archive/2026-08/08-11-market-integrity-check-web/design.md @@ -0,0 +1,26 @@ +# 同步任务检查页面设计 + +## Feature 接口 + +新增 `features/sync/api` 三件套:types、request adapters、React Query hooks。页面只调用 hooks; +query keys 统一以 `marketIntegrity` 开头。 + +- latest query:进入页面获取最近检查; +- check query:running 时按 id 周期轮询,终态停止; +- trigger mutation:POST 成功后写入/失效相关 query cache 并切换到该 id。 + +## 页面状态 + +- `no_data`:解释检查范围并提供“开始完整性检查”。 +- `running`:进度条、checked/target、问题数、开始时间,按钮禁用。 +- `passed`:完成摘要和“未发现 PostgreSQL/CSV 不一致”。 +- `issues_found`:完成摘要、只报告提示和分页问题表。 +- `failed`:安全错误、重新发起入口;不会声称市场数据损坏。 + +409 时刷新 latest 并接管已有 running;其他错误保留 ApiError 状态和用户可见重试。页面刷新通过 latest +恢复 active id,不需要 localStorage/Zustand。 + +## 路由与可访问性 + +`/sync` 加入 route tree、navigation 和 routePresentation;AppLayout active route 识别该路径。按钮、 +进度、状态和问题列表使用可访问标签,焦点不因 polling 被重置。 diff --git a/.trellis/tasks/archive/2026-08/08-11-market-integrity-check-web/implement.jsonl b/.trellis/tasks/archive/2026-08/08-11-market-integrity-check-web/implement.jsonl new file mode 100644 index 0000000..25aa192 --- /dev/null +++ b/.trellis/tasks/archive/2026-08/08-11-market-integrity-check-web/implement.jsonl @@ -0,0 +1,5 @@ +{"file":".trellis/spec/frontend/directory-structure.md","reason":"按 sync feature 垂直切片组织 API、hooks、页面和测试。"} +{"file":".trellis/spec/frontend/hook-guidelines.md","reason":"实现 query key、AbortSignal、mutation 和条件 polling。"} +{"file":".trellis/spec/frontend/state-management.md","reason":"服务器检查状态只进入 React Query,不复制到 Zustand。"} +{"file":".trellis/spec/frontend/component-guidelines.md","reason":"实现可访问状态、进度、按钮和分页问题展示。"} +{"file":".trellis/tasks/08-11-market-integrity-check-web/design.md","reason":"定义页面状态机、409 接管、刷新恢复和路由行为。"} diff --git a/.trellis/tasks/archive/2026-08/08-11-market-integrity-check-web/implement.md b/.trellis/tasks/archive/2026-08/08-11-market-integrity-check-web/implement.md new file mode 100644 index 0000000..ac38ff3 --- /dev/null +++ b/.trellis/tasks/archive/2026-08/08-11-market-integrity-check-web/implement.md @@ -0,0 +1,10 @@ +# 实施计划 + +1. 根据已冻结 OpenAPI/后端测试响应定义 sync types 和 API adapters,先写 request contract tests。 +2. 实现 latest/check queries、trigger mutation 和终态停止 polling。 +3. 新增 Sync page 的 no_data/running/passed/issues_found/failed 展示和问题分页。 +4. 启用 navigation、route tree、route presentation 与 AppLayout active path。 +5. 补页面刷新恢复、409 接管、503/网络错误、只报告提示和可访问性测试。 +6. 运行 `pnpm format:check`、`pnpm lint`、`pnpm typecheck`、`pnpm test`、`pnpm build`。 + +回滚点:`/sync` route 与导航启用可独立回滚,不影响首页和 selection。 diff --git a/.trellis/tasks/archive/2026-08/08-11-market-integrity-check-web/prd.md b/.trellis/tasks/archive/2026-08/08-11-market-integrity-check-web/prd.md new file mode 100644 index 0000000..b5b0711 --- /dev/null +++ b/.trellis/tasks/archive/2026-08/08-11-market-integrity-check-web/prd.md @@ -0,0 +1,35 @@ +# 实现同步任务检查页面 + +## Goal + +启用现有“同步任务”导航,让用户主动触发只读市场数据完整性检查,并持续查看进度、终态和分页问题报告。 + +## Dependencies + +- 依赖 `08-11-market-integrity-check-api` 冻结路径、字段、状态和错误码后开始实现。 + +## Requirements + +- 新增 `/sync` 路由和 `features/sync` 垂直切片,启用桌面/移动导航入口。 +- 页面明确说明检查只比较 PostgreSQL/CSV,不访问 Tushare、不自动修复。 +- 无历史检查时可触发;running 时显示进度并禁用重复触发;终态停止轮询。 +- 展示 passed、issues_found、failed 状态、窗口、checked/target、问题数和时间。 +- 问题报告分页展示对象类型、对象 key、问题类型和安全说明。 +- 409、503、网络错误和刷新页面后的 running 恢复均有可见行为。 +- 服务器状态只放 React Query,不复制到 Zustand。 + +## Acceptance Criteria + +- [ ] 导航“同步任务”可用并正确激活 `/sync` route presentation。 +- [ ] 点击检查调用一次 POST,显示 accepted/running,并轮询返回的 check id。 +- [ ] running 时按钮禁用;终态停止 polling 并展示正确状态与进度。 +- [ ] 页面刷新后能从 latest running 恢复轮询。 +- [ ] issues_found 展示分页问题;passed 显示无问题;failed 显示安全错误和重试入口。 +- [ ] 页面存在明确的“只报告、不自动修复”提示。 +- [ ] API adapter、query/mutation 和页面测试通过;格式、ESLint、TypeScript、Vitest、build 全部通过。 + +## Out of Scope + +- 不提供自动修复按钮或直接启动 Tushare 同步。 +- 不在本任务中重做首页市场概览。 +- 不新增全局任务中心或 Zustand 服务器状态。 diff --git a/.trellis/tasks/archive/2026-08/08-11-market-integrity-check-web/task.json b/.trellis/tasks/archive/2026-08/08-11-market-integrity-check-web/task.json new file mode 100644 index 0000000..ca36261 --- /dev/null +++ b/.trellis/tasks/archive/2026-08/08-11-market-integrity-check-web/task.json @@ -0,0 +1,26 @@ +{ + "id": "market-integrity-check-web", + "name": "market-integrity-check-web", + "title": "实现同步任务检查页面", + "description": "启用同步任务导航,提供触发、轮询、进度和异常报告页面。", + "status": "completed", + "dev_type": null, + "scope": null, + "package": null, + "priority": "P2", + "creator": "yuxuanhui", + "assignee": "yuxuanhui", + "createdAt": "2026-08-11", + "completedAt": "2026-08-11", + "branch": null, + "base_branch": "main", + "worktree_path": null, + "commit": null, + "pr_url": null, + "subtasks": [], + "children": [], + "parent": "08-11-market-data-sync-performance-audit", + "relatedFiles": [], + "notes": "", + "meta": {} +} \ No newline at end of file diff --git a/.trellis/tasks/archive/2026-08/08-11-market-sync-concurrency-storage/check.jsonl b/.trellis/tasks/archive/2026-08/08-11-market-sync-concurrency-storage/check.jsonl new file mode 100644 index 0000000..b8be413 --- /dev/null +++ b/.trellis/tasks/archive/2026-08/08-11-market-sync-concurrency-storage/check.jsonl @@ -0,0 +1,5 @@ +{"file":".trellis/spec/backend/market-data-sync.md","reason":"检查并发实现仍满足市场同步完整契约和测试矩阵。"} +{"file":".trellis/spec/backend/quality-guidelines.md","reason":"检查 Ruff、Pyright、pytest、禁止模式和集成测试证据。"} +{"file":"docs/adr/0004-tushare-six-year-snapshot-sync.md","reason":"检查单股事务/发布顺序、失败隔离和滚动清理没有漂移。"} +{"file":".trellis/tasks/08-11-market-sync-concurrency-storage/prd.md","reason":"逐条核对并发、频控、数据库调用数量和兼容性验收条件。"} +{"file":".trellis/tasks/08-11-market-data-sync-performance-audit/research/legacy-sync-and-current-bottlenecks.md","reason":"用生产基线和旧实现证据检查性能结论是否被夸大。"} diff --git a/.trellis/tasks/archive/2026-08/08-11-market-sync-concurrency-storage/design.md b/.trellis/tasks/archive/2026-08/08-11-market-sync-concurrency-storage/design.md new file mode 100644 index 0000000..ee19eeb --- /dev/null +++ b/.trellis/tasks/archive/2026-08/08-11-market-sync-concurrency-storage/design.md @@ -0,0 +1,43 @@ +# 八路同步与存储批量化设计 + +## 模块接口 + +- `SyncMarketData.execute(command)`:外部接口保持不变,内部使用固定大小 executor。 +- `SyncItemOutcome`:worker 返回值,封装 result/fingerprint/failure;不暴露线程实现。 +- Tushare 请求协调器:适配器内部接口,执行一个真实 client 方法并隐藏间隔、重试和共享冷却。 +- `MarketDataRepository.record_items(...)`:批量持久化 outcomes。 +- `MarketDataRepository.count_valid_stocks(...)`:集合式覆盖率接口。 + +## 并发流 + +stock master 和 daily_basic 保持主线程顺序执行。bar 阶段把股票提交给最多 8 个 worker;每个 worker +完成获取、CSV staging、数据库事务和 CSV 发布后返回。主线程 `as_completed` 聚合并每 100 项刷新 +审计和日志。批量审计写失败是批次级错误,不回滚已提交事实。 + +## Tushare 频控 + +由 coordinated client 代理 `pro_bar` 实际调用的 `daily` / `adj_factor`: + +1. 请求前检查共享 cooldown;正常情况下允许最多 8 路并发; +2. 调用原始 token client; +3. 频控异常扩大共享 cooldown,唤醒/阻塞所有等待 worker; +4. 普通临时错误只退避当前调用; +5. 到达重试预算后抛出安全 `TushareSourceError`。 + +现有可配置请求间隔保留为单股成功后的温和节流,不对所有真实调用强加全局串行间隔。 +`pro_bar(..., retry_count=1)` 避免 SDK 在看不到共享 gate 的位置自行重试。测试注入 fake clock、wait +和 client,不使用真实睡眠或网络。 + +## PostgreSQL + +依赖调整为 Psycopg pool extra。CLI 在一次执行期间打开池并在退出时关闭;池最大连接数至少覆盖 +8 个 worker、一个主线程写连接和 advisory lock 连接。connection 不跨线程共享。 + +`record_items` 使用 `executemany` 或 COPY/staging 一次写一批。`count_valid_stocks` 从 active 股票 +连接/EXISTS 两张目标日事实表后 count,一次返回 valid count。 + +## 失败与回退 + +- executor 创建或批量审计失败记录 batch 错误并收敛状态。 +- worker 异常必须转换为 outcome,不能让 future 异常跳过进度。 +- worker 数可降为 1,得到与原串行流程等价的安全回退。 diff --git a/.trellis/tasks/archive/2026-08/08-11-market-sync-concurrency-storage/implement.jsonl b/.trellis/tasks/archive/2026-08/08-11-market-sync-concurrency-storage/implement.jsonl new file mode 100644 index 0000000..740a246 --- /dev/null +++ b/.trellis/tasks/archive/2026-08/08-11-market-sync-concurrency-storage/implement.jsonl @@ -0,0 +1,5 @@ +{"file":".trellis/spec/backend/market-data-sync.md","reason":"实施时保持六年 qfq、单股事务、CSV 原子发布、失败重试和覆盖率契约。"} +{"file":".trellis/spec/backend/configuration-and-runtime.md","reason":"新增 worker、频控和连接池配置必须通过 Settings 与部署环境传递。"} +{"file":"docs/adr/0004-tushare-six-year-snapshot-sync.md","reason":"并发化不得破坏快照比较、事务顺序、partial success 和滚动保留决策。"} +{"file":".trellis/tasks/08-11-market-data-sync-performance-audit/research/legacy-sync-and-current-bottlenecks.md","reason":"提供生产基线、旧项目 8 worker/退避模式和当前 Tushare 异常隐藏问题。"} +{"file":".trellis/tasks/08-11-market-sync-concurrency-storage/design.md","reason":"定义 worker outcome、请求协调器、连接池和批量仓储接口。"} diff --git a/.trellis/tasks/archive/2026-08/08-11-market-sync-concurrency-storage/implement.md b/.trellis/tasks/archive/2026-08/08-11-market-sync-concurrency-storage/implement.md new file mode 100644 index 0000000..9c430f2 --- /dev/null +++ b/.trellis/tasks/archive/2026-08/08-11-market-sync-concurrency-storage/implement.md @@ -0,0 +1,14 @@ +# 实施计划 + +1. 先补失败测试:worker 上限、完成顺序、单股隔离、共享频控和集合覆盖率。 +2. 在 domain/application 定义 `SyncItemOutcome`,把 `_process_bar` 重构为无共享可变状态的返回式流程。 +3. 在 Tushare adapter 增加 coordinated client/request coordinator,设置 SDK `retry_count=1`,补错误分类测试。 +4. 把 `market_data_max_workers` 默认改为 8 并完成正整数校验;CLI 注入同步用例。 +5. 引入 Psycopg connection pool,保持 advisory lock 与单股事务的独立连接语义。 +6. 增加 `record_items` 和 `count_valid_stocks`,删除编排中的逐股覆盖率查询。 +7. 用固定线程池执行 bar futures,主线程聚合、分批审计和记录有界进度。 +8. 更新 `.env.example`、Compose 参数传递、市场同步文档和依赖锁文件。 +9. 运行 `uv lock --check`、Ruff format/check、Pyright、pytest;有测试数据库时运行 market_data integration tests。 +10. 运行 5002 fake 股票无网络基准,记录最大并发、耗时和 repository 调用次数。 + +回滚点:连接池改造和并发编排分别保持独立变更;出现供应商问题时配置 worker=1。 diff --git a/.trellis/tasks/archive/2026-08/08-11-market-sync-concurrency-storage/prd.md b/.trellis/tasks/archive/2026-08/08-11-market-sync-concurrency-storage/prd.md new file mode 100644 index 0000000..8c201b8 --- /dev/null +++ b/.trellis/tasks/archive/2026-08/08-11-market-sync-concurrency-storage/prd.md @@ -0,0 +1,40 @@ +# 实现八路同步与存储批量化 + +## Goal + +在不改变逐股六年 qfq、单股票事务/CSV 发布和覆盖率业务语义的前提下,把行情阶段从串行改为 +默认 8 路受控并发,并消除 PostgreSQL 连接、审计写入和覆盖率查询的线性额外开销。 + +## Dependencies + +- 无子任务依赖;本任务是完整性检查后端的前置任务。 + +## Requirements + +- `ZHIXING_MARKET_DATA_MAX_WORKERS` 默认 8,CLI 实际传入同步用例;取值必须大于等于 1。 +- bar worker 只处理一只股票并返回不可变 outcome,主线程负责计数、进度和批量审计。 +- 所有 worker 共用 Tushare 请求协调器;真实 `daily` / `adj_factor` 调用能触发共享频控冷却, + 正常流量不能被一个全局固定间隔重新串行化。 +- 频控分类至少覆盖旧项目的中文提示、429 和 403;频控冷却默认 60/120/180 秒且可配置。 +- 普通临时错误有界重试;数据验证错误不重试;所有日志安全且可统计等待时间。 +- `pro_bar` 继续使用由 `pro_api(token)` 创建的 client,并保留 qfq 计算;内部隐藏重试不得绕开协调器。 +- PostgreSQL 使用线程安全连接池,每个 worker 借独立连接完成单股票事务。 +- `market_sync_item` 由主线程分批 upsert;覆盖率由一个集合查询计算。 +- CLI 参数、退出码、失败重试、advisory lock、滚动清理和摘要字段保持兼容。 + +## Acceptance Criteria + +- [ ] 并发测试证明默认/配置 worker 上限有效,5002 个 fake 股票不会创建 5002 个线程。 +- [ ] 不同完成顺序得到相同汇总;单股票失败不影响其他股票,失败股票不发布临时 CSV。 +- [ ] 一个 worker 命中频控后,其他 worker 在共享冷却截止前不启动新的真实供应商调用。 +- [ ] `pro_bar` 的 `daily` 与 `adj_factor` 都经过请求协调器,token client 回归测试继续通过。 +- [ ] PostgreSQL 集成测试覆盖多线程 upsert、事务回滚、批量 item 和集合覆盖率。 +- [ ] 连接获取次数受池上限约束,覆盖率不再逐股票调用 `has_bar` / `has_daily_basic`。 +- [ ] 同步用例、CLI 和 Tushare 单元测试通过;后端 Ruff、Pyright、pytest 全部通过。 +- [ ] 无网络基准记录 5002 股票调度耗时、最大并发和 repository 调用数;生产 30 分钟目标标记为部署后验证。 + +## Out of Scope + +- 不改成按交易日增量下载,不持久化 adj_factor。 +- 不增加 HTTP 或 Web 页面。 +- 不改变六年窗口、数据表业务字段或选股资格语义。 diff --git a/.trellis/tasks/archive/2026-08/08-11-market-sync-concurrency-storage/task.json b/.trellis/tasks/archive/2026-08/08-11-market-sync-concurrency-storage/task.json new file mode 100644 index 0000000..1e8e4ea --- /dev/null +++ b/.trellis/tasks/archive/2026-08/08-11-market-sync-concurrency-storage/task.json @@ -0,0 +1,26 @@ +{ + "id": "market-sync-concurrency-storage", + "name": "market-sync-concurrency-storage", + "title": "实现八路同步与存储批量化", + "description": "为逐股六年 qfq 同步接入 8 路受控并发、共享频控、连接池、批量审计和集合式覆盖率计算。", + "status": "completed", + "dev_type": null, + "scope": null, + "package": null, + "priority": "P2", + "creator": "yuxuanhui", + "assignee": "yuxuanhui", + "createdAt": "2026-08-11", + "completedAt": "2026-08-11", + "branch": null, + "base_branch": "main", + "worktree_path": null, + "commit": null, + "pr_url": null, + "subtasks": [], + "children": [], + "parent": "08-11-market-data-sync-performance-audit", + "relatedFiles": [], + "notes": "", + "meta": {} +} \ No newline at end of file diff --git a/.trellis/workspace/yuxuanhui/index.md b/.trellis/workspace/yuxuanhui/index.md index ce96011..af47402 100644 --- a/.trellis/workspace/yuxuanhui/index.md +++ b/.trellis/workspace/yuxuanhui/index.md @@ -8,8 +8,8 @@ - **Active File**: `journal-1.md` -- **Total Sessions**: 7 -- **Last Active**: 2026-08-10 +- **Total Sessions**: 8 +- **Last Active**: 2026-08-11 --- @@ -19,7 +19,7 @@ | File | Lines | Status | |------|-------|--------| -| `journal-1.md` | ~185 | Active | +| `journal-1.md` | ~222 | Active | --- @@ -29,6 +29,7 @@ | # | Date | Title | Commits | Branch | |---|------|-------|---------|--------| +| 8 | 2026-08-11 | 完成市场数据同步与完整性检查 | `7ce1154`, `8f5f504` | `develop` | | 7 | 2026-08-10 | 完成选股执行状态抽屉与紧凑布局 | `17237e0` | `develop` | | 6 | 2026-08-10 | 按原型完善选股结果分页接口 | `ed7bdda`, `3af97bf` | `develop` | | 5 | 2026-08-09 | 完成响应式前端交互原型设计 | `0511669` | `develop` | diff --git a/.trellis/workspace/yuxuanhui/journal-1.md b/.trellis/workspace/yuxuanhui/journal-1.md index 0fcc582..2d1dcb6 100644 --- a/.trellis/workspace/yuxuanhui/journal-1.md +++ b/.trellis/workspace/yuxuanhui/journal-1.md @@ -183,3 +183,40 @@ ### Status [OK] **Completed** + + +## Session 8: 完成市场数据同步与完整性检查 + +**Date**: 2026-08-11 +**Task**: 完成市场数据同步与完整性检查 +**Branch**: `develop` + +### Summary + +按依赖顺序完成八路同步与存储批量化、市场完整性检查 API、同步任务 Web 页面;完成质量门禁、迁移离线验证和集成测试。真实 PostgreSQL 集成测试因缺少 ZHIXING_TEST_DATABASE_URL 未运行。 + +### Main Changes + +- 完成 8 worker 受控并发、共享频控、连接池和批量审计 +- 完成 PostgreSQL/CSV 完整性检查 API 与 0003 迁移 +- 完成 /sync 页面、轮询、错误态和导航 + +### Git Commits + +| Hash | Message | +|------|---------| +| `7ce1154` | (see git log) | +| `8f5f504` | (see git log) | + +### Testing + +- [OK] 后端 75 passed, 2 skipped;前端 48 passed +- [OK] Ruff、Pyright、TypeScript、build、Compose 和离线迁移通过 + +### Status + +[OK] **Completed** + +### Next Steps + +- 配置 ZHIXING_TEST_DATABASE_URL 后执行真实 PostgreSQL 集成验证