From 60ade5154522a042c816abaa1840751011ae0371 Mon Sep 17 00:00:00 2001
From: liusheng <337615773@qq.com>
Date: 星期二, 29 九月 2026 19:15:03 +0800
Subject: [PATCH] 1.省立同德满度问题处理 2.门急诊数据采集优化

---
 smartor/src/main/java/com/smartor/service/impl/PatSatisfactionServiceImpl.java | 1072 +++++++++++++++++++++++++++++++++++++----------------------
 1 files changed, 675 insertions(+), 397 deletions(-)

diff --git a/smartor/src/main/java/com/smartor/service/impl/PatSatisfactionServiceImpl.java b/smartor/src/main/java/com/smartor/service/impl/PatSatisfactionServiceImpl.java
index 8fd54d1..9e07d77 100644
--- a/smartor/src/main/java/com/smartor/service/impl/PatSatisfactionServiceImpl.java
+++ b/smartor/src/main/java/com/smartor/service/impl/PatSatisfactionServiceImpl.java
@@ -5,7 +5,11 @@
 import java.util.*;
 import java.util.stream.Collectors;
 
+import com.ruoyi.common.core.domain.entity.SysUser;
+import com.ruoyi.common.core.domain.entity.SysUserDept;
+import com.ruoyi.common.core.domain.model.LoginUser;
 import com.ruoyi.common.utils.Arith;
+import com.ruoyi.common.utils.ArrayListUtils;
 import com.ruoyi.common.utils.DateUtils;
 import com.ruoyi.common.utils.DtoConversionUtils;
 import com.smartor.domain.*;
@@ -13,12 +17,15 @@
 import com.smartor.domain.VO.PatSatisfactionReqVO;
 import com.smartor.domain.VO.PatSatisfactionResVO;
 import com.smartor.domain.entity.PatSatisfactionDetailEntity;
+import com.smartor.domain.entity.SatisfactionStatQuery;
 import com.smartor.domain.entity.ServiceSubtaskSatisfactionEntity;
 import com.smartor.mapper.*;
 import org.apache.commons.collections4.CollectionUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import com.smartor.service.IPatSatisfactionService;
