|
|
@@ -2,137 +2,83 @@ package com.fs.company.service.workflow.evolution.impl;
|
|
|
|
|
|
import com.alibaba.fastjson.JSON;
|
|
|
import com.alibaba.fastjson.JSONObject;
|
|
|
+import com.fs.company.domain.LobsterNodeOptimizationConfig;
|
|
|
+import com.fs.company.domain.LobsterUserNodeInteraction;
|
|
|
+import com.fs.company.domain.LobsterUserNodeOptimization;
|
|
|
+import com.fs.company.mapper.LobsterNodeOptimizationConfigMapper;
|
|
|
+import com.fs.company.mapper.LobsterUserNodeInteractionMapper;
|
|
|
+import com.fs.company.mapper.LobsterUserNodeOptimizationMapper;
|
|
|
import com.fs.company.service.llm.MultiModelRouter;
|
|
|
import com.fs.company.service.workflow.evolution.UserNodeOptimizer;
|
|
|
import org.slf4j.Logger;
|
|
|
import org.slf4j.LoggerFactory;
|
|
|
import org.springframework.beans.factory.annotation.Autowired;
|
|
|
-import org.springframework.jdbc.core.JdbcTemplate;
|
|
|
import org.springframework.stereotype.Service;
|
|
|
|
|
|
import java.util.*;
|
|
|
|
|
|
/**
|
|
|
* 用户级节点优化服务实现
|
|
|
- *
|
|
|
- * 核心设计理念:
|
|
|
- * 全局进化引擎(EvolutionEngine)优化的是工作流模板,影响所有用户
|
|
|
- * 本服务优化的是特定用户后续节点的执行内容,实现真正的"千人千面+自动进化"
|
|
|
- *
|
|
|
- * 优化触发条件:
|
|
|
- * - 用户在某个节点连续不回复(>=2次)
|
|
|
- * - 用户在某个节点的情感持续为负面
|
|
|
- * - 用户在某个节点的回复延迟显著增加
|
|
|
- * - 用户主动表达不满或转人工意图
|
|
|
- *
|
|
|
- * 优化策略:
|
|
|
- * - 话术风格调整:根据用户偏好调整语气(正式/轻松/温暖)
|
|
|
- * - 话术内容替换:根据用户画像替换关键话术内容
|
|
|
- * - 节点跳过建议:建议跳过对特定用户无意义的节点
|
|
|
- * - 时机优化:建议调整发送时间
|
|
|
- *
|
|
|
- * 审核机制:
|
|
|
- * - 每个优化建议都包含optimizationReason(优化原因)和optimizationDetail(详细说明)
|
|
|
- * - 自动模式(autoApply=true):置信度>=阈值时自动应用
|
|
|
- * - 审核模式(autoApply=false):所有优化需人工确认
|
|
|
- * - 批量审核:支持审核人员一次审核多条建议,每条含优化原因
|
|
|
- *
|
|
|
- * 数据表:
|
|
|
- * - lobster_user_node_interaction: 用户节点交互记录表
|
|
|
- * - lobster_user_node_optimization: 用户级节点优化记录表(含优化原因、审核状态)
|
|
|
- * - lobster_node_optimization_config: 节点优化配置表(per-node开关)
|
|
|
*/
|
|
|
@Service
|
|
|
public class UserNodeOptimizerImpl implements UserNodeOptimizer {
|
|
|
|
|
|
private static final Logger logger = LoggerFactory.getLogger(UserNodeOptimizerImpl.class);
|
|
|
|
|
|
- private static final String INTERACTION_TABLE = "lobster_user_node_interaction";
|
|
|
- private static final String OPTIMIZATION_TABLE = "lobster_user_node_optimization";
|
|
|
- private static final String CONFIG_TABLE = "lobster_node_optimization_config";
|
|
|
-
|
|
|
- /** 触发优化的连续不回复次数阈值 */
|
|
|
private static final int NO_REPLY_TRIGGER_THRESHOLD = 2;
|
|
|
-
|
|
|
- /** 触发优化的负面情感次数阈值 */
|
|
|
private static final int NEGATIVE_SENTIMENT_THRESHOLD = 2;
|
|
|
-
|
|
|
- /** 自动应用的置信度阈值 */
|
|
|
private static final double AUTO_APPLY_CONFIDENCE = 0.8;
|
|
|
|
|
|
@Autowired
|
|
|
- private JdbcTemplate jdbcTemplate;
|
|
|
+ private LobsterUserNodeInteractionMapper interactionMapper;
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private LobsterUserNodeOptimizationMapper optimizationMapper;
|
|
|
|
|
|
@Autowired
|
|
|
+ private LobsterNodeOptimizationConfigMapper configMapper;
|
|
|
+
|
|
|
+ @Autowired(required = false)
|
|
|
private MultiModelRouter multiModelRouter;
|
|
|
|
|
|
- /**
|
|
|
- * 记录用户节点交互数据
|
|
|
- *
|
|
|
- * 每次用户与工作流节点交互后调用,积累数据用于后续优化分析
|
|
|
- * 交互数据包括:发送的消息、用户回复内容、回复延迟、情感分析结果等
|
|
|
- *
|
|
|
- * @param companyId 租户ID
|
|
|
- * @param workflowId 工作流ID
|
|
|
- * @param instanceId 实例ID
|
|
|
- * @param externalUserId 外部用户ID
|
|
|
- * @param nodeCode 节点编码
|
|
|
- * @param interaction 交互数据Map,包含:
|
|
|
- * - sentMessage: 发送的消息内容
|
|
|
- * - customerReply: 用户回复内容(可能为空)
|
|
|
- * - replyDelayMs: 回复延迟毫秒数(可能为空)
|
|
|
- * - sentiment: 情感值(-1到1,负值表示负面)
|
|
|
- * - intent: 识别的意图
|
|
|
- * - outcome: 结果(replied/no_reply/negative/positive)
|
|
|
- */
|
|
|
@Override
|
|
|
public void recordUserInteraction(Long companyId, Long workflowId, Long instanceId,
|
|
|
String externalUserId, String nodeCode,
|
|
|
Map<String, Object> interaction) {
|
|
|
- ensureInteractionTable();
|
|
|
try {
|
|
|
- String sql = "INSERT INTO " + INTERACTION_TABLE + " " +
|
|
|
- "(company_id, workflow_id, instance_id, external_user_id, node_code, " +
|
|
|
- "sent_message, customer_reply, reply_delay_ms, sentiment, intent, outcome, create_time) " +
|
|
|
- "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW())";
|
|
|
- jdbcTemplate.update(sql,
|
|
|
- companyId, workflowId, instanceId, externalUserId, nodeCode,
|
|
|
- interaction.get("sentMessage"),
|
|
|
- interaction.get("customerReply"),
|
|
|
- interaction.get("replyDelayMs") != null ? ((Number) interaction.get("replyDelayMs")).longValue() : null,
|
|
|
- interaction.get("sentiment") != null ? ((Number) interaction.get("sentiment")).doubleValue() : null,
|
|
|
- interaction.get("intent"),
|
|
|
- interaction.get("outcome"));
|
|
|
+ LobsterUserNodeInteraction entity = new LobsterUserNodeInteraction();
|
|
|
+ entity.setCompanyId(companyId);
|
|
|
+ entity.setWorkflowId(workflowId);
|
|
|
+ entity.setInstanceId(instanceId);
|
|
|
+ entity.setExternalUserId(externalUserId);
|
|
|
+ entity.setNodeCode(nodeCode);
|
|
|
+ entity.setSentMessage((String) interaction.get("sentMessage"));
|
|
|
+ entity.setCustomerReply((String) interaction.get("customerReply"));
|
|
|
+ entity.setReplyDelayMs(interaction.get("replyDelayMs") != null ?
|
|
|
+ ((Number) interaction.get("replyDelayMs")).longValue() : null);
|
|
|
+ entity.setSentiment(interaction.get("sentiment") != null ?
|
|
|
+ ((Number) interaction.get("sentiment")).doubleValue() : null);
|
|
|
+ entity.setIntent((String) interaction.get("intent"));
|
|
|
+ entity.setOutcome((String) interaction.get("outcome"));
|
|
|
+ entity.setCreateTime(java.time.LocalDateTime.now());
|
|
|
+
|
|
|
+ interactionMapper.insert(entity);
|
|
|
} catch (Exception e) {
|
|
|
logger.error("[UserNodeOptimizer] 记录用户交互失败: companyId={}, userId={}, nodeCode={}",
|
|
|
companyId, externalUserId, nodeCode, e);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- /**
|
|
|
- * 分析用户行为并生成优化建议
|
|
|
- *
|
|
|
- * 分析流程:
|
|
|
- * 1. 统计用户在每个节点的交互指标(回复率、平均情感、平均延迟)
|
|
|
- * 2. 识别需要优化的节点(回复率低、情感负面、延迟增加)
|
|
|
- * 3. 使用AI模型生成个性化优化建议
|
|
|
- * 4. 为每个优化建议生成详细的优化原因说明
|
|
|
- * 5. 保存优化建议到数据库
|
|
|
- *
|
|
|
- * @param companyId 租户ID
|
|
|
- * @param externalUserId 外部用户ID
|
|
|
- * @param workflowId 工作流ID(可选,null表示分析所有工作流)
|
|
|
- * @return 优化建议列表
|
|
|
- */
|
|
|
@Override
|
|
|
public List<UserNodeOptimization> analyzeAndSuggest(Long companyId, String externalUserId, Long workflowId) {
|
|
|
List<UserNodeOptimization> suggestions = new ArrayList<>();
|
|
|
try {
|
|
|
- ensureInteractionTable();
|
|
|
- ensureOptimizationTable();
|
|
|
-
|
|
|
- /* 第一步:查找该用户需要优化的节点 */
|
|
|
- List<Map<String, Object>> problemNodes = findProblemNodes(companyId, externalUserId, workflowId);
|
|
|
+ List<Map<String, Object>> problemNodes;
|
|
|
+ if (workflowId != null) {
|
|
|
+ problemNodes = interactionMapper.findProblemNodes(companyId, externalUserId, workflowId, NO_REPLY_TRIGGER_THRESHOLD);
|
|
|
+ } else {
|
|
|
+ problemNodes = interactionMapper.findProblemNodesAll(companyId, externalUserId, NO_REPLY_TRIGGER_THRESHOLD);
|
|
|
+ }
|
|
|
|
|
|
for (Map<String, Object> problemNode : problemNodes) {
|
|
|
String nodeCode = (String) problemNode.get("node_code");
|
|
|
@@ -142,27 +88,21 @@ public class UserNodeOptimizerImpl implements UserNodeOptimizer {
|
|
|
((Number) problemNode.get("avg_sentiment")).doubleValue() : 0.0;
|
|
|
String currentMessage = (String) problemNode.get("sent_message");
|
|
|
|
|
|
- /* 检查该节点是否开启了自动优化 */
|
|
|
if (!isOptimizationEnabled(companyId, wfId, nodeCode)) {
|
|
|
logger.debug("[UserNodeOptimizer] 节点优化已关闭: companyId={}, nodeCode={}", companyId, nodeCode);
|
|
|
continue;
|
|
|
}
|
|
|
|
|
|
- /* 第二步:生成优化原因说明 */
|
|
|
String reason = buildOptimizationReason(noReplyRate, avgSentiment, problemNode);
|
|
|
|
|
|
- /* 第三步:使用AI生成个性化优化内容 */
|
|
|
String optimizedContent = generateOptimizedContent(
|
|
|
companyId, externalUserId, nodeCode, currentMessage, reason);
|
|
|
-
|
|
|
if (optimizedContent == null || optimizedContent.equals(currentMessage)) {
|
|
|
continue;
|
|
|
}
|
|
|
|
|
|
- /* 第四步:计算置信度 */
|
|
|
double confidence = calculateConfidence(noReplyRate, avgSentiment, problemNode);
|
|
|
|
|
|
- /* 第五步:保存优化建议 */
|
|
|
UserNodeOptimization optimization = new UserNodeOptimization();
|
|
|
optimization.setCompanyId(companyId);
|
|
|
optimization.setWorkflowId(wfId);
|
|
|
@@ -181,7 +121,7 @@ public class UserNodeOptimizerImpl implements UserNodeOptimizer {
|
|
|
suggestions.add(optimization);
|
|
|
|
|
|
logger.info("[UserNodeOptimizer] 生成用户级优化: companyId={}, userId={}, nodeCode={}, " +
|
|
|
- "confidence={}, reason={}", companyId, externalUserId, nodeCode, confidence, reason);
|
|
|
+ "confidence={}, reason={}", companyId, externalUserId, nodeCode, confidence, reason);
|
|
|
}
|
|
|
} catch (Exception e) {
|
|
|
logger.error("[UserNodeOptimizer] 分析优化建议失败: companyId={}, userId={}",
|
|
|
@@ -190,72 +130,29 @@ public class UserNodeOptimizerImpl implements UserNodeOptimizer {
|
|
|
return suggestions;
|
|
|
}
|
|
|
|
|
|
- /**
|
|
|
- * 获取用户在特定节点的优化内容
|
|
|
- *
|
|
|
- * 查询逻辑:
|
|
|
- * 1. 查找该用户在该节点上已应用的优化记录
|
|
|
- * 2. 如果有,返回优化后的内容
|
|
|
- * 3. 如果没有,返回null(使用原始模板内容)
|
|
|
- *
|
|
|
- * @param companyId 租户ID
|
|
|
- * @param externalUserId 外部用户ID
|
|
|
- * @param nodeCode 节点编码
|
|
|
- * @return 优化后的消息内容,无优化时返回null
|
|
|
- */
|
|
|
@Override
|
|
|
public String getOptimizedNodeContent(Long companyId, String externalUserId, String nodeCode) {
|
|
|
try {
|
|
|
- ensureOptimizationTable();
|
|
|
- String sql = "SELECT optimized_content FROM " + OPTIMIZATION_TABLE + " " +
|
|
|
- "WHERE company_id = ? AND external_user_id = ? AND node_code = ? " +
|
|
|
- "AND status = 'applied' ORDER BY apply_time DESC LIMIT 1";
|
|
|
- return jdbcTemplate.queryForObject(sql, String.class, companyId, externalUserId, nodeCode);
|
|
|
+ return optimizationMapper.findAppliedContent(companyId, externalUserId, nodeCode);
|
|
|
} catch (Exception e) {
|
|
|
return null;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- /**
|
|
|
- * 应用优化建议
|
|
|
- *
|
|
|
- * 应用逻辑:
|
|
|
- * - autoApply=true: 直接标记为applied状态,下次该用户执行该节点时使用优化内容
|
|
|
- * - autoApply=false: 标记为pending_audit状态,等待审核人员确认
|
|
|
- *
|
|
|
- * 安全机制:
|
|
|
- * - 应用前记录原始内容,支持回滚
|
|
|
- * - 每次应用操作都有审计日志
|
|
|
- * - 只能应用pending状态的优化建议
|
|
|
- *
|
|
|
- * @param companyId 租户ID
|
|
|
- * @param optimizationId 优化记录ID
|
|
|
- * @param autoApply 是否自动应用
|
|
|
- * @return 应用结果
|
|
|
- */
|
|
|
@Override
|
|
|
public ApplyOptimizationResult applyOptimization(Long companyId, Long optimizationId, boolean autoApply) {
|
|
|
try {
|
|
|
- ensureOptimizationTable();
|
|
|
- String querySql = "SELECT * FROM " + OPTIMIZATION_TABLE + " " +
|
|
|
- "WHERE id = ? AND company_id = ? AND status = 'pending'";
|
|
|
- Map<String, Object> record = jdbcTemplate.queryForMap(querySql, optimizationId, companyId);
|
|
|
+ LobsterUserNodeOptimization record = optimizationMapper.selectPendingById(optimizationId, companyId);
|
|
|
if (record == null) {
|
|
|
return ApplyOptimizationResult.failed("优化建议不存在或已处理");
|
|
|
}
|
|
|
|
|
|
if (autoApply) {
|
|
|
- /* 自动应用:直接标记为applied */
|
|
|
- String updateSql = "UPDATE " + OPTIMIZATION_TABLE + " " +
|
|
|
- "SET status = 'applied', apply_time = NOW() WHERE id = ?";
|
|
|
- jdbcTemplate.update(updateSql, optimizationId);
|
|
|
+ optimizationMapper.markApplied(optimizationId);
|
|
|
logger.info("[UserNodeOptimizer] 自动应用优化: id={}, companyId={}", optimizationId, companyId);
|
|
|
return ApplyOptimizationResult.autoApplied("优化已自动应用");
|
|
|
} else {
|
|
|
- /* 需要审核:标记为pending_audit */
|
|
|
- String updateSql = "UPDATE " + OPTIMIZATION_TABLE + " " +
|
|
|
- "SET status = 'pending_audit' WHERE id = ?";
|
|
|
- jdbcTemplate.update(updateSql, optimizationId);
|
|
|
+ optimizationMapper.markPendingAudit(optimizationId);
|
|
|
logger.info("[UserNodeOptimizer] 优化待审核: id={}, companyId={}", optimizationId, companyId);
|
|
|
return ApplyOptimizationResult.pendingAudit("优化建议已提交审核");
|
|
|
}
|
|
|
@@ -265,26 +162,6 @@ public class UserNodeOptimizerImpl implements UserNodeOptimizer {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- /**
|
|
|
- * 批量审核优化建议
|
|
|
- *
|
|
|
- * 审核流程:
|
|
|
- * 1. 遍历每条审核项
|
|
|
- * 2. approved=true: 标记为applied并设置apply_time
|
|
|
- * 3. approved=false: 标记为rejected并记录拒绝原因
|
|
|
- * 4. 记录审核人和审核时间
|
|
|
- * 5. 返回审核统计结果
|
|
|
- *
|
|
|
- * 设计目的:
|
|
|
- * 当用户量大时,优化建议会很多,审核人员需要批量处理
|
|
|
- * 每条建议都包含optimizationReason(优化原因),方便审核人员快速判断
|
|
|
- *
|
|
|
- * @param companyId 租户ID
|
|
|
- * @param auditItems 审核项列表
|
|
|
- * @param auditorId 审核人ID
|
|
|
- * @param auditRemark 审核备注
|
|
|
- * @return 批量审核结果统计
|
|
|
- */
|
|
|
@Override
|
|
|
public BatchAuditResult batchAudit(Long companyId, List<AuditItem> auditItems, String auditorId, String auditRemark) {
|
|
|
BatchAuditResult result = new BatchAuditResult();
|
|
|
@@ -293,26 +170,12 @@ public class UserNodeOptimizerImpl implements UserNodeOptimizer {
|
|
|
|
|
|
for (AuditItem item : auditItems) {
|
|
|
try {
|
|
|
- if (item.isApproved()) {
|
|
|
- /* 审核通过:标记为applied */
|
|
|
- String sql = "UPDATE " + OPTIMIZATION_TABLE + " " +
|
|
|
- "SET status = 'applied', auditor_id = ?, audit_remark = ?, " +
|
|
|
- "audit_time = NOW(), apply_time = NOW() WHERE id = ? AND company_id = ? " +
|
|
|
- "AND status IN ('pending', 'pending_audit')";
|
|
|
- int updated = jdbcTemplate.update(sql, auditorId,
|
|
|
- item.getRemark() != null ? item.getRemark() : auditRemark,
|
|
|
- item.getOptimizationId(), companyId);
|
|
|
- if (updated > 0) approvedCount++;
|
|
|
- } else {
|
|
|
- /* 审核拒绝:标记为rejected */
|
|
|
- String sql = "UPDATE " + OPTIMIZATION_TABLE + " " +
|
|
|
- "SET status = 'rejected', auditor_id = ?, audit_remark = ?, " +
|
|
|
- "audit_time = NOW() WHERE id = ? AND company_id = ? " +
|
|
|
- "AND status IN ('pending', 'pending_audit')";
|
|
|
- int updated = jdbcTemplate.update(sql, auditorId,
|
|
|
- item.getRemark() != null ? item.getRemark() : auditRemark,
|
|
|
- item.getOptimizationId(), companyId);
|
|
|
- if (updated > 0) rejectedCount++;
|
|
|
+ String status = item.isApproved() ? "applied" : "rejected";
|
|
|
+ String remark = item.getRemark() != null ? item.getRemark() : auditRemark;
|
|
|
+ int updated = optimizationMapper.audit(item.getOptimizationId(), companyId, status, auditorId, remark);
|
|
|
+ if (updated > 0) {
|
|
|
+ if (item.isApproved()) approvedCount++;
|
|
|
+ else rejectedCount++;
|
|
|
}
|
|
|
} catch (Exception e) {
|
|
|
logger.error("[UserNodeOptimizer] 审核单条失败: optimizationId={}", item.getOptimizationId(), e);
|
|
|
@@ -329,51 +192,27 @@ public class UserNodeOptimizerImpl implements UserNodeOptimizer {
|
|
|
return result;
|
|
|
}
|
|
|
|
|
|
- /**
|
|
|
- * 获取待审核的优化建议列表
|
|
|
- *
|
|
|
- * 返回字段包含:
|
|
|
- * - optimizationReason: 优化原因(如"该用户连续3次未回复此节点")
|
|
|
- * - optimizationDetail: 详细数据(如"回复率: 0%, 平均情感: -0.5, 平均延迟: 无")
|
|
|
- * - originalContent: 原始话术内容
|
|
|
- * - optimizedContent: 优化后话术内容
|
|
|
- * - confidence: 置信度
|
|
|
- *
|
|
|
- * 审核人员可以对比原始内容和优化内容,结合优化原因快速判断是否通过
|
|
|
- *
|
|
|
- * @param companyId 租户ID
|
|
|
- * @param workflowId 工作流ID(可选,null表示所有工作流)
|
|
|
- * @param page 页码
|
|
|
- * @param pageSize 每页数量
|
|
|
- * @return 待审核的优化建议列表
|
|
|
- */
|
|
|
@Override
|
|
|
public List<UserNodeOptimization> getPendingAuditList(Long companyId, Long workflowId, int page, int pageSize) {
|
|
|
List<UserNodeOptimization> result = new ArrayList<>();
|
|
|
try {
|
|
|
- ensureOptimizationTable();
|
|
|
- StringBuilder sql = new StringBuilder();
|
|
|
- sql.append("SELECT id, company_id, workflow_id, external_user_id, node_code, ");
|
|
|
- sql.append("original_content, optimized_content, optimization_type, confidence, ");
|
|
|
- sql.append("optimization_reason, optimization_detail, status, create_time ");
|
|
|
- sql.append("FROM ").append(OPTIMIZATION_TABLE).append(" ");
|
|
|
- sql.append("WHERE company_id = ? AND status IN ('pending', 'pending_audit') ");
|
|
|
- if (workflowId != null) {
|
|
|
- sql.append("AND workflow_id = ? ");
|
|
|
- }
|
|
|
- sql.append("ORDER BY confidence DESC, create_time DESC ");
|
|
|
- sql.append("LIMIT ? OFFSET ?");
|
|
|
-
|
|
|
int offset = (page - 1) * pageSize;
|
|
|
- List<Map<String, Object>> rows;
|
|
|
- if (workflowId != null) {
|
|
|
- rows = jdbcTemplate.queryForList(sql.toString(), companyId, workflowId, pageSize, offset);
|
|
|
- } else {
|
|
|
- rows = jdbcTemplate.queryForList(sql.toString(), companyId, pageSize, offset);
|
|
|
- }
|
|
|
-
|
|
|
- for (Map<String, Object> row : rows) {
|
|
|
- UserNodeOptimization opt = mapRowToOptimization(row);
|
|
|
+ List<LobsterUserNodeOptimization> rows = optimizationMapper.selectPendingAudit(companyId, pageSize, offset);
|
|
|
+ for (LobsterUserNodeOptimization row : rows) {
|
|
|
+ UserNodeOptimization opt = new UserNodeOptimization();
|
|
|
+ opt.setId(row.getId());
|
|
|
+ opt.setCompanyId(row.getCompanyId());
|
|
|
+ opt.setWorkflowId(row.getWorkflowId());
|
|
|
+ opt.setExternalUserId(row.getExternalUserId());
|
|
|
+ opt.setNodeCode(row.getNodeCode());
|
|
|
+ opt.setOriginalContent(row.getOriginalContent());
|
|
|
+ opt.setOptimizedContent(row.getOptimizedContent());
|
|
|
+ opt.setOptimizationType(row.getOptimizationType());
|
|
|
+ opt.setConfidence(row.getConfidence() != null ? row.getConfidence() : 0.0);
|
|
|
+ opt.setOptimizationReason(row.getOptimizationReason());
|
|
|
+ opt.setOptimizationDetail(row.getOptimizationDetail());
|
|
|
+ opt.setStatus(row.getStatus());
|
|
|
+ opt.setCreateTime(row.getCreateTime() != null ? java.util.Date.from(row.getCreateTime().atZone(java.time.ZoneId.systemDefault()).toInstant()) : null);
|
|
|
result.add(opt);
|
|
|
}
|
|
|
} catch (Exception e) {
|
|
|
@@ -382,131 +221,118 @@ public class UserNodeOptimizerImpl implements UserNodeOptimizer {
|
|
|
return result;
|
|
|
}
|
|
|
|
|
|
- /**
|
|
|
- * 获取优化统计信息
|
|
|
- */
|
|
|
@Override
|
|
|
public Map<String, Object> getOptimizationStats(Long companyId) {
|
|
|
Map<String, Object> stats = new HashMap<>();
|
|
|
try {
|
|
|
- ensureOptimizationTable();
|
|
|
-
|
|
|
- Integer total = jdbcTemplate.queryForObject(
|
|
|
- "SELECT COUNT(*) FROM " + OPTIMIZATION_TABLE + " WHERE company_id = ?",
|
|
|
- Integer.class, companyId);
|
|
|
- stats.put("totalOptimizations", total != null ? total : 0);
|
|
|
-
|
|
|
- Integer pending = jdbcTemplate.queryForObject(
|
|
|
- "SELECT COUNT(*) FROM " + OPTIMIZATION_TABLE + " WHERE company_id = ? AND status = 'pending'",
|
|
|
- Integer.class, companyId);
|
|
|
- stats.put("pendingOptimizations", pending != null ? pending : 0);
|
|
|
-
|
|
|
- Integer pendingAudit = jdbcTemplate.queryForObject(
|
|
|
- "SELECT COUNT(*) FROM " + OPTIMIZATION_TABLE + " WHERE company_id = ? AND status = 'pending_audit'",
|
|
|
- Integer.class, companyId);
|
|
|
- stats.put("pendingAuditOptimizations", pendingAudit != null ? pendingAudit : 0);
|
|
|
-
|
|
|
- Integer applied = jdbcTemplate.queryForObject(
|
|
|
- "SELECT COUNT(*) FROM " + OPTIMIZATION_TABLE + " WHERE company_id = ? AND status = 'applied'",
|
|
|
- Integer.class, companyId);
|
|
|
- stats.put("appliedOptimizations", applied != null ? applied : 0);
|
|
|
-
|
|
|
- Integer rejected = jdbcTemplate.queryForObject(
|
|
|
- "SELECT COUNT(*) FROM " + OPTIMIZATION_TABLE + " WHERE company_id = ? AND status = 'rejected'",
|
|
|
- Integer.class, companyId);
|
|
|
- stats.put("rejectedOptimizations", rejected != null ? rejected : 0);
|
|
|
-
|
|
|
- /* 按优化类型统计 */
|
|
|
- List<Map<String, Object>> typeStats = jdbcTemplate.queryForList(
|
|
|
- "SELECT optimization_type, COUNT(*) as count FROM " + OPTIMIZATION_TABLE +
|
|
|
- " WHERE company_id = ? GROUP BY optimization_type", companyId);
|
|
|
- stats.put("optimizationByType", typeStats);
|
|
|
-
|
|
|
+ stats.put("totalOptimizations", optimizationMapper.countByCompanyId(companyId));
|
|
|
+ stats.put("pendingOptimizations", optimizationMapper.countByStatus(companyId, "pending"));
|
|
|
+ stats.put("pendingAuditOptimizations", optimizationMapper.countByStatus(companyId, "pending_audit"));
|
|
|
+ stats.put("appliedOptimizations", optimizationMapper.countByStatus(companyId, "applied"));
|
|
|
+ stats.put("rejectedOptimizations", optimizationMapper.countByStatus(companyId, "rejected"));
|
|
|
+ stats.put("optimizationByType", optimizationMapper.countGroupByType(companyId));
|
|
|
} catch (Exception e) {
|
|
|
logger.error("[UserNodeOptimizer] 获取统计信息失败: companyId={}", companyId, e);
|
|
|
}
|
|
|
return stats;
|
|
|
}
|
|
|
|
|
|
- /**
|
|
|
- * 查找需要优化的节点
|
|
|
- *
|
|
|
- * 判定条件(满足任一):
|
|
|
- * - 该用户在该节点的回复率 < 50%(至少2次交互)
|
|
|
- * - 该用户在该节点的平均情感 < -0.3(至少2次交互)
|
|
|
- * - 该用户在该节点的回复延迟显著增加(比自身平均延迟高50%以上)
|
|
|
- */
|
|
|
- private List<Map<String, Object>> findProblemNodes(Long companyId, String externalUserId, Long workflowId) {
|
|
|
- StringBuilder sql = new StringBuilder();
|
|
|
- sql.append("SELECT workflow_id, node_code, sent_message, ");
|
|
|
- sql.append("COUNT(*) as total, ");
|
|
|
- sql.append("SUM(CASE WHEN customer_reply IS NULL OR customer_reply = '' THEN 1 ELSE 0 END) as no_reply_count, ");
|
|
|
- sql.append("ROUND(SUM(CASE WHEN customer_reply IS NULL OR customer_reply = '' THEN 1 ELSE 0 END) * 100.0 / COUNT(*), 2) as no_reply_rate, ");
|
|
|
- sql.append("AVG(sentiment) as avg_sentiment, ");
|
|
|
- sql.append("AVG(reply_delay_ms) as avg_delay_ms ");
|
|
|
- sql.append("FROM ").append(INTERACTION_TABLE).append(" ");
|
|
|
- sql.append("WHERE company_id = ? AND external_user_id = ? ");
|
|
|
- if (workflowId != null) {
|
|
|
- sql.append("AND workflow_id = ? ");
|
|
|
+ private boolean isOptimizationEnabled(Long companyId, Long workflowId, String nodeCode) {
|
|
|
+ try {
|
|
|
+ Boolean enabled = configMapper.findEnabled(companyId, workflowId, nodeCode);
|
|
|
+ if (enabled != null) return enabled;
|
|
|
+
|
|
|
+ enabled = configMapper.findEnabled(companyId, workflowId, "*");
|
|
|
+ if (enabled != null) return enabled;
|
|
|
+
|
|
|
+ enabled = configMapper.findEnabled(companyId, 0L, "*");
|
|
|
+ if (enabled != null) return enabled;
|
|
|
+ } catch (Exception e) {
|
|
|
+ logger.debug("[UserNodeOptimizer] 检查优化开关失败: {}", e.getMessage());
|
|
|
}
|
|
|
- sql.append("GROUP BY workflow_id, node_code, sent_message ");
|
|
|
- sql.append("HAVING COUNT(*) >= ? AND (");
|
|
|
- sql.append(" no_reply_rate > 50 ");
|
|
|
- sql.append(" OR avg_sentiment < -0.3 ");
|
|
|
- sql.append(") ");
|
|
|
- sql.append("ORDER BY no_reply_rate DESC LIMIT 10");
|
|
|
+ return true;
|
|
|
+ }
|
|
|
|
|
|
+ public void setOptimizationConfig(Long companyId, Long workflowId, String nodeCode,
|
|
|
+ boolean enabled, boolean autoApply, String configBy) {
|
|
|
try {
|
|
|
- if (workflowId != null) {
|
|
|
- return jdbcTemplate.queryForList(sql.toString(),
|
|
|
- companyId, externalUserId, workflowId, NO_REPLY_TRIGGER_THRESHOLD);
|
|
|
- } else {
|
|
|
- return jdbcTemplate.queryForList(sql.toString(),
|
|
|
- companyId, externalUserId, NO_REPLY_TRIGGER_THRESHOLD);
|
|
|
+ LobsterNodeOptimizationConfig entity = new LobsterNodeOptimizationConfig();
|
|
|
+ entity.setCompanyId(companyId);
|
|
|
+ entity.setWorkflowId(workflowId);
|
|
|
+ entity.setNodeCode(nodeCode);
|
|
|
+ entity.setEnabled(enabled);
|
|
|
+ entity.setAutoApply(autoApply);
|
|
|
+ entity.setConfigBy(configBy);
|
|
|
+ entity.setUpdateTime(java.time.LocalDateTime.now());
|
|
|
+ configMapper.upsert(entity);
|
|
|
+ logger.info("[UserNodeOptimizer] 更新优化配置: companyId={}, workflowId={}, nodeCode={}, " +
|
|
|
+ "enabled={}, autoApply={}", companyId, workflowId, nodeCode, enabled, autoApply);
|
|
|
+ } catch (Exception e) {
|
|
|
+ logger.error("[UserNodeOptimizer] 更新优化配置失败", e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public Map<String, Object> getOptimizationConfig(Long companyId, Long workflowId, String nodeCode) {
|
|
|
+ try {
|
|
|
+ LobsterNodeOptimizationConfig config = configMapper.selectByKey(companyId, workflowId, nodeCode);
|
|
|
+ if (config != null) {
|
|
|
+ Map<String, Object> result = new HashMap<>();
|
|
|
+ result.put("companyId", config.getCompanyId());
|
|
|
+ result.put("workflowId", config.getWorkflowId());
|
|
|
+ result.put("nodeCode", config.getNodeCode());
|
|
|
+ result.put("enabled", config.getEnabled());
|
|
|
+ result.put("autoApply", config.getAutoApply());
|
|
|
+ return result;
|
|
|
}
|
|
|
} catch (Exception e) {
|
|
|
- logger.error("[UserNodeOptimizer] 查找问题节点失败", e);
|
|
|
- return Collections.emptyList();
|
|
|
+ // 返回默认值
|
|
|
+ }
|
|
|
+ Map<String, Object> defaults = new HashMap<>();
|
|
|
+ defaults.put("companyId", companyId);
|
|
|
+ defaults.put("workflowId", workflowId);
|
|
|
+ defaults.put("nodeCode", nodeCode);
|
|
|
+ defaults.put("enabled", true);
|
|
|
+ defaults.put("autoApply", false);
|
|
|
+ return defaults;
|
|
|
+ }
|
|
|
+
|
|
|
+ private void saveOptimization(UserNodeOptimization optimization) {
|
|
|
+ try {
|
|
|
+ LobsterUserNodeOptimization entity = new LobsterUserNodeOptimization();
|
|
|
+ entity.setCompanyId(optimization.getCompanyId());
|
|
|
+ entity.setWorkflowId(optimization.getWorkflowId());
|
|
|
+ entity.setExternalUserId(optimization.getExternalUserId());
|
|
|
+ entity.setNodeCode(optimization.getNodeCode());
|
|
|
+ entity.setOriginalContent(optimization.getOriginalContent());
|
|
|
+ entity.setOptimizedContent(optimization.getOptimizedContent());
|
|
|
+ entity.setOptimizationType(optimization.getOptimizationType());
|
|
|
+ entity.setConfidence(optimization.getConfidence());
|
|
|
+ entity.setOptimizationReason(optimization.getOptimizationReason());
|
|
|
+ entity.setOptimizationDetail(optimization.getOptimizationDetail());
|
|
|
+ entity.setStatus(optimization.getStatus());
|
|
|
+ entity.setCreateTime(java.time.LocalDateTime.now());
|
|
|
+ optimizationMapper.insert(entity);
|
|
|
+ } catch (Exception e) {
|
|
|
+ logger.error("[UserNodeOptimizer] 保存优化记录失败", e);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- /**
|
|
|
- * 构建优化原因说明
|
|
|
- *
|
|
|
- * 生成人类可读的优化原因,方便审核人员快速理解
|
|
|
- * 例如:"该用户在此节点连续3次未回复,不回复率75%,平均情感-0.4,建议调整话术风格"
|
|
|
- */
|
|
|
private String buildOptimizationReason(double noReplyRate, double avgSentiment, Map<String, Object> nodeData) {
|
|
|
StringBuilder reason = new StringBuilder();
|
|
|
reason.append("该用户在此节点");
|
|
|
-
|
|
|
int total = nodeData.get("total") != null ? ((Number) nodeData.get("total")).intValue() : 0;
|
|
|
int noReplyCount = nodeData.get("no_reply_count") != null ? ((Number) nodeData.get("no_reply_count")).intValue() : 0;
|
|
|
-
|
|
|
- if (noReplyRate > 50) {
|
|
|
- reason.append(String.format("连续%d次未回复(不回复率%.0f%%)", noReplyCount, noReplyRate));
|
|
|
- }
|
|
|
+ if (noReplyRate > 50) reason.append(String.format("连续%d次未回复(不回复率%.0f%%)", noReplyCount, noReplyRate));
|
|
|
if (avgSentiment < -0.3) {
|
|
|
if (noReplyRate > 50) reason.append(",");
|
|
|
reason.append(String.format("平均情感偏负面(%.2f)", avgSentiment));
|
|
|
}
|
|
|
-
|
|
|
- if (noReplyRate > 70) {
|
|
|
- reason.append(",建议更换话术风格和内容");
|
|
|
- } else if (noReplyRate > 50) {
|
|
|
- reason.append(",建议调整话术语气和表达方式");
|
|
|
- } else if (avgSentiment < -0.3) {
|
|
|
- reason.append(",建议使用更温暖关怀的话术");
|
|
|
- }
|
|
|
-
|
|
|
+ if (noReplyRate > 70) reason.append(",建议更换话术风格和内容");
|
|
|
+ else if (noReplyRate > 50) reason.append(",建议调整话术语气和表达方式");
|
|
|
+ else if (avgSentiment < -0.3) reason.append(",建议使用更温暖关怀的话术");
|
|
|
return reason.toString();
|
|
|
}
|
|
|
|
|
|
- /**
|
|
|
- * 构建优化详细说明
|
|
|
- *
|
|
|
- * 包含完整的指标数据,供审核人员深入了解
|
|
|
- */
|
|
|
private String buildOptimizationDetail(Map<String, Object> nodeData) {
|
|
|
Map<String, Object> detail = new LinkedHashMap<>();
|
|
|
detail.put("totalInteractions", nodeData.get("total"));
|
|
|
@@ -517,32 +343,17 @@ public class UserNodeOptimizerImpl implements UserNodeOptimizer {
|
|
|
return JSON.toJSONString(detail);
|
|
|
}
|
|
|
|
|
|
- /**
|
|
|
- * 使用AI生成个性化优化内容
|
|
|
- *
|
|
|
- * AI Prompt设计:
|
|
|
- * - 提供当前话术和用户行为数据
|
|
|
- * - 要求AI生成更适合该用户的话术
|
|
|
- * - 限制AI只返回优化后的话术内容
|
|
|
- */
|
|
|
private String generateOptimizedContent(Long companyId, String externalUserId,
|
|
|
String nodeCode, String currentMessage, String reason) {
|
|
|
try {
|
|
|
if (multiModelRouter == null) return null;
|
|
|
-
|
|
|
String prompt = String.format(
|
|
|
"你是一个专业的客户沟通话术优化专家。请根据以下信息优化话术:\n\n" +
|
|
|
- "当前话术:%s\n\n" +
|
|
|
- "优化原因:%s\n\n" +
|
|
|
- "优化要求:\n" +
|
|
|
- "1. 保持核心信息不变\n" +
|
|
|
- "2. 调整语气和表达方式,使其更亲切自然\n" +
|
|
|
- "3. 避免机器感,增加人情味\n" +
|
|
|
- "4. 针对用户不回复的原因进行优化\n" +
|
|
|
- "5. 只返回优化后的话术内容,不要解释\n\n" +
|
|
|
- "优化后话术:",
|
|
|
+ "当前话术:%s\n\n优化原因:%s\n\n" +
|
|
|
+ "优化要求:\n1. 保持核心信息不变\n2. 调整语气和表达方式,使其更亲切自然\n" +
|
|
|
+ "3. 避免机器感,增加人情味\n4. 针对用户不回复的原因进行优化\n" +
|
|
|
+ "5. 只返回优化后的话术内容,不要解释\n\n优化后话术:",
|
|
|
currentMessage, reason);
|
|
|
-
|
|
|
return multiModelRouter.generateResponse(prompt, null,
|
|
|
"你是话术优化专家,只返回优化后的话术内容,不要返回任何解释。");
|
|
|
} catch (Exception e) {
|
|
|
@@ -551,253 +362,25 @@ public class UserNodeOptimizerImpl implements UserNodeOptimizer {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- /**
|
|
|
- * 计算优化置信度
|
|
|
- *
|
|
|
- * 置信度越高,说明优化越有必要
|
|
|
- * 计算因子:不回复率、负面情感程度、交互次数
|
|
|
- */
|
|
|
private double calculateConfidence(double noReplyRate, double avgSentiment, Map<String, Object> nodeData) {
|
|
|
double confidence = 0.0;
|
|
|
-
|
|
|
- /* 不回复率贡献(0-0.4) */
|
|
|
if (noReplyRate > 80) confidence += 0.4;
|
|
|
else if (noReplyRate > 60) confidence += 0.3;
|
|
|
else if (noReplyRate > 40) confidence += 0.2;
|
|
|
-
|
|
|
- /* 负面情感贡献(0-0.3) */
|
|
|
if (avgSentiment < -0.5) confidence += 0.3;
|
|
|
else if (avgSentiment < -0.3) confidence += 0.2;
|
|
|
else if (avgSentiment < -0.1) confidence += 0.1;
|
|
|
-
|
|
|
- /* 交互次数贡献(0-0.3),数据越多越可信 */
|
|
|
int total = nodeData.get("total") != null ? ((Number) nodeData.get("total")).intValue() : 0;
|
|
|
if (total >= 10) confidence += 0.3;
|
|
|
else if (total >= 5) confidence += 0.2;
|
|
|
else if (total >= 2) confidence += 0.1;
|
|
|
-
|
|
|
return Math.min(confidence, 1.0);
|
|
|
}
|
|
|
|
|
|
- /**
|
|
|
- * 判断优化类型
|
|
|
- */
|
|
|
private String determineOptimizationType(double noReplyRate, double avgSentiment) {
|
|
|
if (noReplyRate > 70) return "content_replace";
|
|
|
if (noReplyRate > 50) return "tone_adjustment";
|
|
|
if (avgSentiment < -0.3) return "style_change";
|
|
|
return "minor_adjustment";
|
|
|
}
|
|
|
-
|
|
|
- /**
|
|
|
- * 检查节点是否开启了自动优化
|
|
|
- *
|
|
|
- * 配置优先级:
|
|
|
- * 1. 节点级别配置(lobster_node_optimization_config中node_code对应的记录)
|
|
|
- * 2. 工作流级别配置(node_code为'*'的记录)
|
|
|
- * 3. 租户级别配置(workflow_id为0且node_code为'*'的记录)
|
|
|
- * 4. 默认值:开启
|
|
|
- */
|
|
|
- private boolean isOptimizationEnabled(Long companyId, Long workflowId, String nodeCode) {
|
|
|
- try {
|
|
|
- ensureConfigTable();
|
|
|
-
|
|
|
- /* 优先级1:节点级别 */
|
|
|
- try {
|
|
|
- String sql = "SELECT enabled FROM " + CONFIG_TABLE + " " +
|
|
|
- "WHERE company_id = ? AND workflow_id = ? AND node_code = ?";
|
|
|
- Boolean enabled = jdbcTemplate.queryForObject(sql, Boolean.class, companyId, workflowId, nodeCode);
|
|
|
- if (enabled != null) return enabled;
|
|
|
- } catch (Exception ignored) {}
|
|
|
-
|
|
|
- /* 优先级2:工作流级别 */
|
|
|
- try {
|
|
|
- String sql = "SELECT enabled FROM " + CONFIG_TABLE + " " +
|
|
|
- "WHERE company_id = ? AND workflow_id = ? AND node_code = '*'";
|
|
|
- Boolean enabled = jdbcTemplate.queryForObject(sql, Boolean.class, companyId, workflowId);
|
|
|
- if (enabled != null) return enabled;
|
|
|
- } catch (Exception ignored) {}
|
|
|
-
|
|
|
- /* 优先级3:租户级别 */
|
|
|
- try {
|
|
|
- String sql = "SELECT enabled FROM " + CONFIG_TABLE + " " +
|
|
|
- "WHERE company_id = ? AND workflow_id = 0 AND node_code = '*'";
|
|
|
- Boolean enabled = jdbcTemplate.queryForObject(sql, Boolean.class, companyId);
|
|
|
- if (enabled != null) return enabled;
|
|
|
- } catch (Exception ignored) {}
|
|
|
-
|
|
|
- } catch (Exception e) {
|
|
|
- logger.debug("[UserNodeOptimizer] 检查优化开关失败: {}", e.getMessage());
|
|
|
- }
|
|
|
-
|
|
|
- /* 默认:开启 */
|
|
|
- return true;
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * 设置节点优化开关
|
|
|
- *
|
|
|
- * 支持三个级别的配置:
|
|
|
- * - 节点级别:指定workflowId + nodeCode
|
|
|
- * - 工作流级别:指定workflowId + nodeCode="*"
|
|
|
- * - 租户级别:workflowId=0 + nodeCode="*"
|
|
|
- *
|
|
|
- * @param companyId 租户ID
|
|
|
- * @param workflowId 工作流ID(0表示租户级别)
|
|
|
- * @param nodeCode 节点编码("*"表示工作流级别)
|
|
|
- * @param enabled 是否开启
|
|
|
- * @param autoApply 是否自动应用(true=不需要人工确认,false=需要人工确认)
|
|
|
- * @param configBy 配置人
|
|
|
- */
|
|
|
- public void setOptimizationConfig(Long companyId, Long workflowId, String nodeCode,
|
|
|
- boolean enabled, boolean autoApply, String configBy) {
|
|
|
- try {
|
|
|
- ensureConfigTable();
|
|
|
- String sql = "INSERT INTO " + CONFIG_TABLE + " " +
|
|
|
- "(company_id, workflow_id, node_code, enabled, auto_apply, config_by, update_time) " +
|
|
|
- "VALUES (?, ?, ?, ?, ?, ?, NOW()) " +
|
|
|
- "ON DUPLICATE KEY UPDATE enabled = ?, auto_apply = ?, config_by = ?, update_time = NOW()";
|
|
|
- jdbcTemplate.update(sql, companyId, workflowId, nodeCode, enabled, autoApply, configBy,
|
|
|
- enabled, autoApply, configBy);
|
|
|
- logger.info("[UserNodeOptimizer] 更新优化配置: companyId={}, workflowId={}, nodeCode={}, " +
|
|
|
- "enabled={}, autoApply={}", companyId, workflowId, nodeCode, enabled, autoApply);
|
|
|
- } catch (Exception e) {
|
|
|
- logger.error("[UserNodeOptimizer] 更新优化配置失败", e);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * 获取优化配置
|
|
|
- */
|
|
|
- public Map<String, Object> getOptimizationConfig(Long companyId, Long workflowId, String nodeCode) {
|
|
|
- try {
|
|
|
- ensureConfigTable();
|
|
|
- String sql = "SELECT * FROM " + CONFIG_TABLE + " " +
|
|
|
- "WHERE company_id = ? AND workflow_id = ? AND node_code = ?";
|
|
|
- return jdbcTemplate.queryForMap(sql, companyId, workflowId, nodeCode);
|
|
|
- } catch (Exception e) {
|
|
|
- Map<String, Object> defaults = new HashMap<>();
|
|
|
- defaults.put("companyId", companyId);
|
|
|
- defaults.put("workflowId", workflowId);
|
|
|
- defaults.put("nodeCode", nodeCode);
|
|
|
- defaults.put("enabled", true);
|
|
|
- defaults.put("autoApply", false);
|
|
|
- return defaults;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- private void saveOptimization(UserNodeOptimization optimization) {
|
|
|
- try {
|
|
|
- String sql = "INSERT INTO " + OPTIMIZATION_TABLE + " " +
|
|
|
- "(company_id, workflow_id, external_user_id, node_code, " +
|
|
|
- "original_content, optimized_content, optimization_type, confidence, " +
|
|
|
- "optimization_reason, optimization_detail, status, create_time) " +
|
|
|
- "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW())";
|
|
|
- jdbcTemplate.update(sql,
|
|
|
- optimization.getCompanyId(),
|
|
|
- optimization.getWorkflowId(),
|
|
|
- optimization.getExternalUserId(),
|
|
|
- optimization.getNodeCode(),
|
|
|
- optimization.getOriginalContent(),
|
|
|
- optimization.getOptimizedContent(),
|
|
|
- optimization.getOptimizationType(),
|
|
|
- optimization.getConfidence(),
|
|
|
- optimization.getOptimizationReason(),
|
|
|
- optimization.getOptimizationDetail(),
|
|
|
- optimization.getStatus());
|
|
|
- } catch (Exception e) {
|
|
|
- logger.error("[UserNodeOptimizer] 保存优化建议失败", e);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- private UserNodeOptimization mapRowToOptimization(Map<String, Object> row) {
|
|
|
- UserNodeOptimization opt = new UserNodeOptimization();
|
|
|
- opt.setId(((Number) row.get("id")).longValue());
|
|
|
- opt.setCompanyId(((Number) row.get("company_id")).longValue());
|
|
|
- opt.setWorkflowId(((Number) row.get("workflow_id")).longValue());
|
|
|
- opt.setExternalUserId((String) row.get("external_user_id"));
|
|
|
- opt.setNodeCode((String) row.get("node_code"));
|
|
|
- opt.setOriginalContent((String) row.get("original_content"));
|
|
|
- opt.setOptimizedContent((String) row.get("optimized_content"));
|
|
|
- opt.setOptimizationType((String) row.get("optimization_type"));
|
|
|
- opt.setConfidence(row.get("confidence") != null ? ((Number) row.get("confidence")).doubleValue() : 0.0);
|
|
|
- opt.setOptimizationReason((String) row.get("optimization_reason"));
|
|
|
- opt.setOptimizationDetail((String) row.get("optimization_detail"));
|
|
|
- opt.setStatus((String) row.get("status"));
|
|
|
- opt.setCreateTime((Date) row.get("create_time"));
|
|
|
- return opt;
|
|
|
- }
|
|
|
-
|
|
|
- private void ensureInteractionTable() {
|
|
|
- try {
|
|
|
- jdbcTemplate.queryForObject("SELECT 1 FROM " + INTERACTION_TABLE + " LIMIT 1", Integer.class);
|
|
|
- } catch (Exception e) {
|
|
|
- jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS " + INTERACTION_TABLE + " (" +
|
|
|
- "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
|
|
|
- "company_id BIGINT NOT NULL, " +
|
|
|
- "workflow_id BIGINT, " +
|
|
|
- "instance_id BIGINT, " +
|
|
|
- "external_user_id VARCHAR(64) NOT NULL, " +
|
|
|
- "node_code VARCHAR(64) NOT NULL, " +
|
|
|
- "sent_message TEXT, " +
|
|
|
- "customer_reply TEXT, " +
|
|
|
- "reply_delay_ms BIGINT, " +
|
|
|
- "sentiment DOUBLE, " +
|
|
|
- "intent VARCHAR(100), " +
|
|
|
- "outcome VARCHAR(50), " +
|
|
|
- "create_time DATETIME DEFAULT NOW(), " +
|
|
|
- "INDEX idx_company_user (company_id, external_user_id), " +
|
|
|
- "INDEX idx_company_user_node (company_id, external_user_id, node_code)" +
|
|
|
- ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- private void ensureOptimizationTable() {
|
|
|
- try {
|
|
|
- jdbcTemplate.queryForObject("SELECT 1 FROM " + OPTIMIZATION_TABLE + " LIMIT 1", Integer.class);
|
|
|
- } catch (Exception e) {
|
|
|
- jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS " + OPTIMIZATION_TABLE + " (" +
|
|
|
- "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
|
|
|
- "company_id BIGINT NOT NULL, " +
|
|
|
- "workflow_id BIGINT, " +
|
|
|
- "external_user_id VARCHAR(64) NOT NULL, " +
|
|
|
- "node_code VARCHAR(64) NOT NULL, " +
|
|
|
- "original_content TEXT, " +
|
|
|
- "optimized_content TEXT, " +
|
|
|
- "optimization_type VARCHAR(50), " +
|
|
|
- "confidence DOUBLE, " +
|
|
|
- "optimization_reason VARCHAR(500), " +
|
|
|
- "optimization_detail TEXT, " +
|
|
|
- "status VARCHAR(20) DEFAULT 'pending', " +
|
|
|
- "auditor_id VARCHAR(64), " +
|
|
|
- "audit_remark VARCHAR(500), " +
|
|
|
- "audit_time DATETIME, " +
|
|
|
- "apply_time DATETIME, " +
|
|
|
- "create_time DATETIME DEFAULT NOW(), " +
|
|
|
- "INDEX idx_company_user (company_id, external_user_id), " +
|
|
|
- "INDEX idx_company_status (company_id, status), " +
|
|
|
- "INDEX idx_company_user_node (company_id, external_user_id, node_code)" +
|
|
|
- ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- private void ensureConfigTable() {
|
|
|
- try {
|
|
|
- jdbcTemplate.queryForObject("SELECT 1 FROM " + CONFIG_TABLE + " LIMIT 1", Integer.class);
|
|
|
- } catch (Exception e) {
|
|
|
- jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS " + CONFIG_TABLE + " (" +
|
|
|
- "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
|
|
|
- "company_id BIGINT NOT NULL, " +
|
|
|
- "workflow_id BIGINT DEFAULT 0, " +
|
|
|
- "node_code VARCHAR(64) DEFAULT '*', " +
|
|
|
- "enabled TINYINT(1) DEFAULT 1, " +
|
|
|
- "auto_apply TINYINT(1) DEFAULT 0, " +
|
|
|
- "auto_apply_confidence_threshold DOUBLE DEFAULT 0.8, " +
|
|
|
- "config_by VARCHAR(64), " +
|
|
|
- "create_time DATETIME DEFAULT NOW(), " +
|
|
|
- "update_time DATETIME DEFAULT NOW(), " +
|
|
|
- "UNIQUE KEY uk_config (company_id, workflow_id, node_code)" +
|
|
|
- ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
|
|
|
- }
|
|
|
- }
|
|
|
}
|