|
|
@@ -0,0 +1,385 @@
|
|
|
+# 五维家庭自检 AI 结合(P0 + P1)设计
|
|
|
+
|
|
|
+> 日期:2026-08-31
|
|
|
+> 状态:已获用户方案确认(P0+P1 第一批,P2 后续)
|
|
|
+> 关联:`FiveDimensionSelfCheckService` / `AiGateway` / `cfc-langgraph`
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 1. 背景与目标
|
|
|
+
|
|
|
+现有五维自检使用**静态五行寻源表**(`WuxingSourcingService` 硬编码 5 条建议)生成结果,所有用户看到相同文案,无个性化、无趋势感知。
|
|
|
+
|
|
|
+本批次实现 P0+P1 共 4 个子系统,按优先级排序:
|
|
|
+
|
|
|
+| 优先级 | 子系统 | 目标 |
|
|
|
+|:------:|--------|------|
|
|
|
+| **P0-1** | 静态建议 → AI 个性化建议 | 每次自检生成有温度、有针对性的解读 |
|
|
|
+| **P0-2** | 用户点击生成健康计划 | 自检低分可一键生成家庭健康计划草稿 |
|
|
|
+| **P1-1** | 历史趋势 + AI 解读 | 3 次自检趋势可视化 + AI 趋势分析 |
|
|
|
+| **P1-2** | 自检结果注入 AI Chat | 聊天页自动携带自检上下文 |
|
|
|
+
|
|
|
+P2(家庭成员差异化建议)延后单独实现。
|
|
|
+
|
|
|
+已确认决策:
|
|
|
+- P0-1:**纯 AI 替换**,不保留静态五行寻源建议(`WuxingSourcingService` 仅保留 `levelOf/levelName/dimensionName`)
|
|
|
+- P0-2:**用户点击才生成**,不自动创建 draft plan
|
|
|
+- P0-1 字段设计:**直接改造 `advices` 字段**,新结构 `[{dimension, interpretation, microActions, aiInsight}]`
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 2. 现状盘点
|
|
|
+
|
|
|
+### 后端(自检)
|
|
|
+
|
|
|
+- `FiveDimensionSelfCheckController`:`/questions` `/submit` `/latest` `/history`
|
|
|
+- `FiveDimensionSelfCheckService`:
|
|
|
+ - `QUESTION_BANK`(15题)、`SUB_DIMENSION_QUESTION_BANK`(19题)
|
|
|
+ - `submitSelfCheck`:计分 → `wuxingSourcingService.getAdvicesForLowScores()` → 落库 `adviceJson`
|
|
|
+ - `getQuestions()` 已改为支持 `retake` 参数(上个计划完成)
|
|
|
+- `WuxingSourcingService`:静态 `SOURCING_TABLE`(5行×5列)+ `levelOf/levelName/dimensionName` 纯工具方法
|
|
|
+- 表 `five_dimension_self_checks`:`adviceJson TEXT`
|
|
|
+
|
|
|
+### 后端(AI 基础设施)
|
|
|
+
|
|
|
+- `AiGateway.java`:统一网关(熔断+降级+PII脱敏)
|
|
|
+ - `chat(query, userId, conversationId, inputs)` → `POST /api/v1/chat`
|
|
|
+ - `generateInnateReading(portrait)` → `POST /api/v1/innate/reading`
|
|
|
+ - `generateHealthPlan(inputs)` → `POST /api/v1/analysis/run`
|
|
|
+- LangGraph Python(`cfc-langgraph/app/`,端口 9000):
|
|
|
+ - `app/graphs/chat_graph.py`(78行,Dify 兼容)
|
|
|
+ - `app/graphs/health_plan_graph.py`(297行,Java 客户端 JavaClient 读取家庭数据)
|
|
|
+ - `app/graphs/innate_portrait_graph.py`(65行,参考实现)
|
|
|
+ - `app/api/adapter.py`(Dify 兼容路由)
|
|
|
+ - `app/api/innate_portrait.py`(`/api/v1/innate/reading`)
|
|
|
+ - `app/main.py` 注册各 router
|
|
|
+- 参考链路:`InnatePortraitController` → `InnatePortraitService.generateAiReading()` → `AiGateway.generateInnateReading()` → LangGraph `/api/v1/innate/reading` → `innate_portrait_graph.py` → 成功返回 reading,失败 null(调用方降级模板)
|
|
|
+
|
|
|
+### 前端
|
|
|
+
|
|
|
+- `pages/family/self-check-result.vue`:结果页,advices 展示在第 71-123 行(五行相生寻源建议区块)
|
|
|
+- `pages/family/self-check-entry.vue`:中间页(刚实现)
|
|
|
+- `pages/ai/chat.vue`:AI 聊天页(需确认路径)
|
|
|
+- `utils/api.js`:自检相关方法(`getSelfCheckStatus/ignoreSelfCheck/getSelfCheckQuestions/submitSelfCheck/getSelfCheckLatest/getSelfCheckHistory`)
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 3. 数据层设计
|
|
|
+
|
|
|
+### 3.1 `adviceJson` 结构变更
|
|
|
+
|
|
|
+**旧结构**(静态五行寻源):
|
|
|
+```json
|
|
|
+[
|
|
|
+ {
|
|
|
+ "dimension": "mind",
|
|
|
+ "dimensionName": "心",
|
|
|
+ "element": "火",
|
|
|
+ "color": "#FF6B9D",
|
|
|
+ "score": 2,
|
|
|
+ "level": "tense",
|
|
|
+ "levelName": "紧绷",
|
|
|
+ "upstreamDimension": "action",
|
|
|
+ "upstreamName": "行",
|
|
|
+ "upstreamElement": "木",
|
|
|
+ "upstreamColor": "#10B981",
|
|
|
+ "upstreamReason": "关系顺畅了,内心才安定",
|
|
|
+ "restrainerDimension": "wealth",
|
|
|
+ "restrainerName": "富",
|
|
|
+ "restrainerElement": "水",
|
|
|
+ "restrainerColor": "#F59E0B",
|
|
|
+ "restrainerReason": "钱多了,情薄了",
|
|
|
+ "action": "关系周记;家庭夜谈"
|
|
|
+ }
|
|
|
+]
|
|
|
+```
|
|
|
+
|
|
|
+**新结构**(AI 生成):
|
|
|
+```json
|
|
|
+[
|
|
|
+ {
|
|
|
+ "dimension": "mind",
|
|
|
+ "dimensionName": "心",
|
|
|
+ "element": "火",
|
|
|
+ "color": "#FF6B9D",
|
|
|
+ "score": 2,
|
|
|
+ "level": "tense",
|
|
|
+ "levelName": "紧绷",
|
|
|
+ "interpretation": "你的心能量偏低,可能最近情绪压力较大...",
|
|
|
+ "microActions": ["今晚睡前做10分钟深呼吸", "和伴侣约定每周一次夜谈"],
|
|
|
+ "aiInsight": "建议从'行'维度入手...",
|
|
|
+ "fallbackUsed": false
|
|
|
+ }
|
|
|
+]
|
|
|
+```
|
|
|
+
|
|
|
+**注意**:`dimension`/`dimensionName`/`element`/`color`/`score`/`level`/`levelName` 字段保留,与现有 VO 兼容。`upstreamDimension`/`upstreamReason`/`restrainerDimension` 等静态字段移除,新增 `interpretation`/`microActions`/`aiInsight`/`fallbackUsed`。
|
|
|
+
|
|
|
+### 3.2 新增 LangGraph Graph 输入/输出
|
|
|
+
|
|
|
+**self_check_analysis_graph**(P0-1):
|
|
|
+- 输入 `dict`:`scores`(Map<dimension, score>)、`questionIds`(List[int])、`userId`(int)、`recentHistory`(List[dict] 最近3次自检快照)
|
|
|
+- 输出 `dict`:`advice_json`(新结构 JSON string)、`fallback_used`(bool)
|
|
|
+
|
|
|
+**self_check_trend_graph**(P1-1):
|
|
|
+- 输入 `dict`:`history`(List[dict] 最近3次含 totalScore/dimensions/createdAt)、`userId`(int)
|
|
|
+- 输出 `dict`:`aiInsight`(str)、`trendSummary`(str)
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 4. 后端接口设计
|
|
|
+
|
|
|
+### 4.1 修改 `POST /api/family/self-check/submit`(P0-1)
|
|
|
+
|
|
|
+AI 建议在 `submitSelfCheck` 时**同步生成**并落库 `adviceJson`(不单独暴露 analysis 接口,避免 YAGNI 冗余)。
|
|
|
+
|
|
|
+```
|
|
|
+POST /api/family/self-check/submit
|
|
|
+ ↓
|
|
|
+submitSelfCheck():计分 → selfCheckAnalysisService.generateAdvice(...) → 落库
|
|
|
+```
|
|
|
+
|
|
|
+### 4.2 新增 `POST /api/family/self-check/generate-plan`(P0-2)
|
|
|
+
|
|
|
+请求:`{ "checkId": 12 }`(可空,默认取最近一次)。
|
|
|
+
|
|
|
+响应 `Result<Map>`:
|
|
|
+```json
|
|
|
+{ "planId": 45, "status": "draft" }
|
|
|
+```
|
|
|
+
|
|
|
+### 4.3 新增 `POST /api/family/self-check/trend-analysis`(P1-1)
|
|
|
+
|
|
|
+请求:无 body(JWT 取 `userId`)。
|
|
|
+
|
|
|
+响应 `Result<TrendAnalysisVO>`:
|
|
|
+```json
|
|
|
+{
|
|
|
+ "history": [{ "createdAt": "...", "totalScore": 32, "dimensions": [...] }],
|
|
|
+ "aiInsight": "近三次身维度持续下降,可能...",
|
|
|
+ "trendSummary": "身-2 智+1 富0 行-1 心+2"
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+### 4.4 修改 `POST /api/family/self-check/submit`(P0-1)
|
|
|
+
|
|
|
+删除 `WuxingSourcingService.getAdvicesForLowScores()` 调用,改为:
|
|
|
+```java
|
|
|
+SelfCheckAdviceResult adviceResult = selfCheckAnalysisService.generateAdvice(userId, scoreMap, questionIds);
|
|
|
+// adviceResult 含 adviceJson + fallbackUsed
|
|
|
+// 直接落库
|
|
|
+```
|
|
|
+
|
|
|
+### 4.5 `SelfCheckResultVO` 字段
|
|
|
+
|
|
|
+`SelfCheckResultVO.advices` 类型保持 `List<WuxingSourcingAdviceVO>` 不变,字段结构按 5.4 改造后的 VO 承载 AI 建议内容。
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 5. Java 服务层设计
|
|
|
+
|
|
|
+### 5.1 新增 `SelfCheckAnalysisService`
|
|
|
+
|
|
|
+```java
|
|
|
+@Service
|
|
|
+public class SelfCheckAnalysisService {
|
|
|
+ @Resource private AiGateway aiGateway;
|
|
|
+ @Resource private FiveDimensionSelfCheckMapper selfCheckMapper;
|
|
|
+
|
|
|
+ /** 生成自检建议(AI 优先,LangGraph 失败返回 null) */
|
|
|
+ public SelfCheckAdviceResult generateAdvice(Long userId, Map<String, Integer> scoreMap, List<Integer> questionIds) {
|
|
|
+ // 1. 组装 inputs
|
|
|
+ // 2. 调 aiGateway.generateSelfCheckAdvice(inputs)
|
|
|
+ // 3. 返回 SelfCheckAdviceResult(含 adviceJson + fallbackUsed)
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 生成趋势分析 */
|
|
|
+ public TrendAnalysisVO getTrendAnalysis(Long userId) { ... }
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+### 5.2 AiGateway 新增方法
|
|
|
+
|
|
|
+```java
|
|
|
+public Map<String, Object> generateSelfCheckAdvice(Map<String, Object> inputs) {
|
|
|
+ // POST /api/v1/self-check/analysis
|
|
|
+ // 失败返回 null(调用方设置 fallbackUsed=true)
|
|
|
+}
|
|
|
+
|
|
|
+public Map<String, Object> generateSelfCheckTrend(Map<String, Object> inputs) {
|
|
|
+ // POST /api/v1/self-check/trend
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+### 5.3 WuxingSourcingService 变更
|
|
|
+
|
|
|
+- **保留**:`DIMENSION_META`、`levelOf()`、`levelName()`、`dimensionName()`(被 `buildDimensionScoreVO` 使用)
|
|
|
+- **删除**:`SOURCING_TABLE`、`getAdvice()`、`getAdvicesForLowScores()`
|
|
|
+
|
|
|
+### 5.4 `WuxingSourcingAdviceVO` 改造(P0-1)
|
|
|
+
|
|
|
+不新建 VO,直接改造现有 `dto/WuxingSourcingAdviceVO.java`(保持 `SelfCheckResultVO.advices` 类型不变,最小改动):
|
|
|
+
|
|
|
+- **保留字段**:`dimension` / `dimensionName` / `element` / `color` / `score` / `level` / `levelName`(与现有 VO 兼容)
|
|
|
+- **移除字段**:`upstreamDimension` / `upstreamName` / `upstreamElement` / `upstreamColor` / `upstreamReason` / `restrainerDimension` / `restrainerName` / `restrainerElement` / `restrainerColor` / `restrainerReason` / `action`
|
|
|
+- **新增字段**:
|
|
|
+ - `interpretation`(String,AI 对低分维度的解读)
|
|
|
+ - `microActions`(List<String>,2-3 个本周微行动)
|
|
|
+ - `aiInsight`(String,AI 补充洞察,可空)
|
|
|
+ - `fallbackUsed`(Boolean,AI 不可用降级标记)
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 6. LangGraph Python 设计
|
|
|
+
|
|
|
+### 6.1 新增 `self_check_analysis_graph.py`
|
|
|
+
|
|
|
+```python
|
|
|
+# cfc-langgraph/app/graphs/self_check_analysis_graph.py
|
|
|
+SYSTEM_PROMPT = """你是一位家庭健康顾问,基于五维自检结果(身·智·富·行·心,每维0-9分,满分45)给出个性化建议。
|
|
|
+
|
|
|
+要求:
|
|
|
+1. 用第二人称"你"称呼
|
|
|
+2. 对每个低分维度(≤6分)给出1-2句解读和2-3个具体可执行的微行动
|
|
|
+3. 如有历史数据,简要对比趋势
|
|
|
+4. 语气温暖口语化,不超过300字/维度
|
|
|
+5. 最后给出1句家庭整体洞察
|
|
|
+
|
|
|
+返回 JSON:
|
|
|
+{
|
|
|
+ "advice": [
|
|
|
+ {"dimension": "mind", "dimensionName": "心", "interpretation": "...", "microActions": ["...", "..."]},
|
|
|
+ ...
|
|
|
+ ],
|
|
|
+ "familyInsight": "..."
|
|
|
+}
|
|
|
+"""
|
|
|
+
|
|
|
+class SelfCheckAnalysisAgent:
|
|
|
+ async def run(self, scores: dict, questionIds: list, recentHistory: list, userId: int) -> dict:
|
|
|
+ # 格式化输入 → LLM → JSON 解析 → 返回
|
|
|
+```
|
|
|
+
|
|
|
+### 6.2 新增 `self_check_trend_graph.py`
|
|
|
+
|
|
|
+```python
|
|
|
+SYSTEM_PROMPT = """分析用户近3次五维自检趋势,输出:
|
|
|
+- aiInsight: 趋势解读(2-3句,指出最大变化维度和可能原因)
|
|
|
+- trendSummary: 各维度 delta(如"身-2 智+1 富0 行-1 心+2")
|
|
|
+返回 JSON:{"aiInsight": "...", "trendSummary": "..."}
|
|
|
+"""
|
|
|
+```
|
|
|
+
|
|
|
+### 6.3 注册路由
|
|
|
+
|
|
|
+`cfc-langgraph/app/api/self_check.py`:
|
|
|
+- `POST /api/v1/self-check/analysis` → `self_check_analysis_graph`
|
|
|
+- `POST /api/v1/self-check/trend` → `self_check_trend_graph`
|
|
|
+
|
|
|
+`cfc-langgraph/app/main.py`:追加 `app.include_router(self_check.router)`
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 7. 前端设计
|
|
|
+
|
|
|
+### 7.1 P0-1:`self-check-result.vue` 建议区块重写
|
|
|
+
|
|
|
+删除现有 `scr-card` 寻源建议区块(第 71-123 行),替换为:
|
|
|
+
|
|
|
+```html
|
|
|
+<view class="ai-advice-card" v-if="result.adviceJson">
|
|
|
+ <view class="ai-advice-header">
|
|
|
+ <text class="ai-advice-title">AI 健康解读</text>
|
|
|
+ <text class="ai-advice-sub">基于你的五维自检结果</text>
|
|
|
+ </view>
|
|
|
+ <view v-for="item in result.adviceJson" :key="item.dimension" class="ai-advice-item">
|
|
|
+ <view class="ai-advice-dim-badge" :style="{background: item.color}">
|
|
|
+ <text>{{ item.dimensionName }}</text>
|
|
|
+ <text>{{ item.score }}分</text>
|
|
|
+ </view>
|
|
|
+ <text class="ai-advice-interpretation">{{ item.interpretation }}</text>
|
|
|
+ <view class="ai-advice-actions">
|
|
|
+ <text class="ai-advice-action-label">本周行动:</text>
|
|
|
+ <text v-for="(action, i) in item.microActions" :key="'a'+i" class="ai-advice-action">{{ i+1 }}. {{ action }}</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ <view class="ai-advice-family" v-if="result.adviceJson[0] && result.adviceJson[0].familyInsight">
|
|
|
+ <text class="ai-advice-family-label">💡 家庭整体洞察</text>
|
|
|
+ <text class="ai-advice-family-text">{{ result.adviceJson[0].familyInsight }}</text>
|
|
|
+ </view>
|
|
|
+</view>
|
|
|
+<view class="ai-advice-fallback" v-else-if="result.fallbackUsed">
|
|
|
+ <text>AI 分析暂时不可用,请稍后再试</text>
|
|
|
+</view>
|
|
|
+```
|
|
|
+
|
|
|
+### 7.2 P0-2:结果页加「生成健康计划」按钮
|
|
|
+
|
|
|
+在结果页底部按钮区追加(有低分维度时显示):
|
|
|
+```html
|
|
|
+<button v-if="hasLowScore && !planGenerating" class="plan-btn" @click="generatePlan">
|
|
|
+ 生成健康计划
|
|
|
+</button>
|
|
|
+```
|
|
|
+
|
|
|
+### 7.3 P1-1:趋势展示
|
|
|
+
|
|
|
+新增 `POST /api/family/self-check/trend-analysis`,结果页中间页可展示(或结果页底部加「查看趋势」链接)。
|
|
|
+
|
|
|
+### 7.4 P1-2:Chat 注入 context
|
|
|
+
|
|
|
+在 `self-check-result.vue` 加「问问 AI」按钮:
|
|
|
+```html
|
|
|
+<button @click="askAI">问问 AI</button>
|
|
|
+```
|
|
|
+跳转:`uni.navigateTo({ url: '/pages/ai/chat?selfCheckId=' + result.id })`
|
|
|
+
|
|
|
+`AIChatController` 读取 `selfCheckId`,调 `getSelfCheckLatest`,将结果放入 context 传给 AiGateway。
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 8. 边界行为与错误处理
|
|
|
+
|
|
|
+1. **LangGraph 不可用**:`AiGateway` 熔断/open 时,`generateAdvice` 返回 `fallbackUsed=true`,`adviceJson=null`,前端显示"AI 分析暂时不可用"
|
|
|
+2. **LLM 返回非 JSON**:Python 侧 `validate` 节点捕获,返回 error,Java 侧 fallbackUsed=true
|
|
|
+3. **P0-2 计划生成失败**:toast "计划生成失败,请稍后重试",不阻断结果页
|
|
|
+4. **P1-1 历史不足 3 次**:用现有次数展示(1-2 次),LLM 做有限对比
|
|
|
+5. **P1-2 chat 上下文**:selfCheckId 无效时静默跳过,不影响 chat 正常流程
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 9. 测试与验证
|
|
|
+
|
|
|
+- 后端:`mvn clean compile` BUILD SUCCESS
|
|
|
+- 前端:`node --check` api.js 语法校验
|
|
|
+- 手动验证路径:
|
|
|
+ 1. 提交自检 → 查看 adviceJson 含 AI 生成内容(非静态表)
|
|
|
+ 2. 点「生成健康计划」→ 跳转 plan 详情(draft 状态)
|
|
|
+ 3. 点「查看趋势」→ 展示趋势 + AI 解读
|
|
|
+ 4. 点「问问 AI」→ chat 页,LLM 回答知晓自检背景
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 10. 文件变更清单
|
|
|
+
|
|
|
+**LangGraph Python(新增 2 graph + 1 api + 改 main)**:
|
|
|
+- `cfc-langgraph/app/graphs/self_check_analysis_graph.py`(P0-1)
|
|
|
+- `cfc-langgraph/app/graphs/self_check_trend_graph.py`(P1-1)
|
|
|
+- `cfc-langgraph/app/api/self_check.py`(路由注册)
|
|
|
+- `cfc-langgraph/app/main.py`(追加 router include)
|
|
|
+
|
|
|
+**Java 后端**:
|
|
|
+- `dto/WuxingSourcingAdviceVO.java`(改造:移除 upstream/restrainer/action,新增 interpretation/microActions/aiInsight/fallbackUsed)
|
|
|
+- `service/SelfCheckAnalysisService.java`(新建)
|
|
|
+- `service/WuxingSourcingService.java`(删除 SOURCING_TABLE/getAdvice/getAdvicesForLowScores)
|
|
|
+- `service/FiveDimensionSelfCheckService.java`(修改 submitSelfCheck 调新 Service,新增 trend 方法)
|
|
|
+- `service/AiGateway.java`(新增 2 方法)
|
|
|
+- `controller/family/FiveDimensionSelfCheckController.java`(新增 3 接口,修改 submit)
|
|
|
+- `service/HealthPlanService.java`(新增 generateFromSelfCheck)
|
|
|
+- `service/impl/HealthPlanServiceImpl.java`(实现 generateFromSelfCheck)
|
|
|
+
|
|
|
+**前端**:
|
|
|
+- `utils/api.js`(新增 3 方法)
|
|
|
+- `pages/family/self-check-result.vue`(P0-1/P0-2/P1-2 改造)
|
|
|
+- `pages/family/self-check-entry.vue`(P1-1 趋势展示)
|
|
|
+- `pages/ai/chat.vue`(P1-2 context 注入,需确认是否存在)
|