Selaa lähdekoodia

到家了需求

lmx 3 kuukautta sitten
vanhempi
commit
4702adba62

+ 115 - 0
fs-company/src/main/java/com/fs/company/controller/company/AiOutboundCallDashboardController.java

@@ -0,0 +1,115 @@
+package com.fs.company.controller.company;
+
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.domain.AjaxResult;
+import com.fs.common.utils.ServletUtils;
+import com.fs.company.dto.*;
+import com.fs.company.param.AiOutboundCallDashboardParam;
+import com.fs.company.service.IAiOutboundCallDashboardService;
+import com.fs.framework.security.LoginUser;
+import com.fs.framework.service.TokenService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+
+/**
+ * AI外呼看板Controller
+ */
+@RestController
+@RequestMapping("/company/aiOutboundCallDashboard")
+public class AiOutboundCallDashboardController extends BaseController {
+
+    @Autowired
+    private IAiOutboundCallDashboardService aiOutboundCallDashboardService;
+
+    @Autowired
+    private TokenService tokenService;
+
+    /**
+     * 获取AI外呼统计数据(按任务维度)
+     */
+    @PreAuthorize("@ss.hasPermi('company:aiOutboundCallDashboard:list')")
+    @GetMapping("/statistics")
+    public AjaxResult statistics(AiOutboundCallDashboardParam param) {
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        param.setCompanyId(loginUser.getCompany().getCompanyId());
+        List<AiOutboundCallStatisticsDTO> list = aiOutboundCallDashboardService.getCallStatistics(param);
+        return AjaxResult.success(list);
+    }
+
+    /**
+     * 获取每日外呼趋势数据
+     */
+    @PreAuthorize("@ss.hasPermi('company:aiOutboundCallDashboard:list')")
+    @GetMapping("/dailyTrend")
+    public AjaxResult dailyTrend(AiOutboundCallDashboardParam param) {
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        param.setCompanyId(loginUser.getCompany().getCompanyId());
+        List<AiOutboundCallDailyTrendDTO> list = aiOutboundCallDashboardService.getDailyCallTrend(param);
+        return AjaxResult.success(list);
+    }
+
+    /**
+     * 获取通话状态分布数据
+     */
+    @PreAuthorize("@ss.hasPermi('company:aiOutboundCallDashboard:list')")
+    @GetMapping("/callStatusDistribution")
+    public AjaxResult callStatusDistribution(AiOutboundCallDashboardParam param) {
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        param.setCompanyId(loginUser.getCompany().getCompanyId());
+        List<AiOutboundCallStatusDTO> list = aiOutboundCallDashboardService.getCallStatusDistribution(param);
+        return AjaxResult.success(list);
+    }
+
+    /**
+     * 获取意向分布数据
+     */
+    @PreAuthorize("@ss.hasPermi('company:aiOutboundCallDashboard:list')")
+    @GetMapping("/intentDistribution")
+    public AjaxResult intentDistribution(AiOutboundCallDashboardParam param) {
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        param.setCompanyId(loginUser.getCompany().getCompanyId());
+        List<AiOutboundCallIntentDTO> list = aiOutboundCallDashboardService.getIntentDistribution(param);
+        return AjaxResult.success(list);
+    }
+
+    /**
+     * 获取时段分布数据(按小时)
+     */
+    @PreAuthorize("@ss.hasPermi('company:aiOutboundCallDashboard:list')")
+    @GetMapping("/hourlyDistribution")
+    public AjaxResult hourlyDistribution(AiOutboundCallDashboardParam param) {
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        param.setCompanyId(loginUser.getCompany().getCompanyId());
+        List<AiOutboundCallHourlyDTO> list = aiOutboundCallDashboardService.getHourlyDistribution(param);
+        return AjaxResult.success(list);
+    }
+
+    /**
+     * 获取通话时长分布数据
+     */
+    @PreAuthorize("@ss.hasPermi('company:aiOutboundCallDashboard:list')")
+    @GetMapping("/durationDistribution")
+    public AjaxResult durationDistribution(AiOutboundCallDashboardParam param) {
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        param.setCompanyId(loginUser.getCompany().getCompanyId());
+        List<AiOutboundCallDurationDTO> list = aiOutboundCallDashboardService.getDurationDistribution(param);
+        return AjaxResult.success(list);
+    }
+
+    /**
+     * 获取敏感词触发统计数据
+     */
+    @PreAuthorize("@ss.hasPermi('company:aiOutboundCallDashboard:list')")
+    @GetMapping("/violationStatistics")
+    public AjaxResult violationStatistics(AiOutboundCallDashboardParam param) {
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        param.setCompanyId(loginUser.getCompany().getCompanyId());
+        List<AiOutboundCallViolationDTO> list = aiOutboundCallDashboardService.getViolationStatistics(param);
+        return AjaxResult.success(list);
+    }
+}

