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

99 lines
5.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 技术设计:Home 市场数据概览
## 1. 边界与模块归属
市场数据概览属于现有 `modules/market_data` bounded context:
```text
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` 返回:
```json
{
"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;数据库无需回滚。