Files
group_xinghuo_jinrong/docs/项目框架设计/接口契约-代销平台API-v0.2-行情扩展草案.md
T

237 lines
7.9 KiB
Markdown
Raw Normal View History

# 接口契约 · 代销平台 API v0.2 行情扩展(草案)
> 状态:**草案(未实现)** · v0.1 仍为当前 canonical 实现基线
> 范围:**C-05 产品净值 / 前端「产品行情」页** · Phase B 外部数据同步
> 关联:[接口契约-代销平台API-v0.1.md](./接口契约-代销平台API-v0.1.md) · [C-05-行情数据源选型对比.md](./C-05-行情数据源选型对比.md) · [04-从需求到公共API开发方法.md](../项目管理/04-从需求到公共API开发方法.md)
---
## 0. 与 v0.1 的关系
| 项 | v0.1(已实现) | v0.2 草案(本文件) |
| --- | --- | --- |
| 单产品净值 | `GET /api/products/{product_id}/nav` | **保留不变** |
| 产品列表 | `GET /api/products` | **保留不变** |
| 批量净值快照 | 无(前端 N+1 调 nav) | **新增** `GET /api/products/nav-snapshot` |
| 数据来源 | 仅 `core_product_nav` 种子 | 可选 **外部 sync 脚本** 写入同表 |
| 实时看盘/K 线 | 不在范围 | **仍不在范围** |
**拍板原则(2026-09-09):**
- 前端 / Agent **只调本平台 API**,禁止直连第三方行情 REST。
- 外部数据经 **后端 sync 落 Core**,L0 权威仍为 `jinrong_core.core_product_nav`。
- 页眉须区分 **「模拟静态」** vs **「外部同步 T+1」**(非毫秒实时)。
---
## 1. 新增端点
### 1.1 `GET /api/products/nav-snapshot`
**用途:** 产品行情页、Agent 批量问净值;替代前端对每只产品单独 `GET .../nav` 的 N+1。
**鉴权:** 与 v0.1 `/api/products*` 相同——`Authorization: Bearer`;**不要求** `X-Agent-Type`。
**Query 参数:**
| 参数 | 类型 | 默认 | 说明 |
| --- | --- | --- | --- |
| `limit` | int | 50 | 1~200 |
| `offset` | int | 0 | 分页 |
| `product_ids` | string | — | 可选,逗号分隔,如 `PROD-510300,PROD-161725`;与分页互斥时 **product_ids 优先** |
**成功响应(200):**
```json
{
"code": 0,
"message": "ok",
"trace_id": "trc-…",
"data": {
"items": [
{
"product_id": "PROD-510300",
"product_name": "沪深300ETF联接",
"min_risk_code": "R3",
"nav": 1.03,
"daily_chg_pct": 0.35,
"nav_date": "2026-09-04",
"nav_source": "core_seed"
}
],
"total": 14,
"limit": 50,
"offset": 0,
"as_of": "2026-09-04",
"quote_mode": "static_seed"
}
}
```
**字段说明:**
| 字段 | 说明 |
| --- | --- |
| `nav_source` | `core_seed` \| `external_sync` \| `missing`(无净值行) |
| `quote_mode` | 汇总口径:`static_seed`(仅种子)\| `external_t1`(含外部同步且 `MARKET_NAV_ENABLED=true`) |
| `as_of` | 本页净值最大 `nav_date`(便于 UI 展示「数据截至」) |
**无净值产品:** 仍出现在 `items` 中,`nav`/`daily_chg_pct`/`nav_date` 为 `null`,`nav_source=missing`。
**错误:** 手册 §10 统一错误体;401/403 同 v0.1。
---
### 1.2 v0.2 候选(仍非本草案阻塞)
| 方法 | 路径 | 说明 |
| --- | --- | --- |
| GET | `/api/customers/{customer_id}/cash-flows` | 资金流水;需补 `core_ro` |
| GET | `/api/products/{product_id}/nav/history` | 历史净值序列(C-05 增强);Phase C,需 DDL 或历史表 |
---
## 2. Service / Repository 扩展
### 2.1 分层(延续 v0.1 §2.4)
```text
GET /api/products/nav-snapshot
→ app/api/products.py
→ app/service/platform/product_service.py :: list_products_nav_snapshot()
→ app/repository/core_ro.py :: list_products_with_latest_nav(limit, offset, product_ids?)
```
**禁止:** 路由层直连 `core_ro`;禁止 Service 内调用第三方 HTTP。
### 2.2 `core_ro` 新方法(拟)
```python
def list_products_with_latest_nav(
self,
limit: int = 50,
offset: int = 0,
product_ids: list[str] | None = None,
) -> tuple[list[dict], int]:
"""
LEFT JOIN core_product + 各 product 最新 nav(nav_date DESC 子查询)。
返回 product 字段 + nav/nav_date/daily_chg_pct + nav_source(来自 nav 行扩展列或 settings 推断)。
"""
```
**性能:** 单次 SQL JOIN;演示库 ~14 产品无压力。真库 >500 产品时加索引 `(product_id, nav_date DESC)`。
### 2.3 `nav_source` 落库(可选 DDL · Phase B)
若需区分种子与外部同步,可在 `core_product_nav` 增列(**草案,实施前评审**):
```sql
ALTER TABLE core_product_nav
ADD COLUMN nav_source ENUM('core_seed','external_sync') NOT NULL DEFAULT 'core_seed'
COMMENT '净值来源:种子 / 外部同步';
```
不增列时:`quote_mode` 仅由 `settings.market_nav_enabled` + 同步脚本日志推断。
---
## 3. 外部净值同步(`market_nav_sync` · 脚本,非 HTTP API)
### 3.1 架构
```text
第三方 REST(咕咕/iTick/Choice 等 · 见选型对比 doc)
↓ scripts/sync/sync_market_nav.py (拟)
↓ adapter: app/service/platform/market_nav_adapter.py (拟)
UPSERT jinrong_core.core_product_nav
↓
GET /api/products/nav-snapshot (读路径不变)
```
### 3.2 环境变量(`.env.example` 拟增)
| 变量 | 默认 | 说明 |
| --- | --- | --- |
| `MARKET_NAV_ENABLED` | `false` | 是否启用外部同步(false 时仅种子) |
| `MARKET_NAV_PROVIDER` | `none` | `none` \| `gugu` \| `itick` \| `mock`(联调假源) |
| `MARKET_NAV_API_KEY` | — | 第三方 appkey(**勿提交 git**) |
| `MARKET_NAV_API_BASE` | — | 提供商 REST 根 URL |
| `MARKET_NAV_SYNC_CRON` | — | 文档约定,如 `0 20 * * *`(日终 T+1) |
| `MARKET_NAV_PRODUCT_MAP` | — | 可选 JSON 文件路径:外部 code → `PROD-*` |
**settings.py 拟增:**
```python
market_nav_enabled: bool = False
market_nav_provider: str = "none"
market_nav_api_key: str = ""
market_nav_api_base: str = ""
```
### 3.3 同步脚本行为(拟)
```text
scripts/sync/sync_market_nav.py
--dry-run 只打印将写入行,不写库
--provider gugu 覆盖 MARKET_NAV_PROVIDER
--product-id PROD-510300 单产品调试
```
**规则:**
1. 仅 **UPSERT** `core_product_nav`;**不** UPDATE `core_product` 风险等级等业务字段。
2. 同步失败:**不删**已有种子行;写 `audit_log` 或脚本日志;API 仍返回最后成功快照。
3. `product_id` 必须在 `core_product` 存在且 `is_open=1`,否则 skip 并 warn。
4. 禁止爬虫天天基金 / 雪球等非授权源(见选型对比 §4 禁止清单)。
### 3.4 Bootstrap 补充(FLOW / README)
```text
# Phase B 可选(MARKET_NAV_ENABLED=true 时)
python scripts/sync/sync_market_nav.py
# 然后
GET /api/products/nav-snapshot → quote_mode=external_t1
```
---
## 4. 前端契约(`web/` · 产品行情页)
| 项 | P0(当前) | v0.2 接入后 |
| --- | --- | --- |
| API | `GET /api/products` + N×`GET .../nav` | **`GET /api/products/nav-snapshot`** |
| Alert 文案 | 「Core 模拟库静态净值,非实时行情」 | 「外部数据源 T+1 同步 · 非实时交易行情」或保留静态说明 |
| Tab | 无 | 可选「净值一览 \| 实时行情(Phase C 占位 Disabled)」 |
---
## 5. Agent 接入
| 能力 | 路径 | 说明 |
| --- | --- | --- |
| 单产品净值 | `GET /api/products/{id}/nav` | v0.1 已有 |
| 批量净值 | `GET /api/products/nav-snapshot` | v0.2;Tool 合并期改调 platform Service |
| 禁止 | Agent Tool 直连第三方行情 API | 与「禁止直连 Core SQL」同级 |
---
## 6. 实现检查清单(统筹)
- [ ] `core_ro.list_products_with_latest_nav`
- [ ] `product_service.list_products_nav_snapshot`
- [ ] `GET /api/products/nav-snapshot` + 单测
- [ ] `tests/test_platform_api.py` 增补 snapshot 用例
- [ ] `scripts/sync/sync_market_nav.py` + adapter 抽象
- [ ] `.env.example` + `settings` 字段
- [ ] 可选 DDL `nav_source`
- [ ] 前端 `web/` 改调 snapshot
- [ ] 更新 v0.1 契约 §3 脚注 → 指向本文件
---
## 7. 修订记录
| 日期 | 说明 |
| --- | --- |
| 2026-09-09 | 首版草案:nav-snapshot · market_nav_sync · env · 分层与前端/Agent 口径 |