feat(home): 添加市场数据概览

This commit is contained in:
yuxuanhui
2026-08-07 13:29:31 +08:00
parent 9023e00213
commit b0e846d79c
33 changed files with 2229 additions and 18 deletions
@@ -0,0 +1,9 @@
{"file":".trellis/spec/guides/cross-layer-thinking-guide.md","reason":"Review the completed backend-to-frontend contract for field drift, same-origin routing, and state ownership."}
{"file":".trellis/spec/guides/code-reuse-thinking-guide.md","reason":"Check that the implementation reused existing transport, query, styling, and application composition seams."}
{"file":".trellis/spec/backend/http-api-contracts.md","reason":"Verify response_model, router mounting, dependency boundaries, and black-box HTTP tests."}
{"file":".trellis/spec/backend/error-handling.md","reason":"Verify backend errors are not converted into false no-data success and frontend retry remains possible."}
{"file":".trellis/spec/backend/quality-guidelines.md","reason":"Run and assess backend formatting, lint, strict typing, and behavior tests."}
{"file":".trellis/spec/frontend/component-guidelines.md","reason":"Review semantic structure, keyboard behavior, accessible Dialog, and shadcn primitive composition."}
{"file":".trellis/spec/frontend/hook-guidelines.md","reason":"Review query state handling, AbortSignal propagation, and React Query ownership."}
{"file":".trellis/spec/frontend/quality-guidelines.md","reason":"Run and assess frontend format, lint, typecheck, Vitest, and build gates."}
{"file":".trellis/tasks/08-07-home-market-overview/research/shadcn-ui.md","reason":"Confirm newly added UI primitives follow the researched shadcn CLI/component conventions and do not introduce an unrelated UI system."}
@@ -0,0 +1,98 @@
# 技术设计: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;数据库无需回滚。
@@ -0,0 +1,17 @@
{"file":".trellis/spec/guides/cross-layer-thinking-guide.md","reason":"Home overview changes the FastAPI response, same-origin adapter, TypeScript types, query hook, and page states; keep the contract synchronized across layers."}
{"file":".trellis/spec/guides/code-reuse-thinking-guide.md","reason":"The implementation must reuse requestJson, React Query keys, cn, existing UI primitives, and the application factory before adding helpers."}
{"file":".trellis/spec/backend/directory-structure.md","reason":"The backend feature belongs to the existing market_data bounded context and needs domain/application/infrastructure/presentation boundaries."}
{"file":".trellis/spec/backend/market-data-sync.md","reason":"The overview reads the market-data tables and must preserve current stock-pool, daily bar, valuation, batch, retry, and failure semantics."}
{"file":".trellis/spec/backend/http-api-contracts.md","reason":"The new FastAPI route and Pydantic response must follow the /api/v1 router and HTTP testing contract."}
{"file":".trellis/spec/backend/error-handling.md","reason":"Database and transport failures must remain distinguishable from no-data responses and map cleanly to the frontend retry state."}
{"file":".trellis/spec/backend/quality-guidelines.md","reason":"Backend public types, response models, imports, and tests must satisfy Ruff, Pyright, and pytest rules."}
{"file":".trellis/spec/frontend/directory-structure.md","reason":"Home is a frontend feature vertical slice while shadcn primitives remain in shared/ui."}
{"file":".trellis/spec/frontend/component-guidelines.md","reason":"The page must use semantic, accessible React compositions and shadcn UI primitives with cn/Tailwind conventions."}
{"file":".trellis/spec/frontend/hook-guidelines.md","reason":"The overview adapter and hook must use requestJson, AbortSignal, query keys, and explicit query states."}
{"file":".trellis/spec/frontend/type-safety.md","reason":"The API response needs strict TypeScript types and no unsafe assertions beyond the existing transport boundary."}
{"file":".trellis/spec/frontend/quality-guidelines.md","reason":"Home behavior tests and the final frontend quality gate follow the repository's Vitest/Testing Library and lint/typecheck rules."}
{"file":"docs/adr/0001-bounded-context-first-modular-monolith.md","reason":"The backend overview must remain inside the market_data bounded context rather than creating global service layers."}
{"file":"docs/adr/0002-use-a-same-origin-browser-api.md","reason":"The browser adapter must use /api/v1 and preserve Vite/Nginx same-origin routing."}
{"file":"docs/adr/0003-postgresql-as-market-data-store.md","reason":"PostgreSQL remains the runtime fact source for the overview; CSV is not queried."}
{"file":"docs/adr/0004-tushare-six-year-snapshot-sync.md","reason":"Batch status, retries, partial success, coverage, and failure semantics come from the synchronization ADR."}
{"file":".trellis/tasks/08-07-home-market-overview/research/shadcn-ui.md","reason":"The task explicitly requires full shadcn/ui reuse; this records current CLI and primitive guidance for base-nova."}
@@ -0,0 +1,65 @@
# 实施计划:Home 市场数据概览
## 顺序清单
1. [x] 在后端建立 overview domain/application 读取模型、reader protocol 和 PostgreSQL 聚合读取;保留 `market_data` 层边界。
2. [x] 增加 Home presentation Pydantic response、依赖工厂、`GET /api/v1/home/overview`,并挂载到 `/api/v1`。
3. [x] 增加后端 HTTP contract tests,覆盖 no data、running、success、partial、failed、active count、coverage、stock failures、batch errors;同日 retry 选择由 PostgreSQL 查询实现,未在本机无数据库环境中直接执行。
4. [x] 通过 shadcn CLI 补齐 `shared/ui` 所需 primitives;核对 `base-nova` 风格、`@base-ui/react` 依赖和原生可访问行为。
5. [x] 建立 `features/home/api` 的 types、adapter、query;严格复用 `requestJson`、AbortSignal 和 React Query。
6. [x] 实现 Home shell、sidebar、header、overview card、status branches、失败详情 Dialog/ScrollArea,并把根路由从 system smoke page 切换到 Home。
7. [x] 编写 Home 页面行为测试,覆盖 loading/error/no-data/status 分支、计数/覆盖率、失败详情打开、正常关闭与 Escape 关闭、导航禁用项和 Dialog accessible name。
8. [x] 逐步运行后端单测/类型检查和前端单测/typecheck;修复格式、lint、类型问题。
9. [x] 执行后端与前端质量门禁、build、Trellis check 和 code review 流程;根级 `./dev.sh check` 仅受两个既有格式问题阻断,已如实记录。
## 验证命令
后端(在 `zhixing-server/`):
```bash
uv run ruff format --check .
uv run ruff check .
uv run pyright
uv run pytest tests/test_home_overview_http.py
uv run pytest
```
前端(在 `zhixing-web/`):
```bash
pnpm format:check
pnpm lint
pnpm typecheck
pnpm test -- src/features/home/pages/home-page.test.tsx
pnpm test
pnpm build
```
根级(依赖可用时):
```bash
./dev.sh check
./dev.sh test
```
## 风险与回滚点
- PostgreSQL 读取查询必须正确区分 running、同日 retry 和旧日期批次;先用 fake reader HTTP 测试锁定 contract,再实现 SQL。
- `market_sync_item` 现有表只记录 daily-basic 的日期级失败,不能凭空生成股票级估值错误;缺失事实使用稳定的合成原因,batch error 单独输出。
- 当前仓库只有少量 shared UI primitive;新增 shadcn components 可能更新 `package.json` / `pnpm-lock.yaml`,需检查变更是否只服务本任务。
- 任何测试、格式化或 CLI 自动修改不得覆盖用户已有的 `CONTEXT.md` 和 `.scratch/home-market-overview/spec.md` 变更。
- 如果某个质量命令因本机缺少 PostgreSQL、依赖或网络而无法运行,记录为 not run/blocked,不用假通过替代。
## Final verification record
- 后端:任务相关 Ruff format、Ruff check、Pyright 通过;`pytest` 为 `20 passed, 1 skipped`,集成测试因未设置 `ZHIXING_TEST_DATABASE_URL` 跳过。
- 前端:`pnpm check`、`pnpm build` 通过,10 个测试通过。
- 根级:`./dev.sh test` 通过;`./dev.sh check` 仅被未修改的 `src/zhixing_server/modules/market_data/application/sync.py` 和 `tests/unit/market_data/test_sync.py` 格式问题阻断。
- 质量检查:Trellis check 修复了失败项重复关联风险,并补充了状态和 Escape 行为测试;code-review 子代理因超时未返回可用报告,主会话完成了 spec/standards 对照复核。
## 启动前 review gate
- `prd.md` 已移除导航 open question,并包含 shadcn/ui 复用要求。
- `design.md` 已锁定响应形状、批次选择、失败分类、依赖 seam 和回滚策略。
- `implement.jsonl` / `check.jsonl` 已有真实 spec/research context。
- 用户已明确批准导航结构和 shadcn/ui 要求;完成最终规划摘要后,下一条用户消息需明确批准规划再执行 `task.py start`。
@@ -0,0 +1,60 @@
# Home 市场数据概览
## Goal
让量化研究者进入系统后,在一个稳定的管理后台 Home 页面立即了解当前目标股票池和最近一次市场数据同步状态,并能定位失败股票。
## Background and confirmed facts
- 当前根路由展示的是后端连通性 smoke 页面,前端只有 `system` feature。
- `market_data` bounded context 已有 `market_stock`、`market_daily_bar`、`market_daily_basic`、`market_sync_batch` 和 `market_sync_item` 表及同步写入逻辑。
- PostgreSQL 是运行时市场数据事实源;CSV 仅是同步快照和恢复介质。
- 当前目标股票池由 active 的沪深非 ST A 股组成,历史 inactive 主数据不能计入当前目标数。
- 一只股票只有在目标交易日同时存在日线行情和估值快照时才是有效股票。
- 同一交易日的重试批次以最近一次已完成结果为展示结果;该交易日仍有 running 批次时优先展示“更新中”。
- 浏览器通过同源 `/api/v1` 路径访问后端。
- 侧边栏采用确认过的最小结构:`首页` 可用,`行情数据`、`同步任务`、`选股策略` 可见但禁用。
- 前端项目已配置 shadcn/ui(`base-nova`、Tailwind CSS 4、Lucide 图标和 `@/shared/ui` 别名);本任务必须优先复用现有 shadcn primitives,并按需补齐标准 primitive。
## Requirements
### Backend overview contract
- 新增只读 `GET /api/v1/home/overview` 聚合接口。
- 响应提供当前目标股票池数、最近更新交易日、同步完成时间、更新状态、目标数、成功数、失败数、有效数、覆盖率,以及失败股票详情和批次级错误。
- 状态稳定表示为 `updating`、`success`、`partial_success`、`failed`、`no_data`;无任何批次时必须能与 HTTP/服务错误区分。
- 目标交易日按最近开市日语义展示;接口不得用旧成功批次冒充更新了更晚交易日。
- 日期和完成时间在展示边界按 `Asia/Shanghai` 表达;交易日与完成时间必须是独立字段。
- 目标数只计算 active 当前目标股票池;成功数只计算目标日同时存在 daily bar 和 daily basic 的股票;覆盖率为成功数 / 目标数。
- 失败股票包含代码、名称、失败类型、可读原因和目标交易日。缺失 daily bar、缺失 valuation snapshot、已记录的股票级失败应能区分;批次级错误不得伪造成股票失败。
- PostgreSQL 读取失败沿用现有 HTTP 错误边界,不返回伪造的空成功数据。
### Frontend Home
- 根路由替换为 Home 页面,同时保留现有 provider、router、query client 和主题行为。
- Home 使用 feature 垂直切片,包含 overview API adapter、响应类型、query hook、页面和失败详情对话框。
- 页面包含管理后台 shell:侧边栏、顶部搜索视觉占位、头像视觉区域和主内容区;未实现的导航入口可见但禁用。
- 管理后台 shell、概览卡片、状态反馈和失败详情优先由 `shared/ui` 中的 shadcn primitives 组合实现;按需补齐并使用 `Avatar`、`Dialog`、`Input`、`Progress`、`ScrollArea`、`Separator`、`Skeleton` 等标准组件,不在 feature 中复制同类基础交互。
- 主内容使用一个“市场数据概览”组合卡片,展示最近更新交易日、同步完成时间、状态、目标/成功/失败数量、覆盖率和当前目标股票池数量。
- 分别渲染加载占位、暂无数据、请求异常及重试、更新中、已更新、部分更新和未更新状态;请求异常提供 retry 操作。
- 点击失败汇总打开可滚动、可关闭、支持键盘交互的失败详情对话框;对话框展示代码、名称、失败类型、原因和目标交易日。
- 桌面和平板保持可读;窄屏将内容纵向堆叠,不影响失败详情访问。
- 搜索和头像只做视觉展示,不实现搜索、认证、账户菜单或权限行为。
## Acceptance Criteria
- [ ] `GET /api/v1/home/overview` 的 HTTP 契约可测试覆盖:active 股票计数、完整成功、部分成功、失败批次、running 批次、无批次、同日 retry 选择、覆盖率、股票级失败详情和批次级错误。
- [ ] 接口对完整成功、部分成功、失败、更新中和无数据返回正确稳定状态;批次级错误不会出现在股票失败列表中。
- [ ] Home 根路由可见管理后台 shell 和“市场数据概览”卡片;既有 system HTTP/frontend 测试继续通过。
- [ ] 侧边栏显示 `首页`、`行情数据`、`同步任务`、`选股策略`,只有首页可操作,其余入口明确禁用;搜索和头像保持视觉占位。
- [ ] Home 的卡片、状态反馈和失败弹窗使用项目 `@/shared/ui` 的 shadcn primitives,新增 primitive 保持 `components.json` 的 `base-nova` / Tailwind 约定。
- [ ] Home 前端测试覆盖正常、加载、无数据、请求错误与重试、全部更新状态、数量/覆盖率、打开失败对话框、详情字段和关闭行为,并包含对话框可访问名称断言。
- [ ] 后端 Ruff、Pyright、pytest 以及前端格式检查、lint、typecheck、Vitest、build 通过;根级检查按环境可用性执行并如实记录。
## Out of scope
- 从 Home 触发同步、重试失败股票、修复数据或执行选股。
- 股票搜索、股票详情、同步历史、策略/选股页面、实时或分钟行情。
- 认证、用户资料、账户菜单和权限管理。
- 修改六年保留策略、Tushare 同步流程、市场数据写入语义或 PostgreSQL 事实源。
- 独立交易日历产品和超出可用堆叠布局的移动端视觉打磨。
@@ -0,0 +1,23 @@
# shadcn/ui 组件研究
## 来源
- Context7 library: `/shadcn-ui/ui`
- 当前官方文档通过 Context7 返回的来源包括:
- `https://github.com/shadcn-ui/ui/blob/main/apps/v4/content/docs/(root)/cli.mdx`
- `https://github.com/shadcn-ui/ui/blob/main/apps/v4/content/docs/components/aria/scroll-area.mdx`
- `https://github.com/shadcn-ui/ui/blob/main/apps/v4/content/docs/components/radix/skeleton.mdx`
- `https://github.com/shadcn-ui/ui/blob/main/apps/v4/content/docs/installation/vite.mdx`
## 结论
- 当前仓库的 `components.json` 已配置为 `base-nova`、Tailwind CSS 4、Lucide 图标,并将 `@/shared/ui` 作为 UI alias;新增组件应落在 `zhixing-web/src/shared/ui/`。
- 官方推荐通过 `npx shadcn@latest add [component-name]` 添加组件。若当前 CLI 与仓库版本不兼容,则保留相同 shadcn 组件边界和可访问性契约,在本地按生成结果最小化实现。
- Home 需要的基础能力包括 `Avatar`、`Dialog`、`Input`、`Progress`、`ScrollArea`、`Separator` 和 `Skeleton`;`Card`、`Badge`、`Button` 已存在,应先复用并只补足实际缺失的 variant/API。
- `Skeleton` 使用 Tailwind class 控制尺寸;`Dialog`、`ScrollArea` 等交互组件必须保留标准键盘与可访问行为,页面测试应从 role、name 和用户可见文本验证。
## 对本任务的约束
- 不在 Home feature 内重新实现 modal、滚动容器、头像或 loading primitive。
- 不把 shadcn primitive 变成数据感知组件;数据请求和业务分支留在 feature/page 层。
- 若执行 CLI 添加组件,检查其依赖、生成文件和 `pnpm-lock.yaml`,再运行格式、lint、类型和测试门禁。
@@ -0,0 +1,26 @@
{
"id": "home-market-overview",
"name": "home-market-overview",
"title": "实现 Home 市场数据概览",
"description": "",
"status": "in_progress",
"dev_type": null,
"scope": null,
"package": null,
"priority": "P2",
"creator": "yuxuanhui",
"assignee": "yuxuanhui",
"createdAt": "2026-08-07",
"completedAt": null,
"branch": null,
"base_branch": "main",
"worktree_path": null,
"commit": null,
"pr_url": null,
"subtasks": [],
"children": [],
"parent": null,
"relatedFiles": [],
"notes": "",
"meta": {}
}