feat(analyst): Implement data analysis agent with authentication and query handling

- Introduced `analyst_auth_adapter.py` for managing authentication context and access control for the data analysis agent.
- Added new API endpoints in `analyst.py` for chat, dashboard, asset management, and metrics, utilizing the new authentication context.
- Created Pydantic models in `analyst_schemas.py` for request and response structures, ensuring consistent data handling.
- Updated SQL guard logic in `sql_guard.py` to enforce access restrictions based on user roles and contexts.
- Implemented migration scripts for new database tables related to the data analysis agent, enhancing data management capabilities.
- Removed legacy authentication code from `auth.py`, streamlining the authentication process.

This update significantly enhances the data analysis capabilities, providing a robust framework for querying and managing data securely.
This commit is contained in:
2026-09-09 21:02:11 +08:00
parent aa23e1ae1d
commit bcd6175d4e
28 changed files with 911 additions and 428 deletions
+14 -3
View File
@@ -5,7 +5,7 @@
## 进行中
**前端 P0 主链路真接(2026-09-09)**:ChatPanel · 游客/客户/风控/顾问/分析 Chat · 平台只读页 · 行情 · 风控台账处置 · **730 passed** · **19 Vitest** ✓
**前端 P0 主链路真接(2026-09-09)**:ChatPanel · 游客/客户/风控/顾问/分析 Chat · 平台只读页 · 行情 · 风控台账处置 · **773 passed** · **19 Vitest** · **analytics 问数页已接**
**Redis Docker(2026-09-09)**:`jinrong-redis` @ **6380** · `.env REDIS_URL` 已对齐 · `scripts/dev/start-redis.ps1` ✓
@@ -14,7 +14,7 @@
- [x] **代销平台 API v0.1 实现**(2026-09-08):`customers/products/advisors/staff/compliance` + `service/platform/` + `PLATFORM_RESPONSE_DESENSITIZE` · **530 passed 0 skipped**
- [x] **`web/` P0 脚手架 init**(2026-09-09):Vite+AntD+HashRouter · 登录真接 · AppLayout · 四角色路由 · 占位页 · 见 `web/README.md` · 设计 `docs/superpowers/specs/2026-09-09-frontend-p0-design.md`
- [ ] **接口契约发群**(login + 平台读 API + simulate/trade;强调重复功能以平台路径为准)
- [ ] **前端 P0 真接**(主链路已完成;余 analytics 问数 · 契约发群 · simulate 说明)
- [ ] **前端 P0 真接**(analytics 问数已接 · 契约发群 · simulate 说明)
- [ ] 同步 `MEMORY/REQUIREMENTS/FRAMEWORK` 与各 Agent 负责人联调节奏
### 前端 `web/` · P0 待接(后端已就绪)
@@ -32,7 +32,7 @@
- [x] **risk 预警台账全页**:`GET /api/risk/alerts` 筛选/分页表格 + `POST .../handle` 处置 UI
- [x] **advisor Chat**:SSE + AgentBanner · 名下客户列表页
- [x] **`/app/analytics/chat`**:SSE · `X-Agent-Type: analyst`
- [ ] **`/app/analytics/query`**:PermissionGate + 问数 Mock/403 展示
- [x] **`/app/analytics/query`**:`AnalystQueryPanel` · `POST /api/analyst/chat` · 四角色权限内问数
- [ ] **Vitest**:`authStore` · SSE 解析已绿(19 例)· 可补 ChatPanel hook 测试
- [ ] **前端 P0 收尾**:接口契约发群 · simulate 交易 Disabled 菜单说明
@@ -57,6 +57,17 @@
- [x] **客服 RAG 接 fin_* 三库**:`VisitorRagService` → `search_cs_knowledge`(`fin_product` / `fin_policy` / `fin_faq`);宿主 Tool 仍走 `kb_product_rules`(T-21)
- [x] **tool 层实现 + 种子文档**:`document_parser` / `embedding_tool` / `milvus_tool` + `data/kb_collections/*`(2026-09-09)
### 数据分析 Agent · S3 接缝(2026-09-09 已接线)
> 清单:`docs/项目框架设计/数据分析Agent-合并说明.md` · merge `data-analysis-agent-work` · **773 passed**
- [x] **merge + 接缝**:`analyst_router` · `analyst_auth_adapter` · `analyst_schemas` · 废弃 `utils/auth.py`
- [x] **鉴权 G1–G4 + G8**:`assert_analyst_query_access` · customer `self` 域 · 尾注「AI 分析有风险」
- [x] **Wave6 测试**:`test_wave6_*` · +43 例
- [x] **前端问数页**:`web/src/pages/analytics/AnalystQueryPage.tsx` · `api/analyst.ts`
- [x] **迁移 SQL(本机)**:`scripts/agent/migrate-analyst-d07-d11.sql`(三资产表 · 2026-09-09 已执行 · **无种子数据**)
- [ ] **Scope B 冒烟**:四 Demo 角色各一条问数(需 MySQL + 可选 DeepSeek Key)
### 风控 Agent · 后端已就绪 · 前端/运维未接(盘点 2026-09-09)
**HTTP 接口(后端已实现 · 前端/演示未接)**
@@ -0,0 +1,358 @@
# 数据分析 Agent · 从 `data-analysis-agent-work` 合并说明
> 日期:2026-09-09
> 远程分支:`xinghuo/data-analysis-agent-work` @ `b19a241`(zyi)
> 原则:**只增不盖**;与 `merger` 同路径冲突时 **以 merger 为准**(不覆盖 `main.py` / `deps.py` / `chat.py` / `agent_service.py` 宿主骨架)。
> 状态:**接缝设计稿(未 merge、未接线)**
---
## 0. 一句话定位
数据分析 Agent = **NL2SQL 只读查数线**:自然语言 → 安全 SQL → 表格 + 解读 + 留痕;与宿主 **通用对话线**(`agent_service` 轻提示词 Chat)是两条不同产物,接缝上 **并行挂载**,不替换 `agent_service.py`。
---
## 1. 合并什么(新增文件清单)
从 `data-analysis-agent-work` **整包迁入**(无冲突或仅新增路径):
| 类别 | 路径 | 作用 |
| --- | --- | --- |
| 路由 | `app/api/analyst.py` | `/api/analyst/chat` · `/dashboard` · `/assets` · `/ops/metrics` |
| 编排 | `app/service/analyst_agent.py` | LangGraph StateGraph(input_guard → … → audit_persist) |
| 安全 | `app/service/sql_guard.py` `guardrail.py` | 五层 SQL 白名单 + 数字护栏 |
| 支撑 | `analytics_repo.py` `dict_service.py` `cache_service.py` `schema_meta.py` | 留痕/口径/缓存/元数据 |
| LLM | `app/service/llm.py` | DeepSeek 封装(分析线专用;与 merger `embedding.py` 不冲突) |
| 模型 | `app/model/schemas/analyst.py` | 输出四件套 Pydantic(answer/table/sql/meta) |
| 实体 | `app/model/entities/analytics.py`(若远程有) | analytics_* ORM 映射 |
| 工具 | `app/tool/sql_tool.py`(若远程有) | 只读执行 + 行数/超时 |
| 文档 | `数据分析Agent架构说明书.md` `数据分析Agent开发清单.md` `03-mysql-analyst专用.sql` | 架构 / 任务 / DDL |
| 测试 | 见 §6 | 重命名后迁入,避免覆盖 merger 730 基线 |
**不迁入(冲突 · 保留 merger):**
| 文件 | 原因 |
| --- | --- |
| `app/main.py` | AL-09 全量宿主入口;仅 **追加** `include_router(analyst_router)` |
| `app/api/deps.py` | merger 完整 JWT + 归属 + 矩阵(382 行);远程版被砍成 22 行 stub |
| `app/api/chat.py` `app/service/agent_service.py` | 四 Agent 通用对话线已集成 |
| `app/config/settings.py` | merger 含 risk/customer/visitor/JWT 全量;**追加** `analyst_*` 字段 |
| `app/model/schemas.py` | 保留单文件;分析专用模型放 **`schemas/analyst.py` 子模块**(见 §3.3) |
| `app/repository/core_ro.py` | merger 版含 `check_suitability` 等平台能力;分析线只 **调用** 不覆盖 |
| `app/utils/auth.py` | 远程第二套鉴权(`subject_id`);**废弃**,改 deps 适配(§4) |
| `docs/memory/*`、前端 `web/`、风控/平台 API | 全部保留 merger |
---
## 2. 数据库(需手工执行)
### 2.1 已有(merger canonical)
`docs/项目框架设计/表设计/02-mysql-agent专用.sql` 已含 **`analytics_query_log`**(NL2SQL 留痕)。merger `entities.py` 已有 ORM。**无需重复建表**(若本机已跑过 02 迁移)。
### 2.2 新增(分析资产三表 · D-07 / D-11)
远程 `03-mysql-analyst专用.sql` → 独立迁移脚本(不修改 canonical 02 文件):
```text
scripts/agent/migrate-analyst-d07-d11.sql
→ analytics_metric_dict 口径字典
→ analytics_few_shot 问题→SQL 示例
→ analytics_query_template 参数化模板
```
执行时机:merge 代码后、跑分析线单测/E2E 前;库 `jinrong_agent`,前置 01/02 共用底座已存在。
### 2.3 Redis
复用 merger Docker Redis(`:6380`)。分析线 `cache_service` 使用独立 key 前缀(对齐 `02-redis-keys.md` 分析段);与客服/游客/风控 key **命名空间隔离**,同实例不冲突。
---
## 3. S3 接缝设计(推荐方案 · 待你确认 §8)
### 3.1 入口策略:**并行双轨**(推荐)
| 入口 | 行为 | 前端页面 |
| --- | --- | --- |
| **`POST /api/analyst/chat`** | `AnalystAgent.run()` → JSON 四件套(table/sql/meta/disclaimer) | **`/app/analytics/query` 问数工作台**(主链路) |
| `GET /api/analyst/dashboard` | D-12 智能看数卡片 | `/app/analyst/home` 可接 metrics |
| `POST /api/analyst/assets` | D-11 资产沉淀 | 问数页「沉淀」按钮(P1 后做) |
| `GET /api/analyst/ops/metrics` | N-08 运营指标 | 分析员运维面板(P1 后做) |
| `POST /api/chat` + `X-Agent-Type: analyst` | **仍走 `agent_service`**(轻量对话 stub,无 NL2SQL) | **`/app/analytics/chat` 分析对话**(可选保留) |
**为何不把 analyst 并入 `chat.py` 分流?**
- 问数响应含 **结构化 table + sql + meta**,与 Chat 的 `{reply, has_disclaimer}` 形状不同;硬塞进 SSE 需额外 adapter 层,收益低。
- 架构说明书 §13 已定义独立 `/api/analyst/*`;与客服 S2「customer 分流」场景不同(customer 仍是 Chat 形态)。
**`main.py` 改动(唯一宿主挂载点):**
```python
from app.api.analyst import router as analyst_router
app.include_router(analyst_router) # prefix 已在 analyst.py 内:/api/analyst
```
### 3.2 鉴权接缝:**deps 为准 + 薄适配**
远程代码混用 `deps.get_auth_context` 与 `utils.auth.AuthContext`(字段 `subject_id` vs `actor_id`)。合并后:
1. **删除**迁入后的 `app/utils/auth.py`(或保留空 shim 仅 re-export,最终删除)。
2. 新增 `app/api/analyst_auth_adapter.py`(或扩展现有 `auth_adapter.py`):
```python
def analyst_auth_from_deps(ctx: deps.AuthContext) -> AnalystAuthView:
"""deps.AuthContext → 分析线内部视图(仅字段映射,不做权限判定)。"""
return AnalystAuthView(
subject_id=ctx.actor_id, # 分析 repo 写 staff_id 用
roles=ctx.roles,
permissions=ctx.permissions,
token_type=ctx.token_type,
trace_id=current_trace() or "",
agent_type="analyst",
)
```
3. `analyst.py` 路由层:`auth = Depends(get_auth_context)` → 适配后传入 `AnalystAgent.run()`。
4. 远程 `assert_analyst_access(auth)` 逻辑 **迁入** `deps.py` 或 `app/utils/authz.py`(与 `assert_customer_access` 并列),使用 `actor_id` 命名。
5. **`X-Agent-Type`**:`/api/analyst/*` **不要求**该头(路由即 analyst);JWT 矩阵仍校验 token 角色含 `analyst`/`compliance`/`advisor`/`risk_officer`/`ops`(按远程 `assert_analyst_access` 域划分)。
### 3.3 Schemas:**单文件 + 子模块共存**
merger 当前 `app/model/schemas.py` 为单文件。合并策略:
- **新增** `app/model/schemas/analyst.py`(仅分析请求/响应)。
- **不**把 `schemas.py` 改成包(避免大面积 import 破坏);`analyst.py` 路由 `from app.model.schemas.analyst import ...` 直引子路径。
- 若 Python 包解析与 `schemas.py` 文件冲突:将子模块改为 `app/model/analyst_schemas.py`(备选,merge 时二选一)。
### 3.4 settings 追加字段(合并冲突手工解)
在 merger `settings.py` **追加**(取自远程,命名对齐架构说明书):
| 字段 | 用途 |
| --- | --- |
| `analyst_llm_model` / `analyst_llm_timeout` | DeepSeek NL2SQL |
| `analyst_sql_row_limit` / `analyst_sql_timeout_sec` | 只读执行上限 |
| `analyst_cache_ttl_sec` | 结果缓存 |
| `analyst_rate_limit_per_min` | 问数限流 |
`.env.example` 同步追加;**不**覆盖 merger 已有 `jwt_*` / `risk_*` / `customer_*`。
### 3.5 requirements
merger 已有 `langgraph`;**追加** `sqlglot>=25.0.0`(远程新增,SQL AST 白名单硬依赖)。
### 3.6 core_ro / llm 边界
| 模块 | 策略 |
| --- | --- |
| `core_ro.py` | 保留 merger;`analytics_repo.execute_readonly` 走 **agent 库只读账号** 或现有 Core RO 连接,不 duplicate |
| `llm.py` | 迁入分析线专用;`AnalystAgent` 只 import 此模块 |
| `input_guard` | 分析线复用 merger `app/service/input_guard.py`(限流/注入),不保留远程 duplicate `utils/input_guard.py` |
---
## 4. 前端接缝(merger 现状 → 目标)
### 4.1 现状
| 路由 | 现状 |
| --- | --- |
| `/app/analytics/query` | **PlaceholderPage**(待 NL2SQL) |
| `/app/analytics/chat` | `ChatPanel` + `agentType="analyst"` → `POST /api/chat/stream`(无 table/sql) |
### 4.2 推荐(与 §3.1 双轨一致)
| 页面 | API | UI |
| --- | --- | --- |
| **问数工作台** `/app/analytics/query` | `POST /api/analyst/chat` | 新问题组件:`AnalystQueryPanel`(问题框 + 解读 + Ant Design Table + SQL 折叠 + disclaimer) |
| **分析对话** `/app/analytics/chat` | 暂保留 `ChatPanel` SSE | 页顶 Banner 提示:「复杂查数请用问数工作台」;P2 可下线或改跳转 |
新增 `web/src/api/analyst.ts`:
```typescript
export interface AnalystChatResponse {
answer: string
table: { columns: string[]; rows: unknown[][] }
sql: string
meta: { exec_ms: number; row_count: number; cache_hit: boolean; data_as_of?: string }
disclaimer: string
status: string
trace_id?: string
}
```
**不**强行让 `ChatPanel` 解析四件套(职责分离)。
### 4.3 首页看板
`AnalystMarketDashboard` QuickAction「问数工作台」已指向 `/app/analytics/query`;merge 后可接 `GET /api/analyst/dashboard` 填充 metrics 卡片(P1)。
---
## 5. 需求 ID 对齐
| 需求 | 合并后落点 | 阶段 |
| --- | --- | --- |
| D-01~D-04 | `analyst_agent` + `sql_guard` + `analytics_query_log` | merge 后冒烟 |
| D-05 | 多表只读聚合(sql_generate 节点) | 同上 |
| D-06 | `cache_service` 双层缓存 | 同上 |
| D-07 | `dict_service` + `analytics_metric_dict` | 需跑 migrate-analyst |
| D-08~D-10 | 拒答分支 + `guardrail` | 同上 |
| D-11 | `/assets` + 三资产表 | migrate + 前端 P1 |
| D-12 | `/dashboard` | 首页 P1 |
| N-01~N-08 | 见架构说明书 §4/§6 | 单测覆盖 |
---
## 6. 测试策略
### 6.1 远程测试文件 → 迁入命名
| 远程 | 迁入后 | 说明 |
| --- | --- | --- |
| `tests/test_agent.py` | `tests/test_wave6_analyst_agent.py` | 改 import:`utils.auth` → `analyst_auth_adapter` |
| `tests/test_sql_guard.py` | `tests/test_wave6_sql_guard.py` | 无冲突,直迁 |
| `tests/test_guardrail.py` | `tests/test_wave6_guardrail.py` | 若 merger 已有同名则合并 case |
| `tests/test_dict_service.py` | `tests/test_wave6_dict_service.py` | |
| `tests/test_cache_service.py` | `tests/test_wave6_cache_service.py` | merger 已有 `test_cache_service.py` → **合并或 rename 远程** |
| `tests/test_llm.py` | `tests/test_wave6_analyst_llm.py` | 避免与 merger LLM 测试混淆 |
### 6.2 基线要求
- merge + 适配完成后:`python -m pytest` → **730 + N passed**(N = 新增 wave6 条数)。
- 禁止恢复远程对 `deps.py` / `conftest.py` 的删减。
### 6.3 冒烟清单(Scope B · merge 后)
1. `POST /api/analyst/chat` + analyst JWT → 200 + 四件套 JSON
2. 顾问 token 查他人客户 → 403 + `analytics_query_log(blocked)`
3. `GET /api/analyst/dashboard` → 200 + cards
4. 前端 `/app/analytics/query` 真跑一条问数
5. `POST /api/chat/stream` + analyst → 仍 200(stub 未回归)
---
## 7. Merge 操作顺序(执行 SOP · 尚未做)
```text
1. git worktree add ../JinRong-analyst-merge data-analysis-agent-work # 可选隔离
2. git merge data-analysis-agent-work --no-commit # 或 cherry-pick 新增文件
3. 冲突文件按 §1「不迁入」表逐项保留 merger 版
4. 手工:analyst_auth_adapter · main.py include_router · settings 追加 · requirements sqlglot
5. 删除 utils/auth.py · 改 analyst.py / analyst_agent.py import
6. 执行 scripts/agent/migrate-analyst-d07-d11.sql
7. pytest 全绿 → 前端 AnalystQueryPanel → Scope B 冒烟
8. 更新 docs/memory/TODO.md · FRAMEWORK 实现状态表
```
---
## 8. 已确认决策(2026-09-09)
| # | 决策 | 结论 |
| --- | --- | --- |
| **Q1** | 问数主入口 | **A · 独立 `POST /api/analyst/chat`**;各角色在权限范围内自助查数(见 §9) |
| **Q2** | `/app/analytics/chat` | **A · 保留** ChatPanel stub + Banner 引导问数工作台 |
| **Q3** | schemas 路径 | **A · `app/model/schemas/analyst.py`**(merge 时若与 `schemas.py` 包冲突再改 `analyst_schemas.py`) |
| **Q4** | merge 方式 | **A · `git merge` + 手工解冲突**(默认,未单独拍板则按此执行) |
---
## 9. 鉴权接缝 · 角色矩阵与缺口(**merge 前必须补**)
### 9.1 目标口径(产品确认)
> **每个 Demo 账户角色都能调用数据分析模块,只看其权限范围内的数据。**
前端已把「数据分析」放进 **客户 / 理财师 / 风控** 的 `sharedPlatform` 菜单(`/app/analytics/query`);后端远程实现 **尚未覆盖 customer**,且与 merger `deps` 矩阵不一致,需在 S3 接缝一并补齐。
### 9.2 目标角色 × 数据域(接缝后应达到)
> Demo 仅四角色:**customer · advisor · analyst · risk_officer**(无 compliance / risk_manager 独立账号;问数线不单独开域)。
| 角色 | token | 数据域 key | 可见范围 | sql_guard 要点 |
| --- | --- | --- | --- | --- |
| **customer** | customer | **`self`(待实现)** | 仅本人可读数据(持仓/流水/风评/净值趋势等) | 禁止其他 `CUST-*`;强制 `customer_id = auth.customer_id`;**见 §9.6 产品约束** |
| **advisor** | staff | `assigned` | 名下客户(`core_customer_advisor` active) | 远程已有;`inject_ownership` + 白名单 |
| **analyst** | staff | `full` | 全量 + 敏感列策略 | 远程已有 |
| **risk_officer** | staff | `risk` | 预警台账全量 + 客户/持仓/交易只读(脱敏列) | 远程已有;**表域评估见 §9.7** |
| **ops** | staff | `aggregate` | 无客户维度,仅聚合 | 远程已有(非 Demo 角色,预留) |
### 9.6 客户问数 · 产品约束(2026-09-09 确认)
客户走 **`self` 域**,能力边界如下(merge 时在 `analyst_agent` 解读阶段 + prompt 硬约束):
| 项 | 口径 |
| --- | --- |
| **允许** | 基于本人可读数据的**趋势总结、分布描述、数量统计**(如持仓结构、近 N 日交易笔数、盈亏区间描述) |
| **禁止** | **投资建议、收益承诺、买卖时点、产品推荐**;命中则拒答或降级为「仅展示表格」 |
| **尾部声明** | 在标准 `disclaimer` 之外,客户域回复**追加**:「**AI 分析有风险,仅供参考。**」 |
| **留痕** | `analytics_query_log.actor_id = customer_id`,`actor_role = customer` |
实现落点(merge 时):
- `scope_resolve`:`token_type=customer` → `domain=self`,`scope=[auth.customer_id]`
- `sql_guard.validate(domain=self)`:同 advisor 归属逻辑,但 scope 固定单人
- `_generate_sql` / `ANSWER_SYSTEM`:注入「仅描述数据、不给建议」
- `answer_compose`:`domain=self` 时 append 客户专用尾注
### 9.7 风控专员 · 表域是否够用(2026-09-09 评估)
**结论:够用。** 风控 Agent **自动监督触发的结果**(规则引擎出单、AML 命中、适当性拦截、集中度/代理人行为链等)**权威落点都在现有白名单内**;风控专员问数线是**只读查台账与关联上下文**,不通过 NL2SQL 触发处置或扫名单。
| 监督链路(风控 Agent / 引擎) | 落库/可读表 | 已在 sql_guard 白名单 |
| --- | --- | --- |
| RISK-001~006 交易/持仓规则 | `risk_alert`(`alert_type` + `payload`/`triggered_rules`) | ✅ |
| RISK-006 集中度 | `risk_alert`(`pattern` + `payload.alert_subtype=concentration`)+ `core_holding` | ✅ |
| RISK-008 代理人行为链 | `risk_alert`(`pattern` + `payload.alert_subtype=agent_behavior`) | ✅ |
| AML 命中 | `risk_alert`(`alert_type=aml`)+ `customer_profile_l3` 标记 | ✅ |
| 适当性拦截 | `risk_alert`(`alert_type=suitability`) | ✅ |
| 客户监测画像 | `customer_profile_l1/l2/l3` | ✅ |
| 关联上下文(客户/持仓/交易/产品) | `core_*` 系列 | ✅ |
**刻意不进白名单(安全/职责分离):**
| 表 | 原因 |
| --- | --- |
| `risk_aml_list` | 反洗钱**名单本体**;命中结果已在 `risk_alert`,不应 NL2SQL 直查名单库 |
| `audit_log` | 审计明细走 HTTP/专用 Tool;问数以 `risk_alert` 聚合即可 |
**可选 P2(非阻塞 merge):** 若后续要问「适当性判定历史通过率/明细」,可把 `risk_suitability_log` 加入白名单并在 `risk` 域只读;当前 Demo 问数(待处理预警数、按类型分布、客户监测分布)**不依赖**该表。
**域规则:** 维持远程 `domain=risk`(台账全量 + 白名单内客户只读),**不新增** compliance/manager 域;与现网风控 Demo(`STAFF-30001` · risk_officer)一致即可。
### 9.3 远程 vs merger 缺口清单
| 缺口 | 远程现状 | merger 现状 | 接缝动作(记入 TODO) |
| --- | --- | --- | --- |
| **G1 customer 不可用** | `assert_analyst_access` 要求 `token_type=staff` | 平台 G-01 支持 customer 本人 | 新增域 `self`;`/api/analyst/*` 入口允许 customer token |
| **G2 sql_guard 无 self 域** | 仅 full/assigned/risk/aggregate | — | `validate()` + `inject_ownership()` 支持 `self` + 强制 `customer_id = auth.customer_id` |
| **G3 双套 AuthContext** | `subject_id` + `utils/auth.py` | `actor_id` + `deps.py` | §3.2 适配器;**废弃** remote auth |
| **G4 入口矩阵分裂** | `/api/analyst/*` 用 `ANALYST_ROLES` 四角色 | `AGENT_ACCESS_MATRIX["analyst"]` 仅 analyst+compliance(**对话线**) | 问数路由 **不走** chat 矩阵;`assert_analyst_query_access()` 覆盖 **customer/advisor/analyst/risk_officer** |
| ~~**G5**~~ | — | — | **已关闭**:无 compliance/risk_manager Demo 角色,不单独开域 |
| **G6 留痕 actor** | `staff_id=subject_id` | customer 无 staff_id | `analytics_query_log` 写 `actor_id` + `actor_role`;customer 写 `customer_id` |
| **G7 前端 PermissionGate** | — | 问数页仍 Placeholder | 403 展示可查范围引导(对齐 `AnalystResponse.suggestions`) |
| **G8 客户解读约束** | 远程无 customer 域 | — | §9.6:趋势总结 · 禁建议 · 尾部「AI 分析有风险」 |
### 9.4 推荐实现落点(merge 时)
```text
app/utils/authz.py # 新增 assert_analyst_query_access(auth) → domain
app/service/sql_guard.py # 扩展 domain=self(§9.6 客户约束)
app/api/analyst.py # Depends(get_auth_context) → assert_analyst_query_access → adapter → agent.run
app/api/analyst_auth_adapter.py # actor_id ↔ 分析线内部视图(可选 NamedTuple)
```
**`/api/chat` analyst stub** 仍走现有 `AGENT_ACCESS_MATRIX`(analyst/compliance 轻聊,Demo 无 compliance 账号);与问数线 **刻意分离**。
---
## 10. 修订记录
| 日期 | 说明 |
| --- | --- |
| 2026-09-09 | 初稿:远程 `b19a241` 审阅 + merger 730 基线对照;**未 merge** |
| 2026-09-09 | Q1=A Q2=A;§9 鉴权缺口与角色矩阵(含 customer self 域待实现) |
| 2026-09-09 | 产品拍板:§9.6 客户趋势总结/禁建议/AI 风险尾注;§9.7 risk 表域够用;关闭 G5 |