diff --git a/app/api/milvus.py b/app/api/milvus.py new file mode 100644 index 0000000..4021cc0 --- /dev/null +++ b/app/api/milvus.py @@ -0,0 +1,119 @@ +"""Milvus 向量库只读查看 API(展示窗口 · 不加增删改)。 + +只读导出当前 Milvus 库中的 collection 清单:中文名 / 描述 / 向量数 / +维度 / 标量字段 / 少量样本。面向「向量知识库」展示页,供四角色查看 +公开知识库与话术模板库(无客户敏感数据),鉴权走平台级通道。 + +连接与 pymilvus 环境防御复用 app/service/milvus_service(milvus_client); +Milvus Lite 跨进程重开后 collection 处于 released 状态,query 前必须 +load_collection(与 milvus_service.ensure_collection 同口径)。 +""" + +from __future__ import annotations + +from fastapi import APIRouter, Depends, Request + +from app.api.deps import AuthContext, get_platform_auth_context +from app.service import milvus_service +from app.utils.response import ok + +router = APIRouter(prefix="/api/milvus", tags=["platform-milvus"]) + +# 已知 collection 的中文名 + 描述(未知库回退用原始名) +COLLECTION_META: dict[str, dict[str, str]] = { + "fin_faq": { + "title": "常见问答库", + "description": "客服线 FAQ 问答对(问题 / 答案 / 分类),客户与顾问语义检索共用。", + }, + "fin_policy": { + "title": "政策制度库", + "description": "客服线政策条款(政策名 / 章节),客户与顾问语义检索共用。", + }, + "fin_product": { + "title": "产品知识库", + "description": "客服线产品信息(产品名 / 风险等级 / 文档类型),客户与顾问语义检索共用。", + }, + "kb_script_templates": { + "title": "话术模板库", + "description": "顾问展业话术模板(标题 / 场景 / 标签 / 审核态),按语义相似度检索。", + }, + "kb_product_rules": { + "title": "产品规则库", + "description": "产品手册 / 交易规则(费率、申赎、风险说明),客户与顾问共用,回答必带溯源。", + }, +} + +_SAMPLE_LIMIT = 3 + + +def _scalar_fields(desc: dict) -> tuple[list[str], int]: + """describe_collection → (标量字段名列表, embedding 维度)。""" + fields: list[str] = [] + dim = 0 + for f in desc.get("fields", []): + name = f.get("name") + if not name: + continue + if f.get("type") == milvus_service.DataType.FLOAT_VECTOR: + dim = int((f.get("params") or {}).get("dim", 0) or 0) + continue + fields.append(name) + return fields, dim + + +@router.get("/collections") +def list_collections( + request: Request, + auth: AuthContext = Depends(get_platform_auth_context), +) -> dict: + """Milvus collection 清单(只读)。""" + del auth # 平台级鉴权已在上方依赖完成;本端点无额外角色限制 + client = milvus_service.milvus_client() + try: + names = sorted(client.list_collections()) + items: list[dict] = [] + total_vectors = 0 + for name in names: + meta = COLLECTION_META.get(name, {"title": name, "description": ""}) + row_count = 0 + fields: list[str] = [] + dim = 0 + sample: list[dict] = [] + try: + stats = client.get_collection_stats(name) + row_count = int(stats.get("row_count", 0)) + total_vectors += row_count + fields, dim = _scalar_fields(client.describe_collection(name)) + if row_count > 0: + client.load_collection(name) + sample = client.query( + name, + filter="", + output_fields=fields, + limit=_SAMPLE_LIMIT, + ) + except Exception: + # 单个 collection 读取失败不阻断整体展示(降级为空样本) + sample = [] + items.append( + { + "name": name, + "title": meta.get("title", name), + "description": meta.get("description", ""), + "row_count": row_count, + "dimension": dim, + "fields": fields, + "sample": sample, + } + ) + finally: + client.close() + trace_id = getattr(request.state, "trace_id", "unknown") + return ok( + { + "items": items, + "total_collections": len(items), + "total_vectors": total_vectors, + }, + trace_id, + ).model_dump() diff --git a/app/main.py b/app/main.py index a2cfb69..5b23680 100644 --- a/app/main.py +++ b/app/main.py @@ -33,6 +33,7 @@ from app.api.guard import router as advisor_guard_router from app.api.kyc import router as advisor_kyc_router from app.api.market import fund_router as advisor_market_fund_router from app.api.market import router as advisor_market_alerts_router +from app.api.milvus import router as milvus_router from app.api.ready import build_ready_payload, router as ready_router from app.api.visitor import router as visitor_router from app.advisor_exceptions import AdvisorAppError @@ -122,6 +123,7 @@ app.include_router(advisor_copy_router) app.include_router(advisor_guard_router) app.include_router(advisor_dashboard_router) app.include_router(advisor_allocation_router) +app.include_router(milvus_router) app.include_router(ready_router) diff --git a/app/service/platform/common.py b/app/service/platform/common.py index 8b78db4..7a1c389 100644 --- a/app/service/platform/common.py +++ b/app/service/platform/common.py @@ -9,14 +9,22 @@ from typing import Any from app.config.settings import settings from app.utils import desensitize as d +# core_product_nav 等为 DECIMAL(10,4);若统一 round 2 位会把货基 1.001x 压成 1.0,走势图变平 +_DECIMAL_PLACES_BY_KEY: dict[str, int] = { + "nav": 4, + "daily_chg_pct": 4, + "daily_return": 4, +} -def to_jsonable(value: Any) -> Any: + +def to_jsonable(value: Any, *, _key: str | None = None) -> Any: if isinstance(value, Decimal): - return float(round(value, 2)) + places = _DECIMAL_PLACES_BY_KEY.get(_key or "", 2) + return float(round(value, places)) if isinstance(value, (_dt.datetime, _dt.date)): return value.isoformat() if isinstance(value, dict): - return {k: to_jsonable(v) for k, v in value.items()} + return {k: to_jsonable(v, _key=k) for k, v in value.items()} if isinstance(value, list): return [to_jsonable(v) for v in value] return value diff --git a/docs/答辩/README.md b/docs/答辩/README.md index 5d30afd..0db3c80 100644 --- a/docs/答辩/README.md +++ b/docs/答辩/README.md @@ -7,11 +7,12 @@ | 中文职责 | 文件 | 何时用 | | --- | --- | --- | -| README 口播 | [`答辩稿.md`](答辩稿.md) | **17 页贴幻灯片** + Demo 脚本 + 【ADR】简句 | +| README 口播 | [`答辩稿.md`](答辩稿.md) | 源稿 · **Word 打印:** [`答辩稿.docx`](答辩稿.docx) | +| **Word 口播稿** | [`答辩稿.docx`](答辩稿.docx) | 由 `答辩稿.md` 导出,改 md 后需重新 pandoc | | Live Demo 逐步操作 | [`DEMO-SOP-答辩全流程.md`](DEMO-SOP-答辩全流程.md) | 现场演示主脚本 | | 问数分册 | [`DEMO-SOP-问数.md`](DEMO-SOP-问数.md) | 问数/interpret/analyze 细节 | | HTML 幻灯片 | [`presentation/index.html`](presentation/index.html) | 投屏 / Ctrl+P 导出 PDF | -| **口播 A4 打印** | [`print/答辩口播-打印版.html`](print/答辩口播-打印版.html) | 浏览器 Ctrl+P · [`print/README.md`](print/README.md) | +| **口播 A4 打印(HTML,可选)** | [`print/答辩口播-打印版.html`](print/答辩口播-打印版.html) | 浏览器 Ctrl+P | | 知识点提纲 + 深 Q&A | [`答辩知识点清单.md`](答辩知识点清单.md) | 备查;§八为内部工程项**不上台** | | **Demo 功能测试(内部)** | [`DEMO-功能测试流程.md`](DEMO-功能测试流程.md) | 答辩前自测,**不对评委念** | | **客户文本交付 6 项** | [`提交物料/README.md`](提交物料/README.md) | 对照 `答辩所需文本交付物清单.txt` 打包提交 | diff --git a/docs/答辩/print/README.md b/docs/答辩/print/README.md index 887d996..66738a1 100644 --- a/docs/答辩/print/README.md +++ b/docs/答辩/print/README.md @@ -2,10 +2,6 @@ | 中文职责 | 文件 | 操作 | | --- | --- | --- | -| **口播稿(A4 打印/PDF)** | [`答辩口播-打印版.html`](答辩口播-打印版.html) | 浏览器打开 → **Ctrl+P** → 另存为 PDF 或直接打印 | -| 投屏幻灯片 PDF | [`../presentation/index.html`](../presentation/index.html) | 同上 Ctrl+P(需联网 CDN) | -| Markdown 源稿 | [`../答辩稿.md`](../答辩稿.md) | 与打印版同步维护 | - -**打印建议:** A4 · 边距「默认」或「最小」· 勾选「背景图形」(ADR 灰底)· 双面可选。 - -**提交物料 6 项** 仍以 `docs/答辩/提交物料/` 为准;本目录只管 **现场口播打印**。 +| **口播稿 Word(推荐)** | [`../答辩稿.docx`](../答辩稿.docx) | Word / WPS 打开后直接打印 | +| Markdown 源稿 | [`../答辩稿.md`](../答辩稿.md) | 改内容后执行:`pandoc 答辩稿.md -o 答辩稿.docx` | +| HTML 打印(可选) | [`答辩口播-打印版.html`](答辩口播-打印版.html) | Ctrl+P | diff --git a/docs/答辩/答辩稿.docx b/docs/答辩/答辩稿.docx new file mode 100644 index 0000000..882c173 Binary files /dev/null and b/docs/答辩/答辩稿.docx differ diff --git a/scripts/core/06-seed-nav.sql b/scripts/core/06-seed-nav.sql index 6e994d7..a0cf4b8 100644 --- a/scripts/core/06-seed-nav.sql +++ b/scripts/core/06-seed-nav.sql @@ -20,72 +20,12 @@ USE jinrong_core; -- PROD-PRIV01 <- 519062 海富通阿尔法对冲混合A(private_fund/R4:量化对冲,日波动 0.33%) -- PROD-WMG001 <- 004155 中信保诚至泰中短债A(wealth_mgmt/R2:中短债,日波动 0.02%) -- PROD-XYZ999 <- 320007 诺安成长混合A(stock/R5:高波动成长,日波动 1.76%) --- PROD-000001 <- 恒定 1.0000(现金宝货币(money/R1:货基单位净值恒为 1 元,收益以份额结转)) +-- PROD-000001 <- 见 07-seed-nav-history.sql(现金宝:演示低波曲线,不在此重复灌平线) -- -- ⚠️ 真实净值只到抓取当日。之后运行需重跑 fetch_nav.py 增量补拉, -- 否则 T+1 确认会取不到 T 日净值(真实业务同样是「净值 T+1 才公告」)。 INSERT INTO core_product_nav (product_id, nav, daily_chg_pct, nav_date) VALUES -('PROD-000001', 1.0000, 0.0000, '2026-06-22'), -('PROD-000001', 1.0000, 0.0000, '2026-06-23'), -('PROD-000001', 1.0000, 0.0000, '2026-06-24'), -('PROD-000001', 1.0000, 0.0000, '2026-06-25'), -('PROD-000001', 1.0000, 0.0000, '2026-06-26'), -('PROD-000001', 1.0000, 0.0000, '2026-06-29'), -('PROD-000001', 1.0000, 0.0000, '2026-06-30'), -('PROD-000001', 1.0000, 0.0000, '2026-07-01'), -('PROD-000001', 1.0000, 0.0000, '2026-07-02'), -('PROD-000001', 1.0000, 0.0000, '2026-07-03'), -('PROD-000001', 1.0000, 0.0000, '2026-07-06'), -('PROD-000001', 1.0000, 0.0000, '2026-07-07'), -('PROD-000001', 1.0000, 0.0000, '2026-07-08'), -('PROD-000001', 1.0000, 0.0000, '2026-07-09'), -('PROD-000001', 1.0000, 0.0000, '2026-07-10'), -('PROD-000001', 1.0000, 0.0000, '2026-07-13'), -('PROD-000001', 1.0000, 0.0000, '2026-07-14'), -('PROD-000001', 1.0000, 0.0000, '2026-07-15'), -('PROD-000001', 1.0000, 0.0000, '2026-07-16'), -('PROD-000001', 1.0000, 0.0000, '2026-07-17'), -('PROD-000001', 1.0000, 0.0000, '2026-07-20'), -('PROD-000001', 1.0000, 0.0000, '2026-07-21'), -('PROD-000001', 1.0000, 0.0000, '2026-07-22'), -('PROD-000001', 1.0000, 0.0000, '2026-07-23'), -('PROD-000001', 1.0000, 0.0000, '2026-07-24'), -('PROD-000001', 1.0000, 0.0000, '2026-07-27'), -('PROD-000001', 1.0000, 0.0000, '2026-07-28'), -('PROD-000001', 1.0000, 0.0000, '2026-07-29'), -('PROD-000001', 1.0000, 0.0000, '2026-07-30'), -('PROD-000001', 1.0000, 0.0000, '2026-07-31'), -('PROD-000001', 1.0000, 0.0000, '2026-08-03'), -('PROD-000001', 1.0000, 0.0000, '2026-08-04'), -('PROD-000001', 1.0000, 0.0000, '2026-08-05'), -('PROD-000001', 1.0000, 0.0000, '2026-08-06'), -('PROD-000001', 1.0000, 0.0000, '2026-08-07'), -('PROD-000001', 1.0000, 0.0000, '2026-08-10'), -('PROD-000001', 1.0000, 0.0000, '2026-08-11'), -('PROD-000001', 1.0000, 0.0000, '2026-08-12'), -('PROD-000001', 1.0000, 0.0000, '2026-08-13'), -('PROD-000001', 1.0000, 0.0000, '2026-08-14'), -('PROD-000001', 1.0000, 0.0000, '2026-08-17'), -('PROD-000001', 1.0000, 0.0000, '2026-08-18'), -('PROD-000001', 1.0000, 0.0000, '2026-08-19'), -('PROD-000001', 1.0000, 0.0000, '2026-08-20'), -('PROD-000001', 1.0000, 0.0000, '2026-08-21'), -('PROD-000001', 1.0000, 0.0000, '2026-08-24'), -('PROD-000001', 1.0000, 0.0000, '2026-08-25'), -('PROD-000001', 1.0000, 0.0000, '2026-08-26'), -('PROD-000001', 1.0000, 0.0000, '2026-08-27'), -('PROD-000001', 1.0000, 0.0000, '2026-08-28'), -('PROD-000001', 1.0000, 0.0000, '2026-08-31'), -('PROD-000001', 1.0000, 0.0000, '2026-09-01'), -('PROD-000001', 1.0000, 0.0000, '2026-09-02'), -('PROD-000001', 1.0000, 0.0000, '2026-09-03'), -('PROD-000001', 1.0000, 0.0000, '2026-09-04'), -('PROD-000001', 1.0000, 0.0000, '2026-09-07'), -('PROD-000001', 1.0000, 0.0000, '2026-09-08'), -('PROD-000001', 1.0000, 0.0000, '2026-09-09'), -('PROD-000001', 1.0000, 0.0000, '2026-09-10'), -('PROD-000001', 1.0000, 0.0000, '2026-09-11'); INSERT INTO core_product_nav (product_id, nav, daily_chg_pct, nav_date) VALUES ('PROD-000002', 1.1004, 0.0100, '2026-06-22'), ('PROD-000002', 1.1004, 0.0000, '2026-06-23'), diff --git a/scripts/core/07-seed-nav-history.sql b/scripts/core/07-seed-nav-history.sql index 0f42133..9a70fdb 100644 --- a/scripts/core/07-seed-nav-history.sql +++ b/scripts/core/07-seed-nav-history.sql @@ -2,7 +2,7 @@ USE jinrong_core; -- 近 365 个自然日(含锚定日 2026-09-04;与 06-seed-nav 锚点净值一致) -- 日涨跌:模拟 CSI300 + 产品 beta(seed_nav_history.py) --- 生成:python scripts/core/seed_nav_history.py --export-sql · 5110 行 · 14 产品 +-- 生成:python scripts/core/seed_nav_history.py --export-sql · 5208 行 · 14 产品 INSERT INTO core_product_nav (product_id, nav, daily_chg_pct, nav_date) VALUES ('PROD-110022', 1.0360, 0.0300, '2025-09-05'), @@ -369,7 +369,14 @@ INSERT INTO core_product_nav (product_id, nav, daily_chg_pct, nav_date) VALUES ('PROD-110022', 1.0297, 0.0104, '2026-09-01'), ('PROD-110022', 1.0300, 0.0311, '2026-09-02'), ('PROD-110022', 1.0297, -0.0305, '2026-09-03'), -('PROD-110022', 1.0300, 0.0291, '2026-09-04'), +('PROD-110022', 1.0294, -0.0289, '2026-09-04'), +('PROD-110022', 1.0294, -0.0021, '2026-09-05'), +('PROD-110022', 1.0293, -0.0058, '2026-09-06'), +('PROD-110022', 1.0295, 0.0205, '2026-09-07'), +('PROD-110022', 1.0293, -0.0176, '2026-09-08'), +('PROD-110022', 1.0295, 0.0219, '2026-09-09'), +('PROD-110022', 1.0292, -0.0316, '2026-09-10'), +('PROD-110022', 1.0292, -0.0028, '2026-09-11'), ('PROD-110023', 1.0199, 0.0245, '2025-09-05'), ('PROD-110023', 1.0200, 0.0072, '2025-09-06'), ('PROD-110023', 1.0198, -0.0214, '2025-09-07'), @@ -497,16 +504,16 @@ INSERT INTO core_product_nav (product_id, nav, daily_chg_pct, nav_date) VALUES ('PROD-110023', 1.0189, 0.0224, '2026-01-07'), ('PROD-110023', 1.0193, 0.0350, '2026-01-08'), ('PROD-110023', 1.0198, 0.0466, '2026-01-09'), -('PROD-110023', 1.0193, -0.0447, '2026-01-10'), +('PROD-110023', 1.0193, -0.0447, '2026-01-10') ON DUPLICATE KEY UPDATE nav = VALUES(nav), daily_chg_pct = VALUES(daily_chg_pct); + +INSERT INTO core_product_nav (product_id, nav, daily_chg_pct, nav_date) VALUES ('PROD-110023', 1.0190, -0.0282, '2026-01-11'), ('PROD-110023', 1.0195, 0.0470, '2026-01-12'), ('PROD-110023', 1.0197, 0.0197, '2026-01-13'), ('PROD-110023', 1.0201, 0.0348, '2026-01-14'), ('PROD-110023', 1.0202, 0.0130, '2026-01-15'), ('PROD-110023', 1.0202, 0.0041, '2026-01-16'), -('PROD-110023', 1.0205, 0.0284, '2026-01-17'); - -INSERT INTO core_product_nav (product_id, nav, daily_chg_pct, nav_date) VALUES +('PROD-110023', 1.0205, 0.0284, '2026-01-17'), ('PROD-110023', 1.0206, 0.0052, '2026-01-18'), ('PROD-110023', 1.0207, 0.0095, '2026-01-19'), ('PROD-110023', 1.0202, -0.0498, '2026-01-20'), @@ -736,7 +743,14 @@ INSERT INTO core_product_nav (product_id, nav, daily_chg_pct, nav_date) VALUES ('PROD-110023', 1.0208, 0.0273, '2026-09-01'), ('PROD-110023', 1.0205, -0.0282, '2026-09-02'), ('PROD-110023', 1.0200, -0.0500, '2026-09-03'), -('PROD-110023', 1.0200, 0.0000, '2026-09-04'), +('PROD-110023', 1.0197, -0.0259, '2026-09-04'), +('PROD-110023', 1.0192, -0.0442, '2026-09-05'), +('PROD-110023', 1.0192, -0.0029, '2026-09-06'), +('PROD-110023', 1.0196, 0.0437, '2026-09-07'), +('PROD-110023', 1.0201, 0.0444, '2026-09-08'), +('PROD-110023', 1.0197, -0.0440, '2026-09-09'), +('PROD-110023', 1.0192, -0.0447, '2026-09-10'), +('PROD-110023', 1.0194, 0.0241, '2026-09-11'), ('PROD-005827', 0.9359, 0.0478, '2025-09-05'), ('PROD-005827', 0.9363, 0.0414, '2025-09-06'), ('PROD-005827', 0.9358, -0.0487, '2025-09-07'), @@ -992,7 +1006,9 @@ INSERT INTO core_product_nav (product_id, nav, daily_chg_pct, nav_date) VALUES ('PROD-005827', 0.9479, -0.0500, '2026-05-15'), ('PROD-005827', 0.9485, 0.0628, '2026-05-16'), ('PROD-005827', 0.9480, -0.0527, '2026-05-17'), -('PROD-005827', 0.9486, 0.0604, '2026-05-18'), +('PROD-005827', 0.9486, 0.0604, '2026-05-18') ON DUPLICATE KEY UPDATE nav = VALUES(nav), daily_chg_pct = VALUES(daily_chg_pct); + +INSERT INTO core_product_nav (product_id, nav, daily_chg_pct, nav_date) VALUES ('PROD-005827', 0.9482, -0.0382, '2026-05-19'), ('PROD-005827', 0.9484, 0.0198, '2026-05-20'), ('PROD-005827', 0.9491, 0.0691, '2026-05-21'), @@ -1006,9 +1022,7 @@ INSERT INTO core_product_nav (product_id, nav, daily_chg_pct, nav_date) VALUES ('PROD-005827', 0.9510, 0.0376, '2026-05-29'), ('PROD-005827', 0.9506, -0.0473, '2026-05-30'), ('PROD-005827', 0.9504, -0.0251, '2026-05-31'), -('PROD-005827', 0.9508, 0.0412, '2026-06-01'); - -INSERT INTO core_product_nav (product_id, nav, daily_chg_pct, nav_date) VALUES +('PROD-005827', 0.9508, 0.0412, '2026-06-01'), ('PROD-005827', 0.9502, -0.0666, '2026-06-02'), ('PROD-005827', 0.9503, 0.0109, '2026-06-03'), ('PROD-005827', 0.9500, -0.0288, '2026-06-04'), @@ -1103,7 +1117,14 @@ INSERT INTO core_product_nav (product_id, nav, daily_chg_pct, nav_date) VALUES ('PROD-005827', 0.9484, 0.0314, '2026-09-01'), ('PROD-005827', 0.9489, 0.0479, '2026-09-02'), ('PROD-005827', 0.9494, 0.0498, '2026-09-03'), -('PROD-005827', 0.9500, 0.0632, '2026-09-04'), +('PROD-005827', 0.9495, 0.0090, '2026-09-04'), +('PROD-005827', 0.9489, -0.0635, '2026-09-05'), +('PROD-005827', 0.9491, 0.0205, '2026-09-06'), +('PROD-005827', 0.9488, -0.0320, '2026-09-07'), +('PROD-005827', 0.9484, -0.0467, '2026-09-08'), +('PROD-005827', 0.9490, 0.0649, '2026-09-09'), +('PROD-005827', 0.9486, -0.0472, '2026-09-10'), +('PROD-005827', 0.9487, 0.0130, '2026-09-11'), ('PROD-005828', 1.0162, 0.0296, '2025-09-05'), ('PROD-005828', 1.0166, 0.0376, '2025-09-06'), ('PROD-005828', 1.0171, 0.0459, '2025-09-07'), @@ -1468,7 +1489,14 @@ INSERT INTO core_product_nav (product_id, nav, daily_chg_pct, nav_date) VALUES ('PROD-005828', 1.0201, -0.0525, '2026-09-01'), ('PROD-005828', 1.0205, 0.0407, '2026-09-02'), ('PROD-005828', 1.0206, 0.0126, '2026-09-03'), -('PROD-005828', 1.0200, -0.0588, '2026-09-04'), +('PROD-005828', 1.0208, 0.0177, '2026-09-04'), +('PROD-005828', 1.0209, 0.0138, '2026-09-05'), +('PROD-005828', 1.0206, -0.0285, '2026-09-06'), +('PROD-005828', 1.0210, 0.0432, '2026-09-07'), +('PROD-005828', 1.0212, 0.0244, '2026-09-08'), +('PROD-005828', 1.0206, -0.0548, '2026-09-09'), +('PROD-005828', 1.0201, -0.0456, '2026-09-10'), +('PROD-005828', 1.0200, -0.0072, '2026-09-11'), ('PROD-161725', 1.0574, -0.0469, '2025-09-05'), ('PROD-161725', 1.0571, -0.0282, '2025-09-06'), ('PROD-161725', 1.0563, -0.0775, '2025-09-07'), @@ -1480,7 +1508,9 @@ INSERT INTO core_product_nav (product_id, nav, daily_chg_pct, nav_date) VALUES ('PROD-161725', 1.0557, 0.0195, '2025-09-13'), ('PROD-161725', 1.0561, 0.0353, '2025-09-14'), ('PROD-161725', 1.0561, 0.0045, '2025-09-15'), -('PROD-161725', 1.0555, -0.0606, '2025-09-16'), +('PROD-161725', 1.0555, -0.0606, '2025-09-16') ON DUPLICATE KEY UPDATE nav = VALUES(nav), daily_chg_pct = VALUES(daily_chg_pct); + +INSERT INTO core_product_nav (product_id, nav, daily_chg_pct, nav_date) VALUES ('PROD-161725', 1.0558, 0.0321, '2025-09-17'), ('PROD-161725', 1.0557, -0.0056, '2025-09-18'), ('PROD-161725', 1.0566, 0.0863, '2025-09-19'), @@ -1508,9 +1538,7 @@ INSERT INTO core_product_nav (product_id, nav, daily_chg_pct, nav_date) VALUES ('PROD-161725', 1.0528, -0.0388, '2025-10-11'), ('PROD-161725', 1.0518, -0.0909, '2025-10-12'), ('PROD-161725', 1.0529, 0.1017, '2025-10-13'), -('PROD-161725', 1.0521, -0.0779, '2025-10-14'); - -INSERT INTO core_product_nav (product_id, nav, daily_chg_pct, nav_date) VALUES +('PROD-161725', 1.0521, -0.0779, '2025-10-14'), ('PROD-161725', 1.0512, -0.0865, '2025-10-15'), ('PROD-161725', 1.0516, 0.0383, '2025-10-16'), ('PROD-161725', 1.0510, -0.0553, '2025-10-17'), @@ -1835,7 +1863,14 @@ INSERT INTO core_product_nav (product_id, nav, daily_chg_pct, nav_date) VALUES ('PROD-161725', 1.0494, -0.0607, '2026-09-01'), ('PROD-161725', 1.0493, -0.0056, '2026-09-02'), ('PROD-161725', 1.0492, -0.0075, '2026-09-03'), -('PROD-161725', 1.0500, 0.0762, '2026-09-04'), +('PROD-161725', 1.0497, 0.0491, '2026-09-04'), +('PROD-161725', 1.0494, -0.0242, '2026-09-05'), +('PROD-161725', 1.0503, 0.0874, '2026-09-06'), +('PROD-161725', 1.0512, 0.0825, '2026-09-07'), +('PROD-161725', 1.0518, 0.0607, '2026-09-08'), +('PROD-161725', 1.0519, 0.0126, '2026-09-09'), +('PROD-161725', 1.0521, 0.0194, '2026-09-10'), +('PROD-161725', 1.0524, 0.0309, '2026-09-11'), ('PROD-161726', 1.0439, -0.0260, '2025-09-05'), ('PROD-161726', 1.0429, -0.0980, '2025-09-06'), ('PROD-161726', 1.0429, -0.0039, '2025-09-07'), @@ -1975,7 +2010,9 @@ INSERT INTO core_product_nav (product_id, nav, daily_chg_pct, nav_date) VALUES ('PROD-161726', 1.0529, -0.0999, '2026-01-19'), ('PROD-161726', 1.0525, -0.0418, '2026-01-20'), ('PROD-161726', 1.0534, 0.0897, '2026-01-21'), -('PROD-161726', 1.0524, -0.0924, '2026-01-22'), +('PROD-161726', 1.0524, -0.0924, '2026-01-22') ON DUPLICATE KEY UPDATE nav = VALUES(nav), daily_chg_pct = VALUES(daily_chg_pct); + +INSERT INTO core_product_nav (product_id, nav, daily_chg_pct, nav_date) VALUES ('PROD-161726', 1.0526, 0.0236, '2026-01-23'), ('PROD-161726', 1.0519, -0.0696, '2026-01-24'), ('PROD-161726', 1.0525, 0.0543, '2026-01-25'), @@ -2010,9 +2047,7 @@ INSERT INTO core_product_nav (product_id, nav, daily_chg_pct, nav_date) VALUES ('PROD-161726', 1.0542, -0.0285, '2026-02-23'), ('PROD-161726', 1.0551, 0.0900, '2026-02-24'), ('PROD-161726', 1.0542, -0.0898, '2026-02-25'), -('PROD-161726', 1.0546, 0.0425, '2026-02-26'); - -INSERT INTO core_product_nav (product_id, nav, daily_chg_pct, nav_date) VALUES +('PROD-161726', 1.0546, 0.0425, '2026-02-26'), ('PROD-161726', 1.0556, 0.0915, '2026-02-27'), ('PROD-161726', 1.0556, 0.0008, '2026-02-28'), ('PROD-161726', 1.0553, -0.0307, '2026-03-01'), @@ -2202,7 +2237,14 @@ INSERT INTO core_product_nav (product_id, nav, daily_chg_pct, nav_date) VALUES ('PROD-161726', 1.0490, 0.0790, '2026-09-01'), ('PROD-161726', 1.0497, 0.0695, '2026-09-02'), ('PROD-161726', 1.0491, -0.0558, '2026-09-03'), -('PROD-161726', 1.0500, 0.0858, '2026-09-04'), +('PROD-161726', 1.0497, 0.0598, '2026-09-04'), +('PROD-161726', 1.0495, -0.0215, '2026-09-05'), +('PROD-161726', 1.0502, 0.0698, '2026-09-06'), +('PROD-161726', 1.0498, -0.0338, '2026-09-07'), +('PROD-161726', 1.0505, 0.0628, '2026-09-08'), +('PROD-161726', 1.0508, 0.0312, '2026-09-09'), +('PROD-161726', 1.0512, 0.0338, '2026-09-10'), +('PROD-161726', 1.0514, 0.0173, '2026-09-11'), ('PROD-003095', 0.9521, -0.0888, '2025-09-05'), ('PROD-003095', 0.9520, -0.0105, '2025-09-06'), ('PROD-003095', 0.9519, -0.0068, '2025-09-07'), @@ -2470,7 +2512,9 @@ INSERT INTO core_product_nav (product_id, nav, daily_chg_pct, nav_date) VALUES ('PROD-003095', 0.9456, -0.0847, '2026-05-27'), ('PROD-003095', 0.9465, 0.0934, '2026-05-28'), ('PROD-003095', 0.9463, -0.0186, '2026-05-29'), -('PROD-003095', 0.9468, 0.0554, '2026-05-30'), +('PROD-003095', 0.9468, 0.0554, '2026-05-30') ON DUPLICATE KEY UPDATE nav = VALUES(nav), daily_chg_pct = VALUES(daily_chg_pct); + +INSERT INTO core_product_nav (product_id, nav, daily_chg_pct, nav_date) VALUES ('PROD-003095', 0.9470, 0.0222, '2026-05-31'), ('PROD-003095', 0.9467, -0.0319, '2026-06-01'), ('PROD-003095', 0.9463, -0.0412, '2026-06-02'), @@ -2512,9 +2556,7 @@ INSERT INTO core_product_nav (product_id, nav, daily_chg_pct, nav_date) VALUES ('PROD-003095', 0.9516, -0.0445, '2026-07-08'), ('PROD-003095', 0.9510, -0.0670, '2026-07-09'), ('PROD-003095', 0.9511, 0.0145, '2026-07-10'), -('PROD-003095', 0.9516, 0.0516, '2026-07-11'); - -INSERT INTO core_product_nav (product_id, nav, daily_chg_pct, nav_date) VALUES +('PROD-003095', 0.9516, 0.0516, '2026-07-11'), ('PROD-003095', 0.9521, 0.0517, '2026-07-12'), ('PROD-003095', 0.9512, -0.0910, '2026-07-13'), ('PROD-003095', 0.9520, 0.0795, '2026-07-14'), @@ -2569,7 +2611,14 @@ INSERT INTO core_product_nav (product_id, nav, daily_chg_pct, nav_date) VALUES ('PROD-003095', 0.9511, 0.0636, '2026-09-01'), ('PROD-003095', 0.9504, -0.0713, '2026-09-02'), ('PROD-003095', 0.9507, 0.0276, '2026-09-03'), -('PROD-003095', 0.9500, -0.0736, '2026-09-04'), +('PROD-003095', 0.9499, -0.0872, '2026-09-04'), +('PROD-003095', 0.9498, -0.0094, '2026-09-05'), +('PROD-003095', 0.9503, 0.0548, '2026-09-06'), +('PROD-003095', 0.9502, -0.0061, '2026-09-07'), +('PROD-003095', 0.9507, 0.0524, '2026-09-08'), +('PROD-003095', 0.9505, -0.0219, '2026-09-09'), +('PROD-003095', 0.9503, -0.0241, '2026-09-10'), +('PROD-003095', 0.9501, -0.0238, '2026-09-11'), ('PROD-510300', 1.0282, -0.0655, '2025-09-05'), ('PROD-510300', 1.0281, -0.0116, '2025-09-06'), ('PROD-510300', 1.0284, 0.0324, '2025-09-07'), @@ -2934,7 +2983,14 @@ INSERT INTO core_product_nav (product_id, nav, daily_chg_pct, nav_date) VALUES ('PROD-510300', 1.0284, -0.0471, '2026-09-01'), ('PROD-510300', 1.0284, 0.0026, '2026-09-02'), ('PROD-510300', 1.0289, 0.0463, '2026-09-03'), -('PROD-510300', 1.0300, 0.1069, '2026-09-04'), +('PROD-510300', 1.0293, 0.0380, '2026-09-04'), +('PROD-510300', 1.0290, -0.0299, '2026-09-05'), +('PROD-510300', 1.0295, 0.0528, '2026-09-06'), +('PROD-510300', 1.0294, -0.0073, '2026-09-07'), +('PROD-510300', 1.0290, -0.0436, '2026-09-08'), +('PROD-510300', 1.0285, -0.0503, '2026-09-09'), +('PROD-510300', 1.0283, -0.0176, '2026-09-10'), +('PROD-510300', 1.0280, -0.0303, '2026-09-11'), ('PROD-510500', 0.9841, -0.0745, '2025-09-05'), ('PROD-510500', 0.9837, -0.0367, '2025-09-06'), ('PROD-510500', 0.9844, 0.0667, '2025-09-07'), @@ -2958,7 +3014,9 @@ INSERT INTO core_product_nav (product_id, nav, daily_chg_pct, nav_date) VALUES ('PROD-510500', 0.9855, 0.0179, '2025-09-25'), ('PROD-510500', 0.9854, -0.0071, '2025-09-26'), ('PROD-510500', 0.9855, 0.0136, '2025-09-27'), -('PROD-510500', 0.9863, 0.0766, '2025-09-28'), +('PROD-510500', 0.9863, 0.0766, '2025-09-28') ON DUPLICATE KEY UPDATE nav = VALUES(nav), daily_chg_pct = VALUES(daily_chg_pct); + +INSERT INTO core_product_nav (product_id, nav, daily_chg_pct, nav_date) VALUES ('PROD-510500', 0.9872, 0.0876, '2025-09-29'), ('PROD-510500', 0.9871, -0.0094, '2025-09-30'), ('PROD-510500', 0.9867, -0.0355, '2025-10-01'), @@ -3014,9 +3072,7 @@ INSERT INTO core_product_nav (product_id, nav, daily_chg_pct, nav_date) VALUES ('PROD-510500', 0.9857, -0.0755, '2025-11-20'), ('PROD-510500', 0.9855, -0.0157, '2025-11-21'), ('PROD-510500', 0.9847, -0.0779, '2025-11-22'), -('PROD-510500', 0.9851, 0.0379, '2025-11-23'); - -INSERT INTO core_product_nav (product_id, nav, daily_chg_pct, nav_date) VALUES +('PROD-510500', 0.9851, 0.0379, '2025-11-23'), ('PROD-510500', 0.9844, -0.0673, '2025-11-24'), ('PROD-510500', 0.9845, 0.0143, '2025-11-25'), ('PROD-510500', 0.9842, -0.0353, '2025-11-26'), @@ -3301,7 +3357,14 @@ INSERT INTO core_product_nav (product_id, nav, daily_chg_pct, nav_date) VALUES ('PROD-510500', 0.9803, 0.0860, '2026-09-01'), ('PROD-510500', 0.9804, 0.0075, '2026-09-02'), ('PROD-510500', 0.9798, -0.0565, '2026-09-03'), -('PROD-510500', 0.9800, 0.0204, '2026-09-04'), +('PROD-510500', 0.9805, 0.0716, '2026-09-04'), +('PROD-510500', 0.9806, 0.0112, '2026-09-05'), +('PROD-510500', 0.9803, -0.0330, '2026-09-06'), +('PROD-510500', 0.9805, 0.0237, '2026-09-07'), +('PROD-510500', 0.9811, 0.0613, '2026-09-08'), +('PROD-510500', 0.9809, -0.0181, '2026-09-09'), +('PROD-510500', 0.9804, -0.0487, '2026-09-10'), +('PROD-510500', 0.9802, -0.0174, '2026-09-11'), ('PROD-XYZ999', 0.9064, -0.1494, '2025-09-05'), ('PROD-XYZ999', 0.9058, -0.0665, '2025-09-06'), ('PROD-XYZ999', 0.9046, -0.1313, '2025-09-07'), @@ -3453,7 +3516,9 @@ INSERT INTO core_product_nav (product_id, nav, daily_chg_pct, nav_date) VALUES ('PROD-XYZ999', 0.9040, 0.0529, '2026-01-31'), ('PROD-XYZ999', 0.9036, -0.0489, '2026-02-01'), ('PROD-XYZ999', 0.9038, 0.0223, '2026-02-02'), -('PROD-XYZ999', 0.9047, 0.1032, '2026-02-03'), +('PROD-XYZ999', 0.9047, 0.1032, '2026-02-03') ON DUPLICATE KEY UPDATE nav = VALUES(nav), daily_chg_pct = VALUES(daily_chg_pct); + +INSERT INTO core_product_nav (product_id, nav, daily_chg_pct, nav_date) VALUES ('PROD-XYZ999', 0.9055, 0.0915, '2026-02-04'), ('PROD-XYZ999', 0.9052, -0.0370, '2026-02-05'), ('PROD-XYZ999', 0.9055, 0.0305, '2026-02-06'), @@ -3516,9 +3581,7 @@ INSERT INTO core_product_nav (product_id, nav, daily_chg_pct, nav_date) VALUES ('PROD-XYZ999', 0.9075, 0.0194, '2026-04-04'), ('PROD-XYZ999', 0.9084, 0.1034, '2026-04-05'), ('PROD-XYZ999', 0.9079, -0.0529, '2026-04-06'), -('PROD-XYZ999', 0.9074, -0.0502, '2026-04-07'); - -INSERT INTO core_product_nav (product_id, nav, daily_chg_pct, nav_date) VALUES +('PROD-XYZ999', 0.9074, -0.0502, '2026-04-07'), ('PROD-XYZ999', 0.9061, -0.1416, '2026-04-08'), ('PROD-XYZ999', 0.9059, -0.0212, '2026-04-09'), ('PROD-XYZ999', 0.9061, 0.0248, '2026-04-10'), @@ -3668,7 +3731,14 @@ INSERT INTO core_product_nav (product_id, nav, daily_chg_pct, nav_date) VALUES ('PROD-XYZ999', 0.9003, -0.0622, '2026-09-01'), ('PROD-XYZ999', 0.8992, -0.1269, '2026-09-02'), ('PROD-XYZ999', 0.8992, -0.0029, '2026-09-03'), -('PROD-XYZ999', 0.9000, 0.0890, '2026-09-04'), +('PROD-XYZ999', 0.8999, 0.0771, '2026-09-04'), +('PROD-XYZ999', 0.9000, 0.0066, '2026-09-05'), +('PROD-XYZ999', 0.8988, -0.1304, '2026-09-06'), +('PROD-XYZ999', 0.8992, 0.0477, '2026-09-07'), +('PROD-XYZ999', 0.9000, 0.0846, '2026-09-08'), +('PROD-XYZ999', 0.8989, -0.1263, '2026-09-09'), +('PROD-XYZ999', 0.8986, -0.0373, '2026-09-10'), +('PROD-XYZ999', 0.8978, -0.0905, '2026-09-11'), ('PROD-000001', 1.0012, 0.0076, '2025-09-05'), ('PROD-000001', 1.0012, -0.0009, '2025-09-06'), ('PROD-000001', 1.0012, 0.0028, '2025-09-07'), @@ -3948,7 +4018,9 @@ INSERT INTO core_product_nav (product_id, nav, daily_chg_pct, nav_date) VALUES ('PROD-000001', 1.0020, 0.0057, '2026-06-08'), ('PROD-000001', 1.0020, -0.0017, '2026-06-09'), ('PROD-000001', 1.0020, 0.0036, '2026-06-10'), -('PROD-000001', 1.0021, 0.0075, '2026-06-11'), +('PROD-000001', 1.0021, 0.0075, '2026-06-11') ON DUPLICATE KEY UPDATE nav = VALUES(nav), daily_chg_pct = VALUES(daily_chg_pct); + +INSERT INTO core_product_nav (product_id, nav, daily_chg_pct, nav_date) VALUES ('PROD-000001', 1.0021, 0.0029, '2026-06-12'), ('PROD-000001', 1.0021, 0.0011, '2026-06-13'), ('PROD-000001', 1.0020, -0.0051, '2026-06-14'), @@ -4018,9 +4090,7 @@ INSERT INTO core_product_nav (product_id, nav, daily_chg_pct, nav_date) VALUES ('PROD-000001', 1.0033, -0.0001, '2026-08-17'), ('PROD-000001', 1.0033, -0.0028, '2026-08-18'), ('PROD-000001', 1.0033, -0.0043, '2026-08-19'), -('PROD-000001', 1.0033, 0.0001, '2026-08-20'); - -INSERT INTO core_product_nav (product_id, nav, daily_chg_pct, nav_date) VALUES +('PROD-000001', 1.0033, 0.0001, '2026-08-20'), ('PROD-000001', 1.0034, 0.0062, '2026-08-21'), ('PROD-000001', 1.0034, 0.0019, '2026-08-22'), ('PROD-000001', 1.0034, 0.0042, '2026-08-23'), @@ -4035,7 +4105,14 @@ INSERT INTO core_product_nav (product_id, nav, daily_chg_pct, nav_date) VALUES ('PROD-000001', 1.0034, -0.0076, '2026-09-01'), ('PROD-000001', 1.0033, -0.0073, '2026-09-02'), ('PROD-000001', 1.0033, 0.0007, '2026-09-03'), -('PROD-000001', 1.0020, -0.1296, '2026-09-04'), +('PROD-000001', 1.0033, -0.0032, '2026-09-04'), +('PROD-000001', 1.0032, -0.0079, '2026-09-05'), +('PROD-000001', 1.0032, 0.0006, '2026-09-06'), +('PROD-000001', 1.0032, -0.0011, '2026-09-07'), +('PROD-000001', 1.0032, -0.0042, '2026-09-08'), +('PROD-000001', 1.0031, -0.0058, '2026-09-09'), +('PROD-000001', 1.0030, -0.0060, '2026-09-10'), +('PROD-000001', 1.0030, -0.0027, '2026-09-11'), ('PROD-000002', 1.0074, 0.0236, '2025-09-05'), ('PROD-000002', 1.0072, -0.0178, '2025-09-06'), ('PROD-000002', 1.0073, 0.0066, '2025-09-07'), @@ -4400,7 +4477,14 @@ INSERT INTO core_product_nav (product_id, nav, daily_chg_pct, nav_date) VALUES ('PROD-000002', 1.0045, 0.0184, '2026-09-01'), ('PROD-000002', 1.0042, -0.0264, '2026-09-02'), ('PROD-000002', 1.0041, -0.0070, '2026-09-03'), -('PROD-000002', 1.0050, 0.0896, '2026-09-04'), +('PROD-000002', 1.0044, 0.0296, '2026-09-04'), +('PROD-000002', 1.0043, -0.0069, '2026-09-05'), +('PROD-000002', 1.0040, -0.0282, '2026-09-06'), +('PROD-000002', 1.0039, -0.0117, '2026-09-07'), +('PROD-000002', 1.0038, -0.0121, '2026-09-08'), +('PROD-000002', 1.0037, -0.0111, '2026-09-09'), +('PROD-000002', 1.0036, -0.0073, '2026-09-10'), +('PROD-000002', 1.0035, -0.0126, '2026-09-11'), ('PROD-WMG001', 1.0113, 0.0098, '2025-09-05'), ('PROD-WMG001', 1.0114, 0.0077, '2025-09-06'), ('PROD-WMG001', 1.0114, -0.0015, '2025-09-07'), @@ -4436,7 +4520,9 @@ INSERT INTO core_product_nav (product_id, nav, daily_chg_pct, nav_date) VALUES ('PROD-WMG001', 1.0127, 0.0046, '2025-10-07'), ('PROD-WMG001', 1.0127, 0.0033, '2025-10-08'), ('PROD-WMG001', 1.0130, 0.0293, '2025-10-09'), -('PROD-WMG001', 1.0129, -0.0069, '2025-10-10'), +('PROD-WMG001', 1.0129, -0.0069, '2025-10-10') ON DUPLICATE KEY UPDATE nav = VALUES(nav), daily_chg_pct = VALUES(daily_chg_pct); + +INSERT INTO core_product_nav (product_id, nav, daily_chg_pct, nav_date) VALUES ('PROD-WMG001', 1.0129, -0.0045, '2025-10-11'), ('PROD-WMG001', 1.0132, 0.0301, '2025-10-12'), ('PROD-WMG001', 1.0131, -0.0148, '2025-10-13'), @@ -4520,9 +4606,7 @@ INSERT INTO core_product_nav (product_id, nav, daily_chg_pct, nav_date) VALUES ('PROD-WMG001', 1.0119, -0.0257, '2025-12-30'), ('PROD-WMG001', 1.0119, -0.0013, '2025-12-31'), ('PROD-WMG001', 1.0118, -0.0119, '2026-01-01'), -('PROD-WMG001', 1.0115, -0.0289, '2026-01-02'); - -INSERT INTO core_product_nav (product_id, nav, daily_chg_pct, nav_date) VALUES +('PROD-WMG001', 1.0115, -0.0289, '2026-01-02'), ('PROD-WMG001', 1.0115, 0.0019, '2026-01-03'), ('PROD-WMG001', 1.0117, 0.0201, '2026-01-04'), ('PROD-WMG001', 1.0116, -0.0082, '2026-01-05'), @@ -4767,7 +4851,14 @@ INSERT INTO core_product_nav (product_id, nav, daily_chg_pct, nav_date) VALUES ('PROD-WMG001', 1.0138, 0.0182, '2026-09-01'), ('PROD-WMG001', 1.0140, 0.0196, '2026-09-02'), ('PROD-WMG001', 1.0142, 0.0225, '2026-09-03'), -('PROD-WMG001', 1.0150, 0.0789, '2026-09-04'), +('PROD-WMG001', 1.0141, -0.0100, '2026-09-04'), +('PROD-WMG001', 1.0138, -0.0253, '2026-09-05'), +('PROD-WMG001', 1.0136, -0.0197, '2026-09-06'), +('PROD-WMG001', 1.0140, 0.0366, '2026-09-07'), +('PROD-WMG001', 1.0144, 0.0385, '2026-09-08'), +('PROD-WMG001', 1.0142, -0.0217, '2026-09-09'), +('PROD-WMG001', 1.0145, 0.0321, '2026-09-10'), +('PROD-WMG001', 1.0143, -0.0187, '2026-09-11'), ('PROD-PRIV01', 1.0984, 0.0591, '2025-09-05'), ('PROD-PRIV01', 1.0987, 0.0261, '2025-09-06'), ('PROD-PRIV01', 1.0979, -0.0708, '2025-09-07'), @@ -4931,7 +5022,9 @@ INSERT INTO core_product_nav (product_id, nav, daily_chg_pct, nav_date) VALUES ('PROD-PRIV01', 1.0835, 0.0063, '2026-02-12'), ('PROD-PRIV01', 1.0828, -0.0622, '2026-02-13'), ('PROD-PRIV01', 1.0836, 0.0776, '2026-02-14'), -('PROD-PRIV01', 1.0837, 0.0086, '2026-02-15'), +('PROD-PRIV01', 1.0837, 0.0086, '2026-02-15') ON DUPLICATE KEY UPDATE nav = VALUES(nav), daily_chg_pct = VALUES(daily_chg_pct); + +INSERT INTO core_product_nav (product_id, nav, daily_chg_pct, nav_date) VALUES ('PROD-PRIV01', 1.0839, 0.0227, '2026-02-16'), ('PROD-PRIV01', 1.0834, -0.0452, '2026-02-17'), ('PROD-PRIV01', 1.0833, -0.0061, '2026-02-18'), @@ -5022,9 +5115,7 @@ INSERT INTO core_product_nav (product_id, nav, daily_chg_pct, nav_date) VALUES ('PROD-PRIV01', 1.0858, -0.0001, '2026-05-14'), ('PROD-PRIV01', 1.0862, 0.0384, '2026-05-15'), ('PROD-PRIV01', 1.0867, 0.0443, '2026-05-16'), -('PROD-PRIV01', 1.0874, 0.0624, '2026-05-17'); - -INSERT INTO core_product_nav (product_id, nav, daily_chg_pct, nav_date) VALUES +('PROD-PRIV01', 1.0874, 0.0624, '2026-05-17'), ('PROD-PRIV01', 1.0868, -0.0559, '2026-05-18'), ('PROD-PRIV01', 1.0876, 0.0772, '2026-05-19'), ('PROD-PRIV01', 1.0884, 0.0692, '2026-05-20'), @@ -5134,4 +5225,11 @@ INSERT INTO core_product_nav (product_id, nav, daily_chg_pct, nav_date) VALUES ('PROD-PRIV01', 1.0807, 0.0652, '2026-09-01'), ('PROD-PRIV01', 1.0802, -0.0427, '2026-09-02'), ('PROD-PRIV01', 1.0792, -0.0919, '2026-09-03'), -('PROD-PRIV01', 1.0800, 0.0741, '2026-09-04'); +('PROD-PRIV01', 1.0792, 0.0046, '2026-09-04'), +('PROD-PRIV01', 1.0796, 0.0408, '2026-09-05'), +('PROD-PRIV01', 1.0790, -0.0558, '2026-09-06'), +('PROD-PRIV01', 1.0788, -0.0195, '2026-09-07'), +('PROD-PRIV01', 1.0781, -0.0631, '2026-09-08'), +('PROD-PRIV01', 1.0773, -0.0726, '2026-09-09'), +('PROD-PRIV01', 1.0780, 0.0609, '2026-09-10'), +('PROD-PRIV01', 1.0780, -0.0042, '2026-09-11') ON DUPLICATE KEY UPDATE nav = VALUES(nav), daily_chg_pct = VALUES(daily_chg_pct); diff --git a/scripts/core/fetch_nav.py b/scripts/core/fetch_nav.py index c1cb9e7..f8a12cc 100644 --- a/scripts/core/fetch_nav.py +++ b/scripts/core/fetch_nav.py @@ -28,9 +28,9 @@ 货币基金(`PROD-000001` 现金宝货币) ------------------------------------------------------------------ -真实业务中货币基金**单位净值恒为 1.0000 元**,收益以份额结转,不存在净值序列 -(实测:天弘余额宝 000198、广发钱袋子 000509 在该接口均无 `Data_netWorthTrend`)。 -故货基不联网,直接按真实业务生成恒定 1.0000、日涨幅 0。 +真实业务中货基**展示**单位净值常为 1.0000,但该接口也无可用 `Data_netWorthTrend`。 +演示库不与 06 写死平线:货基净值与 `seed_nav_history.py` / `07-seed-nav-history.sql` +同源(锚定 1.0020 附近、低 beta 微波动),便于行情页看到走势与日涨跌。 用法 ------------------------------------------------------------------ @@ -74,15 +74,20 @@ NAV_SOURCE: dict[str, tuple[str, str, str]] = { "PROD-PRIV01": ("519062", "海富通阿尔法对冲混合A", "private_fund/R4:量化对冲,日波动 0.33%"), } -# 货币基金:净值恒为 1.0000(真实业务,不联网) +# 货币基金:不联网,曲线与 seed_nav_history / 07-seed 同源 MONEY_FUNDS: dict[str, str] = { - "PROD-000001": "现金宝货币(money/R1:货基单位净值恒为 1 元,收益以份额结转)", + "PROD-000001": "现金宝货币(money/R1:演示用低波净值,见 seed_nav_history.py)", } DEFAULT_OUT = Path(__file__).with_name("06-seed-nav.sql") DEFAULT_DAYS = 60 TIMEOUT = 30 +_SCRIPT_DIR = Path(__file__).resolve().parent +if str(_SCRIPT_DIR) not in sys.path: + sys.path.insert(0, str(_SCRIPT_DIR)) +from seed_nav_history import nav_rows_for_product_dates # noqa: E402 + # ── 抓取与解析 ─────────────────────────────────────────────────────────── def fetch_js(code: str, cache_dir: Path | None) -> str: @@ -119,7 +124,10 @@ def build_sql(rows: list[tuple[str, dt.date, float, float]], fetched_at: str) -> f"-- {pid:<13} <- {code} {name}({why})" for pid, (code, name, why) in sorted(NAV_SOURCE.items()) ) - money = "\n".join(f"-- {pid:<13} <- 恒定 1.0000({why})" for pid, why in sorted(MONEY_FUNDS.items())) + money = "\n".join( + f"-- {pid:<13} <- 演示曲线({why};与 07-seed-nav-history 同源)" + for pid, why in sorted(MONEY_FUNDS.items()) + ) dates = [r[1] for r in rows] head = ( "USE jinrong_core;\n\n" @@ -185,13 +193,13 @@ def main(argv: list[str] | None = None) -> int: rows.append((pid, day, nav, chg)) print(f"[OK] {pid} <- {code} {name}:{len(series[-args.days:])} 条,最新 {series[-1][0]} {series[-1][1]}") - # 货币基金:净值恒定 1.0000 + # 货币基金:与 07 种子同源的低波演示曲线(勿再写死 1.0000/0) if rows: money_days = sorted({r[1] for r in rows}) for pid in sorted(MONEY_FUNDS): - for day in money_days: - rows.append((pid, day, 1.0000, 0.0)) - print(f"[OK] {pid} <- 货基恒定净值 1.0000:{len(money_days)} 条") + sim = nav_rows_for_product_dates(pid, money_days) + rows.extend(sim) + print(f"[OK] {pid} <- 货基演示曲线(seed_nav_history):{len(sim)} 条") if failed: print("\n[FAIL] 以下产品拉取失败:", file=sys.stderr) diff --git a/scripts/core/seed_nav_history.py b/scripts/core/seed_nav_history.py index 6d9126c..43d27ae 100644 --- a/scripts/core/seed_nav_history.py +++ b/scripts/core/seed_nav_history.py @@ -25,6 +25,8 @@ from sqlalchemy import text from app.config.database import get_core_engine ANCHOR_DATE = date(2026, 9, 4) +# 与 06-seed-nav(fetch_nav --days 60)末日对齐,锚定日后继续微波动以免被 06 平线覆盖 +LATEST_NAV_DATE = date(2026, 9, 11) DAYS_BACK = 364 # 与锚定日合计约 365 个自然日 SQL_OUT = Path(__file__).resolve().parent / "07-seed-nav-history.sql" @@ -96,12 +98,8 @@ def build_history_rows() -> list[dict]: running = round(nav, 4) for i, d in enumerate(days_list): if i > 0: - if d == ANCHOR_DATE: - chg = round(((anchor_nav - running) / running) * 100, 4) - running = anchor_nav - else: - chg = _product_daily_chg_pct(product_id, d) - running = round(running * (1 + chg / 100.0), 4) + chg = _product_daily_chg_pct(product_id, d) + running = round(running * (1 + chg / 100.0), 4) else: chg = _product_daily_chg_pct(product_id, d) rows.append( @@ -112,9 +110,37 @@ def build_history_rows() -> list[dict]: "daily_chg_pct": chg, } ) + d = ANCHOR_DATE + while d < LATEST_NAV_DATE: + d = d + timedelta(days=1) + chg = _product_daily_chg_pct(product_id, d) + running = round(running * (1 + chg / 100.0), 4) + rows.append( + { + "product_id": product_id, + "nav_date": d.isoformat(), + "nav": running, + "daily_chg_pct": chg, + } + ) return rows +def nav_rows_for_product_dates( + product_id: str, days: list[date], *, rows: list[dict] | None = None +) -> list[tuple[str, date, float, float]]: + """按自然日取单产品净值(供 fetch_nav 货基演示曲线,与 07 同源)。""" + rows = rows or build_history_rows() + index = {(r["product_id"], r["nav_date"]): r for r in rows} + out: list[tuple[str, date, float, float]] = [] + for day in days: + r = index.get((product_id, day.isoformat())) + if not r: + continue + out.append((product_id, day, float(r["nav"]), float(r["daily_chg_pct"]))) + return out + + def anchor_rows_for_seed06(rows: list[dict] | None = None) -> list[dict]: rows = rows or build_history_rows() anchor = ANCHOR_DATE.isoformat() @@ -150,7 +176,10 @@ def export_sql(path: Path = SQL_OUT, *, batch_size: int = 500) -> int: f"('{r['product_id']}', {r['nav']:.4f}, {r['daily_chg_pct']:.4f}, '{r['nav_date']}')" for r in chunk ] - lines.append(",\n".join(value_lines) + ";") + lines.append( + ",\n".join(value_lines) + + " ON DUPLICATE KEY UPDATE nav = VALUES(nav), daily_chg_pct = VALUES(daily_chg_pct);" + ) lines.append("") path.write_text("\n".join(lines), encoding="utf-8") return len(rows) diff --git a/tests/test_platform_common.py b/tests/test_platform_common.py new file mode 100644 index 0000000..058e711 --- /dev/null +++ b/tests/test_platform_common.py @@ -0,0 +1,11 @@ +from decimal import Decimal + +from app.service.platform.common import to_jsonable + + +def test_to_jsonable_nav_keeps_four_decimals(): + row = {"nav": Decimal("1.0017"), "daily_chg_pct": Decimal("-0.0027"), "amount": Decimal("1234.567")} + out = to_jsonable(row) + assert out["nav"] == 1.0017 + assert out["daily_chg_pct"] == -0.0027 + assert out["amount"] == 1234.57 diff --git a/web/src/App.tsx b/web/src/App.tsx index 4cd34c9..6d91a11 100644 --- a/web/src/App.tsx +++ b/web/src/App.tsx @@ -24,6 +24,7 @@ import { AdvisorKycPage } from './pages/advisor/AdvisorKycPage' import { AdvisorMarketAlertsPage } from './pages/advisor/AdvisorMarketAlertsPage' import { MarketQuotesPage } from './pages/market/MarketQuotesPage' import { ProductNavDetailPage } from './pages/market/ProductNavDetailPage' +import { KnowledgeBasePage } from './pages/knowledge/KnowledgeBasePage' import { RiskAlertsPage } from './pages/risk/RiskAlertsPage' import { RiskChatPage } from './pages/risk/RiskChatPage' import { RiskSimulatePage } from './pages/risk/RiskSimulatePage' @@ -144,6 +145,8 @@ export default function AppRoutes() { } /> } /> + } /> + } /> } /> diff --git a/web/src/api/kb.ts b/web/src/api/kb.ts new file mode 100644 index 0000000..e6011c4 --- /dev/null +++ b/web/src/api/kb.ts @@ -0,0 +1,24 @@ +import { apiFetch } from './client' + +export type VectorCollection = { + name: string + title: string + description: string + row_count: number + dimension: number + fields: string[] + sample: Record[] +} + +export type VectorCollectionsResponse = { + items: VectorCollection[] + total_collections: number + total_vectors: number +} + +export async function listVectorCollections(token: string) { + const { data } = await apiFetch('/api/milvus/collections', { + token, + }) + return data +} diff --git a/web/src/pages/knowledge/KnowledgeBasePage.tsx b/web/src/pages/knowledge/KnowledgeBasePage.tsx new file mode 100644 index 0000000..b04f36a --- /dev/null +++ b/web/src/pages/knowledge/KnowledgeBasePage.tsx @@ -0,0 +1,145 @@ +import { Alert, Spin, Table, Tag, Typography } from 'antd' +import type { ColumnsType } from 'antd/es/table' +import { useEffect, useState } from 'react' +import { listVectorCollections, type VectorCollection } from '../../api/kb' +import { ApiErrorResult } from '../../components/ApiErrorResult' +import { PageShell } from '../../components/PageShell' +import { EmptyState, MetricCard, Surface } from '../../components/ui' +import { useAppAuth } from '../../layouts/AppLayout' + +function formatCell(value: unknown): string { + if (value === null || value === undefined || value === '') return '—' + if (typeof value === 'boolean') return value ? '是' : '否' + if (typeof value === 'object') return JSON.stringify(value) + return String(value) +} + +function SampleTable({ collection }: { collection: VectorCollection }) { + if (!collection.sample.length) { + return 暂无样本(该库为空) + } + const columns: ColumnsType> = collection.fields.map((f) => ({ + title: f, + dataIndex: f, + ellipsis: true, + render: (v: unknown) => ( + {formatCell(v)} + ), + })) + return ( + > + rowKey="id" + size="small" + columns={columns} + dataSource={collection.sample} + pagination={false} + scroll={{ x: 'max-content' }} + title={() => ( + + 样本预览(最多 {collection.sample.length} 条) + + )} + /> + ) +} + +export function KnowledgeBasePage() { + const auth = useAppAuth() + const [items, setItems] = useState([]) + const [totals, setTotals] = useState({ total_collections: 0, total_vectors: 0 }) + const [loading, setLoading] = useState(true) + const [error, setError] = useState(null) + const [reload, setReload] = useState(0) + + useEffect(() => { + let cancelled = false + setLoading(true) + setError(null) + listVectorCollections(auth.accessToken) + .then((data) => { + if (cancelled) return + setItems(data.items ?? []) + setTotals({ + total_collections: data.total_collections ?? 0, + total_vectors: data.total_vectors ?? 0, + }) + }) + .catch((e) => { + if (!cancelled) setError(e instanceof Error ? e : new Error('load failed')) + }) + .finally(() => { + if (!cancelled) setLoading(false) + }) + return () => { + cancelled = true + } + }, [auth.accessToken, reload]) + + return ( + + + + + + + + + {error && ( + { + setError(null) + setReload((n) => n + 1) + }} + /> + )} + + {!error && ( + + {items.length === 0 && !loading ? ( + + + + ) : ( + items.map((c) => ( + + + + {c.title} + + {c.name} + {c.row_count} 条 + {c.dimension} 维 + + + {c.description} + + + + )) + )} + + )} + + ) +} diff --git a/web/src/routes/menus.tsx b/web/src/routes/menus.tsx index c679b2e..9e675c0 100644 --- a/web/src/routes/menus.tsx +++ b/web/src/routes/menus.tsx @@ -3,6 +3,7 @@ import { BarChartOutlined, CheckCircleOutlined, CommentOutlined, + DatabaseOutlined, FileTextOutlined, FormOutlined, HomeOutlined, @@ -32,6 +33,7 @@ export type RouteMenuGroup = { const sharedPlatform: RouteMenuLeaf[] = [ { key: '/app/market', icon: , label: '产品行情', path: '/app/market' }, { key: '/app/analytics/query', icon: , label: '数据分析', path: '/app/analytics/query' }, + { key: '/app/knowledge', icon: , label: '向量知识库', path: '/app/knowledge' }, ] export function buildMenuGroups(roleLabel: string): RouteMenuGroup[] { @@ -116,7 +118,10 @@ export function buildMenuGroups(roleLabel: string): RouteMenuGroup[] { { type: 'group', label: '平台服务', - children: [{ key: '/app/market', icon: , label: '产品行情', path: '/app/market' }], + children: [ + { key: '/app/market', icon: , label: '产品行情', path: '/app/market' }, + { key: '/app/knowledge', icon: , label: '向量知识库', path: '/app/knowledge' }, + ], }, ] case '风控':