Files
group_xinghuo_jinrong/docs/memory/tests/2026-09-13-analyst-e2e/TEST-LOG-2026-09-13-AN-003.md
T
zhanghongyu_0626 188bf6a438 feat(analyst_agent): Add support for recent days queries and enhance SQL hint generation
- Introduced `_recent_days_series_hint` to handle queries related to "最近N天" for time series data aggregation.
- Added `_cn_num_to_int` function to convert Chinese numerals to integers for better query parsing.
- Updated `_nl_sql_hints` to incorporate the new hint generation logic, ensuring accurate SQL output for recent days queries.
- Enhanced documentation to reflect these changes and improve clarity on the new functionalities.
2026-09-13 22:36:19 +08:00

12 KiB
Raw Blame History

测试日志:问数线(数据分析 Agent)真实 HTTP 端到端补测

编号接续:TEST-LOG-2026-09-11-AN-001(域 RBAC,进程内 TestClient)→ AN-002(模板短语)→ 本文件 AN-003(出进程真实 HTTP 全路由)。

1. 文档元数据

项 值
测试记录编号 TEST-AN-003
缺陷/变更标题 问数线 12 个 handler 的真实 HTTP 端到端补测;暴露缺陷 A / A2 / B
文档版本 v1.0
创建日期 2026-09-13
关联分支 merger
关联拍板/TODO 本轮七项拍板(口径 2「只报告不修」· 口径 7「一线一包」);docs/memory/TODO.md E2E 覆盖缺口条目
风险等级 MEDIUM(缺陷 A 用户可见;但业务库零写入、无数据损坏风险)
缺陷类型 ① 数据库列名错误(缺陷 A)② 异常分级失真(缺陷 A2)③ 响应契约不一致(缺陷 B)
发现阶段 端到端补测(本轮新建脚本首次执行)

2. 组织与责任

项 值
所属模块 数据分析 Agent(问数线)—— app/api/analyst.py · app/service/analyst_agent.py · app/service/analytics_repo.py · jinrong_agent 模板数据
模块负责人 (待指定)
发现人 本轮 E2E 补测
修改人 无(本轮口径:只报告不修)
测试执行人 本轮 E2E 补测
评审人 待评审

3. 缺陷描述

3.1 缺陷 A —— core_trade.trade_date 列不存在

项 内容
缺陷编号 DEF-AN-003-A
严重等级 P1
复现步骤 1. python scripts/dev/analyst_e2e_smoke.py --only AN8
2. 看 8-03b-07 / 8-03b-08 / 8-03e-04 / 8-07c
3. 直连库复核:SHOW COLUMNS FROM core_trade(见 _raw/defect-A-direct-sql-proof.txt)
期望 ① 客户 dashboard 返回 metrics 含 holdings 与 trades_30d;② 问数页 9 个收录问句无一落到 escalate
实际 ① metrics={}(恒空对象)② 3 条落到 status='escalate' code='EXEC_TIMEOUT':近30天申购金额总额 · 申购金额汇总 · 我近30天有多少笔交易
根因 core_trade 的时间列真名是 traded_at,无 trade_date(15 列实测清单见取证文件 §1)。三处按 trade_date 写:app/api/analyst.py:148(客户 dashboard 的 trades_30d 子查询)、库模板 subscribe_amount_recent_days、库模板 customer_self_trade_count_recent。执行即 1054 Unknown column 'trade_date' in 'where clause'。
放大器 1(连坐): holdings 与 trades_30d 在同一条 SELECT 内,列错误使整条语句失败 → 本可独立算出的 holdings 一并丢失。
放大器 2(掩盖): app/api/analyst.py:170-171 的 except Exception: pass 吞掉异常,HTTP 仍 200,前端拿到"正常的空数据"。
放大器 3(漏检): customer_self_trade_count_recent 声明 extract='scope_customer',只在 customer 域渲染;按 analyst 身份探它会因参数解析失败而跳过模板 → 回落真实 LLM,LLM 自行生成用 traded_at 的正确 SQL,于是 status='success',缺陷被掩盖。单身份探测会漏报此条。
修复方案(本轮不实施) 四选一,须先拍板:① 改三处代码/模板为 traded_at(最小改动,但需确认 traded_at 的语义与 trade_date 的 30 天窗口口径一致);② 在 core_trade 加 trade_date 生成列/视图(改动 DDL,属高风险项,须确认);③ 客户 dashboard 拆成两条独立 SELECT(消除连坐,但不治根);④ 问数线改走 traded_at 并给模板加列名校验。另须一并处置放大器 2(裸 except 改为记日志/上抛)与放大器 3(模板参数解析失败应显式降级并留痕,而非静默回落 LLM)。

