Просмотр исходного кода

feat(self-check): 清理 WuxingSourcingService 静态表 + submitSelfCheck 接 AI

iwt 2 недель назад
Родитель
Сommit
9370b72654

+ 40 - 18
cfc-backend/src/main/java/com/etotem/cfc/service/FiveDimensionSelfCheckService.java

@@ -35,7 +35,7 @@ import com.etotem.cfc.util.SortUtil;
  * 对应书稿《终章-绘制你家的幸福全景图》15题自检问卷
  * 对应书稿《终章-绘制你家的幸福全景图》15题自检问卷
  *
  *
  * 计分:A=3分,B=2分,C=1分,D=0分,每维度 3 题满分 9 分
  * 计分:A=3分,B=2分,C=1分,D=0分,每维度 3 题满分 9 分
- * 结果解读:≥7 健康 / 4-6 留意 / ≤3 紧绷(紧绷维度触发五行相生寻源建议)
+ * 结果解读:≥7 健康 / 4-6 留意 / ≤3 紧绷(紧绷维度触发 AI 建议)
  */
  */
 @Service
 @Service
 public class FiveDimensionSelfCheckService {
 public class FiveDimensionSelfCheckService {
@@ -57,6 +57,9 @@ public class FiveDimensionSelfCheckService {
     @Resource
     @Resource
     private SelfCheckIgnoreMapper ignoreMapper;
     private SelfCheckIgnoreMapper ignoreMapper;
 
 
+    @Resource
+    private SelfCheckAnalysisService selfCheckAnalysisService;
+
     private final ObjectMapper objectMapper = new ObjectMapper();
     private final ObjectMapper objectMapper = new ObjectMapper();
 
 
     /** 维度中文名与五行 */
     /** 维度中文名与五行 */
@@ -467,12 +470,18 @@ public class FiveDimensionSelfCheckService {
         }
         }
         Collections.sort(submittedQuestionIds);
         Collections.sort(submittedQuestionIds);
 
 
-        // 生成寻源建议(仅 ≤3 紧绷维度
+        // 生成 AI 建议(AI 优先,失败降级为空建议
         Map<String, Integer> scoreMap = new LinkedHashMap<>();
         Map<String, Integer> scoreMap = new LinkedHashMap<>();
         for (SelfCheckResultVO.DimensionScoreVO dvo : dimensions) {
         for (SelfCheckResultVO.DimensionScoreVO dvo : dimensions) {
             scoreMap.put(dvo.getDimension(), dvo.getScore());
             scoreMap.put(dvo.getDimension(), dvo.getScore());
         }
         }
-        List<WuxingSourcingAdviceVO> advices = wuxingSourcingService.getAdvicesForLowScores(scoreMap);
+        String adviceJson = null;
+        try {
+            Map<String, Object> adviceResult = selfCheckAnalysisService.generateAdvice(userId, scoreMap, submittedQuestionIds);
+            adviceJson = (String) adviceResult.get("adviceJson");
+        } catch (Exception e) {
+            log.warn("AI 建议生成异常,使用空建议: {}", e.getMessage());
+        }
 
 
         // 保存记录
         // 保存记录
         FiveDimensionSelfCheck record = new FiveDimensionSelfCheck();
         FiveDimensionSelfCheck record = new FiveDimensionSelfCheck();
@@ -481,7 +490,7 @@ public class FiveDimensionSelfCheckService {
         record.setAnswersJson(toJson(dto.getAnswers()));
         record.setAnswersJson(toJson(dto.getAnswers()));
         record.setScoresJson(toJson(scoreMap));
         record.setScoresJson(toJson(scoreMap));
         record.setTotalScore(totalScore);
         record.setTotalScore(totalScore);
-        record.setAdviceJson(toJson(advices));
+        record.setAdviceJson(adviceJson != null ? adviceJson : "[]");
         record.setQuestionIdsJson(toJson(submittedQuestionIds));
         record.setQuestionIdsJson(toJson(submittedQuestionIds));
         record.setCreatedAt(new Date());
         record.setCreatedAt(new Date());
         selfCheckMapper.insert(record);
         selfCheckMapper.insert(record);
@@ -499,7 +508,7 @@ public class FiveDimensionSelfCheckService {
         vo.setFamilyMemberId(record.getFamilyMemberId());
         vo.setFamilyMemberId(record.getFamilyMemberId());
         vo.setDimensions(dimensions);
         vo.setDimensions(dimensions);
         vo.setTotalScore(totalScore);
         vo.setTotalScore(totalScore);
-        vo.setAdvices(advices);
+        vo.setAdvices(parseAdvices(adviceJson));
         vo.setCreatedAt(record.getCreatedAt());
         vo.setCreatedAt(record.getCreatedAt());
         return vo;
         return vo;
     }
     }
@@ -602,6 +611,18 @@ public class FiveDimensionSelfCheckService {
         return ignore.getCreatedAt();
         return ignore.getCreatedAt();
     }
     }
 
 
+    /**
+     * 获取自检趋势分析结果(含历史列表)
+     */
+    public Map<String, Object> getTrendAnalysis(Long userId) {
+        Map<String, Object> trend = selfCheckAnalysisService.generateTrend(userId);
+        Map<String, Object> resp = new HashMap<>();
+        resp.put("history", selfCheckAnalysisService.getRecentHistory(userId, 3));
+        resp.put("aiInsight", trend.getOrDefault("aiInsight", ""));
+        resp.put("trendSummary", trend.getOrDefault("trendSummary", ""));
+        return resp;
+    }
+
     // === 内部方法 ===
     // === 内部方法 ===
 
 
     private SelfCheckResultVO.DimensionScoreVO buildDimensionScoreVO(String dimension, int score) {
     private SelfCheckResultVO.DimensionScoreVO buildDimensionScoreVO(String dimension, int score) {
@@ -666,6 +687,19 @@ public class FiveDimensionSelfCheckService {
         }
         }
     }
     }
 
 