+ 1 - 0
fs-service/src/main/java/com/fs/aiSipCall/service/impl/AiSipCallOutboundCdrServiceImpl.java

@@ -531,6 +531,7 @@ public class AiSipCallOutboundCdrServiceImpl extends ServiceImpl<AiSipCallOutbou
         companyVoiceRoboticCallLogCallphone.setIntention(req.getIntent());
         companyVoiceRoboticCallLogCallphone.setCompanyId(req.getCompanyId());
         companyVoiceRoboticCallLogCallphone.setCompanyUserId(req.getCompanyUserId());
+        companyVoiceRoboticCallLogCallphone.setCreateTime(new Date());
         if(null != callPhoneRes.getTimeLenValid() && Integer.valueOf(0).compareTo(callPhoneRes.getTimeLenValid()) < 0){
             BigDecimal divide = new BigDecimal(callPhoneRes.getTimeLenValid()).divide(new BigDecimal(1000), 0, RoundingMode.CEILING);
             companyVoiceRoboticCallLogCallphone.setCallTime(divide.longValue());

+ 18 - 0
fs-service/src/main/java/com/fs/company/dto/AiOutboundCallDailyTrendDTO.java

@@ -0,0 +1,18 @@
+package com.fs.company.dto;
+
+import lombok.Data;
+
+/**
+ * AI外呼看板-每日趋势DTO
+ */
+@Data
+public class AiOutboundCallDailyTrendDTO {
+    /** 日期 yyyy-MM-dd */
+    private String callDate;
+
+    /** 当日总外呼数 */
+    private Integer totalCalls;
+
+    /** 当日接通数 */
+    private Integer connectedCalls;
+}

+ 15 - 0
fs-service/src/main/java/com/fs/company/dto/AiOutboundCallDurationDTO.java

@@ -0,0 +1,15 @@
+package com.fs.company.dto;
+
+import lombok.Data;
+
+/**
+ * AI外呼看板-通话时长分布DTO
+ */
+@Data
+public class AiOutboundCallDurationDTO {
+    /** 时长区间标签 */
+    private String range;
+
+    /** 数量 */
+    private Integer count;
+}

+ 15 - 0
fs-service/src/main/java/com/fs/company/dto/AiOutboundCallHourlyDTO.java

@@ -0,0 +1,15 @@
+package com.fs.company.dto;
+
+import lombok.Data;
+
+/**
+ * AI外呼看板-时段分布DTO
+ */
+@Data
+public class AiOutboundCallHourlyDTO {
+    /** 小时(0-23) */
+    private Integer hour;
+
+    /** 外呼数量 */
+    private Integer count;
+}

+ 15 - 0
fs-service/src/main/java/com/fs/company/dto/AiOutboundCallIntentDTO.java

@@ -0,0 +1,15 @@
+package com.fs.company.dto;
+
+import lombok.Data;
+
+/**
+ * AI外呼看板-意向分布DTO
+ */
+@Data
+public class AiOutboundCallIntentDTO {
+    /** 意向标签名称 */
+    private String name;
+
+    /** 数量 */
+    private Integer value;
+}

+ 48 - 0
fs-service/src/main/java/com/fs/company/dto/AiOutboundCallStatisticsDTO.java

@@ -0,0 +1,48 @@
+package com.fs.company.dto;
+
+import lombok.Data;
+
+/**
+ * AI外呼看板-按任务维度统计DTO
+ */
+@Data
+public class AiOutboundCallStatisticsDTO {
+    /** 任务ID */
+    private Long roboticId;
+
+    /** 任务名称 */
+    private String taskName;
+
+    /** 总外呼数 */
+    private Integer totalCalls;
+
+    /** 接通数 */
+    private Integer connectedCalls;
+
+    /** 未接通数 */
+    private Integer unconnectedCalls;
+
+    /** 接通率(%) */
+    private Double connectRate;
+
+    /** 意向客户数 */
+    private Integer intentCalls;
+
+    /** 意向率(%) */
+    private Double intentRate;
+
+    /** 转人工数 */
+    private Integer transferCalls;
+
+    /** 转人工率(%) */
+    private Double transferRate;
+
+    /** 总通话时长(秒) */
+    private Long totalDuration;
+
+    /** 平均通话时长(秒) */
+    private Long avgDuration;
+
+    /** 呼叫类型: 01呼入 02AI外呼 03人工外呼 */
+    private String callType;
+}

+ 15 - 0
fs-service/src/main/java/com/fs/company/dto/AiOutboundCallStatusDTO.java

@@ -0,0 +1,15 @@
+package com.fs.company.dto;
+
+import lombok.Data;
+
+/**
+ * AI外呼看板-通话状态分布DTO
+ */
+@Data
+public class AiOutboundCallStatusDTO {
+    /** 状态名称 */
+    private String name;
+
+    /** 数量 */
+    private Integer value;
+}

+ 27 - 0
fs-service/src/main/java/com/fs/company/dto/AiOutboundCallViolationDTO.java

@@ -0,0 +1,27 @@
+package com.fs.company.dto;
+
+import lombok.Data;
+
+/**
+ * AI外呼看板-敏感词触发统计DTO
+ */
+@Data
+public class AiOutboundCallViolationDTO {
+    /** 任务ID */
+    private Long roboticId;
+    
+    /** 任务名称 */
+    private String taskName;
+    
+    /** 总通话数 */
+    private Integer totalCalls;
+    
+    /** 触发敏感词次数(is_warning=1的记录数) */
+    private Integer warningCount;
+    
+    /** 违规词总数(violation_num求和) */
+    private Integer violationNum;
+    
+    /** 触发率(%) */
+    private Double warningRate;
+}

+ 50 - 0
fs-service/src/main/java/com/fs/company/mapper/AiOutboundCallDashboardMapper.java

@@ -0,0 +1,50 @@
+package com.fs.company.mapper;
+
+import com.fs.company.dto.*;
+import com.fs.company.param.AiOutboundCallDashboardParam;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+/**
+ * AI外呼看板数据访问层
+ */
+@Mapper
+public interface AiOutboundCallDashboardMapper {
+
+    /**
+     * 按任务维度查询外呼统计
+     */
+    List<AiOutboundCallStatisticsDTO> selectCallStatisticsByTask(AiOutboundCallDashboardParam param);
+
+    /**
+     * 查询每日外呼趋势
+     */
+    List<AiOutboundCallDailyTrendDTO> selectDailyCallTrend(AiOutboundCallDashboardParam param);
+
+    /**
+     * 查询通话状态分布
+     */
+    List<AiOutboundCallStatusDTO> selectCallStatusDistribution(AiOutboundCallDashboardParam param);
+
+    /**
+     * 查询意向分布
+     */
+    List<AiOutboundCallIntentDTO> selectIntentDistribution(AiOutboundCallDashboardParam param);
+
+    /**
+     * 查询时段分布(按小时)
+     */
+    List<AiOutboundCallHourlyDTO> selectHourlyDistribution(AiOutboundCallDashboardParam param);
+
+    /**
+     * 查询通话时长分布
+     */
+    List<AiOutboundCallDurationDTO> selectDurationDistribution(AiOutboundCallDashboardParam param);
+
+    /**
+     * 查询敏感词触发统计(按任务维度)
+     */
+    List<AiOutboundCallViolationDTO> selectViolationStatistics(AiOutboundCallDashboardParam param);
+}

+ 18 - 0
fs-service/src/main/java/com/fs/company/param/AiOutboundCallDashboardParam.java

@@ -0,0 +1,18 @@
+package com.fs.company.param;
+
+import lombok.Data;
+
+/**
+ * AI外呼看板查询参数
+ */
+@Data
+public class AiOutboundCallDashboardParam {
+    /** 开始时间 yyyy-MM-dd */
+    private String startTime;
+
+    /** 结束时间 yyyy-MM-dd */
+    private String endTime;
+
+    /** 公司ID(Controller自动注入) */
+    private Long companyId;
+}

+ 47 - 0
fs-service/src/main/java/com/fs/company/service/IAiOutboundCallDashboardService.java

@@ -0,0 +1,47 @@
+package com.fs.company.service;
+
+import com.fs.company.dto.*;
+import com.fs.company.param.AiOutboundCallDashboardParam;
+
+import java.util.List;
+
+/**
+ * AI外呼看板服务接口
+ */
+public interface IAiOutboundCallDashboardService {
+
+    /**
+     * 获取AI外呼统计数据(按任务维度)
+     */
+    List<AiOutboundCallStatisticsDTO> getCallStatistics(AiOutboundCallDashboardParam param);
+
+    /**
+     * 获取每日外呼趋势数据
+     */
+    List<AiOutboundCallDailyTrendDTO> getDailyCallTrend(AiOutboundCallDashboardParam param);
+
+    /**
+     * 获取通话状态分布数据
+     */
+    List<AiOutboundCallStatusDTO> getCallStatusDistribution(AiOutboundCallDashboardParam param);
+
+    /**
+     * 获取意向分布数据
+     */
+    List<AiOutboundCallIntentDTO> getIntentDistribution(AiOutboundCallDashboardParam param);
+
+    /**
+     * 获取时段分布数据(按小时)
+     */
+    List<AiOutboundCallHourlyDTO> getHourlyDistribution(AiOutboundCallDashboardParam param);
+
+    /**
+     * 获取通话时长分布数据
+     */
+    List<AiOutboundCallDurationDTO> getDurationDistribution(AiOutboundCallDashboardParam param);
+
+    /**
+     * 获取敏感词触发统计数据(按任务维度)
+     */
+    List<AiOutboundCallViolationDTO> getViolationStatistics(AiOutboundCallDashboardParam param);
+}

+ 111 - 0
fs-service/src/main/java/com/fs/company/service/impl/AiOutboundCallDashboardServiceImpl.java

@@ -0,0 +1,111 @@
+package com.fs.company.service.impl;
+
+import com.fs.company.dto.*;
+import com.fs.company.mapper.AiOutboundCallDashboardMapper;
+import com.fs.company.param.AiOutboundCallDashboardParam;
+import com.fs.company.service.IAiOutboundCallDashboardService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+/**
+ * AI外呼看板服务实现类
+ */
+@Service
+public class AiOutboundCallDashboardServiceImpl implements IAiOutboundCallDashboardService {
+
+    @Autowired
+    private AiOutboundCallDashboardMapper aiOutboundCallDashboardMapper;
+
+    @Override
+    public List<AiOutboundCallStatisticsDTO> getCallStatistics(AiOutboundCallDashboardParam param) {
+        List<AiOutboundCallStatisticsDTO> list = aiOutboundCallDashboardMapper.selectCallStatisticsByTask(param);
+
+        // 业务计算逻辑
+        for (AiOutboundCallStatisticsDTO dto : list) {
+            int totalCalls = dto.getTotalCalls() != null ? dto.getTotalCalls() : 0;
+            int connectedCalls = dto.getConnectedCalls() != null ? dto.getConnectedCalls() : 0;
+            int intentCalls = dto.getIntentCalls() != null ? dto.getIntentCalls() : 0;
+            int transferCalls = dto.getTransferCalls() != null ? dto.getTransferCalls() : 0;
+            long totalDuration = dto.getTotalDuration() != null ? dto.getTotalDuration() : 0L;
+
+            // 计算未接通数
+            dto.setUnconnectedCalls(totalCalls - connectedCalls);
+
+            // 计算接通率(%)
+            if (totalCalls > 0) {
+                double rate = (double) connectedCalls / totalCalls * 100;
+                dto.setConnectRate(Math.round(rate * 100.0) / 100.0);
+            } else {
+                dto.setConnectRate(0.0);
+            }
+
+            // 计算意向率(%): 意向客户数/接通数
+            if (connectedCalls > 0) {
+                double intentRate = (double) intentCalls / connectedCalls * 100;
+                dto.setIntentRate(Math.round(intentRate * 100.0) / 100.0);
+            } else {
+                dto.setIntentRate(0.0);
+            }
+
+            // 计算转人工率(%): 转人工数/接通数
+            if (connectedCalls > 0) {
+                double transferRate = (double) transferCalls / connectedCalls * 100;
+                dto.setTransferRate(Math.round(transferRate * 100.0) / 100.0);
+            } else {
+                dto.setTransferRate(0.0);
+            }
+
+            // 平均通话时长(秒)
+            if (connectedCalls > 0) {
+                dto.setAvgDuration((long) Math.ceil((double) totalDuration / connectedCalls));
+            } else {
+                dto.setAvgDuration(0L);
+            }
+        }
+        return list;
+    }
+
+    @Override
+    public List<AiOutboundCallDailyTrendDTO> getDailyCallTrend(AiOutboundCallDashboardParam param) {
+        return aiOutboundCallDashboardMapper.selectDailyCallTrend(param);
+    }
+
+    @Override
+    public List<AiOutboundCallStatusDTO> getCallStatusDistribution(AiOutboundCallDashboardParam param) {
+        return aiOutboundCallDashboardMapper.selectCallStatusDistribution(param);
+    }
+
+    @Override
+    public List<AiOutboundCallIntentDTO> getIntentDistribution(AiOutboundCallDashboardParam param) {
+        return aiOutboundCallDashboardMapper.selectIntentDistribution(param);
+    }
+
+    @Override
+    public List<AiOutboundCallHourlyDTO> getHourlyDistribution(AiOutboundCallDashboardParam param) {
+        return aiOutboundCallDashboardMapper.selectHourlyDistribution(param);
+    }
+
+    @Override
+    public List<AiOutboundCallDurationDTO> getDurationDistribution(AiOutboundCallDashboardParam param) {
+        return aiOutboundCallDashboardMapper.selectDurationDistribution(param);
+    }
+
+    @Override
+    public List<AiOutboundCallViolationDTO> getViolationStatistics(AiOutboundCallDashboardParam param) {
+        List<AiOutboundCallViolationDTO> list = aiOutboundCallDashboardMapper.selectViolationStatistics(param);
+        // 计算触发率
+        for (AiOutboundCallViolationDTO dto : list) {
+            int totalCalls = dto.getTotalCalls() != null ? dto.getTotalCalls() : 0;
+            int warningCount = dto.getWarningCount() != null ? dto.getWarningCount() : 0;
+            if (totalCalls > 0) {
+                double rate = (double) warningCount / totalCalls * 100;
+                dto.setWarningRate(Math.round(rate * 100.0) / 100.0);
+            } else {
+                dto.setWarningRate(0.0);
+            }
+        }
+        return list;
+    }
+}

+ 1 - 0
fs-service/src/main/java/com/fs/company/service/impl/CompanyVoiceRoboticCallLogCallphoneServiceImpl.java

@@ -348,6 +348,7 @@ public class CompanyVoiceRoboticCallLogCallphoneServiceImpl extends ServiceImpl<
                     // 写入其他记录
 //                JSONObject telData = dialogMap.getTelData();
                     companyVoiceRoboticCallLog.setRecordPath(result.getWavfile());
+                    companyVoiceRoboticCallLog.setCallType(2);
                     companyVoiceRoboticCallLog.setContentList(result.getDialogue());
                     companyVoiceRoboticCallLog.setCallerNum(result.getTelephone());
                     companyVoiceRoboticCallLog.setCalleeNum(result.getCallerNumber());

+ 158 - 0
fs-service/src/main/resources/mapper/company/AiOutboundCallDashboardMapper.xml

@@ -0,0 +1,158 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.fs.company.mapper.AiOutboundCallDashboardMapper">
+
+    <!-- 按任务维度查询外呼统计 -->
+    <select id="selectCallStatisticsByTask" parameterType="com.fs.company.param.AiOutboundCallDashboardParam"
+            resultType="com.fs.company.dto.AiOutboundCallStatisticsDTO">
+        SELECT
+            ccl.robotic_id AS roboticId,
+            cvr.name AS taskName,
+            COUNT(*) AS totalCalls,
+            SUM(CASE WHEN ccl.status = 2 AND ccl.call_time > 0 THEN 1 ELSE 0 END) AS connectedCalls,
+            SUM(CASE WHEN ccl.intention IS NOT NULL AND ccl.intention != '' AND ccl.intention IN ('5', '4') THEN 1 ELSE 0 END) AS intentCalls,
+            SUM(CASE WHEN ccl.intention = '1' THEN 1 ELSE 0 END) AS transferCalls,
+            SUM(CASE WHEN ccl.status = 2 AND ccl.call_time > 0 THEN ccl.call_time ELSE 0 END) AS totalDuration,
+            MAX(ccl.call_type) AS callType
+        FROM company_voice_robotic_call_log_callphone ccl
+        INNER JOIN company_voice_robotic cvr ON cvr.id = ccl.robotic_id
+        WHERE ccl.company_id = #{companyId}
+            AND cvr.del_flag = 0
+            AND ccl.run_time >= CONCAT(#{startTime}, ' 00:00:00')
+            AND ccl.run_time &lt;= CONCAT(#{endTime}, ' 23:59:59')
+        GROUP BY ccl.robotic_id, cvr.name
+        ORDER BY totalCalls DESC
+    </select>
+
+    <!-- 查询每日外呼趋势 -->
+    <select id="selectDailyCallTrend" parameterType="com.fs.company.param.AiOutboundCallDashboardParam"
+            resultType="com.fs.company.dto.AiOutboundCallDailyTrendDTO">
+        SELECT
+            DATE_FORMAT(ccl.run_time, '%Y-%m-%d') AS callDate,
+            COUNT(*) AS totalCalls,
+            SUM(CASE WHEN ccl.status = 2 AND ccl.call_time > 0 THEN 1 ELSE 0 END) AS connectedCalls
+        FROM company_voice_robotic_call_log_callphone ccl
+        WHERE ccl.company_id = #{companyId}
+            AND ccl.run_time >= CONCAT(#{startTime}, ' 00:00:00')
+            AND ccl.run_time &lt;= CONCAT(#{endTime}, ' 23:59:59')
+        GROUP BY DATE_FORMAT(ccl.run_time, '%Y-%m-%d')
+        ORDER BY callDate ASC
+    </select>
+
+    <!-- 查询通话状态分布 -->
+    <select id="selectCallStatusDistribution" parameterType="com.fs.company.param.AiOutboundCallDashboardParam"
+            resultType="com.fs.company.dto.AiOutboundCallStatusDTO">
+        SELECT
+            CASE
+                WHEN ccl.status = 2 AND ccl.call_time > 0 THEN '接通'
+                WHEN ccl.status = 3 THEN '未接通'
+                WHEN ccl.status = 1 THEN '呼叫中'
+                ELSE '其他'
+            END AS name,
+            COUNT(*) AS value
+        FROM company_voice_robotic_call_log_callphone ccl
+        WHERE ccl.company_id = #{companyId}
+            AND ccl.run_time >= CONCAT(#{startTime}, ' 00:00:00')
+            AND ccl.run_time &lt;= CONCAT(#{endTime}, ' 23:59:59')
+        GROUP BY
+            CASE
+                WHEN ccl.status = 2 AND ccl.call_time > 0 THEN '接通'
+                WHEN ccl.status = 3 THEN '未接通'
+                WHEN ccl.status = 1 THEN '呼叫中'
+                ELSE '其他'
+            END
+        ORDER BY value DESC
+    </select>
+
+    <!-- 查询意向分布 -->
+    <select id="selectIntentDistribution" parameterType="com.fs.company.param.AiOutboundCallDashboardParam"
+            resultType="com.fs.company.dto.AiOutboundCallIntentDTO">
+        SELECT
+            CASE
+                WHEN ccl.intention = '5' THEN 'A级(高意向)'
+                WHEN ccl.intention = '4' THEN 'B级(较高意向)'
+                WHEN ccl.intention = '3' THEN 'C级(一般意向)'
+                WHEN ccl.intention = '2' THEN 'D级(较低意向)'
+                WHEN ccl.intention = '1' THEN 'E级(需转人工)'
+                WHEN ccl.intention = '10' THEN 'F级(无意向)'
+                WHEN ccl.intention = '11' THEN 'G级'
+                WHEN ccl.intention = '12' THEN 'H级'
+                WHEN ccl.intention = '13' THEN 'I级'
+                WHEN ccl.intention = '14' THEN 'J级'
+                WHEN ccl.intention = '0' OR ccl.intention IS NULL OR ccl.intention = '' THEN '未标记'
+                ELSE CONCAT(ccl.intention, '级')
+            END AS name,
+            COUNT(*) AS value
+        FROM company_voice_robotic_call_log_callphone ccl
+        WHERE ccl.company_id = #{companyId}
+            AND ccl.run_time >= CONCAT(#{startTime}, ' 00:00:00')
+            AND ccl.run_time &lt;= CONCAT(#{endTime}, ' 23:59:59')
+            AND ccl.status = 2 AND ccl.call_time > 0
+        GROUP BY
+            CASE
+                WHEN ccl.intention = '5' THEN 'A级(高意向)'
+                WHEN ccl.intention = '4' THEN 'B级(较高意向)'
+                WHEN ccl.intention = '3' THEN 'C级(一般意向)'
+                WHEN ccl.intention = '2' THEN 'D级(较低意向)'
+                WHEN ccl.intention = '1' THEN 'E级(需转人工)'
+                WHEN ccl.intention = '10' THEN 'F级(无意向)'
+                WHEN ccl.intention = '11' THEN 'G级'
+                WHEN ccl.intention = '12' THEN 'H级'
+                WHEN ccl.intention = '13' THEN 'I级'
+                WHEN ccl.intention = '14' THEN 'J级'
+                WHEN ccl.intention = '0' OR ccl.intention IS NULL OR ccl.intention = '' THEN '未标记'
+                ELSE CONCAT(ccl.intention, '级')
+            END
+        ORDER BY value DESC
+    </select>
+
+    <!-- 查询敏感词触发统计(按任务维度) -->
+    <select id="selectViolationStatistics" parameterType="com.fs.company.param.AiOutboundCallDashboardParam"
+            resultType="com.fs.company.dto.AiOutboundCallViolationDTO">
+        SELECT
+            ccl.robotic_id AS roboticId,
+            cvr.name AS taskName,
+            COUNT(*) AS totalCalls,
+            SUM(CASE WHEN ccl.is_warning = 1 THEN 1 ELSE 0 END) AS warningCount,
+            IFNULL(SUM(CASE WHEN ccl.is_warning = 1 THEN ccl.violation_num ELSE 0 END), 0) AS violationNum
+        FROM company_voice_robotic_call_log_callphone ccl
+        INNER JOIN company_voice_robotic cvr ON cvr.id = ccl.robotic_id
+        WHERE ccl.company_id = #{companyId}
+            AND cvr.del_flag = 0
+            AND ccl.run_time >= CONCAT(#{startTime}, ' 00:00:00')
+            AND ccl.run_time &lt;= CONCAT(#{endTime}, ' 23:59:59')
+        GROUP BY ccl.robotic_id, cvr.name
+        ORDER BY warningCount DESC
+    </select>
+
+    <!-- 查询通话时长分布 -->
+    <select id="selectDurationDistribution" parameterType="com.fs.company.param.AiOutboundCallDashboardParam"
+            resultType="com.fs.company.dto.AiOutboundCallDurationDTO">
+        SELECT
+            CASE
+                WHEN ccl.call_time > 0 AND ccl.call_time &lt;= 30 THEN '0-30秒'
+                WHEN ccl.call_time > 30 AND ccl.call_time &lt;= 60 THEN '30-60秒'
+                WHEN ccl.call_time > 60 AND ccl.call_time &lt;= 120 THEN '1-2分钟'
+                WHEN ccl.call_time > 120 AND ccl.call_time &lt;= 300 THEN '2-5分钟'
+                WHEN ccl.call_time > 300 THEN '5分钟以上'
+                ELSE '未接通'
+            END AS 
ange,
+            COUNT(*) AS count
+        FROM company_voice_robotic_call_log_callphone ccl
+        WHERE ccl.company_id = #{companyId}
+            AND ccl.run_time >= CONCAT(#{startTime}, ' 00:00:00')
+            AND ccl.run_time &lt;= CONCAT(#{endTime}, ' 23:59:59')
+        GROUP BY
+            CASE
+                WHEN ccl.call_time > 0 AND ccl.call_time &lt;= 30 THEN '0-30秒'
+                WHEN ccl.call_time > 30 AND ccl.call_time &lt;= 60 THEN '30-60秒'
+                WHEN ccl.call_time > 60 AND ccl.call_time &lt;= 120 THEN '1-2分钟'
+                WHEN ccl.call_time > 120 AND ccl.call_time &lt;= 300 THEN '2-5分钟'
+                WHEN ccl.call_time > 300 THEN '5分钟以上'
+                ELSE '未接通'
+            END
+        ORDER BY
+            FIELD(
ange, '0-30秒', '30-60秒', '1-2分钟', '2-5分钟', '5分钟以上', '未接通')
+    </select>
+
+</mapper>