+
+import static com.ruoyi.common.utils.SecurityUtils.getLoginUser;
 
 /**
  * 鎮h�呮弧鎰忓害Service涓氬姟灞傚鐞�
@@ -101,6 +108,50 @@
     }
 
     /**
+     * 鏂板鎮h�呮弧鎰忓害
+     *
+     * @param patSatisfactions 鎮h�呮弧鎰忓害
+     * @return 缁撴灉
+     */
+    @Override
+    public int batchInsert(List<PatSatisfaction> patSatisfactions) {
+        if(CollectionUtils.isEmpty(patSatisfactions)){
+            return 0;
+        }
+        List<Long> ids = patSatisfactions.stream().map(PatSatisfaction::getId).
+                distinct().collect(Collectors.toList());
+        List<PatSatisfaction> patSatisfactionsOld = new ArrayList<>();
+        PatSatisfaction patSatisfactionVo = new PatSatisfaction();
+        patSatisfactionVo.setIds(ids);
+        patSatisfactionsOld = patSatisfactionMapper.selectPatSatisfactionList(patSatisfactionVo);
+        Map<String, List<PatSatisfaction>> maps = ArrayListUtils.splitListsById(patSatisfactions,
+                patSatisfactionsOld, PatSatisfaction::getId);
+        //鍙湪patSatisfactions鐨勬暟鎹紝鎵ц鏂板鎿嶄綔
+        List<PatSatisfaction> addItems = maps.get("onlyIn1");
+        //鍙湪patSatisfactionsOld鐨勬暟鎹紝鎵ц鍒犻櫎鎿嶄綔
+//        List<PatSatisfaction> delItems = maps.get("onlyIn2");
+        //閲嶅彔鐨勬暟鎹紝鎵ц鏇存柊鎿嶄綔
+        List<PatSatisfaction> editItems = maps.get("common");
+        Integer count = 0;
+        LoginUser loginUser = getLoginUser();
+        SysUser user = loginUser.getUser();
+        for (PatSatisfaction patSatisfaction0 : editItems) {
+            patSatisfaction0.setUpdateBy(user.getNickName());
+            patSatisfaction0.setUpdateTime(DateUtils.getNowDate());
+            count = count + patSatisfactionMapper.updatePatSatisfaction(patSatisfaction0);
+        }
+
+        for (PatSatisfaction patSatisfaction1 : addItems) {
+            patSatisfaction1.setCreateBy(user.getNickName());
+            patSatisfaction1.setOrgid(user.getOrgid());
+            patSatisfaction1.setCreateTime(DateUtils.getNowDate());
+            patSatisfaction1.setUpdateTime(DateUtils.getNowDate());
+            count = count + patSatisfactionMapper.insertPatSatisfaction(patSatisfaction1);
+        }
+        return count;
+    }
+
+    /**
      * 淇敼鎮h�呮弧鎰忓害
      *
      * @param patSatisfaction 鎮h�呮弧鎰忓害
@@ -144,95 +195,35 @@
     public PatSatisfactionResVO statistics(PatSatisfactionReqVO patSatisfactionReqVO) {
         //瀹氫箟杩斿弬
         PatSatisfactionResVO result = new PatSatisfactionResVO();
-        //绛旈鎬讳汉鏁�(宸茬粡鍥炵瓟闂鐨�)
-        int totalPerson = 0;
-        // 鎬讳汉鏁帮紙鍖呭惈鏈洖绛旂殑锛�
-        int allPerson = 0;
         //鎮h�呮弧鎰忓害缁熻鎯�
         List<PatSatisfactionDetailEntity> patSatisfactionDetailEntities = new ArrayList<>();
+        // [0]=绛旈鎬讳汉鏁�(宸茬粡鍥炵瓟闂鐨�) [1]=鎬讳汉鏁帮紙鍖呭惈鏈洖绛旂殑锛�
+        int[] personTotals = new int[2];
 
-        List<ServiceSubtask> serviceSubtasks = serviceSubtaskMapper.selectSatisfactionSubtaskList(patSatisfactionReqVO);
-        // 鎸塼ype鍒嗙粍锛�1-璇煶锛�2-闂嵎
-        Map<String, List<ServiceSubtask>> groupByType = serviceSubtasks.stream().filter(s -> s.getType() != null).collect(Collectors.groupingBy(ServiceSubtask::getType));
-        List<ServiceSubtask> voiceList = groupByType.getOrDefault("1", Collections.emptyList());
-        List<ServiceSubtask> questionnaireList = groupByType.getOrDefault("2", Collections.emptyList());
+        // 鍙煡鏈鏉′欢涓嬪疄闄呭瓨鍦ㄥ摢浜� type锛堝師鏉ユ槸鎶婃帴杩戝叏琛ㄧ殑涓婄櫨涓瓧娈垫煡鍥炴潵锛屼粎鐢ㄤ簬鍒ゆ柇涓嬮潰涓や釜鍒嗘敮鏄惁涓虹┖锛�
+        List<String> types = serviceSubtaskMapper.selectSatisfactionSubtaskTypes(patSatisfactionReqVO);
+        boolean hasVoice = types != null && types.contains("1");
+        boolean hasQuestionnaire = types != null && types.contains("2");
 
-        if (CollectionUtils.isNotEmpty(voiceList)) {
+        if (hasVoice) {
             //1. 鍏堣幏鍙杋vr_liba_script_assort鐨勬弧鎰忓害鍒嗙被ID
             IvrLibaScriptAssort ivrLibaScriptAssort = new IvrLibaScriptAssort();
             ivrLibaScriptAssort.setOrgid(patSatisfactionReqVO.getOrgid());
             List<IvrLibaScriptAssort> ivrLibaScriptAssorts = ivrLibaScriptAssortMapper.selectIvrLibaScriptAssortList(ivrLibaScriptAssort);
-
             List<Long> mydIdList = ivrLibaScriptAssorts.stream().filter(item -> item.getType() != null && item.getType().contains("myd")).map(IvrLibaScriptAssort::getId).collect(Collectors.toList());
 
             //2 鍐嶈幏鍙栬闂婊℃剰搴﹀垎绫讳笅鐨処vrLibaTemplateScript鐨勯泦鍚�
             List<IvrLibaTemplateScript> ivrLibaTemplateScripts = ivrLibaTemplateScriptMapper.queryLibTemplateIds(mydIdList);
 
-            //3 鍐嶉�氳繃鍗曚釜ivr_liba_template鑾峰彇鍒版墍鏈夌殑婊℃剰搴﹂棶棰� 鍜� 浣跨敤璇ユā鏉跨殑subTask浜烘暟
+            //3 涓�娆$粺璁″叏閮ㄩ棶棰樼殑鍙戦�侀噺/濉姤閲�/閫夐」鍒嗗�硷紙鍘熸潵鏄瘡涓棶棰樺悇鏌� 3 娆★級
+            List<SatScriptRef> scriptRefs = new ArrayList<>();
             for (IvrLibaTemplateScript ivrLibaTemplateScript : ivrLibaTemplateScripts) {
-                //璁板綍鍗曚釜鏄庣粏淇℃伅
-                PatSatisfactionDetailEntity patSatisfactionDetailEntity = new PatSatisfactionDetailEntity();
-
-                //閫氳繃闂ID锛岃幏鍙栭棶棰橀�夐」(涓轰簡鑾峰彇閫夐」涓婄殑鏈�楂樺垎鏈�浣庡垎)
-                IvrLibaTemplateTargetoption ivrLibaTemplateTargetoption = new IvrLibaTemplateTargetoption();
-                ivrLibaTemplateTargetoption.setScriptid(ivrLibaTemplateScript.getScriptid());
-                List<IvrLibaTemplateTargetoption> ivrLibaTemplateTargetoptions = ivrLibaTemplateTargetoptionMapper.selectIvrLibaTemplateTargetoptionList(ivrLibaTemplateTargetoption);
-
-                //鑾峰彇libTemplateId
-                List<Long> libTemplateIds = new ArrayList<>();
-                libTemplateIds.add(ivrLibaTemplateScript.getTemplateid());
-
-                // 鍐嶉�氳繃妯℃澘id鑾峰彇闂鍙戦�佹�婚噺
-                ServiceSubtaskSatisfactionEntity serviceSubtaskSatisfactionEntity = DtoConversionUtils.sourceToTarget(patSatisfactionReqVO, ServiceSubtaskSatisfactionEntity.class);
-                serviceSubtaskSatisfactionEntity.setLibTemplateIds(libTemplateIds);
-                serviceSubtaskSatisfactionEntity.setType(1);
-                List<Long> subids = serviceSubtaskMapper.querySendCount(serviceSubtaskSatisfactionEntity);
-
-                //鑾峰彇濉姤鏁伴噺
-                List<ServiceSubtaskDetailDTO> fillCount = null;
-                if (CollectionUtils.isNotEmpty(subids))
-                    fillCount = serviceSubtaskDetailMapper.queryFillCount(subids, ivrLibaTemplateScript.getScriptContent());
-
-                // 鏍规嵁matchedtext鍒嗙粍锛岀粺璁″悇閫夐」鏁伴噺鍙婂崰姣�
-                Map<String, Map<String, Object>> matchedtextStats = new HashMap<>();
-                if (CollectionUtils.isNotEmpty(fillCount)) {
-                    int totalFill = fillCount.size();
-                    fillCount.stream().filter(dto -> dto.getMatchedtext() != null).collect(Collectors.groupingBy(ServiceSubtaskDetailDTO::getMatchedtext, Collectors.counting())).forEach((matchedtext, count) -> {
-                        Map<String, Object> stat = new HashMap<>();
-                        stat.put("count", count);
-                        stat.put("ratio", new BigDecimal((double) count / totalFill * 100).setScale(1, RoundingMode.HALF_UP).doubleValue());
-                        matchedtextStats.put(matchedtext, stat);
-                    });
-                    // 浠巉illCount涓彇targetvalue锛�&鍒嗛殧鐨勫綋鍓嶉棶棰樼殑鎵�鏈夐�夐」锛夛紝琛ュ叏鏈嚭鐜扮殑閫夐」锛宑ount鍜宺atio鍧囦负0
-                    fillCount.stream().filter(dto -> dto.getTargetvalue() != null).findFirst().ifPresent(dto -> {
-                        for (String option : dto.getTargetvalue().split("&")) {
-                            String optionTrim = option.trim();
-                            if (!matchedtextStats.containsKey(optionTrim)) {
-                                Map<String, Object> emptyStat = new HashMap<>();
-                                emptyStat.put("count", 0L);
-                                emptyStat.put("ratio", 0.0);
-                                matchedtextStats.put(optionTrim, emptyStat);
-                            }
-                        }
-                    });
-                }
-
-                patSatisfactionDetailEntity.setScriptContent(ivrLibaTemplateScript.getScriptContent());
-                patSatisfactionDetailEntity.setAnswerPerson(CollectionUtils.isEmpty(fillCount) ? 0 : fillCount.size());
-                patSatisfactionDetailEntity.setNoAnswerPerson(CollectionUtils.isNotEmpty(fillCount) && CollectionUtils.isNotEmpty(subids) ? (subids.size() - fillCount.size()) : CollectionUtils.isNotEmpty(subids) ? subids.size() : 0);
-                patSatisfactionDetailEntity.setAverageScore(String.valueOf(averageScore(fillCount)));
-                patSatisfactionDetailEntity.setMaxScore(String.valueOf(maxScore(ivrLibaTemplateTargetoptions)));
-                patSatisfactionDetailEntity.setMinScore(String.valueOf(minScore(ivrLibaTemplateTargetoptions)));
-                patSatisfactionDetailEntity.setAnswerRate(CollectionUtils.isEmpty(subids) ? 0 : CollectionUtils.isEmpty(fillCount) ? 0 : fillCount.size() * 1.0 / subids.size());
-                patSatisfactionDetailEntity.setMatchedtextStats(matchedtextStats);
-                patSatisfactionDetailEntities.add(patSatisfactionDetailEntity);
-
-                totalPerson += CollectionUtils.isEmpty(fillCount) ? 0 : fillCount.size();
-                allPerson += subids.size();
+                scriptRefs.add(new SatScriptRef(ivrLibaTemplateScript.getTemplateid(), ivrLibaTemplateScript.getScriptid(), ivrLibaTemplateScript.getScriptContent()));
             }
+            buildSatisfactionDetailEntities(patSatisfactionReqVO, "1", true, scriptRefs, patSatisfactionDetailEntities, personTotals);
         }
 
-        if (CollectionUtils.isNotEmpty(questionnaireList)) {
+        if (hasQuestionnaire) {
             //1. 鍏堣幏鍙杝vy_lib_script_category鐨勬弧鎰忓害鍒嗙被ID
             SvyLibScriptCategory svyLibScriptCategory = new SvyLibScriptCategory();
             svyLibScriptCategory.setOrgid(patSatisfactionReqVO.getOrgid());
@@ -242,85 +233,17 @@
             //2 鍐嶈幏鍙栬闂婊℃剰搴﹀垎绫讳笅鐨処vrLibaTemplateScript鐨勯泦鍚�
             List<SvyLibTemplateScript> svyLibTemplateScripts = svyLibTemplateScriptMapper.querySvyLibTemplateIds(mydIdList);
 
+            //3 涓�娆$粺璁″叏閮ㄩ棶棰樼殑鍙戦�侀噺/濉姤閲�/閫夐」鍒嗗�硷紙鍘熸潵鏄瘡涓棶棰樺悇鏌� 3 娆★級
+            List<SatScriptRef> scriptRefs = new ArrayList<>();
             for (SvyLibTemplateScript svyLibTemplateScript : svyLibTemplateScripts) {
-                //璁板綍鍗曚釜鏄庣粏淇℃伅
-                PatSatisfactionDetailEntity patSatisfactionDetailEntity = new PatSatisfactionDetailEntity();
-
-                //閫氳繃闂ID锛岃幏鍙栭棶棰橀�夐」(涓轰簡鑾峰彇閫夐」涓婄殑鏈�楂樺垎鏈�浣庡垎)
-                SvyLibTemplateTargetoption svyLibTemplateTargetoption = new SvyLibTemplateTargetoption();
-                svyLibTemplateTargetoption.setScriptid(svyLibTemplateScript.getId());
-                List<SvyLibTemplateTargetoption> svyLibTemplateTargetoptions = svyLibTemplateTargetoptionMapper.selectSvyLibTemplateTargetoptionList(svyLibTemplateTargetoption);
-
-                //鑾峰彇libTemplateId
-                List<Long> libTemplateIds = new ArrayList<>();
-                libTemplateIds.add(svyLibTemplateScript.getSvyid());
-
-                // 鍐嶉�氳繃妯℃澘id鑾峰彇闂鍙戦�佹�婚噺
-                ServiceSubtaskSatisfactionEntity serviceSubtaskSatisfactionEntity = DtoConversionUtils.sourceToTarget(patSatisfactionReqVO, ServiceSubtaskSatisfactionEntity.class);
-                serviceSubtaskSatisfactionEntity.setLibTemplateIds(libTemplateIds);
-                serviceSubtaskSatisfactionEntity.setType(2);
-                List<Long> subids = serviceSubtaskMapper.querySendCount(serviceSubtaskSatisfactionEntity);
-
-                //鑾峰彇濉姤鏁伴噺
-                List<ServiceSubtaskDetailDTO> fillCount = null;
-                if (CollectionUtils.isNotEmpty(subids))
-                    fillCount = serviceSubtaskDetailMapper.queryFillCount(subids, svyLibTemplateScript.getScriptContent());
-
-                // 鏍规嵁matchedtext鍒嗙粍锛岀粺璁″悇閫夐」鏁伴噺鍙婂崰姣�
-                Map<String, Map<String, Object>> matchedtextStats = new HashMap<>();
-                if (CollectionUtils.isNotEmpty(fillCount)) {
-                    int totalFill = fillCount.size();
-                    fillCount.stream().filter(dto -> dto.getMatchedtext() != null).collect(Collectors.groupingBy(ServiceSubtaskDetailDTO::getMatchedtext, Collectors.counting())).forEach((matchedtext, count) -> {
-                        Map<String, Object> stat = new HashMap<>();
-                        stat.put("count", count);
-                        stat.put("ratio", new BigDecimal((double) count / totalFill * 100).setScale(1, RoundingMode.HALF_UP).doubleValue());
-                        matchedtextStats.put(matchedtext, stat);
-                    });
-                    // 浠巉illCount涓彇targetvalue锛�&鍒嗛殧鐨勫綋鍓嶉棶棰樼殑鎵�鏈夐�夐」锛夛紝琛ュ叏鏈嚭鐜扮殑閫夐」锛宑ount鍜宺atio鍧囦负0
-                    fillCount.stream().filter(dto -> dto.getTargetvalue() != null).findFirst().ifPresent(dto -> {
-                        for (String option : dto.getTargetvalue().split("&")) {
-                            String optionTrim = option.trim();
-                            if (!matchedtextStats.containsKey(optionTrim)) {
-                                Map<String, Object> emptyStat = new HashMap<>();
-                                emptyStat.put("count", 0L);
-                                emptyStat.put("ratio", 0.0);
-                                matchedtextStats.put(optionTrim, emptyStat);
-                            }
-                        }
-                    });
-                }
-                patSatisfactionDetailEntity.setScriptContent(svyLibTemplateScript.getScriptContent());
-                patSatisfactionDetailEntity.setAnswerPerson(CollectionUtils.isEmpty(fillCount) ? 0 : fillCount.size());
-                patSatisfactionDetailEntity.setNoAnswerPerson(CollectionUtils.isNotEmpty(fillCount) && CollectionUtils.isNotEmpty(subids) ? (subids.size() - fillCount.size()) : CollectionUtils.isNotEmpty(subids) ? subids.size() : 0);
-                patSatisfactionDetailEntity.setAverageScore(String.valueOf(averageScore(fillCount)));
-
-                Double maxScore = svyLibTemplateTargetoptions.stream()
-                        .map(dto -> dto.getScore())
-                        .filter(scoreStr -> scoreStr != null && !scoreStr.isEmpty())
-                        .mapToDouble(Double::parseDouble)
-                        .max()
-                        .orElse(0.0);
-                patSatisfactionDetailEntity.setMaxScore(String.valueOf(maxScore));
-
-                Double minScore = svyLibTemplateTargetoptions.stream()
-                        .map(dto -> dto.getScore())
-                        .filter(scoreStr -> scoreStr != null && !scoreStr.isEmpty())
-                        .mapToDouble(Double::parseDouble)
-                        .min()
-                        .orElse(0.0);
-                patSatisfactionDetailEntity.setMinScore(String.valueOf(minScore));
-
-                patSatisfactionDetailEntity.setAnswerRate(CollectionUtils.isEmpty(subids) ? 0 : CollectionUtils.isEmpty(fillCount) ? 0 : fillCount.size() * 1.0 / subids.size());
-                patSatisfactionDetailEntity.setMatchedtextStats(matchedtextStats);
-                patSatisfactionDetailEntities.add(patSatisfactionDetailEntity);
-
-                totalPerson += CollectionUtils.isEmpty(fillCount) ? 0 : fillCount.size();
-                allPerson += subids.size();
+                // 闂嵎鐨勯�夐」琛ㄦ寜 svy_lib_template_script 鐨勪富閿叧鑱�
+                scriptRefs.add(new SatScriptRef(svyLibTemplateScript.getSvyid(), svyLibTemplateScript.getId(), svyLibTemplateScript.getScriptContent()));
             }
+            buildSatisfactionDetailEntities(patSatisfactionReqVO, "2", false, scriptRefs, patSatisfactionDetailEntities, personTotals);
         }
-        result.setTotalAnswerRate(Arith.div(totalPerson, allPerson));
+        result.setTotalAnswerRate(Arith.div(personTotals[0], personTotals[1]));
         result.setPatSatisfactionDetailEntities(patSatisfactionDetailEntities);
-        result.setTotalPerson(totalPerson);
+        result.setTotalPerson(personTotals[0]);
 
         return result;
     }
@@ -335,19 +258,17 @@
     public PatSatisfactionResVO statisticsByDimension(PatSatisfactionReqVO patSatisfactionReqVO) {
         //瀹氫箟杩斿弬
         PatSatisfactionResVO result = new PatSatisfactionResVO();
-        //绛旈鎬讳汉鏁�(宸茬粡鍥炵瓟闂鐨�)
-        int totalPerson = 0;
-        // 鎬讳汉鏁帮紙鍖呭惈鏈洖绛旂殑锛�
-        int allPerson = 0;
         //鎮h�呮弧鎰忓害缁熻鎯�
         List<PatSatisfactionDetailEntity> patSatisfactionDetailEntities = new ArrayList<>();
-        List<ServiceSubtask> serviceSubtasks = serviceSubtaskMapper.selectSatisfactionSubtaskList(patSatisfactionReqVO);
-        // 鎸塼ype鍒嗙粍锛�1-璇煶锛�2-闂嵎
-        Map<String, List<ServiceSubtask>> groupByType = serviceSubtasks.stream().filter(s -> s.getType() != null).collect(Collectors.groupingBy(ServiceSubtask::getType));
-        List<ServiceSubtask> voiceList = groupByType.getOrDefault("1", Collections.emptyList());
-        List<ServiceSubtask> questionnaireList = groupByType.getOrDefault("2", Collections.emptyList());
+        // [0]=绛旈鎬讳汉鏁�(宸茬粡鍥炵瓟闂鐨�) [1]=鎬讳汉鏁帮紙鍖呭惈鏈洖绛旂殑锛�
+        int[] personTotals = new int[2];
 
-        if (CollectionUtils.isNotEmpty(voiceList)) {
+        // 鍙煡鏈鏉′欢涓嬪疄闄呭瓨鍦ㄥ摢浜� type锛堝師鏉ユ槸鎶婃帴杩戝叏琛ㄧ殑涓婄櫨涓瓧娈垫煡鍥炴潵锛屼粎鐢ㄤ簬鍒ゆ柇涓嬮潰涓や釜鍒嗘敮鏄惁涓虹┖锛�
+        List<String> types = serviceSubtaskMapper.selectSatisfactionSubtaskTypes(patSatisfactionReqVO);
+        boolean hasVoice = types != null && types.contains("1");
+        boolean hasQuestionnaire = types != null && types.contains("2");
+
+        if (hasVoice) {
             //1. 鍏堣幏鍙栧叆鍙備腑鐨勭淮鎶ら泦鍚�
             IvrLibaTemplateScript iltscript = new IvrLibaTemplateScript();
             iltscript.setDimensionList(patSatisfactionReqVO.getDimensionList());
@@ -355,158 +276,34 @@
             //2 鍐嶈幏鍙栬闂婊℃剰搴﹀垎绫讳笅鐨処vrLibaTemplateScript鐨勯泦鍚�
             List<IvrLibaTemplateScript> ivrLibaTemplateScripts = ivrLibaTemplateScriptMapper.selectILTSBydimension(iltscript);
             if (CollectionUtils.isNotEmpty(ivrLibaTemplateScripts)) {
-                //3 鍐嶉�氳繃鍗曚釜ivr_liba_template鑾峰彇鍒版墍鏈夌殑婊℃剰搴﹂棶棰� 鍜� 浣跨敤璇ユā鏉跨殑subTask浜烘暟
+                //3 涓�娆$粺璁″叏閮ㄩ棶棰樼殑鍙戦�侀噺/濉姤閲�/閫夐」鍒嗗�硷紙鍘熸潵鏄瘡涓棶棰樺悇鏌� 3 娆★級
+                List<SatScriptRef> scriptRefs = new ArrayList<>();
                 for (IvrLibaTemplateScript ivrLibaTemplateScript : ivrLibaTemplateScripts) {
-                    //璁板綍鍗曚釜鏄庣粏淇℃伅
-                    PatSatisfactionDetailEntity patSatisfactionDetailEntity = new PatSatisfactionDetailEntity();
-
-                    //閫氳繃闂ID锛岃幏鍙栭棶棰橀�夐」(涓轰簡鑾峰彇閫夐」涓婄殑鏈�楂樺垎鏈�浣庡垎)
-                    IvrLibaTemplateTargetoption ivrLibaTemplateTargetoption = new IvrLibaTemplateTargetoption();
-                    ivrLibaTemplateTargetoption.setScriptid(ivrLibaTemplateScript.getScriptid());
-                    List<IvrLibaTemplateTargetoption> ivrLibaTemplateTargetoptions = ivrLibaTemplateTargetoptionMapper.selectIvrLibaTemplateTargetoptionList(ivrLibaTemplateTargetoption);
-
-                    //鑾峰彇libTemplateId
-                    List<Long> libTemplateIds = new ArrayList<>();
-                    libTemplateIds.add(ivrLibaTemplateScript.getTemplateid());
-
-                    // 鍐嶉�氳繃妯℃澘id鑾峰彇闂鍙戦�佹�婚噺
-                    ServiceSubtaskSatisfactionEntity serviceSubtaskSatisfactionEntity = DtoConversionUtils.sourceToTarget(patSatisfactionReqVO, ServiceSubtaskSatisfactionEntity.class);
-                    serviceSubtaskSatisfactionEntity.setLibTemplateIds(libTemplateIds);
-                    serviceSubtaskSatisfactionEntity.setType(1);
-                    List<Long> subids = serviceSubtaskMapper.querySendCount(serviceSubtaskSatisfactionEntity);
-
-                    //鑾峰彇濉姤鏁伴噺
-                    List<ServiceSubtaskDetailDTO> fillCount = null;
-                    if (CollectionUtils.isNotEmpty(subids))
-                        fillCount = serviceSubtaskDetailMapper.queryFillCount(subids, ivrLibaTemplateScript.getScriptContent());
-
-                    // 鏍规嵁matchedtext鍒嗙粍锛岀粺璁″悇閫夐」鏁伴噺鍙婂崰姣�
-                    Map<String, Map<String, Object>> matchedtextStats = new HashMap<>();
-                    if (CollectionUtils.isNotEmpty(fillCount)) {
-                        int totalFill = fillCount.size();
-                        fillCount.stream().filter(dto -> dto.getMatchedtext() != null).collect(Collectors.groupingBy(ServiceSubtaskDetailDTO::getMatchedtext, Collectors.counting())).forEach((matchedtext, count) -> {
-                            Map<String, Object> stat = new HashMap<>();
-                            stat.put("count", count);
-                            stat.put("ratio", new BigDecimal((double) count / totalFill * 100).setScale(1, RoundingMode.HALF_UP).doubleValue());
-                            matchedtextStats.put(matchedtext, stat);
-                        });
-                        // 浠巉illCount涓彇targetvalue锛�&鍒嗛殧鐨勫綋鍓嶉棶棰樼殑鎵�鏈夐�夐」锛夛紝琛ュ叏鏈嚭鐜扮殑閫夐」锛宑ount鍜宺atio鍧囦负0
-                        fillCount.stream().filter(dto -> dto.getTargetvalue() != null).findFirst().ifPresent(dto -> {
-                            for (String option : dto.getTargetvalue().split("&")) {
-                                String optionTrim = option.trim();
-                                if (!matchedtextStats.containsKey(optionTrim)) {
-                                    Map<String, Object> emptyStat = new HashMap<>();
-                                    emptyStat.put("count", 0L);
-                                    emptyStat.put("ratio", 0.0);
-                                    matchedtextStats.put(optionTrim, emptyStat);
-                                }
-                            }
-                        });
-                    }
-
-                    patSatisfactionDetailEntity.setScriptContent(ivrLibaTemplateScript.getScriptContent());
-                    patSatisfactionDetailEntity.setAnswerPerson(CollectionUtils.isEmpty(fillCount) ? 0 : fillCount.size());
-                    patSatisfactionDetailEntity.setNoAnswerPerson(CollectionUtils.isNotEmpty(fillCount) && CollectionUtils.isNotEmpty(subids) ? (subids.size() - fillCount.size()) : CollectionUtils.isNotEmpty(subids) ? subids.size() : 0);
-                    patSatisfactionDetailEntity.setAverageScore(String.valueOf(averageScore(fillCount)));
-                    patSatisfactionDetailEntity.setMaxScore(String.valueOf(maxScore(ivrLibaTemplateTargetoptions)));
-                    patSatisfactionDetailEntity.setMinScore(String.valueOf(minScore(ivrLibaTemplateTargetoptions)));
-                    patSatisfactionDetailEntity.setAnswerRate(CollectionUtils.isEmpty(subids) ? 0 : CollectionUtils.isEmpty(fillCount) ? 0 : fillCount.size() * 1.0 / subids.size());
-                    patSatisfactionDetailEntity.setMatchedtextStats(matchedtextStats);
-                    patSatisfactionDetailEntities.add(patSatisfactionDetailEntity);
-
-                    totalPerson += CollectionUtils.isEmpty(fillCount) ? 0 : fillCount.size();
-                    allPerson += subids.size();
+                    scriptRefs.add(new SatScriptRef(ivrLibaTemplateScript.getTemplateid(), ivrLibaTemplateScript.getScriptid(), ivrLibaTemplateScript.getScriptContent()));
                 }
+                buildSatisfactionDetailEntities(patSatisfactionReqVO, "1", true, scriptRefs, patSatisfactionDetailEntities, personTotals);
             }
         }
 
-        if (CollectionUtils.isNotEmpty(questionnaireList)) {
+        if (hasQuestionnaire) {
             //1. 鍏堣幏鍙栧叆鍙備腑鐨勬弧鎰忓害缁村害
             SvyLibTemplateScriptVO svyLibTemplateScriptVO = new SvyLibTemplateScriptVO();
             svyLibTemplateScriptVO.setDimensionList(patSatisfactionReqVO.getDimensionList());
             //2 鍐嶈幏鍙栬闂婊℃剰搴﹀垎绫讳笅鐨処vrLibaTemplateScript鐨勯泦鍚�
             List<SvyLibTemplateScript> svyLibTemplateScripts = svyLibTemplateScriptMapper.selectSLTScriptListBydimension(svyLibTemplateScriptVO);
             if (CollectionUtils.isNotEmpty(svyLibTemplateScripts)) {
+                //3 涓�娆$粺璁″叏閮ㄩ棶棰樼殑鍙戦�侀噺/濉姤閲�/閫夐」鍒嗗�硷紙鍘熸潵鏄瘡涓棶棰樺悇鏌� 3 娆★級
+                List<SatScriptRef> scriptRefs = new ArrayList<>();
                 for (SvyLibTemplateScript svyLibTemplateScript : svyLibTemplateScripts) {
-                    //璁板綍鍗曚釜鏄庣粏淇℃伅
-                    PatSatisfactionDetailEntity patSatisfactionDetailEntity = new PatSatisfactionDetailEntity();
-
-                    //閫氳繃闂ID锛岃幏鍙栭棶棰橀�夐」(涓轰簡鑾峰彇閫夐」涓婄殑鏈�楂樺垎鏈�浣庡垎)
-                    SvyLibTemplateTargetoption svyLibTemplateTargetoption = new SvyLibTemplateTargetoption();
-                    svyLibTemplateTargetoption.setScriptid(svyLibTemplateScript.getId());
-                    List<SvyLibTemplateTargetoption> svyLibTemplateTargetoptions = svyLibTemplateTargetoptionMapper.selectSvyLibTemplateTargetoptionList(svyLibTemplateTargetoption);
-
-                    //鑾峰彇libTemplateId
-                    List<Long> libTemplateIds = new ArrayList<>();
-                    libTemplateIds.add(svyLibTemplateScript.getSvyid());
-
-                    // 鍐嶉�氳繃妯℃澘id鑾峰彇闂鍙戦�佹�婚噺
-                    ServiceSubtaskSatisfactionEntity serviceSubtaskSatisfactionEntity = DtoConversionUtils.sourceToTarget(patSatisfactionReqVO, ServiceSubtaskSatisfactionEntity.class);
-                    serviceSubtaskSatisfactionEntity.setLibTemplateIds(libTemplateIds);
-                    serviceSubtaskSatisfactionEntity.setType(2);
-                    List<Long> subids = serviceSubtaskMapper.querySendCount(serviceSubtaskSatisfactionEntity);
-
-                    //鑾峰彇濉姤鏁伴噺
-                    List<ServiceSubtaskDetailDTO> fillCount = null;
-                    if (CollectionUtils.isNotEmpty(subids))
-                        fillCount = serviceSubtaskDetailMapper.queryFillCount(subids, svyLibTemplateScript.getScriptContent());
-
-                    // 鏍规嵁matchedtext鍒嗙粍锛岀粺璁″悇閫夐」鏁伴噺鍙婂崰姣�
-                    Map<String, Map<String, Object>> matchedtextStats = new HashMap<>();
-                    if (CollectionUtils.isNotEmpty(fillCount)) {
-                        int totalFill = fillCount.size();
-                        fillCount.stream().filter(dto -> dto.getMatchedtext() != null).collect(Collectors.groupingBy(ServiceSubtaskDetailDTO::getMatchedtext, Collectors.counting())).forEach((matchedtext, count) -> {
-                            Map<String, Object> stat = new HashMap<>();
-                            stat.put("count", count);
-                            stat.put("ratio", new BigDecimal((double) count / totalFill * 100).setScale(1, RoundingMode.HALF_UP).doubleValue());
-                            matchedtextStats.put(matchedtext, stat);
-                        });
-                        // 浠巉illCount涓彇targetvalue锛�&鍒嗛殧鐨勫綋鍓嶉棶棰樼殑鎵�鏈夐�夐」锛夛紝琛ュ叏鏈嚭鐜扮殑閫夐」锛宑ount鍜宺atio鍧囦负0
-                        fillCount.stream().filter(dto -> dto.getTargetvalue() != null).findFirst().ifPresent(dto -> {
-                            for (String option : dto.getTargetvalue().split("&")) {
-                                String optionTrim = option.trim();
-                                if (!matchedtextStats.containsKey(optionTrim)) {
-                                    Map<String, Object> emptyStat = new HashMap<>();
-                                    emptyStat.put("count", 0L);
-                                    emptyStat.put("ratio", 0.0);
-                                    matchedtextStats.put(optionTrim, emptyStat);
-                                }
-                            }
-                        });
-                    }
-                    patSatisfactionDetailEntity.setScriptContent(svyLibTemplateScript.getScriptContent());
-                    patSatisfactionDetailEntity.setAnswerPerson(CollectionUtils.isEmpty(fillCount) ? 0 : fillCount.size());
-                    patSatisfactionDetailEntity.setNoAnswerPerson(CollectionUtils.isNotEmpty(fillCount) && CollectionUtils.isNotEmpty(subids) ? (subids.size() - fillCount.size()) : CollectionUtils.isNotEmpty(subids) ? subids.size() : 0);
-                    patSatisfactionDetailEntity.setAverageScore(String.valueOf(averageScore(fillCount)));
-                    
-                    Double maxScore = svyLibTemplateTargetoptions.stream()
-                            .map(dto -> dto.getScore())
-                            .filter(scoreStr -> scoreStr != null && !scoreStr.isEmpty())
-                            .mapToDouble(Double::parseDouble)
-                            .max()
-                            .orElse(0.0);
-                    patSatisfactionDetailEntity.setMaxScore(String.valueOf(maxScore));
-                    
-                    Double minScore = svyLibTemplateTargetoptions.stream()
-                            .map(dto -> dto.getScore())
-                            .filter(scoreStr -> scoreStr != null && !scoreStr.isEmpty())
-                            .mapToDouble(Double::parseDouble)
-                            .min()
-                            .orElse(0.0);
-                    patSatisfactionDetailEntity.setMinScore(String.valueOf(minScore));
-
-                    patSatisfactionDetailEntity.setAnswerRate(CollectionUtils.isEmpty(subids) ? 0 : CollectionUtils.isEmpty(fillCount) ? 0 : fillCount.size() * 1.0 / subids.size());
-                    patSatisfactionDetailEntity.setMatchedtextStats(matchedtextStats);
-                    patSatisfactionDetailEntities.add(patSatisfactionDetailEntity);
-
-                    totalPerson += CollectionUtils.isEmpty(fillCount) ? 0 : fillCount.size();
-                    allPerson += subids.size();
+                    // 闂嵎鐨勯�夐」琛ㄦ寜 svy_lib_template_script 鐨勪富閿叧鑱�
+                    scriptRefs.add(new SatScriptRef(svyLibTemplateScript.getSvyid(), svyLibTemplateScript.getId(), svyLibTemplateScript.getScriptContent()));
                 }
+                buildSatisfactionDetailEntities(patSatisfactionReqVO, "2", false, scriptRefs, patSatisfactionDetailEntities, personTotals);
             }
         }
-        result.setTotalAnswerRate(Arith.div(totalPerson, allPerson));
+        result.setTotalAnswerRate(Arith.div(personTotals[0], personTotals[1]));
         result.setPatSatisfactionDetailEntities(patSatisfactionDetailEntities);
-        result.setTotalPerson(totalPerson);
+        result.setTotalPerson(personTotals[0]);
 
         return result;
     }
@@ -516,8 +313,8 @@
         //鐢ㄦ潵璁板綍鏌辩姸鍥句俊鎭紙key=婊℃剰搴﹀垎绫诲悕绉帮紝value=婊℃剰搴﹀垎绫讳笅鐨勬�讳汉鏁板拰濉姤浜烘暟锛�
         Map<String, Object> zzt = new HashMap<>();
 
-        // 璁板綍涓�涓嬫瘡绉嶅垎绫荤殑濉姤鎯呭喌
-        Map<String, List<ServiceSubtaskDetailDTO>> assortNameFillCountMap = new HashMap<>();
+        // 璁板綍涓�涓嬫瘡绉嶅垎绫荤殑濉姤鎯呭喌锛堝彧璁板垎鍊间笌鏉℃暟锛屼笉鍐嶆妸鏄庣粏琛屽叏閮ㄦ煡鍥� Java锛�
+        Map<String, List<ScoreCount>> assortNameFillCountMap = new HashMap<>();
 
         if (patSatisfactionReqVO.getType() == null || patSatisfactionReqVO.getType() == 1) {
             //1. 鍏堣幏鍙杋vr_liba_script_assort鐨勬弧鎰忓害鍒嗙被ID
@@ -529,37 +326,40 @@
             //2 鍐嶈幏鍙栬闂婊℃剰搴﹀垎绫讳笅鐨処vrLibaTemplateScript鐨勯泦鍚�
             List<IvrLibaTemplateScript> ivrLibaTemplateScripts = ivrLibaTemplateScriptMapper.queryLibTemplateIds(mydIdList);
 
+            //2.1 涓�娆℃�ф妸鏈璇锋眰鍏ㄩ儴妯℃澘鐨勫彂閫侀噺銆佸洖鏀朵汉鏁版煡鍥炴潵锛堝師鏉ユ槸姣忎釜妯℃澘闂鍚勬煡涓ゆ锛�
+            SatisfactionStatQuery statQuery = buildSatisfactionStatQuery(patSatisfactionReqVO, "1",
+                    ivrLibaTemplateScripts.stream().map(IvrLibaTemplateScript::getTemplateid).collect(Collectors.toList()));
+            Map<Long, Long> sendCountMap = mapSatisfactionSendCount(statQuery);
+            SatisfactionFillStat fillStat = querySatisfactionFillStat(statQuery);
+            Map<Long, Long> fillPersonCountMap = mapSatisfactionFillPersonCount(statQuery);
 
             //3 鎸塻criptAssortname鍒嗙粍,骞惰幏鍙栨瘡涓泦鍚堣繘琛岄亶鍘嗭紝璁$畻姣忎釜鍒嗙被鐨勬�讳汉鏁板拰濉姤浜烘暟
             Map<String, List<IvrLibaTemplateScript>> scriptGroupByAssortName = ivrLibaTemplateScripts.stream().filter(item -> item.getScriptAssortname() != null).collect(Collectors.groupingBy(IvrLibaTemplateScript::getScriptAssortname));
             if (scriptGroupByAssortName != null && !scriptGroupByAssortName.isEmpty()) {
                 for (String assortNameKey : scriptGroupByAssortName.keySet()) {
-                    //璁板綍鎬讳汉鏁�
-                    Double subidAll = 0.0;
-                    //璁板綍鎬荤殑濉姤浜烘暟
-                    Double fillCountAll = 0.0;
-                    //鑾峰彇璇ュ垎绫荤殑濉姤鎬绘暟閲�
-                    List<ServiceSubtaskDetailDTO> allFillCountList = new ArrayList<>();
+                    // 璇ュ垎绫绘秹鍙婄殑鍘婚噸妯℃澘锛氬彂閫佷汉鏁�/鍥炴敹浜烘暟閮芥寜妯℃澘缁熻锛�
+                    // 鎸夐鐩惊鐜疮鍔犱細鎶婂悓涓�涓ā鏉跨殑閲忛噸澶嶈鎴愩�岄閲忋��
+                    Set<Long> categoryTemplateIds = new LinkedHashSet<>();
+                    //鑾峰彇璇ュ垎绫荤殑濉姤鎬绘暟閲忥紙鍙敤浜庣畻骞冲潎鍒嗭紝鎸夐鐩仛鍚堢殑琛屾暟锛�
+                    List<ScoreCount> allFillCountList = new ArrayList<>();
 
                     List<IvrLibaTemplateScript> ivrLibaTemplateScriptList = scriptGroupByAssortName.get(assortNameKey);
                     for (IvrLibaTemplateScript ivrLibaTemplateScript : ivrLibaTemplateScriptList) {
-                        //鑾峰彇libTemplateId
-                        List<Long> libTemplateIds = new ArrayList<>();
-                        libTemplateIds.add(ivrLibaTemplateScript.getTemplateid());
-                        // 鍐嶉�氳繃妯℃澘id鑾峰彇闂鍙戦�佹�婚噺
-                        ServiceSubtaskSatisfactionEntity serviceSubtaskSatisfactionEntity = DtoConversionUtils.sourceToTarget(patSatisfactionReqVO, ServiceSubtaskSatisfactionEntity.class);
-                        serviceSubtaskSatisfactionEntity.setLibTemplateIds(libTemplateIds);
-                        serviceSubtaskSatisfactionEntity.setType(1);
-                        List<Long> subids = serviceSubtaskMapper.querySendCount(serviceSubtaskSatisfactionEntity);
-
-                        //鑾峰彇濉姤鏁伴噺
-                        List<ServiceSubtaskDetailDTO> fillCount = null;
-                        if (CollectionUtils.isNotEmpty(subids)) {
-                            fillCount = serviceSubtaskDetailMapper.queryFillCount(subids, ivrLibaTemplateScript.getScriptContent());
-                            if (CollectionUtils.isNotEmpty(fillCount)) allFillCountList.addAll(fillCount);
+                        Long templateId = ivrLibaTemplateScript.getTemplateid();
+                        if (templateId != null) {
+                            categoryTemplateIds.add(templateId);
                         }
-                        subidAll += subids.size();
-                        fillCountAll += CollectionUtils.isEmpty(fillCount) ? 0 : fillCount.size();
+                        //鑾峰彇濉姤鍒嗗�煎垎甯冿紙鍙敤浜庡钩鍧囧垎锛�
+                        List<ScoreCount> fillCount = fillStat.get(templateId, ivrLibaTemplateScript.getScriptContent());
+                        if (CollectionUtils.isNotEmpty(fillCount)) allFillCountList.addAll(fillCount);
+                    }
+
+                    //璁板綍鍙戦�佹�讳汉鏁般�佸洖鏀舵�讳汉鏁�
+                    Double subidAll = 0.0;
+                    Double fillCountAll = 0.0;
+                    for (Long templateId : categoryTemplateIds) {
+                        subidAll += sendCountMap.getOrDefault(templateId, 0L);
+                        fillCountAll += fillPersonCountMap.getOrDefault(templateId, 0L);
                     }
 
                     // 杩欓噷鍏堣褰曚竴涓嬫瘡涓垎绫荤殑鎬讳汉鏁板拰濉姤浜烘暟
@@ -567,7 +367,7 @@
                     personCount.put("fillCountAll", fillCountAll);
                     personCount.put("subidAll", subidAll);
                     personCount.put("receiveRate", Arith.div(fillCountAll, subidAll));
-                    personCount.put("averageScore", averageScore(allFillCountList));
+                    personCount.put("averageScore", averageScoreByStat(allFillCountList));
                     zzt.put(assortNameKey, personCount);
                     assortNameFillCountMap.put(assortNameKey, allFillCountList);
                 }
@@ -584,36 +384,40 @@
             //2 鍐嶈幏鍙栬闂婊℃剰搴﹀垎绫讳笅鐨処vrLibaTemplateScript鐨勯泦鍚�
             List<SvyLibTemplateScript> svyLibTemplateScripts = svyLibTemplateScriptMapper.querySvyLibTemplateIds(mydIdList);
 
+            //2.1 涓�娆℃�ф妸鏈璇锋眰鍏ㄩ儴妯℃澘鐨勫彂閫侀噺銆佸洖鏀朵汉鏁版煡鍥炴潵锛堝師鏉ユ槸姣忎釜妯℃澘闂鍚勬煡涓ゆ锛�
+            SatisfactionStatQuery statQuery = buildSatisfactionStatQuery(patSatisfactionReqVO, "2",
+                    svyLibTemplateScripts.stream().map(SvyLibTemplateScript::getSvyid).collect(Collectors.toList()));
+            Map<Long, Long> sendCountMap = mapSatisfactionSendCount(statQuery);
+            SatisfactionFillStat fillStat = querySatisfactionFillStat(statQuery);
+            Map<Long, Long> fillPersonCountMap = mapSatisfactionFillPersonCount(statQuery);
+
             //3 鎸塻criptAssortname鍒嗙粍,骞惰幏鍙栨瘡涓泦鍚堣繘琛岄亶鍘嗭紝璁$畻姣忎釜鍒嗙被鐨勬�讳汉鏁板拰濉姤浜烘暟
             Map<String, List<SvyLibTemplateScript>> scriptGroupByatCegoryName = svyLibTemplateScripts.stream().filter(item -> item.getCategoryName() != null).collect(Collectors.groupingBy(SvyLibTemplateScript::getCategoryName));
             if (scriptGroupByatCegoryName != null && !scriptGroupByatCegoryName.isEmpty()) {
                 for (String categoryNameKey : scriptGroupByatCegoryName.keySet()) {
-                    //璁板綍鎬讳汉鏁�
-                    Double subidAll = 0.0;
-                    //璁板綍鎬荤殑濉姤浜烘暟
-                    Double fillCountAll = 0.0;
-                    //鑾峰彇璇ュ垎绫荤殑濉姤鎬绘暟閲�
-                    List<ServiceSubtaskDetailDTO> allFillCountList = new ArrayList<>();
+                    // 璇ュ垎绫绘秹鍙婄殑鍘婚噸妯℃澘锛氬彂閫佷汉鏁�/鍥炴敹浜烘暟閮芥寜妯℃澘缁熻锛�
+                    // 鎸夐鐩惊鐜疮鍔犱細鎶婂悓涓�涓ā鏉跨殑閲忛噸澶嶈鎴愩�岄閲忋��
+                    Set<Long> categoryTemplateIds = new LinkedHashSet<>();
+                    //鑾峰彇璇ュ垎绫荤殑濉姤鎬绘暟閲忥紙鍙敤浜庣畻骞冲潎鍒嗭紝鎸夐鐩仛鍚堢殑琛屾暟锛�
+                    List<ScoreCount> allFillCountList = new ArrayList<>();
 
                     List<SvyLibTemplateScript> svyLibTemplateScriptList = scriptGroupByatCegoryName.get(categoryNameKey);
                     for (SvyLibTemplateScript svyLibTemplateScript : svyLibTemplateScriptList) {
-                        //鑾峰彇libTemplateId
-                        List<Long> libTemplateIds = new ArrayList<>();
-                        libTemplateIds.add(svyLibTemplateScript.getSvyid());
-                        // 鍐嶉�氳繃妯℃澘id鑾峰彇闂鍙戦�佹�婚噺
-                        ServiceSubtaskSatisfactionEntity serviceSubtaskSatisfactionEntity = DtoConversionUtils.sourceToTarget(patSatisfactionReqVO, ServiceSubtaskSatisfactionEntity.class);
-                        serviceSubtaskSatisfactionEntity.setLibTemplateIds(libTemplateIds);
-                        serviceSubtaskSatisfactionEntity.setType(2);
-                        List<Long> subids = serviceSubtaskMapper.querySendCount(serviceSubtaskSatisfactionEntity);
-
-                        //鑾峰彇濉姤鏁伴噺
-                        List<ServiceSubtaskDetailDTO> fillCount = null;
-                        if (CollectionUtils.isNotEmpty(subids)) {
-                            fillCount = serviceSubtaskDetailMapper.queryFillCount(subids, svyLibTemplateScript.getScriptContent());
-                            if (CollectionUtils.isNotEmpty(fillCount)) allFillCountList.addAll(fillCount);
+                        Long templateId = svyLibTemplateScript.getSvyid();
+                        if (templateId != null) {
+                            categoryTemplateIds.add(templateId);
                         }
-                        subidAll += subids.size();
-                        fillCountAll += CollectionUtils.isEmpty(fillCount) ? 0 : fillCount.size();
+                        //鑾峰彇濉姤鍒嗗�煎垎甯冿紙鍙敤浜庡钩鍧囧垎锛�
+                        List<ScoreCount> fillCount = fillStat.get(templateId, svyLibTemplateScript.getScriptContent());
+                        if (CollectionUtils.isNotEmpty(fillCount)) allFillCountList.addAll(fillCount);
+                    }
+
+                    //璁板綍鍙戦�佹�讳汉鏁般�佸洖鏀舵�讳汉鏁�
+                    Double subidAll = 0.0;
+                    Double fillCountAll = 0.0;
+                    for (Long templateId : categoryTemplateIds) {
+                        subidAll += sendCountMap.getOrDefault(templateId, 0L);
+                        fillCountAll += fillPersonCountMap.getOrDefault(templateId, 0L);
                     }
 
 
@@ -627,16 +431,16 @@
                         personCount.put("receiveRate", Arith.div((Double) personCount.get("fillCountAll"), (Double) personCount.get("subidAll")));
 
                         //鐪嬩竴涓嬩箣鍓嶆湁娌℃湁璇ュ垎绫荤殑濉姤閲�
-                        List<ServiceSubtaskDetailDTO> serviceSubtaskDetailDTOS = assortNameFillCountMap.get(categoryNameKey);
+                        List<ScoreCount> serviceSubtaskDetailDTOS = assortNameFillCountMap.get(categoryNameKey);
                         if (CollectionUtils.isNotEmpty(serviceSubtaskDetailDTOS))
                             allFillCountList.addAll(serviceSubtaskDetailDTOS);
-                        personCount.put("averageScore", averageScore(allFillCountList));
+                        personCount.put("averageScore", averageScoreByStat(allFillCountList));
                     } else {
                         personCount = new HashMap<>();
                         personCount.put("fillCountAll", fillCountAll);
                         personCount.put("subidAll", subidAll);
                         personCount.put("receiveRate", Arith.div(fillCountAll, subidAll));
-                        personCount.put("averageScore", averageScore(allFillCountList));
+                        personCount.put("averageScore", averageScoreByStat(allFillCountList));
                         zzt.put(categoryNameKey, personCount);
                     }
                 }
@@ -648,11 +452,11 @@
 
     @Override
     public Map<String, Object> satisfactionGraphDimension(PatSatisfactionReqVO patSatisfactionReqVO) {
-        //鐢ㄦ潵璁板綍鏌辩姸鍥句俊鎭紙key=婊℃剰搴﹀垎绫诲悕绉帮紝value=婊℃剰搴﹀垎绫讳笅鐨勬�讳汉鏁板拰濉姤浜烘暟锛�
+        //鐢ㄦ潵璁板綍鏌辩姸鍥句俊鎭紙key=婊℃剰搴︾淮搴﹀悕绉帮紝value=璇ョ淮搴︿笅鐨勬�讳汉鏁板拰濉姤浜烘暟锛�
         Map<String, Object> zzt = new HashMap<>();
 
-        // 璁板綍涓�涓嬫瘡绉嶅垎绫荤殑濉姤鎯呭喌
-        Map<String, List<ServiceSubtaskDetailDTO>> assortNameFillCountMap = new HashMap<>();
+        // 璁板綍涓�涓嬫瘡涓淮搴︾殑濉姤鎯呭喌锛堝彧璁板垎鍊间笌鏉℃暟锛屼笉鍐嶆妸鏄庣粏琛屽叏閮ㄦ煡鍥� Java锛�
+        Map<String, List<ScoreCount>> assortNameFillCountMap = new HashMap<>();
 
         if (patSatisfactionReqVO.getType() == null || patSatisfactionReqVO.getType() == 1) {
             //1. 浠庡叆鍙備腑鑾峰彇婊℃剰搴︾淮缁村害
@@ -663,36 +467,40 @@
             List<IvrLibaTemplateScript> ivrLibaTemplateScripts = ivrLibaTemplateScriptMapper.selectILTSBydimension(ivrLibaTemplateScript2);
             if (ivrLibaTemplateScripts != null && !ivrLibaTemplateScripts.isEmpty()) {
                 //3 鎸塪imension缁村害鍒嗙粍,骞惰幏鍙栨瘡涓泦鍚堣繘琛岄亶鍘嗭紝璁$畻姣忎釜鍒嗙被鐨勬�讳汉鏁板拰濉姤浜烘暟
-                Map<String, List<IvrLibaTemplateScript>> groupByDimension = ivrLibaTemplateScripts.stream().filter(item -> item.getDimension() != null).collect(Collectors.groupingBy(IvrLibaTemplateScript::getDimension));
+                List<IvrLibaTemplateScript> dimensionScripts = ivrLibaTemplateScripts.stream().filter(item -> item.getDimension() != null).collect(Collectors.toList());
+                Map<String, List<IvrLibaTemplateScript>> groupByDimension = dimensionScripts.stream().collect(Collectors.groupingBy(IvrLibaTemplateScript::getDimension));
 
                 if (groupByDimension != null && !groupByDimension.isEmpty()) {
+                    //3.1 涓�娆℃�ф妸鏈璇锋眰鍏ㄩ儴妯℃澘鐨勫彂閫侀噺銆佸洖鏀朵汉鏁版煡鍥炴潵锛堝師鏉ユ槸姣忎釜妯℃澘闂鍚勬煡涓ゆ锛�
+                    SatisfactionStatQuery statQuery = buildSatisfactionStatQuery(patSatisfactionReqVO, "1",
+                            dimensionScripts.stream().map(IvrLibaTemplateScript::getTemplateid).collect(Collectors.toList()));
+                    Map<Long, Long> sendCountMap = mapSatisfactionSendCount(statQuery);
+                    SatisfactionFillStat fillStat = querySatisfactionFillStat(statQuery);
+                    Map<Long, Long> fillPersonCountMap = mapSatisfactionFillPersonCount(statQuery);
+
                     for (String dimension : groupByDimension.keySet()) {
-                        //璁板綍鎬讳汉鏁�
-                        Double subidAll = 0.0;
-                        //璁板綍鎬荤殑濉姤浜烘暟
-                        Double fillCountAll = 0.0;
-                        //鑾峰彇璇ョ淮搴︾殑濉姤鎬绘暟閲�
-                        List<ServiceSubtaskDetailDTO> allFillCountList = new ArrayList<>();
+                        // 璇ョ淮搴︽秹鍙婄殑鍘婚噸妯℃澘锛氬彂閫佷汉鏁�/鍥炴敹浜烘暟閮芥寜妯℃澘缁熻锛屾寜棰樼洰绱姞浼氬彉鎴愰閲�
+                        Set<Long> dimensionTemplateIds = new LinkedHashSet<>();
+                        //鑾峰彇璇ョ淮搴︾殑濉姤鍒嗗�煎垎甯冿紙鍙敤浜庣畻骞冲潎鍒嗭級
+                        List<ScoreCount> allFillCountList = new ArrayList<>();
 
                         List<IvrLibaTemplateScript> ivrLibaTemplateScriptList = groupByDimension.get(dimension);
                         for (IvrLibaTemplateScript ivrLibaTemplateScript : ivrLibaTemplateScriptList) {
-                            //鑾峰彇libTemplateId
-                            List<Long> libTemplateIds = new ArrayList<>();
-                            libTemplateIds.add(ivrLibaTemplateScript.getTemplateid());
-                            // 鍐嶉�氳繃妯℃澘id鑾峰彇闂鍙戦�佹�婚噺
-                            ServiceSubtaskSatisfactionEntity serviceSubtaskSatisfactionEntity = DtoConversionUtils.sourceToTarget(patSatisfactionReqVO, ServiceSubtaskSatisfactionEntity.class);
-                            serviceSubtaskSatisfactionEntity.setLibTemplateIds(libTemplateIds);
-                            serviceSubtaskSatisfactionEntity.setType(1);
-                            List<Long> subids = serviceSubtaskMapper.querySendCount(serviceSubtaskSatisfactionEntity);
-
-                            //鑾峰彇濉姤鏁伴噺
-                            List<ServiceSubtaskDetailDTO> fillCount = null;
-                            if (CollectionUtils.isNotEmpty(subids)) {
-                                fillCount = serviceSubtaskDetailMapper.queryFillCount(subids, ivrLibaTemplateScript.getScriptContent());
-                                if (CollectionUtils.isNotEmpty(fillCount)) allFillCountList.addAll(fillCount);
+                            Long templateId = ivrLibaTemplateScript.getTemplateid();
+                            if (templateId != null) {
+                                dimensionTemplateIds.add(templateId);
                             }
-                            subidAll += subids.size();
-                            fillCountAll += CollectionUtils.isEmpty(fillCount) ? 0 : fillCount.size();
+                            //鑾峰彇濉姤鍒嗗�煎垎甯冿紙鍙敤浜庡钩鍧囧垎锛�
+                            List<ScoreCount> fillCount = fillStat.get(templateId, ivrLibaTemplateScript.getScriptContent());
+                            if (CollectionUtils.isNotEmpty(fillCount)) allFillCountList.addAll(fillCount);
+                        }
+
+                        //璁板綍鍙戦�佹�讳汉鏁般�佸洖鏀舵�讳汉鏁�
+                        Double subidAll = 0.0;
+                        Double fillCountAll = 0.0;
+                        for (Long templateId : dimensionTemplateIds) {
+                            subidAll += sendCountMap.getOrDefault(templateId, 0L);
+                            fillCountAll += fillPersonCountMap.getOrDefault(templateId, 0L);
                         }
 
                         // 杩欓噷鍏堣褰曚竴涓嬫瘡涓垎绫荤殑鎬讳汉鏁板拰濉姤浜烘暟
@@ -700,7 +508,7 @@
                         personCount.put("fillCountAll", fillCountAll);
                         personCount.put("subidAll", subidAll);
                         personCount.put("receiveRate", Arith.div(fillCountAll, subidAll));
-                        personCount.put("averageScore", averageScore(allFillCountList));
+                        personCount.put("averageScore", averageScoreByStat(allFillCountList));
                         zzt.put(dimension, personCount);
                         assortNameFillCountMap.put(dimension, allFillCountList);
                     }
@@ -716,36 +524,40 @@
             //2 鍐嶈幏鍙栬闂婊℃剰搴﹀垎绫讳笅鐨処vrLibaTemplateScript鐨勯泦鍚�
             List<SvyLibTemplateScript> svyLibTemplateScripts = svyLibTemplateScriptMapper.selectSLTScriptListBydimension(svyLibTemplateScriptVO);
             if (svyLibTemplateScripts != null && !svyLibTemplateScripts.isEmpty()) {
-                //3 鎸塻criptAssortname鍒嗙粍,骞惰幏鍙栨瘡涓泦鍚堣繘琛岄亶鍘嗭紝璁$畻姣忎釜鍒嗙被鐨勬�讳汉鏁板拰濉姤浜烘暟
-                Map<String, List<SvyLibTemplateScript>> groupByDimension = svyLibTemplateScripts.stream().filter(item -> item.getDimension() != null).collect(Collectors.groupingBy(SvyLibTemplateScript::getDimension));
+                //3 鎸塪imension缁村害鍒嗙粍,骞惰幏鍙栨瘡涓泦鍚堣繘琛岄亶鍘嗭紝璁$畻姣忎釜鍒嗙被鐨勬�讳汉鏁板拰濉姤浜烘暟
+                List<SvyLibTemplateScript> dimensionScripts = svyLibTemplateScripts.stream().filter(item -> item.getDimension() != null).collect(Collectors.toList());
+                Map<String, List<SvyLibTemplateScript>> groupByDimension = dimensionScripts.stream().collect(Collectors.groupingBy(SvyLibTemplateScript::getDimension));
                 if (groupByDimension != null && !groupByDimension.isEmpty()) {
+                    //3.1 涓�娆℃�ф妸鏈璇锋眰鍏ㄩ儴妯℃澘鐨勫彂閫侀噺銆佸洖鏀朵汉鏁版煡鍥炴潵锛堝師鏉ユ槸姣忎釜妯℃澘闂鍚勬煡涓ゆ锛�
+                    SatisfactionStatQuery statQuery = buildSatisfactionStatQuery(patSatisfactionReqVO, "2",
+                            dimensionScripts.stream().map(SvyLibTemplateScript::getSvyid).collect(Collectors.toList()));
+                    Map<Long, Long> sendCountMap = mapSatisfactionSendCount(statQuery);
+                    SatisfactionFillStat fillStat = querySatisfactionFillStat(statQuery);
+                    Map<Long, Long> fillPersonCountMap = mapSatisfactionFillPersonCount(statQuery);
+
                     for (String dimension : groupByDimension.keySet()) {
-                        //璁板綍鎬讳汉鏁�
-                        Double subidAll = 0.0;
-                        //璁板綍鎬荤殑濉姤浜烘暟
-                        Double fillCountAll = 0.0;
-                        //鑾峰彇璇ュ垎绫荤殑濉姤鎬绘暟閲�
-                        List<ServiceSubtaskDetailDTO> allFillCountList = new ArrayList<>();
+                        // 璇ョ淮搴︽秹鍙婄殑鍘婚噸妯℃澘锛氬彂閫佷汉鏁�/鍥炴敹浜烘暟閮芥寜妯℃澘缁熻锛屾寜棰樼洰绱姞浼氬彉鎴愰閲�
+                        Set<Long> dimensionTemplateIds = new LinkedHashSet<>();
+                        //鑾峰彇璇ュ垎绫荤殑濉姤鍒嗗�煎垎甯冿紙鍙敤浜庣畻骞冲潎鍒嗭級
+                        List<ScoreCount> allFillCountList = new ArrayList<>();
 
                         List<SvyLibTemplateScript> svyLibTemplateScriptList = groupByDimension.get(dimension);
                         for (SvyLibTemplateScript svyLibTemplateScript : svyLibTemplateScriptList) {
-                            //鑾峰彇libTemplateId
-                            List<Long> libTemplateIds = new ArrayList<>();
-                            libTemplateIds.add(svyLibTemplateScript.getSvyid());
-                            // 鍐嶉�氳繃妯℃澘id鑾峰彇闂鍙戦�佹�婚噺
-                            ServiceSubtaskSatisfactionEntity serviceSubtaskSatisfactionEntity = DtoConversionUtils.sourceToTarget(patSatisfactionReqVO, ServiceSubtaskSatisfactionEntity.class);
-                            serviceSubtaskSatisfactionEntity.setLibTemplateIds(libTemplateIds);
-                            serviceSubtaskSatisfactionEntity.setType(2);
-                            List<Long> subids = serviceSubtaskMapper.querySendCount(serviceSubtaskSatisfactionEntity);
-
-                            //鑾峰彇濉姤鏁伴噺
-                            List<ServiceSubtaskDetailDTO> fillCount = null;
-                            if (CollectionUtils.isNotEmpty(subids)) {
-                                fillCount = serviceSubtaskDetailMapper.queryFillCount(subids, svyLibTemplateScript.getScriptContent());
-                                if (CollectionUtils.isNotEmpty(fillCount)) allFillCountList.addAll(fillCount);
+                            Long templateId = svyLibTemplateScript.getSvyid();
+                            if (templateId != null) {
+                                dimensionTemplateIds.add(templateId);
                             }
-                            subidAll += subids.size();
-                            fillCountAll += CollectionUtils.isEmpty(fillCount) ? 0 : fillCount.size();
+                            //鑾峰彇濉姤鍒嗗�煎垎甯冿紙鍙敤浜庡钩鍧囧垎锛�
+                            List<ScoreCount> fillCount = fillStat.get(templateId, svyLibTemplateScript.getScriptContent());
+                            if (CollectionUtils.isNotEmpty(fillCount)) allFillCountList.addAll(fillCount);
+                        }
+
+                        //璁板綍鍙戦�佹�讳汉鏁般�佸洖鏀舵�讳汉鏁�
+                        Double subidAll = 0.0;
+                        Double fillCountAll = 0.0;
+                        for (Long templateId : dimensionTemplateIds) {
+                            subidAll += sendCountMap.getOrDefault(templateId, 0L);
+                            fillCountAll += fillPersonCountMap.getOrDefault(templateId, 0L);
                         }
 
 
@@ -759,16 +571,16 @@
                             personCount.put("receiveRate", Arith.div((Double) personCount.get("fillCountAll"), (Double) personCount.get("subidAll")));
 
                             //鐪嬩竴涓嬩箣鍓嶆湁娌℃湁璇ュ垎绫荤殑濉姤閲�
-                            List<ServiceSubtaskDetailDTO> serviceSubtaskDetailDTOS = assortNameFillCountMap.get(dimension);
+                            List<ScoreCount> serviceSubtaskDetailDTOS = assortNameFillCountMap.get(dimension);
                             if (CollectionUtils.isNotEmpty(serviceSubtaskDetailDTOS))
                                 allFillCountList.addAll(serviceSubtaskDetailDTOS);
-                            personCount.put("averageScore", averageScore(allFillCountList));
+                            personCount.put("averageScore", averageScoreByStat(allFillCountList));
                         } else {
                             personCount = new HashMap<>();
                             personCount.put("fillCountAll", fillCountAll);
                             personCount.put("subidAll", subidAll);
                             personCount.put("receiveRate", Arith.div(fillCountAll, subidAll));
-                            personCount.put("averageScore", averageScore(allFillCountList));
+                            personCount.put("averageScore", averageScoreByStat(allFillCountList));
                             zzt.put(dimension, personCount);
                         }
                     }
@@ -823,4 +635,470 @@
         double sum = fillCount.stream().filter(dto -> dto.getScore() != null && !dto.getScore().isEmpty()).mapToDouble(dto -> Double.parseDouble(dto.getScore())).sum();
         return new BigDecimal(sum / fillCount.size()).setScale(1, RoundingMode.HALF_UP).doubleValue();
     }
+
+    // ==================== satisfactionGraph 鑱氬悎缁熻锛堟浛浠i�愪釜妯℃澘鏌ュ簱锛� ====================
+
+    /**
+     * 鏋勫缓鑱氬悎缁熻鍏ュ弬锛堝彇鍊兼潵婧愪笌鍘� DtoConversionUtils.sourceToTarget(patSatisfactionReqVO, ServiceSubtaskSatisfactionEntity.class) 涓�鑷达級
+     *
+     * @param reqVO       鍘熷璇锋眰
+     * @param type        service_subtask.type锛�1 璇煶銆�2 闂嵎锛夛紝鐢ㄥ瓧绗︿覆姣旇緝閬垮厤闅愬紡杞崲
+     * @param templateIds 鏈璇锋眰娑夊強鐨勬ā鏉縤d锛坕vr 鐢� templateid锛岄棶鍗风敤 svyid锛�
+     */
+    private SatisfactionStatQuery buildSatisfactionStatQuery(PatSatisfactionReqVO reqVO, String type, List<Long> templateIds) {
+        SatisfactionStatQuery statQuery = new SatisfactionStatQuery();
+        statQuery.setOrgid(reqVO.getOrgid());
+        statQuery.setCampusid(reqVO.getCampusid());
+        statQuery.setType(type);
+        statQuery.setStartTime(reqVO.getStartTime());
+        statQuery.setEndTime(reqVO.getEndTime());
+        statQuery.setDeptcodes(reqVO.getDeptcodes());
+        statQuery.setHospitaldistrictcodes(reqVO.getHospitaldistrictcodes());
+        statQuery.setLibTemplateIds(CollectionUtils.isEmpty(templateIds) ? templateIds
+                : templateIds.stream().filter(Objects::nonNull).distinct().collect(Collectors.toList()));
+        return statQuery;
+    }
+
+    /**
+     * 涓�娆℃煡鍑哄叏閮ㄦā鏉跨殑鍙戦�侀噺
+     *
+     * @return key=妯℃澘id锛寁alue=鍙戦�侀噺锛堢瓑浠蜂簬鍘� querySendCount 缁撴灉鐨� size锛�
+     */
+    private Map<Long, Long> mapSatisfactionSendCount(SatisfactionStatQuery statQuery) {
+        Map<Long, Long> sendCountMap = new HashMap<>();
+        if (statQuery == null || CollectionUtils.isEmpty(statQuery.getLibTemplateIds())) return sendCountMap;
+        List<Map<String, Object>> rows = serviceSubtaskMapper.selectSatisfactionSendCount(statQuery);
+        if (CollectionUtils.isEmpty(rows)) return sendCountMap;
+        for (Map<String, Object> row : rows) {
+            Long templateId = toLong(row.get("libtemplateid"));
+            if (templateId == null) continue;
+            sendCountMap.put(templateId, toLong(row.get("cnt"), 0L));
+        }
+        return sendCountMap;
+    }
+
+    /**
+     * 涓�娆℃煡鍑哄叏閮ㄦā鏉跨殑銆屽洖鏀朵汉鏁般�嶏紙鎸夋ā鏉垮幓閲嶏紝鍚屼竴涓汉绛斿閬撻鍙畻涓�浜猴級
+     *
+     * @return key=妯℃澘id锛寁alue=鍥炴敹浜烘暟
+     */
+    private Map<Long, Long> mapSatisfactionFillPersonCount(SatisfactionStatQuery statQuery) {
+        Map<Long, Long> fillPersonCountMap = new HashMap<>();
+        if (statQuery == null || CollectionUtils.isEmpty(statQuery.getLibTemplateIds())) return fillPersonCountMap;
+        List<Map<String, Object>> rows = serviceSubtaskDetailMapper.selectSatisfactionFillPersonCount(statQuery);
+        if (CollectionUtils.isEmpty(rows)) return fillPersonCountMap;
+        for (Map<String, Object> row : rows) {
+            Long templateId = toLong(row.get("libtemplateid"));
+            if (templateId == null) continue;
+            fillPersonCountMap.put(templateId, toLong(row.get("cnt"), 0L));
+        }
+        return fillPersonCountMap;
+    }
+
+    /**
+     * 涓�娆℃煡鍑哄叏閮ㄦā鏉跨殑濉姤閲忥紙鎸� 妯℃澘+闂+鍒嗗�� 鍒嗙粍锛�
+     */
+    private SatisfactionFillStat querySatisfactionFillStat(SatisfactionStatQuery statQuery) {
+        SatisfactionFillStat fillStat = new SatisfactionFillStat();
+        if (statQuery == null || CollectionUtils.isEmpty(statQuery.getLibTemplateIds())) return fillStat;
+        List<Map<String, Object>> rows = serviceSubtaskDetailMapper.selectSatisfactionFillStat(statQuery);
+        if (CollectionUtils.isEmpty(rows)) return fillStat;
+        for (Map<String, Object> row : rows) {
+            Long templateId = toLong(row.get("libtemplateid"));
+            if (templateId == null) continue;
+            long cnt = toLong(row.get("cnt"), 0L);
+            if (cnt <= 0L) continue;
+            Object scoreValue = row.get("score");
+            Object questiontextValue = row.get("questiontext");
+            ScoreCount scoreCount = new ScoreCount(scoreValue == null ? null : String.valueOf(scoreValue), cnt);
+            fillStat.add(templateId, questiontextValue == null ? null : String.valueOf(questiontextValue), scoreCount);
+        }
+        return fillStat;
+    }
+
+    /**
+     * 鑾峰彇骞冲潎鍒嗭紙鑱氬悎鐗堬細鍒嗗�间笌鏉℃暟锛�
+     * <p>
+     * 鍙e緞涓� {@link #averageScore(List)} 瀹屽叏涓�鑷达細鍒嗗瓙涓哄彲瑙f瀽鍒嗗�间箣鍜岋紝鍒嗘瘝涓烘槑缁嗚鏁帮紙鍖呭惈鍒嗗�间负绌虹殑琛岋級銆�
+     * 娉ㄦ剰涓嶈兘鏀规垚 sql 鐨� avg() 鎴栥�屾湁鍒嗗�肩殑琛屾暟銆嶅仛鍒嗘瘝锛屽惁鍒欒繑鍥炵殑 averageScore 浼氬彉銆�
+     */
+    private Double averageScoreByStat(List<ScoreCount> fillCount) {
+        if (CollectionUtils.isEmpty(fillCount)) return 0.0;
+        double sum = 0.0;
+        long rows = 0L;
+        for (ScoreCount scoreCount : fillCount) {
+            if (scoreCount.getCount() <= 0L) continue;
+            rows += scoreCount.getCount();
+            if (scoreCount.getScore() != null && !scoreCount.getScore().isEmpty()) {
+                sum += Double.parseDouble(scoreCount.getScore()) * scoreCount.getCount();
+            }
+        }
+        if (rows == 0L) return 0.0;
+        return new BigDecimal(sum / rows).setScale(1, RoundingMode.HALF_UP).doubleValue();
+    }
+
+    /**
+     * 鍒嗗�煎垎缁勯敭锛氭ā鏉縤d + 鍒嗛殧绗� + 瑙勮寖鍖栧悗鐨勯棶棰樺唴瀹�
+     * <p>
+     * MySQL 閲� questiontext = #{questiontext} 鏄�屽拷鐣ュぇ灏忓啓銆佸拷鐣ュ熬閮ㄧ┖鏍笺�嶇殑姣旇緝锛�
+     * 杩欓噷鐢� trim + 灏忓啓鍋氱瓑浠峰尮閰嶃��
+     */
+    private static String statKey(Long templateId, String questiontext) {
+        return templateId + "\u0001" + (questiontext == null ? "" : questiontext.trim().toLowerCase(Locale.ROOT));
+    }
+
+    private static Long toLong(Object value) {
+        if (value == null) return null;
+        if (value instanceof Number) return ((Number) value).longValue();
+        String text = String.valueOf(value).trim();
+        return text.isEmpty() ? null : Long.valueOf(text);
+    }
+
+    private static long toLong(Object value, long defaultValue) {
+        Long result = toLong(value);
+        return result == null ? defaultValue : result;
+    }
+
+    /**
+     * 婊℃剰搴﹀垎鍊艰仛鍚堢粨鏋滐細鍒嗗�� + 璇ュ垎鍊煎搴旂殑鏄庣粏琛屾暟
+     */
+    private static class ScoreCount {
+        private final String score;
+        private final long count;
+
+        ScoreCount(String score, long count) {
+            this.score = score;
+            this.count = count;
+        }
+
+        String getScore() {
+            return score;
+        }
+
+        long getCount() {
+            return count;
+        }
+    }
+
+    /**
+     * 婊℃剰搴﹀~鎶ラ噺鑱氬悎缁撴灉
+     * <p>
+     * byTemplateQuestion锛氭寜 妯℃澘+闂 鍒嗙粍锛�
+     * byTemplate锛氭寜妯℃澘鍒嗙粍锛堣妯℃澘鍏ㄩ儴闂锛夛紝鐢ㄤ簬鍏煎鍘� queryFillCount 鍦�
+     * script_content 涓� null 鏃朵笉鎷� questiontext 鏉′欢鐨勮涓恒��
+     */
+    private static class SatisfactionFillStat {
+        private final Map<String, List<ScoreCount>> byTemplateQuestion = new HashMap<>();
+        private final Map<Long, List<ScoreCount>> byTemplate = new HashMap<>();
+
+        void add(Long templateId, String questiontext, ScoreCount scoreCount) {
+            byTemplate.computeIfAbsent(templateId, key -> new ArrayList<>()).add(scoreCount);
+            if (questiontext != null) {
+                byTemplateQuestion.computeIfAbsent(statKey(templateId, questiontext), key -> new ArrayList<>()).add(scoreCount);
+            }
+        }
+
+        List<ScoreCount> get(Long templateId, String scriptContent) {
+            if (templateId == null) return null;
+            if (scriptContent == null) return byTemplate.get(templateId);
+            return byTemplateQuestion.get(statKey(templateId, scriptContent));
+        }
+    }
+
+    // ==================== statistics / statisticsByDimension 鎵归噺缁熻锛堟浛浠i�愪釜闂鏌ュ簱锛� ====================
+
+    /**
+     * 婊℃剰搴︽槑缁嗙粺璁★紙statistics銆乻tatisticsByDimension 鍏辩敤锛�
+     * <p>
+     * 鎶婂師鏉ャ�屾瘡涓棶棰� 3 鏉� SQL銆嶆敼鎴愩�屾暣涓姹� 3 鏉℃煡璇� + 鍐呭瓨缁勮銆嶏細
+     * 鈶� 鍙戦�侀噺鎸� libtemplateid 鍒嗙粍锛涒憽 濉姤閲忔寜 妯℃澘+闂+matchedtext+score 鍒嗙粍锛涒憿 閫夐」鍒嗗�兼寜 scriptid 鍒嗙粍銆�
+     *
+     * @param reqVO      鍘熷璇锋眰
+     * @param type       service_subtask.type锛�1 璇煶銆�2 闂嵎锛�
+     * @param voice      true=璇煶闂锛堥�夐」鍒嗗�煎彇 ivr_liba_template_targetoption锛�
+     * @param scriptRefs 鏈瑕佺粺璁$殑闂锛堥『搴忓嵆杩斿洖椤哄簭锛�
+     * @param out        鏄庣粏缁撴灉
+     * @param totals     [0]=totalPerson, [1]=allPerson
+     */
+    private void buildSatisfactionDetailEntities(PatSatisfactionReqVO reqVO, String type, boolean voice,
+                                                 List<SatScriptRef> scriptRefs,
+                                                 List<PatSatisfactionDetailEntity> out, int[] totals) {
+        if (CollectionUtils.isEmpty(scriptRefs)) return;
+
+        List<Long> templateIds = scriptRefs.stream().map(SatScriptRef::getTemplateId)
+                .filter(Objects::nonNull).distinct().collect(Collectors.toList());
+        SatisfactionStatQuery statQuery = buildSatisfactionStatQuery(reqVO, type, templateIds);
+        Map<Long, Long> sendCountMap = mapSatisfactionSendCount(statQuery);
+        SatisfactionFillStatByMatchedtext fillStat = querySatisfactionFillStatByMatchedtext(statQuery);
+        Map<Long, List<String>> optionScores = loadTargetOptionScores(voice, scriptRefs);
+
+        for (SatScriptRef scriptRef : scriptRefs) {
+            Long templateId = scriptRef.getTemplateId();
+            //鍙戦�佹�婚噺锛堝師鏉ユ槸鎶婂叏閮� sub_id 鏌ュ洖 Java 鍐嶅彇 size锛�
+            long sendCount = templateId == null ? 0L : sendCountMap.getOrDefault(templateId, 0L);
+            //濉姤鏁伴噺锛堝師鏉ユ槸鎶婃槑缁嗚鍏ㄩ儴鏌ュ洖 Java 鍐嶇粺璁★級
+            List<MatchedtextCount> fillRows = fillStat.get(templateId, scriptRef.getScriptContent());
+            long fillCount = countFillRowsByMatchedtext(fillRows);
+
+            PatSatisfactionDetailEntity detail = new PatSatisfactionDetailEntity();
+            detail.setScriptContent(scriptRef.getScriptContent());
+            detail.setAnswerPerson((int) fillCount);
+            detail.setNoAnswerPerson(fillCount > 0L && sendCount > 0L ? (int) (sendCount - fillCount) : sendCount > 0L ? (int) sendCount : 0);
+            detail.setAverageScore(String.valueOf(averageScoreByMatchedtext(fillRows)));
+            List<String> scores = optionScores.get(scriptRef.getOptionScriptId());
+            detail.setMaxScore(String.valueOf(maxScoreOf(scores)));
+            detail.setMinScore(String.valueOf(minScoreOf(scores)));
+            detail.setAnswerRate(sendCount == 0L ? 0 : fillCount == 0L ? 0 : fillCount * 1.0 / sendCount);
+            detail.setMatchedtextStats(buildMatchedtextStats(fillRows, fillCount));
+            out.add(detail);
+
+            totals[0] += (int) fillCount;
+            totals[1] += (int) sendCount;
+        }
+    }
+
+    /**
+     * 涓�娆℃煡鍑哄叏閮ㄩ棶棰樼殑濉姤閲忥紙鎸� 妯℃澘+闂+matchedtext+score 鍒嗙粍锛屽惈 targetvalue锛�
+     */
+    private SatisfactionFillStatByMatchedtext querySatisfactionFillStatByMatchedtext(SatisfactionStatQuery statQuery) {
+        SatisfactionFillStatByMatchedtext fillStat = new SatisfactionFillStatByMatchedtext();
+        if (statQuery == null || CollectionUtils.isEmpty(statQuery.getLibTemplateIds())) return fillStat;
+        List<Map<String, Object>> rows = serviceSubtaskDetailMapper.selectSatisfactionFillStatByMatchedtext(statQuery);
+        if (CollectionUtils.isEmpty(rows)) return fillStat;
+        for (Map<String, Object> row : rows) {
+            Long templateId = toLong(row.get("libtemplateid"));
+            if (templateId == null) continue;
+            long cnt = toLong(row.get("cnt"), 0L);
+            if (cnt <= 0L) continue;
+            Object questiontext = row.get("questiontext");
+            Object matchedtext = row.get("matchedtext");
+            Object score = row.get("score");
+            Object targetvalue = row.get("targetvalue");
+            fillStat.add(templateId, questiontext == null ? null : String.valueOf(questiontext),
+                    new MatchedtextCount(matchedtext == null ? null : String.valueOf(matchedtext),
+                            score == null ? null : String.valueOf(score),
+                            targetvalue == null ? null : String.valueOf(targetvalue), cnt));
+        }
+        return fillStat;
+    }
+
+    /**
+     * 涓�娆℃煡鍑哄叏閮ㄩ棶棰樼殑閫夐」鍒嗗�硷紙鍙彇 scriptid銆乻core 涓ゅ垪锛�
+     * <p>
+     * 鍘熷疄鐜版槸姣忎釜闂鏌ヤ竴娆℃暣琛岋紝鑰岃皟鐢ㄦ柟鍙敤鍒� score 绠楁渶楂�/鏈�浣庡垎锛�
+     * 闂 id 涓� null 鏃跺師瀹炵幇涓嶆嫾 scriptid 鏉′欢锛堢瓑浜庡彇璇ヨ〃鍏ㄩ儴鍒嗗�硷級锛岃繖閲岀敤 null 浣滀负 key 瀛樿繖涓�浠姐��
+     */
+    private Map<Long, List<String>> loadTargetOptionScores(boolean voice, List<SatScriptRef> scriptRefs) {
+        Map<Long, List<String>> optionScores = new HashMap<>();
+        List<Long> scriptIds = scriptRefs.stream().map(SatScriptRef::getOptionScriptId)
+                .filter(Objects::nonNull).distinct().collect(Collectors.toList());
+        if (CollectionUtils.isNotEmpty(scriptIds)) {
+            addTargetOptionScores(optionScores, voice
+                    ? ivrLibaTemplateTargetoptionMapper.selectIvrLibaTargetoptionScores(scriptIds)
+                    : svyLibTemplateTargetoptionMapper.selectSvyLibTargetoptionScores(scriptIds));
+        }
+        if (scriptRefs.stream().anyMatch(ref -> ref.getOptionScriptId() == null)) {
+            optionScores.put(null, collectTargetOptionScores(voice
+                    ? ivrLibaTemplateTargetoptionMapper.selectIvrLibaTargetoptionScores(null)
+                    : svyLibTemplateTargetoptionMapper.selectSvyLibTargetoptionScores(null)));
+        }
+        return optionScores;
+    }
+
+    private void addTargetOptionScores(Map<Long, List<String>> optionScores, List<Map<String, Object>> rows) {
+        if (CollectionUtils.isEmpty(rows)) return;
+        for (Map<String, Object> row : rows) {
+            Long scriptId = toLong(row.get("scriptid"));
+            if (scriptId == null) continue;
+            Object score = row.get("score");
+            optionScores.computeIfAbsent(scriptId, key -> new ArrayList<>())
+                    .add(score == null ? null : String.valueOf(score));
+        }
+    }
+
+    private List<String> collectTargetOptionScores(List<Map<String, Object>> rows) {
+        List<String> scores = new ArrayList<>();
+        if (CollectionUtils.isEmpty(rows)) return scores;
+        for (Map<String, Object> row : rows) {
+            Object score = row.get("score");
+            scores.add(score == null ? null : String.valueOf(score));
+        }
+        return scores;
+    }
+
+    /**
+     * 鍚勯�夐」锛坢atchedtext锛夋暟閲忓強鍗犳瘮
+     * <p>
+     * 鍙e緞涓庡師瀹炵幇涓�鑷达細鍒嗘瘝鏄叏閮ㄦ槑缁嗚鏁帮紙鍚� matchedtext 涓� null 鐨勮锛夛紱
+     * targetvalue 鐢ㄤ簬琛ュ叏鏈嚭鐜扮殑閫夐」锛坈ount=0銆乺atio=0锛夈��
+     */
+    private Map<String, Map<String, Object>> buildMatchedtextStats(List<MatchedtextCount> fillRows, long totalFill) {
+        Map<String, Map<String, Object>> matchedtextStats = new HashMap<>();
+        if (CollectionUtils.isEmpty(fillRows) || totalFill <= 0L) return matchedtextStats;
+
+        Map<String, Long> matchedtextCounts = new LinkedHashMap<>();
+        String targetvalue = null;
+        for (MatchedtextCount row : fillRows) {
+            if (row.getMatchedtext() != null) {
+                matchedtextCounts.merge(row.getMatchedtext(), row.getCount(), Long::sum);
+            }
+            if (targetvalue == null && row.getTargetvalue() != null) {
+                targetvalue = row.getTargetvalue();
+            }
+        }
+        for (Map.Entry<String, Long> entry : matchedtextCounts.entrySet()) {
+            Map<String, Object> stat = new HashMap<>();
+            stat.put("count", entry.getValue());
+            stat.put("ratio", new BigDecimal((double) entry.getValue() / totalFill * 100).setScale(1, RoundingMode.HALF_UP).doubleValue());
+            matchedtextStats.put(entry.getKey(), stat);
+        }
+        // 浠庢槑缁嗛噷鍙杢argetvalue锛�&鍒嗛殧鐨勫綋鍓嶉棶棰樼殑鎵�鏈夐�夐」锛夛紝琛ュ叏鏈嚭鐜扮殑閫夐」锛宑ount鍜宺atio鍧囦负0
+        if (targetvalue != null) {
+            for (String option : targetvalue.split("&")) {
+                String optionTrim = option.trim();
+                if (!matchedtextStats.containsKey(optionTrim)) {
+                    Map<String, Object> emptyStat = new HashMap<>();
+                    emptyStat.put("count", 0L);
+                    emptyStat.put("ratio", 0.0);
+                    matchedtextStats.put(optionTrim, emptyStat);
+                }
+            }
+        }
+        return matchedtextStats;
+    }
+
+    /**
+     * 鍙栧~鎶ユ槑缁嗚鏁帮紙鍘� fillCount.size()锛�
+     */
+    private long countFillRowsByMatchedtext(List<MatchedtextCount> fillRows) {
+        if (CollectionUtils.isEmpty(fillRows)) return 0L;
+        long rows = 0L;
+        for (MatchedtextCount row : fillRows) {
+            rows += row.getCount();
+        }
+        return rows;
+    }
+
+    /**
+     * 鑾峰彇骞冲潎鍒嗭紙鑱氬悎鐗堬細鍒嗗�间笌鏉℃暟锛�
+     * <p>
+     * 鍙e緞涓� {@link #averageScore(List)} 涓�鑷达細鍒嗗瓙涓哄彲瑙f瀽鍒嗗�间箣鍜岋紝鍒嗘瘝涓烘槑缁嗚鏁帮紙鍚垎鍊间负绌虹殑琛岋級銆�
+     */
+    private Double averageScoreByMatchedtext(List<MatchedtextCount> fillRows) {
+        if (CollectionUtils.isEmpty(fillRows)) return 0.0;
+        double sum = 0.0;
+        long rows = 0L;
+        for (MatchedtextCount row : fillRows) {
+            if (row.getCount() <= 0L) continue;
+            rows += row.getCount();
+            if (row.getScore() != null && !row.getScore().isEmpty()) {
+                sum += Double.parseDouble(row.getScore()) * row.getCount();
+            }
+        }
+        if (rows == 0L) return 0.0;
+        return new BigDecimal(sum / rows).setScale(1, RoundingMode.HALF_UP).doubleValue();
+    }
+
+    /**
+     * 鑾峰彇鏈�楂樺垎锛堣仛鍚堢増锛屽彛寰勫悓 {@link #maxScore(List)}锛氱┖鍒楄〃杩斿洖 0.0锛�
+     */
+    private Double maxScoreOf(List<String> scores) {
+        if (CollectionUtils.isEmpty(scores)) return 0.0;
+        return scores.stream().filter(scoreStr -> scoreStr != null && !scoreStr.isEmpty())
+                .mapToDouble(Double::parseDouble).max().orElse(0.0);
+    }
+
+    /**
+     * 鑾峰彇鏈�浣庡垎锛堣仛鍚堢増锛屽彛寰勫悓 {@link #minScore(List)}锛氱┖鍒楄〃杩斿洖 0.0锛�
+     */
+    private Double minScoreOf(List<String> scores) {
+        if (CollectionUtils.isEmpty(scores)) return 0.0;
+        return scores.stream().filter(scoreStr -> scoreStr != null && !scoreStr.isEmpty())
+                .mapToDouble(Double::parseDouble).min().orElse(0.0);
+    }
+
+    /**
+     * 婊℃剰搴︾粺璁$敤鐨勯棶棰樺紩鐢細妯℃澘id銆侀�夐」琛ㄥ叧鑱旂殑闂id锛堝彲鑳戒负 null锛夈�侀棶棰樺唴瀹�
+     */
+    private static class SatScriptRef {
+        private final Long templateId;
+        private final Long optionScriptId;
+        private final String scriptContent;
+
+        SatScriptRef(Long templateId, Long optionScriptId, String scriptContent) {
+            this.templateId = templateId;
+            this.optionScriptId = optionScriptId;
+            this.scriptContent = scriptContent;
+        }
+
+        Long getTemplateId() {
+            return templateId;
+        }
+
+        Long getOptionScriptId() {
+            return optionScriptId;
+        }
+
+        String getScriptContent() {
+            return scriptContent;
+        }
+    }
+
+    /**
+     * 婊℃剰搴﹀~鎶ヨ仛鍚堢粨鏋滐細matchedtext + score + targetvalue + 鏉℃暟
+     */
+    private static class MatchedtextCount {
+        private final String matchedtext;
+        private final String score;
+        private final String targetvalue;
+        private final long count;
+
+        MatchedtextCount(String matchedtext, String score, String targetvalue, long count) {
+            this.matchedtext = matchedtext;
+            this.score = score;
+            this.targetvalue = targetvalue;
+            this.count = count;
+        }
+
+        String getMatchedtext() {
+            return matchedtext;
+        }
+
+        String getScore() {
+            return score;
+        }
+
+        String getTargetvalue() {
+            return targetvalue;
+        }
+
+        long getCount() {
+            return count;
+        }
+    }
+
+    /**
+     * 婊℃剰搴﹀~鎶ヨ仛鍚堢粨鏋滐紙鎸� matchedtext 鐗堬級
+     * <p>
+     * byTemplateQuestion锛氭寜 妯℃澘+闂 鍒嗙粍锛�
+     * byTemplate锛氭寜妯℃澘鍒嗙粍锛堣妯℃澘鍏ㄩ儴闂锛夛紝鐢ㄤ簬鍏煎鍘� queryFillCount 鍦�
+     * script_content 涓� null 鏃朵笉鎷� questiontext 鏉′欢鐨勮涓恒��
+     */
+    private static class SatisfactionFillStatByMatchedtext {
+        private final Map<String, List<MatchedtextCount>> byTemplateQuestion = new HashMap<>();
+        private final Map<Long, List<MatchedtextCount>> byTemplate = new HashMap<>();
+
+        void add(Long templateId, String questiontext, MatchedtextCount row) {
+            byTemplate.computeIfAbsent(templateId, key -> new ArrayList<>()).add(row);
+            if (questiontext != null) {
+                byTemplateQuestion.computeIfAbsent(statKey(templateId, questiontext), key -> new ArrayList<>()).add(row);
+            }
+        }
+
+        List<MatchedtextCount> get(Long templateId, String scriptContent) {
+            if (templateId == null) return null;
+            if (scriptContent == null) return byTemplate.get(templateId);
+            return byTemplateQuestion.get(statKey(templateId, scriptContent));
+        }
+    }
 }

--
Gitblit v1.9.3