3.2 缺陷 A2 —— schema 错误被误报为「查询超时」

项 内容
缺陷编号 DEF-AN-003-A2
严重等级 P2
复现步骤 python scripts/dev/analyst_e2e_smoke.py --only AN8,看 8-03d;或执行取证文件 §3 的直连探针
期望 列不存在这类 schema 错误应报 EXEC_ERROR(并带原始 SQL 错误)
实际 error_code='EXEC_TIMEOUT',HTTP 200,用户看到**「查询超时(>10s)」**
根因 app/service/analytics_repo.py:16-18 的 _is_timeout_error() 仅凭异常类名判定:exc.__class__.__name__ in ("OperationalError",)。而 pymysql 对 1054(Unknown column)/ 1146(Unknown table)等 schema 错误同样抛 OperationalError。实测 _is_timeout_error(<1054 OperationalError>) -> True → app/service/analyst_agent.py:228 走 EXEC_TIMEOUT 分支。
危害说明 非文案瑕疵。该提示把排障方向指向错误的地方("库慢 / 加重试"),而真实原因是查询根本没执行;且它把缺陷 A 又遮掩了一道。
修复方案(本轮不实施) 收窄判定:只认真正的超时(pymysql.err.OperationalError 的 args[0] ∈ 超时码 2013/2006/1205,或 msg 含 timeout);把 schema 错误(1054/1146/1064 等)归入 EXEC_ERROR 并回传原始 SQL 错误信息。

3.3 缺陷 B —— /ops/metrics 返回形状不一致,列名丢失

项 内容
缺陷编号 DEF-AN-003-B
严重等级 P3
复现步骤 python scripts/dev/analyst_e2e_smoke.py --only AN11,看 11-01a(实测 list [216, 49])
期望 恒返回 {"total": <int>, "blocked": <int>}
实际 有数据时返回裸数组 [216, 49];无数据时返回 {"total": 0, "blocked": 0}
根因 app/api/analyst.py:270 返回 res["rows"][0],而 execute_readonly(analytics_repo.py:75)返回 rows: [list(r.values())] —— 即裸值列表,列名在传递中丢失。同文件 :143/:151/:161/:169 四处 dashboard 分支都正确写了 dict(zip(res["columns"], res["rows"][0])),唯独 ops_metrics 漏了 → 属实现不一致,非设计取舍。数据本身正确(列序与 SELECT 一致)。
缓解事实 web/src 全仓无 /api/analyst/ops/metrics 引用(用例 11-03)—— 接口可用但无消费方,当前不对前端造成实际故障。
修复方案(本轮不实施) 补 dict(zip(res["columns"], res["rows"][0])),与 dashboard 分支写法对齐。一行改动。

4. 变更清单

