# 接口契约 · 代销平台 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 口径 |