|
|
@@ -0,0 +1,156 @@
|
|
|
+# 报告快速分析功能设计
|
|
|
+
|
|
|
+日期:2026-09-10
|
|
|
+状态:已批准
|
|
|
+
|
|
|
+## 1. 背景与目标
|
|
|
+
|
|
|
+报告详情页(`report-detail.vue`)目前只有被动的数据展示,用户无法针对报告内容主动提问。本功能提供「快速分析」入口,让用户输入问题,AI 基于当前报告内容做初步解读。
|
|
|
+
|
|
|
+**免费版价值主张**:免费用户也能体验 AI 报告解读(每日 1 次),作为免费版的核心功能之一,同时引导升级付费版。
|
|
|
+
|
|
|
+### 目标
|
|
|
+
|
|
|
+- 免费版(FREE):每日 1 次快速分析,按天重置(Redis 计数)
|
|
|
+- 付费版(FAMILY/PROVIDER):多轮对话,不限制次数
|
|
|
+
|
|
|
+### 非目标
|
|
|
+
|
|
|
+- 不做完整的多轮会话管理界面(复用现有 `pages/ai/chat.vue` 的能力范围之外)
|
|
|
+- 不做报告深度解析/结构化解读(仅问答式初步解读)
|
|
|
+- 不修改 LangGraph `chat_graph`(零 Python 改动)
|
|
|
+
|
|
|
+## 2. 架构设计
|
|
|
+
|
|
|
+复用现有 AI 链路,最小新增。核心思路:**后端加一个端点 + Redis 计数,前端加按钮和浮层,LangGraph 零改动**。
|
|
|
+
|
|
|
+```
|
|
|
+report-detail.vue(底部按钮 + 浮层)
|
|
|
+ │ POST /api/ai/report/analyze { query, reportId, conversationId?, memberId? }
|
|
|
+ ▼
|
|
|
+AIChatController 新增端点 /report/analyze
|
|
|
+ │ 1. 查 User.memberLevel(FREE/FAMILY/PROVIDER)
|
|
|
+ │ 2. FREE → Redis INCR dailyKey,>1 拒绝
|
|
|
+ │ 3. familyContextService.buildContext(userId, reportId) 组装报告上下文
|
|
|
+ │ 4. AiGateway.chat() → LangGraph /api/v1/chat
|
|
|
+ ▼
|
|
|
+LangGraph chat_graph(现有,零改动)
|
|
|
+ classify → load_context → llm_call → answer
|
|
|
+```
|
|
|
+
|
|
|
+### 数据流
|
|
|
+
|
|
|
+1. 前端浮层用户输入问题,调用 `POST /api/ai/report/analyze`
|
|
|
+2. 后端校验会员级别与免费次数
|
|
|
+3. 组装报告上下文(`familyContextService.buildContext(userId, reportId)` 已支持报告注入)
|
|
|
+4. 调 `AiGateway.chat()` → LangGraph `/api/v1/chat`
|
|
|
+5. LangGraph `chat_graph` 现有流程返回 `answer`
|
|
|
+
|
|
|
+## 3. 关键决策
|
|
|
+
|
|
|
+### 3.1 复用现有链路,不新建 LangGraph graph
|
|
|
+
|
|
|
+- 现有 `chat_graph` 已支持 `context.report_id` 注入(`chat_graph.py` 129-137 行:`report_id` → 家庭上下文 SystemMessage)
|
|
|
+- `familyContextService.buildContext(userId, reportId)` 已组装报告相关上下文(`health_reports` + indicators)
|
|
|
+- `AiGateway.chat(query, userId, conversationId, inputs)` 已透传 context
|
|
|
+
|
|
|
+**结论**:后端一个端点 + 前端浮层即完成,LangGraph 零改动。
|
|
|
+
|
|
|
+### 3.2 新增端点 `/api/ai/report/analyze`(而非复用 `/chat/send`)
|
|
|
+
|
|
|
+- `/chat/send` 承载家庭助手通用聊天,职责过重
|
|
|
+- 新端点专门做「报告快速分析」:会员限制 + 报告上下文 + 返回 answer
|
|
|
+- 复用 `sendMessage` 内部已有的上下文组装逻辑(mascot/portrait),抽出私有方法 `buildChatInputs(userId, params)`
|
|
|
+
|
|
|
+### 3.3 免费次数限制:Redis 计数
|
|
|
+
|
|
|
+```java
|
|
|
+String key = "report:analyze:free:" + userId + ":" + LocalDate.now();
|
|
|
+Long count = redisTemplate.opsForValue().increment(key);
|
|
|
+if (count == 1) redisTemplate.expire(key, Duration.ofDays(1));
|
|
|
+if (count > 1) return Result.error("免费版每日仅限 1 次快速分析,升级会员解锁更多");
|
|
|
+```
|
|
|
+
|
|
|
+- **Redis 不可用降级**:Redis 异常时 fail-open 放行(避免免费用户因 Redis 故障完全不可用),记 `log.warn`
|
|
|
+- 生产 Redis 连接由用户提供(host/port/password),实现时填入 `application.yml`
|
|
|
+
|
|
|
+### 3.4 免费版锁死多轮
|
|
|
+
|
|
|
+- 付费版:前端回传 `conversationId` → LangGraph 保持上下文 → 可追问
|
|
|
+- 免费版:前端**每次不带** `conversationId`(不传则 LangGraph 每次新建会话)→ 天然单次
|
|
|
+
|
|
|
+### 3.5 前端浮层(页面内嵌,不新建页面)
|
|
|
+
|
|
|
+- `report-detail.vue` 底部新增「🎯 快速分析」悬浮按钮
|
|
|
+- 点击展开浮层:消息区 + 输入框 + 3 个快捷问题 chips
|
|
|
+- 免费版发送一次后输入框禁用,显示「今日次数已用完」
|
|
|
+- 付费版可连续追问(每轮带 conversationId 回传)
|
|
|
+
|
|
|
+## 4. 改动清单
|
|
|
+
|
|
|
+### 后端(cfc-backend)
|
|
|
+
|
|
|
+| 文件 | 改动 |
|
|
|
+|------|------|
|
|
|
+| `pom.xml` | 新增 `spring-boot-starter-data-redis` 依赖 |
|
|
|
+| `application.yml` | Redis 连接配置 |
|
|
|
+| `RedisConfig.java`(新增 `config/`) | RedisTemplate bean,String 序列化 |
|
|
|
+| `AIChatController.java` | 新增 `POST /report/analyze` 端点:会员检查 + Redis 计数 + 上下文组装 + 调 AiGateway。复用 `sendMessage` 的上下文组装逻辑(抽出私有方法) |
|
|
|
+
|
|
|
+端点签名:
|
|
|
+
|
|
|
+```json
|
|
|
+POST /api/ai/report/analyze
|
|
|
+Request: { "query": "..." , "reportId": 123, "conversationId": "可选", "memberId": "可选" }
|
|
|
+Response: Result<{ answer, conversationId }>
|
|
|
+```
|
|
|
+
|
|
|
+### 前端(cfc-frontend)
|
|
|
+
|
|
|
+| 文件 | 改动 |
|
|
|
+|------|------|
|
|
|
+| `report-detail.vue` | 新增底部「🎯 快速分析」按钮 + 浮层组件(消息区/输入框/快捷问题/加载态/禁用态) |
|
|
|
+| `api.js` | 新增 `reportQuickAnalyze(query, reportId, conversationId)` 封装 |
|
|
|
+
|
|
|
+## 5. 会员判定
|
|
|
+
|
|
|
+- 后端判定源:`User.memberLevel`(FREE/FAMILY/PROVIDER)
|
|
|
+- JwtInterceptor 已从 token 提取 `userId`,端点内 `userService.getUserInfo(userId)` 取 `memberLevel`
|
|
|
+- 前端也可用 `getMyMembership()` 预判(仅用于 UI 展示,最终以后端校验为准)
|
|
|
+
|
|
|
+## 6. 错误处理
|
|
|
+
|
|
|
+| 场景 | 行为 |
|
|
|
+|------|------|
|
|
|
+| query 为空 | `Result.error("消息不能为空")` |
|
|
|
+| reportId 为空 | `Result.error("报告ID不能为空")` |
|
|
|
+| 免费版超限 | `Result.error("免费版每日仅限 1 次快速分析,升级会员解锁更多")` |
|
|
|
+| Redis 异常(免费计数失败) | fail-open 放行 + `log.warn("Redis 计数失败,放行: {}", e.getMessage())` |
|
|
|
+| AiGateway 返回 null(熔断/超时) | `Result.error("AI 服务暂不可用,请稍后重试")` |
|
|
|
+| LangGraph 调用异常 | 同上,由 AiGateway 捕获后返回 null,端点统一处理 |
|
|
|
+
|
|
|
+## 7. 验证计划
|
|
|
+
|
|
|
+### 后端
|
|
|
+
|
|
|
+```bash
|
|
|
+cd cfc-backend && mvn clean compile
|
|
|
+```
|
|
|
+
|
|
|
+### 手动验证
|
|
|
+
|
|
|
+| 场景 | 预期 |
|
|
|
+|------|------|
|
|
|
+| 免费版第 1 次提问 | 正常返回 answer |
|
|
|
+| 免费版第 2 次提问 | 返回错误「免费版每日仅限 1 次…」 |
|
|
|
+| 免费版次日 | 计数重置,可再问 |
|
|
|
+| 付费版多轮 | 追问时带 conversationId,AI 记得上文 |
|
|
|
+| 报告上下文注入 | 回答体现报告内容(如指标/菌群) |
|
|
|
+| Redis 停掉 | 免费版仍可用(fail-open),记 warn 日志 |
|
|
|
+
|
|
|
+## 8. 风险与注意事项
|
|
|
+
|
|
|
+1. **Redis 是新增基础设施**:后端首次引入,需确认生产 Redis 可用;本地开发需 Docker/本地 Redis 或后端 `redis.host` 指向测试环境
|
|
|
+2. **免费版滥用**:按 userId 计数,同一家庭多账号可分别获得 1 次额度(可接受的初期策略)
|
|
|
+3. **上下文长度**:报告 payload 可能较大,`familyContextService.buildContext` 已做截断控制(沿 `sendMessage` 既有逻辑,无需新增)
|
|
|
+4. **AiGateway 熔断共享**:快速分析与通用聊天共享同一个 AiGateway 熔断器,若聊天高频失败会连带影响快速分析(可接受的现状)
|