类型 路径 说明
新增 scripts/dev/analyst_e2e_smoke.py 问数线真实 HTTP E2E,13 组 166 条,约 1.5k 行
新增 docs/memory/tests/2026-09-13-analyst-e2e/README.md · 本日志 · _raw/ 本包
业务代码 —— 本轮未改动任何 app/**(口径 2「只报告不修」)

5. 测试执行记录

序号 类型 用例/命令 执行人 结果 证据
1 前置 curl -s http://127.0.0.1:8000/api/ready 本轮 PASS ok=true degraded=false redis=true
2 契约冻结 AN1:openapi 含 12 条 analyst 路由、路径总数 = 66 本轮 PASS _raw/an-canonical.md AN1 组
3 鉴权矩阵 AN2:五角色→五域 + 越权探针 + 无 token 本轮 PASS 同上 AN2 组(17 PASS)
4 只读红线 AN13-01:core 五表前后差值 本轮 PASS core_trade 95→95 等五表 Δ0
5 预警红线 AN3:/chat 前后 risk_alert 零增量 本轮 PASS AN3-05(3-05a 预警问句 200 · 3-05b 计数与库中事实一致)
6 提权加固 AN1-06:customer actor 传 roles=["analyst","risk_officer"] 本轮 PASS 签发仍为 ['customer']
7 跨域越权 AN2-10:customer 问全量聚合 本轮 PASS status='deny' code='AUTH_403_SCOPE'
8 缓存 AN12:二次同问 本轮 PASS cache_hit=True exec_ms=0 template_hit=True
9 缺陷取证 AN8:--only AN8 本轮 FAIL ×5 缺陷 A(4 表现)+ A2(1)
10 缺陷取证 AN11:--only AN11 本轮 FAIL ×1 缺陷 B
11 全量跑批 python scripts/dev/analyst_e2e_smoke.py(连续 3 轮) 本轮 137 PASS / 6 FAIL / 2 SKIP / 21 INFO,退出码 1 _raw/an-canonical.*;三轮 FAIL 集合一致
12 直连库取证 _raw/defect-A-direct-sql-proof.txt 三段命令 本轮 全部复现 1054 异常原文 + _is_timeout_error -> True

FAIL 明细(6 条,全部为缺陷表现,无脚本自身 bug):

用例 实测 归属
8-03b-07 收录问句可答 · 近30天申购金额总额 status='escalate' code='EXEC_TIMEOUT' 缺陷 A
8-03b-08 收录问句可答 · 申购金额汇总 status='escalate' code='EXEC_TIMEOUT' 缺陷 A
8-03e-04(as customer) 收录问句可答 · 我近30天有多少笔交易 status='escalate' code='EXEC_TIMEOUT' 缺陷 A
8-07c 客户看数板 metrics 应非空 metrics={} 缺陷 A
8-03d 失败原因不被误报为 EXEC_TIMEOUT codes=['EXEC_TIMEOUT'] 缺陷 A2
11-01a 返回 total/blocked 两字段 list [216, 49] 缺陷 B

首轮曾出现的 5 条 FAIL 中有 4 条是脚本自身缺陷,已修正下述,不属被测系统问题(记录在案以示区分): ① AN4-04 误把 advisor 当越权角色 —— advisor 在 analyst_auth_adapter._ROLE_PRIORITY 内、域 assigned 被授予,属合法调用方(问数线宽于 chat 矩阵);已改用 compliance 作负例,并保留 advisor 作正例。 ② AN8-02 期望"模板数"而 list_published_prompts 按 match_phrase 展开(7 模板 → 9 项);已改为同时断言"条目数 == 短语总数"与"去重 key 数 == 有短语的模板数"。 ③④ expect_db_eq 用 str() 直比 Decimal("7297060.00") 与 float 7297060.0,产生纯表示层假 FAIL;已加数值归一化。 ⑤ AN11-01a 是真缺陷(缺陷 B),保留。

6. 数据准备

数据集 是否重灌 操作
jinrong_core(5 张写表) 否 本线不写入;AN13-01 前后差值取证 Δ0,无需还原
jinrong_agent.analytics_query_template 否 只读(加载发布模板池与短语数作为对账基线)
jinrong_agent.analytics_query_log 否 追加型留痕,按语义不回滚
jinrong_agent.analytics_metric_dict / analytics_few_shot 否 AN9 沉淀资产,键名带 E2E-AN-<runstamp> 前缀;三轮跑批各新增 1 行,残留已登记(AN9-10)
jinrong_agent.audit_log 否 追加型留痕,永不回滚

7. 结论

项 结论
需求覆盖 问数线 12/12 个 handler 全部建立真实 HTTP 出进程 E2E。其中 10 条为本轮首次(此前仅有 /chat 与 /template-prompts 的进程内 TestClient 覆盖)。13 组 166 条:137 PASS / 6 FAIL / 2 SKIP / 21 INFO。
回归 本线不涉及既有脚本改动,不影响 advisor_e2e_smoke.py / risk_e2e_smoke.py 回归基线。未改动任何业务代码。
缺陷 3 个(A / A2 / B),6 条 FAIL 表现。均只报告不修。其中缺陷 A 为 P1、用户可见(客户看数板恒空 + 3 个推荐问句是坏按钮)。
正面证据 7 条红线/契约已取证转绿:只读红线(core 五表 Δ0)· 不处置预警(risk_alert Δ0)· CT-001 P0 提权加固仍成立 · 跨域越权真被拦(AUTH_403_SCOPE)· 阻断路径留痕(Δ+1)· 模板缓存 Tag · openapi 基线 66。
剩余风险 ① Part B 浏览器未跑(AnalystAssetsPage 表单从未提交、AnalystQueryPage 数字从未对账)② LLM 语义类一律软探针、不计入分母 ③ 2 条 SKIP 是缺陷次生结果,修复后应自动转 PASS,可作修复轮验收信号 ④ ops 角色与未登录组合的越权未穷举。
建议人工再验 ① 缺陷 A 修复后,重点人工复核 traded_at 与"30 天窗口"的口径是否等价(不只是列名替换)② 缺陷 A 的放大器 3:模板参数解析失败静默回落 LLM 是否应显式降级 ③ 待拍板 C-1/C-2/C-3(拒绝形态不统一 · /escalate 无角色校验 · 403 错误码被压成 HTTP_ERROR)。
可复现性 连续三轮跑批 FAIL 集合完全一致;--only AN8 可独立复现缺陷 A 全部 5 条(已实测)。

8. 签核 / 修订历史

版本 日期 修改人 说明
v1.0 2026-09-13 本轮 E2E 补测 首版。Part A 完成(137/6/2/21,退出码 1);Part B 待 Phase 4 追加