feat(analyst): Add analyze endpoint and chart specification validation

- Introduced a new `/analyze` endpoint in the analyst API to process analysis requests, allowing users to receive textual interpretations and chart specifications based on provided prompts.
- Enhanced `analyst_schemas.py` with `AnalyzeRequest` and `ChartSpec` models to structure analysis requests and validate chart specifications.
- Implemented chart validation logic in a new `analyst_chart.py` service, ensuring that chart types and fields are correctly specified and conform to allowed values.
- Updated `AnalystAgent` to handle analysis requests, integrating the new logic for generating responses based on user prompts and data availability.
- Added unit tests to verify the functionality of the new endpoint and validation mechanisms, ensuring robustness and reliability.

This update significantly enhances the analytical capabilities of the application, providing users with improved tools for data interpretation and visualization.
This commit is contained in:
2026-09-12 12:33:37 +08:00
parent d4a2461ba4
commit a0f550646e
155 changed files with 43199 additions and 5211 deletions
+2 -2
View File
@@ -25,7 +25,7 @@
- **内部分析**:SQL 或 IT 导表。
- **风控**:事后/T+1 发现异常;适当性靠交易前规则;AML 批处理或人工。
Agent **尚未上线**;后端已在 **`merger` 分支**集成(AL-09 + 客服 Wave3 + 问数 S3/D-06/N-03/N-07 + 风控 TEST-RISK-001 修复 + 平台 API v0.1 + `/api/ready`):**`python -m pytest` → 825 passed 1 skipped**(集成需本机 MySQL/Redis);无生产 Core/托管接入。代理人/分析 **chat 骨架**仍为主,问数/客服/风控已有独立业务链路。
Agent **尚未上线**;后端已在 **`merger` 分支**集成:**`python -m pytest` → 861 passed, 1 skipped** · **`web` Vitest 27**(2026-09-12);问数见 [TEST-LOG-2026-09-12-AN-002](tests/2026-09-12-analyst-template-phrases/TEST-LOG-2026-09-12-AN-002.md)。
------
@@ -55,5 +55,5 @@ Agent **尚未上线**;后端已在 **`merger` 分支**集成(AL-09 + 客服
```text
痛点:客户看不清持仓与规则;代理人多系统慢、话术易违规;问数靠 SQL;风控感知滞后
不能动:正式 C1~C5 仅测评/人工变更;事实账以 Core 为准(模拟库亦只读);监管要求审计与适当性
模拟边界:不模拟 TA/清算/报盘;种子数据静态净值可接受;Neo4j 由同步脚本维护
模拟边界:不模拟 TA/清算/报盘;**净值 365 日与模拟宽基同向(beta)** · 交易 05+08 跨月 · 持仓为单日快照 · Neo4j 由同步脚本维护
```
+1 -1
View File
@@ -65,7 +65,7 @@ Client → Gateway(JWT/RBAC) → api/chat → agent_service(LangGraph) → Tools
**下一步(统筹 P1):** merger commit · 20 题 battery · 客服/风控 E2E 详测 · analyst **D-09 多轮** · **客服** L2 Redis(风控不做 L1/L2)。
**本机已就位状态(2026-09-11):** `.env` 含 `REDIS_URL=redis://127.0.0.1:6380/0`;pytest **825 绿**;问数 D-06 + sample/escalate;`GET /api/ready`;前端四角色 Demo 可 walkthrough。
**本机已就位状态(2026-09-12):** pytest **861 绿** · Vitest **27 绿** · 问数模板 **`match_phrases`** + 常用问法 UI · NL2SQL **内置 few-shot**(`analyst_agent._BUILTIN_SQL_FEW_SHOTS`)· 测试包 [AN-002](tests/2026-09-12-analyst-template-phrases/TEST-LOG-2026-09-12-AN-002.md)。
**本机已知坑:** `mysql.exe` 不在 PATH;`reset.ps1` 自动化用 `MYSQL_PWD`;**6379 常被 Windows Redis 3.x 占用** → 项目 Docker 映射 **6380**;Milvus 中文路径需英文 `MILVUS_URI`;问数模板需灌 `seed-analyst-query-templates.sql` 才见蓝色「模板命中」标签;`battery_report.json` **不入库**(`.gitignore`)。
+3 -3
View File
@@ -35,15 +35,15 @@
| 模块 | 职责 | 依赖 | 代码状态 |
| --- | --- | --- | --- |
| Agent Gateway / Auth SDK | JWT、RBAC、归属校验 | Redis、MySQL customer_advisor_rel | **已实现(T-01 + AL-09)**:模块 `service/auth_service.py` + `api/deps.py`;宿主 `gateway/` 四件套并存;`/api/auth/login` 统一走 `issue_dev_token`;S2 接缝 `auth_adapter.module_auth_from_host` |
| 客户财富 Agent | L1 画像、事实查询、阈值提醒 | Core RO、Milvus 产品库 | **S2 + Wave3(2026-09-11)**:Chat SSE · 1B/R1 · C-04/C-05/C-11 · L1 懒读 · **833 pytest**
| 客户财富 Agent | L1 画像、事实查询、阈值提醒 | Core RO、Milvus 产品库 | **S2 + Wave3(2026-09-11)**:Chat SSE · 1B/R1 · C-04/C-05/C-11 · **861 pytest** |
| 代理人助手 Agent | L2 画像、RAG、草稿 | L1 只读、Milvus | **Chat 骨架已通(agent_service/advisor)· 产品未完整接入:无独立深编排 · T-20 / A-01~A-05 未做** |
| 数据分析 Agent | NL→SQL→解读 | Core RO、画像只读 | **S3+P2+D-06+D-09 子集 + N-03/N-07(2026-09-11)**:interpret 拆分 · sql_guard **Q17** · sample/escalate · **833 pytest** |
| 数据分析 Agent | NL→SQL→解读 | Core RO、画像只读 | **S3+D-06(2026-09-12)**:`match_phrases` · 内置 few-shot · `/analyze` · `/template-prompts` · **861 pytest** |
| 风控监测 Agent | 预警、L3、R-02 适当性 | 交易事件、AML 名单 | **已实现 B1~B9b + C1~C6(FR-1~10)** + **TEST-RISK-001 修复**;**AL-09 已并入 `merger` 分支** |
| Core 只读层 | L0 事实查询 | `jinrong_core` | **已实现 + 已接对话 Tool(T-04)**:core_ro 经 app/tool/core_tools.py 三只读 Tool(L0/持仓/流水)进 chat;风控扩展查询照旧 |
| 共用底座 | 会话、审计、输入防护 | MySQL 11 表 + Redis | **已接入(2026-09-07)**:会话(T-06 session_repository + memory_service 窗口)、审计中间件(T-02 http_access + input_guard_log 双写)、agent_tool_call Tool 留痕(T-04)、输入防护(T-03 input_guard:注入词表纯函数检测 + oversize + Redis 固定窗口限流,chat 链路 限流→注入/超长→归属) |
| 对话编排 | LangGraph StateGraph + DeepSeek | langgraph/langchain-openai | **已实现**:customer/advisor/risk/analyst 四线;**analyst 问数**独立 `analyst_agent`(非 chat StateGraph) |
| 同步脚本 | 归属、Neo4j | Core → agent / 图库 | **sync_*.py 已实现** |
| 前端 Demo(`web/`) | 四角色工作台、平台读 UI、Chat | FastAPI v0.1 + chat B/C | **P0 Demo(2026-09-11)**:问数+解读按钮 · 模板/缓存 Tag · **`/analytics/chat`→问数** · **22 Vitest** |
| 前端 Demo(`web/`) | 四角色工作台、平台读 UI、Chat | FastAPI v0.1 + chat B/C | **P0 Demo(2026-09-12)**:问数常用问法/标星 · 分析助手 Modal · **27 Vitest** |
------
+3 -1
View File
@@ -34,5 +34,7 @@
| 2026-09-11 | **问数 N-03/N-07 + 自检**:`GET .../sample` · `POST /escalate` · `GET /api/ready` · DevReadyBanner | Superpowers 批次 | `analyst.py` · `ready.py` · web |
| 2026-09-11 | **TEST-AN-001 RBAC 修复**:sql_guard A/B · `_audit_terminal` · TEST-LOG v1.2 · **820 pytest** | 沙盘测试文档 | `sql_guard` · `analyst_agent` |
| 2026-09-11 | **行情历史种子入库**:`07-seed-nav-history.sql` · `nav/history` API · **833 pytest** | 近一年 Demo | `scripts/core` · MEMORY |
| 2026-09-11 | **行情详情图表**:时间轴 scale · Y domain · 1M~ALL/日周/走势·柱·组合 · **26 Vitest** · ready+restart-dev 防旧后端 | 轴与平台能力 | web · MEMORY |
| 2026-09-12 | **问数分析助手 + 模板原句**:`/analyze` · UNION 规范化 · **858 pytest** · **27 Vitest** | 答辩实测 | 见 [TEST-LOG-AN-002](tests/2026-09-12-analyst-template-phrases/TEST-LOG-2026-09-12-AN-002.md) |
| 2026-09-12 | **问数原句模板 + 常用问法 UI**:`match_phrases` · `/template-prompts` · 标星/最近 · 种子问句 | 模板误命中口径 | template_service · web · seed |
| 2026-09-12 | **Core 模拟数据 enriched**:净值 **CSI300+beta**(`seed_nav_history.py`)· **08 跨月交易** · 持仓/阈值 DEMO 行保留 | 问数/趋势/demo 更贴近大盘 | `scripts/core/07` `08` · reset.ps1 |
| 2026-09-10 | **TODO 日终清单 + 优化 TODO 补全**:硬伤盘点落账 · 2026-09-10 已完成/进行中/将要做 | 用户要求 | TODO · ITERATION |
+11 -9
View File
@@ -9,7 +9,7 @@
**项目是什么:** 金融四 Agent(客户财富 / 代理人 / 数据分析 / 风控)共用数据层与合规底座;**不**互调 LLM,跨 Agent 走 L1/L2/L3 画像与预警表。
**当前进度:** 需求与表设计已定 · **833 pytest** · **26 Vitest** · **Redis @ 6380** · **`merger` 未 commit** · **⚠️ 代理人 Agent 未完整接入**
**当前进度:** 需求与表设计已定 · **861 pytest** · **27 Vitest** · **Redis @ 6380** · **`merger` 未 commit** · **⚠️ 代理人 Agent 未完整接入**
**工作分支:** 团队开发在 **`merger`**;历史 `risk-control-agent` 交付冻结。
@@ -24,9 +24,9 @@
| `app/api/risk.py` `simulate.py` `deps.py` | **已实现** | 风控 4 API + 模拟网关 · **compliance/service_risk 矩阵(F2/F3)** · 台账 **`stats` 待审计数(F7)** |
| `app/api/chat.py` `audit_middleware.py` | **已实现** | POST /api/chat · **POST /api/chat/stream(customer 分流 → prepare_customer_stream)** · visitor · sessions 三端点 |
| `app/api/customers.py` `products.py` `advisors.py` `staff.py` `compliance.py` | **已实现(v0.1 + 行情历史)** | 代销平台 REST;`get_platform_auth_context`;**`GET /api/products/{id}/nav/history`**(RBAC · compliance 拒) |
| `app/api/analyst.py` `analyst_auth_adapter.py` | **已实现(S3+D-06+D-09 子集 + N-03/N-07)** | 问数 `POST /api/analyst/chat`(默认 `interpret=false`)· **`POST /api/analyst/interpret`** · **`GET /query/{trace_id}/sample`** · **`POST /escalate`** · dashboard/assets/metrics |
| `app/api/analyst.py` `analyst_auth_adapter.py` | **已实现(S3+D-06 + N-03/N-07 + analyze)** | 问数/chat · **`POST /analyze`** · interpret · sample · escalate · dashboard |
| `app/api/ready.py` | **已实现(2026-09-11)** | `GET /api/ready`:Redis + 关键路由自检;前端 `DevReadyBanner` |
| `app/service/analyst_agent.py` `template_service.py` `cache_service.py` | **已实现(S3+D-06)** | NL2SQL 编排 · guardrail · **结果缓存(表世代键)+ 写侧 bump**(`analyst_cache_invalidate`)· **模板填参**(published `analytics_query_template`) |
| `app/service/analyst_agent.py` `template_service.py` `cache_service.py` `analyst_chart.py` | **已实现(S3+D-06)** | NL2SQL · **`_BUILTIN_SQL_FEW_SHOTS` 内置示例**(不接 D-11 表)· `_nl_sql_hints` 仅双极值 UNION · 模板 · **`normalize_union_all_sql`** · analyze 图表 |
| `app/api/knowledge.py` `admin.py` | 空壳 | 待审计查询台与知识库 API(T-21 拍板一期只做脚本入库,上传/重建端点不做) |
| `app/service/platform/` | **已实现(v0.1)** | 封装 core_ro + `PLATFORM_RESPONSE_DESENSITIZE` 脱敏开关 |
| `app/service/embedding.py` `milvus_service.py` `rag_service.py` | **已实现(T21-1/2/4)** | Ollama bge-m3 1024 维(失败不静默降级)/ kb_product_rules 建集合+upsert+合规过滤检索 / search_knowledge→chunks+source_refs 溯源 |
@@ -50,7 +50,7 @@
| `scripts/core/*.sql` + `reset.ps1` | **已实现** | Core 模拟库 DDL + 种子 · **`07-seed-nav-history.sql`(14 产品 ×364 日)** · 再生 `seed_nav_history.py --export-sql` |
| `scripts/agent/` `scripts/demo/` `scripts/dev/` | **已实现** | **`prepare_all.ps1`** · **`push_threshold_alerts.py`** · **`seed_analyst.ps1`** · **`sandbox_risk_test.py`** · `run_query_battery.py`(**不入库**) |
| `scripts/sync/*.py` | **已实现** | 归属同步 + Neo4j 全图 |
| `tests/` | **已实现** | **833 用例** 1 skipped |
| `tests/` | **已实现** | **861 用例** 1 skipped · 问数包 [TEST-LOG-2026-09-12-AN-002](tests/2026-09-12-analyst-template-phrases/TEST-LOG-2026-09-12-AN-002.md) |
| `docs/答辩/` | **答辩提纲 + Demo SOP** | `答辩知识点清单.md` · **`DEMO-SOP-答辩全流程.md`** · `DEMO-SOP-问数.md` · spec `docs/superpowers/specs/2026-09-11-defense-stable-package.md` |
| `docs/course/` | **交互课程集** | 导览中心 + 总览 **8 模块** + 问数 **5 模块**(D-06)+ 风控深潜 **7 模块** · 与答辩清单同步 |
| `docs/PRD/PRD-风控监测Agent.md` | **已冻结(v1.1)** | 风控 PRD v1.0 + v1.1 追加 FR-8/9/10(§4A)+ 规则表附录 |
@@ -76,7 +76,7 @@
(风控演示:`.\scripts\demo\prepare_all.ps1` 或 `prepare_risk_demo.sql` · PRD §10.2)
6. python scripts/sync/sync_advisor_rel.py && python scripts/sync/sync_neo4j.py
7. `docker compose up -d redis`(或 `.\scripts\dev\start-redis.ps1`)→ **REDIS_URL=redis://127.0.0.1:6380/0**(Docker Redis 7;避开本机 Windows Redis 占 6379)
8. uvicorn … · python -m pytest(**833 绿**);问数答辩:`docs/答辩/DEMO-SOP-问数.md` · 种子 **`scripts/dev/seed_analyst.ps1`**
8. uvicorn … · python -m pytest(**861 绿**);问数答辩:`docs/答辩/DEMO-SOP-问数.md` · 模板种子 **`scripts/agent/seed-analyst-query-templates.sql`**
```
**AL-09 合并后架构(一句话):** 宿主 `gateway/` + 模块 `deps.py` **双栈并存**;对外登录/token **统一**;chat/risk 均走模块鉴权;接缝 S2 用 `auth_adapter`。
@@ -174,7 +174,9 @@ audit_log 等审计表(只 INSERT)
| **C-11** | 「我能买什么/匹配产品」→ `suitability_check` + Core 可购列表;**「推荐稳赚/买什么好」仍 reject** | `customer_prompts._ELIGIBLE_PRODUCTS_KW` |
| **C-07** | 风评查询走 Core;**「重新测评/重做风评」** → 引导 App/网点(Agent 内不做问卷) | `customer_prompts._RISK_KW` |
| **C-08** | 仅 L1 槽位 `investment.allocation_target`(13 槽);**无自动偏离检测/调仓** | `profile_slots.py` |
| **问数 vs 分析对话** | 问数页 + **`/interpret`** · **N-03 抽样溯源** · **N-07 escalate/失败转人工** · 分析对话 URL **重定向问数** | spec `2026-09-11-query-interpret-split-design.md` |
| **问数 vs 分析对话** | 问数页 · **`/interpret`** · **`/analyze` 对话框(文/图)** · N-03/N-07 · 分析对话 URL 重定向问数 | spec `2026-09-11-query-interpret-split` · `2026-09-12-analyst-query-visualization` |
| **问数 Demo(2026-09-12)** | **`match_phrases` 原句命中** · `GET /template-prompts` · 问数页标星/最近(**localStorage,非模板**)· UNION 括号 · 分析 **`POST /analyze`** | spec + [TEST-LOG-AN-002](tests/2026-09-12-analyst-template-phrases/TEST-LOG-2026-09-12-AN-002.md) |
| **问数 NL2SQL(2026-09-12 拍板)** | **复合问不走模板** · 不靠关键词补口径(除净值双极值 `_nl_sql_hints`)· **内置 few-shot**(`analyst_agent._BUILTIN_SQL_FEW_SHOTS`)+ schema · **D-11 表/UI 未接 NL2SQL**(draft 沉淀无效)· 持仓产品走势 = `core_holding`→`core_product_nav`(`schema_meta` 关系说明) | 用户拍板「先信大模型+few-shot」 |
| **客户「趋势/走势」** | **平台行情详情**:`nav/history` + **区间(1M~ALL)/粒度(日周)/图型(走势/涨跌柱/组合)** · Chat 仍无历史 Tool · Phase B 外部 sync 未做 | 见 `web/src/utils/navChartSeries.ts` |
| **L0 优先** | 抽槽与 L0 撞车**永远听 L0**;L1 只 enrich 措辞 | `profile_slots` D7 |
@@ -190,9 +192,9 @@ Core 模拟:scripts/core/reset.ps1 · 文档 docs/项目框架设计/Core模
依赖:requirements.txt(LangGraph + langchain-core/openai + FastAPI + SQLAlchemy)
启动:uvicorn app.main:app --reload → GET /health · **GET /api/ready**(环境自检)
Redis:`docker compose up -d redis` · `REDIS_URL=redis://127.0.0.1:6380/0` · `scripts/dev/start-redis.ps1`
测试:python -m pytest(**833 绿**;集成需本机 MySQL + AML + 风控演示数据)
测试:python -m pytest(**861 绿**;集成需本机 MySQL + AML + 风控演示数据)
风控沙盘:`python scripts/dev/sandbox_risk_test.py`(TEST-RISK-001)
前端:cd web && npm run dev · npm run build/test/lint(**26** Vitest)· 四角色 Demo 见 **`docs/frontend/FRONTEND-HANDOFF.md` §8** · 本地后端 **`scripts/dev/restart-dev.ps1 -Backend Local`**
前端:cd web && npm run dev · npm run build/test/lint(**27** Vitest)· 四角色 Demo 见 **`docs/frontend/FRONTEND-HANDOFF.md` §8** · 本地后端 **`scripts/dev/restart-dev.ps1 -Backend Local`**
问数模板:mysql … < scripts/agent/seed-analyst-query-templates.sql
风控一键灌库:.\scripts\demo\prepare_all.ps1
运维/演示脚本:scripts/demo/subscribe_alerts.py(订阅推送演示)· rebuild_alerts.py TRD-xxx(引擎异常补偿重放)
@@ -242,6 +244,6 @@ RBAC 联调账号:scripts/dev/rbac-seed-reference.md
2. 改动属于 api / service / tool / repository 哪一层?
3. 是否需 customer_id 归属与 JWT RBAC?
4. Core 是模拟库只读还是 agent 库读写?
5. 如何验证?(`python -m pytest` **833 绿** · Redis **6380** · `/api/ready` · 行情详情 `/app/market/PROD-000001` · 问数 Demo 见 `docs/答辩/DEMO-SOP-问数.md`)
5. 如何验证?(`python -m pytest` **861 绿** · `cd web && npm run test` **27 绿** · Redis **6380** · `/api/ready` · 问数 **点选常用问法收录句** · 复合问看 NL2SQL+内置 few-shot · 日志 [AN-002](tests/2026-09-12-analyst-template-phrases/TEST-LOG-2026-09-12-AN-002.md))
大任务:FRAMEWORK/FLOW 与实现状态不符时先更新 memory 再编码(用户确认跳过除外)。
+4 -2
View File
@@ -95,7 +95,9 @@
> **四角色 Demo 主链路已通**(登录 → Dashboard → Chat/问数/风控 REST);以下为 polish / 规格缺口。
- [x] 四角色 Dashboard · 平台只读 · ChatPanel · 游客试聊 · 行情 · 风控台账+筛选+适当性+AML+模拟交易 · 问数工作台+资产沉淀
- [x] **问数结果标签**:`AnalystQueryPage` 展示 `template_hit` / `cache_hit`(2026-09-10)
- [x] **问数模板原句(2026-09-12)**:`match_phrases` · `/template-prompts` · 常用问法/标星/最近 · TEST-LOG-AN-002 · **861 pytest / 27 Vitest**
- [x] **问数 NL2SQL few-shot(2026-09-12)**:`analyst_agent._BUILTIN_SQL_FEW_SHOTS` · 复合问不模板化 · 无 D-11 库读
- [ ] **D-11 真落地(延后)**:`analytics_few_shot` 发布流 · 沉淀页 draft→published · NL2SQL 读库 · 「成功问数一键沉淀」
- [x] **答辩稳套餐 ①(2026-09-11)**:A1 sql_guard Q17 · A5 流水 clarify 单测 · C1 种子 +2 模板 · D1 分析对话重定向/菜单收 · E1 `docs/答辩/DEMO-SOP-问数.md` · 答辩清单同步
- [ ] **分析对话菜单下线或强引导**(问数页已承载解读)· **看板钻取** — D-12 往后排
- [ ] **Vitest 补测**(可选):`useChatPanel` · `api/analyst.ts` mock
@@ -193,7 +195,7 @@
- [x] customer `self` 域 + 「AI 分析有风险」尾注
- [x] Wave6 测试 · 迁移 `migrate-analyst-d07-d11.sql` · 重置 `reset-analyst-d07-d11.sql`
- [x] 前端问数页 · Scope B 冒烟 19/19
- [x] **P2 · D-11 资产沉淀 UI**(`AnalystAssetsPage` · `POST /api/analyst/assets`)
- [x] **P2 · D-11 资产沉淀 UI**(`AnalystAssetsPage` · `POST /api/analyst/assets`)— **仅写 draft 表,未接 NL2SQL/发布(2026-09-12 拍板延后)**
- [x] **P2 · 问数页 dashboard 卡片**(`GET /api/analyst/dashboard` · `AnalystQueryPage` 顶部 metrics)
- [x] **P2 · 口径字典种子 SQL**(`scripts/agent/seed-analyst-metric-dict.sql` · 本机需手工执行)
- [x] **customer Chat SSE**(`chat.py` stream → `prepare_customer_stream` · 前端 `mode="stream"`)
@@ -0,0 +1,7 @@
# 问数模板原句 + 分析助手 · 测试包
| 文件 | 说明 |
| --- | --- |
| [TEST-LOG-2026-09-12-AN-002.md](./TEST-LOG-2026-09-12-AN-002.md) | 2026-09-12 答辩实测修复与自动化基线 |
前置沙盘:[../2026-09-11-analyst-domain-rbac/TEST-LOG-2026-09-11-AN-001.md](../2026-09-11-analyst-domain-rbac/TEST-LOG-2026-09-11-AN-001.md)
@@ -0,0 +1,103 @@
# 企业级测试日志 · TEST-2026-09-12-AN-002
> 问数模板原句命中 · UNION 规范化 · 分析助手 `/analyze` · 常用问法 UI
---
## 1. 文档元数据
| 字段 | 值 |
| --- | --- |
| **测试记录编号** | TEST-2026-09-12-AN-002 |
| **缺陷/变更标题** | 答辩实测:模板 `match_all` 误绑涨跌幅口径;LLM UNION 缺括号 1064;改为 `match_phrases` + 问数页引导 |
| **文档版本** | v1.2 |
| **创建日期** | 2026-09-12 |
| **关联分支** | `merger` |
| **关联拍板 / TODO** | spec `2026-09-12-analyst-template-exact-phrases-design.md` · `2026-09-12-analyst-query-visualization-design.md` |
| **风险等级** | **LOW**(模板层;未收录原句仍走 NL2SQL) |
| **缺陷类型** | 产品口径(模板)+ SQL 语法(UNION)→ **已修复** |
| **发现阶段** | 分析员问数答辩 walkthrough · `analytics_query_log` 留痕 |
| **修复阶段** | 单元测试 + 种子重灌 + 问数页 UI |
---
## 2. 环境与基线
| 字段 | 值 |
| --- | --- |
| **测试环境** | development · 本机 Windows |
| **修复后测试基线** | **`python -m pytest` → 861+ passed, 1 skipped**(含本日志 §7 增补) |
| **前端** | **`cd web && npm run test` → 27 passed**(含 `analystChartSpec.test.ts`) |
| **种子** | `scripts/agent/seed-analyst-query-templates.sql`(`match_phrases` + `prompt_label` + `featured`) |
---
## 3. 测试范围
| 区域 | 用例/文件 | 结论 |
| --- | --- | --- |
| 原句模板匹配 | `tests/test_wave6_template_service.py`(exact / 不 fuzzy / list_prompts) | PASS |
| UNION 规范化 | `tests/test_wave6_sql_guard.py` · `normalize_union_all_sql` | PASS |
| 分析助手 | `tests/test_wave6_analyst_agent.py` · `tests/test_wave6_analyst_chart.py`(含 **chart_type 非字符串**) | PASS |
| 路由挂载 | `tests/test_main.py`(`/analyze` · `/template-prompts`) | PASS |
| 问数页 UI | 手工:推荐 Tag · 标星 · 最近成功 · 点击整句发问 | 待答辩复验 |
---
## 4. 答辩实测问题(已闭环)
| 现象 | 根因 | 修复 |
| --- | --- | --- |
| 「净值最高/最低」返回涨跌幅极值 | 模板 `match_all` 命中 + SQL 按 `daily_chg_pct` | 净值/涨跌幅 **分列收录原句**;有 `match_phrases` 不再 `match_all` |
| SQL 1064 near `UNION ALL` | LLM 子查询未加括号 | `normalize_union_all_sql` 于执行前 |
| 用户自由改字仍误命中 | 关键词泛化 | 仅 **normalize 后与原句一致** 命中 |
**收录问句(种子 SEED · 答辩推荐点选):**
- 净值:`目前净值最高和最低的两个产品`
- 涨跌幅:`最新净值日涨跌幅最高和最低各一只产品`
---
## 5. 变更文件索引
| 中文职责 | 路径 |
| --- | --- |
| 原句匹配与 prompt 列表 | `app/service/template_service.py` |
| UNION 执行前规范化 | `app/service/sql_guard.py` |
| 分析图表与 `/analyze` | `app/service/analyst_agent.py` · `app/service/analyst_chart.py` |
| 模板问句 API | `GET /api/analyst/template-prompts` · `app/api/analyst.py` |
| 常用问法 UI | `web/src/pages/analytics/AnalystQueryPage.tsx` · `web/src/utils/analystQueryHistory.ts` |
---
## 6. 增补 · chart_type 类型安全(2026-09-12)
| 字段 | 值 |
| --- | --- |
| **缺陷** | `validate_chart_spec` 对 `data["chart_type"]` 直接 `.strip()`;LLM JSON 若返回 `int`/`bool`/`list` 会 **AttributeError**,`/analyze` 整段失败 |
| **根因** | `analyst_chart.py` 未做字符串化 |
| **修复** | `str(data.get("chart_type") or "none").strip().lower()` |
| **状态** | **已修复**(v1.1) |
| **回归** | `tests/test_wave6_analyst_chart.py` · `test_validate_chart_type_non_string_coerced`(整数 → 友好错误;`" LINE "` → `line`) |
| **风险** | **LOW**(仅 malformed LLM 输出路径) |
### 6.1 同路径加固(v1.2 · 预防性)
| 项 | 问题 | 修复 |
| --- | --- | --- |
| `y_fields` | LLM 有时返回字符串 `"nav"` 而非数组 → 校验报「不能为空」 | `_coerce_y_fields`:字符串转单元素列表 |
| `x_field` / `series_field` | 非字符串列名 | `_field_name`:`str()` + strip |
| `classify_analysis_kind` | `answer` 非字符串时 `.strip()` 崩溃 | `str(answer or "").strip()` |
| `/analyze` degrade 分支 | `chart_type` 非字符串时与 `"none"` 比较不准 | 与校验同口径 `str(...).strip().lower()` |
| **回归** | `test_validate_y_fields_single_string` · `test_classify_analysis_kind_non_string_answer` |
---
## 7. 修改记录
| 修改人 | 日期 | 说明 |
| --- | --- | --- |
| Cursor Agent | 2026-09-12 | 初版 · 858 pytest · 27 Vitest |
| Cursor Agent | 2026-09-12 | v1.2 · analyze JSON 字段 coercion(y_fields/x_field/classify)· +2 单测 |