Procházet zdrojové kódy

docs: 报告快速分析扩展实现计划

iwt před 6 dny
rodič
revize
e20c4d7d25

+ 561 - 0
docs/superpowers/plans/2026-09-13-report-quick-analyze-extend.md

@@ -0,0 +1,561 @@
+# 报告快速分析扩展实现计划
+
+> **面向 AI 代理的工作者:** 必需子技能:使用 superpowers:subagent-driven-development(推荐)或 superpowers:executing-plans 逐任务实现此计划。步骤使用复选框(`- [ ]`)语法来跟踪进度。
+
+**目标:** 在 `gut-flora-detail.vue` 和 `report-confirm.vue` 新增「报告快速分析」入口,后端支持 `draftId` 与 `reportId` 双模式,复用现有免费版限制与 AI 链路。
+
+**架构:** 后端 `FamilyContextService` 新增 `buildContextFromDraft`,`AIChatController` 扩展 `buildChatInputs` 与 `reportAnalyze` 校验;前端 `api.js` 扩展 `reportQuickAnalyze` 可选 `draftId`,两个 Vue 页面复用 `report-detail.vue` 的浮层结构与样式。
+
+**技术栈:** Spring Boot 2.7.18 (Java 8) + MyBatis-Plus + Redis + uni-app Vue 2 小程序(Options API,禁可选链/禁 CSS Grid/禁 `:key` 表达式)
+
+---
+
+## 文件结构
+
+### 后端(cfc-backend)
+
+| 文件 | 操作 | 职责 |
+|------|------|------|
+| `src/main/java/com/etotem/cfc/service/FamilyContextService.java` | 修改 | 注入 `HealthReportDraftService` + `ObjectMapper`,新增 `buildContextFromDraft(userId, draftId)` |
+| `src/main/java/com/etotem/cfc/controller/ai/AIChatController.java` | 修改 | 1. `buildChatInputs`:识别 `draftId` 参数,调用新方法<br>2. `reportAnalyze`:校验 `reportId || draftId`,为空则报错 |
+
+### 前端(cfc-frontend)
+
+| 文件 | 操作 | 职责 |
+|------|------|------|
+| `utils/api.js` | 修改 | `reportQuickAnalyze(query, reportId, conversationId, draftId)` 新增第四参数 |
+| `pages/health/gut-flora-detail.vue` | 修改 | 复用浮层逻辑/样式,FAB 条件 `!isEditing && (reportId || draftId)` |
+| `pages/health/report-confirm.vue` | 修改 | 复用浮层逻辑/样式,FAB 条件 `!parsing && draftId` |
+
+---
+
+## 任务 1:后端 FamilyContextService 新增 buildContextFromDraft
+
+**文件:**
+- 修改:`cfc-backend/src/main/java/com/etotem/cfc/service/FamilyContextService.java`
+
+**背景:** 现有 `buildContext(userId, reportId)` 读已入库报告;草稿场景需读 `HealthReportDraft.payloadJson` 并解析为 `Map` 注入上下文。
+
+- [ ] **步骤 1:新增 import**
+
+在 import 区(现有 import 在 20-62 行)添加:
+```java
+import com.etotem.cfc.service.HealthReportDraftService;
+import com.fasterxml.jackson.databind.ObjectMapper;
+```
+
+- [ ] **步骤 2:注入依赖**
+
+在 `@Resource private ReportSurveyService reportSurveyService;`(约 36 行)之后添加:
+```java
+    @Resource
+    private HealthReportDraftService healthReportDraftService;
+
+    @Resource
+    private ObjectMapper objectMapper;
+```
+
+- [ ] **步骤 3:新增 buildContextFromDraft 方法**
+
+在 `buildContext(Long userId, Long reportId, Long surveyId)` 方法结束后(约 222 行)添加:
+```java
+    /**
+     * 基于草稿 ID 构建上下文(用于快速分析)
+     *
+     * @param userId  当前用户
+     * @param draftId 草稿 ID
+     * @return 上下文 Map,包含 reportDetail(从 payloadJson 还原)
+     */
+    public Map<String, Object> buildContextFromDraft(Long userId, Long draftId) {
+        Map<String, Object> ctx = buildContext(userId);
+
+        if (draftId == null) {
+            return ctx;
+        }
+
+        HealthReportDraft draft = healthReportDraftService.getDraftById(draftId);
+        if (draft == null || !draft.getUserId().equals(userId)) {
+            // 权限不通过静默返回基础上下文
+            return ctx;
+        }
+
+        // 解析 payloadJson
+        Map<String, Object> reportDetail = null;
+        try {
+            if (draft.getPayloadJson() != null && !draft.getPayloadJson().isEmpty()) {
+                reportDetail = objectMapper.readValue(draft.getPayloadJson(), Map.class);
+            }
+        } catch (Exception e) {
+            log.warn("解析草稿 payload 失败: {}", e.getMessage());
+        }
+        if (reportDetail != null) {
+            ctx.put("reportDetail", reportDetail);
+        }
+
+        return ctx;
+    }
+```
+
+- [ ] **步骤 4:编译验证**
+
+运行:`cd cfc-backend && mvn clean compile -q -Dmaven.test.skip=true`
+预期:BUILD SUCCESS(无输出即成功)
+
+- [ ] **步骤 5:Commit**
+
+```bash
+git add cfc-backend/src/main/java/com/etotem/cfc/service/FamilyContextService.java
+git commit -m "feat: FamilyContextService 新增 buildContextFromDraft 支持草稿上下文"
+```
+
+---
+
+## 任务 2:后端 AIChatController 扩展 buildChatInputs + reportAnalyze
+
+**文件:**
+- 修改:`cfc-backend/src/main/java/com/etotem/cfc/controller/ai/AIChatController.java`
+
+**背景:** 现有 `buildChatInputs` 只处理 `reportId`/`surveyId`/`selfCheckId`;需加 `draftId` 分支。`reportAnalyze` 校验需接受 `draftId`。
+
+- [ ] **步骤 1:修改 buildChatInputs 增加 draftId 处理**
+
+在 `buildChatInputs` 方法内(约 136-207 行),`surveyId` 解析之后(约 154 行)添加:
+```java
+        String draftIdStr = params.get("draftId");
+        Long draftId = null;
+        if (draftIdStr != null && !draftIdStr.trim().isEmpty()) {
+            draftId = Long.valueOf(draftIdStr);
+        }
+```
+
+修改上下文组装分支(约 155-162 行):
+```java
+        Map<String, Object> inputs;
+        if (reportId != null) {
+            inputs = familyContextService.buildContext(userId, reportId);
+        } else if (draftId != null) {
+            inputs = familyContextService.buildContextFromDraft(userId, draftId);
+        } else {
+            inputs = familyContextService.buildContext(userId);
+        }
+```
+
+- [ ] **步骤 2:修改 reportAnalyze 校验**
+
+在 `reportAnalyze` 方法内(约 213-265 行),`reportIdStr` 获取后(约 215 行)添加 `draftIdStr` 获取,修改校验为:
+```java
+        String reportIdStr = params.get("reportId");
+        String draftIdStr = params.get("draftId");
+        if ((reportIdStr == null || reportIdStr.trim().isEmpty())
+                && (draftIdStr == null || draftIdStr.trim().isEmpty())) {
+            return Result.error("报告ID或草稿ID不能为空");
+        }
+        Long reportId = null;
+        if (reportIdStr != null && !reportIdStr.trim().isEmpty()) {
+            reportId = Long.valueOf(reportIdStr);
+        }
+```
+原有 `Long reportId = Long.valueOf(reportIdStr);`(约 222 行)删除。
+
+- [ ] **步骤 3:编译验证**
+
+运行:`cd cfc-backend && mvn clean compile -q -Dmaven.test.skip=true`
+预期:BUILD SUCCESS
+
+- [ ] **步骤 4:Commit**
+
+```bash
+git add cfc-backend/src/main/java/com/etotem/cfc/controller/ai/AIChatController.java
+git commit -m "feat: AIChatController 支持 draftId 参数的快速分析"
+```
+
+---
+
+## 任务 3:前端 api.js 扩展 reportQuickAnalyze
+
+**文件:**
+- 修改:`cfc-frontend/utils/api.js:1420`
+
+- [ ] **步骤 1:修改函数签名**
+
+```javascript
+// 报告快速分析(免费版每日1次,付费版多轮)
+export const reportQuickAnalyze = (query, reportId, conversationId, draftId) => {
+  var data = { query: query }
+  if (reportId) data.reportId = reportId
+  if (draftId) data.draftId = draftId
+  if (conversationId) data.conversationId = conversationId
+  return request('/api/ai/report/analyze', 'POST', data)
+}
+```
+
+- [ ] **步骤 2:语法校验**
+
+运行:`node -e "const fs=require('fs');const src=fs.readFileSync('cfc-frontend/utils/api.js','utf8');const m=src.match(/export const reportQuickAnalyze[\s\S]*?\n}/);if(!m){console.log('FAIL');process.exit(1)}console.log('OK')"`
+预期:OK
+
+- [ ] **步骤 3:Commit**
+
+```bash
+git add cfc-frontend/utils/api.js
+git commit -m "feat(frontend): api.js reportQuickAnalyze 新增可选 draftId 参数"
+```
+
+---
+
+## 任务 4:前端 gut-flora-detail.vue 新增快速分析浮层
+
+**文件:**
+- 修改:`cfc-frontend/pages/health/gut-flora-detail.vue`
+
+**策略:** 直接复制 `report-detail.vue` 的 `quickAnalyze` data/computed/methods/template/style,仅改入口条件与 import。
+
+- [ ] **步骤 1:import 新增**
+
+第 329 行 import 行添加 `reportQuickAnalyze, getMyMembership`:
+```javascript
+import { getReportDetail, editHealthReport, queryKnowledgeBase, queryIndicatorKnowledge, getGutFloraAnalysis, confirmReportPreview, discardReportDraft, parseReportDraft, getHealthFoods, getFoodCautionList, getFamilyMemberList, addFamilyMember, reportQuickAnalyze, getMyMembership } from '../../utils/api.js'
+```
+
+- [ ] **步骤 2:data 新增 quickAnalyze 对象**
+
+在 `extractedAge: ''`(约 394 行)之后添加:
+```javascript
+      // 报告快速分析浮层
+      quickAnalyze: {
+        show: false,
+        sending: false,
+        messages: [],      // [{role:'user'|'ai', content}]
+        inputText: '',
+        conversationId: '',
+        usedToday: false    // 免费版今日已用(前端预判,最终以后端为准)
+      }
+```
+
+- [ ] **步骤 3:computed 新增 quickQuestions**
+
+在 `draftMemberText` computed 后(约 460 行)添加:
+```javascript
+    quickQuestions: function() {
+      return [
+        '这份报告最需要关注哪些异常指标?',
+        '报告中提示了哪些健康风险?',
+        '有哪些改善建议?'
+      ]
+    }
+```
+
+- [ ] **步骤 4:methods 新增浮层逻辑**
+
+在 `draftMemberText` 方法后(约 460 行)添加:
+```javascript
+    // ===== 报告快速分析 =====
+    openQuickAnalyze: function() {
+      this.quickAnalyze.show = true
+      var self = this
+      // 预判免费版今日额度(仅用于 UI 展示,最终以后端校验为准)
+      getMyMembership().then(function(res) {
+        var level = res.data && res.data.memberLevel
+        if (level && level !== 'FREE') {
+          self.quickAnalyze.usedToday = false
+        }
+      }).catch(function() {})
+    },
+    closeQuickAnalyze: function() {
+      this.quickAnalyze.show = false
+    },
+    sendQuickQuestion: function(q) {
+      if (q && q.trim()) {
+        this.quickAnalyze.inputText = q
+        this.sendQuickAnalyze()
+      }
+    },
+    sendQuickAnalyze: function() {
+      var self = this
+      var text = (this.quickAnalyze.inputText || '').trim()
+      if (!text || this.quickAnalyze.sending) return
+      this.quickAnalyze.messages.push({ role: 'user', content: text })
+      this.quickAnalyze.inputText = ''
+      this.quickAnalyze.sending = true
+      // 传 reportId 或 draftId
+      var rid = this.reportId
+      var did = this.draftId
+      reportQuickAnalyze(text, rid, this.quickAnalyze.conversationId, did).then(function(res) {
+        if (res.code === 200 && res.data) {
+          self.quickAnalyze.messages.push({ role: 'ai', content: res.data.answer || '(无回答)' })
+          if (res.data.conversationId) {
+            self.quickAnalyze.conversationId = res.data.conversationId
+          }
+        } else {
+          var msg = res.message || '分析失败'
+          if (msg.indexOf('每日仅限') !== -1) {
+            self.quickAnalyze.usedToday = true
+          }
+          self.quickAnalyze.messages.push({ role: 'ai', content: msg })
+        }
+      }).catch(function() {
+        self.quickAnalyze.messages.push({ role: 'ai', content: 'AI 服务暂不可用,请稍后重试' })
+      }).finally(function() {
+        self.quickAnalyze.sending = false
+      })
+    }
+```
+
+- [ ] **步骤 5:template 新增 FAB + 浮层**
+
+在 `</scroll-view>` 之后(约 223 行),`<health-knowledge-popup>` 之前插入:
+```html
+    <!-- 报告快速分析入口(仅查看模式,reportId 或 draftId 存在) -->
+    <view class="quick-analyze-fab" v-if="!isEditing && (reportId || draftId)" @tap="openQuickAnalyze">
+      <text class="quick-analyze-fab-icon">🎯</text>
+      <text class="quick-analyze-fab-text">快速分析</text>
+    </view>
+
+    <!-- 快速分析浮层 -->
+    <view class="quick-analyze-overlay" v-if="quickAnalyze.show" @tap="closeQuickAnalyze"></view>
+    <view class="quick-analyze-panel" v-if="quickAnalyze.show">
+      <view class="quick-analyze-header">
+        <text class="quick-analyze-title">报告快速分析</text>
+        <text class="quick-analyze-close" @tap="closeQuickAnalyze">✕</text>
+      </view>
+      <view class="quick-analyze-hint" v-if="quickAnalyze.usedToday">免费版今日次数已用完,升级会员解锁更多分析</view>
+      <scroll-view class="quick-analyze-messages" scroll-y>
+        <view class="quick-analyze-message" v-for="(msg, qidx) in quickAnalyze.messages" :key="qidx" :class="'msg-' + msg.role">
+          <text class="quick-analyze-msg-text">{{ msg.content }}</text>
+        </view>
+        <view class="quick-analyze-loading" v-if="quickAnalyze.sending">
+          <text class="quick-analyze-loading-text">AI 正在分析中...</text>
+        </view>
+      </scroll-view>
+      <view class="quick-analyze-chips" v-if="quickAnalyze.messages.length === 0">
+        <view class="quick-analyze-chip" v-for="(q, qi) in quickQuestions" :key="qi" @tap="sendQuickQuestion(q)">
+          <text class="quick-analyze-chip-text">{{ q }}</text>
+        </view>
+      </view>
+      <view class="quick-analyze-input-bar">
+        <input class="quick-analyze-input" v-model="quickAnalyze.inputText" placeholder="输入你想问的问题..."
+          :disabled="quickAnalyze.sending || quickAnalyze.usedToday" confirm-type="send" @confirm="sendQuickAnalyze" />
+        <view class="quick-analyze-send" :class="{ 'send-disabled': quickAnalyze.sending || quickAnalyze.usedToday }" @tap="sendQuickAnalyze">
+          <text class="quick-analyze-send-text">发送</text>
+        </view>
+      </view>
+    </view>
+```
+
+- [ ] **步骤 6:style 复用**
+
+在 `<style scoped>` 末尾(约 1310 行)添加 `report-detail.vue` 的 `.quick-analyze-*` 完整样式(第 2292-2398 行)。
+
+- [ ] **步骤 7:语法校验**
+
+运行:`cd cfc-frontend && node -e "const fs=require('fs');const src=fs.readFileSync('pages/health/gut-flora-detail.vue','utf8');const m=src.match(/<script>([\s\S]*?)<\/script>/);new Function(m[1].replace(/import[\s\S]*?(?=\n)/g,'').replace(/export default/,'const x='));console.log('OK')"`
+预期:OK
+
+- [ ] **步骤 8:Commit**
+
+```bash
+git add cfc-frontend/pages/health/gut-flora-detail.vue
+git commit -m "feat(frontend): gut-flora-detail 新增报告快速分析浮层(支持 reportId/draftId)"
+```
+
+---
+
+## 任务 5:前端 report-confirm.vue 新增快速分析浮层
+
+**文件:**
+- 修改:`cfc-frontend/pages/health/report-confirm.vue`
+
+- [ ] **步骤 1:import 新增**
+
+第 350 行 import 行添加 `reportQuickAnalyze, getMyMembership`:
+```javascript
+import { confirmReportPreview, discardReportDraft, confirmTongue, discardTongue, parseReportDraft, reportQuickAnalyze, getMyMembership } from '../../utils/api.js'
+```
+
+- [ ] **步骤 2:data 新增 quickAnalyze 对象**
+
+在 `extractedAge: ''`(约 394 行)之后添加(同 gut-flora-detail):
+```javascript
+      // 报告快速分析浮层
+      quickAnalyze: {
+        show: false,
+        sending: false,
+        messages: [],
+        inputText: '',
+        conversationId: '',
+        usedToday: false
+      }
+```
+
+- [ ] **步骤 3:computed 新增 quickQuestions**
+
+在 `memberDisplayText` computed 后(约 441 行)添加:
+```javascript
+    quickQuestions: function() {
+      return [
+        '这份报告最需要关注哪些异常指标?',
+        '报告中提示了哪些健康风险?',
+        '有哪些改善建议?'
+      ]
+    }
+```
+
+- [ ] **步骤 4:methods 新增浮层逻辑**
+
+在 `memberDisplayText` 方法后添加(同 gut-flora-detail,但传参只需 `draftId`):
+```javascript
+    // ===== 报告快速分析 =====
+    openQuickAnalyze: function() {
+      this.quickAnalyze.show = true
+      var self = this
+      getMyMembership().then(function(res) {
+        var level = res.data && res.data.memberLevel
+        if (level && level !== 'FREE') {
+          self.quickAnalyze.usedToday = false
+        }
+      }).catch(function() {})
+    },
+    closeQuickAnalyze: function() {
+      this.quickAnalyze.show = false
+    },
+    sendQuickQuestion: function(q) {
+      if (q && q.trim()) {
+        this.quickAnalyze.inputText = q
+        this.sendQuickAnalyze()
+      }
+    },
+    sendQuickAnalyze: function() {
+      var self = this
+      var text = (this.quickAnalyze.inputText || '').trim()
+      if (!text || this.quickAnalyze.sending) return
+      this.quickAnalyze.messages.push({ role: 'user', content: text })
+      this.quickAnalyze.inputText = ''
+      this.quickAnalyze.sending = true
+      reportQuickAnalyze(text, null, this.quickAnalyze.conversationId, this.draftId).then(function(res) {
+        if (res.code === 200 && res.data) {
+          self.quickAnalyze.messages.push({ role: 'ai', content: res.data.answer || '(无回答)' })
+          if (res.data.conversationId) {
+            self.quickAnalyze.conversationId = res.data.conversationId
+          }
+        } else {
+          var msg = res.message || '分析失败'
+          if (msg.indexOf('每日仅限') !== -1) {
+            self.quickAnalyze.usedToday = true
+          }
+          self.quickAnalyze.messages.push({ role: 'ai', content: msg })
+        }
+      }).catch(function() {
+        self.quickAnalyze.messages.push({ role: 'ai', content: 'AI 服务暂不可用,请稍后重试' })
+      }).finally(function() {
+        self.quickAnalyze.sending = false
+      })
+    }
+```
+
+- [ ] **步骤 5:template 新增 FAB + 浮层**
+
+在 `</view>`(form-section 结束)之后、`<view class="bottom-spacer"></view>` 之前插入:
+```html
+    <!-- 报告快速分析入口(解析完成、非解析中) -->
+    <view class="quick-analyze-fab" v-if="!parsing && draftId" @tap="openQuickAnalyze">
+      <text class="quick-analyze-fab-icon">🎯</text>
+      <text class="quick-analyze-fab-text">快速分析</text>
+    </view>
+
+    <!-- 快速分析浮层 -->
+    <view class="quick-analyze-overlay" v-if="quickAnalyze.show" @tap="closeQuickAnalyze"></view>
+    <view class="quick-analyze-panel" v-if="quickAnalyze.show">
+      <view class="quick-analyze-header">
+        <text class="quick-analyze-title">报告快速分析</text>
+        <text class="quick-analyze-close" @tap="closeQuickAnalyze">✕</text>
+      </view>
+      <view class="quick-analyze-hint" v-if="quickAnalyze.usedToday">免费版今日次数已用完,升级会员解锁更多分析</view>
+      <scroll-view class="quick-analyze-messages" scroll-y>
+        <view class="quick-analyze-message" v-for="(msg, qidx) in quickAnalyze.messages" :key="qidx" :class="'msg-' + msg.role">
+          <text class="quick-analyze-msg-text">{{ msg.content }}</text>
+        </view>
+        <view class="quick-analyze-loading" v-if="quickAnalyze.sending">
+          <text class="quick-analyze-loading-text">AI 正在分析中...</text>
+        </view>
+      </scroll-view>
+      <view class="quick-analyze-chips" v-if="quickAnalyze.messages.length === 0">
+        <view class="quick-analyze-chip" v-for="(q, qi) in quickQuestions" :key="qi" @tap="sendQuickQuestion(q)">
+          <text class="quick-analyze-chip-text">{{ q }}</text>
+        </view>
+      </view>
+      <view class="quick-analyze-input-bar">
+        <input class="quick-analyze-input" v-model="quickAnalyze.inputText" placeholder="输入你想问的问题..."
+          :disabled="quickAnalyze.sending || quickAnalyze.usedToday" confirm-type="send" @confirm="sendQuickAnalyze" />
+        <view class="quick-analyze-send" :class="{ 'send-disabled': quickAnalyze.sending || quickAnalyze.usedToday }" @tap="sendQuickAnalyze">
+          <text class="quick-analyze-send-text">发送</text>
+        </view>
+      </view>
+    </view>
+```
+
+- [ ] **步骤 6:style 复用**
+
+在 `<style scoped>` 末尾添加同一套 `.quick-analyze-*` 样式。
+
+- [ ] **步骤 7:语法校验**
+
+运行:`cd cfc-frontend && node -e "const fs=require('fs');const src=fs.readFileSync('pages/health/report-confirm.vue','utf8');const m=src.match(/<script>([\s\S]*?)<\/script>/);new Function(m[1].replace(/import[\s\S]*?(?=\n)/g,'').replace(/export default/,'const x='));console.log('OK')"`
+预期:OK
+
+- [ ] **步骤 8:Commit**
+
+```bash
+git add cfc-frontend/pages/health/report-confirm.vue
+git commit -m "feat(frontend): report-confirm 新增报告快速分析浮层(支持 draftId)"
+```
+
+---
+
+## 任务 6:验证
+
+**文件:**
+- 验证:后端编译 + 手动验证清单
+
+- [ ] **步骤 1:后端全量编译**
+
+运行:`cd cfc-backend && mvn clean compile -q -Dmaven.test.skip=true`
+预期:BUILD SUCCESS
+
+- [ ] **步骤 2:手动验证清单**
+
+| 场景 | 预期 |
+|------|------|
+| gut-flora-detail(reportId 模式)点击快速分析 | 正常返回 answer |
+| gut-flora-detail(draftId 模式)点击快速分析 | 正常返回 answer(基于草稿 payload) |
+| report-confirm(draftId)点击快速分析 | 正常返回 answer |
+| 免费版第 2 次(任意页面) | 返回超限错误「免费版每日仅限 1 次快速分析,升级会员解锁更多」 |
+| 付费版多轮追问 | 带 conversationId,AI 记得上文 |
+| Redis 停掉 | 免费版仍可用(fail-open),后端日志记 warn |
+
+- [ ] **步骤 3:最终 Commit(若有无需变更则跳过)**
+
+```bash
+git status --short
+# 确认无遗漏
+```
+
+---
+
+## 自检结果
+
+**规格覆盖度检查:**
+- ✅ 后端 draftId 支持(FamilyContextService + AIChatController)→ 任务 1、2
+- ✅ 前端 api.js 扩展 draftId 参数 → 任务 3
+- ✅ gut-flora-detail 浮层(reportId/draftId 双模式) → 任务 4
+- ✅ report-confirm 浮层(draftId 模式) → 任务 5
+- ✅ 样式复用 → 任务 4、6
+- ✅ 免费版限制/会员校验/Redis fail-open 复用现有 → 任务 2
+- ✅ 错误处理(空参数/超限/AI 不可用) → 任务 2、验证步骤 2
+
+**占位符扫描:** 无 TODO/待定/后续实现 等占位符。
+
+**类型一致性:**
+- `buildContextFromDraft(Long userId, Long draftId)` → 任务 1 步骤 3 定义,任务 2 步骤 1 调用,签名一致
+- `reportQuickAnalyze(query, reportId, conversationId, draftId)` → 任务 3 定义,任务 4/5 步骤 4 调用,签名一致
+- `quickAnalyze` data 结构 → 任务 4/5 步骤 2 定义,步骤 4/5 使用,一致
+- `draftId` → 任务 4 步骤 5 使用 `this.draftId`(data 已有,第 374 行),任务 5 步骤 5 使用 `this.draftId`(data 已有,第 356 行),一致