+    private List<WuxingSourcingAdviceVO> parseAdvices(String adviceJson) {
+        if (adviceJson == null || adviceJson.isEmpty() || "[]".equals(adviceJson)) {
+            return new ArrayList<>();
+        }
+        try {
+            return objectMapper.readValue(adviceJson,
+                    objectMapper.getTypeFactory().constructCollectionType(List.class, WuxingSourcingAdviceVO.class));
+        } catch (Exception e) {
+            log.warn("解析 AI 建议 JSON 失败: {}", e.getMessage());
+            return new ArrayList<>();
+        }
+    }
+
     @SuppressWarnings("unchecked")
     @SuppressWarnings("unchecked")
     private SelfCheckResultVO toVO(FiveDimensionSelfCheck record) {
     private SelfCheckResultVO toVO(FiveDimensionSelfCheck record) {
         SelfCheckResultVO vo = new SelfCheckResultVO();
         SelfCheckResultVO vo = new SelfCheckResultVO();
@@ -694,19 +728,7 @@ public class FiveDimensionSelfCheckService {
         vo.setDimensions(dimensions);
         vo.setDimensions(dimensions);
 
 
         // 解析寻源建议
         // 解析寻源建议
-        if (record.getAdviceJson() != null) {
-            try {
-                List<WuxingSourcingAdviceVO> advices = objectMapper.readValue(
-                        record.getAdviceJson(),
-                        objectMapper.getTypeFactory().constructCollectionType(List.class, WuxingSourcingAdviceVO.class));
-                vo.setAdvices(advices);
-            } catch (Exception e) {
-                log.warn("解析adviceJson失败: {}", e.getMessage());
-                vo.setAdvices(new ArrayList<>());
-            }
-        } else {
-            vo.setAdvices(new ArrayList<>());
-        }
+        vo.setAdvices(parseAdvices(record.getAdviceJson()));
         return vo;
         return vo;
     }
     }
 }
 }

+ 2 - 108
cfc-backend/src/main/java/com/etotem/cfc/service/WuxingSourcingService.java

@@ -3,23 +3,14 @@ package com.etotem.cfc.service;
 import com.etotem.cfc.dto.WuxingSourcingAdviceVO;
 import com.etotem.cfc.dto.WuxingSourcingAdviceVO;
 import org.springframework.stereotype.Service;
 import org.springframework.stereotype.Service;
 
 
-import java.util.ArrayList;
 import java.util.LinkedHashMap;
 import java.util.LinkedHashMap;
