Files
zhixing-system/.trellis/tasks/08-07-home-market-overview/design.md
T
2026-08-07 13:29:31 +08:00

5.7 KiB
Raw Blame History

技术设计:Home 市场数据概览

1. 边界与模块归属

市场数据概览属于现有 modules/market_data bounded context:

zhixing-server/src/zhixing_server/modules/market_data/
├── domain/overview.py              # 无框架的读取模型和值
├── application/overview.py         # 读取用例/端口编排
├── infrastructure/postgres.py      # PostgreSQL 聚合读取适配器
└── presentation/home.py            # Pydantic 模型与 /home/overview 路由

顶层 interfaces/http/router.py 只挂载 bounded context 的 presentation router。FastAPI 依赖和 Pydantic 只出现在 presentation;domain/application 不导入 FastAPI 或 Psycopg。

前端增加 features/home/ 垂直切片;shared/ui 仅承载无业务状态的 shadcn primitives。Home shell 和 overview card 的业务组合保留在 feature 内。

2. HTTP 契约

GET /api/v1/home/overview 返回:

{
  "status": "success",
  "stock_pool_count": 2,
  "latest_update": {
    "trade_date": "2026-08-06",
    "completed_at": "2026-08-07T16:30:00+08:00",
    "status": "success",
    "target_count": 2,
    "successful_count": 2,
    "failed_count": 0,
    "valid_count": 2,
    "coverage_rate": 1.0,
    "failures": [],
    "batch_errors": []
  }
}

约定:

  • 顶层 status 表示 Home 当前状态。没有任何批次时为 no_data,latest_update 为 null。
  • latest_update.status 只使用 updating、success、partial_success、failed;无批次不创建虚假的 update 对象。
  • completed_at 对 running 批次为 null。所有非空时间在 presentation 边界转换到 Asia/Shanghai。
  • failure 包含 ts_code、name、failure_type、reason、trade_date。
  • batch_error 包含 item_key、error_type、reason;只聚合 market_sync_item.item_kind = 'batch',不把日期级 daily_basic 错误伪造成股票级错误。
  • 覆盖率以 Decimal 在读取层计算,Pydantic 在 JSON 边界输出数值;分母为 0 时返回 0。

3. 批次选择与事实计算

读取适配器在一个 PostgreSQL 连接/读取事务中完成聚合:

  1. 统计 market_stock.is_active = true 的当前目标股票池。
  2. 找出最新 target_trade_date;该日期上的最新 running 批次优先,否则选该日期 status != 'running' 的最新已完成批次,排序使用 finished_at DESC, created_at DESC。这保证同日 retry 只展示最后完成结果,且不会让旧日期 running 批次遮蔽更新日期。
  3. 若没有任何批次,返回 no_data。
  4. 对选中批次的目标交易日,使用 active 股票与 market_daily_bar、market_daily_basic 的 left join;两张事实表均存在的股票构成有效/成功集合。
  5. failed_count 是 active 目标股票池减去有效集合;失败详情优先使用同批次 market_sync_item 中该代码的 bar 失败记录,否则按缺失的日线/估值事实生成可读原因。若两类事实都缺失,优先呈现已记录的股票级错误,再使用稳定的缺失类型。
  6. 读取 item_kind = 'batch' AND status = 'failed' 作为独立 batch errors。
  7. target_count 保留选中批次记录的目标数;stock_pool_count 是当前 active 数;正常同步期间两者相同。successful_count / valid_count 来自当前事实完整性计算,coverage_rate 为成功数除以目标数,分母为 0 时为 0。

状态映射:

事实 状态
无同步批次 no_data
最新交易日仍有 running 批次 updating
已完成批次无失败且所有目标股票完整 success
已完成批次存在失败但至少一只股票完整 partial_success
已完成批次失败或没有任何完整股票 failed

这条读取路径不触发同步、不修改数据、不读取 CSV。数据库错误继续抛出 MarketDataRepositoryError,由现有 FastAPI 默认 500 边界暴露,避免把基础设施错误伪装成 no_data。

4. 依赖与测试 seam

  • 增加 MarketDataOverviewReader protocol,HTTP 依赖工厂返回具体 PostgresMarketDataRepository;测试通过 app.dependency_overrides 注入内存 reader,HTTP 测试仍调用 TestClient(create_app())。
  • 读取模型使用 frozen dataclass,Pydantic response model 负责日期/时间和字段名转换。
  • 后端契约测试使用多个 active/inactive 股票、完整/缺失事实、running/completed/retry/batch error 记录构造 fake reader,验证用户可见 JSON,不断言 SQL。
  • 前端 API 通过 requestJson 保留 AbortSignal;query 使用 feature 内 as const key。

5. shadcn/ui 组合策略

现有 Card、Badge、Button 作为基础,按当前 components.json 的 base-nova 约定补齐并复用:

  • shell:Avatar、Input、Separator、Button;
  • loading:Skeleton;
  • status/data:Card、Badge、Progress;
  • failure details:Dialog + ScrollArea。

优先用 shadcn CLI 生成标准 primitive;如果生成器与锁定依赖版本不兼容,则采用 CLI 对应的当前源代码形状,在 shared/ui 保持同样的可访问属性、原生 props、cn 合并和组件组合接口。Home feature 不直接依赖第三方 modal/scroll implementation。

6. 兼容性、迁移与回滚

  • 不新增数据库迁移;只读模型适配现有五张表。
  • API 新增,不修改 /api/v1/system/status;根路由从 smoke 页面切换为 Home,但既有 system feature 测试文件保留。
  • 若读取查询需要兼容已有迁移字段,使用当前 schema 中的字段和状态值,不变更同步写入语义。
  • 回滚点是移除 Home route、overview presentation/reader 及新增前端 feature/shared primitives;数据库无需回滚。