|
|
@@ -0,0 +1,347 @@
|
|
|
+package com.etotem.cfc.service;
|
|
|
+
|
|
|
+import com.etotem.cfc.dto.ChildInfoDTO;
|
|
|
+import com.etotem.cfc.entity.AiConversationSummary;
|
|
|
+import com.etotem.cfc.entity.AiUserFact;
|
|
|
+import com.etotem.cfc.entity.EmotionCheckin;
|
|
|
+import com.etotem.cfc.entity.HealthReport;
|
|
|
+import com.etotem.cfc.entity.User;
|
|
|
+import com.etotem.cfc.mapper.AiConversationSummaryMapper;
|
|
|
+import com.etotem.cfc.mapper.AiUserFactMapper;
|
|
|
+import com.etotem.cfc.mapper.EmotionCheckinMapper;
|
|
|
+import com.etotem.cfc.mapper.UserMapper;
|
|
|
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
|
|
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
|
|
+import org.springframework.stereotype.Service;
|
|
|
+
|
|
|
+import javax.annotation.Resource;
|
|
|
+import java.math.BigDecimal;
|
|
|
+import java.util.*;
|
|
|
+import java.util.stream.Collectors;
|
|
|
+
|
|
|
+/**
|
|
|
+ * AI 对话上下文服务
|
|
|
+ * 为 Dify Workflow 提供按意图获取的家庭数据
|
|
|
+ */
|
|
|
+@Service
|
|
|
+public class AiContextService {
|
|
|
+
|
|
|
+ @Resource
|
|
|
+ private UserService userService;
|
|
|
+
|
|
|
+ @Resource
|
|
|
+ private UserMapper userMapper;
|
|
|
+
|
|
|
+ @Resource
|
|
|
+ private TaskService taskService;
|
|
|
+
|
|
|
+ @Resource
|
|
|
+ private HealthReportService healthReportService;
|
|
|
+
|
|
|
+ @Resource
|
|
|
+ private AiUserFactMapper aiUserFactMapper;
|
|
|
+
|
|
|
+ @Resource
|
|
|
+ private AiConversationSummaryMapper aiConversationSummaryMapper;
|
|
|
+
|
|
|
+ @Resource
|
|
|
+ private EmotionCheckinMapper emotionCheckinMapper;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 根据意图类型获取对应上下文数据
|
|
|
+ *
|
|
|
+ * @param intentType 意图类型:health_report / task_progress / child_info / emotion_status / user_identity / user_facts
|
|
|
+ * @param userId 当前用户ID
|
|
|
+ * @param params 可选参数(如 childId、reportId 等)
|
|
|
+ * @return Dify 格式的上下文数据
|
|
|
+ */
|
|
|
+ public Map<String, Object> getContext(String intentType, Long userId, Map<String, Object> params) {
|
|
|
+ if (intentType == null) {
|
|
|
+ intentType = "user_identity";
|
|
|
+ }
|
|
|
+ switch (intentType) {
|
|
|
+ case "health_report":
|
|
|
+ return getHealthReportContext(userId, params);
|
|
|
+ case "task_progress":
|
|
|
+ return getTaskProgressContext(userId, params);
|
|
|
+ case "child_info":
|
|
|
+ return getChildInfoContext(userId, params);
|
|
|
+ case "emotion_status":
|
|
|
+ return getEmotionStatusContext(userId, params);
|
|
|
+ case "user_identity":
|
|
|
+ default:
|
|
|
+ return getUserIdentityContext(userId);
|
|
|
+ case "user_facts":
|
|
|
+ return getUserFactsContext(userId);
|
|
|
+ case "recent_summaries":
|
|
|
+ return getRecentSummariesContext(userId, params);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 用户身份基础信息(始终返回) */
|
|
|
+ private Map<String, Object> getUserIdentityContext(Long userId) {
|
|
|
+ Map<String, Object> result = new LinkedHashMap<>();
|
|
|
+ User user = userMapper.selectById(userId);
|
|
|
+ if (user == null) {
|
|
|
+ result.put("has_data", false);
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+ result.put("has_data", true);
|
|
|
+ result.put("用户姓名", user.getNickname() != null ? user.getNickname() : "");
|
|
|
+ result.put("用户角色", user.getRole() != null ? user.getRole() : "parent");
|
|
|
+ result.put("总能量", user.getTotalPoints() != null ? user.getTotalPoints() : 0);
|
|
|
+
|
|
|
+ // 家庭成员简要列表
|
|
|
+ List<ChildInfoDTO> children = userService.getChildren(userId);
|
|
|
+ if (children != null && !children.isEmpty()) {
|
|
|
+ List<Map<String, Object>> childList = new ArrayList<>();
|
|
|
+ for (ChildInfoDTO child : children) {
|
|
|
+ Map<String, Object> c = new LinkedHashMap<>();
|
|
|
+ c.put("姓名", child.getNickname() != null ? child.getNickname() : "");
|
|
|
+ c.put("年龄", child.getAge() != null ? child.getAge() : 0);
|
|
|
+ c.put("能量", child.getTotalPoints() != null ? child.getTotalPoints() : 0);
|
|
|
+ c.put("连续打卡", child.getStreakDays() != null ? child.getStreakDays() : 0);
|
|
|
+ childList.add(c);
|
|
|
+ }
|
|
|
+ result.put("家庭成员", childList);
|
|
|
+ } else {
|
|
|
+ result.put("家庭成员", Collections.emptyList());
|
|
|
+ }
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 健康报告上下文 */
|
|
|
+ private Map<String, Object> getHealthReportContext(Long userId, Map<String, Object> params) {
|
|
|
+ Map<String, Object> result = new LinkedHashMap<>();
|
|
|
+ result.put("数据类型", "健康报告");
|
|
|
+
|
|
|
+ Long targetUserId = userId;
|
|
|
+ if (params != null && params.get("userId") != null) {
|
|
|
+ targetUserId = Long.valueOf(params.get("userId").toString());
|
|
|
+ }
|
|
|
+
|
|
|
+ HealthReport report = healthReportService.getLatestReport(targetUserId);
|
|
|
+ if (report == null) {
|
|
|
+ result.put("has_data", false);
|
|
|
+ result.put("提示", "暂无健康报告数据");
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+ result.put("has_data", true);
|
|
|
+ result.put("报告日期", report.getReportDate() != null ? report.getReportDate().toString() : "");
|
|
|
+ result.put("整体评分", report.getOverallScore() != null ? report.getOverallScore() : "未知");
|
|
|
+ result.put("肠道健康评分", report.getGutHealthScore() != null ? report.getGutHealthScore() : "未知");
|
|
|
+ result.put("慢病风险评分", report.getChronicDiseaseScore() != null ? report.getChronicDiseaseScore() : "未知");
|
|
|
+ result.put("营养评分", report.getNutritionScore() != null ? report.getNutritionScore() : "未知");
|
|
|
+ result.put("肠道类型", report.getGutType() != null ? report.getGutType() : "未知");
|
|
|
+
|
|
|
+ // 可选:获取详细报告
|
|
|
+ if (params != null && Boolean.TRUE.equals(params.get("includeDetail"))) {
|
|
|
+ Map<String, Object> detail = healthReportService.getReportDetail(report.getId());
|
|
|
+ if (detail != null) {
|
|
|
+ result.put("报告详情", detail);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 任务进度上下文 */
|
|
|
+ private Map<String, Object> getTaskProgressContext(Long userId, Map<String, Object> params) {
|
|
|
+ Map<String, Object> result = new LinkedHashMap<>();
|
|
|
+ result.put("数据类型", "任务进度");
|
|
|
+
|
|
|
+ Long targetUserId = userId;
|
|
|
+ if (params != null && params.get("childId") != null) {
|
|
|
+ targetUserId = Long.valueOf(params.get("childId").toString());
|
|
|
+ }
|
|
|
+
|
|
|
+ List<ChildInfoDTO> children = userService.getChildren(userId);
|
|
|
+
|
|
|
+ List<Map<String, Object>> childTaskList = new ArrayList<>();
|
|
|
+ if (children != null) {
|
|
|
+ for (ChildInfoDTO child : children) {
|
|
|
+ Long childId = child.getId();
|
|
|
+ if (targetUserId != null && !targetUserId.equals(userId)) {
|
|
|
+ // 如果指定了 childId,只返回该孩子
|
|
|
+ if (!childId.equals(targetUserId)) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ Map<String, Object> childTasks = new LinkedHashMap<>();
|
|
|
+ childTasks.put("姓名", child.getNickname() != null ? child.getNickname() : "");
|
|
|
+ childTasks.put("年龄", child.getAge() != null ? child.getAge() : 0);
|
|
|
+
|
|
|
+ Map<String, Object> stats = taskService.getChildTaskStats(childId);
|
|
|
+ childTasks.put("任务完成率", stats.getOrDefault("completionRate", 0) + "%");
|
|
|
+ childTasks.put("完成任务数", stats.getOrDefault("completedCount", 0));
|
|
|
+ childTasks.put("总任务数", stats.getOrDefault("totalCount", 0));
|
|
|
+ childTasks.put("连续打卡天数", child.getStreakDays() != null ? child.getStreakDays() : 0);
|
|
|
+ childTaskList.add(childTasks);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ result.put("children_tasks", childTaskList);
|
|
|
+ result.put("has_data", !childTaskList.isEmpty());
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 孩子详细信息上下文 */
|
|
|
+ private Map<String, Object> getChildInfoContext(Long userId, Map<String, Object> params) {
|
|
|
+ Map<String, Object> result = new LinkedHashMap<>();
|
|
|
+ result.put("数据类型", "孩子信息");
|
|
|
+
|
|
|
+ List<ChildInfoDTO> children = userService.getChildren(userId);
|
|
|
+ if (children == null || children.isEmpty()) {
|
|
|
+ result.put("has_data", false);
|
|
|
+ result.put("提示", "暂无孩子信息");
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果指定了孩子姓名,筛选
|
|
|
+ String childName = params != null ? (String) params.get("childName") : null;
|
|
|
+ List<Map<String, Object>> childList = new ArrayList<>();
|
|
|
+
|
|
|
+ for (ChildInfoDTO child : children) {
|
|
|
+ if (childName != null && !childName.isEmpty()) {
|
|
|
+ String nickname = child.getNickname() != null ? child.getNickname() : "";
|
|
|
+ if (!nickname.contains(childName) && !childName.contains(nickname)) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ Map<String, Object> c = new LinkedHashMap<>();
|
|
|
+ c.put("姓名", child.getNickname() != null ? child.getNickname() : "");
|
|
|
+ c.put("年龄", child.getAge() != null ? child.getAge() : 0);
|
|
|
+ c.put("性别", child.getGender() != null ? child.getGender() : "");
|
|
|
+ c.put("能量", child.getTotalPoints() != null ? child.getTotalPoints() : 0);
|
|
|
+ c.put("连续打卡", child.getStreakDays() != null ? child.getStreakDays() : 0);
|
|
|
+ c.put("用户ID", child.getId());
|
|
|
+
|
|
|
+ // 健康报告
|
|
|
+ HealthReport report = healthReportService.getLatestReport(child.getId());
|
|
|
+ if (report != null) {
|
|
|
+ Map<String, Object> reportInfo = new LinkedHashMap<>();
|
|
|
+ reportInfo.put("报告日期", report.getReportDate() != null ? report.getReportDate().toString() : "");
|
|
|
+ reportInfo.put("整体评分", report.getOverallScore());
|
|
|
+ reportInfo.put("肠道类型", report.getGutType() != null ? report.getGutType() : "");
|
|
|
+ c.put("健康报告", reportInfo);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 任务统计
|
|
|
+ Map<String, Object> taskStats = taskService.getChildTaskStats(child.getId());
|
|
|
+ c.put("任务完成率", taskStats.getOrDefault("completionRate", 0) + "%");
|
|
|
+
|
|
|
+ childList.add(c);
|
|
|
+ }
|
|
|
+
|
|
|
+ result.put("children", childList);
|
|
|
+ result.put("has_data", !childList.isEmpty());
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 情绪状态上下文 */
|
|
|
+ private Map<String, Object> getEmotionStatusContext(Long userId, Map<String, Object> params) {
|
|
|
+ Map<String, Object> result = new LinkedHashMap<>();
|
|
|
+ result.put("数据类型", "情绪状态");
|
|
|
+
|
|
|
+ int days = 7; // 默认查最近7天
|
|
|
+ if (params != null && params.get("days") != null) {
|
|
|
+ days = Integer.valueOf(params.get("days").toString());
|
|
|
+ }
|
|
|
+
|
|
|
+ List<EmotionCheckin> recentCheckins = emotionCheckinMapper.selectList(
|
|
|
+ new LambdaQueryWrapper<EmotionCheckin>()
|
|
|
+ .eq(EmotionCheckin::getChildId, userId)
|
|
|
+ .orderByDesc(EmotionCheckin::getCreatedAt)
|
|
|
+ .last("LIMIT " + days)
|
|
|
+ );
|
|
|
+
|
|
|
+ if (recentCheckins == null || recentCheckins.isEmpty()) {
|
|
|
+ result.put("has_data", false);
|
|
|
+ result.put("提示", "最近暂无情绪打卡记录");
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ List<Map<String, Object>> checkinList = new ArrayList<>();
|
|
|
+ for (EmotionCheckin checkin : recentCheckins) {
|
|
|
+ Map<String, Object> item = new LinkedHashMap<>();
|
|
|
+ item.put("日期", checkin.getCreatedAt() != null ? checkin.getCreatedAt().toString() : "");
|
|
|
+ item.put("心情评分", checkin.getMoodScore() != null ? checkin.getMoodScore().toString() : "");
|
|
|
+ item.put("情绪标签", checkin.getEmotionTags() != null ? checkin.getEmotionTags() : "");
|
|
|
+ item.put("备注", checkin.getNote() != null ? checkin.getNote() : "");
|
|
|
+ checkinList.add(item);
|
|
|
+ }
|
|
|
+
|
|
|
+ result.put("has_data", true);
|
|
|
+ result.put("打卡记录", checkinList);
|
|
|
+ result.put("最近天数", days);
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 用户关键事实(Layer 2) */
|
|
|
+ private Map<String, Object> getUserFactsContext(Long userId) {
|
|
|
+ Map<String, Object> result = new LinkedHashMap<>();
|
|
|
+ result.put("数据类型", "用户关键事实");
|
|
|
+
|
|
|
+ List<AiUserFact> facts = aiUserFactMapper.selectList(
|
|
|
+ new LambdaQueryWrapper<AiUserFact>()
|
|
|
+ .eq(AiUserFact::getUserId, userId)
|
|
|
+ .orderByDesc(AiUserFact::getUpdatedAt)
|
|
|
+ .last("LIMIT 20")
|
|
|
+ );
|
|
|
+
|
|
|
+ if (facts == null || facts.isEmpty()) {
|
|
|
+ result.put("has_data", false);
|
|
|
+ result.put("提示", "暂无记录的关键事实");
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ List<Map<String, Object>> factList = new ArrayList<>();
|
|
|
+ for (AiUserFact fact : facts) {
|
|
|
+ Map<String, Object> f = new LinkedHashMap<>();
|
|
|
+ f.put("事实", fact.getFactKey() + ":" + fact.getFactValue());
|
|
|
+ f.put("置信度", fact.getConfidence() != null ? fact.getConfidence() : BigDecimal.ONE);
|
|
|
+ factList.add(f);
|
|
|
+ }
|
|
|
+
|
|
|
+ result.put("has_data", true);
|
|
|
+ result.put("事实列表", factList);
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 最近会话摘要(Layer 1) */
|
|
|
+ private Map<String, Object> getRecentSummariesContext(Long userId, Map<String, Object> params) {
|
|
|
+ Map<String, Object> result = new LinkedHashMap<>();
|
|
|
+ result.put("数据类型", "最近对话摘要");
|
|
|
+
|
|
|
+ int limit = 5;
|
|
|
+ if (params != null && params.get("limit") != null) {
|
|
|
+ limit = Integer.valueOf(params.get("limit").toString());
|
|
|
+ }
|
|
|
+
|
|
|
+ List<AiConversationSummary> summaries = aiConversationSummaryMapper.selectList(
|
|
|
+ new LambdaQueryWrapper<AiConversationSummary>()
|
|
|
+ .eq(AiConversationSummary::getUserId, userId)
|
|
|
+ .orderByDesc(AiConversationSummary::getCreatedAt)
|
|
|
+ .last("LIMIT " + limit)
|
|
|
+ );
|
|
|
+
|
|
|
+ if (summaries == null || summaries.isEmpty()) {
|
|
|
+ result.put("has_data", false);
|
|
|
+ result.put("提示", "暂无历史对话摘要");
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ List<Map<String, Object>> summaryList = new ArrayList<>();
|
|
|
+ for (AiConversationSummary summary : summaries) {
|
|
|
+ Map<String, Object> s = new LinkedHashMap<>();
|
|
|
+ s.put("摘要", summary.getSummaryText());
|
|
|
+ s.put("对话消息数", summary.getMessageCount());
|
|
|
+ s.put("时间", summary.getCreatedAt() != null ? summary.getCreatedAt().toString() : "");
|
|
|
+ summaryList.add(s);
|
|
|
+ }
|
|
|
+
|
|
|
+ result.put("has_data", true);
|
|
|
+ result.put("历史摘要", summaryList);
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+}
|