-import java.util.List;
 import java.util.Map;
 import java.util.Map;
 
 
 /**
 /**
- * 五行相生寻源建议引擎(P0-2)
- * 对应书稿《附录-工具包汇总》附录六「五行相生速查表(主动健康寻源表)」
- *
- * 核心逻辑:
- * 1. 当一个维度亮红灯(分数低)时,先别急着只补它,用两步寻源:
- *    - 相生寻源:看它的上游(谁生它)——是不是上游滋养不足?
- *    - 相克寻源:看谁在压制它——是不是哪个维度过度膨胀?
- * 2. 依据速查表输出:上游维度、压制维度、简单应对
+ * 五行维度元信息工具(P0-2)
+ * 保留维度元数据、等级计算等基础工具方法,寻源逻辑已迁移至 AI 服务。
  *
  *
  * 五行相生:身(土)→智(金)→富(水)→行(木)→心(火)→身
  * 五行相生:身(土)→智(金)→富(水)→行(木)→心(火)→身
- * 五行相克:身克富、智克行、富克心、行克身、心克智
  */
  */
 @Service
 @Service
 public class WuxingSourcingService {
 public class WuxingSourcingService {
@@ -34,103 +25,6 @@ public class WuxingSourcingService {
         DIMENSION_META.put("mind", new String[]{"心", "火", "#FF6B9D"});
         DIMENSION_META.put("mind", new String[]{"心", "火", "#FF6B9D"});
     }
     }
 
 
-    /**
-     * 寻源表(附录六):
-     * 症状维度 → [上游维度, 上游原因, 压制维度, 压制原因, 简单应对]
-     */
-    private static final Map<String, String[]> SOURCING_TABLE = new LinkedHashMap<>();
-    static {
-        SOURCING_TABLE.put("body", new String[]{
-                "mind", "情绪安全了,身体才有行动力",
-                "action", "老好人透支自己,累垮身体",
-                "情绪停机坪;先照顾自己,再照顾别人"
-        });
-        SOURCING_TABLE.put("wisdom", new String[]{
-                "body", "身体状态好,学习效率才高",
-                "mind", "爱太多变成控制,压制独立思考",
-                "睡够再学;给孩子留白"
-        });
-        SOURCING_TABLE.put("wealth", new String[]{
-                "wisdom", "能力上去了,财富才跟着来",
-                "body", "用健康换钱,本末倒置",
-                "先投资自己;别省体检钱"
-        });
-        SOURCING_TABLE.put("action", new String[]{
-                "wealth", "经济宽裕了,才有余力经营关系",
-                "wisdom", "算得太清,关系变冷",
-                "先理财务;少算多听"
-        });
-        SOURCING_TABLE.put("mind", new String[]{
-                "action", "关系顺畅了,内心才安定",
-                "wealth", "钱多了,情薄了",
-                "关系周记;家庭夜谈"
-        });
-    }
-
-    /**
-     * 为单个低分维度生成寻源建议
-     *
-     * @param dimension 维度 code
-     * @param score     自检得分(0-9,可为 null)
-     * @param level     等级(healthy/attention/tense,可为 null,由调用方传入)
-     * @return 寻源建议;维度不存在时返回 null
-     */
-    public WuxingSourcingAdviceVO getAdvice(String dimension, Integer score, String level) {
-        String[] meta = DIMENSION_META.get(dimension);
-        String[] row = SOURCING_TABLE.get(dimension);
-        if (meta == null || row == null) {
-            return null;
-        }
-        WuxingSourcingAdviceVO vo = new WuxingSourcingAdviceVO();
-        vo.setDimension(dimension);
-        vo.setDimensionName(meta[0]);
-        vo.setElement(meta[1]);
-        vo.setColor(meta[2]);
-        vo.setScore(score);
-        vo.setLevel(level != null ? level : (score == null ? null : levelOf(score)));
-        vo.setLevelName(levelName(vo.getLevel()));
-
-        String upstream = row[0];
-        String restrainer = row[2];
-        vo.setUpstreamDimension(upstream);
-        vo.setUpstreamName(DIMENSION_META.get(upstream)[0]);
-        vo.setUpstreamElement(DIMENSION_META.get(upstream)[1]);
-        vo.setUpstreamColor(DIMENSION_META.get(upstream)[2]);
-        vo.setUpstreamReason(row[1]);
-
-        vo.setRestrainerDimension(restrainer);
-        vo.setRestrainerName(DIMENSION_META.get(restrainer)[0]);
-        vo.setRestrainerElement(DIMENSION_META.get(restrainer)[1]);
-        vo.setRestrainerColor(DIMENSION_META.get(restrainer)[2]);
-        vo.setRestrainerReason(row[3]);
-
-        vo.setAction(row[4]);
-        return vo;
-    }
-
-    /**
-     * 为多个低分维度批量生成寻源建议
-     *
-     * @param scoreMap 维度 code → 得分(0-9)
-     * @return 所有分数≤3(紧绷)维度的寻源建议列表
-     */
-    public List<WuxingSourcingAdviceVO> getAdvicesForLowScores(Map<String, Integer> scoreMap) {
-        List<WuxingSourcingAdviceVO> result = new ArrayList<>();
-        if (scoreMap == null) {
-            return result;
-        }
-        for (Map.Entry<String, Integer> entry : scoreMap.entrySet()) {
-            Integer score = entry.getValue();
-            if (score != null && score <= 3) {
-                WuxingSourcingAdviceVO vo = getAdvice(entry.getKey(), score, "tense");
-                if (vo != null) {
-                    result.add(vo);
-                }
-            }
-        }
-        return result;
-    }
-
     /**
     /**
      * 分数 → 等级:≥7 健康 / 4-6 留意 / ≤3 紧绷
      * 分数 → 等级:≥7 健康 / 4-6 留意 / ≤3 紧绷
      */
      */