From 0c1f8c479065739f3c7a5126de8e667035d00b58 Mon Sep 17 00:00:00 2001
From: liusheng <337615773@qq.com>
Date: 星期一, 12 十月 2026 14:21:52 +0800
Subject: [PATCH] 【丽水】满意度模块化:新增 ruoyi-satisfaction 独立模块

---
 ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/service/impl/SatisfactionSubtaskServiceImpl.java        | 1218 +++++++
 ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/domain/vo/MzMydScoreRankVO.java                         |    2 
 ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/domain/row/SatisfactionSvyScriptRow.java                |   77 
 ruoyi-satisfaction/src/test/java/com/ruoyi/satisfaction/api/SatisfactionSwitchCheck.java                        |  236 +
 ruoyi-common/src/main/java/com/ruoyi/common/core/service/ISatisfactionCategoryService.java                      |   30 
 ruoyi-satisfaction/pom.xml                                                                                      |   52 
 ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/domain/entity/PatSatisfactionDetailEntity.java          |    2 
 ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/api/DefaultSatisfactionApi.java                         |   80 
 ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/domain/vo/MydTrendReqVO.java                            |    2 
 ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/service/strategy/SltdSatisfactionScoreStrategy.java     |  137 
 ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/controller/SltdSatisfactionController.java              |  172 +
 smartor/src/main/java/com/smartor/mapper/ServiceSubtaskDetailMapper.java                                        |   51 
 ruoyi-common/src/main/java/com/ruoyi/common/core/service/IUserExtInfoService.java                               |   12 
 ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/controller/PatSatisfactionController.java               |   22 
 smartor/pom.xml                                                                                                 |    9 
 ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/domain/dto/ServiceSubtaskOptionStatDTO.java             |    2 
 smartor/src/main/java/com/smartor/domain/PatMedOuthospError.java                                                |   83 
 ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/service/strategy/CommonSatisfactionScoreStrategy.java   |   82 
 smartor/src/main/resources/mapper/smartor/ServiceSubtaskDetailMapper.xml                                        |  212 -
 ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/domain/row/SatisfactionSubtaskQuery.java                |   70 
 ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/service/strategy/SatisfactionSites.java                 |   73 
 ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/domain/vo/PatSatisfactionResVO.java                     |    4 
 ruoyi-quartz/src/main/java/com/ruoyi/quartz/constant/MzGatherConstant.java                                      |   35 
 ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/domain/vo/PatSatisfactionReqVO.java                     |    4 
 ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/mapper/SatisfactionMapper.java                          |  296 +
 smartor/src/main/java/com/smartor/mapper/IvrLibaTemplateScriptMapper.java                                       |    1 
 ruoyi-satisfaction/src/test/java/com/ruoyi/satisfaction/api/SatisfactionContextCheck.java                       |  269 +
 ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/service/strategy/SatisfactionScoreStrategyResolver.java |   71 
 ruoyi-admin/src/main/java/com/ruoyi/web/controller/smartor/ServiceSubtaskDetailTraceController.java             |   12 
 ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/domain/row/SatisfactionSubtaskRef.java                  |   30 
 ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/domain/row/SatisfactionTaskRow.java                     |   47 
 ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/domain/row/SatisfactionOptionScoreRow.java              |   33 
 ruoyi-admin/src/main/resources/application.yml                                                                  |    2 
 ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/domain/dto/MydTrendReqDTO.java                          |    2 
 ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/service/strategy/SatisfactionScoreStrategy.java         |   44 
 ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/domain/row/SatisfactionIvrScriptRow.java                |   78 
 ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/domain/entity/ServiceSubtaskSatisfactionEntity.java     |    3 
 ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/SatisfactionModule.java                                 |   26 
 ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/api/SatisfactionFacade.java                             |  222 +
 ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/controller/SatisfactionExceptConfigController.java      |   64 
 smartor/src/main/java/com/smartor/message/LishuiMessageConfigProviderImpl.java                                  |   87 
 smartor/src/main/java/com/smartor/statemachine/README.java                                                      |  258 +
 ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/domain/vo/SltdMydTotalVO.java                           |    2 
 ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/domain/dto/MzMydScoreRankDTO.java                       |    2 
 ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/domain/PatSatisfaction.java                             |    2 
 ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/config/SatisfactionAvailabilityInterceptor.java         |   65 
 ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/config/SatisfactionSwitch.java                          |  206 +
 ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/config/SatisfactionWebConfig.java                       |   81 
 pom.xml                                                                                                         |    8 
 ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/domain/dto/QuestionResultDTO.java                       |    4 
 ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/service/SatisfactionSubtaskService.java                 |  163 +
 ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/service/IPatSatisfactionService.java                    |   92 
 smartor/src/main/java/com/smartor/service/impl/ServiceSubtaskServiceImpl.java                                   |  945 -----
 ruoyi-satisfaction/src/main/resources/mapper/satisfaction/PatSatisfactionMapper.xml                             |   10 
 ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/domain/vo/SatisfactionExceptConfigQuestionReqVO.java    |    2 
 smartor/src/main/java/com/smartor/mapper/ServiceSubtaskMapper.java                                              |   91 
 ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/api/SatisfactionApi.java                                |   87 
 ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/support/MydOptionRatioChartBuilder.java                 |   15 
 ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/api/SatisfactionExportBundle.java                       |   82 
 smartor/src/main/java/com/smartor/service/impl/UserExtInfoServiceImpl.java                                      |   78 
 ruoyi-admin/src/main/java/com/ruoyi/web/controller/smartor/ServiceSubtaskDetailController.java                  |  197 -
 ruoyi-satisfaction/src/main/resources/mapper/satisfaction/SatisfactionScriptMapper.xml                          |  200 +
 ruoyi-admin/pom.xml                                                                                             |    5 
 ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/domain/entity/SatisfactionStatQuery.java                |   60 
 ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/domain/row/SatisfactionCategoryRow.java                 |   54 
 ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/domain/row/SatisfactionScriptQuery.java                 |   52 
 smartor/src/main/resources/mapper/smartor/PatMedOuthospErrorMapper.xml                                          |   79 
 smartor/src/main/java/com/smartor/service/impl/ServiceSubtaskDetailTraceServiceImpl.java                        |   78 
 ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/mapper/SatisfactionScriptMapper.java                    |  146 
 smartor/src/main/java/com/smartor/service/IServiceSubtaskService.java                                           |   63 
 ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/config/SatisfactionProperties.java                      |   49 
 smartor/src/main/resources/mapper/smartor/ServiceSubtaskMapper.xml                                              |  749 ----
 /dev/null                                                                                                       |  100 
 ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysUserServiceImpl.java                                |   34 
 ruoyi-admin/src/main/java/com/ruoyi/web/controller/smartor/ServiceSubtaskController.java                        |   24 
 ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/domain/dto/QuestionCountKsOrBqDTO.java                  |    4 
 ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/mapper/PatSatisfactionMapper.java                       |   67 
 smartor/src/main/java/com/smartor/service/IServiceSubtaskDetailTraceService.java                                |    8 
 ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/service/impl/PatSatisfactionServiceImpl.java            |  322 -
 smartor/src/main/java/com/smartor/mapper/PatMedOuthospErrorMapper.java                                          |   39 
 smartor/src/main/java/com/smartor/message/WechatMpConfigProviderImpl.java                                       |   89 
 ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/domain/ServiceSubtaskDetailRatioExport.java             |    2 
 ruoyi-satisfaction/src/main/resources/mapper/satisfaction/SatisfactionMapper.xml                                |  986 ++++++
 ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/domain/vo/QuestionCountKsOrBqVO.java                    |    4 
 84 files changed, 6,799 insertions(+), 2,759 deletions(-)

diff --git a/pom.xml b/pom.xml
index 875721c..af402c0 100644
--- a/pom.xml
+++ b/pom.xml
@@ -192,6 +192,13 @@
                 <version>${ruoyi.version}</version>
             </dependency>
 
+            <!-- 婊℃剰搴︽ā鍧� -->
+            <dependency>
+                <groupId>com.ruoyi</groupId>
+                <artifactId>ruoyi-satisfaction</artifactId>
+                <version>${ruoyi.version}</version>
+            </dependency>
+
         </dependencies>
     </dependencyManagement>
 
@@ -203,6 +210,7 @@
         <module>ruoyi-generator</module>
         <module>ruoyi-common</module>
         <module>ruoyi-message</module>
+        <module>ruoyi-satisfaction</module>
         <module>smartor</module>
     </modules>
     <packaging>pom</packaging>
diff --git a/ruoyi-admin/pom.xml b/ruoyi-admin/pom.xml
index 735cba5..b0cdb89 100644
--- a/ruoyi-admin/pom.xml
+++ b/ruoyi-admin/pom.xml
@@ -92,6 +92,11 @@
             <groupId>com.ruoyi</groupId>
             <artifactId>ruoyi-message</artifactId>
         </dependency>
+        <!-- 婊℃剰搴︽ā鍧楋紙Controller / Service / Mapper / SQL / domain 鍏ㄩ儴鍦ㄦ湰妯″潡鍐咃級 -->
+        <dependency>
+            <groupId>com.ruoyi</groupId>
+            <artifactId>ruoyi-satisfaction</artifactId>
+        </dependency>
         <!-- https://mvnrepository.com/artifact/net.glxn.qrgen/javase -->
         <dependency>
             <groupId>net.glxn.qrgen</groupId>
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/smartor/ServiceSubtaskController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/smartor/ServiceSubtaskController.java
index 6852e78..c9f4cbe 100644
--- a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/smartor/ServiceSubtaskController.java
+++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/smartor/ServiceSubtaskController.java
@@ -23,9 +23,9 @@
 import com.ruoyi.quartz.task.RyTask;
 import com.ruoyi.system.service.ISysConfigService;
 import com.ruoyi.system.service.ISysUserDeptService;
-import com.smartor.common.MydOptionRatioChartBuilder;
+import com.ruoyi.satisfaction.api.SatisfactionExportBundle;
+import com.ruoyi.satisfaction.api.SatisfactionFacade;
 import com.smartor.domain.*;
-import com.smartor.domain.DTO.QuestionResultDTO;
 import com.smartor.domain.VO.*;
 import com.smartor.domain.entity.ServiceSubtaskEntity;
 import com.smartor.service.IServiceSubtaskRecordService;
@@ -69,6 +69,15 @@
 
     @Autowired
     private IServiceSubtaskService serviceSubtaskService;
+
+    /**
+     * 婊℃剰搴︽ā鍧楀澶栭棬闈紙甯搁┗ Bean锛夈��
+     * <p>
+     * 婊℃剰搴︾浉鍏虫暟鎹紙闂璇︽儏鍗犳瘮锛夊彧鑳介�氳繃瀹冭幏鍙栵紱婊℃剰搴︽ā鍧楀叧闂椂
+     * 闂ㄩ潰杩斿洖绌虹粨鏋滃苟鎵� WARN锛�<b>涓嶄細</b>褰卞搷闅忚瀵煎嚭鐨勫叾瀹冨唴瀹广��
+     */
+    @Autowired
+    private SatisfactionFacade satisfactionFacade;
 
     @Autowired
     private IServiceTaskService serviceTaskService;
@@ -205,7 +214,8 @@
             serviceSubtaskList = serviceSubtaskService.patItem(serviceSubtaskEntity);
         }
         List<ServiceSubtaskExport> serviceSubtaskExports = null;
-        List<ServiceSubtaskDetailRatioExport> serviceSubtaskDetailRatioExports = null;
+        //婊℃剰搴﹀崰姣斿鍑哄寘锛氶粯璁ょ┖鍖咃紙娌℃湁鍙鍑虹殑鎮h�呮椂淇濇寔涓虹┖锛夛紝鏈夋暟鎹椂鐢辨弧鎰忓害闂ㄩ潰濉厖
+        SatisfactionExportBundle ratioBundle = SatisfactionExportBundle.empty();
         if (!CollectionUtils.isEmpty(serviceSubtaskList)) {
             serviceSubtaskExports = DtoConversionUtils.sourceToTarget(serviceSubtaskList, ServiceSubtaskExport.class);
             for (ServiceSubtaskExport serviceSubtaskExport : serviceSubtaskExports) {
@@ -223,21 +233,23 @@
                 }
             }
             //闂璇︽儏鍗犳瘮锛氬彧缁熻鏈瀵煎嚭鐨勮繖鎵规偅鑰咃紝鏁伴噺=閫夎閫夐」鐨勬偅鑰呮暟锛屾瘮渚�=鍗犺棰樹綔绛斾汉鏁�
+            //婊℃剰搴︽ā鍧楀寲鍚庯細鍙�氳繃婊℃剰搴︽ā鍧楁毚闇茬殑 api 鍖呭彇鏁帮紙瀵煎嚭鍖� = 琛� + 琛岀被鍨� + 鍥捐〃鏋勫缓鍣級锛�
+            //妯″潡鍏抽棴锛坰atisfaction.enabled=false锛夋椂闂ㄩ潰杩斿洖绌哄鍑哄寘锛屽鍑虹収甯稿畬鎴愶紙鍙槸娌℃湁鍗犳瘮 Sheet锛夈��
             List<Long> subtaskIds = new ArrayList<>();
             for (ServiceSubtaskRes serviceSubtaskRes : serviceSubtaskList) {
                 if (serviceSubtaskRes.getId() != null) {
                     subtaskIds.add(serviceSubtaskRes.getId());
                 }
             }
-            serviceSubtaskDetailRatioExports = serviceSubtaskService.statMydQuestionOptionForExport(subtaskIds, null);
+            ratioBundle = satisfactionFacade.getQuestionOptionRatioExport(subtaskIds, null);
         }
 
 
         ExcelUtil<ServiceSubtaskExport> util = new ExcelUtil<ServiceSubtaskExport>(ServiceSubtaskExport.class);
         //绗笁涓猄heet锛氶棶棰樿鎯呭崰姣斿浘琛紙鎸変换鍔″悕绉板垎缁勶紝涓�涓换鍔′竴寮犳煴鐘跺浘锛�
         util.exportExcelTwoSheetWithChart(response, serviceSubtaskExports, "鎮h�呴殢璁夸俊鎭〃鍗�",
-                serviceSubtaskDetailRatioExports, "闂璇︽儏鍗犳瘮", ServiceSubtaskDetailRatioExport.class,
-                new MydOptionRatioChartBuilder(serviceSubtaskDetailRatioExports));
+                ratioBundle.getRows(), "闂璇︽儏鍗犳瘮", ratioBundle.getRowClass(),
+                ratioBundle.getChartBuilder());
     }
 
     /**
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/smartor/ServiceSubtaskDetailController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/smartor/ServiceSubtaskDetailController.java
index ca2cba4..e0a6bed 100644
--- a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/smartor/ServiceSubtaskDetailController.java
+++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/smartor/ServiceSubtaskDetailController.java
@@ -10,12 +10,7 @@
 import com.ruoyi.common.utils.DtoConversionUtils;
 import com.ruoyi.common.utils.StringUtils;
 import com.ruoyi.system.service.ISysConfigService;
-import com.smartor.domain.DTO.QuestionResultDTO;
 import com.smartor.domain.*;
-import com.smartor.domain.VO.MydTrendReqVO;
-import com.smartor.domain.VO.MzMydScoreRankVO;
-import com.smartor.domain.VO.QuestionCountKsOrBqVO;
-import com.smartor.domain.VO.SltdMydTotalVO;
 import com.smartor.domain.entity.ServiceSubtaskEntity;
 import com.smartor.service.IServiceSubtaskDetailService;
 import com.smartor.service.IServiceSubtaskService;
@@ -202,194 +197,4 @@
         map = ServiceSubtaskDetailService.getServiceSubtaskDetails(serviceSubtaskDetailVO);
         return map;
     }
-
-
-    /**
-     * 鐪佺珛鍚屽痉婊℃剰搴︽暟鎹粺璁★紙鎸夐鐩粺璁¢�夐」鏁伴噺/鍗犳瘮锛屽苟缁欏嚭璇ラ鐨勭患鍚堝緱鍒嗭級
-     * <p>
-     * 缁煎悎婊℃剰搴� = (寰堟弧鎰忛」鏁懊�1 + 婊℃剰椤规暟脳0.8 + 涓�鑸」鏁懊�0.6 + 涓嶆弧鎰忛」鏁懊�0.4 + 寰堜笉婊℃剰椤规暟脳0.2)
-     * / (寰堟弧鎰忛」鏁� + 婊℃剰椤规暟 + 涓�鑸」鏁� + 涓嶆弧鎰忛」鏁� + 寰堜笉婊℃剰椤规暟) 脳 100%锛�
-     * 銆屾湭缁忓巻銆嶃�屼笉閫傜敤銆嶇瓑涓嶅湪浜旀。閲岀殑閫夐」鏃笉杩涘垎瀛愪篃涓嶈繘鍒嗘瘝銆�
-     */
-    @ApiOperation("鐪佺珛鍚屽痉婊℃剰搴︽暟鎹粺璁�")
-    @AddOrgId(field = "orgid", paramIndex = 0, campusField = "campusid")
-    @PostMapping("/sltdMydTotalByScore")
-    public Map<String, Object> sltdMydTotalByScore(@RequestBody SltdMydTotalVO sltdMydTotalVO) {
-        List<ServiceSubtaskRes> serviceSubtaskResList = null;
-        if (CollectionUtils.isNotEmpty(sltdMydTotalVO.getServiceTypeList())) {
-            //serviceType鍙兘浼氬嚭鐜板涓紝涓嶆兂澶氬啓鏂规硶浜嗭紝涓�涓竴涓煡璇㈠惂
-            for (String st : sltdMydTotalVO.getServiceTypeList()) {
-                ServiceSubtaskEntity serviceSubtaskEntity = new ServiceSubtaskEntity();
-                serviceSubtaskEntity.setServiceType(st);
-                serviceSubtaskEntity.setEndOutHospTime(sltdMydTotalVO.getEndOutHospTime());
-                serviceSubtaskEntity.setStartOutHospTime(sltdMydTotalVO.getStartOutHospTime());
-                serviceSubtaskEntity.setDeptOrDistrict(sltdMydTotalVO.getDeptOrDistrict());
-                serviceSubtaskEntity.setLeavehospitaldistrictcodes(sltdMydTotalVO.getLeavehospitaldistrictcodes());
-                serviceSubtaskEntity.setLeaveldeptcodes(sltdMydTotalVO.getLeaveldeptcodes());
-                //鏈烘瀯/闄㈠尯鐢� @AddOrgId 娉ㄥ叆锛岃惤鍒版煡璇㈡潯浠朵笂锛岄伩鍏嶈法鏈烘瀯鍙栨暟
-                serviceSubtaskEntity.setOrgid(sltdMydTotalVO.getOrgid());
-                serviceSubtaskEntity.setCampusid(sltdMydTotalVO.getCampusid());
-                if (serviceSubtaskEntity != null) {
-                    List<ServiceSubtask> serviceSubtasks = serviceSubtaskService.getServiceSubtasks(serviceSubtaskEntity);
-                    List<ServiceSubtaskRes> serviceSubtaskRes = DtoConversionUtils.sourceToTarget(serviceSubtasks, ServiceSubtaskRes.class);
-                    if (CollectionUtils.isNotEmpty(serviceSubtaskResList)) {
-                        serviceSubtaskResList.addAll(serviceSubtaskRes);
-                    } else {
-                        if (CollectionUtils.isEmpty(serviceSubtaskResList)) serviceSubtaskResList = serviceSubtaskRes;
-                    }
-                }
-            }
-        }
-        // 鎸� questiontext 鍒嗙粍鍚庤浆鎹负 QuestionResultVO
-        List<QuestionResultDTO> result = new ArrayList<>();
-        if (CollectionUtils.isNotEmpty(serviceSubtaskResList)) {
-            List<ServiceSubtaskDetailRatioExport> detailList = serviceSubtaskService.getDetailRatio(sltdMydTotalVO, serviceSubtaskResList);
-
-            if (detailList == null) {
-                //serviceType 缁勫悎涓嶅湪鏀寔鑼冨洿鍐咃紙濡傚悓鏃跺惈闅忚 2 鍜屾弧鎰忓害 6/14锛夛紝缁欐槑纭彁绀鸿�屼笉鏄倓鎮勮繑鍥炵┖鏁扮粍
-                return error("褰撳墠 serviceType 缁勫悎鏆備笉鏀寔缁熻闂鍗犳瘮锛岃鍒嗗紑鏌ヨ锛堜粎鏀寔闅忚 2 鎴栨弧鎰忓害 6/14锛�");
-            }
-            if (CollectionUtils.isNotEmpty(detailList)) {
-                Map<String, List<ServiceSubtaskDetailRatioExport>> groupedMap = detailList.stream().collect(Collectors.groupingBy(ServiceSubtaskDetailRatioExport::getQuestiontext));
-
-                groupedMap.forEach((questionText, exportList) -> {
-                    QuestionResultDTO questionResultDTO = new QuestionResultDTO();
-                    questionResultDTO.setQeustionText(questionText);
-                    questionResultDTO.setSubtaskDetailRatioExportList(exportList);
-                    //缁煎悎寰楀垎锛�(寰堟弧鎰徝�1 + 婊℃剰脳0.8 + 涓�鑸�0.6 + 涓嶆弧鎰徝�0.4 + 寰堜笉婊℃剰脳0.2) / 浜旀。椤规暟涔嬪拰 脳 100%
-                    questionResultDTO.setAvgScore(calcMydScore(exportList));
-                    result.add(questionResultDTO);
-                });
-            }
-        }
-
-        return success(result);
-    }
-
-    @ApiOperation("鐪佺珛鍚屽痉婊℃剰搴︽暟鎹粺璁¢�氳繃绉戝鎴栫梾鍖�")
-    @PostMapping("/statQuestionOptionByKsOrBq")
-    public Map<String, Object> statQuestionOptionByKsOrBq(@RequestBody QuestionCountKsOrBqVO questionResultVO) {
-        return success(serviceSubtaskService.statQuestionOptionByKsOrBq(questionResultVO));
-    }
-
-    @ApiOperation("鐪佺珛鍚屽痉鎮h�呮弧鎰忓害瓒嬪娍")
-    @PostMapping("/mydTrend")
-    public Map<String, Object> mydTrend(@RequestBody MydTrendReqVO mydTrendReqVO) {
-        return success(serviceSubtaskService.mydTrend(mydTrendReqVO));
-    }
-
-    @ApiOperation("鐪佺珛鍚屽痉鎮h�呮弧鎰忓害缁存姢")
-    @PostMapping("/sltdMydTotalByDimension")
-    public Map<String, Object> sltdMydTotalByDimension(@RequestBody MydTrendReqVO mydTrendReqVO) {
-        return success(serviceSubtaskService.sltdMydTotalByDimension(mydTrendReqVO));
-    }
-
-    @ApiOperation("闂ㄨ瘖婊℃剰搴︾患鍚堝緱鍒嗘帓鍚嶏紙鎸夌瀹ゆ垨鐥呭尯锛�")
-    @AddOrgId(field = "orgid", paramIndex = 0, campusField = "campusid")
-    @PostMapping("/mzMydScoreRank")
-    public Map<String, Object> mzMydScoreRank(@RequestBody MzMydScoreRankVO mzMydScoreRankVO) {
-        return success(serviceSubtaskService.mzMydScoreRank(mzMydScoreRankVO));
-    }
-
-    /**
-     * 澶氶�夐鐨勯棶棰樼被鍨�
-     */
-    private static final String VALUE_TYPE_MULTIPLE = "2";
-
-    /**
-     * 婊℃剰搴︿簲妗i�夐」锛堥『搴忎笌鏉冮噸椤哄簭涓�鑷达級
-     */
-    private static final String[] MYD_SCORE_OPTIONS = {"寰堟弧鎰�", "婊℃剰", "涓�鑸�", "涓嶆弧鎰�", "寰堜笉婊℃剰"};
-
-    /**
-     * 婊℃剰搴︿簲妗f潈閲嶏細寰堟弧鎰� 1銆佹弧鎰� 0.8銆佷竴鑸� 0.6銆佷笉婊℃剰 0.4銆佸緢涓嶆弧鎰� 0.2
-     */
-    private static final BigDecimal[] MYD_SCORE_WEIGHTS = {
-            new BigDecimal("1"), new BigDecimal("0.8"), new BigDecimal("0.6"), new BigDecimal("0.4"), new BigDecimal("0.2")};
-
-    /**
-     * 璁$畻涓�閬撻鐨勬弧鎰忓害缁煎悎寰楀垎锛堢櫨鍒嗗埗锛屼繚鐣欎袱浣嶅皬鏁帮級
-     * <p>
-     * 缁煎悎婊℃剰搴� = (寰堟弧鎰忛」鏁懊�1 + 婊℃剰椤规暟脳0.8 + 涓�鑸」鏁懊�0.6 + 涓嶆弧鎰忛」鏁懊�0.4 + 寰堜笉婊℃剰椤规暟脳0.2)
-     * / (寰堟弧鎰忛」鏁� + 婊℃剰椤规暟 + 涓�鑸」鏁� + 涓嶆弧鎰忛」鏁� + 寰堜笉婊℃剰椤规暟) 脳 100%
-     * <p>
-     * 鏉冮噸鎸夐�夐」鏂囨鍒ゅ畾锛堣 {@link #mydScoreOptionIndex(String)}锛夛紝涓嶅啀渚濊禆鏄庣粏鐨� score 瀛楁锛�
-     * 鍘嗗彶鏁版嵁閲� score 鏈� 0銆併��0000000001銆嶈繖绫昏剰鍊硷紝鎸� score 绠椾細鎶婂緱鍒嗗浐瀹氭垚 0 鎴� 100銆�
-     * 銆屾湭缁忓巻銆嶃�屼笉閫傜敤銆嶇瓑涓嶅湪浜旀。閲岀殑閫夐」鏃笉杩涘垎瀛愪篃涓嶈繘鍒嗘瘝銆�
-     *
-     * @param exportList 鍚屼竴閬撻鐩殑閫夐」缁熻鏄庣粏
-     * @return 鐧惧垎鍒剁患鍚堝緱鍒嗭紝鏃犳湁鏁堥」鏁版椂杩斿洖 "0.00"
-     */
-    private String calcMydScore(List<ServiceSubtaskDetailRatioExport> exportList) {
-        if (CollectionUtils.isEmpty(exportList)) return "0.00";
-
-        BigDecimal scoreSum = BigDecimal.ZERO;   //鍒嗗瓙锛毼�(妗d綅鏉冮噸 脳 椤规暟)
-        BigDecimal countSum = BigDecimal.ZERO;   //鍒嗘瘝锛氫簲妗i」鏁颁箣鍜�
-
-        for (ServiceSubtaskDetailRatioExport detail : exportList) {
-            //澶氶�夐涓�琛屾槸澶氫釜閫夐」鐨勫垎鍊间箣鍜岋紝鍒嗘瘝鍙畻 1 椤癸紝浼氭妸寰楀垎绠楅珮锛屼笉璁″叆缁煎悎寰楀垎
-            if (VALUE_TYPE_MULTIPLE.equals(detail.getValueType())) continue;
-            //銆屼笉閫傜敤銆嶄笉鍦ㄥ叕寮忕殑浜旀。閲岋紝涓嶈鍏ョ患鍚堝緱鍒�
-            if (isNotApplicable(detail)) continue;
-
-            int optionIndex = mydScoreOptionIndex(detail.getOptionresult());
-            BigDecimal count = parseDecimal(detail.getCount());
-            if (optionIndex < 0 || count == null || count.signum() <= 0) continue;
-
-            scoreSum = scoreSum.add(MYD_SCORE_WEIGHTS[optionIndex].multiply(count));
-            countSum = countSum.add(count);
-        }
-
-        if (countSum.signum() <= 0) return "0.00";
-
-        return scoreSum.divide(countSum, 6, RoundingMode.HALF_UP)
-                .multiply(BigDecimal.valueOf(100))
-                .setScale(2, RoundingMode.HALF_UP)
-                .toPlainString();
-    }
-
-    /**
-     * 閫夐」鏂囨 鈫� 婊℃剰搴︿簲妗d笅鏍囷紙寰堟弧鎰� 0銆佹弧鎰� 1銆佷竴鑸� 2銆佷笉婊℃剰 3銆佸緢涓嶆弧鎰� 4锛夛紝涓嶅湪浜旀。閲岃繑鍥� -1
-     */
-    private int mydScoreOptionIndex(String optionresult) {
-        if (StringUtils.isEmpty(optionresult)) return -1;
-        String text = optionresult.trim();
-        for (int i = 0; i < MYD_SCORE_OPTIONS.length; i++) {
-            if (MYD_SCORE_OPTIONS[i].equals(text)) return i;
-        }
-        //鍏煎銆孉 寰堟弧鎰忋�嶃�孊.婊℃剰銆嶈繖绫诲甫閫夐」搴忓彿鍓嶇紑鐨勫巻鍙叉暟鎹�
-        if (text.length() > 2 && (text.charAt(1) == ' ' || text.charAt(1) == '.' || text.charAt(1) == '銆�')) {
-            String stripped = text.substring(2).trim();
-            for (int i = 0; i < MYD_SCORE_OPTIONS.length; i++) {
-                if (MYD_SCORE_OPTIONS[i].equals(stripped)) return i;
-            }
-        }
-        return -1;
-    }
-
-    /**
-     * 鏄惁銆屼笉閫傜敤銆嶉�夐」
-     */
-    private boolean isNotApplicable(ServiceSubtaskDetailRatioExport detail) {
-        return containsNotApplicable(detail.getOptionresult()) || containsNotApplicable(detail.getTargetvalue());
-    }
-
-    private boolean containsNotApplicable(String optionText) {
-        if (StringUtils.isEmpty(optionText)) return false;
-        String text = optionText.trim();
-        return text.contains("涓嶉�傜敤") || text.contains("鏈瓟") || text.contains("鎷掔瓟");
-    }
-
-    /**
-     * 瀛楃涓茶浆 BigDecimal锛岀┖鍊�/闈炴硶鍊艰繑鍥� null
-     */
-    private BigDecimal parseDecimal(String value) {
-        if (StringUtils.isEmpty(value)) return null;
-        try {
-            return new BigDecimal(value.trim());
-        } catch (NumberFormatException e) {
-            return null;
-        }
-    }
-
-}
+}
\ No newline at end of file
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/smartor/ServiceSubtaskDetailTraceController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/smartor/ServiceSubtaskDetailTraceController.java
index 3df473e..be3eba3 100644
--- a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/smartor/ServiceSubtaskDetailTraceController.java
+++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/smartor/ServiceSubtaskDetailTraceController.java
@@ -9,7 +9,6 @@
 import com.ruoyi.common.utils.poi.ExcelUtil;
 import com.smartor.domain.ServiceSubtaskDetailTrace;
 import com.smartor.domain.VO.DetailTraceDealVO;
-import com.smartor.domain.VO.SatisfactionExceptConfigQuestionReqVO;
 import com.smartor.domain.VO.ServiceSubtaskDetailTraceVO;
 import com.smartor.service.IServiceSubtaskDetailTraceService;
 import io.swagger.annotations.Api;
@@ -72,17 +71,6 @@
         Object questionTotal = result.get("questionTotal");
         long total = questionTotal == null ? 0L : Long.parseLong(String.valueOf(questionTotal));
         return getDataTable3(total, result);
-    }
-
-    /**
-     * 鑾峰彇婊℃剰搴﹀紓甯搁厤缃鐩�
-     */
-    @ApiOperation("鑾峰彇婊℃剰搴﹀紓甯搁厤缃鐩�")
-    @PostMapping("/getSatisfactionExceptConfigQuestion")
-    public Map<String, Object> getSatisfactionExceptConfigQuestion(@RequestBody SatisfactionExceptConfigQuestionReqVO questionReqVO) {
-        log.info("鑾峰彇婊℃剰搴﹀紓甯搁厤缃鐩殑鍏ュ弬: {}", questionReqVO);
-        Map<String, Object> satisfactionExceptConfigQuestion = serviceSubtaskDetailTraceService.getSatisfactionExceptConfigQuestion(questionReqVO);
-        return satisfactionExceptConfigQuestion;
     }
 
     /**
diff --git a/ruoyi-admin/src/main/resources/application.yml b/ruoyi-admin/src/main/resources/application.yml
index dfa12b4..49041c9 100644
--- a/ruoyi-admin/src/main/resources/application.yml
+++ b/ruoyi-admin/src/main/resources/application.yml
@@ -158,6 +158,8 @@
 #涓嶉渶瑕佽繘琛岄暱鏈熶换鍔$殑鏈烘瀯
 noLongTask:
 
+
+
 #鐪佺珛鍚屽痉鍙栨暟鎹叕鍏辨帴鍙�
 sltd_pub_path: "http://open-tdyy.cfuture.shop/kapi/gw-api/"
 
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/service/ISatisfactionCategoryService.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/service/ISatisfactionCategoryService.java
new file mode 100644
index 0000000..ad1bd46
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/service/ISatisfactionCategoryService.java
@@ -0,0 +1,30 @@
+package com.ruoyi.common.core.service;
+
+import java.util.Map;
+
+/**
+ * 婊℃剰搴﹀垎绫绘煡璇㈡帴鍙o紙渚� ruoyi-system 绛夋ā鍧楅�氳繃 Spring 瀹瑰櫒璋冪敤婊℃剰搴︽ā鍧楃殑瀹炵幇锛夈��
+ * <p>
+ * <b>涓轰粈涔堝崟鐙媶鍑鸿繖涓帴鍙�</b>锛氭弧鎰忓害鍒嗙被鍘熸湰鎸傚湪 {@link IUserExtInfoService} 涓婏紝
+ * 鐢� smartor 妯″潡鐨� {@code UserExtInfoServiceImpl} 瀹炵幇銆傛弧鎰忓害妯″潡鍖栧悗锛�
+ * 璇ユ煡璇㈠凡鏁存鎼叆 ruoyi-satisfaction锛涗负浜嗕笉璁╄皟鐢ㄦ柟锛堢櫥褰曢摼璺級涓庢弧鎰忓害妯″潡鐩存帴鑰﹀悎锛�
+ * 杩欓噷鎶�"婊℃剰搴﹀垎绫�"杩欎竴鑳藉姏鍗曠嫭鎶芥垚鎺ュ彛鏀惧湪 ruoyi-common锛�
+ * <ul>
+ *   <li>鎺ュ彛鍦� ruoyi-common 鈥斺�� 璋冪敤鏂瑰彧渚濊禆鎺ュ彛锛屼笉渚濊禆婊℃剰搴︽ā鍧楋紱</li>
+ *   <li>瀹炵幇鐢辨弧鎰忓害妯″潡鐨勫父椹婚棬闈� {@code SatisfactionFacade} 鎻愪緵锛�
+ *       璇� Bean <b>姘歌繙瀛樺湪</b>锛屽洜姝よ皟鐢ㄦ柟涓嶄細鍥犱负婊℃剰搴︽ā鍧楄鍏抽棴鑰屽惎鍔ㄥけ璐ワ紱</li>
+ *   <li>婊℃剰搴︽ā鍧楀叧闂紙{@code satisfaction.enabled=false}锛夋垨璇ユā鍧楁暣浣撲笉鍦ㄤ緷璧栭噷鏃讹紝
+ *       璋冪敤鏂规嬁涓嶅埌瀹炵幇锛屽簲闄嶇骇涓虹┖缁撴灉锛堣 {@code SysUserServiceImpl#loadSatisfactionCategories}锛夈��</li>
+ * </ul>
+ */
+public interface ISatisfactionCategoryService {
+
+    /**
+     * 鑾峰彇婊℃剰搴﹂棶棰樺垎绫伙紙璇煶 + 闂嵎锛夛紝甯︾紦瀛樸��
+     *
+     * @param orgid 鏈烘瀯ID
+     * @return 鍖呭惈 {@code voiceCategories} 涓� {@code questionnaireCategorys} 鐨� Map锛�
+     * 婊℃剰搴︽ā鍧楀叧闂垨涓嶅彲鐢ㄦ椂杩斿洖绌� Map
+     */
+    Map<String, Object> getSatisfactionCategories(String orgid);
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/service/IUserExtInfoService.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/service/IUserExtInfoService.java
index c328b51..248459a 100644
--- a/ruoyi-common/src/main/java/com/ruoyi/common/core/service/IUserExtInfoService.java
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/service/IUserExtInfoService.java
@@ -4,6 +4,10 @@
 
 /**
  * 鐢ㄦ埛鎵╁睍淇℃伅鏈嶅姟鎺ュ彛锛堜緵 ruoyi-system 閫氳繃 SpringUtils 璋冪敤 smartor 瀹炵幇锛�
+ * <p>
+ * 娉ㄦ剰锛氬師 {@code getSatisfactionCategories} 宸查殢婊℃剰搴︽ā鍧楀寲杩佸嚭锛�
+ * 鏀圭敱 {@link ISatisfactionCategoryService} 鎻愪緵锛堝疄鐜板湪 ruoyi-satisfaction 妯″潡锛夛紝
+ * 杩欐牱鐧诲綍鍙栨弧鎰忓害鍒嗙被鐨勯摼璺笉鍐嶄緷璧� smartor 鐨勬ā鏉�/鍒嗙被 Mapper銆�
  */
 public interface IUserExtInfoService {
 
@@ -16,12 +20,4 @@
      * @return 鍖呭惈 orgid 鍜� campusid 鐨� Map锛屾壘涓嶅埌鏃惰繑鍥炵┖ Map
      */
     Map<String, String> getUserOrgInfo(String userId, String orgid, String campusid);
-
-    /**
-     * 鑾峰彇婊℃剰搴﹂棶棰樺垎绫伙紙璇煶 + 闂嵎锛�
-     *
-     * @param orgid 鏈烘瀯ID
-     * @return 鍖呭惈 voiceCategories 鍜� questionnaireCategorys 鐨� Map
-     */
-    Map<String, Object> getSatisfactionCategories(String orgid);
 }
diff --git a/ruoyi-quartz/src/main/java/com/ruoyi/quartz/constant/MzGatherConstant.java b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/constant/MzGatherConstant.java
new file mode 100644
index 0000000..e4e64be
--- /dev/null
+++ b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/constant/MzGatherConstant.java
@@ -0,0 +1,35 @@
+package com.ruoyi.quartz.constant;
+
+import java.time.format.DateTimeFormatter;
+
+/**
+ * 鐪佺珛鍚屽痉闂ㄦ�ヨ瘖閲囬泦鍥哄畾甯搁噺锛堜笉闇�瑕佸湪 sys_config 閲岄厤鐨勯偅浜涳級
+ *
+ * @author smartor
+ */
+public class MzGatherConstant {
+
+    private MzGatherConstant() {
+    }
+
+    /** 鐪佺珛鍚屽痉鏈烘瀯ID */
+    public static final String ORG_ID = "20001001";
+
+    /** 鏂偣绛夌紦瀛樹繚鐣欐椂闀匡紙灏忔椂锛夛紝榛樿 30 澶� */
+    public static final long CACHE_TTL_HOURS = 24L * 30L;
+
+    /** 鍗曢〉鍦ㄦ湰杞唴鐨勯噸璇曟鏁� */
+    public static final int PAGE_RETRY_TIMES = 3;
+
+    /** 鍗曢〉閲嶈瘯闂撮殧锛堟绉掞級 */
+    public static final long PAGE_RETRY_SLEEP_MS = 2000L;
+
+    /** 杩炵画澶氬皯椤垫帴鍙eけ璐ュ氨鍒ゅ畾"鎺ュ彛鏁翠綋涓嶅彲鐢�"锛屾湰杞彁鍓嶇粨鏉燂紝涓嶅啀绌鸿浆鍚庨潰鍑犲崄椤� */
+    public static final int MAX_CONSECUTIVE_PAGE_FAIL = 3;
+
+    /** 鏉ユ湭鏉ユ帴鍙e彧鏀寔鎸夊ぉ鏌ワ紝鏃堕棿鍙傛暟浼� yyyy-MM-dd */
+    public static final DateTimeFormatter DAY_FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd");
+
+    /** 鏃ュ織涓庢柇鐐归噷鐢ㄧ殑鏃堕棿鏍煎紡 */
+    public static final DateTimeFormatter MINUTE_FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
+}
diff --git a/ruoyi-satisfaction/pom.xml b/ruoyi-satisfaction/pom.xml
new file mode 100644
index 0000000..53e6df6
--- /dev/null
+++ b/ruoyi-satisfaction/pom.xml
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>ruoyi</artifactId>
+        <groupId>com.ruoyi</groupId>
+        <version>3.8.5</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>ruoyi-satisfaction</artifactId>
+
+    <description>
+        婊℃剰搴︽ā鍧楋細婊℃剰搴︾粺璁�/鏌ヨ/浠诲姟鏂板鐨勫敮涓�瀹炵幇澶勩��
+        鏈ā鍧楄嚜甯� Mapper 涓� SQL锛屽彲鐩存帴鏌ヨ service_subtask銆乻ervice_subtask_detail 鍙婃ā鏉垮垎绫昏〃锛�
+        鍏跺畠妯″潡闇�瑕佹弧鎰忓害鏁版嵁鍙兘閫氳繃 com.ruoyi.satisfaction.api 璋冪敤銆�
+        鏀寔閫氳繃 sys_config 鍙傛暟琛ㄩ噷鐨� satisfaction.enabled 鏁翠綋鍏抽棴锛堣繍琛屾椂璇诲彇锛屾敼瀹屾渶澶� 10 绉掔敓鏁堬級銆�
+    </description>
+
+    <dependencies>
+        <!-- 閫氱敤宸ュ叿锛欱aseController / ExcelUtil / PageUtils / Arith / SecurityUtils / DtoConversionUtils
+             鍚屾椂浼犻�掑甫鍏� pagehelper(鍚� mybatis)銆乻wagger-annotations銆乧ommons-lang3銆乯ackson 绛� -->
+        <dependency>
+            <groupId>com.ruoyi</groupId>
+            <artifactId>ruoyi-common</artifactId>
+        </dependency>
+
+        <!-- lombok锛歳uoyi-common 閲屾槸鐩存帴渚濊禆锛屾澶勬樉寮忓0鏄庣増鏈紝閬垮厤娉ㄨВ澶勭悊鍣ㄧ己澶� -->
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+            <version>1.18.34</version>
+        </dependency>
+
+        <!-- MyBatis锛歁apper 鎺ュ彛涓� XML锛坧agehelper 宸蹭紶閫掑甫鍏ワ紝杩欓噷鍙槸鏄惧紡鍖栨剰鍥撅級 -->
+        <dependency>
+            <groupId>org.mybatis.spring.boot</groupId>
+            <artifactId>mybatis-spring-boot-starter</artifactId>
+            <version>2.2.2</version>
+        </dependency>
+
+        <!-- Servlet API锛欻ttpServletResponse锛圕ontroller 瀵煎嚭鐢級 -->
+        <dependency>
+            <groupId>javax.servlet</groupId>
+            <artifactId>javax.servlet-api</artifactId>
+            <version>4.0.1</version>
+            <scope>provided</scope>
+        </dependency>
+    </dependencies>
+
+</project>
diff --git a/ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/SatisfactionModule.java b/ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/SatisfactionModule.java
new file mode 100644
index 0000000..d4e2fec
--- /dev/null
+++ b/ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/SatisfactionModule.java
@@ -0,0 +1,26 @@
+package com.ruoyi.satisfaction;
+
+/**
+ * 婊℃剰搴︽ā鍧楀寘鏍广��
+ * <p>
+ * 鐩綍绾﹀畾锛�
+ * <ul>
+ *   <li>{@code com.ruoyi.satisfaction.api} 鈥斺�� <b>鍞竴瀵瑰鍖�</b>锛屽叾瀹冩ā鍧楀彧鑳戒緷璧栬繖涓寘锛�</li>
+ *   <li>{@code controller} / {@code service} / {@code mapper} / {@code domain} 鈥斺�� 妯″潡鍐呴儴瀹炵幇锛�
+ *       鍏跺畠妯″潡涓嶅簲鐩存帴寮曠敤銆�</li>
+ * </ul>
+ * 妯″潡鍙�氳繃 {@code sys_config} 鍙傛暟琛ㄧ殑 {@code satisfaction.enabled=false} 鏁翠綋鍏抽棴
+ * 锛堣繍琛屾椂璇诲彇锛屾敼瀹屾渶澶� 10 绉掔敓鏁堬級锛涘叧闂悗
+ * {@code com.ruoyi.satisfaction.api.SatisfactionFacade} 浠嶅瓨鍦紝浣嗘墍鏈夎皟鐢ㄩ檷绾т负绌哄��/鍏ㄩ浂锛�
+ * 鐩存帴璁块棶婊℃剰搴︾鐐圭殑璇锋眰浼氳鎷︽埅鍣ㄦ嫤涓嬪苟杩斿洖鏄庣‘鐨�"鏈惎鐢�"涓氬姟閿欒銆�
+ */
+public final class SatisfactionModule {
+
+    /**
+     * 妯″潡閰嶇疆鍓嶇紑锛堜粎浣滀负 yml 鍏滃簳閰嶇疆鐨勯敭鍓嶇紑锛涗富寮�鍏冲湪 sys_config 鍙傛暟琛ㄩ噷锛�
+     */
+    public static final String PROPERTIES_PREFIX = "satisfaction";
+
+    private SatisfactionModule() {
+    }
+}
diff --git a/ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/api/DefaultSatisfactionApi.java b/ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/api/DefaultSatisfactionApi.java
new file mode 100644
index 0000000..8d346bd
--- /dev/null
+++ b/ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/api/DefaultSatisfactionApi.java
@@ -0,0 +1,80 @@
+package com.ruoyi.satisfaction.api;
+
+import com.ruoyi.satisfaction.domain.ServiceSubtaskDetailRatioExport;
+import com.ruoyi.satisfaction.service.SatisfactionSubtaskService;
+import com.ruoyi.satisfaction.support.MydOptionRatioChartBuilder;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * {@link SatisfactionApi} 鐨勫唴閮ㄥ疄鐜帮紙<b>甯搁┗ Bean</b>锛夈��
+ * <p>
+ * 寮�鍏冲凡鏀逛负瀛樻斁鍦� {@code sys_config} 鍙傛暟琛ㄧ殑 {@code satisfaction.enabled}锛�
+ * <b>杩愯鏃剁儹璇�</b>锛屽洜姝ゆ湰 Bean 鏃犳潯浠跺垱寤猴細
+ * <ul>
+ *   <li>鍙傛暟涓� {@code true}锛堥粯璁わ級鈫� {@link SatisfactionFacade} 姝e父杞彂鍒版湰瀹炵幇锛�</li>
+ *   <li>鍙傛暟涓� {@code false} 鈫� 鏈� Bean 浠嶇劧瀛樺湪锛屼絾闂ㄩ潰鍦�
+ *       {@link com.ruoyi.satisfaction.config.SatisfactionSwitch} 鍒ゆ柇涓哄叧闂悗
+ *       涓嶅啀杞彂锛岃�屾槸杩斿洖瀹夊叏绌哄��/鍏ㄩ浂銆�</li>
+ * </ul>
+ * 涔嬫墍浠ヤ笉鍐嶇敤 {@code @ConditionalOnProperty}锛氶偅涓敞瑙e彧鍦�<b>鍚姩鏃�</b>姹傚�间竴娆★紝
+ * 涓�"鏀瑰畬鍙傛暟閰嶇疆鍗虫椂鐢熸晥"鐨勮瘔姹傚啿绐併��
+ * <p>
+ * 璋冪敤鏂�<b>涓嶈</b>鐩存帴娉ㄥ叆鏈被锛屽簲娉ㄥ叆 {@link SatisfactionFacade}銆�
+ */
+@Slf4j
+@Component
+public class DefaultSatisfactionApi implements SatisfactionApi {
+
+    private final SatisfactionSubtaskService satisfactionSubtaskService;
+
+    public DefaultSatisfactionApi(SatisfactionSubtaskService satisfactionSubtaskService) {
+        this.satisfactionSubtaskService = satisfactionSubtaskService;
+    }
+
+    @Override
+    public boolean isEnabled() {
+        return true;
+    }
+
+    @Override
+    public Map<String, Object> getSatisfactionCategories(String orgid) {
+        return satisfactionSubtaskService.getSatisfactionCategories(orgid);
+    }
+
+    @Override
+    public SatisfactionExportBundle getQuestionOptionRatioExport(List<Long> subtaskIds, String questiontext) {
+        List<ServiceSubtaskDetailRatioExport> rows =
+                satisfactionSubtaskService.statMydQuestionOptionForExport(subtaskIds, questiontext);
+        if (rows == null || rows.isEmpty()) {
+            // 娌℃湁琛屾椂涓嶇粰鍥捐〃鏋勫缓鍣細ExcelUtil 浼氳烦杩囪 Sheet锛堜笌鍘熷疄鐜�"绌烘暟鎹笉鐢诲浘"涓�鑷达級
+            return new SatisfactionExportBundle(rows, ServiceSubtaskDetailRatioExport.class, null);
+        }
+        return new SatisfactionExportBundle(rows, ServiceSubtaskDetailRatioExport.class,
+                new MydOptionRatioChartBuilder(rows));
+    }
+
+    @Override
+    public boolean hasSatisfactionCategory(String orgid) {
+        Map<String, Object> categories = satisfactionSubtaskService.getSatisfactionCategories(orgid);
+        if (categories == null || categories.isEmpty()) {
+            return false;
+        }
+        Object voice = categories.get("voiceCategories");
+        Object questionnaire = categories.get("questionnaireCategorys");
+        return isNotEmpty(voice) || isNotEmpty(questionnaire);
+    }
+
+    private boolean isNotEmpty(Object value) {
+        return value instanceof java.util.Collection && !((java.util.Collection<?>) value).isEmpty();
+    }
+
+    @Override
+    public Map<String, Object> getSatisfactionIndex(String type, String code, String orgid,
+                                                    Long ivrCategoryid, Long svyCategoryid) {
+        return satisfactionSubtaskService.getSatisfactionIndex(type, code, orgid, ivrCategoryid, svyCategoryid);
+    }
+}
diff --git a/ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/api/SatisfactionApi.java b/ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/api/SatisfactionApi.java
new file mode 100644
index 0000000..310245b
--- /dev/null
+++ b/ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/api/SatisfactionApi.java
@@ -0,0 +1,87 @@
+package com.ruoyi.satisfaction.api;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 婊℃剰搴︽ā鍧�<b>瀵瑰鎺ュ彛</b>锛堝敮涓�鍏佽鍏跺畠妯″潡渚濊禆鐨勫叆鍙o級銆�
+ * <p>
+ * 璁捐绾﹀畾锛�
+ * <ul>
+ *   <li>鍙毚闇� DTO / 鍩烘湰绫诲瀷锛�<b>涓嶆毚闇�</b>婊℃剰搴︽ā鍧楀唴閮ㄧ殑瀹炰綋涓� Mapper锛�</li>
+ *   <li>鏈帴鍙g敱 {@link SatisfactionFacade} 缁熶竴杞嚭锛岄棬闈�<b>姘歌繙瀛樺湪</b>锛�
+ *       鍗充娇婊℃剰搴︽ā鍧楄鍏抽棴锛坽@code sys_config} 鍙傛暟 {@code satisfaction.enabled=false}锛�
+ *       涔熶笉浼氬鑷磋皟鐢ㄦ柟鍚姩澶辫触锛�</li>
+ *   <li>鍏抽棴鏃跺悇鏂规硶杩斿洖 {@code false} / 绌洪泦鍚� / {@code null}锛屽苟鎵� WARN 鏃ュ織锛�
+ *       <b>涓嶆姏寮傚父</b>锛屼互鍏嶅奖鍝嶈皟鐢ㄦ柟涓绘祦绋嬶紙濡傞殢璁垮鍑恒�佺櫥褰曪級銆�</li>
+ * </ul>
+ */
+public interface SatisfactionApi {
+
+    /**
+     * 婊℃剰搴︽ā鍧楁槸鍚﹀凡鍚敤
+     */
+    boolean isEnabled();
+
+    /**
+     * 鑾峰彇婊℃剰搴﹂棶棰樺垎绫伙紙璇煶 + 闂嵎锛夛紝甯︾紦瀛樸��
+     * <p>
+     * 渚涚櫥褰曢摼璺娇鐢紙鍘� {@code IUserExtInfoService.getSatisfactionCategories}锛夈��
+     * <p>
+     * 杩斿洖缁撴瀯锛堜笌杩佺Щ鍓嶅畬鍏ㄤ竴鑷达級锛�
+     * <pre>
+     * {
+     *   "voiceCategories":        [ {categoryid, categoryName}, ... ],
+     *   "questionnaireCategorys": [ {categoryid, categoryName}, ... ]
+     * }
+     * </pre>
+     *
+     * @param orgid 鏈烘瀯 id
+     * @return 鍒嗙被缁撴瀯锛涙ā鍧楀叧闂椂杩斿洖绌� Map
+     */
+    Map<String, Object> getSatisfactionCategories(String orgid);
+
+    /**
+     * 闂璇︽儏鍗犳瘮瀵煎嚭鍖咃紙渚涢殢璁垮鍑轰娇鐢級銆�
+     * <p>
+     * 瀵瑰簲鍘� {@code IServiceSubtaskService.statMydQuestionOptionForExport}銆�
+     * <p>
+     * 杩斿洖 {@link SatisfactionExportBundle}锛氳鍒楄〃 + 琛岀被鍨� + 鍥捐〃鏋勫缓鍣ㄤ竴娆$粰榻愶紝
+     * 璋冪敤鏂癸紙闅忚瀵煎嚭锛夊洜姝�<b>鍙渶 import api 鍖�</b>鍗冲彲鎶婄粨鏋滀氦缁�
+     * {@code ExcelUtil.exportExcelTwoSheetWithChart}锛�
+     * 涓嶉渶瑕佹帴瑙︽湰妯″潡鐨� {@code domain} / {@code support} 鍖呫��
+     *
+     * @param subtaskIds   鏈瀵煎嚭鐨勫瓙浠诲姟 id
+     * @param questiontext 棰樼洰鍐呭锛堝彲閫夛紝妯$硦鍖归厤锛�
+     * @return 瀵煎嚭鍖咃紱妯″潡鍏抽棴鏃惰繑鍥� {@link SatisfactionExportBundle#empty()}锛堝鍑虹収甯稿畬鎴愶紝鍙槸娌℃湁鍗犳瘮 Sheet锛�
+     */
+    SatisfactionExportBundle getQuestionOptionRatioExport(List<Long> subtaskIds, String questiontext);
+
+    /**
+     * 鍒ゆ柇璇ユ満鏋勬槸鍚﹀瓨鍦ㄦ弧鎰忓害閰嶇疆锛堝彲閫夎兘鍔涳紝渚涜皟鐢ㄦ柟鍋氬墠缃牎楠岋級
+     *
+     * @param orgid 鏈烘瀯 id
+     * @return 鏈夎闊虫垨闂嵎婊℃剰搴﹀垎绫绘椂杩斿洖 true
+     */
+    boolean hasSatisfactionCategory(String orgid);
+
+    /**
+     * 闅忚缁熻椤电殑銆屾弧鎰忓害鎸囨暟銆嶏細婊℃剰搴﹂鐩�婚噺 / 濉姤閲� / 瀹屾垚姣斾緥銆�
+     * <p>
+     * 瀵瑰簲鍘� {@code IServiceSubtaskService#getSfStatisticsJoy} 鍐呴儴绉佹湁鐨�
+     * {@code getSatisfactionIndex}锛堢幇宸叉惉鍏ユ湰妯″潡锛夈��
+     * <p>
+     * 鈿狅笍 鍑轰簬"涓嶆敼琛屼负"鐨勮姹傦紝琚縼绉诲疄鐜伴噷鐨勫師缂洪櫡<b>鍘熸牱淇濈暀</b>锛�
+     * 鏈柟娉曟亽瀹氳繑鍥� {@code {mydtmzl: 0, mydtbl: 0, wcbl: 0.0}}銆�
+     * 鍥犳妯″潡鍏抽棴鏃惰繑鍥炲悓鏍风殑鍏ㄩ浂 Map锛岃皟鐢ㄦ柟寰楀埌鐨勬樉绀虹粨鏋滀笌寮�鍚椂涓�鑷淬��
+     *
+     * @param type          "1"=鎸夌瀹わ紝"2"=鎸夌梾鍖�
+     * @param code          绉戝/鐥呭尯缂栫爜
+     * @param orgid         鏈烘瀯 id
+     * @param ivrCategoryid 璇煶婊℃剰搴﹀垎绫� id锛堝彲涓� null锛�
+     * @param svyCategoryid 闂嵎婊℃剰搴﹀垎绫� id锛堝彲涓� null锛�
+     * @return {@code mydtmzl}(Integer) / {@code mydtbl}(Integer) / {@code wcbl}(Double)
+     */
+    Map<String, Object> getSatisfactionIndex(String type, String code, String orgid,
+                                            Long ivrCategoryid, Long svyCategoryid);
+}
diff --git a/ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/api/SatisfactionExportBundle.java b/ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/api/SatisfactionExportBundle.java
new file mode 100644
index 0000000..a463706
--- /dev/null
+++ b/ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/api/SatisfactionExportBundle.java
@@ -0,0 +1,82 @@
+package com.ruoyi.satisfaction.api;
+
+import com.ruoyi.common.utils.poi.ExcelUtil;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * 銆岄棶棰樿鎯呭崰姣斻�嶅鍑哄寘锛堟弧鎰忓害妯″潡瀵瑰鏆撮湶鐨勫鍑鸿浇浣擄級銆�
+ * <p>
+ * <b>涓轰粈涔堥渶瑕佸畠</b>锛氶殢璁垮鍑猴紙{@code ServiceSubtaskController#patItemExport}锛夐渶瑕佹妸婊℃剰搴﹀崰姣旇
+ * 浜ょ粰 {@code ExcelUtil.exportExcelTwoSheetWithChart(...)}锛岃�岃繖涓柟娉曡姹傚悓鏃剁粰鍑�
+ * 銆岃鍒楄〃 + 琛岀被鍨� {@code Class} + 鍥捐〃鏋勫缓鍣ㄣ�嶃�傚鏋滆璋冪敤鏂硅嚜宸辨嬁杩欎笁鏍凤紝
+ * 瀹冨氨蹇呴』 import 婊℃剰搴︽ā鍧楀唴閮ㄧ殑 {@code domain} 涓� {@code support} 鍖咃紝
+ * 杩濆弽銆屽叾瀹冩ā鍧楀彧鑳界敤鏈ā鍧楁毚闇茬殑 api銆嶇殑绾︽潫銆�
+ * <p>
+ * 鍥犳杩欓噷鎶婁笁鏍蜂笢瑗挎墦鍖呮垚涓�涓� <b>api 灞傜殑鍊煎璞�</b>锛�
+ * <ul>
+ *   <li>{@link #getRows()} 鈥斺�� 鍗犳瘮琛岋紙璋冪敤鏂瑰彧褰撲綔 {@code List<?>} 閫忎紶锛屼笉闇�瑕佺煡閬撳叿浣撶被鍨嬶級锛�</li>
+ *   <li>{@link #getRowClass()} 鈥斺�� 琛岀被鍨嬶紙鍚屾牱鍙槸閫忎紶缁� {@code ExcelUtil}锛夛紱</li>
+ *   <li>{@link #getChartBuilder()} 鈥斺�� 鏌辩姸鍥炬瀯寤哄櫒锛堜互 ruoyi-common 鐨�
+ *       {@link ExcelUtil.ChartSheetBuilder} 鎺ュ彛绫诲瀷鏆撮湶锛屽疄鐜板湪妯″潡鍐呴儴锛�
+ *       妯″潡鍏抽棴鏃朵负 {@code null}锛夈��</li>
+ * </ul>
+ * 璋冪敤鏂瑰彧闇� import 鏈被涓� {@link SatisfactionFacade} 鍗冲彲瀹屾垚鏁翠釜瀵煎嚭銆�
+ */
+public class SatisfactionExportBundle {
+
+    /**
+     * 妯″潡鍏抽棴鏃朵娇鐢ㄧ殑绌哄鍑哄寘锛堟棤琛屻�佹棤鍥捐〃鏋勫缓鍣級
+     */
+    private static final SatisfactionExportBundle EMPTY =
+            new SatisfactionExportBundle(Collections.emptyList(), null, null);
+
+    private final List<?> rows;
+
+    private final Class<?> rowClass;
+
+    private final ExcelUtil.ChartSheetBuilder chartBuilder;
+
+    public SatisfactionExportBundle(List<?> rows, Class<?> rowClass, ExcelUtil.ChartSheetBuilder chartBuilder) {
+        this.rows = rows == null ? Collections.emptyList() : rows;
+        this.rowClass = rowClass;
+        this.chartBuilder = chartBuilder;
+    }
+
+    /**
+     * 妯″潡鍏抽棴鏃剁殑绌哄寘锛堣鍒楄〃涓虹┖銆佽绫诲瀷涓庡浘琛ㄦ瀯寤哄櫒涓� null锛夈��
+     * <p>閰嶅悎 {@code ExcelUtil} 鐨�"琛屽垪琛ㄤ负绌哄垯璺宠繃璇� Sheet"琛屼负锛屽鍑哄彲浠ョ収甯稿畬鎴愩��
+     */
+    public static SatisfactionExportBundle empty() {
+        return EMPTY;
+    }
+
+    /**
+     * 鍗犳瘮琛岋紙璋冪敤鏂瑰彧浣滈�忎紶锛屼笉寤鸿鑷瑙f瀽鍏跺瓧娈佃涔夛級
+     */
+    public List<?> getRows() {
+        return rows;
+    }
+
+    /**
+     * 鍗犳瘮琛岀殑绫诲瀷锛堥�忎紶缁� {@code ExcelUtil} 鐢紱妯″潡鍏抽棴鏃朵负 null锛�
+     */
+    public Class<?> getRowClass() {
+        return rowClass;
+    }
+
+    /**
+     * 鏌辩姸鍥� Sheet 鏋勫缓鍣紙閫忎紶缁� {@code ExcelUtil} 鐢紱鏃犳暟鎹垨妯″潡鍏抽棴鏃朵负 null锛�
+     */
+    public ExcelUtil.ChartSheetBuilder getChartBuilder() {
+        return chartBuilder;
+    }
+
+    /**
+     * 鏄惁娌℃湁鍙鍑虹殑琛�
+     */
+    public boolean isEmpty() {
+        return rows.isEmpty();
+    }
+}
diff --git a/ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/api/SatisfactionFacade.java b/ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/api/SatisfactionFacade.java
new file mode 100644
index 0000000..3447307
--- /dev/null
+++ b/ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/api/SatisfactionFacade.java
@@ -0,0 +1,222 @@
+package com.ruoyi.satisfaction.api;
+
+import com.ruoyi.common.core.service.ISatisfactionCategoryService;
+import com.ruoyi.satisfaction.config.SatisfactionProperties;
+import com.ruoyi.satisfaction.config.SatisfactionSwitch;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.ObjectProvider;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 婊℃剰搴︽ā鍧楀澶栭棬闈紙<b>甯搁┗ Bean</b>锛夈��
+ * <p>
+ * 杩欐槸鍏跺畠妯″潡璁块棶婊℃剰搴﹁兘鍔涚殑<b>鍞竴鍏ュ彛</b>銆�
+ * <p>
+ * 涓� {@link SatisfactionApi} 瀹炵幇绫荤殑鍏崇郴锛�
+ * <ul>
+ *   <li>瀹炵幇绫� {@link DefaultSatisfactionApi} <b>甯搁┗</b>锛堜笉鍐嶅彈鍚姩鏈熸潯浠惰閰嶆帶鍒讹級锛�</li>
+ *   <li>寮�鍏冲瓨鏀惧湪 {@code sys_config} 鍙傛暟琛ㄧ殑 {@code satisfaction.enabled}锛�
+ *       鐢� {@link SatisfactionSwitch} <b>杩愯鏃�</b>璇诲彇锛�10 绉掕繘绋嬪唴缂撳瓨锛夛紝
+ *       鏀瑰畬鍙傛暟璁剧疆鏃犻渶閲嶅惎锛�</li>
+ *   <li>鏈棬闈�<b>姘歌繙</b>鏄� Spring Bean锛屽洜姝よ皟鐢ㄦ柟锛堥殢璁垮鍑恒�佺櫥褰曢摼璺級
+ *       鐨� {@code @Autowired} 鍦ㄤ换浣曞紑鍏崇姸鎬佷笅閮�<b>涓嶄細鍚姩澶辫触</b>锛�</li>
+ *   <li>鍏抽棴鏃跺悇鏂规硶杩斿洖澶辫触/绌哄�煎苟鎵� WARN锛�<b>涓嶆姏寮傚父</b>锛�
+ *       淇濊瘉璋冪敤鏂逛富娴佺▼锛堝瀵煎嚭銆佺櫥褰曪級鐓у父瀹屾垚銆�</li>
+ * </ul>
+ *
+ * <h3>浣跨敤绀轰緥</h3>
+ * <pre>
+ * &#64;Autowired
+ * private SatisfactionFacade satisfactionFacade;
+ *
+ * if (satisfactionFacade.isEnabled()) {
+ *     Map&lt;String, Object&gt; categories = satisfactionFacade.getSatisfactionCategories(orgid);
+ * }
+ * </pre>
+ */
+@Slf4j
+@Component
+public class SatisfactionFacade implements SatisfactionApi, ISatisfactionCategoryService {
+
+    private final SatisfactionProperties properties;
+
+    /**
+     * 杩愯鏃跺紑鍏筹紙{@code sys_config} 浼樺厛锛寋@link SatisfactionProperties} 鍏滃簳锛�
+     */
+    private final SatisfactionSwitch satisfactionSwitch;
+
+    /**
+     * 鍐呴儴瀹炵幇锛涙ā鍧楀叧闂椂涓� {@code null}銆�
+     * <p>
+     * 鐢� {@code ObjectProvider} 鎯版�ц幏鍙栵細鍗充娇瀹炵幇 Bean 涓嶅瓨鍦ㄤ篃涓嶄細瀵艰嚧娉ㄥ叆澶辫触銆�
+     */
+    private final ObjectProvider<SatisfactionApi> apiProvider;
+
+    /**
+     * Spring 浣跨敤鐨勬瀯閫犲櫒銆�
+     * <p>
+     * 鈿狅笍 <b>蹇呴』鏄惧紡鏍囨敞 {@code @Autowired}</b>锛氭湰绫绘湁涓や釜鍏紑鏋勯�犲櫒
+     * 锛堝彟涓�涓槸缁欐祴璇�/闈� Spring 鐜鐢ㄧ殑锛夛紝鑻ヤ笉鏍囨敞锛孲pring 浼氬洜涓�"鏋勯�犲櫒涓嶅敮涓�"
+     * 鑰岄��鍥炴煡鎵炬棤鍙傛瀯閫犲櫒锛屼粠鑰屽湪鍚姩鏃舵姤
+     * {@code No default constructor found}銆傚凡鐢�
+     * {@code sql/_probe/verify_spring_beans.py} 闈欐�佹妸鍏炽��
+     */
+    @Autowired
+    public SatisfactionFacade(SatisfactionProperties properties,
+                              SatisfactionSwitch satisfactionSwitch,
+                              ObjectProvider<SatisfactionApi> apiProvider) {
+        this.properties = properties;
+        this.satisfactionSwitch = satisfactionSwitch;
+        this.apiProvider = apiProvider;
+    }
+
+    /**
+     * 渚�<b>娴嬭瘯/闈� Spring 鐜</b>鐩存帴鏋勯�狅紙鐩存帴缁欏嚭鍐呴儴瀹炵幇锛屽彲涓� null锛夈��
+     * <p>
+     * 寮�鍏冲彇鑷� {@code sys_config} 鐨勬ā鎷熼敭鍊硷紙瑙� {@link SatisfactionSwitch#SatisfactionSwitch}锛夛紝
+     * 浠庤�屼繚璇�"寮�鍏虫牎楠�"鑴氭湰鏃犻渶 Spring/鏁版嵁搴撳嵆鍙繍琛屻��
+     */
+    public SatisfactionFacade(SatisfactionProperties properties, SatisfactionApi delegate, String sysConfigValue) {
+        this(properties, new SatisfactionSwitch(properties, sysConfigValue), new FixedProvider(delegate));
+    }
+
+    /**
+     * 鍙栬繍琛屾椂寮�鍏筹紙渚涙嫤鎴櫒澶嶇敤鍚屼竴涓疄渚嬨�佷篃渚涙祴璇曟柇瑷�锛�
+     */
+    public SatisfactionSwitch getSatisfactionSwitch() {
+        return satisfactionSwitch;
+    }
+
+    /**
+     * 鍙栧唴閮ㄥ疄鐜帮紱妯″潡鍏抽棴銆佸疄鐜扮己澶便�佹垨鍙壘鍒拌嚜宸辨椂杩斿洖 {@code null}銆�
+     * <p>
+     * 鈿狅笍 <b>蹇呴』鎺掗櫎"鑷繁"</b>锛氭湰绫昏嚜韬篃瀹炵幇浜� {@link SatisfactionApi}锛岃�� Spring 鍦�
+     * 鎸夌被鍨嬫煡鎵惧�欓�� Bean 鏃讹紝鑻ョ涓�杞病鎵惧埌鍒殑瀹炵幇锛堜緥濡� ruoyi-satisfaction
+     * 娌℃湁琚墦杩涘簲鐢紝鎴栧皢鏉ュ張鏈変汉鐢ㄦ潯浠惰閰嶆妸瀹冩憳鎺夛級锛屼細<b>鎶婅嚜寮曠敤褰撲綔鍏滃簳鍊欓��</b>杩斿洖銆�
+     * 閭f牱 {@code delegateOrNull()} 浼氭嬁鍒� {@code this}锛岄殢鍚�
+     * {@code getSatisfactionCategories()} 閲� {@code delegate.getXxx()} 灏卞彉鎴愭棤闄愰�掑綊
+     * 锛圫tackOverflowError锛夈�傝繖閲屾樉寮忚繃婊ゆ帀鑷繁锛屾妸"娌℃湁瀹炵幇"濡傚疄瑙嗕负涓嶅彲鐢ㄣ��
+     * <p>
+     * 鈿狅笍 <b>鏈柟娉曚笉鑳芥浛浠e紑鍏冲垽鏂�</b>锛氬紑鍏筹紙{@code sys_config: satisfaction.enabled}锛�
+     * 鍏抽棴鏃� {@link DefaultSatisfactionApi} 渚濈劧瀛樺湪锛屾墍浠ヨ皟鐢ㄧ偣涓�寰嬪厛鍒� {@link #isEnabled()}銆�
+     * <p>
+     * 鐢� {@code stream()} 鑰屼笉鏄� {@code getIfAvailable()}锛氬悗鑰呭湪鍊欓�夊浜庝竴涓椂鎶�
+     * {@code NoUniqueBeanDefinitionException}锛堝畠缁ф壙鑷� {@code NoSuchBeanDefinitionException}锛夛紝
+     * 鏈�缁堜細琚悶鎴� null锛屾帺鐩�"鏈変汉鍙堝姞浜嗕竴涓疄鐜�"杩欑閰嶇疆闂銆�
+     */
+    private SatisfactionApi delegateOrNull() {
+        if (apiProvider == null) {
+            return null;
+        }
+        try {
+            return apiProvider.stream()
+                    .filter(api -> api != this)
+                    .findFirst()
+                    .orElse(null);
+        } catch (Exception e) {
+            // 瀹瑰櫒鐘舵�佸紓甯告椂鎸�"鏈惎鐢�"澶勭悊锛岀粷涓嶈璋冪敤鏂癸紙鐧诲綍銆侀殢璁垮鍑猴級鍥犱负婊℃剰搴﹁�屽け璐�
+            log.warn("銆愭弧鎰忓害銆戣幏鍙栧唴閮ㄥ疄鐜板け璐ワ紝鎸夋湭鍚敤澶勭悊锛歿}", e.toString());
+            return null;
+        }
+    }
+
+    @Override
+    public boolean isEnabled() {
+        return satisfactionSwitch.isEnabled() && delegateOrNull() != null;
+    }
+
+    @Override
+    public Map<String, Object> getSatisfactionCategories(String orgid) {
+        if (!isEnabled()) {
+            log.warn("銆愭弧鎰忓害銆戞ā鍧楁湭鍚敤锛堝弬鏁伴厤缃� satisfaction.enabled=false锛夛紝getSatisfactionCategories 杩斿洖绌虹粨鏋溿�俹rgid={}", orgid);
+            return Collections.emptyMap();
+        }
+        return delegateOrNull().getSatisfactionCategories(orgid);
+    }
+
+    @Override
+    public SatisfactionExportBundle getQuestionOptionRatioExport(List<Long> subtaskIds, String questiontext) {
+        if (!isEnabled()) {
+            log.warn("銆愭弧鎰忓害銆戞ā鍧楁湭鍚敤锛堝弬鏁伴厤缃� satisfaction.enabled=false锛夛紝getQuestionOptionRatioExport 杩斿洖绌哄鍑哄寘銆俿ubtaskIds 鏁伴噺={}",
+                    subtaskIds == null ? 0 : subtaskIds.size());
+            return SatisfactionExportBundle.empty();
+        }
+        return delegateOrNull().getQuestionOptionRatioExport(subtaskIds, questiontext);
+    }
+
+    @Override
+    public boolean hasSatisfactionCategory(String orgid) {
+        if (!isEnabled()) {
+            return false;
+        }
+        return delegateOrNull().hasSatisfactionCategory(orgid);
+    }
+
+    @Override
+    public Map<String, Object> getSatisfactionIndex(String type, String code, String orgid,
+                                                    Long ivrCategoryid, Long svyCategoryid) {
+        if (!isEnabled()) {
+            log.warn("銆愭弧鎰忓害銆戞ā鍧楁湭鍚敤锛堝弬鏁伴厤缃� satisfaction.enabled=false锛夛紝getSatisfactionIndex 杩斿洖鍏ㄩ浂缁撴灉銆倀ype={}, code={}",
+                    type, code);
+            // 杩斿洖涓�"鍚敤鏃�"瀹屽叏鐩稿悓鐨勫叏闆� Map锛堣杩佺Щ瀹炵幇鍥犲師缂洪櫡涔熸亽瀹氳繑鍥炲叏闆讹級锛�
+            // 杩欐牱闅忚缁熻椤靛湪妯″潡鍏抽棴鍓嶅悗鐨勬樉绀虹粨鏋滀竴鑷淬��
+            return emptySatisfactionIndex();
+        }
+        return delegateOrNull().getSatisfactionIndex(type, code, orgid, ivrCategoryid, svyCategoryid);
+    }
+
+    /**
+     * 鍏ㄩ浂鐨勬弧鎰忓害鎸囨暟锛堥敭鍚嶄笌绫诲瀷涓庤杩佺Щ瀹炵幇涓�鑷达細{@code mydtmzl}(Integer)銆�
+     * {@code mydtbl}(Integer)銆亄@code wcbl}(Double)锛�
+     */
+    public static Map<String, Object> emptySatisfactionIndex() {
+        Map<String, Object> map = new HashMap<>();
+        map.put("mydtmzl", 0);
+        map.put("mydtbl", 0);
+        map.put("wcbl", 0.0);
+        return map;
+    }
+
+    /**
+     * 鍥哄畾鐨� ObjectProvider 瀹炵幇锛氫粎鐢ㄤ簬闈� Spring 鐜/娴嬭瘯銆�
+     */
+    private static final class FixedProvider implements ObjectProvider<SatisfactionApi> {
+
+        private final SatisfactionApi api;
+
+        private FixedProvider(SatisfactionApi api) {
+            this.api = api;
+        }
+
+        @Override
+        public SatisfactionApi getObject(Object... args) {
+            return api;
+        }
+
+        @Override
+        public SatisfactionApi getIfAvailable() {
+            return api;
+        }
+
+        @Override
+        public SatisfactionApi getIfUnique() {
+            return api;
+        }
+
+        @Override
+        public SatisfactionApi getObject() {
+            return api;
+        }
+
+        @Override
+        public java.util.stream.Stream<SatisfactionApi> stream() {
+            return api == null ? java.util.stream.Stream.empty() : java.util.stream.Stream.of(api);
+        }
+    }
+}
diff --git a/ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/config/SatisfactionAvailabilityInterceptor.java b/ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/config/SatisfactionAvailabilityInterceptor.java
new file mode 100644
index 0000000..4ce8f15
--- /dev/null
+++ b/ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/config/SatisfactionAvailabilityInterceptor.java
@@ -0,0 +1,65 @@
+package com.ruoyi.satisfaction.config;
+
+import com.ruoyi.common.exception.ServiceException;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+import org.springframework.web.servlet.HandlerInterceptor;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * 婊℃剰搴︽ā鍧楀紑鍏虫嫤鎴櫒銆�
+ * <p>
+ * <b>浣滅敤</b>锛氬綋寮�鍏冲叧闂紙{@code sys_config} 涓� {@code satisfaction.enabled=false}锛夋椂锛�
+ * 鍛戒腑婊℃剰搴︾鐐圭殑璇锋眰浼氳绔嬪嵆鎷︿笅骞舵姏鍑� {@link ServiceException}锛�
+ * 鐢� RuoYi 鐨勫叏灞�寮傚父澶勭悊鍣ㄨ繑鍥�<b>鏄庣‘鐨勪笟鍔¢敊璇彁绀�</b>锛堣�屼笉鏄� 404 鎴栭潤榛樿繑鍥炵┖鏁版嵁锛夈��
+ * <p>
+ * 寮�鍏冲�肩敱 {@link SatisfactionSwitch} 鍦ㄨ繍琛屾椂璇诲彇锛坽@code sys_config}锛�10 绉掕繘绋嬪唴缂撳瓨锛夛紝
+ * 鍥犳鏀瑰畬鍙傛暟璁剧疆<b>涓嶉渶瑕侀噸鍚�</b>锛屾渶澶� 10 绉掑嵆鐢熸晥銆�
+ * <p>
+ * <b>涓轰粈涔堜笉鐢� {@code @ConditionalOnProperty} 璁� Controller 鐩存帴涓嶆敞鍐�</b>锛�
+ * <ul>
+ *   <li>閭f牱璋冪敤鏂瑰彧浼氭嬁鍒� HTTP 404 鈥斺�� 铏界劧涔熸槸澶辫触锛屼絾涓嶆槸"鏄庣‘閿欒"锛�
+ *       鍓嶇/澶栭摼鏃犳硶鍖哄垎"妯″潡琚叧闂�"鍜�"鍦板潃鍐欓敊浜�"锛�</li>
+ *   <li>鑰屼笖 {@code @ConditionalOnProperty} 鍙湪<b>鍚姩鏃�</b>姹傚�间竴娆★紝
+ *       鍋氫笉鍒�"鏀瑰畬鍙傛暟閰嶇疆灏辩敓鏁�"锛屼笌鍙傛暟琛ㄥ紑鍏崇殑璇夋眰鍐茬獊銆�</li>
+ * </ul>
+ * <p>
+ * <b>涓轰粈涔堜笉浼氬奖鍝嶅叾瀹冧笟鍔′笌鍚姩</b>锛�
+ * <ul>
+ *   <li>鏈嫤鎴櫒鍙櫥璁板湪 <b>17 涓弧鎰忓害绔偣</b>鐨勭簿纭矾寰勪笂
+ *       锛堣 {@link SatisfactionWebConfig}锛夛紝涓嶄細纰板埌闅忚/闂嵎/鍏跺畠鎺ュ彛锛�</li>
+ *   <li>妯″潡鍏抽棴鏃讹紝Controller 涓� Service 渚濇棫鏄櫘閫� Bean锛堝惎鍔ㄤ笉鍙楀奖鍝嶏級锛�
+ *       鍏跺畠妯″潡閫氳繃 {@code SatisfactionFacade} 鍙栨暟鏃舵嬁鍒扮殑鏄畨鍏ㄧ┖鍊�/鍏ㄩ浂锛�
+ *       鍙湁"涓撻棬璋冩弧鎰忓害绔偣"鐨勮姹傛墠浼氭敹鍒伴敊璇��</li>
+ * </ul>
+ */
+@Slf4j
+@Component
+public class SatisfactionAvailabilityInterceptor implements HandlerInterceptor {
+
+    /**
+     * 妯″潡琚叧闂椂杩斿洖缁欒皟鐢ㄦ柟鐨勯敊璇彁绀猴紙鍓嶇鍙洿鎺ュ睍绀猴級
+     */
+    public static final String DISABLED_MESSAGE =
+            "婊℃剰搴﹀姛鑳芥湭鍚敤锛堝弬鏁伴厤缃� satisfaction.enabled=false锛夛紝璇ユ帴鍙d笉鍙敤";
+
+    private final SatisfactionSwitch satisfactionSwitch;
+
+    public SatisfactionAvailabilityInterceptor(SatisfactionSwitch satisfactionSwitch) {
+        this.satisfactionSwitch = satisfactionSwitch;
+    }
+
+    @Override
+    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
+        if (!satisfactionSwitch.isEnabled()) {
+            // request 鍏佽涓� null锛堝崟鍏冩祴璇曠洿鎺ヨ皟 preHandle 鏃朵笉鏋勯�犺姹傚璞★級
+            String method = request == null ? "-" : request.getMethod();
+            String uri = request == null ? "-" : request.getRequestURI();
+            log.warn("銆愭弧鎰忓害銆戞ā鍧楀凡鍏抽棴锛堝弬鏁伴厤缃� satisfaction.enabled=false锛夛紝鎷掔粷璁块棶 {} {}", method, uri);
+            throw new ServiceException(DISABLED_MESSAGE);
+        }
+        return true;
+    }
+}
diff --git a/ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/config/SatisfactionProperties.java b/ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/config/SatisfactionProperties.java
new file mode 100644
index 0000000..729deac
--- /dev/null
+++ b/ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/config/SatisfactionProperties.java
@@ -0,0 +1,49 @@
+package com.ruoyi.satisfaction.config;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
+
+/**
+ * 婊℃剰搴︽ā鍧楅厤缃紙鍓嶇紑 {@code satisfaction}锛夈��
+ * <p>
+ * 鈿狅笍 <b>杩欎笉鏄富寮�鍏�</b>銆傛弧鎰忓害妯″潡鐨勫紑鍏�<b>瀛樺湪 {@code sys_config} 鍙傛暟閰嶇疆琛ㄩ噷</b>锛�
+ * <pre>
+ * 鍙傛暟閿悕锛歴atisfaction.enabled
+ * 鍙傛暟閿�硷細true锛堝惎鐢紝榛樿锛�/ false锛堝叧闂級
+ * </pre>
+ * 瑙� {@link SatisfactionSwitch}銆傛湰绫荤殑 {@link #isEnabled()} 鍙綔涓�<b>鍏滃簳</b>锛�
+ * 褰� {@code sys_config} 閲屾病鏈夎繖涓�琛屻�佹垨闈� Spring 鐜鍙栦笉鍒伴厤缃湇鍔℃椂鎵嶄細鐢ㄥ埌銆�
+ * <p>
+ * 濡傞渶鍦� yml 涓厹搴曪紙涓�鑸笉蹇呰锛屾甯稿簲鍦ㄥ弬鏁拌缃〉缁存姢锛夛細
+ * <pre>
+ * satisfaction:
+ *   enabled: false
+ * </pre>
+ * <p>
+ * <b>鍏抽棴鍚庣殑琛屼负</b>锛�
+ * <ul>
+ *   <li>鏈ā鍧楃殑 <b>17 涓弧鎰忓害绔偣</b>锛坽@code /smartor/satisfaction/**}銆�
+ *       {@code /smartor/serviceSubtaskDetail} 涓嬬殑 5 涓�亄@code /smartor/trace/getSatisfactionExceptConfigQuestion}锛�
+ *       浠嶄細娉ㄥ唽锛屼絾璇锋眰浼氬湪杩涘叆鏂规硶鍓嶈
+ *       {@link SatisfactionAvailabilityInterceptor} 鎷︿笅骞舵姏鍑�
+ *       {@code ServiceException("婊℃剰搴﹀姛鑳芥湭鍚敤鈥�")} 鈥斺�� 璋冪敤鏂规嬁鍒扮殑鏄�
+ *       <b>鏄庣‘鐨勪笟鍔¢敊璇彁绀�</b>锛岃�屼笉鏄� 404锛屼究浜庡尯鍒�"妯″潡琚叧闂�"涓�"鍦板潃鍐欓敊"锛�</li>
+ *   <li>{@code com.ruoyi.satisfaction.api.SatisfactionFacade} 浠嶇劧瀛樺湪锛�
+ *       鍏跺畠妯″潡锛堥殢璁垮鍑恒�侀殢璁跨粺璁°�佺櫥褰曢摼璺級閫氳繃瀹冨彇鏁版椂浼氭嬁鍒板畨鍏ㄧ┖鍊�/鍏ㄩ浂锛�
+ *       <b>涓嶅奖鍝嶈皟鐢ㄦ柟鍚姩涓庝富娴佺▼</b>锛�</li>
+ *   <li>闅忚銆侀棶鍗枫�佺數璇濈瓑鍏跺畠涓氬姟瀹屽叏涓嶅彈褰卞搷锛堟嫤鎴櫒鍙寕鍦ㄤ笂杩� 17 涓矾寰勪笂锛夈��</li>
+ * </ul>
+ */
+@Data
+@Component
+@ConfigurationProperties(prefix = "satisfaction")
+public class SatisfactionProperties {
+
+    /**
+     * 鏄惁鍚敤婊℃剰搴︽ā鍧楋紙榛樿鍚敤锛夈��
+     * <p>
+     * 浠呭綋 {@code sys_config} 涓病鏈� {@code satisfaction.enabled} 鏃剁敓鏁堬紝瑙� {@link SatisfactionSwitch}銆�
+     */
+    private boolean enabled = true;
+}
diff --git a/ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/config/SatisfactionSwitch.java b/ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/config/SatisfactionSwitch.java
new file mode 100644
index 0000000..6603274
--- /dev/null
+++ b/ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/config/SatisfactionSwitch.java
@@ -0,0 +1,206 @@
+package com.ruoyi.satisfaction.config;
+
+import com.ruoyi.common.core.service.IConfigService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.ObjectProvider;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+/**
+ * 婊℃剰搴︽ā鍧�<b>鎬诲紑鍏�</b>锛氬�煎瓨鏀惧湪 {@code sys_config} 鍙傛暟閰嶇疆琛ㄤ腑锛�<b>杩愯鏃惰鍙栥�佸彲鐑垏鎹�</b>銆�
+ *
+ * <h3>鍙傛暟閰嶇疆</h3>
+ * <pre>
+ * 鍙傛暟閿悕锛歴atisfaction.enabled
+ * 鍙傛暟閿�硷細true  = 鍚敤锛堥粯璁わ級
+ *          false = 鍏抽棴
+ * </pre>
+ * 浣嶇疆锛氱郴缁熺鐞� 鈫� 鍙傛暟璁剧疆 鈫� 鏂板/淇敼銆�
+ * <pre>
+ * insert into sys_config (config_name, config_key, config_value, config_type, create_by, create_time, remark)
+ * values ('婊℃剰搴︽ā鍧楀紑鍏�', 'satisfaction.enabled', 'true', 'Y', 'admin', sysdate(), 'true鍚敤锛宖alse鍏抽棴锛堥粯璁rue锛�');
+ * </pre>
+ *
+ * <h3>鍙栧�间紭鍏堢骇</h3>
+ * <ol>
+ *   <li>{@code sys_config} 閲� {@code satisfaction.enabled} 鐨勫�硷紙<b>涓荤敤</b>锛�
+ *       鏉ヨ嚜鏈」鐩� {@code IConfigService} 鎺ュ彛锛屽疄鐜板湪 ruoyi-system 鐨� {@code SysConfigServiceImpl}锛夛紱</li>
+ *   <li>{@link SatisfactionProperties#isEnabled()}锛�<b>鍏滃簳</b>锛氫粎褰� sys_config 閲屾病鏈夎繖涓�琛屻��
+ *       鎴栭潪 Spring 鐜鍙栦笉鍒伴厤缃湇鍔℃椂浣跨敤锛岄粯璁� true锛夈��</li>
+ * </ol>
+ *
+ * <h3>涓轰粈涔堥粯璁� true</h3>
+ * 鍘嗗彶琛屼负灏辨槸"榛樿寮�鍚�"銆傚弬鏁拌缂哄け鏃舵寜 true 澶勭悊锛屼繚璇佸崌绾у埌鏈増鏈悗
+ * 婊℃剰搴﹀姛鑳戒笉浼氬洜涓哄皯浜嗕竴琛岄厤缃�岀獊鐒朵笉鍙敤锛堣鍏抽棴蹇呴』鏄惧紡閰嶇疆 false锛夈��
+ *
+ * <h3>涓轰粈涔堣涓嶅埌閰嶇疆鏃朵篃鎸� true锛坒ail-open锛�</h3>
+ * 璇� {@code sys_config} 闇�瑕佽蛋鏁版嵁搴擄紝鏁版嵁搴撴姈鍔ㄦ椂濡傛灉 fail-closed 浼氭妸鏁翠釜婊℃剰搴︿笟鍔℃墦姝汇��
+ * 杩欓噷閫夋嫨涓�"鍙傛暟琛岀己澶�"涓�鑷寸殑澶勭悊锛氬彧鎵� WARN 骞舵斁琛岋紝瀹佸彲澶氬紑涓�浼氬効锛屼篃涓嶅仛鏁版嵁搴撴姈鍔ㄦ斁澶у櫒銆�
+ *
+ * <h3>涓轰粈涔堟湁 10 绉掔紦瀛�</h3>
+ * {@code SysConfigServiceImpl#selectConfigByKey} 褰撳墠<b>姣忔閮芥煡搴�</b>锛圧edis 缂撳瓨閭f琚敞閲婃帀浜嗭級锛�
+ * 鑰屾湰寮�鍏冲湪姣忔婊℃剰搴﹁姹傘�佹瘡娆$櫥褰曞彇鍒嗙被鏃堕兘浼氳璇诲埌銆傝繖閲屽姞涓�涓繘绋嬪唴 TTL 缂撳瓨锛�
+ * 鎶婃煡搴撻檷鍒�"姣忎釜瀹炰緥姣� 10 绉掓渶澶氫竴娆�"銆備唬浠锋槸鍙傛暟鏀瑰姩鏈�澶� 10 绉掔敓鏁堬紙澶氬疄渚嬮儴缃叉椂鍚勮嚜璁℃椂锛夈��
+ */
+@Slf4j
+@Component
+public class SatisfactionSwitch {
+
+    /**
+     * sys_config 閲岀殑鍙傛暟閿悕
+     */
+    public static final String CONFIG_KEY = "satisfaction.enabled";
+
+    /**
+     * 杩涚▼鍐呯紦瀛樻湁鏁堟湡锛堟绉掞級
+     */
+    private static final long CACHE_TTL_MILLIS = 10_000L;
+
+    private final SatisfactionProperties satisfactionProperties;
+
+    /**
+     * 鍙傛暟閰嶇疆鏈嶅姟锛坮uoyi-common 鐨勬帴鍙o紝瀹炵幇鍦� ruoyi-system锛夈��
+     * <p>
+     * 鐢� {@code ObjectProvider} 鎯版�у彇锛氭湰妯″潡鍙緷璧� ruoyi-common锛�
+     * 鍦ㄥ崟鍏冩祴璇�/鐙珛閮ㄧ讲绛�"瀹瑰櫒閲屾病鏈夊疄鐜�"鐨勫満鏅笅涓嶈兘鍥犳鍚姩澶辫触銆�
+     */
+    private final ObjectProvider<IConfigService> configServiceProvider;
+
+    /**
+     * 鏈�杩戜竴娆¤В鏋愮粨鏋滐紙鍚槸鍚︽潵鑷� sys_config锛�
+     */
+    private volatile Boolean cachedEnabled;
+
+    /**
+     * 鏈�杩戜竴娆¤В鏋愮殑鏃堕棿鎴�
+     */
+    private volatile long cachedAt;
+
+    /**
+     * Spring 浣跨敤鐨勬瀯閫犲櫒銆�
+     * <p>
+     * 鈿狅笍 <b>蹇呴』鏄惧紡鏍囨敞 {@code @Autowired}</b>锛氭湰绫绘湁涓や釜鍏紑鏋勯�犲櫒
+     * 锛堝彟涓�涓粰娴嬭瘯/闈� Spring 鐜鐢級锛屼笉鏍囨敞鐨勮瘽 Spring 浼氬洜"鏋勯�犲櫒涓嶅敮涓�"
+     * 閫�鍥炴煡鎵炬棤鍙傛瀯閫犲櫒锛屽惎鍔ㄥ嵆鎶� {@code No default constructor found}銆�
+     */
+    @Autowired
+    public SatisfactionSwitch(SatisfactionProperties satisfactionProperties,
+                              ObjectProvider<IConfigService> configServiceProvider) {
+        this.satisfactionProperties = satisfactionProperties;
+        this.configServiceProvider = configServiceProvider;
+    }
+
+    /**
+     * 渚�<b>娴嬭瘯/闈� Spring 鐜</b>鐩存帴鏋勯�狅細{@code configValue} 灏辨槸 sys_config 閲岀殑閿�硷紝
+     * 浼� {@code null} 琛ㄧず"鍙傛暟琛屼笉瀛樺湪"锛堟鏃跺洖钀藉埌 {@link SatisfactionProperties}锛夈��
+     */
+    public SatisfactionSwitch(SatisfactionProperties satisfactionProperties, final String configValue) {
+        this(satisfactionProperties, new FixedProvider(configValue));
+        // 闈� Spring 鐜涓嬫病鏈�"鏀归厤缃悗绛� 10 绉�"鐨勬蹇碉紝鐩存帴绂佺敤缂撳瓨锛屼繚璇佹柇瑷�鍗虫椂鍙
+        this.cachedEnabled = resolve();
+        this.cachedAt = System.currentTimeMillis() + CACHE_TTL_MILLIS * 1000L;
+    }
+
+    /**
+     * 婊℃剰搴﹀姛鑳芥槸鍚﹀惎鐢紙杩愯鏃剁儹璇伙紝鏈�澶� 10 绉掑欢杩燂級
+     */
+    public boolean isEnabled() {
+        long now = System.currentTimeMillis();
+        Boolean cached = this.cachedEnabled;
+        if (cached != null && now - this.cachedAt < CACHE_TTL_MILLIS) {
+            return cached;
+        }
+        boolean resolved = resolve();
+        this.cachedEnabled = resolved;
+        this.cachedAt = now;
+        return resolved;
+    }
+
+    /**
+     * 娓呮帀杩涚▼鍐呯紦瀛橈紝涓嬫璋冪敤绔嬪嵆閲嶆柊璇� sys_config锛堜緵娴嬭瘯/闇�瑕佺珛鍗崇敓鏁堢殑鍦烘櫙锛�
+     */
+    public void invalidateCache() {
+        this.cachedEnabled = null;
+    }
+
+    /**
+     * 瑙f瀽褰撳墠寮�鍏冲�硷細sys_config 浼樺厛锛屽叾娆� yml 鍏滃簳
+     */
+    private boolean resolve() {
+        String value = readFromSysConfig();
+        if (value != null) {
+            if (!"true".equalsIgnoreCase(value) && !"false".equalsIgnoreCase(value)) {
+                log.warn("銆愭弧鎰忓害銆憇ys_config 涓� {} 鐨勫�间笉鏄� true/false锛堝疄闄�=銆寋}銆嶏級锛屽皢鎸夈�屼笉鏄� false 鍗冲惎鐢ㄣ�嶅鐞�",
+                        CONFIG_KEY, value);
+            }
+            boolean enabled = !"false".equalsIgnoreCase(value);
+            log.debug("銆愭弧鎰忓害銆戝紑鍏冲彇鑷� sys_config锛歿}={} -> enabled={}", CONFIG_KEY, value, enabled);
+            return enabled;
+        }
+        boolean enabled = satisfactionProperties.isEnabled();
+        log.debug("銆愭弧鎰忓害銆憇ys_config 涓湭閰嶇疆 {}锛屽洖钀� satisfaction.enabled={}", CONFIG_KEY, enabled);
+        return enabled;
+    }
+
+    /**
+     * 璇� sys_config 鐨勫師濮嬮敭鍊硷紱娌℃湁瀹炵幇銆佹病鏈夎繖涓�琛屻�佹垨璇诲簱澶辫触閮借繑鍥� {@code null}
+     */
+    private String readFromSysConfig() {
+        if (configServiceProvider == null) {
+            return null;
+        }
+        try {
+            IConfigService configService = configServiceProvider.stream().findFirst().orElse(null);
+            if (configService == null) {
+                return null;
+            }
+            String value = configService.selectConfigByKey(CONFIG_KEY);
+            if (value == null || value.trim().isEmpty()) {
+                return null;
+            }
+            return value.trim();
+        } catch (Exception e) {
+            // 瀹瑰櫒娌℃湁瀹炵幇銆佹暟鎹簮寮傚父銆丼QL 鎶ラ敊鈥︹�︿竴寰嬫寜"鍙傛暟缂哄け"澶勭悊锛坒ail-open锛夛紝缁濅笉褰卞搷涓绘祦绋�
+            log.warn("銆愭弧鎰忓害銆戣鍙� sys_config 鐨� {} 澶辫触锛屾湰娆℃寜鏈厤缃鐞嗭紙fail-open锛夛細{}",
+                    CONFIG_KEY, e.toString());
+            return null;
+        }
+    }
+
+    /**
+     * 鍥哄畾鍊肩殑 {@code ObjectProvider}锛氬彧鐢ㄤ簬闈� Spring 鐜/娴嬭瘯
+     */
+    private static final class FixedProvider implements ObjectProvider<IConfigService> {
+
+        private final IConfigService configService;
+
+        private FixedProvider(final String configValue) {
+            this.configService = configValue == null ? null : configKey -> configValue;
+        }
+
+        @Override
+        public IConfigService getObject(Object... args) {
+            return configService;
+        }
+
+        @Override
+        public IConfigService getIfAvailable() {
+            return configService;
+        }
+
+        @Override
+        public IConfigService getIfUnique() {
+            return configService;
+        }
+
+        @Override
+        public IConfigService getObject() {
+            return configService;
+        }
+
+        @Override
+        public java.util.stream.Stream<IConfigService> stream() {
+            return configService == null ? java.util.stream.Stream.empty() : java.util.stream.Stream.of(configService);
+        }
+    }
+}
diff --git a/ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/config/SatisfactionWebConfig.java b/ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/config/SatisfactionWebConfig.java
new file mode 100644
index 0000000..d14eb7d
--- /dev/null
+++ b/ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/config/SatisfactionWebConfig.java
@@ -0,0 +1,81 @@
+package com.ruoyi.satisfaction.config;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * 婊℃剰搴︽ā鍧楃殑 MVC 閰嶇疆锛氭妸 {@link SatisfactionAvailabilityInterceptor} 鍙寕鍒�
+ * <b>21 涓弧鎰忓害鎺ュ彛涓睘浜庢湰妯″潡鐨� 17 涓鐐�</b>涓娿��
+ * <p>
+ * <b>涓轰粈涔堥�愪釜鍒楄矾寰勮�屼笉鏄敤 {@code /smartor/**} 涔嬬被鐨勫墠缂�</b>锛�
+ * <ul>
+ *   <li>{@code /smartor/serviceSubtaskDetail/**} 涓嬫棦鏈夋弧鎰忓害绔偣锛�5 涓級锛�
+ *       涔熸湁闅忚鏄庣粏绔偣锛堝叾瀹冿級锛岀敤鍓嶇紑浼氳浼わ紱</li>
+ *   <li>{@code /smartor/trace/**} 涓嬪彧鏈� 1 涓弧鎰忓害绔偣锛屽叾浣欐槸寮傚父璺熻釜涓氬姟锛�</li>
+ *   <li>鍥犳杩欓噷鏄惧紡鏋氫妇锛屼繚璇�"鍙嫤婊℃剰搴�"锛岀粷涓嶅奖鍝嶅叾瀹冧笟鍔°��</li>
+ * </ul>
+ * 鍙﹀ 4 涓娇鐢ㄦ弧鎰忓害鏁版嵁鐨勯殢璁挎帴鍙o紙{@code patItemExport} / {@code getSfStatisticsJoy} /
+ * {@code getSfStatisticsJoydetails} / {@code saveMYDQuestionAnswer}锛�<b>涓嶅湪杩欓噷</b>锛�
+ * 瀹冧滑灞炰簬闅忚涓氬姟锛屾ā鍧楀叧闂椂蹇呴』缁х画鍙敤锛堟弧鎰忓害閮ㄥ垎闄嶇骇涓虹┖/鍏ㄩ浂锛夈��
+ */
+@Configuration
+public class SatisfactionWebConfig implements WebMvcConfigurer {
+
+    /**
+     * 鎮h�呮弧鎰忓害锛�11 涓紝鍘� {@code PatSatisfactionController}锛�
+     */
+    private static final String PAT_SATISFACTION_PATTERN = "/smartor/satisfaction/**";
+
+    /**
+     * 鐪佺珛鍚屽痉 / 闂ㄨ瘖婊℃剰搴︾嫭鍗犵殑 5 涓鐐癸紙鍘熸寕鍦ㄩ殢璁挎槑缁� Controller 涓婏級
+     */
+    private static final List<String> SLTD_ENDPOINTS = Arrays.asList(
+            "/smartor/serviceSubtaskDetail/sltdMydTotalByScore",
+            "/smartor/serviceSubtaskDetail/statQuestionOptionByKsOrBq",
+            "/smartor/serviceSubtaskDetail/mydTrend",
+            "/smartor/serviceSubtaskDetail/sltdMydTotalByDimension",
+            "/smartor/serviceSubtaskDetail/mzMydScoreRank");
+
+    /**
+     * 婊℃剰搴﹀紓甯搁厤缃鐩紙1 涓紝鍘熸寕鍦ㄩ殢璁垮紓甯歌窡韪� Controller 涓婏級
+     */
+    private static final String EXCEPT_CONFIG_ENDPOINT = "/smartor/trace/getSatisfactionExceptConfigQuestion";
+
+    @Autowired
+    private SatisfactionAvailabilityInterceptor satisfactionAvailabilityInterceptor;
+
+    /**
+     * 渚涙祴璇�/闈� Spring 鐜鐩存帴鏋勯��
+     */
+    public SatisfactionWebConfig() {
+    }
+
+    public SatisfactionWebConfig(SatisfactionAvailabilityInterceptor interceptor) {
+        this.satisfactionAvailabilityInterceptor = interceptor;
+    }
+
+    /**
+     * 鏈ā鍧楅渶瑕�"鍏抽棴鍗虫槑纭姤閿�"鐨� 17 涓鐐癸紙渚涙牎楠岃剼鏈�/鍗曞厓娴嬭瘯鏂█锛�
+     */
+    public static List<String> guardedPathPatterns() {
+        List<String> patterns = new ArrayList<>();
+        patterns.add(PAT_SATISFACTION_PATTERN);
+        patterns.addAll(SLTD_ENDPOINTS);
+        patterns.add(EXCEPT_CONFIG_ENDPOINT);
+        return patterns;
+    }
+
+    @Override
+    public void addInterceptors(InterceptorRegistry registry) {
+        registry.addInterceptor(satisfactionAvailabilityInterceptor)
+                .addPathPatterns(guardedPathPatterns())
+                // 鎷︽埅鍣ㄦ湰韬緢杞伙紙鍙涓�涓� boolean锛夛紝order 缁欎釜灏忓�艰瀹冨湪涓氬姟鎷︽埅鍣ㄤ箣鍓嶆墽琛�
+                .order(-100);
+    }
+}
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/smartor/PatSatisfactionController.java b/ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/controller/PatSatisfactionController.java
similarity index 87%
rename from ruoyi-admin/src/main/java/com/ruoyi/web/controller/smartor/PatSatisfactionController.java
rename to ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/controller/PatSatisfactionController.java
index 544bf9d..3e2706b 100644
--- a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/smartor/PatSatisfactionController.java
+++ b/ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/controller/PatSatisfactionController.java
@@ -1,4 +1,4 @@
-package com.ruoyi.web.controller.smartor;
+package com.ruoyi.satisfaction.controller;
 
 import java.util.List;
 import java.util.Map;
@@ -8,8 +8,8 @@
 import com.ruoyi.common.core.domain.entity.SysUser;
 import com.ruoyi.common.core.domain.model.LoginUser;
 import com.ruoyi.common.utils.PageUtils;
-import com.smartor.domain.VO.PatSatisfactionReqVO;
-import com.smartor.domain.VO.PatSatisfactionResVO;
+import com.ruoyi.satisfaction.domain.vo.PatSatisfactionReqVO;
+import com.ruoyi.satisfaction.domain.vo.PatSatisfactionResVO;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
 
@@ -27,13 +27,23 @@
 import com.ruoyi.common.core.controller.BaseController;
 import com.ruoyi.common.core.domain.AjaxResult;
 import com.ruoyi.common.enums.BusinessType;
-import com.smartor.domain.PatSatisfaction;
-import com.smartor.service.IPatSatisfactionService;
+import com.ruoyi.satisfaction.domain.PatSatisfaction;
+import com.ruoyi.satisfaction.service.IPatSatisfactionService;
 import com.ruoyi.common.utils.poi.ExcelUtil;
 import com.ruoyi.common.core.page.TableDataInfo;
 
 /**
- * 鎮h�呮弧鎰忓害Controller
+ * 鎮h�呮弧鎰忓害 Controller锛堝凡杩佸叆 ruoyi-satisfaction 妯″潡锛夈��
+ * <p>
+ * 鈿狅笍 <b>URL 涓庡嚭鍏ュ弬涓�瀛楁湭鏀�</b>锛歿@code @RequestMapping("/smartor/satisfaction")} 涓庡叏閮�
+ * {@code @PostMapping}/{@code @GetMapping} 璺緞淇濇寔鍘熸牱锛屽墠绔棤闇�浠讳綍鏀瑰姩銆�
+ * 杩佺Щ鍙敼浜嗙被鎵�鍦ㄧ殑 Maven 妯″潡涓庡寘鍚嶃��
+ * <p>
+ * <b>寮�鍏�</b>锛氭湰 Controller 濮嬬粓娉ㄥ唽锛堜笉闅� {@code satisfaction.enabled} 娑堝け锛夈��
+ * 妯″潡琚叧闂椂鐢� {@code SatisfactionAvailabilityInterceptor}
+ * 锛堣 {@code config/SatisfactionWebConfig}锛夊湪杩涘叆鏂规硶鍓嶆姏鍑� {@code ServiceException}锛�
+ * 璋冪敤鏂逛細鎷垮埌<b>鏄庣‘鐨勯敊璇彁绀�</b>锛堣�屼笉鏄� 404锛岃兘鍖哄垎"妯″潡琚叧闂�"涓�"鍦板潃鍐欓敊"锛夈��
+ * 鎷︽埅鍣ㄥ彧鎸傚湪鏈ā鍧楃殑 17 涓弧鎰忓害绔偣涓婏紝闅忚/闂嵎绛夊叾瀹冧笟鍔″畬鍏ㄤ笉鍙楀奖鍝嶃��
  *
  * @author lihu
  * @date 2025-06-30
diff --git a/ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/controller/SatisfactionExceptConfigController.java b/ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/controller/SatisfactionExceptConfigController.java
new file mode 100644
index 0000000..9a82cf8
--- /dev/null
+++ b/ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/controller/SatisfactionExceptConfigController.java
@@ -0,0 +1,64 @@
+package com.ruoyi.satisfaction.controller;
+
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.satisfaction.domain.vo.SatisfactionExceptConfigQuestionReqVO;
+import com.ruoyi.satisfaction.service.SatisfactionSubtaskService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.Map;
+
+/**
+ * 婊℃剰搴﹀紓甯搁厤缃鐩� Controller锛堝凡杩佸叆 ruoyi-satisfaction 妯″潡锛夈��
+ * <p>
+ * 鈿狅笍 <b>URL 涓庡嚭鍏ュ弬涓�瀛楁湭鏀�</b>锛氳绔偣鍘熸湰鎸傚湪
+ * {@code com.ruoyi.web.controller.smartor.ServiceSubtaskDetailTraceController}
+ * 锛堢被绾� {@code @RequestMapping("/smartor/trace")}锛変笅锛岃縼绉诲悗
+ * <b>绫荤骇璺緞涓庡師鏂规硶璺緞閮戒繚鎸佸師鏍�</b>锛屾渶缁堝澶栧湴鍧�浠嶆槸
+ * {@code POST /smartor/trace/getSatisfactionExceptConfigQuestion}锛屽墠绔棤闇�浠讳綍鏀瑰姩銆�
+ * <p>
+ * <b>涓轰粈涔堢被绾ц矾寰勮娌跨敤 {@code /smartor/trace}锛�</b>
+ * 璇ョ鐐圭殑涓氬姟鏄�"闅忚浠诲姟寮傚父璺熻釜椤甸潰閲岄�夋嫨婊℃剰搴﹀紓甯搁鐩�"锛�
+ * 浣嗗畠鐨勬暟鎹潵婧愪笌绛涢�夐�昏緫锛堟弧鎰忓害鍒嗙被 + 妯℃澘棰樼洰锛夊畬鍏ㄥ睘浜庢弧鎰忓害鍩燂紝
+ * 鎸夋ā鍧楀寲瑕佹眰蹇呴』鎼叆鏈ā鍧椼�備负浜嗗仛鍒�"URL 涓嶅彉"锛岃繖閲屽埢鎰忔部鐢ㄥ師绫荤骇鍓嶇紑锛�
+ * 鑰屼笉鏄敼鍒� {@code /smartor/satisfaction} 涓嬨��
+ * <p>
+ * <b>寮�鍏�</b>锛氭湰 Controller 濮嬬粓娉ㄥ唽锛堜笉闅� {@code satisfaction.enabled} 娑堝け锛夈��
+ * 妯″潡琚叧闂椂鐢� {@code SatisfactionAvailabilityInterceptor}
+ * 锛堣 {@code config/SatisfactionWebConfig}锛夊湪杩涘叆鏂规硶鍓嶆姏鍑� {@code ServiceException}锛�
+ * 璋冪敤鏂逛細鎷垮埌<b>鏄庣‘鐨勯敊璇彁绀�</b>锛堣�屼笉鏄� 404锛夈��
+ * 娉ㄦ剰锛氬悓涓�鍓嶇紑 {@code /smartor/trace} 涓嬬殑<b>寮傚父璺熻釜</b>绔偣涓嶅彈褰卞搷
+ * 鈥斺�� 鎷︽埅鍣ㄥ彧鎸傚湪鏈被鐨勮繖 1 涓矾寰勪笂銆�
+ */
+@Slf4j
+@Api(description = "婊℃剰搴﹀紓甯搁厤缃鐩�")
+@RestController
+@RequestMapping("/smartor/trace")
+public class SatisfactionExceptConfigController extends BaseController {
+
+    @Autowired
+    private SatisfactionSubtaskService satisfactionSubtaskService;
+
+    /**
+     * 鑾峰彇婊℃剰搴﹀紓甯搁厤缃鐩�
+     * <p>
+     * 杩佺Щ璇存槑锛氬叆鍙傘�佽繑鍥炵粨鏋勩�佹棩蹇楁牸寮忎笌琚縼绉荤殑瀹炵幇瀹屽叏涓�鑷达紱
+     * 鍘熷疄鐜伴噷鐨勬煡璇㈤�昏緫鏁存鎼埌浜�
+     * {@link SatisfactionSubtaskService#getSatisfactionExceptConfigQuestion}銆�
+     */
+    @ApiOperation("鑾峰彇婊℃剰搴﹀紓甯搁厤缃鐩�")
+    @PostMapping("/getSatisfactionExceptConfigQuestion")
+    public Map<String, Object> getSatisfactionExceptConfigQuestion(
+            @RequestBody SatisfactionExceptConfigQuestionReqVO questionReqVO) {
+        log.info("鑾峰彇婊℃剰搴﹀紓甯搁厤缃鐩殑鍏ュ弬: {}", questionReqVO);
+        Map<String, Object> satisfactionExceptConfigQuestion =
+                satisfactionSubtaskService.getSatisfactionExceptConfigQuestion(questionReqVO);
+        return satisfactionExceptConfigQuestion;
+    }
+}
diff --git a/ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/controller/SltdSatisfactionController.java b/ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/controller/SltdSatisfactionController.java
new file mode 100644
index 0000000..88e3810
--- /dev/null
+++ b/ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/controller/SltdSatisfactionController.java
@@ -0,0 +1,172 @@
+package com.ruoyi.satisfaction.controller;
+
+import com.ruoyi.common.annotation.AddOrgId;
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.satisfaction.domain.ServiceSubtaskDetailRatioExport;
+import com.ruoyi.satisfaction.domain.dto.QuestionResultDTO;
+import com.ruoyi.satisfaction.domain.row.SatisfactionSubtaskQuery;
+import com.ruoyi.satisfaction.domain.row.SatisfactionSubtaskRef;
+import com.ruoyi.satisfaction.domain.vo.MydTrendReqVO;
+import com.ruoyi.satisfaction.domain.vo.MzMydScoreRankVO;
+import com.ruoyi.satisfaction.domain.vo.QuestionCountKsOrBqVO;
+import com.ruoyi.satisfaction.domain.vo.SltdMydTotalVO;
+import com.ruoyi.satisfaction.service.SatisfactionSubtaskService;
+import com.ruoyi.satisfaction.service.strategy.SatisfactionScoreStrategyResolver;
+import com.ruoyi.satisfaction.service.strategy.SatisfactionSites;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.apache.commons.collections4.CollectionUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * 鐪佺珛鍚屽痉 / 闂ㄨ瘖婊℃剰搴︾粺璁� Controller锛堝凡杩佸叆 ruoyi-satisfaction 妯″潡锛夈��
+ * <p>
+ * 鈿狅笍 <b>URL 涓庡嚭鍏ュ弬涓�瀛楁湭鏀�</b>锛氳繖浜涚鐐瑰師鏈寕鍦�
+ * {@code ServiceSubtaskDetailController}锛坽@code /smartor/serviceSubtaskDetail}锛変笅锛�
+ * 杩佺Щ鍚� {@code @RequestMapping} 涓庡叏閮� {@code @PostMapping} 璺緞淇濇寔鍘熸牱锛屽墠绔棤闇�浠讳綍鏀瑰姩銆�
+ * <p>
+ * 杩佺Щ鏃跺敮涓�璋冩暣鐨勪竴澶勶細{@code sltdMydTotalByScore} 鍘熸湰璋冪敤
+ * {@code serviceSubtaskService.getServiceSubtasks(...)}锛堥殢璁夸富骞� Service锛屼笉鑳芥惉锛夛紝
+ * 鐜版敼涓鸿皟鐢ㄦ弧鎰忓害妯″潡鑷甫鐨� {@link SatisfactionSubtaskService#getMydSubtaskRefs}锛�
+ * 鏌ヨ鏉′欢涓庡悗缁粺璁″彛寰勯�愬瓧淇濇寔涓�鑷达紙瑙佸悇鏂规硶鍐呮敞閲婏級銆�
+ * <p>
+ * <b>寮�鍏�</b>锛氭湰 Controller 濮嬬粓娉ㄥ唽锛堜笉闅� {@code satisfaction.enabled} 娑堝け锛夈��
+ * 妯″潡琚叧闂椂鐢� {@code SatisfactionAvailabilityInterceptor}
+ * 锛堣 {@code config/SatisfactionWebConfig}锛夊湪杩涘叆鏂规硶鍓嶆姏鍑� {@code ServiceException}锛�
+ * 璋冪敤鏂逛細鎷垮埌<b>鏄庣‘鐨勯敊璇彁绀�</b>锛堣�屼笉鏄� 404锛夈��
+ * 娉ㄦ剰锛氬悓涓�鍓嶇紑 {@code /smartor/serviceSubtaskDetail} 涓嬬殑<b>闅忚鏄庣粏</b>绔偣涓嶅彈褰卞搷
+ * 鈥斺�� 鎷︽埅鍣ㄥ彧鎸傚湪鏈被鐨� 5 涓弧鎰忓害璺緞涓娿��
+ */
+@Api(description = "鐪佺珛鍚屽痉婊℃剰搴︾粺璁�")
+@RestController
+@RequestMapping("/smartor/serviceSubtaskDetail")
+public class SltdSatisfactionController extends BaseController {
+
+    /**
+     * 绔欑偣鍙e緞绛栫暐瑙f瀽鍣細缁煎悎婊℃剰搴﹀緱鍒嗘寜绔欑偣锛堢渷绔嬪悓寰凤級鍙栫瓥鐣ヨ绠楋紝
+     * 涓嶅啀鎶婄畻娉曚笌甯搁噺鍐欏湪 Controller 閲屻��
+     */
+    @Autowired
+    private SatisfactionScoreStrategyResolver satisfactionScoreStrategyResolver;
+
+    @Autowired
+    private SatisfactionSubtaskService satisfactionSubtaskService;
+
+    // ================================================================ 绔偣锛圲RL 鍘熸牱锛�
+
+    /**
+     * 鐪佺珛鍚屽痉婊℃剰搴︽暟鎹粺璁★紙鎸夐鐩粺璁¢�夐」鏁伴噺/鍗犳瘮锛屽苟缁欏嚭璇ラ鐨勭患鍚堝緱鍒嗭級
+     * <p>
+     * 缁煎悎婊℃剰搴� = (寰堟弧鎰忛」鏁懊�1 + 婊℃剰椤规暟脳0.8 + 涓�鑸」鏁懊�0.6 + 涓嶆弧鎰忛」鏁懊�0.4 + 寰堜笉婊℃剰椤规暟脳0.2)
+     * / (寰堟弧鎰忛」鏁� + 婊℃剰椤规暟 + 涓�鑸」鏁� + 涓嶆弧鎰忛」鏁� + 寰堜笉婊℃剰椤规暟) 脳 100%锛�
+     * 銆屾湭缁忓巻銆嶃�屼笉閫傜敤銆嶇瓑涓嶅湪浜旀。閲岀殑閫夐」鏃笉杩涘垎瀛愪篃涓嶈繘鍒嗘瘝銆�
+     */
+    @ApiOperation("鐪佺珛鍚屽痉婊℃剰搴︽暟鎹粺璁�")
+    @AddOrgId(field = "orgid", paramIndex = 0, campusField = "campusid")
+    @PostMapping("/sltdMydTotalByScore")
+    public Map<String, Object> sltdMydTotalByScore(@RequestBody SltdMydTotalVO sltdMydTotalVO) {
+        List<SatisfactionSubtaskRef> subtaskRefs = null;
+        if (CollectionUtils.isNotEmpty(sltdMydTotalVO.getServiceTypeList())) {
+            //serviceType鍙兘浼氬嚭鐜板涓紝涓嶆兂澶氬啓鏂规硶浜嗭紝涓�涓竴涓煡璇㈠惂
+            for (String st : sltdMydTotalVO.getServiceTypeList()) {
+                SatisfactionSubtaskQuery query = new SatisfactionSubtaskQuery();
+                query.setServiceType(st);
+                query.setEndOutHospTime(sltdMydTotalVO.getEndOutHospTime());
+                query.setStartOutHospTime(sltdMydTotalVO.getStartOutHospTime());
+                query.setDeptOrDistrict(sltdMydTotalVO.getDeptOrDistrict());
+                query.setLeavehospitaldistrictcodes(sltdMydTotalVO.getLeavehospitaldistrictcodes());
+                query.setLeaveldeptcodes(sltdMydTotalVO.getLeaveldeptcodes());
+                //鏈烘瀯/闄㈠尯鐢� @AddOrgId 娉ㄥ叆锛岃惤鍒版煡璇㈡潯浠朵笂锛岄伩鍏嶈法鏈烘瀯鍙栨暟
+                query.setOrgid(sltdMydTotalVO.getOrgid());
+                query.setCampusid(sltdMydTotalVO.getCampusid());
+                List<SatisfactionSubtaskRef> refs = satisfactionSubtaskService.getMydSubtaskRefs(query);
+                if (CollectionUtils.isNotEmpty(subtaskRefs)) {
+                    subtaskRefs.addAll(refs);
+                } else {
+                    if (CollectionUtils.isEmpty(subtaskRefs)) subtaskRefs = refs;
+                }
+            }
+        }
+        // 鎸� questiontext 鍒嗙粍鍚庤浆鎹负 QuestionResultVO
+        List<QuestionResultDTO> result = new ArrayList<>();
+        if (CollectionUtils.isNotEmpty(subtaskRefs)) {
+            List<ServiceSubtaskDetailRatioExport> detailList =
+                    satisfactionSubtaskService.getDetailRatio(sltdMydTotalVO, subtaskRefs);
+
+            if (detailList == null) {
+                //serviceType 缁勫悎涓嶅湪鏀寔鑼冨洿鍐咃紙濡傚悓鏃跺惈闅忚 2 鍜屾弧鎰忓害 6/14锛夛紝缁欐槑纭彁绀鸿�屼笉鏄倓鎮勮繑鍥炵┖鏁扮粍
+                return error("褰撳墠 serviceType 缁勫悎鏆備笉鏀寔缁熻闂鍗犳瘮锛岃鍒嗗紑鏌ヨ锛堜粎鏀寔闅忚 2 鎴栨弧鎰忓害 6/14锛�");
+            }
+            if (CollectionUtils.isNotEmpty(detailList)) {
+                Map<String, List<ServiceSubtaskDetailRatioExport>> groupedMap =
+                        detailList.stream().collect(Collectors.groupingBy(ServiceSubtaskDetailRatioExport::getQuestiontext));
+
+                groupedMap.forEach((questionText, exportList) -> {
+                    QuestionResultDTO questionResultDTO = new QuestionResultDTO();
+                    questionResultDTO.setQeustionText(questionText);
+                    questionResultDTO.setSubtaskDetailRatioExportList(exportList);
+                    //缁煎悎寰楀垎锛�(寰堟弧鎰徝�1 + 婊℃剰脳0.8 + 涓�鑸�0.6 + 涓嶆弧鎰徝�0.4 + 寰堜笉婊℃剰脳0.2) / 浜旀。椤规暟涔嬪拰 脳 100%
+                    questionResultDTO.setAvgScore(calcMydScore(exportList));
+                    result.add(questionResultDTO);
+                });
+            }
+        }
+
+        return success(result);
+    }
+
+    @ApiOperation("鐪佺珛鍚屽痉婊℃剰搴︽暟鎹粺璁¢�氳繃绉戝鎴栫梾鍖�")
+    @PostMapping("/statQuestionOptionByKsOrBq")
+    public Map<String, Object> statQuestionOptionByKsOrBq(@RequestBody QuestionCountKsOrBqVO questionResultVO) {
+        return success(satisfactionSubtaskService.statQuestionOptionByKsOrBq(questionResultVO));
+    }
+
+    @ApiOperation("鐪佺珛鍚屽痉鎮h�呮弧鎰忓害瓒嬪娍")
+    @PostMapping("/mydTrend")
+    public Map<String, Object> mydTrend(@RequestBody MydTrendReqVO mydTrendReqVO) {
+        return success(satisfactionSubtaskService.mydTrend(mydTrendReqVO));
+    }
+
+    @ApiOperation("鐪佺珛鍚屽痉鎮h�呮弧鎰忓害缁存姢")
+    @PostMapping("/sltdMydTotalByDimension")
+    public Map<String, Object> sltdMydTotalByDimension(@RequestBody MydTrendReqVO mydTrendReqVO) {
+        return success(satisfactionSubtaskService.sltdMydTotalByDimension(mydTrendReqVO));
+    }
+
+    @ApiOperation("闂ㄨ瘖婊℃剰搴︾患鍚堝緱鍒嗘帓鍚嶏紙鎸夌瀹ゆ垨鐥呭尯锛�")
+    @AddOrgId(field = "orgid", paramIndex = 0, campusField = "campusid")
+    @PostMapping("/mzMydScoreRank")
+    public Map<String, Object> mzMydScoreRank(@RequestBody MzMydScoreRankVO mzMydScoreRankVO) {
+        return success(satisfactionSubtaskService.mzMydScoreRank(mzMydScoreRankVO));
+    }
+
+    // ================================================================ 缁艰瘎璁$畻锛堟敼璧扮珯鐐圭瓥鐣ワ級
+
+    /**
+     * 璁$畻涓�閬撻鐨勬弧鎰忓害缁煎悎寰楀垎锛堢櫨鍒嗗埗锛屼繚鐣欎袱浣嶅皬鏁帮級銆�
+     * <p>
+     * <b>绛栫暐鍖栨敼閫�</b>锛氱畻娉曟湰韬凡鍘熸牱鎼埌
+     * {@link com.ruoyi.satisfaction.service.strategy.SltdSatisfactionScoreStrategy}
+     * 锛堢渷绔嬪悓寰峰彛寰勶級锛屾湰鏂规硶鍙礋璐�"鎸夌珯鐐瑰彇绛栫暐 + 鎶� null 鍛堢幇涓� 0.00"銆�
+     * 璁$畻杩囩▼涓庤繑鍥炲�间笌杩佺Щ鍓�<b>瀹屽叏涓�鑷�</b>銆�
+     *
+     * @param exportList 鍚屼竴閬撻鐩殑閫夐」缁熻鏄庣粏
+     * @return 鐧惧垎鍒剁患鍚堝緱鍒嗭紝鏃犳湁鏁堥」鏁版椂杩斿洖 "0.00"
+     */
+    private String calcMydScore(List<ServiceSubtaskDetailRatioExport> exportList) {
+        BigDecimal score = satisfactionScoreStrategyResolver
+                .resolve(SatisfactionSites.SLTD)
+                .comprehensiveScore(exportList);
+        return score == null ? "0.00" : score.toPlainString();
+    }
+}
diff --git a/smartor/src/main/java/com/smartor/domain/PatSatisfaction.java b/ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/domain/PatSatisfaction.java
similarity index 98%
rename from smartor/src/main/java/com/smartor/domain/PatSatisfaction.java
rename to ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/domain/PatSatisfaction.java
index 775146a..d2cc809 100644
--- a/smartor/src/main/java/com/smartor/domain/PatSatisfaction.java
+++ b/ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/domain/PatSatisfaction.java
@@ -1,4 +1,4 @@
-package com.smartor.domain;
+package com.ruoyi.satisfaction.domain;
 
 import com.fasterxml.jackson.annotation.JsonFormat;
 import com.ruoyi.common.annotation.Excel;
diff --git a/smartor/src/main/java/com/smartor/domain/ServiceSubtaskDetailRatioExport.java b/ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/domain/ServiceSubtaskDetailRatioExport.java
similarity index 97%
rename from smartor/src/main/java/com/smartor/domain/ServiceSubtaskDetailRatioExport.java
rename to ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/domain/ServiceSubtaskDetailRatioExport.java
index 87845e4..a2f532f 100644
--- a/smartor/src/main/java/com/smartor/domain/ServiceSubtaskDetailRatioExport.java
+++ b/ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/domain/ServiceSubtaskDetailRatioExport.java
@@ -1,4 +1,4 @@
-package com.smartor.domain;
+package com.ruoyi.satisfaction.domain;
 
 import com.fasterxml.jackson.annotation.JsonFormat;
 import com.ruoyi.common.annotation.Excel;
diff --git a/smartor/src/main/java/com/smartor/domain/DTO/MydTrendReqDTO.java b/ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/domain/dto/MydTrendReqDTO.java
similarity index 95%
rename from smartor/src/main/java/com/smartor/domain/DTO/MydTrendReqDTO.java
rename to ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/domain/dto/MydTrendReqDTO.java
index c999063..9c89b0d 100644
--- a/smartor/src/main/java/com/smartor/domain/DTO/MydTrendReqDTO.java
+++ b/ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/domain/dto/MydTrendReqDTO.java
@@ -1,4 +1,4 @@
-package com.smartor.domain.DTO;
+package com.ruoyi.satisfaction.domain.dto;
 
 import com.fasterxml.jackson.annotation.JsonFormat;
 import com.ruoyi.common.core.domain.BaseEntity;
diff --git a/smartor/src/main/java/com/smartor/domain/DTO/MzMydScoreRankDTO.java b/ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/domain/dto/MzMydScoreRankDTO.java
similarity index 96%
rename from smartor/src/main/java/com/smartor/domain/DTO/MzMydScoreRankDTO.java
rename to ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/domain/dto/MzMydScoreRankDTO.java
index 1688ec0..c00b44e 100644
--- a/smartor/src/main/java/com/smartor/domain/DTO/MzMydScoreRankDTO.java
+++ b/ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/domain/dto/MzMydScoreRankDTO.java
@@ -1,4 +1,4 @@
-package com.smartor.domain.DTO;
+package com.ruoyi.satisfaction.domain.dto;
 
 import com.ruoyi.common.core.domain.BaseEntity;
 import io.swagger.annotations.ApiModel;
diff --git a/smartor/src/main/java/com/smartor/domain/DTO/QuestionCountKsOrBqDTO.java b/ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/domain/dto/QuestionCountKsOrBqDTO.java
similarity index 92%
rename from smartor/src/main/java/com/smartor/domain/DTO/QuestionCountKsOrBqDTO.java
rename to ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/domain/dto/QuestionCountKsOrBqDTO.java
index cdff3ea..e68ff2d 100644
--- a/smartor/src/main/java/com/smartor/domain/DTO/QuestionCountKsOrBqDTO.java
+++ b/ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/domain/dto/QuestionCountKsOrBqDTO.java
@@ -1,7 +1,7 @@
-package com.smartor.domain.DTO;
+package com.ruoyi.satisfaction.domain.dto;
 
 import com.ruoyi.common.core.domain.BaseEntity;
-import com.smartor.domain.ServiceSubtaskDetailRatioExport;
+import com.ruoyi.satisfaction.domain.ServiceSubtaskDetailRatioExport;
 import io.swagger.annotations.ApiModel;
 import io.swagger.annotations.ApiModelProperty;
 import lombok.Data;
diff --git a/smartor/src/main/java/com/smartor/domain/DTO/QuestionResultDTO.java b/ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/domain/dto/QuestionResultDTO.java
similarity index 87%
rename from smartor/src/main/java/com/smartor/domain/DTO/QuestionResultDTO.java
rename to ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/domain/dto/QuestionResultDTO.java
index 1fa924c..2956f6f 100644
--- a/smartor/src/main/java/com/smartor/domain/DTO/QuestionResultDTO.java
+++ b/ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/domain/dto/QuestionResultDTO.java
@@ -1,7 +1,7 @@
-package com.smartor.domain.DTO;
+package com.ruoyi.satisfaction.domain.dto;
 
 import com.ruoyi.common.core.domain.BaseEntity;
-import com.smartor.domain.ServiceSubtaskDetailRatioExport;
+import com.ruoyi.satisfaction.domain.ServiceSubtaskDetailRatioExport;
 import io.swagger.annotations.ApiModel;
 import io.swagger.annotations.ApiModelProperty;
 import lombok.Data;
diff --git a/smartor/src/main/java/com/smartor/domain/DTO/ServiceSubtaskOptionStatDTO.java b/ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/domain/dto/ServiceSubtaskOptionStatDTO.java
similarity index 96%
rename from smartor/src/main/java/com/smartor/domain/DTO/ServiceSubtaskOptionStatDTO.java
rename to ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/domain/dto/ServiceSubtaskOptionStatDTO.java
index 7ba51dc..4d06eee 100644
--- a/smartor/src/main/java/com/smartor/domain/DTO/ServiceSubtaskOptionStatDTO.java
+++ b/ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/domain/dto/ServiceSubtaskOptionStatDTO.java
@@ -1,4 +1,4 @@
-package com.smartor.domain.DTO;
+package com.ruoyi.satisfaction.domain.dto;
 
 import io.swagger.annotations.ApiModel;
 import io.swagger.annotations.ApiModelProperty;
diff --git a/smartor/src/main/java/com/smartor/domain/entity/PatSatisfactionDetailEntity.java b/ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/domain/entity/PatSatisfactionDetailEntity.java
similarity index 96%
rename from smartor/src/main/java/com/smartor/domain/entity/PatSatisfactionDetailEntity.java
rename to ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/domain/entity/PatSatisfactionDetailEntity.java
index b0d0af0..a15c845 100644
--- a/smartor/src/main/java/com/smartor/domain/entity/PatSatisfactionDetailEntity.java
+++ b/ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/domain/entity/PatSatisfactionDetailEntity.java
@@ -1,4 +1,4 @@
-package com.smartor.domain.entity;
+package com.ruoyi.satisfaction.domain.entity;
 
 import com.fasterxml.jackson.annotation.JsonFormat;
 import com.ruoyi.common.core.domain.BaseEntity;
diff --git a/ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/domain/entity/SatisfactionStatQuery.java b/ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/domain/entity/SatisfactionStatQuery.java
new file mode 100644
index 0000000..a94666b
--- /dev/null
+++ b/ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/domain/entity/SatisfactionStatQuery.java
@@ -0,0 +1,60 @@
+package com.ruoyi.satisfaction.domain.entity;
+
+import com.ruoyi.common.core.domain.BaseEntity;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 婊℃剰搴︾粺璁¤仛鍚堟煡璇㈠叆鍙�
+ * <p>
+ * 鐢ㄤ簬 satisfactionGraph 涓�娆℃�ф煡鍑恒�屾湰娆¤姹傛秹鍙婄殑鎵�鏈夋ā鏉裤�嶇殑鍙戦�侀噺涓庡~鎶ラ噺锛�
+ * 鏇夸唬鍘熸潵銆屾瘡涓ā鏉块棶棰樺悇鏌ヤ袱娆°�嶇殑鍐欐硶銆�
+ * orgid / campusid 澶嶇敤 BaseEntity 瀛楁锛堜笌鍘� ServiceSubtaskSatisfactionEntity 鍙栧�兼潵婧愪竴鑷达級銆�
+ *
+ * @author smartor
+ */
+@ApiModel(value = "SatisfactionStatQuery", description = "婊℃剰搴︾粺璁¤仛鍚堟煡璇㈠叆鍙�")
+@Data
+public class SatisfactionStatQuery extends BaseEntity {
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 浠诲姟绫诲瀷锛坰ervice_subtask.type锛夛紝瀛楃涓叉瘮杈冿紝閬垮厤 varchar 涓庢暟瀛楅殣寮忚浆鎹�
+     */
+    @ApiModelProperty(value = "浠诲姟绫诲瀷(1銆佽闊筹紱2銆侀棶鍗疯〃锛�)")
+    private String type;
+
+    /**
+     * 鏌ヨ寮�濮嬫椂闂�
+     */
+    @ApiModelProperty(value = "鏌ヨ寮�濮嬫椂闂�")
+    private Date startTime;
+
+    /**
+     * 鏌ヨ缁撴潫鏃堕棿
+     */
+    @ApiModelProperty(value = "鏌ヨ缁撴潫鏃堕棿")
+    private Date endTime;
+
+    /**
+     * 閮ㄩ棬闆嗗悎
+     */
+    @ApiModelProperty(value = "閮ㄩ棬闆嗗悎")
+    private List<String> deptcodes;
+
+    /**
+     * 鐥呭尯缂栧彿
+     */
+    @ApiModelProperty(value = "鐥呭尯闆嗗悎")
+    private List<String> hospitaldistrictcodes;
+
+    /**
+     * 鍩虹妯℃澘Ids锛坕vr 鐢� templateid锛岄棶鍗风敤 svyid锛�
+     */
+    @ApiModelProperty(value = "鍩虹妯℃澘Ids")
+    private List<Long> libTemplateIds;
+}
diff --git a/smartor/src/main/java/com/smartor/domain/entity/ServiceSubtaskSatisfactionEntity.java b/ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/domain/entity/ServiceSubtaskSatisfactionEntity.java
similarity index 95%
rename from smartor/src/main/java/com/smartor/domain/entity/ServiceSubtaskSatisfactionEntity.java
rename to ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/domain/entity/ServiceSubtaskSatisfactionEntity.java
index 5a713e7..d5ff33f 100644
--- a/smartor/src/main/java/com/smartor/domain/entity/ServiceSubtaskSatisfactionEntity.java
+++ b/ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/domain/entity/ServiceSubtaskSatisfactionEntity.java
@@ -1,9 +1,8 @@
-package com.smartor.domain.entity;
+package com.ruoyi.satisfaction.domain.entity;
 
 import com.fasterxml.jackson.annotation.JsonFormat;
 import com.ruoyi.common.annotation.Excel;
 import com.ruoyi.common.core.domain.BaseEntity;
-import com.smartor.domain.TaskSendTimeVO;
 import io.swagger.annotations.ApiModel;
 import io.swagger.annotations.ApiModelProperty;
 import lombok.Data;
diff --git a/ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/domain/row/SatisfactionCategoryRow.java b/ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/domain/row/SatisfactionCategoryRow.java
new file mode 100644
index 0000000..8e0b04d
--- /dev/null
+++ b/ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/domain/row/SatisfactionCategoryRow.java
@@ -0,0 +1,54 @@
+package com.ruoyi.satisfaction.domain.row;
+
+import lombok.Data;
+
+/**
+ * 婊℃剰搴﹀垎绫诲彧璇昏鍥撅紙璇煶渚т笌闂嵎渚у叡鐢級銆�
+ * <p>
+ * 涓轰粈涔堝叡鐢ㄤ竴涓被锛氳闊崇殑 {@code ivr_liba_script_assort} 涓庨棶鍗风殑
+ * {@code svy_lib_script_category} 鍦ㄦ弧鎰忓害缁熻閲岃涔夊畬鍏ㄤ竴鑷粹�斺��
+ * 閮芥槸銆屼竴涓弧鎰忓害鍒嗙被锛歩d + 鍚嶇О + type銆嶏紝璋冪敤鏂瑰彧鐢ㄦ潵
+ * 銆屾寜 {@code type.contains("myd")} 杩囨护鍑哄垎绫� id銆佸苟鎸夊悕绉板垎缁勩�嶃��
+ * <p>
+ * 涓よ〃鐨勫師濮嬪垪鍚嶄笉鍚岋紝SQL 閲屽凡鍋氬埆鍚嶅綊涓�锛�
+ * <ul>
+ *   <li>璇煶锛歿@code index_assort_name as name}</li>
+ *   <li>闂嵎锛歿@code name}锛堝垪鍚嶆湰鏉ュ氨鏄� name锛�</li>
+ * </ul>
+ * <p>
+ * 鈿狅笍 杩欐槸<b>鍙</b>瑙嗗浘銆�
+ */
+@Data
+public class SatisfactionCategoryRow {
+
+    /**
+     * 鍒嗙被 id
+     */
+    private Long id;
+
+    /**
+     * 鍒嗙被鍚嶇О
+     * <p>娉ㄦ剰锛氬垎绫诲彛寰勭粺璁℃槸鎸夈�屾ā鏉夸笂鐨勫垎绫诲悕銆嶏紙{@code script_assortname} /
+     * {@code categoryName}锛夊垎缁勶紝鑰屼笉鏄敤鏈瓧娈碉紱鏈瓧娈典富瑕佺敤浜庡缓鍒嗙被 id 鈫� 鍚嶇О鐨勬槧灏勩��
+     */
+    private String name;
+
+    /**
+     * 鍒嗙被绫诲瀷锛堟弧鎰忓害鍥哄畾涓� myd 鐩稿叧锛岃皟鐢ㄦ柟鎸� contains("myd") 杩囨护锛�
+     */
+    private String type;
+
+    /**
+     * 瀵瑰簲鍒� {@code pid}锛氱埗鍒嗙被 id锛堥《灞備负 {@code null} 鎴� {@code -1}锛夈��
+     * <p>
+     * 鐢ㄩ�旓細{@code /smartor/trace/getSatisfactionExceptConfigQuestion} 璧扮殑鏄�
+     * 璇煶鍒嗙被鐨�<b>鏈嶅姟鏂规硶</b> {@code IvrLibaScriptAssortServiceImpl.selectIvrLibaScriptAssortList}锛�
+     * 璇ユ柟娉曚細鎶婂垎绫绘嫾鎴愭爲銆�<b>鍙繑鍥為《灞傝妭鐐�</b>锛坽@code pid == null || pid == -1}锛夛紝
+     * 鎵�浠ヨ皟鐢ㄦ柟蹇呴』鐢ㄦ湰瀛楁鍋氬悓鏍风殑椤跺眰绛涢�夋墠鑳戒笌鍘熸枃涓�鑷淬��
+     * <p>
+     * 璇煶渚т笌闂嵎渚ч兘鍙栦簡鏈垪锛屼絾<b>鍙湁璇煶渚�</b>闇�瑕佹寜瀹冪瓫椤跺眰鈥斺��
+     * 闂嵎渚у師鏂囩洿鎺ョ敤 {@code svyLibScriptCategoryMapper} 鍙栧叏閮ㄥ垎绫伙紙涓嶆嫾鏍戯級锛�
+     * 涓や晶涓嶄竴鑷存槸鍘熸枃琛屼负锛屽繀椤讳繚鐣欍��
+     */
+    private Long pid;
+}
diff --git a/ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/domain/row/SatisfactionIvrScriptRow.java b/ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/domain/row/SatisfactionIvrScriptRow.java
new file mode 100644
index 0000000..7498650
--- /dev/null
+++ b/ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/domain/row/SatisfactionIvrScriptRow.java
@@ -0,0 +1,78 @@
+package com.ruoyi.satisfaction.domain.row;
+
+import lombok.Data;
+
+/**
+ * 婊℃剰搴︾粺璁$敤鐨勩�岃闊虫ā鏉块棶棰樸�嶅彧璇昏鍥俱��
+ * <p>
+ * 涓轰粈涔堥渶瑕佽繖涓被锛堣�屼笉鏄洿鎺ョ敤 {@code com.smartor.domain.IvrLibaTemplateScript}锛夛細
+ * <ul>
+ *   <li>閭d釜瀹炰綋灞炰簬 smartor 鐨�**璇煶妯℃澘绠$悊**涓氬姟锛岃 9 涓枃浠朵娇鐢紙鍚ā鏉垮鍒犳敼鏌ワ級锛屼笉鑳芥惉杩涙湰妯″潡锛�</li>
+ *   <li>婊℃剰搴︾粺璁″叾瀹炲彧鐢ㄥ埌瀹冪殑 5 涓瓧娈碉紙瑙佷笅锛夛紝鎸夐渶鍙栧垪姣旀暣濂楀疄浣撴洿娓呮櫚锛�</li>
+ *   <li>鏈ā鍧楄姹傘�屼笉渚濊禆 smartor銆嶏紝鎵�浠ヤ笉鑳� import 瀹冦��</li>
+ * </ul>
+ * 瀛楁涓庢簮琛� {@code ivr_liba_template_script} 鐨勫垪涓�涓�瀵瑰簲锛屽彛寰勪笌鍘熷疄鐜板畬鍏ㄤ竴鑷淬��
+ * <p>
+ * 鈿狅笍 杩欐槸<b>鍙</b>瑙嗗浘锛屾湰妯″潡涓嶄細瀵瑰畠鍋氫换浣曞啓鎿嶄綔銆�
+ */
+@Data
+public class SatisfactionIvrScriptRow {
+
+    /**
+     * 瀵瑰簲鍒� {@code templateid}锛氳闊虫ā鏉� id銆�
+     * <p>鑱氬悎缁熻锛堝彂閫侀噺/鍥炴敹浜烘暟/濉姤閲忥級鎸夊畠鍒嗙粍銆�
+     */
+    private Long templateid;
+
+    /**
+     * 瀵瑰簲鍒� {@code scriptid}锛氶�夐」琛� {@code ivr_liba_template_targetoption} 鍏宠仈鐨勯棶棰� id銆�
+     * <p>鐢ㄤ簬鍙栬闂鐨勯�夐」鍒嗗�硷紙绠楁渶楂�/鏈�浣庡垎锛夈��<b>鍙兘涓� null</b>锛�
+     * 姝ゆ椂鍘熷疄鐜扮殑 SQL 涓嶆嫾 scriptid 鏉′欢锛堢瓑浜庡彇璇ヨ〃鍏ㄩ儴鍒嗗�硷級銆�
+     */
+    private Long scriptid;
+
+    /**
+     * 瀵瑰簲鍒� {@code script_content}锛氶棶棰樺唴瀹广��
+     * <p>鐢ㄤ簬涓庡~鎶ユ槑缁嗙殑 {@code questiontext} 鍋氬尮閰嶏紙MySQL 鐨� {@code =} 蹇界暐澶у皬鍐欎笌灏鹃儴绌烘牸锛夈��
+     */
+    private String scriptContent;
+
+    /**
+     * 瀵瑰簲鍒� {@code script_assortname}锛氳闊虫弧鎰忓害鍒嗙被鍚嶇О銆�
+     * <p>鍒嗙被鍙e緞缁熻锛坽@code satisfactionGraph}锛夋寜瀹冨垎缁勩��
+     */
+    private String scriptAssortname;
+
+    /**
+     * 瀵瑰簲鍒� {@code dimension}锛氱淮搴︺��
+     * <p>缁村害鍙e緞缁熻锛坽@code satisfactionGraphDimension}锛夋寜瀹冨垎缁勩��
+     */
+    private String dimension;
+
+    // ==================== 浠呫�屾弧鎰忓害寮傚父閰嶇疆棰樼洰銆嶆帴鍙d娇鐢� ====================
+    // 涓嬮潰 3 涓瓧娈靛彧鏈� /smartor/trace/getSatisfactionExceptConfigQuestion 闇�瑕侊紝
+    // 缁熻鍙e緞锛坰atisfactionGraph / satisfactionGraphDimension锛夌殑鏌ヨ涓嶅彇杩欎簺鍒楋紝淇濇寔涓� null銆�
+
+    /**
+     * 瀵瑰簲鍒� {@code id}锛歿@code ivr_liba_template_script} 涓婚敭銆�
+     * <p>銆屾弧鎰忓害寮傚父閰嶇疆棰樼洰銆嶆帴鍙f妸瀹冧綔涓鸿繑鍥為噷鐨� {@code questionId}銆�
+     */
+    private Long id;
+    /**
+     * 瀵瑰簲鍒� {@code script_topic}锛氶鐩富棰樸��
+     * <p>銆屾弧鎰忓害寮傚父閰嶇疆棰樼洰銆嶆帴鍙f妸瀹冧綔涓鸿繑鍥為噷鐨� {@code questionTopic}銆�
+     */
+    private String scriptTopic;
+
+    /**
+     * 瀵瑰簲鍒� {@code script_type}锛氶鐩被鍨嬨��
+     * <p>銆屾弧鎰忓害寮傚父閰嶇疆棰樼洰銆嶆帴鍙f妸瀹冧綔涓鸿繑鍥為噷鐨� {@code questionType}銆�
+     */
+    private String scriptType;
+
+    /**
+     * 瀵瑰簲鍒� {@code script_assortid}锛氭墍灞炶闊冲垎绫� id銆�
+     * <p>銆屾弧鎰忓害寮傚父閰嶇疆棰樼洰銆嶆帴鍙f妸瀹冧綔涓鸿繑鍥為噷鐨� {@code categoryId}銆�
+     */
+    private Long scriptAssortid;
+}
diff --git a/ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/domain/row/SatisfactionOptionScoreRow.java b/ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/domain/row/SatisfactionOptionScoreRow.java
new file mode 100644
index 0000000..41998be
--- /dev/null
+++ b/ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/domain/row/SatisfactionOptionScoreRow.java
@@ -0,0 +1,33 @@
+package com.ruoyi.satisfaction.domain.row;
+
+import lombok.Data;
+
+/**
+ * 閫夐」鍒嗗�煎彧璇昏鍥撅紙鍙彇 scriptid 涓� score 涓ゅ垪锛夈��
+ * <p>
+ * 鐢ㄩ�旓細婊℃剰搴︽槑缁嗙粺璁¢噷绠楁瘡涓棶棰樼殑**鏈�楂樺垎 / 鏈�浣庡垎**銆�
+ * 鍘熷疄鐜板搴�
+ * {@code IvrLibaTemplateTargetoptionMapper.selectIvrLibaTargetoptionScores} 涓�
+ * {@code SvyLibTemplateTargetoptionMapper.selectSvyLibTargetoptionScores}锛�
+ * 涓よ�呭垪涓庤涔夊畬鍏ㄤ竴鑷达紙鍒嗗埆鏉ヨ嚜 {@code ivr_liba_template_targetoption} 涓�
+ * {@code svy_lib_template_targetoption}锛夛紝鎵�浠ュ叡鐢ㄤ竴涓被銆�
+ * <p>
+ * 鈿狅笍 {@code score} 鐢� {@code String} 鎵胯浇锛氬巻鍙叉暟鎹噷瀛樺湪
+ * {@code 0}銆亄@code 0000000001} 杩欑被鑴忓�硷紝鍘熷疄鐜颁篃鏄寜瀛楃涓茶В鏋愶紝
+ * <b>涓嶈兘</b>鏀规垚鏁板瓧绫诲瀷锛屽惁鍒欒В鏋愯涓轰細鍙樸��
+ * <p>
+ * 鈿狅笍 杩欐槸<b>鍙</b>瑙嗗浘銆�
+ */
+@Data
+public class SatisfactionOptionScoreRow {
+
+    /**
+     * 瀵瑰簲鍒� {@code scriptid}锛氶棶棰� id锛堝叧鑱� {@code *_lib_template_script}锛�
+     */
+    private Long scriptid;
+
+    /**
+     * 瀵瑰簲鍒� {@code score}锛氶�夐」鍒嗗�硷紙瀛楃涓诧紝淇濈暀鑴忓�煎師鏍凤級
+     */
+    private String score;
+}
diff --git a/ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/domain/row/SatisfactionScriptQuery.java b/ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/domain/row/SatisfactionScriptQuery.java
new file mode 100644
index 0000000..b524dd8
--- /dev/null
+++ b/ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/domain/row/SatisfactionScriptQuery.java
@@ -0,0 +1,52 @@
+package com.ruoyi.satisfaction.domain.row;
+
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * 婊℃剰搴︾粺璁″彇銆屾弧鎰忓害鍒嗙被 / 妯℃澘闂銆嶆椂鐨勬煡璇㈡潯浠躲��
+ * <p>
+ * 涓轰粈涔堝崟鐙畾涔夛細鍘熷疄鐜扮殑鍏ュ弬澶嶇敤浜� smartor 鐨勫疄浣擄紙{@code IvrLibaScriptAssort} /
+ * {@code SvyLibScriptCategory} / {@code IvrLibaTemplateScript} / {@code SvyLibTemplateScriptVO}锛夛紝
+ * 杩欎簺瀹炰綋涓嶈兘鎼繘鏈ā鍧椼�傛弧鎰忓害瀹為檯鍙敤鍒颁笅闈㈣繖鍑犱釜鏉′欢锛屾晠鏀舵暃涓轰竴涓煡璇㈠璞°��
+ * <p>
+ * 鍚勬潯浠朵笌鍘熸潵閫愬瓧瀵瑰簲锛�
+ * <ul>
+ *   <li>{@code orgid} 鈥斺�� 鎸夋満鏋勮繃婊ゅ垎绫伙紙{@code and orgid = #{orgid}}锛夛紱</li>
+ *   <li>{@code type} 鈥斺�� 鎸夊垎绫荤被鍨嬭繃婊わ紝鍘熷疄鐜版槸 {@code like concat('%', type, '%')}锛�
+ *       婊℃剰搴︿紶 {@code "myd"}锛�</li>
+ *   <li>{@code scriptAssortids} 鈥斺�� 鎸夎闊冲垎绫� id 鍙栭棶棰橈紙{@code script_assortid in (...)}锛夛紱</li>
+ *   <li>{@code scriptCategoryids} 鈥斺�� 鎸夐棶鍗峰垎绫� id 鍙栭棶棰橈紙{@code categoryid in (...)}锛夛紱</li>
+ *   <li>{@code dimensionList} 鈥斺�� 鎸夌淮搴﹀彇闂銆傚師瀹炵幇锛氶潪绌烘椂 {@code dimension in (...)}锛�
+ *       <b>涓虹┖鏃�</b>璇煶渚ф嫾 {@code dimension IS NOT NULL}銆侀棶鍗蜂晶涓嶆嫾璇ユ潯浠讹紙鍙e緞涓嶅悓锛屽繀椤讳繚鐣欙級銆�</li>
+ * </ul>
+ */
+@Data
+public class SatisfactionScriptQuery {
+
+    /**
+     * 鏈烘瀯 id
+     */
+    private String orgid;
+
+    /**
+     * 鍒嗙被绫诲瀷锛堟弧鎰忓害鍥哄畾浼� "myd"锛�
+     */
+    private String type;
+
+    /**
+     * 璇煶鍒嗙被 id 鍒楄〃锛坽@code ivr_liba_script_assort.id}锛�
+     */
+    private List<Long> scriptAssortids;
+
+    /**
+     * 闂嵎鍒嗙被 id 鍒楄〃锛坽@code svy_lib_script_category.id}锛�
+     */
+    private List<Long> scriptCategoryids;
+
+    /**
+     * 缁村害鍒楄〃
+     */
+    private List<String> dimensionList;
+}
diff --git a/ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/domain/row/SatisfactionSubtaskQuery.java b/ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/domain/row/SatisfactionSubtaskQuery.java
new file mode 100644
index 0000000..f2ca5ae
--- /dev/null
+++ b/ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/domain/row/SatisfactionSubtaskQuery.java
@@ -0,0 +1,70 @@
+package com.ruoyi.satisfaction.domain.row;
+
+import lombok.Data;
+
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 婊℃剰搴︾粺璁″彇銆屽瓙浠诲姟銆嶆椂鐨勬煡璇㈡潯浠躲��
+ * <p>
+ * 瀵瑰簲鍘熷疄鐜� {@code ServiceSubtaskServiceImpl.getServiceSubtasks(ServiceSubtaskEntity)}
+ * 閲� {@code sltdMydTotalByScore} 瀹為檯鐢ㄥ埌鐨勬潯浠躲�傚師鏂规硶杩樻敮鎸佽繎鐧句釜鍏跺畠瀛楁锛�
+ * 浣嗘弧鎰忓害鍙敤鍒颁笅闈㈣繖浜涳紝鏁呮敹鏁涗负鏈煡璇㈠璞°��
+ * <p>
+ * 姣忎釜瀛楁涓庡師鏉ョ殑 SQL 鏉′欢<b>閫愬瓧瀵瑰簲</b>锛堣 {@code SatisfactionMapper.xml}锛夛細
+ * <ul>
+ *   <li>{@code orgid}锛歿@code and orgid = #{orgid}}锛堥潪绌烘墠鎷硷級</li>
+ *   <li>{@code campusid}锛歿@code and campusid = #{campusid}}锛堥潪绌烘墠鎷硷級</li>
+ *   <li>{@code serviceType}锛歿@code and service_type = #{serviceType}}锛堥潪绌烘墠鎷硷紱璋冪敤鏂归�愪釜 serviceType 鏌ヨ鍐嶅悎骞讹級</li>
+ *   <li>{@code startOutHospTime} / {@code endOutHospTime}锛氭寜 {@code endtime} 鐨� yymmdd 姣旇緝</li>
+ *   <li>{@code deptOrDistrict} 涓� {@code 1} 鏃讹紝鎸� {@code leavehospitaldistrictcodes} 杩囨护鐥呭尯</li>
+ *   <li>{@code leaveldeptcodes}锛氭寜 {@code deptcode} 杩囨护绉戝</li>
+ * </ul>
+ */
+@Data
+public class SatisfactionSubtaskQuery {
+
+    /**
+     * 鏈烘瀯 id
+     */
+    private String orgid;
+
+    /**
+     * 闄㈠尯 id
+     */
+    private String campusid;
+
+    /**
+     * 鏈嶅姟绫诲瀷锛堟弧鎰忓害涓� 6 浣忛櫌 / 14 闂ㄨ瘖锛�
+     */
+    private String serviceType;
+
+    /**
+     * 鍑洪櫌寮�濮嬫椂闂达紙鎸� endtime 杩囨护锛�
+     */
+    private Date startOutHospTime;
+
+    /**
+     * 鍑洪櫌缁撴潫鏃堕棿锛堟寜 endtime 杩囨护锛�
+     */
+    private Date endOutHospTime;
+
+    /**
+     * 缁村害鏍囪瘑锛歿@code 1} 琛ㄧず鎸夌梾鍖鸿繃婊�
+     * <p>
+     * 鈿狅笍 鍘� SQL 鐢ㄧ殑鏄� {@code <if test="deptOrDistrict==1">}锛堜笌鏁板瓧姣旇緝锛夛紝
+     * 鍥犳杩欓噷鏄� {@code Integer}锛屽彇鍊艰涔変笌鍘熸潵涓�鑷淬��
+     */
+    private String deptOrDistrict = "1";
+
+    /**
+     * 鐥呭尯浠g爜鍒楄〃锛堜粎鍦� {@code deptOrDistrict == 1} 鏃剁敓鏁堬級
+     */
+    private List<String> leavehospitaldistrictcodes;
+
+    /**
+     * 绉戝浠g爜鍒楄〃
+     */
+    private List<String> leaveldeptcodes;
+}
diff --git a/ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/domain/row/SatisfactionSubtaskRef.java b/ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/domain/row/SatisfactionSubtaskRef.java
new file mode 100644
index 0000000..713e4a9
--- /dev/null
+++ b/ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/domain/row/SatisfactionSubtaskRef.java
@@ -0,0 +1,30 @@
+package com.ruoyi.satisfaction.domain.row;
+
+import lombok.Data;
+
+/**
+ * 婊℃剰搴︾粺璁$敤鐨勩�屽瓙浠诲姟寮曠敤銆嶅彧璇昏鍥撅細鍙彇 id 涓� taskid銆�
+ * <p>
+ * 涓轰粈涔堥渶瑕佸畠锛�
+ * <ul>
+ *   <li>{@code sltdMydTotalByScore} 鍘熸湰鎶婃煡璇㈢粨鏋滆浆鎴� smartor 鐨� {@code ServiceSubtaskRes}锛�
+ *       浣嗗悗缁彧鐢ㄥ埌 {@code getId()} 涓� {@code getTaskid()} 涓や釜瀛楁
+ *       锛堜緵 {@code getDetailRatio} 鎶藉嚭 subtaskIds / taskIds锛夛紱</li>
+ *   <li>鏈ā鍧楄姹備笉渚濊禆 smartor 鐨勫疄浣擄紝鏁呯敤杩欎釜鏋佺畝鍙瑙嗗浘鎵胯浇锛�</li>
+ *   <li>鍙� select 涓ゅ垪锛岄伩鍏嶅儚鍘熷疄鐜伴偅鏍锋妸鎺ヨ繎鍏ㄨ〃鐨勫瓧娈垫煡鍥� Java銆�</li>
+ * </ul>
+ * 鈿狅笍 杩欐槸<b>鍙</b>瑙嗗浘銆�
+ */
+@Data
+public class SatisfactionSubtaskRef {
+
+    /**
+     * 瀵瑰簲鍒� {@code id}锛氬瓙浠诲姟 id锛堟弧鎰忓害 6/14 鍒嗘敮鐢ㄥ畠缁熻閫夐」鍗犳瘮锛�
+     */
+    private Long id;
+
+    /**
+     * 瀵瑰簲鍒� {@code taskid}锛氫换鍔� id锛堥殢璁� 2 鍒嗘敮鐢ㄥ畠缁熻閫夐」鍗犳瘮锛�
+     */
+    private Long taskid;
+}
diff --git a/ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/domain/row/SatisfactionSvyScriptRow.java b/ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/domain/row/SatisfactionSvyScriptRow.java
new file mode 100644
index 0000000..91dff00
--- /dev/null
+++ b/ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/domain/row/SatisfactionSvyScriptRow.java
@@ -0,0 +1,77 @@
+package com.ruoyi.satisfaction.domain.row;
+
+import lombok.Data;
+
+/**
+ * 婊℃剰搴︾粺璁$敤鐨勩�岄棶鍗锋ā鏉块棶棰樸�嶅彧璇昏鍥俱��
+ * <p>
+ * 涓轰粈涔堥渶瑕佽繖涓被锛堣�屼笉鏄洿鎺ョ敤 {@code com.smartor.domain.SvyLibTemplateScript}锛夛細
+ * <ul>
+ *   <li>閭d釜瀹炰綋灞炰簬 smartor 鐨�**闂嵎妯℃澘绠$悊**涓氬姟锛岃 13 涓枃浠朵娇鐢紙鍚ā鏉垮鍒犳敼鏌ワ級锛屼笉鑳芥惉杩涙湰妯″潡锛�</li>
+ *   <li>婊℃剰搴︾粺璁″彧鐢ㄥ埌瀹冪殑 5 涓瓧娈碉紙瑙佷笅锛夛紱</li>
+ *   <li>鏈ā鍧楄姹傘�屼笉渚濊禆 smartor銆嶏紝鎵�浠ヤ笉鑳� import 瀹冦��</li>
+ * </ul>
+ * <p>
+ * 鈿狅笍 閲嶈鍙e緞宸紓锛堝師瀹炵幇灏辨湁锛屽繀椤讳繚鐣欙級锛�
+ * <ul>
+ *   <li>{@code querySvyLibTemplateIds}锛堟寜鍒嗙被鍙栭棶棰橈級锛氳仛鍚堢粺璁$敤鐨勬ā鏉� id 鏄� {@code svyid} 鍒楋紝
+ *       鑰屽彇閫夐」鍒嗗�肩敤鐨勫叧鑱� id 鏄� {@code id} 鍒楋紙{@code svy_lib_template_script} 涓婚敭锛夛紱</li>
+ *   <li>{@code selectSLTScriptListBydimension}锛堟寜缁村害鍙栭棶棰橈級锛氫袱鑰呴兘鐢� {@code svyid}銆�</li>
+ * </ul>
+ * 涓轰繚鎸佷笌琚縼绉诲疄鐜板畬鍏ㄤ竴鑷达紝鏈被<b>鍚屾椂</b>淇濈暀 {@code svyid} 涓� {@code id}锛�
+ * 鐢卞悇鏌ヨ鑷鍐冲畾鍙栧摢涓�鍒楋紙涓庡師鏉ラ�愬瓧瀵瑰簲锛夈��
+ * <p>
+ * 鈿狅笍 杩欐槸<b>鍙</b>瑙嗗浘锛屾湰妯″潡涓嶄細瀵瑰畠鍋氫换浣曞啓鎿嶄綔銆�
+ */
+@Data
+public class SatisfactionSvyScriptRow {
+
+    /**
+     * 瀵瑰簲鍒� {@code svyid}锛氶棶鍗锋ā鏉� id銆�
+     */
+    private Long svyid;
+
+    /**
+     * 瀵瑰簲鍒� {@code id}锛歿@code svy_lib_template_script} 涓婚敭銆�
+     * <p>鎸夊垎绫绘煡璇㈡椂锛岄�夐」琛� {@code svy_lib_template_targetoption} 鎸夊畠鍏宠仈銆�
+     */
+    private Long id;
+
+    /**
+     * 瀵瑰簲鍒� {@code script_content}锛氶棶棰樺唴瀹广��
+     */
+    private String scriptContent;
+
+    /**
+     * 瀵瑰簲鍒� {@code categoryName}锛氶棶鍗锋弧鎰忓害鍒嗙被鍚嶇О銆�
+     * <p>鍒嗙被鍙e緞缁熻锛坽@code satisfactionGraph}锛夋寜瀹冨垎缁勩��
+     */
+    private String categoryName;
+
+    /**
+     * 瀵瑰簲鍒� {@code dimension}锛氱淮搴︺��
+     */
+    private String dimension;
+
+    // ==================== 浠呫�屾弧鎰忓害寮傚父閰嶇疆棰樼洰銆嶆帴鍙d娇鐢� ====================
+    // 涓嬮潰 3 涓瓧娈靛彧鏈� /smartor/trace/getSatisfactionExceptConfigQuestion 闇�瑕侊紝
+    // 缁熻鍙e緞锛坰atisfactionGraph / satisfactionGraphDimension锛夌殑鏌ヨ涓嶅彇杩欎簺鍒楋紝淇濇寔涓� null銆�
+
+    /**
+     * 瀵瑰簲鍒� {@code script_topic}锛氶鐩富棰樸��
+     * <p>銆屾弧鎰忓害寮傚父閰嶇疆棰樼洰銆嶆帴鍙f妸瀹冧綔涓鸿繑鍥為噷鐨� {@code questionTopic}銆�
+     */
+    private String scriptTopic;
+
+    /**
+     * 瀵瑰簲鍒� {@code script_type}锛氶鐩被鍨嬨��
+     * <p>銆屾弧鎰忓害寮傚父閰嶇疆棰樼洰銆嶆帴鍙f妸瀹冧綔涓鸿繑鍥為噷鐨� {@code questionType}銆�
+     */
+    private String scriptType;
+
+    /**
+     * 瀵瑰簲鍒� {@code categoryid}锛氭墍灞為棶鍗峰垎绫� id銆�
+     * <p>銆屾弧鎰忓害寮傚父閰嶇疆棰樼洰銆嶆帴鍙f妸瀹冧綔涓鸿繑鍥為噷鐨� {@code categoryId}銆�
+     */
+    private Long categoryid;
+}
diff --git a/ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/domain/row/SatisfactionTaskRow.java b/ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/domain/row/SatisfactionTaskRow.java
new file mode 100644
index 0000000..0b96dea
--- /dev/null
+++ b/ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/domain/row/SatisfactionTaskRow.java
@@ -0,0 +1,47 @@
+package com.ruoyi.satisfaction.domain.row;
+
+import lombok.Data;
+
+/**
+ * 銆岄殢璁挎弧鎰忓害鎸囨暟銆嶇粺璁$敤鐨� {@code service_task} 鍙瑙嗗浘銆�
+ * <p>
+ * 涓轰粈涔堥渶瑕佽繖涓被锛堣�屼笉鏄洿鎺ョ敤 {@code com.smartor.domain.ServiceTask}锛夛細
+ * <ul>
+ *   <li>閭d釜瀹炰綋灞炰簬 smartor 鐨�**闅忚浠诲姟绠$悊**涓氬姟锛堣澶ч噺鏂囦欢浣跨敤锛屽惈澧炲垹鏀规煡锛夛紝涓嶈兘鎼繘鏈ā鍧楋紱</li>
+ *   <li>婊℃剰搴︽寚鏁扮粺璁″彧鐢ㄥ埌瀹冪殑 4 涓瓧娈碉紙瑙佷笅锛夛紱</li>
+ *   <li>鏈ā鍧楄姹傘�屼笉渚濊禆 smartor銆嶏紝鎵�浠ヤ笉鑳� import 瀹冦��</li>
+ * </ul>
+ * <p>
+ * 瀵瑰簲鍘熷疄鐜帮細{@code ServiceTaskMapper.selectServiceTaskList}锛堢粡
+ * {@code ServiceTaskServiceImpl.selectServiceTaskList} 璋冪敤锛夛紝
+ * 璋冪敤鏂瑰彧鍙� {@code getType()} / {@code getTemplateid()} / {@code getTaskid()} / {@code getOrgid()}銆�
+ * <p>
+ * 鈿狅笍 杩欐槸<b>鍙</b>瑙嗗浘锛屾湰妯″潡涓嶄細瀵瑰畠鍋氫换浣曞啓鎿嶄綔銆�
+ */
+@Data
+public class SatisfactionTaskRow {
+
+    /**
+     * 瀵瑰簲鍒� {@code taskid}锛氶殢璁夸换鍔� id銆�
+     * <p>鐢ㄤ簬鎸� {@code taskid} 缁熻鍙戦�侀噺锛坽@code service_subtask}锛変笌濉姤閲忥紙{@code service_subtask_detail}锛夈��
+     */
+    private Long taskid;
+
+    /**
+     * 瀵瑰簲鍒� {@code templateid}锛氫换鍔℃墍鐢ㄦā鏉� id銆�
+     * <p>鐢ㄤ簬鏌ヨ浠诲姟妯℃澘涓嬬殑婊℃剰搴﹂鐩暟閲忥紙璇煶 {@code ivr_task_template_script} /
+     * 闂嵎 {@code svy_task_template_script}锛夈��
+     */
+    private Long templateid;
+
+    /**
+     * 瀵瑰簲鍒� {@code type}锛氫换鍔$被鍨嬶紙{@code "1"}=璇煶锛寋@code "2"}=闂嵎锛夈��
+     */
+    private String type;
+
+    /**
+     * 瀵瑰簲鍒� {@code orgid}锛氭満鏋� id銆�
+     * <p>鍘熷疄鐜版妸瀹冮�忎紶缁欏悗缁� 3 鏉℃煡璇㈢殑 {@code orgid} 鏉′欢銆�
+     */
+    private String orgid;
+}
diff --git a/smartor/src/main/java/com/smartor/domain/VO/MydTrendReqVO.java b/ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/domain/vo/MydTrendReqVO.java
similarity index 96%
rename from smartor/src/main/java/com/smartor/domain/VO/MydTrendReqVO.java
rename to ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/domain/vo/MydTrendReqVO.java
index 740e060..99c3027 100644
--- a/smartor/src/main/java/com/smartor/domain/VO/MydTrendReqVO.java
+++ b/ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/domain/vo/MydTrendReqVO.java
@@ -1,4 +1,4 @@
-package com.smartor.domain.VO;
+package com.ruoyi.satisfaction.domain.vo;
 
 import com.fasterxml.jackson.annotation.JsonFormat;
 import com.ruoyi.common.annotation.Excel;
diff --git a/smartor/src/main/java/com/smartor/domain/VO/MzMydScoreRankVO.java b/ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/domain/vo/MzMydScoreRankVO.java
similarity index 97%
rename from smartor/src/main/java/com/smartor/domain/VO/MzMydScoreRankVO.java
rename to ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/domain/vo/MzMydScoreRankVO.java
index 48c9445..f5bb4cc 100644
--- a/smartor/src/main/java/com/smartor/domain/VO/MzMydScoreRankVO.java
+++ b/ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/domain/vo/MzMydScoreRankVO.java
@@ -1,4 +1,4 @@
-package com.smartor.domain.VO;
+package com.ruoyi.satisfaction.domain.vo;
 
 import com.fasterxml.jackson.annotation.JsonFormat;
 import com.ruoyi.common.core.domain.BaseEntity;
diff --git a/smartor/src/main/java/com/smartor/domain/VO/PatSatisfactionReqVO.java b/ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/domain/vo/PatSatisfactionReqVO.java
similarity index 95%
rename from smartor/src/main/java/com/smartor/domain/VO/PatSatisfactionReqVO.java
rename to ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/domain/vo/PatSatisfactionReqVO.java
index 5b8f0ef..a665a83 100644
--- a/smartor/src/main/java/com/smartor/domain/VO/PatSatisfactionReqVO.java
+++ b/ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/domain/vo/PatSatisfactionReqVO.java
@@ -1,8 +1,8 @@
-package com.smartor.domain.VO;
+package com.ruoyi.satisfaction.domain.vo;
 
 import com.fasterxml.jackson.annotation.JsonFormat;
 import com.ruoyi.common.core.domain.BaseEntity;
-import com.smartor.domain.entity.PatSatisfactionDetailEntity;
+import com.ruoyi.satisfaction.domain.entity.PatSatisfactionDetailEntity;
 import io.swagger.annotations.ApiModel;
 import io.swagger.annotations.ApiModelProperty;
 import lombok.Data;
diff --git a/smartor/src/main/java/com/smartor/domain/VO/PatSatisfactionResVO.java b/ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/domain/vo/PatSatisfactionResVO.java
similarity index 89%
rename from smartor/src/main/java/com/smartor/domain/VO/PatSatisfactionResVO.java
rename to ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/domain/vo/PatSatisfactionResVO.java
index e985f8a..746a0ef 100644
--- a/smartor/src/main/java/com/smartor/domain/VO/PatSatisfactionResVO.java
+++ b/ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/domain/vo/PatSatisfactionResVO.java
@@ -1,8 +1,8 @@
-package com.smartor.domain.VO;
+package com.ruoyi.satisfaction.domain.vo;
 
 import com.fasterxml.jackson.annotation.JsonFormat;
 import com.ruoyi.common.core.domain.BaseEntity;
-import com.smartor.domain.entity.PatSatisfactionDetailEntity;
+import com.ruoyi.satisfaction.domain.entity.PatSatisfactionDetailEntity;
 import io.swagger.annotations.ApiModel;
 import io.swagger.annotations.ApiModelProperty;
 import lombok.Data;
diff --git a/smartor/src/main/java/com/smartor/domain/VO/QuestionCountKsOrBqVO.java b/ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/domain/vo/QuestionCountKsOrBqVO.java
similarity index 94%
rename from smartor/src/main/java/com/smartor/domain/VO/QuestionCountKsOrBqVO.java
rename to ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/domain/vo/QuestionCountKsOrBqVO.java
index 16724ca..7412daf 100644
--- a/smartor/src/main/java/com/smartor/domain/VO/QuestionCountKsOrBqVO.java
+++ b/ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/domain/vo/QuestionCountKsOrBqVO.java
@@ -1,9 +1,9 @@
-package com.smartor.domain.VO;
+package com.ruoyi.satisfaction.domain.vo;
 
 import com.fasterxml.jackson.annotation.JsonFormat;
 import com.ruoyi.common.annotation.Excel;
 import com.ruoyi.common.core.domain.BaseEntity;
-import com.smartor.domain.ServiceSubtaskDetailRatioExport;
+import com.ruoyi.satisfaction.domain.ServiceSubtaskDetailRatioExport;
 import io.swagger.annotations.ApiModel;
 import io.swagger.annotations.ApiModelProperty;
 import lombok.Data;
diff --git a/smartor/src/main/java/com/smartor/domain/VO/SatisfactionExceptConfigQuestionReqVO.java b/ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/domain/vo/SatisfactionExceptConfigQuestionReqVO.java
similarity index 96%
rename from smartor/src/main/java/com/smartor/domain/VO/SatisfactionExceptConfigQuestionReqVO.java
rename to ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/domain/vo/SatisfactionExceptConfigQuestionReqVO.java
index a809375..5adb4dc 100644
--- a/smartor/src/main/java/com/smartor/domain/VO/SatisfactionExceptConfigQuestionReqVO.java
+++ b/ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/domain/vo/SatisfactionExceptConfigQuestionReqVO.java
@@ -1,4 +1,4 @@
-package com.smartor.domain.VO;
+package com.ruoyi.satisfaction.domain.vo;
 
 import com.fasterxml.jackson.annotation.JsonFormat;
 import com.ruoyi.common.annotation.Excel;
diff --git a/smartor/src/main/java/com/smartor/domain/VO/SltdMydTotalVO.java b/ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/domain/vo/SltdMydTotalVO.java
similarity index 98%
rename from smartor/src/main/java/com/smartor/domain/VO/SltdMydTotalVO.java
rename to ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/domain/vo/SltdMydTotalVO.java
index 73899fa..13e34d8 100644
--- a/smartor/src/main/java/com/smartor/domain/VO/SltdMydTotalVO.java
+++ b/ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/domain/vo/SltdMydTotalVO.java
@@ -1,4 +1,4 @@
-package com.smartor.domain.VO;
+package com.ruoyi.satisfaction.domain.vo;
 
 import com.fasterxml.jackson.annotation.JsonFormat;
 import com.ruoyi.common.annotation.Excel;
diff --git a/ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/mapper/PatSatisfactionMapper.java b/ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/mapper/PatSatisfactionMapper.java
new file mode 100644
index 0000000..d0e6648
--- /dev/null
+++ b/ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/mapper/PatSatisfactionMapper.java
@@ -0,0 +1,67 @@
+package com.ruoyi.satisfaction.mapper;
+
+import com.ruoyi.satisfaction.domain.PatSatisfaction;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.List;
+
+/**
+ * 鎮h�呮弧鎰忓害 Mapper 鎺ュ彛銆�
+ * <p>
+ * 杩佺Щ璇存槑锛氬師涓� {@code com.smartor.mapper.PatSatisfactionMapper}锛屾暣浣撴惉鍏ユ弧鎰忓害妯″潡銆�
+ * 瀵瑰簲 XML锛歿@code ruoyi-satisfaction/src/main/resources/mapper/satisfaction/PatSatisfactionMapper.xml}
+ *
+ * @author lihu
+ * @date 2025-06-30
+ */
+@Mapper
+public interface PatSatisfactionMapper {
+
+    /**
+     * 鏌ヨ鎮h�呮弧鎰忓害
+     *
+     * @param id 鎮h�呮弧鎰忓害涓婚敭
+     * @return 鎮h�呮弧鎰忓害
+     */
+    PatSatisfaction selectPatSatisfactionById(Long id);
+
+    /**
+     * 鏌ヨ鎮h�呮弧鎰忓害鍒楄〃
+     *
+     * @param patSatisfaction 鎮h�呮弧鎰忓害
+     * @return 鎮h�呮弧鎰忓害闆嗗悎
+     */
+    List<PatSatisfaction> selectPatSatisfactionList(PatSatisfaction patSatisfaction);
+
+    /**
+     * 鏂板鎮h�呮弧鎰忓害
+     *
+     * @param patSatisfaction 鎮h�呮弧鎰忓害
+     * @return 缁撴灉
+     */
+    int insertPatSatisfaction(PatSatisfaction patSatisfaction);
+
+    /**
+     * 淇敼鎮h�呮弧鎰忓害
+     *
+     * @param patSatisfaction 鎮h�呮弧鎰忓害
+     * @return 缁撴灉
+     */
+    int updatePatSatisfaction(PatSatisfaction patSatisfaction);
+
+    /**
+     * 鍒犻櫎鎮h�呮弧鎰忓害
+     *
+     * @param id 鎮h�呮弧鎰忓害涓婚敭
+     * @return 缁撴灉
+     */
+    int deletePatSatisfactionById(Long id);
+
+    /**
+     * 鎵归噺鍒犻櫎鎮h�呮弧鎰忓害
+     *
+     * @param ids 闇�瑕佸垹闄ょ殑鏁版嵁涓婚敭闆嗗悎
+     * @return 缁撴灉
+     */
+    int deletePatSatisfactionByIds(Long[] ids);
+}
diff --git a/ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/mapper/SatisfactionMapper.java b/ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/mapper/SatisfactionMapper.java
new file mode 100644
index 0000000..44d3189
--- /dev/null
+++ b/ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/mapper/SatisfactionMapper.java
@@ -0,0 +1,296 @@
+package com.ruoyi.satisfaction.mapper;
+
+import com.ruoyi.satisfaction.domain.ServiceSubtaskDetailRatioExport;
+import com.ruoyi.satisfaction.domain.dto.MydTrendReqDTO;
+import com.ruoyi.satisfaction.domain.dto.MzMydScoreRankDTO;
+import com.ruoyi.satisfaction.domain.dto.QuestionCountKsOrBqDTO;
+import com.ruoyi.satisfaction.domain.dto.ServiceSubtaskOptionStatDTO;
+import com.ruoyi.satisfaction.domain.entity.SatisfactionStatQuery;
+import com.ruoyi.satisfaction.domain.row.SatisfactionSubtaskQuery;
+import com.ruoyi.satisfaction.domain.row.SatisfactionSubtaskRef;
+import com.ruoyi.satisfaction.domain.row.SatisfactionTaskRow;
+import com.ruoyi.satisfaction.domain.vo.MzMydScoreRankVO;
+import com.ruoyi.satisfaction.domain.vo.PatSatisfactionReqVO;
+import com.ruoyi.satisfaction.domain.vo.QuestionCountKsOrBqVO;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 婊℃剰搴︾粺璁� Mapper 鎺ュ彛銆�
+ * <p>
+ * 杩佺Щ璇存槑锛氭柟娉曞師鏈暎钀藉湪 {@code com.smartor.mapper.ServiceSubtaskMapper} 涓�
+ * {@code com.smartor.mapper.ServiceSubtaskDetailMapper} 涓紝婊℃剰搴︽ā鍧楀寲鍚庨泦涓埌姝ゅ銆�
+ * <p>
+ * 鏈ā鍧楄嚜甯� SQL锛�<b>鐩存帴鏌ヨ</b> {@code service_subtask} / {@code service_subtask_detail}锛�
+ * 浠ュ強妯℃澘鍒嗙被琛紱涓嶄緷璧栦换浣曞叾瀹冩ā鍧楃殑 Mapper銆�
+ * <p>
+ * 瀵瑰簲 XML锛歿@code ruoyi-satisfaction/src/main/resources/mapper/satisfaction/SatisfactionMapper.xml}
+ * <p>
+ * 宸插垹闄ょ殑姝讳唬鐮侊紙鎼縼鏃剁‘璁ら浂璋冪敤鐐癸級锛�
+ * <ul>
+ *   <li>{@code selectSatisfactionSubtaskList} 鈥斺�� 琚� {@code selectSatisfactionSubtaskTypes} 鏇夸唬锛�</li>
+ *   <li>{@code querySendCount} 鈥斺�� 琚� {@code selectSatisfactionSendCount} 鏇夸唬銆�</li>
+ * </ul>
+ */
+@Mapper
+public interface SatisfactionMapper {
+
+    // ================================================================ 鍙戦�侀噺缁熻
+
+    /**
+     * 婊℃剰搴︽煴鐘跺浘锛氫竴娆℃�х粺璁℃湰娆¤姹傚叏閮ㄦā鏉跨殑鍙戦�侀噺锛堟寜 libtemplateid 鍒嗙粍锛夈��
+     *
+     * @param query 鑱氬悎鏌ヨ鏉′欢
+     * @return 姣忚鍚� libtemplateid銆乧nt
+     */
+    List<Map<String, Object>> selectSatisfactionSendCount(SatisfactionStatQuery query);
+
+    /**
+     * 婊℃剰搴︾粺璁★細鍙煡鏈鏉′欢涓嬪疄闄呭瓨鍦ㄥ摢浜� type锛�1 璇煶 / 2 闂嵎锛夈��
+     *
+     * @param patSatisfactionReqVO 鏌ヨ鏉′欢
+     * @return 鍘婚噸鍚庣殑 type 鍒楄〃锛堝彲鑳戒负 null锛�
+     */
+    List<String> selectSatisfactionSubtaskTypes(PatSatisfactionReqVO patSatisfactionReqVO);
+
+    // ================================================================ 濉姤閲忕粺璁�
+
+    /**
+     * 婊℃剰搴︽煴鐘跺浘锛氫竴娆℃�х粺璁℃湰娆¤姹傚叏閮ㄦā鏉跨殑濉姤閲忥紙鎸� 妯℃澘+闂+score 鍒嗙粍锛夈��
+     *
+     * @param query 鑱氬悎鏌ヨ鏉′欢
+     * @return 姣忚鍚� libtemplateid銆乹uestiontext銆乻core銆乧nt
+     */
+    List<Map<String, Object>> selectSatisfactionFillStat(SatisfactionStatQuery query);
+
+    /**
+     * 婊℃剰搴︾粺璁★細涓�娆℃�х粺璁℃湰娆¤姹傚叏閮ㄩ棶棰樼殑濉姤閲忥紙鎸� 妯℃澘+闂+matchedtext+score 鍒嗙粍锛夈��
+     *
+     * @param query 鑱氬悎鏌ヨ鏉′欢
+     * @return 姣忚鍚� libtemplateid銆乹uestiontext銆乵atchedtext銆乻core銆乧nt銆乼argetvalue
+     */
+    List<Map<String, Object>> selectSatisfactionFillStatByMatchedtext(SatisfactionStatQuery query);
+
+    /**
+     * 婊℃剰搴︽煴鐘跺浘锛氫竴娆℃�х粺璁℃湰娆¤姹傚叏閮ㄦā鏉跨殑銆屽洖鏀朵汉鏁般�嶃��
+     * <p>
+     * 鍥炴敹浜烘暟蹇呴』鏄汉鏁板彛寰勶紙count distinct sub_id锛夛紝涓嶈兘鎸夐鐩眹鎬荤瓟妗堣鏁帮紙棰橀噺鍙e緞锛夈��
+     *
+     * @param query 鑱氬悎鏌ヨ鏉′欢
+     * @return 姣忚鍚� libtemplateid銆乧nt
+     */
+    List<Map<String, Object>> selectSatisfactionFillPersonCount(SatisfactionStatQuery query);
+
+    // ================================================================ 鐪佺珛鍚屽痉锛氳秼鍔夸笌缁村害
+
+    /**
+     * 鐪佺珛鍚屽痉鎮h�呮弧鎰忓害瓒嬪娍銆�
+     *
+     * @param startFinishTime 寮�濮嬫椂闂�
+     * @param endFinishTime   缁撴潫鏃堕棿
+     * @param type            绮掑害锛歸eek / month / year / day
+     * @return 瓒嬪娍鏁版嵁
+     */
+    List<MydTrendReqDTO> mydTrend(@Param("startFinishTime") Date startFinishTime,
+                                  @Param("endFinishTime") Date endFinishTime,
+                                  @Param("type") String type);
+
+    /**
+     * 鐪佺珛鍚屽痉鎮h�呮弧鎰忓害鈥斺�旀寜绉戝缁村害缁熻銆�
+     */
+    List<MydTrendReqDTO> sltdMydTotalByDimension(@Param("startFinishTime") Date startFinishTime,
+                                                 @Param("endFinishTime") Date endFinishTime,
+                                                 @Param("type") String type);
+
+    /**
+     * 鐪佺珛鍚屽痉鎮h�呮弧鎰忓害鈥斺�旀寜闄㈠尯缁村害缁熻銆�
+     */
+    List<MydTrendReqDTO> sltdMydTotalByYQ(@Param("startFinishTime") Date startFinishTime,
+                                          @Param("endFinishTime") Date endFinishTime,
+                                          @Param("type") String type);
+
+    // ================================================================ 閫夐」鍗犳瘮
+
+    /**
+     * 缁熻婊℃剰搴﹂�夐」鐨勭櫨鍒嗘瘮銆�
+     *
+     * @param taskIds         浠诲姟 ID 鍒楄〃
+     * @param subtaskIds      瀛愪换鍔� ID 鍒楄〃锛堥潪绌烘椂鍙粺璁¤繖浜涘瓙浠诲姟锛涗负绌哄垯缁熻杩欎簺浠诲姟涓嬬殑鍏ㄩ儴瀛愪换鍔★級
+     * @param startOutHospTime 鍑洪櫌寮�濮嬫椂闂�
+     * @param endOutHospTime   鍑洪櫌缁撴潫鏃堕棿
+     * @param startFinishTime  瀹屾垚寮�濮嬫椂闂�
+     * @param endFinishTime    瀹屾垚缁撴潫鏃堕棿
+     * @param scoreStart       鍒嗗�间笅闄�
+     * @param scoreEnd         鍒嗗�间笂闄�
+     * @param questiontext     棰樼洰鍐呭锛堟ā绯婂尮閰嶏級
+     * @param valueType        棰樺瀷锛�2=澶氶�夛級
+     */
+    List<ServiceSubtaskDetailRatioExport> statQuestionOption(
+            @Param("taskIds") List<Long> taskIds,
+            @Param("startOutHospTime") Date startOutHospTime,
+            @Param("endOutHospTime") Date endOutHospTime,
+            @Param("startFinishTime") Date startFinishTime,
+            @Param("endFinishTime") Date endFinishTime,
+            @Param("scoreStart") Double scoreStart,
+            @Param("scoreEnd") Double scoreEnd,
+            @Param("questiontext") String questiontext,
+            @Param("valueType") String valueType,
+            @Param("subtaskIds") List<Long> subtaskIds);
+
+    /**
+     * 婊℃剰搴﹀鍑猴細閫夐」鍗犳瘮銆�
+     * <p>
+     * 椤规暟 = 閫変簡璇ラ�夐」鐨勬偅鑰呮暟锛堝悓涓�浜哄悓涓�棰樺彧鍙栨渶鏂颁竴鏉℃槑缁嗭級锛涘垎鏁� = 璇ラ�夐」鎵�鏈夋槑缁� score 涔嬪拰銆�
+     *
+     * @param subtaskIds   鏈瀵煎嚭鐨勫瓙浠诲姟 ID
+     * @param questiontext 棰樼洰鍐呭锛堝彲閫夛紝妯$硦鍖归厤锛�
+     */
+    List<ServiceSubtaskOptionStatDTO> statMydQuestionOptionForExport(
+            @Param("subtaskIds") List<Long> subtaskIds,
+            @Param("questiontext") String questiontext);
+
+    /**
+     * 鏍规嵁瀛愪换鍔� ID 缁熻婊℃剰搴﹂�夐」鐨勭櫨鍒嗘瘮锛堢敤浜庢弧鎰忓害缁熻 serviceType=6/14锛夈��
+     */
+    List<ServiceSubtaskDetailRatioExport> statQuestionOptionBySubtaskIds(
+            @Param("subtaskIds") List<Long> subtaskIds,
+            @Param("startOutHospTime") Date startOutHospTime,
+            @Param("endOutHospTime") Date endOutHospTime,
+            @Param("startFinishTime") Date startFinishTime,
+            @Param("endFinishTime") Date endFinishTime,
+            @Param("scoreStart") Double scoreStart,
+            @Param("scoreEnd") Double scoreEnd,
+            @Param("questiontext") String questiontext,
+            @Param("valueType") String valueType);
+
+    /**
+     * 鎸夌瀹�/鐥呭尯瀵规弧鎰忓害棰樼洰鍋氬綊绫荤粺璁°��
+     */
+    List<QuestionCountKsOrBqDTO> statQuestionOptionByKsOrBq(QuestionCountKsOrBqVO questionCountKsOrBqVO);
+
+    // ================================================================ 缁煎悎寰楀垎鎺掑悕
+
+    /**
+     * 闂ㄨ瘖婊℃剰搴︾患鍚堝緱鍒嗘帓鍚嶏紙鎸夌瀹ゆ垨鐥呭尯锛夈��
+     * <p>
+     * 缁煎悎婊℃剰搴� = 危(璇勫垎鏄庣粏鏉冮噸) / 璇勫垎鏄庣粏鏉℃暟 脳 100%锛涙潈閲嶅彧鍙� 1銆�0.8銆�0.6銆�0.4銆�0.2 浜旀。銆�
+     *
+     * @param mzMydScoreRankVO 鏌ヨ鏉′欢锛坉imensionType銆乻erviceType銆佹椂闂村尯闂淬�佺瀹�/鐥呭尯缂栧彿銆乷rgid/campusid锛�
+     * @return 姣忚绉戝/鐥呭尯鐨勭患鍚堝緱鍒嗭紝宸叉寜缁煎悎寰楀垎闄嶅簭
+     */
+    List<MzMydScoreRankDTO> selectMzMydScoreRank(MzMydScoreRankVO mzMydScoreRankVO);
+    // ================================================================ 閲嶅鏄庣粏娓呯悊锛堟弧鎰忓害缁熻鍓嶇疆锛�
+
+    /**
+     * 鎵惧嚭銆屽悓涓� sub_id + questiontext銆嶄笅闄ゆ渶鏂颁竴鏉′箣澶栫殑閲嶅鏄庣粏 id銆�
+     * <p>
+     * 鍘熷睘 {@code ServiceSubtaskDetailMapper}锛屽彧鏈夋弧鎰忓害缁熻璋冪敤锛堝凡鏍稿疄锛夛紝鏁呬竴骞舵惉鍏ャ��
+     * <p>
+     * 鈿狅笍 娉ㄦ剰锛氳皟鐢ㄥ畠鐨� {@code statQuestionOption} / {@code statQuestionOptionBySubtaskIds}
+     * 浼氬厛鎵ц鏈煡璇㈠啀鎵ц {@link #deleteRepeatDetailByIds}锛�<b>杩欐槸浼氬啓搴撶殑琛屼负</b>锛堝師瀹炵幇鍗冲姝わ級銆�
+     *
+     * @return 闇�瑕佹竻鐞嗙殑鏄庣粏 id
+     */
+    List<Long> selectRepeatDetailIds();
+
+    /**
+     * 鎸� id 閫昏緫鍒犻櫎閲嶅鏄庣粏锛坽@code del_flag = 1}锛夈��
+     */
+    Integer deleteRepeatDetailByIds(@Param("ids") List<Long> ids);
+    // ================================================================ 瀛愪换鍔★紙婊℃剰搴︾粺璁″彇鏁帮級
+
+    /**
+     * 鎸夋満鏋�/闄㈠尯/鏈嶅姟绫诲瀷/鍑洪櫌鏃堕棿/绉戝鐥呭尯鍙栧瓙浠诲姟锛�<b>鍙繑鍥� id 涓� taskid</b>銆�
+     * <p>
+     * 瀵瑰簲鍘熷疄鐜� {@code ServiceSubtaskServiceImpl.getServiceSubtasks(ServiceSubtaskEntity)}
+     * 涓� {@code sltdMydTotalByScore} 瀹為檯鐢ㄥ埌鐨勫彛寰勩�傚師鏂规硶鍩轰簬
+     * {@code selectServiceSubtaskList}锛堣繎鐧句釜鍙�夋潯浠� + 杩斿洖鍏ㄨ〃瀛楁锛夛紝
+     * 婊℃剰搴﹀彧闇�瑕佷笂杩版潯浠朵笌涓や釜 id 鍒楋紝鍥犳杩欓噷涓撳啓涓�鏉$簿绠�鏌ヨ銆�
+     * <p>
+     * <b>鏉′欢涓庡師鏂囬�愬瓧涓�鑷�</b>锛堝潎銆岄潪绌烘墠鎷笺�嶏級锛�
+     * {@code del_flag = 0}銆亄@code orgid}銆亄@code campusid}銆亄@code service_type}銆�
+     * {@code endtime} 鐨勬棩鏈熷尯闂淬�亄@code deptOrDistrict==1} 鏃剁殑鐥呭尯 IN銆�
+     * {@code leaveldeptcodes} 鐨勭瀹� IN銆�
+     * <p>
+     * 鈿狅笍 鍘熸枃鏃犻粯璁ゆ帓搴忋�佹棤榛樿鍒嗛〉锛坽@code sort}/{@code pageSize} 鏈缃嵆涓嶇敓鏁堬級锛�
+     * 鏈煡璇㈠悓鏍蜂笉鍔犳帓搴忥紝淇濊瘉缁撴灉椤哄簭琛屼负涓�鑷淬��
+     */
+    List<SatisfactionSubtaskRef> selectMydSubtaskRefs(@Param("q") SatisfactionSubtaskQuery query);
+
+    // ================================================================ 闅忚銆屾弧鎰忓害鎸囨暟銆嶅彇鏁�
+    // 浠ヤ笅 5 涓柟娉曟湇鍔′簬 getSatisfactionIndex锛堥殢璁跨粺璁¢〉鐨勩�屾弧鎰忓害棰樼洰鎬婚噺/濉姤閲�/瀹屾垚姣斾緥銆嶏級锛�
+    // 鍘熸湰鏁h惤鍦� smartor 鐨� ServiceTaskMapper / IvrTaskTemplateScriptMapper /
+    // SvyTaskTemplateScriptMapper / ServiceSubtaskMapper / ServiceSubtaskDetailMapper 涓紝
+    // 鐢� ServiceSubtaskServiceImpl#getSatisfactionIndex 閫愪釜璋冪敤銆�
+
+    /**
+     * 鎸夌瀹� <b>鎴�</b> 鐥呭尯鍙栭殢璁夸换鍔★紙鍙彇缁熻鐢ㄥ埌鐨� 4 鍒楋級銆�
+     * <p>
+     * 瀵瑰簲鍘熷疄鐜� {@code ServiceTaskMapper.selectServiceTaskList}锛岃皟鐢ㄦ柟鎸� type 浜岄�変竴浼犲弬锛�
+     * <ul>
+     *   <li>{@code deptcode} 闈炵┖ 鈫� 鎷� {@code deptcode REGEXP CONCAT('(^|,)', #{deptcode}, '(,|$)')}锛�</li>
+     *   <li>{@code leavehospitaldistrictcode} 闈炵┖ 鈫� 鎷煎悓褰㈡�佺殑 REGEXP锛�</li>
+     * </ul>
+     * 涓よ�呭彧浼氭湁涓�涓潪绌猴紝鍥犳鏈柟娉曚竴鏉� SQL 鍗冲彲瑕嗙洊鍘熷疄鐜扮殑涓や釜鍒嗘敮銆�
+     * <p>
+     * <b>鍙e緞閫愬瓧涓�鑷�</b>锛歿@code del_flag = 0}銆亄@code orgid}銆亄@code order by update_time desc, taskid desc}銆�
+     */
+    List<SatisfactionTaskRow> selectSatisfactionServiceTasks(
+            @Param("orgid") String orgid,
+            @Param("deptcode") String deptcode,
+            @Param("leavehospitaldistrictcode") String leavehospitaldistrictcode);
+
+    /**
+     * 缁熻鏌愭ā鏉夸笅鐨勮闊虫弧鎰忓害棰樼洰鏁伴噺銆�
+     * <p>
+     * 瀵瑰簲鍘熷疄鐜� {@code IvrTaskTemplateScriptMapper.selectIvrTaskTemplateScriptList}
+     * 鍦� {@code templateID + orgid + scriptAssortid} 涓変釜鏉′欢涓嬬殑 {@code size()}銆�
+     * <p>
+     * <b>鍙e緞娉ㄦ剰</b>锛歿@code scriptAssortid} 涓� {@code null} 鏃�<b>涓嶆嫾</b>璇ユ潯浠�
+     * 锛堢瓑浜庣粺璁¤妯℃澘鍏ㄩ儴棰樼洰锛夆�斺�斾笌鍘熸枃 {@code <if test="scriptAssortid != null">} 涓�鑷淬��
+     */
+    int countIvrTaskTemplateScripts(@Param("templateID") Long templateID,
+                                    @Param("orgid") String orgid,
+                                    @Param("scriptAssortid") Long scriptAssortid);
+
+    /**
+     * 缁熻鏌愭ā鏉夸笅鐨勯棶鍗锋弧鎰忓害棰樼洰鏁伴噺銆�
+     * <p>
+     * 瀵瑰簲鍘熷疄鐜� {@code SvyTaskTemplateScriptMapper.selectSvyTaskTemplateScriptList}
+     * 鍦� {@code templateID + orgid + categoryid} 涓変釜鏉′欢涓嬬殑 {@code size()}銆�
+     * <p>
+     * <b>鍙e緞娉ㄦ剰</b>锛歿@code categoryid} 涓� {@code null} 鏃�<b>涓嶆嫾</b>璇ユ潯浠讹紙涓庡師鏂囦竴鑷达級銆�
+     */
+    int countSvyTaskTemplateScripts(@Param("templateID") Long templateID,
+                                    @Param("orgid") String orgid,
+                                    @Param("categoryid") Long categoryid);
+
+    /**
+     * 缁熻鏌愪换鍔′笅鎸囧畾鍙戦�佺姸鎬佺殑瀛愪换鍔℃暟閲忋��
+     * <p>
+     * 瀵瑰簲鍘熷疄鐜� {@code ServiceSubtaskMapper.selectServiceSubtaskBySendstate}
+     * 鍦� {@code taskid + sendstates(3,5,6)} 涓や釜鏉′欢涓嬬殑 {@code size()}銆�
+     * 鍘熻皟鐢ㄦ病鏈夎缃� {@code orgid}锛屾晠鏈柟娉曚篃涓嶅惈璇ユ潯浠躲��
+     * <p>
+     * <b>鍙e緞娉ㄦ剰</b>锛氬惈 {@code del_flag = 0}锛泏@code sendstates} 涓� {@code null} 鏃朵笉鎷� IN 鏉′欢銆�
+     */
+    int countSubtaskBySendstates(@Param("taskid") Long taskid,
+                                @Param("sendstates") List<Long> sendstates);
+
+    /**
+     * 缁熻鏌愪换鍔′笅鐨勬弧鎰忓害鏄庣粏鏁伴噺銆�
+     * <p>
+     * 瀵瑰簲鍘熷疄鐜� {@code ServiceSubtaskDetailMapper.selectServiceSubtaskDetailList}
+     * 鍦� {@code taskid + categoryid + orgid} 涓変釜鏉′欢涓嬬殑 {@code size()}銆�
+     * <p>
+     * <b>鍙e緞娉ㄦ剰</b>锛氬師鏂�<b>娌℃湁</b> {@code del_flag} 鏉′欢锛屾湰鏌ヨ鍚屾牱涓嶅姞锛�
+     * {@code categoryid} 涓� {@code null} 鏃朵笉鎷艰鏉′欢锛堜繚鐣欏師鏂囩殑"鎸変换鍔$被鍨嬪喅瀹氭槸鍚﹁ categoryid"琛屼负锛夈��
+     */
+    int countSubtaskDetail(@Param("taskid") Long taskid,
+                           @Param("categoryid") Long categoryid,
+                           @Param("orgid") String orgid);
+}
\ No newline at end of file
diff --git a/ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/mapper/SatisfactionScriptMapper.java b/ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/mapper/SatisfactionScriptMapper.java
new file mode 100644
index 0000000..f215878
--- /dev/null
+++ b/ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/mapper/SatisfactionScriptMapper.java
@@ -0,0 +1,146 @@
+package com.ruoyi.satisfaction.mapper;
+
+import com.ruoyi.satisfaction.domain.row.SatisfactionCategoryRow;
+import com.ruoyi.satisfaction.domain.row.SatisfactionIvrScriptRow;
+import com.ruoyi.satisfaction.domain.row.SatisfactionOptionScoreRow;
+import com.ruoyi.satisfaction.domain.row.SatisfactionScriptQuery;
+import com.ruoyi.satisfaction.domain.row.SatisfactionSvyScriptRow;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 婊℃剰搴︾粺璁″彇銆屾弧鎰忓害鍒嗙被 / 妯℃澘闂 / 閫夐」鍒嗗�笺�嶇殑鍙 Mapper銆�
+ * <p>
+ * <b>涓轰粈涔堜笉澶嶇敤 smartor 鐨勬ā鏉�/鍒嗙被 Mapper锛�</b>
+ * <ul>
+ *   <li>{@code com.smartor.domain.IvrLibaScriptAssort} / {@code IvrLibaTemplateScript} /
+ *       {@code SvyLibScriptCategory} / {@code SvyLibTemplateScript} 杩� 4 涓疄浣撳睘浜�
+ *       smartor 鐨�**妯℃澘涓庡垎绫荤鐞�**涓氬姟锛堝垎鍒 11/9/12/13 涓枃浠朵娇鐢紝鍚鍒犳敼鏌ワ級锛�
+ *       涓嶈兘鎼繘鏈ā鍧楋紱</li>
+ *   <li>鏈ā鍧楄姹傘�屼笉渚濊禆 smartor锛屽叾瀹冩ā鍧楄鐢ㄥ彧鑳借皟鏈ā鍧� api銆嶏紝鎵�浠ヤ笉鑳� import 瀹冧滑锛�</li>
+ *   <li>鍘熸枃杩欎簺鏌ヨ渚濊禆 `resultMap` + 鍏变韩 `<sql>` 鐗囨锛岃�岀墖娈佃鍚屾枃浠堕噷鍏跺畠
+ *       <b>闈炴弧鎰忓害</b>鏌ヨ鍏辩敤銆傜洿鎺ュ鍒剁墖娈典細閫犳垚銆屽悓涓�浠� SQL 涓ゅ缁存姢銆嶇殑闅愭偅锛�
+ *       鍥犳杩欓噷<b>鏀瑰啓涓烘樉寮忓垪娓呭崟</b>锛屽彧鍙栨弧鎰忓害鐪熸鐢ㄥ埌鐨勫瓧娈点��</li>
+ * </ul>
+ * <p>
+ * 鈿狅笍 鏈� Mapper 鍏ㄩ儴鏄�<b>鍙鏌ヨ</b>锛屼笉浼氬 {@code ivr_liba_*} / {@code svy_lib_*} 鍋氫换浣曞啓鎿嶄綔銆�
+ * <p>
+ * 瀵瑰簲 XML锛歿@code mapper/satisfaction/SatisfactionScriptMapper.xml}
+ */
+@Mapper
+public interface SatisfactionScriptMapper {
+
+    // ================================================================ 婊℃剰搴﹀垎绫�
+
+    /**
+     * 鏌ヨ鎸囧畾鏈烘瀯涓嬬殑婊℃剰搴﹀垎绫伙紙璇煶渚э級銆�
+     * <p>
+     * 鍘熷疄鐜帮細{@code IvrLibaScriptAssortMapper.selectIvrLibaScriptAssortList}
+     * <pre>
+     * select id, index_assort_name, type from ivr_liba_script_assort
+     * where del_flag = 0 and orgid = ? and type like '%myd%' order by seqno asc
+     * </pre>
+     * 璋冪敤鏂瑰啀鎸� {@code type.contains("myd")} 杩囨护鍑烘弧鎰忓害鍒嗙被 id銆�
+     *
+     * @param query orgid 涓� type锛坱ype 浼� "myd"锛�
+     * @return 姣忚鍚� id銆乮ndex_assort_name銆乼ype
+     */
+    List<SatisfactionCategoryRow> selectIvrCategoryList(@Param("q") SatisfactionScriptQuery query);
+
+    /**
+     * 鏌ヨ鎸囧畾鏈烘瀯涓嬬殑婊℃剰搴﹀垎绫伙紙闂嵎渚э級銆�
+     * <p>
+     * 鍘熷疄鐜帮細{@code SvyLibScriptCategoryMapper.selectSvyLibScriptCategoryList}
+     * 锛堟敞鎰忥細璇� statement 瀹氫箟鍦� {@code SvyLibTopicCategoryMapper.xml} 閲岋紝namespace 涓庢枃浠跺悕涓嶄竴鑷达級
+     * <pre>
+     * select id, name, type from svy_lib_script_category
+     * where del_flag = 0 and orgid = ? and type like '%myd%' order by seqno asc
+     * </pre>
+     *
+     * @param query orgid 涓� type锛坱ype 浼� "myd"锛�
+     * @return 姣忚鍚� id銆乶ame銆乼ype
+     */
+    List<SatisfactionCategoryRow> selectSvyCategoryList(@Param("q") SatisfactionScriptQuery query);
+
+    // ================================================================ 妯℃澘闂锛堟寜鍒嗙被锛�
+
+    /**
+     * 鎸夎闊冲垎绫� id 鍙栭棶棰樸��
+     * <p>
+     * 鍘熷疄鐜帮細{@code IvrLibaTemplateScriptMapper.queryLibTemplateIds}
+     * <pre>
+     * select templateid, scriptid, script_content, script_assortname, dimension
+     * from ivr_liba_template_script
+     * where del_flag = 0 [and script_assortid in (...)]
+     * </pre>
+     * <b>鍙e緞娉ㄦ剰</b>锛歿@code scriptAssortids} 涓虹┖鏃朵笉鍐嶆嫾璇ユ潯浠讹紙鍘熷疄鐜版槸
+     * {@code if test="scriptAssortids != null and scriptAssortids.size > 0"}锛夈��
+     */
+    List<SatisfactionIvrScriptRow> queryIvrScriptByAssort(@Param("q") SatisfactionScriptQuery query);
+
+    /**
+     * 鎸夐棶鍗峰垎绫� id 鍙栭棶棰樸��
+     * <p>
+     * 鍘熷疄鐜帮細{@code SvyLibTemplateScriptMapper.querySvyLibTemplateIds}
+     * <pre>
+     * select id, svyid, script_content, categoryName, dimension
+     * from svy_lib_template_script
+     * where del_flag = 0 [and categoryid in (...)]
+     * </pre>
+     * <b>鍙e緞娉ㄦ剰</b>锛氬師瀹炵幇鍙栭�夐」鍒嗗�兼椂鐢ㄧ殑鏄� {@code id}锛堜富閿級锛�
+     * 鑰岃仛鍚堢粺璁$殑妯℃澘 id 鐢ㄧ殑鏄� {@code svyid}鈥斺�斾袱鑰呴兘淇濈暀鍦ㄧ粨鏋滆閲屻��
+     */
+    List<SatisfactionSvyScriptRow> querySvyScriptByCategory(@Param("q") SatisfactionScriptQuery query);
+
+    // ================================================================ 妯℃澘闂锛堟寜缁村害锛�
+
+    /**
+     * 鎸夌淮搴﹀彇闂锛堣闊充晶锛夈��
+     * <p>
+     * 鍘熷疄鐜帮細{@code IvrLibaTemplateScriptMapper.selectILTSBydimension}
+     * <pre>
+     * ... where del_flag = 0
+     *     [and dimension in (...)]
+     *     [dimension IS NOT NULL]   鈫� dimensionList 涓虹┖鏃舵嫾杩欎釜
+     * </pre>
+     * <b>鍙e緞娉ㄦ剰</b>锛歿@code dimensionList} 涓虹┖鏃讹紝璇煶渚т細鎷�
+     * {@code dimension IS NOT NULL}锛堥棶鍗蜂晶涓嶄細锛夆�斺�旇繖鏄師瀹炵幇鐨勮涓猴紝蹇呴』淇濈暀銆�
+     * 鍚屾椂鍘熷疄鐜拌繕鏀寔 duty/report 鐩稿叧杩囨护锛屾弧鎰忓害璋冪敤鏃朵笉浼狅紝鏁呬笉鍐嶆毚闇层��
+     */
+    List<SatisfactionIvrScriptRow> selectIvrScriptByDimension(@Param("q") SatisfactionScriptQuery query);
+
+    /**
+     * 鎸夌淮搴﹀彇闂锛堥棶鍗蜂晶锛夈��
+     * <p>
+     * 鍘熷疄鐜帮細{@code SvyLibTemplateScriptMapper.selectSLTScriptListBydimension}
+     * <pre>
+     * ... where del_flag = 0 [and dimension in (...)]
+     * </pre>
+     * <b>鍙e緞娉ㄦ剰</b>锛氫笌璇煶渚т笉鍚岋紝{@code dimensionList} 涓虹┖鏃�<b>涓嶆嫾</b>
+     * {@code dimension IS NOT NULL}锛堢瓑浜庡彇璇ヨ〃鍏ㄩ儴 del_flag=0 鐨勯棶棰橈級銆�
+     * 鍙﹀姝ゅ鑱氬悎缁熻鐢ㄧ殑妯℃澘 id 涓庡彇閫夐」鍒嗗�肩敤鐨勫叧鑱� id <b>閮芥槸 {@code svyid}</b>銆�
+     */
+    List<SatisfactionSvyScriptRow> selectSvyScriptByDimension(@Param("q") SatisfactionScriptQuery query);
+
+    // ================================================================ 閫夐」鍒嗗��
+
+    /**
+     * 鎸夐棶棰� id 鎵归噺鍙栬闊抽�夐」鍒嗗�硷紙鍙彇 scriptid銆乻core 涓ゅ垪锛夈��
+     * <p>
+     * 鍘熷疄鐜帮細{@code IvrLibaTemplateTargetoptionMapper.selectIvrLibaTargetoptionScores}
+     * <p>
+     * <b>鍙e緞娉ㄦ剰</b>锛歿@code scriptIds} 涓� null 鏃跺師瀹炵幇涓嶆嫾 scriptid 鏉′欢
+     * 锛堢瓑浜庡彇璇ヨ〃鍏ㄩ儴鍒嗗�硷級锛岃皟鐢ㄦ柟鐢� null 浣� key 瀛樿繖涓�浠姐��
+     */
+    List<SatisfactionOptionScoreRow> selectIvrTargetOptionScores(@Param("scriptIds") List<Long> scriptIds);
+
+    /**
+     * 鎸夐棶棰� id 鎵归噺鍙栭棶鍗烽�夐」鍒嗗�硷紙鍙彇 scriptid銆乻core 涓ゅ垪锛夈��
+     * <p>
+     * 鍘熷疄鐜帮細{@code SvyLibTemplateTargetoptionMapper.selectSvyLibTargetoptionScores}
+     */
+    List<SatisfactionOptionScoreRow> selectSvyTargetOptionScores(@Param("scriptIds") List<Long> scriptIds);
+}
diff --git a/ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/service/IPatSatisfactionService.java b/ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/service/IPatSatisfactionService.java
new file mode 100644
index 0000000..4b873c4
--- /dev/null
+++ b/ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/service/IPatSatisfactionService.java
@@ -0,0 +1,92 @@
+package com.ruoyi.satisfaction.service;
+
+import com.ruoyi.satisfaction.domain.PatSatisfaction;
+import com.ruoyi.satisfaction.domain.vo.PatSatisfactionReqVO;
+import com.ruoyi.satisfaction.domain.vo.PatSatisfactionResVO;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 鎮h�呮弧鎰忓害 Service 鎺ュ彛銆�
+ * <p>
+ * 杩佺Щ璇存槑锛氬師涓� {@code com.smartor.service.IPatSatisfactionService}锛屾暣浣撴惉鍏ユ弧鎰忓害妯″潡銆�
+ * 鏂规硶绛惧悕涓庤涔�<b>涓�瀛楁湭鏀�</b>锛孋ontroller 鐨� URL 涓庡嚭鍏ュ弬涔熷洜姝や繚鎸佷笉鍙樸��
+ * <p>
+ * 鈿狅笍 鏈帴鍙e彧搴斿湪婊℃剰搴︽ā鍧楀唴閮ㄤ娇鐢紱鍏跺畠妯″潡闇�瑕佹弧鎰忓害鏁版嵁璇蜂娇鐢�
+ * {@code com.ruoyi.satisfaction.api} 涓嬬殑闂ㄩ潰鎺ュ彛銆�
+ *
+ * @author lihu
+ * @date 2025-06-30
+ */
+public interface IPatSatisfactionService {
+
+    // ================================================================ pat_satisfaction 琛� CRUD
+
+    /**
+     * 鏌ヨ鎮h�呮弧鎰忓害
+     *
+     * @param id 鎮h�呮弧鎰忓害涓婚敭
+     * @return 鎮h�呮弧鎰忓害
+     */
+    PatSatisfaction selectPatSatisfactionById(Long id);
+
+    /**
+     * 鏌ヨ鎮h�呮弧鎰忓害鍒楄〃
+     *
+     * @param patSatisfaction 鎮h�呮弧鎰忓害
+     * @return 鎮h�呮弧鎰忓害闆嗗悎
+     */
+    List<PatSatisfaction> selectPatSatisfactionList(PatSatisfaction patSatisfaction);
+
+    /**
+     * 鏂板鎮h�呮弧鎰忓害
+     */
+    int insertPatSatisfaction(PatSatisfaction patSatisfaction);
+
+    /**
+     * 鎵归噺鏂板鎮h�呮弧鎰忓害
+     */
+    int batchInsert(List<PatSatisfaction> patSatisfactions);
+
+    /**
+     * 淇敼鎮h�呮弧鎰忓害
+     */
+    int updatePatSatisfaction(PatSatisfaction patSatisfaction);
+
+    /**
+     * 鎵归噺鍒犻櫎鎮h�呮弧鎰忓害
+     */
+    int deletePatSatisfactionByIds(Long[] ids);
+
+    /**
+     * 鍒犻櫎鎮h�呮弧鎰忓害淇℃伅
+     */
+    int deletePatSatisfactionById(Long id);
+
+    // ================================================================ 缁熻
+
+    /**
+     * 鎮h�呮弧鎰忓害缁熻锛堟槑缁嗗彛寰勶細閫愰棶棰樿繑鍥炵瓟棰樹汉鏁般�佸钩鍧囧垎銆佹渶楂�/鏈�浣庡垎銆佺瓟棰樼巼銆侀�夐」鍗犳瘮锛�
+     *
+     * @param patSatisfactionReqVO 缁熻鏉′欢
+     * @return 缁熻缁撴灉
+     */
+    PatSatisfactionResVO statistics(PatSatisfactionReqVO patSatisfactionReqVO);
+
+    /**
+     * 鎮h�呮弧鎰忓害缁熻锛堢淮搴﹀彛寰勶紝涓� {@link #statistics} 鐨勫樊寮備粎鍦ㄣ�岄棶棰樻潵婧愩�嶏細
+     * 鍓嶈�呮寜婊℃剰搴﹀垎绫诲彇锛屾湰鏂规硶鎸夊叆鍙� dimensionList 鍙栵級
+     */
+    PatSatisfactionResVO statisticsByDimension(PatSatisfactionReqVO patSatisfactionReqVO);
+
+    /**
+     * 鎮h�呮弧鎰忓害鏌辩姸鍥句俊鎭紙鍒嗙被鍙e緞锛氭寜鍒嗙被姹囨�诲彂閫侀噺銆佸洖鏀朵汉鏁般�佸洖鏀剁巼銆佸钩鍧囧垎锛�
+     */
+    Map<String, Object> satisfactionGraph(PatSatisfactionReqVO patSatisfactionReqVO);
+
+    /**
+     * 鎮h�呮弧鎰忓害鏌辩姸鍥句俊鎭紙缁村害鍙e緞锛�
+     */
+    Map<String, Object> satisfactionGraphDimension(PatSatisfactionReqVO patSatisfactionReqVO);
+}
diff --git a/ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/service/SatisfactionSubtaskService.java b/ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/service/SatisfactionSubtaskService.java
new file mode 100644
index 0000000..4fc40af
--- /dev/null
+++ b/ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/service/SatisfactionSubtaskService.java
@@ -0,0 +1,163 @@
+package com.ruoyi.satisfaction.service;
+
+import com.ruoyi.satisfaction.domain.ServiceSubtaskDetailRatioExport;
+import com.ruoyi.satisfaction.domain.dto.MydTrendReqDTO;
+import com.ruoyi.satisfaction.domain.dto.MzMydScoreRankDTO;
+import com.ruoyi.satisfaction.domain.dto.QuestionCountKsOrBqDTO;
+import com.ruoyi.satisfaction.domain.vo.MydTrendReqVO;
+import com.ruoyi.satisfaction.domain.vo.MzMydScoreRankVO;
+import com.ruoyi.satisfaction.domain.vo.QuestionCountKsOrBqVO;
+import com.ruoyi.satisfaction.domain.row.SatisfactionCategoryRow;
+import com.ruoyi.satisfaction.domain.row.SatisfactionIvrScriptRow;
+import com.ruoyi.satisfaction.domain.row.SatisfactionScriptQuery;
+import com.ruoyi.satisfaction.domain.row.SatisfactionSubtaskQuery;
+import com.ruoyi.satisfaction.domain.row.SatisfactionSubtaskRef;
+import com.ruoyi.satisfaction.domain.row.SatisfactionSvyScriptRow;
+import com.ruoyi.satisfaction.domain.vo.SatisfactionExceptConfigQuestionReqVO;
+import com.ruoyi.satisfaction.domain.vo.SltdMydTotalVO;
+
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 婊℃剰搴︺�屽瓙浠诲姟缁村害銆嶇粺璁℃湇鍔°��
+ * <p>
+ * 杩佺Щ璇存槑锛氳繖浜涙柟娉曞師灞� {@code com.smartor.service.IServiceSubtaskService}锛�
+ * 浣嗗彧鏈夋弧鎰忓害鐩稿叧鍏ュ彛鍦ㄧ敤锛堝凡閫愪釜鏍稿疄鏃犲叾瀹冭皟鐢ㄦ柟锛夛紝鏁呮暣鎵规惉鍏ユ弧鎰忓害妯″潡銆�
+ * 鏂规硶绛惧悕闄� {@link #getDetailRatio} 澶栧潎<b>涓�瀛楁湭鏀�</b>銆�
+ * <p>
+ * 鈿狅笍 鏈帴鍙e彧搴斿湪婊℃剰搴︽ā鍧楀唴閮ㄤ娇鐢紱鍏跺畠妯″潡闇�瑕佹弧鎰忓害鏁版嵁璇风敤
+ * {@code com.ruoyi.satisfaction.api} 涓嬬殑闂ㄩ潰鎺ュ彛銆�
+ */
+public interface SatisfactionSubtaskService {
+
+    // ================================================================ 閫夐」鍗犳瘮
+
+    /**
+     * 缁熻婊℃剰搴﹂�夐」鐨勭櫨鍒嗘瘮銆�
+     * <p>
+     * 娉ㄦ剰锛氭湰鏂规硶寮�澶翠細鍏堟竻鐞嗛噸澶嶆槑缁嗭紙{@code selectRepeatDetailIds} / {@code deleteRepeatDetailByIds}锛夛紝
+     * 杩欐槸鍘熷疄鐜板氨鏈夌殑琛屼负銆�
+     */
+    List<ServiceSubtaskDetailRatioExport> statQuestionOption(
+            List<Long> taskIds, Date startFinishTime, Date endFinishTime,
+            Date startOutHospTime, Date endOutHospTime, String scriptContent,
+            Double scoreStart, Double scoreEnd, String valueType, List<Long> subtaskIds);
+
+    /**
+     * 婊℃剰搴﹀鍑猴細閫夐」鍗犳瘮銆�
+     */
+    List<ServiceSubtaskDetailRatioExport> statMydQuestionOptionForExport(
+            List<Long> subtaskIds, String questiontext);
+
+    /**
+     * 鏍规嵁瀛愪换鍔� ID 缁熻婊℃剰搴﹂�夐」鐨勭櫨鍒嗘瘮锛堟弧鎰忓害 serviceType=6/14 鐢級銆�
+     */
+    List<ServiceSubtaskDetailRatioExport> statQuestionOptionBySubtaskIds(
+            List<Long> subtaskIds, Date startOutHospTime, Date endOutHospTime,
+            Date startFinishTime, Date endFinishTime, String scriptContent,
+            Double scoreStart, Double scoreEnd, String valueType);
+
+    /**
+     * 鎸夌瀹�/鐥呭尯瀵规弧鎰忓害棰樼洰鍋氬綊绫荤粺璁°��
+     */
+    Map<String, Map<String, List<QuestionCountKsOrBqDTO>>> statQuestionOptionByKsOrBq(QuestionCountKsOrBqVO vo);
+
+    /**
+     * 闂缁撴灉鍗犳瘮锛堢渷绔嬪悓寰锋弧鎰忓害缁熻鐢級銆�
+     * <p>
+     * <b>涓庡師鏂囩殑宸紓锛堜粎绛惧悕锛�</b>锛氬師鏂囧叆鍙傛槸 {@code List<ServiceSubtaskRes>}锛�
+     * 鏂规硶鍐呴儴浠庝腑鎶藉嚭 taskIds 涓� subtaskIds銆備负璁╂弧鎰忓害妯″潡涓嶄緷璧� smartor 鐨勫疄浣擄紝
+     * 鏀逛负鐩存帴鎺ユ敹杩欎袱涓� id 鍒楄〃锛�<b>鍙e緞涓庡垎鏀垽鏂畬鍏ㄤ笉鍙�</b>锛�
+     * <ul>
+     *   <li>serviceTypeList 鍚屾椂鍚� 2 涓� 6/14 鈫� 杩斿洖 {@code null}锛堝師鏂囧嵆濡傛锛岃皟鐢ㄦ柟鎹鎻愮ず"鏆備笉鏀寔"锛夛紱</li>
+     *   <li>鍚� 2 鈫� 璧� {@link #statQuestionOption}锛堟寜 taskid 缁熻锛夛紱</li>
+     *   <li>鍚� 6/14 鈫� 璧� {@link #statQuestionOptionBySubtaskIds}锛堟寜 subtask id 缁熻锛夈��</li>
+     * </ul>
+     *
+     * @param taskIds    浠诲姟 id 鍒楄〃锛堥殢璁垮垎鏀敤锛涚敱璋冪敤鏂逛粠鏌ヨ缁撴灉鎶藉嚭锛�
+     * @param subtaskIds 瀛愪换鍔� id 鍒楄〃锛堟弧鎰忓害鍒嗘敮鐢紱鐢辫皟鐢ㄦ柟浠庢煡璇㈢粨鏋滄娊鍑猴級
+     */
+    List<ServiceSubtaskDetailRatioExport> getDetailRatio(
+            SltdMydTotalVO sltdMydTotalVO, List<SatisfactionSubtaskRef> subtaskRefs);
+
+    // ================================================================ 鐪佺珛鍚屽痉锛氳秼鍔夸笌缁村害
+
+    /**
+     * 鐪佺珛鍚屽痉鎮h�呮弧鎰忓害瓒嬪娍锛堟寜鍛�/鏈�/骞�/鏃ヨˉ闆讹級銆�
+     */
+    List<MydTrendReqDTO> mydTrend(MydTrendReqVO mydTrendReqVO);
+
+    /**
+     * 鐪佺珛鍚屽痉鎮h�呮弧鎰忓害鈥斺�旀寜缁村害锛堢瀹�/闄㈠尯锛夌粺璁°��
+     */
+    List<MydTrendReqDTO> sltdMydTotalByDimension(MydTrendReqVO mydTrendReqVO);
+
+    // ================================================================ 闂ㄨ瘖寰楀垎鎺掑悕
+
+    /**
+     * 闂ㄨ瘖婊℃剰搴︾患鍚堝緱鍒嗘帓鍚嶏紙鎸夌瀹ゆ垨鐥呭尯锛夈��
+     * <p>
+     * 鍏ュ弬缂虹渷澶勭悊淇濈暀鍘熷疄鐜帮細{@code dimensionType} 涓虹┖鍒欑疆 {@code "1"}锛�
+     * {@code serviceTypes} 涓虹┖鍒欑疆 {@code ["14"]}锛堥棬璇婃弧鎰忓害锛夈��
+     */
+    List<MzMydScoreRankDTO> mzMydScoreRank(MzMydScoreRankVO mzMydScoreRankVO);
+    /**
+     * 鎸夋満鏋�/闄㈠尯/鏈嶅姟绫诲瀷/鍑洪櫌鏃堕棿/绉戝鐥呭尯鍙栧瓙浠诲姟寮曠敤锛堝彧鍚� id銆乼askid锛夈��
+     * <p>
+     * 渚� {@code /sltdMydTotalByScore} 浣跨敤锛氬師瀹炵幇閫愪釜 serviceType 璋冪敤
+     * {@code getServiceSubtasks} 鍐嶈浆鎴� {@code ServiceSubtaskRes}锛岀幇鏀逛负鏈柟娉曘��
+     */
+    List<SatisfactionSubtaskRef> getMydSubtaskRefs(SatisfactionSubtaskQuery query);
+    /**
+     * 鑾峰彇婊℃剰搴﹂棶棰樺垎绫伙紙璇煶 + 闂嵎锛夛紝甯︾紦瀛樸��
+     * <p>
+     * 渚涚櫥褰曢摼璺娇鐢紙鍘� {@code smartor.UserExtInfoServiceImpl.getSatisfactionCategories}锛夈��
+     */
+    Map<String, Object> getSatisfactionCategories(String orgid);
+
+    /**
+     * 鑾峰彇婊℃剰搴﹀紓甯搁厤缃鐩紙璇煶 + 闂嵎锛夛紝鐢ㄤ簬銆屾弧鎰忓害寮傚父閰嶇疆銆嶉〉闈€��
+     * <p>
+     * 杩佺Щ璇存槑锛氬師涓� {@code com.smartor.service.IServiceSubtaskDetailTraceService#getSatisfactionExceptConfigQuestion}锛�
+     * 鏈嶅姟瀹炵幇钀藉湪闅忚寮傚父璺熻釜 Service 閲岋紝浣嗛�昏緫瀹屽叏鏄弧鎰忓害鍒嗙被/棰樼洰鐨勬煡璇紝
+     * 鏁呮寜妯″潡鍖栬姹傛惉鍏ユ湰妯″潡銆�<b>杩斿洖缁撴瀯銆佸瓧娈靛悕銆佺瓫閫夋潯浠跺潎涓庡師鏂囦竴鑷�</b>锛�
+     * <pre>
+     * {
+     *   "voiceDataList":         [ {questionId, libtemplateid, questionContent, questionTopic, questionType, categoryName, categoryId}, ... ],
+     *   "questionnaireDataList": [ 鍚屼笂 ]
+     * }
+     * </pre>
+     * <b>淇濈暀鐨勫師鏂囦笉瀵圭О琛屼负</b>锛堜笉瑕�"缁熶竴"锛夛細
+     * <ul>
+     *   <li>{@code scriptType} 涓虹┖鏃讹紝璇煶涓庨棶鍗�<b>涓ゆ閮芥墽琛�</b>锛屼袱涓� key 閮戒細鍑虹幇锛�</li>
+     *   <li>璇煶渚х殑鍒嗙被鍙栫殑鏄湇鍔℃柟娉曪紙鎷兼爲鍚�<b>鍙繑鍥為《灞傚垎绫�</b>锛夛紝闂嵎渚х敤鐨勬槸 Mapper锛堝彇<b>鍏ㄩ儴</b>鍒嗙被锛夛紱</li>
+     *   <li>{@code type} 涓虹┖鏃舵寜 {@code "myd"} 杩囨护锛屽叆鍙傛湁鍊兼椂瑕嗙洊銆�</li>
+     * </ul>
+     *
+     * @param questionReqVO 鍏ュ弬锛坰criptType锛�1 璇煶 / 2 闂嵎锛屼负绌哄彇涓よ�咃紱type锛氭弧鎰忓害绫诲瀷锛岄粯璁� myd锛�
+     * @return 璇煶涓庨棶鍗风殑棰樼洰鍒楄〃
+     */
+    Map<String, Object> getSatisfactionExceptConfigQuestion(SatisfactionExceptConfigQuestionReqVO questionReqVO);
+
+    /**
+     * 闅忚缁熻椤电殑銆屾弧鎰忓害鎸囨暟銆嶏細婊℃剰搴﹂鐩�婚噺 / 濉姤閲� / 瀹屾垚姣斾緥銆�
+     * <p>
+     * 杩佺Щ璇存槑锛氬師涓� {@code com.smartor.service.impl.ServiceSubtaskServiceImpl#getSatisfactionIndex}
+     * 锛堢鏈夋柟娉曪紝琚� {@code getSfStatisticsJoy} 璋冪敤锛夈��
+     * <p>
+     * 鈿狅笍 <b>鍘熷疄鐜扮己闄峰凡鍘熸牱淇濈暀</b>锛氱敱浜� {@code BigDecimal.add(...)} 鐨勮繑鍥炲�艰涓㈠純锛�
+     * 鏈柟娉曞疄闄呬笂鎭掑畾杩斿洖 {@code {mydtmzl:0, mydtbl:0, wcbl:0.0}}锛堜絾鏌ヨ鐓ф棫鎵ц锛夈��
+     * 璇﹁瀹炵幇绫讳笂鐨勬敞閲娿��
+     *
+     * @param type          "1"=鎸夌瀹わ紝"2"=鎸夌梾鍖�
+     * @param code          绉戝/鐥呭尯缂栫爜
+     * @param orgid         鏈烘瀯 id
+     * @param ivrCategoryid 璇煶婊℃剰搴﹀垎绫� id锛堝彲涓� null锛�
+     * @param svyCategoryid 闂嵎婊℃剰搴﹀垎绫� id锛堝彲涓� null锛�
+     * @return {@code mydtmzl}(Integer) / {@code mydtbl}(Integer) / {@code wcbl}(Double)
+     */
+    Map<String, Object> getSatisfactionIndex(String type, String code, String orgid,
+                                            Long ivrCategoryid, Long svyCategoryid);
+}
\ No newline at end of file
diff --git a/smartor/src/main/java/com/smartor/service/impl/PatSatisfactionServiceImpl.java b/ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/service/impl/PatSatisfactionServiceImpl.java
similarity index 79%
rename from smartor/src/main/java/com/smartor/service/impl/PatSatisfactionServiceImpl.java
rename to ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/service/impl/PatSatisfactionServiceImpl.java
index 9e07d77..5d1078a 100644
--- a/smartor/src/main/java/com/smartor/service/impl/PatSatisfactionServiceImpl.java
+++ b/ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/service/impl/PatSatisfactionServiceImpl.java
@@ -1,4 +1,4 @@
-package com.smartor.service.impl;
+package com.ruoyi.satisfaction.service.impl;
 
 import java.math.BigDecimal;
 import java.math.RoundingMode;
@@ -12,18 +12,27 @@
 import com.ruoyi.common.utils.ArrayListUtils;
 import com.ruoyi.common.utils.DateUtils;
 import com.ruoyi.common.utils.DtoConversionUtils;
-import com.smartor.domain.*;
-import com.smartor.domain.DTO.ServiceSubtaskDetailDTO;
-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 com.ruoyi.satisfaction.domain.vo.PatSatisfactionReqVO;
+import com.ruoyi.satisfaction.domain.vo.PatSatisfactionResVO;
+import com.ruoyi.satisfaction.domain.entity.PatSatisfactionDetailEntity;
+import com.ruoyi.satisfaction.domain.entity.SatisfactionStatQuery;
+import com.ruoyi.satisfaction.domain.entity.ServiceSubtaskSatisfactionEntity;
+import com.ruoyi.satisfaction.mapper.PatSatisfactionMapper;
+import com.ruoyi.satisfaction.mapper.SatisfactionMapper;
+import com.ruoyi.satisfaction.mapper.SatisfactionScriptMapper;
+import com.ruoyi.satisfaction.domain.row.SatisfactionCategoryRow;
+import com.ruoyi.satisfaction.domain.row.SatisfactionIvrScriptRow;
+import com.ruoyi.satisfaction.domain.row.SatisfactionOptionScoreRow;
+import com.ruoyi.satisfaction.domain.row.SatisfactionScriptQuery;
+import com.ruoyi.satisfaction.domain.row.SatisfactionSvyScriptRow;
+import com.ruoyi.satisfaction.domain.ServiceSubtaskDetailRatioExport;
+import com.ruoyi.satisfaction.service.strategy.SatisfactionScoreStrategyResolver;
+import com.ruoyi.satisfaction.service.strategy.SatisfactionSites;
 import org.apache.commons.collections4.CollectionUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
-import com.smartor.service.IPatSatisfactionService;
+import com.ruoyi.satisfaction.domain.PatSatisfaction;
+import com.ruoyi.satisfaction.service.IPatSatisfactionService;
 
 import static com.ruoyi.common.utils.SecurityUtils.getLoginUser;
 
@@ -39,28 +48,21 @@
     private PatSatisfactionMapper patSatisfactionMapper;
 
     @Autowired
-    private ServiceSubtaskMapper serviceSubtaskMapper;
+    private SatisfactionMapper satisfactionMapper;
 
     @Autowired
-    private ServiceSubtaskDetailMapper serviceSubtaskDetailMapper;
+    private SatisfactionScriptMapper satisfactionScriptMapper;
 
+    /**
+     * 绔欑偣鍙e緞绛栫暐瑙f瀽鍣ㄣ��
+     * <p>
+     * 鏈被鐨� {@code averageScoreByStat} 灏辨槸銆岄�氱敤鍙e緞锛堝崡鍗庯級銆嶇殑缁煎悎婊℃剰搴﹀緱鍒嗗彛寰勶紝
+     * 鐜版敼涓哄鎵� {@link com.ruoyi.satisfaction.service.strategy.CommonSatisfactionScoreStrategy}锛�
+     * 涓庣渷绔嬪悓寰峰彛寰勶紙{@code SltdSatisfactionScoreStrategy}锛夐�氳繃鍚屼竴绛栫暐鎺ュ彛骞跺垪锛�
+     * 閬垮厤鍚屼竴涓蹇靛湪涓ゅ鍚勫啓涓�浠藉疄鐜般��
+     */
     @Autowired
-    private IvrLibaTemplateScriptMapper ivrLibaTemplateScriptMapper;
-
-    @Autowired
-    private IvrLibaScriptAssortMapper ivrLibaScriptAssortMapper;
-
-    @Autowired
-    private SvyLibScriptCategoryMapper svyLibScriptCategoryMapper;
-
-    @Autowired
-    private IvrLibaTemplateTargetoptionMapper ivrLibaTemplateTargetoptionMapper;
-
-    @Autowired
-    private SvyLibTemplateTargetoptionMapper svyLibTemplateTargetoptionMapper;
-
-    @Autowired
-    private SvyLibTemplateScriptMapper svyLibTemplateScriptMapper;
+    private SatisfactionScoreStrategyResolver satisfactionScoreStrategyResolver;
 
     /**
      * 鏌ヨ鎮h�呮弧鎰忓害
@@ -201,41 +203,33 @@
         int[] personTotals = new int[2];
 
         // 鍙煡鏈鏉′欢涓嬪疄闄呭瓨鍦ㄥ摢浜� type锛堝師鏉ユ槸鎶婃帴杩戝叏琛ㄧ殑涓婄櫨涓瓧娈垫煡鍥炴潵锛屼粎鐢ㄤ簬鍒ゆ柇涓嬮潰涓や釜鍒嗘敮鏄惁涓虹┖锛�
-        List<String> types = serviceSubtaskMapper.selectSatisfactionSubtaskTypes(patSatisfactionReqVO);
+        List<String> types = satisfactionMapper.selectSatisfactionSubtaskTypes(patSatisfactionReqVO);
         boolean hasVoice = types != null && types.contains("1");
         boolean hasQuestionnaire = types != null && types.contains("2");
 
         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());
+            List<SatisfactionCategoryRow> ivrLibaScriptAssorts = satisfactionScriptMapper.selectIvrCategoryList(ivrCategoryQuery(patSatisfactionReqVO));
+            List<Long> mydIdList = ivrLibaScriptAssorts.stream().filter(item -> item.getType() != null && item.getType().contains("myd")).map(SatisfactionCategoryRow::getId).collect(Collectors.toList());
 
-            //2 鍐嶈幏鍙栬闂婊℃剰搴﹀垎绫讳笅鐨処vrLibaTemplateScript鐨勯泦鍚�
-            List<IvrLibaTemplateScript> ivrLibaTemplateScripts = ivrLibaTemplateScriptMapper.queryLibTemplateIds(mydIdList);
+            List<SatisfactionIvrScriptRow> ivrLibaTemplateScripts = satisfactionScriptMapper.queryIvrScriptByAssort(assortQuery(mydIdList));
 
             //3 涓�娆$粺璁″叏閮ㄩ棶棰樼殑鍙戦�侀噺/濉姤閲�/閫夐」鍒嗗�硷紙鍘熸潵鏄瘡涓棶棰樺悇鏌� 3 娆★級
             List<SatScriptRef> scriptRefs = new ArrayList<>();
-            for (IvrLibaTemplateScript ivrLibaTemplateScript : ivrLibaTemplateScripts) {
+            for (SatisfactionIvrScriptRow ivrLibaTemplateScript : ivrLibaTemplateScripts) {
                 scriptRefs.add(new SatScriptRef(ivrLibaTemplateScript.getTemplateid(), ivrLibaTemplateScript.getScriptid(), ivrLibaTemplateScript.getScriptContent()));
             }
             buildSatisfactionDetailEntities(patSatisfactionReqVO, "1", true, scriptRefs, patSatisfactionDetailEntities, personTotals);
         }
 
         if (hasQuestionnaire) {
-            //1. 鍏堣幏鍙杝vy_lib_script_category鐨勬弧鎰忓害鍒嗙被ID
-            SvyLibScriptCategory svyLibScriptCategory = new SvyLibScriptCategory();
-            svyLibScriptCategory.setOrgid(patSatisfactionReqVO.getOrgid());
-            List<SvyLibScriptCategory> svyLibScriptCategories = svyLibScriptCategoryMapper.selectSvyLibScriptCategoryList(svyLibScriptCategory);
-            List<Long> mydIdList = svyLibScriptCategories.stream().filter(item -> item.getType() != null && item.getType().contains("myd")).map(SvyLibScriptCategory::getId).collect(Collectors.toList());
+            List<SatisfactionCategoryRow> svyLibScriptCategories = satisfactionScriptMapper.selectSvyCategoryList(svyCategoryQuery(patSatisfactionReqVO));
+            List<Long> mydIdList = svyLibScriptCategories.stream().filter(item -> item.getType() != null && item.getType().contains("myd")).map(SatisfactionCategoryRow::getId).collect(Collectors.toList());
 
-            //2 鍐嶈幏鍙栬闂婊℃剰搴﹀垎绫讳笅鐨処vrLibaTemplateScript鐨勯泦鍚�
-            List<SvyLibTemplateScript> svyLibTemplateScripts = svyLibTemplateScriptMapper.querySvyLibTemplateIds(mydIdList);
+            List<SatisfactionSvyScriptRow> svyLibTemplateScripts = satisfactionScriptMapper.querySvyScriptByCategory(categoryQuery(mydIdList));
 
             //3 涓�娆$粺璁″叏閮ㄩ棶棰樼殑鍙戦�侀噺/濉姤閲�/閫夐」鍒嗗�硷紙鍘熸潵鏄瘡涓棶棰樺悇鏌� 3 娆★級
             List<SatScriptRef> scriptRefs = new ArrayList<>();
-            for (SvyLibTemplateScript svyLibTemplateScript : svyLibTemplateScripts) {
+            for (SatisfactionSvyScriptRow svyLibTemplateScript : svyLibTemplateScripts) {
                 // 闂嵎鐨勯�夐」琛ㄦ寜 svy_lib_template_script 鐨勪富閿叧鑱�
                 scriptRefs.add(new SatScriptRef(svyLibTemplateScript.getSvyid(), svyLibTemplateScript.getId(), svyLibTemplateScript.getScriptContent()));
             }
@@ -264,21 +258,18 @@
         int[] personTotals = new int[2];
 
         // 鍙煡鏈鏉′欢涓嬪疄闄呭瓨鍦ㄥ摢浜� type锛堝師鏉ユ槸鎶婃帴杩戝叏琛ㄧ殑涓婄櫨涓瓧娈垫煡鍥炴潵锛屼粎鐢ㄤ簬鍒ゆ柇涓嬮潰涓や釜鍒嗘敮鏄惁涓虹┖锛�
-        List<String> types = serviceSubtaskMapper.selectSatisfactionSubtaskTypes(patSatisfactionReqVO);
+        List<String> types = satisfactionMapper.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());
 
-            //2 鍐嶈幏鍙栬闂婊℃剰搴﹀垎绫讳笅鐨処vrLibaTemplateScript鐨勯泦鍚�
-            List<IvrLibaTemplateScript> ivrLibaTemplateScripts = ivrLibaTemplateScriptMapper.selectILTSBydimension(iltscript);
+            List<SatisfactionIvrScriptRow> ivrLibaTemplateScripts = satisfactionScriptMapper.selectIvrScriptByDimension(dimensionQuery(patSatisfactionReqVO));
             if (CollectionUtils.isNotEmpty(ivrLibaTemplateScripts)) {
                 //3 涓�娆$粺璁″叏閮ㄩ棶棰樼殑鍙戦�侀噺/濉姤閲�/閫夐」鍒嗗�硷紙鍘熸潵鏄瘡涓棶棰樺悇鏌� 3 娆★級
                 List<SatScriptRef> scriptRefs = new ArrayList<>();
-                for (IvrLibaTemplateScript ivrLibaTemplateScript : ivrLibaTemplateScripts) {
+                for (SatisfactionIvrScriptRow ivrLibaTemplateScript : ivrLibaTemplateScripts) {
                     scriptRefs.add(new SatScriptRef(ivrLibaTemplateScript.getTemplateid(), ivrLibaTemplateScript.getScriptid(), ivrLibaTemplateScript.getScriptContent()));
                 }
                 buildSatisfactionDetailEntities(patSatisfactionReqVO, "1", true, scriptRefs, patSatisfactionDetailEntities, personTotals);
@@ -287,14 +278,11 @@
 
         if (hasQuestionnaire) {
             //1. 鍏堣幏鍙栧叆鍙備腑鐨勬弧鎰忓害缁村害
-            SvyLibTemplateScriptVO svyLibTemplateScriptVO = new SvyLibTemplateScriptVO();
-            svyLibTemplateScriptVO.setDimensionList(patSatisfactionReqVO.getDimensionList());
-            //2 鍐嶈幏鍙栬闂婊℃剰搴﹀垎绫讳笅鐨処vrLibaTemplateScript鐨勯泦鍚�
-            List<SvyLibTemplateScript> svyLibTemplateScripts = svyLibTemplateScriptMapper.selectSLTScriptListBydimension(svyLibTemplateScriptVO);
+            List<SatisfactionSvyScriptRow> svyLibTemplateScripts = satisfactionScriptMapper.selectSvyScriptByDimension(dimensionQuery(patSatisfactionReqVO));
             if (CollectionUtils.isNotEmpty(svyLibTemplateScripts)) {
                 //3 涓�娆$粺璁″叏閮ㄩ棶棰樼殑鍙戦�侀噺/濉姤閲�/閫夐」鍒嗗�硷紙鍘熸潵鏄瘡涓棶棰樺悇鏌� 3 娆★級
                 List<SatScriptRef> scriptRefs = new ArrayList<>();
-                for (SvyLibTemplateScript svyLibTemplateScript : svyLibTemplateScripts) {
+                for (SatisfactionSvyScriptRow svyLibTemplateScript : svyLibTemplateScripts) {
                     // 闂嵎鐨勯�夐」琛ㄦ寜 svy_lib_template_script 鐨勪富閿叧鑱�
                     scriptRefs.add(new SatScriptRef(svyLibTemplateScript.getSvyid(), svyLibTemplateScript.getId(), svyLibTemplateScript.getScriptContent()));
                 }
@@ -317,24 +305,20 @@
         Map<String, List<ScoreCount>> assortNameFillCountMap = new HashMap<>();
 
         if (patSatisfactionReqVO.getType() == null || patSatisfactionReqVO.getType() == 1) {
-            //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());
+            List<SatisfactionCategoryRow> ivrLibaScriptAssorts = satisfactionScriptMapper.selectIvrCategoryList(ivrCategoryQuery(patSatisfactionReqVO));
+            List<Long> mydIdList = ivrLibaScriptAssorts.stream().filter(item -> item.getType() != null && item.getType().contains("myd")).map(SatisfactionCategoryRow::getId).collect(Collectors.toList());
 
-            //2 鍐嶈幏鍙栬闂婊℃剰搴﹀垎绫讳笅鐨処vrLibaTemplateScript鐨勯泦鍚�
-            List<IvrLibaTemplateScript> ivrLibaTemplateScripts = ivrLibaTemplateScriptMapper.queryLibTemplateIds(mydIdList);
+            List<SatisfactionIvrScriptRow> ivrLibaTemplateScripts = satisfactionScriptMapper.queryIvrScriptByAssort(assortQuery(mydIdList));
 
             //2.1 涓�娆℃�ф妸鏈璇锋眰鍏ㄩ儴妯℃澘鐨勫彂閫侀噺銆佸洖鏀朵汉鏁版煡鍥炴潵锛堝師鏉ユ槸姣忎釜妯℃澘闂鍚勬煡涓ゆ锛�
             SatisfactionStatQuery statQuery = buildSatisfactionStatQuery(patSatisfactionReqVO, "1",
-                    ivrLibaTemplateScripts.stream().map(IvrLibaTemplateScript::getTemplateid).collect(Collectors.toList()));
+                    ivrLibaTemplateScripts.stream().map(SatisfactionIvrScriptRow::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));
+            Map<String, List<SatisfactionIvrScriptRow>> scriptGroupByAssortName = ivrLibaTemplateScripts.stream().filter(item -> item.getScriptAssortname() != null).collect(Collectors.groupingBy(SatisfactionIvrScriptRow::getScriptAssortname));
             if (scriptGroupByAssortName != null && !scriptGroupByAssortName.isEmpty()) {
                 for (String assortNameKey : scriptGroupByAssortName.keySet()) {
                     // 璇ュ垎绫绘秹鍙婄殑鍘婚噸妯℃澘锛氬彂閫佷汉鏁�/鍥炴敹浜烘暟閮芥寜妯℃澘缁熻锛�
@@ -343,8 +327,8 @@
                     //鑾峰彇璇ュ垎绫荤殑濉姤鎬绘暟閲忥紙鍙敤浜庣畻骞冲潎鍒嗭紝鎸夐鐩仛鍚堢殑琛屾暟锛�
                     List<ScoreCount> allFillCountList = new ArrayList<>();
 
-                    List<IvrLibaTemplateScript> ivrLibaTemplateScriptList = scriptGroupByAssortName.get(assortNameKey);
-                    for (IvrLibaTemplateScript ivrLibaTemplateScript : ivrLibaTemplateScriptList) {
+                    List<SatisfactionIvrScriptRow> ivrLibaTemplateScriptList = scriptGroupByAssortName.get(assortNameKey);
+                    for (SatisfactionIvrScriptRow ivrLibaTemplateScript : ivrLibaTemplateScriptList) {
                         Long templateId = ivrLibaTemplateScript.getTemplateid();
                         if (templateId != null) {
                             categoryTemplateIds.add(templateId);
@@ -375,24 +359,20 @@
         }
 
         if (patSatisfactionReqVO.getType() == null || patSatisfactionReqVO.getType() == 2) {
-            //1. 鍏堣幏鍙杝vy_lib_script_category鐨勬弧鎰忓害鍒嗙被ID
-            SvyLibScriptCategory svyLibScriptCategory = new SvyLibScriptCategory();
-            svyLibScriptCategory.setOrgid(patSatisfactionReqVO.getOrgid());
-            List<SvyLibScriptCategory> svyLibScriptCategories = svyLibScriptCategoryMapper.selectSvyLibScriptCategoryList(svyLibScriptCategory);
-            List<Long> mydIdList = svyLibScriptCategories.stream().filter(item -> item.getType() != null && item.getType().contains("myd")).map(SvyLibScriptCategory::getId).collect(Collectors.toList());
+            List<SatisfactionCategoryRow> svyLibScriptCategories = satisfactionScriptMapper.selectSvyCategoryList(svyCategoryQuery(patSatisfactionReqVO));
+            List<Long> mydIdList = svyLibScriptCategories.stream().filter(item -> item.getType() != null && item.getType().contains("myd")).map(SatisfactionCategoryRow::getId).collect(Collectors.toList());
 
-            //2 鍐嶈幏鍙栬闂婊℃剰搴﹀垎绫讳笅鐨処vrLibaTemplateScript鐨勯泦鍚�
-            List<SvyLibTemplateScript> svyLibTemplateScripts = svyLibTemplateScriptMapper.querySvyLibTemplateIds(mydIdList);
+            List<SatisfactionSvyScriptRow> svyLibTemplateScripts = satisfactionScriptMapper.querySvyScriptByCategory(categoryQuery(mydIdList));
 
             //2.1 涓�娆℃�ф妸鏈璇锋眰鍏ㄩ儴妯℃澘鐨勫彂閫侀噺銆佸洖鏀朵汉鏁版煡鍥炴潵锛堝師鏉ユ槸姣忎釜妯℃澘闂鍚勬煡涓ゆ锛�
             SatisfactionStatQuery statQuery = buildSatisfactionStatQuery(patSatisfactionReqVO, "2",
-                    svyLibTemplateScripts.stream().map(SvyLibTemplateScript::getSvyid).collect(Collectors.toList()));
+                    svyLibTemplateScripts.stream().map(SatisfactionSvyScriptRow::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));
+            Map<String, List<SatisfactionSvyScriptRow>> scriptGroupByatCegoryName = svyLibTemplateScripts.stream().filter(item -> item.getCategoryName() != null).collect(Collectors.groupingBy(SatisfactionSvyScriptRow::getCategoryName));
             if (scriptGroupByatCegoryName != null && !scriptGroupByatCegoryName.isEmpty()) {
                 for (String categoryNameKey : scriptGroupByatCegoryName.keySet()) {
                     // 璇ュ垎绫绘秹鍙婄殑鍘婚噸妯℃澘锛氬彂閫佷汉鏁�/鍥炴敹浜烘暟閮芥寜妯℃澘缁熻锛�
@@ -401,8 +381,8 @@
                     //鑾峰彇璇ュ垎绫荤殑濉姤鎬绘暟閲忥紙鍙敤浜庣畻骞冲潎鍒嗭紝鎸夐鐩仛鍚堢殑琛屾暟锛�
                     List<ScoreCount> allFillCountList = new ArrayList<>();
 
-                    List<SvyLibTemplateScript> svyLibTemplateScriptList = scriptGroupByatCegoryName.get(categoryNameKey);
-                    for (SvyLibTemplateScript svyLibTemplateScript : svyLibTemplateScriptList) {
+                    List<SatisfactionSvyScriptRow> svyLibTemplateScriptList = scriptGroupByatCegoryName.get(categoryNameKey);
+                    for (SatisfactionSvyScriptRow svyLibTemplateScript : svyLibTemplateScriptList) {
                         Long templateId = svyLibTemplateScript.getSvyid();
                         if (templateId != null) {
                             categoryTemplateIds.add(templateId);
@@ -419,7 +399,6 @@
                         subidAll += sendCountMap.getOrDefault(templateId, 0L);
                         fillCountAll += fillPersonCountMap.getOrDefault(templateId, 0L);
                     }
-
 
                     // 杩欓噷鍏堣褰曚竴涓嬫瘡涓垎绫荤殑鎬讳汉鏁板拰濉姤浜烘暟
                     Map<String, Object> personCount = (Map<String, Object>) zzt.get(categoryNameKey);
@@ -449,7 +428,6 @@
         return zzt;
     }
 
-
     @Override
     public Map<String, Object> satisfactionGraphDimension(PatSatisfactionReqVO patSatisfactionReqVO) {
         //鐢ㄦ潵璁板綍鏌辩姸鍥句俊鎭紙key=婊℃剰搴︾淮搴﹀悕绉帮紝value=璇ョ淮搴︿笅鐨勬�讳汉鏁板拰濉姤浜烘暟锛�
@@ -459,21 +437,17 @@
         Map<String, List<ScoreCount>> assortNameFillCountMap = new HashMap<>();
 
         if (patSatisfactionReqVO.getType() == null || patSatisfactionReqVO.getType() == 1) {
-            //1. 浠庡叆鍙備腑鑾峰彇婊℃剰搴︾淮缁村害
-            IvrLibaTemplateScript ivrLibaTemplateScript2 = new IvrLibaTemplateScript();
-            ivrLibaTemplateScript2.setDimensionList(patSatisfactionReqVO.getDimensionList());
 
-            //2 鍐嶈幏鍙栬闂婊℃剰搴﹀垎绫讳笅鐨処vrLibaTemplateScript鐨勯泦鍚�
-            List<IvrLibaTemplateScript> ivrLibaTemplateScripts = ivrLibaTemplateScriptMapper.selectILTSBydimension(ivrLibaTemplateScript2);
+            List<SatisfactionIvrScriptRow> ivrLibaTemplateScripts = satisfactionScriptMapper.selectIvrScriptByDimension(dimensionQuery(patSatisfactionReqVO));
             if (ivrLibaTemplateScripts != null && !ivrLibaTemplateScripts.isEmpty()) {
                 //3 鎸塪imension缁村害鍒嗙粍,骞惰幏鍙栨瘡涓泦鍚堣繘琛岄亶鍘嗭紝璁$畻姣忎釜鍒嗙被鐨勬�讳汉鏁板拰濉姤浜烘暟
-                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));
+                List<SatisfactionIvrScriptRow> dimensionScripts = ivrLibaTemplateScripts.stream().filter(item -> item.getDimension() != null).collect(Collectors.toList());
+                Map<String, List<SatisfactionIvrScriptRow>> groupByDimension = dimensionScripts.stream().collect(Collectors.groupingBy(SatisfactionIvrScriptRow::getDimension));
 
                 if (groupByDimension != null && !groupByDimension.isEmpty()) {
                     //3.1 涓�娆℃�ф妸鏈璇锋眰鍏ㄩ儴妯℃澘鐨勫彂閫侀噺銆佸洖鏀朵汉鏁版煡鍥炴潵锛堝師鏉ユ槸姣忎釜妯℃澘闂鍚勬煡涓ゆ锛�
                     SatisfactionStatQuery statQuery = buildSatisfactionStatQuery(patSatisfactionReqVO, "1",
-                            dimensionScripts.stream().map(IvrLibaTemplateScript::getTemplateid).collect(Collectors.toList()));
+                            dimensionScripts.stream().map(SatisfactionIvrScriptRow::getTemplateid).collect(Collectors.toList()));
                     Map<Long, Long> sendCountMap = mapSatisfactionSendCount(statQuery);
                     SatisfactionFillStat fillStat = querySatisfactionFillStat(statQuery);
                     Map<Long, Long> fillPersonCountMap = mapSatisfactionFillPersonCount(statQuery);
@@ -484,8 +458,8 @@
                         //鑾峰彇璇ョ淮搴︾殑濉姤鍒嗗�煎垎甯冿紙鍙敤浜庣畻骞冲潎鍒嗭級
                         List<ScoreCount> allFillCountList = new ArrayList<>();
 
-                        List<IvrLibaTemplateScript> ivrLibaTemplateScriptList = groupByDimension.get(dimension);
-                        for (IvrLibaTemplateScript ivrLibaTemplateScript : ivrLibaTemplateScriptList) {
+                        List<SatisfactionIvrScriptRow> ivrLibaTemplateScriptList = groupByDimension.get(dimension);
+                        for (SatisfactionIvrScriptRow ivrLibaTemplateScript : ivrLibaTemplateScriptList) {
                             Long templateId = ivrLibaTemplateScript.getTemplateid();
                             if (templateId != null) {
                                 dimensionTemplateIds.add(templateId);
@@ -518,19 +492,16 @@
 
         if (patSatisfactionReqVO.getType() == null || patSatisfactionReqVO.getType() == 2) {
             //1.  浠庡叆鍙備腑鑾峰彇婊℃剰搴︾淮缁村害
-            SvyLibTemplateScriptVO svyLibTemplateScriptVO = new SvyLibTemplateScriptVO();
-            svyLibTemplateScriptVO.setDimensionList(patSatisfactionReqVO.getDimensionList());
 
-            //2 鍐嶈幏鍙栬闂婊℃剰搴﹀垎绫讳笅鐨処vrLibaTemplateScript鐨勯泦鍚�
-            List<SvyLibTemplateScript> svyLibTemplateScripts = svyLibTemplateScriptMapper.selectSLTScriptListBydimension(svyLibTemplateScriptVO);
+            List<SatisfactionSvyScriptRow> svyLibTemplateScripts = satisfactionScriptMapper.selectSvyScriptByDimension(dimensionQuery(patSatisfactionReqVO));
             if (svyLibTemplateScripts != null && !svyLibTemplateScripts.isEmpty()) {
                 //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));
+                List<SatisfactionSvyScriptRow> dimensionScripts = svyLibTemplateScripts.stream().filter(item -> item.getDimension() != null).collect(Collectors.toList());
+                Map<String, List<SatisfactionSvyScriptRow>> groupByDimension = dimensionScripts.stream().collect(Collectors.groupingBy(SatisfactionSvyScriptRow::getDimension));
                 if (groupByDimension != null && !groupByDimension.isEmpty()) {
                     //3.1 涓�娆℃�ф妸鏈璇锋眰鍏ㄩ儴妯℃澘鐨勫彂閫侀噺銆佸洖鏀朵汉鏁版煡鍥炴潵锛堝師鏉ユ槸姣忎釜妯℃澘闂鍚勬煡涓ゆ锛�
                     SatisfactionStatQuery statQuery = buildSatisfactionStatQuery(patSatisfactionReqVO, "2",
-                            dimensionScripts.stream().map(SvyLibTemplateScript::getSvyid).collect(Collectors.toList()));
+                            dimensionScripts.stream().map(SatisfactionSvyScriptRow::getSvyid).collect(Collectors.toList()));
                     Map<Long, Long> sendCountMap = mapSatisfactionSendCount(statQuery);
                     SatisfactionFillStat fillStat = querySatisfactionFillStat(statQuery);
                     Map<Long, Long> fillPersonCountMap = mapSatisfactionFillPersonCount(statQuery);
@@ -541,8 +512,8 @@
                         //鑾峰彇璇ュ垎绫荤殑濉姤鍒嗗�煎垎甯冿紙鍙敤浜庣畻骞冲潎鍒嗭級
                         List<ScoreCount> allFillCountList = new ArrayList<>();
 
-                        List<SvyLibTemplateScript> svyLibTemplateScriptList = groupByDimension.get(dimension);
-                        for (SvyLibTemplateScript svyLibTemplateScript : svyLibTemplateScriptList) {
+                        List<SatisfactionSvyScriptRow> svyLibTemplateScriptList = groupByDimension.get(dimension);
+                        for (SatisfactionSvyScriptRow svyLibTemplateScript : svyLibTemplateScriptList) {
                             Long templateId = svyLibTemplateScript.getSvyid();
                             if (templateId != null) {
                                 dimensionTemplateIds.add(templateId);
@@ -559,7 +530,6 @@
                             subidAll += sendCountMap.getOrDefault(templateId, 0L);
                             fillCountAll += fillPersonCountMap.getOrDefault(templateId, 0L);
                         }
-
 
                         // 杩欓噷鍏堣褰曚竴涓嬫瘡涓垎绫荤殑鎬讳汉鏁板拰濉姤浜烘暟
                         Map<String, Object> personCount = (Map<String, Object>) zzt.get(dimension);
@@ -590,53 +560,7 @@
         return zzt;
     }
 
-    /**
-     * 鑾峰彇鏈�楂樺垎
-     *
-     * @param options
-     * @return
-     */
-    private Double maxScore(List<IvrLibaTemplateTargetoption> options) {
-        if (CollectionUtils.isEmpty(options)) return 0.0;
-        Double maxScore = options.stream()
-                .map(IvrLibaTemplateTargetoption::getScore)
-                .filter(scoreStr -> scoreStr != null && !scoreStr.isEmpty())
-                .mapToDouble(Double::parseDouble)
-                .max()
-                .orElse(0.0);
-        return maxScore;
-    }
-
-    /**
-     * 鑾峰彇鏈�浣庡垎
-     *
-     * @param options
-     * @return
-     */
-    private Double minScore(List<IvrLibaTemplateTargetoption> options) {
-        if (CollectionUtils.isEmpty(options)) return 0.0;
-        Double minScore = options.stream()
-                .map(IvrLibaTemplateTargetoption::getScore)
-                .filter(scoreStr -> scoreStr != null && !scoreStr.isEmpty())
-                .mapToDouble(Double::parseDouble)
-                .min()
-                .orElse(0.0);
-        return minScore;
-    }
-
-    /**
-     * 鑾峰彇骞冲潎鍒�
-     *
-     * @param fillCount
-     * @return
-     */
-    private Double averageScore(List<ServiceSubtaskDetailDTO> fillCount) {
-        if (CollectionUtils.isEmpty(fillCount)) return 0.0;
-        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�愪釜妯℃澘鏌ュ簱锛� ====================
+                // ==================== satisfactionGraph 鑱氬悎缁熻锛堟浛浠i�愪釜妯℃澘鏌ュ簱锛� ====================
 
     /**
      * 鏋勫缓鑱氬悎缁熻鍏ュ弬锛堝彇鍊兼潵婧愪笌鍘� DtoConversionUtils.sourceToTarget(patSatisfactionReqVO, ServiceSubtaskSatisfactionEntity.class) 涓�鑷达級
@@ -667,7 +591,7 @@
     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);
+        List<Map<String, Object>> rows = satisfactionMapper.selectSatisfactionSendCount(statQuery);
         if (CollectionUtils.isEmpty(rows)) return sendCountMap;
         for (Map<String, Object> row : rows) {
             Long templateId = toLong(row.get("libtemplateid"));
@@ -685,7 +609,7 @@
     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);
+        List<Map<String, Object>> rows = satisfactionMapper.selectSatisfactionFillPersonCount(statQuery);
         if (CollectionUtils.isEmpty(rows)) return fillPersonCountMap;
         for (Map<String, Object> row : rows) {
             Long templateId = toLong(row.get("libtemplateid"));
@@ -701,7 +625,7 @@
     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);
+        List<Map<String, Object>> rows = satisfactionMapper.selectSatisfactionFillStat(statQuery);
         if (CollectionUtils.isEmpty(rows)) return fillStat;
         for (Map<String, Object> row : rows) {
             Long templateId = toLong(row.get("libtemplateid"));
@@ -719,22 +643,31 @@
     /**
      * 鑾峰彇骞冲潎鍒嗭紙鑱氬悎鐗堬細鍒嗗�间笌鏉℃暟锛�
      * <p>
-     * 鍙e緞涓� {@link #averageScore(List)} 瀹屽叏涓�鑷达細鍒嗗瓙涓哄彲瑙f瀽鍒嗗�间箣鍜岋紝鍒嗘瘝涓烘槑缁嗚鏁帮紙鍖呭惈鍒嗗�间负绌虹殑琛岋級銆�
+     * 鍙e緞涓庡師 {@code averageScore(List)} 瀹屽叏涓�鑷达細鍒嗗瓙涓哄彲瑙f瀽鍒嗗�间箣鍜岋紝鍒嗘瘝涓烘槑缁嗚鏁帮紙鍖呭惈鍒嗗�间负绌虹殑琛岋級銆�
      * 娉ㄦ剰涓嶈兘鏀规垚 sql 鐨� avg() 鎴栥�屾湁鍒嗗�肩殑琛屾暟銆嶅仛鍒嗘瘝锛屽惁鍒欒繑鍥炵殑 averageScore 浼氬彉銆�
+     * <p>
+     * <b>绛栫暐鍖栵紙2026锛�</b>锛氱湡姝g殑绠楁硶宸叉敹鏁涘埌銆岄�氱敤鍙e緞锛堝崡鍗庯級銆嶇瓥鐣�
+     * {@link com.ruoyi.satisfaction.service.strategy.CommonSatisfactionScoreStrategy}锛�
+     * 鏈柟娉曞彧鍋氬叆鍙傞�傞厤锛堟妸鍐呴儴 {@code ScoreCount} 杞垚缁熶竴鐨勫崰姣旇锛夈��
+     * <b>璁$畻缁撴灉涓庤縼绉诲墠閫愪綅鐩稿悓</b>锛堜繚鐣� 1 浣嶃�丠ALF_UP锛涙棤琛屾椂 0.0锛夈��
      */
     private Double averageScoreByStat(List<ScoreCount> fillCount) {
         if (CollectionUtils.isEmpty(fillCount)) return 0.0;
-        double sum = 0.0;
-        long rows = 0L;
+
+        // 閫傞厤鎴愮瓥鐣ョ殑缁熶竴鍏ュ弬锛氬彧甯� score 涓� count 涓や釜瀛楁锛堢瓥鐣ュ彧鐢ㄥ埌杩欎袱涓級
+        List<ServiceSubtaskDetailRatioExport> rows = new ArrayList<>(fillCount.size());
         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 (scoreCount.getCount() <= 0L) continue;   // 涓庡師鏂囦竴鑷达細闈炴鏁扮殑鑱氬悎琛屼笉鍙備笌
+            ServiceSubtaskDetailRatioExport row = new ServiceSubtaskDetailRatioExport();
+            row.setScore(scoreCount.getScore());
+            row.setCount(String.valueOf(scoreCount.getCount()));
+            rows.add(row);
         }
-        if (rows == 0L) return 0.0;
-        return new BigDecimal(sum / rows).setScale(1, RoundingMode.HALF_UP).doubleValue();
+
+        BigDecimal average = satisfactionScoreStrategyResolver
+                .resolve(SatisfactionSites.COMMON)
+                .comprehensiveScore(rows);
+        return average == null ? 0.0 : average.doubleValue();
     }
 
     /**
@@ -863,7 +796,7 @@
     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);
+        List<Map<String, Object>> rows = satisfactionMapper.selectSatisfactionFillStatByMatchedtext(statQuery);
         if (CollectionUtils.isEmpty(rows)) return fillStat;
         for (Map<String, Object> row : rows) {
             Long templateId = toLong(row.get("libtemplateid"));
@@ -894,34 +827,33 @@
                 .filter(Objects::nonNull).distinct().collect(Collectors.toList());
         if (CollectionUtils.isNotEmpty(scriptIds)) {
             addTargetOptionScores(optionScores, voice
-                    ? ivrLibaTemplateTargetoptionMapper.selectIvrLibaTargetoptionScores(scriptIds)
-                    : svyLibTemplateTargetoptionMapper.selectSvyLibTargetoptionScores(scriptIds));
+                    ? satisfactionScriptMapper.selectIvrTargetOptionScores(scriptIds)
+                    : satisfactionScriptMapper.selectSvyTargetOptionScores(scriptIds));
         }
         if (scriptRefs.stream().anyMatch(ref -> ref.getOptionScriptId() == null)) {
             optionScores.put(null, collectTargetOptionScores(voice
-                    ? ivrLibaTemplateTargetoptionMapper.selectIvrLibaTargetoptionScores(null)
-                    : svyLibTemplateTargetoptionMapper.selectSvyLibTargetoptionScores(null)));
+                    ? satisfactionScriptMapper.selectIvrTargetOptionScores(null)
+                    : satisfactionScriptMapper.selectSvyTargetOptionScores(null)));
         }
         return optionScores;
     }
 
-    private void addTargetOptionScores(Map<Long, List<String>> optionScores, List<Map<String, Object>> rows) {
+    private void addTargetOptionScores(Map<Long, List<String>> optionScores,
+                                      List<SatisfactionOptionScoreRow> rows) {
         if (CollectionUtils.isEmpty(rows)) return;
-        for (Map<String, Object> row : rows) {
-            Long scriptId = toLong(row.get("scriptid"));
+        for (SatisfactionOptionScoreRow row : rows) {
+            Long scriptId = row.getScriptid();
             if (scriptId == null) continue;
-            Object score = row.get("score");
             optionScores.computeIfAbsent(scriptId, key -> new ArrayList<>())
-                    .add(score == null ? null : String.valueOf(score));
+                    .add(row.getScore());
         }
     }
 
-    private List<String> collectTargetOptionScores(List<Map<String, Object>> rows) {
+    private List<String> collectTargetOptionScores(List<SatisfactionOptionScoreRow> 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));
+        for (SatisfactionOptionScoreRow row : rows) {
+            scores.add(row.getScore());
         }
         return scores;
     }
@@ -1101,4 +1033,46 @@
             return byTemplateQuestion.get(statKey(templateId, scriptContent));
         }
     }
+
+    // ==================== 婊℃剰搴︽ā鍧楀寲锛氭煡璇㈠璞℃瀯閫� ====================
+    // 鍘熷疄鐜扮洿鎺� new smartor 鐨勫疄浣撳綋鍏ュ弬锛涙ā鍧楀寲鍚庣粺涓�鐢� SatisfactionScriptQuery锛�
+    // 杩欓噷鎶婁笁澶勬瀯閫犳敹鏁涗负甯姪鏂规硶锛屾潯浠朵笌鍘熸潵閫愬瓧瀵瑰簲銆�
+
+    /** 婊℃剰搴﹀垎绫绘煡璇紙orgid + type=myd锛� */
+    private SatisfactionScriptQuery ivrCategoryQuery(PatSatisfactionReqVO reqVO) {
+        SatisfactionScriptQuery q = new SatisfactionScriptQuery();
+        q.setOrgid(reqVO.getOrgid());
+        q.setType("myd");
+        return q;
+    }
+
+    /** 婊℃剰搴﹀垎绫绘煡璇紙闂嵎渚э紝鏉′欢鍚岃闊充晶锛� */
+    private SatisfactionScriptQuery svyCategoryQuery(PatSatisfactionReqVO reqVO) {
+        SatisfactionScriptQuery q = new SatisfactionScriptQuery();
+        q.setOrgid(reqVO.getOrgid());
+        q.setType("myd");
+        return q;
+    }
+
+    /** 鎸夎闊冲垎绫� id 鍙栭棶棰� */
+    private SatisfactionScriptQuery assortQuery(List<Long> scriptAssortids) {
+        SatisfactionScriptQuery q = new SatisfactionScriptQuery();
+        q.setScriptAssortids(scriptAssortids);
+        return q;
+    }
+
+    /** 鎸夐棶鍗峰垎绫� id 鍙栭棶棰� */
+    private SatisfactionScriptQuery categoryQuery(List<Long> scriptCategoryids) {
+        SatisfactionScriptQuery q = new SatisfactionScriptQuery();
+        q.setScriptCategoryids(scriptCategoryids);
+        return q;
+    }
+
+    /** 鎸夌淮搴﹀彇闂锛堣闊�/闂嵎鍏辩敤锛涗袱渚с�宒imensionList 涓虹┖鏃舵槸鍚︽嫾 dimension IS NOT NULL銆嶇殑宸紓鍦� SQL 閲屼繚鐣欙級 */
+    private SatisfactionScriptQuery dimensionQuery(PatSatisfactionReqVO reqVO) {
+        SatisfactionScriptQuery q = new SatisfactionScriptQuery();
+        q.setDimensionList(reqVO.getDimensionList());
+        return q;
+    }
+
 }
diff --git a/ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/service/impl/SatisfactionSubtaskServiceImpl.java b/ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/service/impl/SatisfactionSubtaskServiceImpl.java
new file mode 100644
index 0000000..29ef42d
--- /dev/null
+++ b/ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/service/impl/SatisfactionSubtaskServiceImpl.java
@@ -0,0 +1,1218 @@
+package com.ruoyi.satisfaction.service.impl;
+
+import com.ruoyi.satisfaction.domain.ServiceSubtaskDetailRatioExport;
+import com.ruoyi.satisfaction.domain.dto.MydTrendReqDTO;
+import com.ruoyi.satisfaction.domain.dto.MzMydScoreRankDTO;
+import com.ruoyi.satisfaction.domain.dto.QuestionCountKsOrBqDTO;
+import com.ruoyi.satisfaction.domain.dto.ServiceSubtaskOptionStatDTO;
+import com.ruoyi.satisfaction.domain.vo.MydTrendReqVO;
+import com.ruoyi.satisfaction.domain.vo.MzMydScoreRankVO;
+import com.ruoyi.satisfaction.domain.vo.QuestionCountKsOrBqVO;
+import com.ruoyi.satisfaction.domain.row.SatisfactionScriptQuery;
+import com.ruoyi.satisfaction.domain.row.SatisfactionSubtaskQuery;
+import com.ruoyi.satisfaction.domain.row.SatisfactionSubtaskRef;
+import com.ruoyi.satisfaction.domain.row.SatisfactionIvrScriptRow;
+import com.ruoyi.satisfaction.domain.row.SatisfactionSvyScriptRow;
+import com.ruoyi.satisfaction.domain.row.SatisfactionTaskRow;
+import com.ruoyi.satisfaction.domain.vo.SatisfactionExceptConfigQuestionReqVO;
+import com.ruoyi.satisfaction.domain.vo.SltdMydTotalVO;
+import com.ruoyi.satisfaction.domain.row.SatisfactionCategoryRow;
+import com.ruoyi.satisfaction.mapper.SatisfactionMapper;
+import com.ruoyi.satisfaction.mapper.SatisfactionScriptMapper;
+import com.ruoyi.satisfaction.service.SatisfactionSubtaskService;
+import com.ruoyi.common.core.redis.RedisCache;
+import com.ruoyi.common.utils.DtoConversionUtils;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.collections4.CollectionUtils;
+import org.apache.commons.lang3.ObjectUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+
+/**
+ * 婊℃剰搴︺�屽瓙浠诲姟缁村害銆嶇粺璁℃湇鍔°��
+ * <p>
+ * 杩佺Щ璇存槑锛氭湰绫绘柟娉曞師鏁h惤鍦ㄩ殢璁夸富骞茬殑澶ф枃浠� ServiceSubtaskServiceImpl 涓�
+ * 锛堣瑙嗕负闅忚涓诲共鐨勫ぇ鏂囦欢锛夛紝鐜版暣娈垫惉鍏ユ弧鎰忓害妯″潡锛屼笟鍔¢�昏緫涓�琛屾湭鏀广��
+ * <p>
+ * 涓� {@link PatSatisfactionServiceImpl} 鐨勫垎宸ワ細
+ * <ul>
+ *   <li>{@code PatSatisfactionServiceImpl} 鈥斺�� 閫氱敤婊℃剰搴︾粺璁★紙鍗楀崕涓庡悓寰峰叡鐢級锛�
+ *   <li>鏈被 鈥斺�� <b>鐪佺珛鍚屽痉瀵煎悜</b>鐨勭粺璁★細閫夐」鍗犳瘮銆佹寜绉戝/鐥呭尯銆佽秼鍔裤�侀棬璇婂緱鍒嗘帓鍚嶃��
+ *       杩欎簺鍏ュ彛鍘熸湰鎸傚湪闅忚鏄庣粏 Controller 涓婏紝杩佺Щ鍚庝粛鐢辨弧鎰忓害妯″潡缁熶竴鎻愪緵銆�</li>
+ * </ul>
+ * 鏈ā鍧楄嚜甯� SQL锛岀洿鎺ユ煡 {@code service_subtask} / {@code service_subtask_detail}锛�
+ * 涓嶄緷璧� smartor 鐨勪换浣曞疄浣擄紙璋冪敤鏂瑰彧浼� id 鍒楄〃锛夈��
+ */
+@Slf4j
+@Service
+public class SatisfactionSubtaskServiceImpl implements SatisfactionSubtaskService {
+
+    @Autowired
+    private SatisfactionMapper satisfactionMapper;
+
+    @Autowired
+    private SatisfactionScriptMapper satisfactionScriptMapper;
+
+    public List<ServiceSubtaskDetailRatioExport> statQuestionOption(List<Long> taskIds, Date startFinishTime, Date endFinishTime, Date startOutHospTime, Date endOutHospTime, String scriptContent, Double scoreStart, Double scoreEnd, String valueType, List<Long> subtaskIds) {
+        //鍏堟竻鐞嗕竴涓嬮噸澶嶆暟鎹�
+        List<Long> repeatIds = satisfactionMapper.selectRepeatDetailIds();
+        if (!CollectionUtils.isEmpty(repeatIds)) {
+            satisfactionMapper.deleteRepeatDetailByIds(repeatIds);
+        }
+        List<ServiceSubtaskDetailRatioExport> serviceSubtaskDetailRatioExports = satisfactionMapper.statQuestionOption(taskIds, startOutHospTime, endOutHospTime, startFinishTime, endFinishTime, scoreStart, scoreEnd, scriptContent, valueType, subtaskIds);
+
+        // 澶勭悊 targetvalue锛氭寜 & 鍒嗗壊鎴愬鏉¤褰�
+        List<ServiceSubtaskDetailRatioExport> result = new ArrayList<>();
+        for (ServiceSubtaskDetailRatioExport export : serviceSubtaskDetailRatioExports) {
+            if (StringUtils.isNotEmpty(export.getTargetvalue()) && export.getTargetvalue().contains("&")) {
+                String[] values = export.getTargetvalue().split("&");
+                // 濡傛灉鏈夊鏉$洰鏍囧�硷紝鎷嗗垎鎴愬琛�
+                for (String value : values) {
+                    //鍏堝幓 result 涓煡涓�涓嬶紝鏈夋病鏈夎繖涓� questiontext 鍜屽搴旂殑 targetvalue
+                    //娉ㄦ剰蹇呴』甯︿笂 taskid 涓�璧峰尮閰嶏細鍚﹀垯浼氭妸鍒殑浠诲姟鐨勬暟鎹~鍒版湰浠诲姟鐨勮涓婏紝
+                    //瀵艰嚧瀵煎嚭鐨勩�屾暟閲�/姣斾緥銆嶅拰璇ヨ鐨勩�屼换鍔″悕绉�/浠诲姟缂栧彿銆嶅涓嶄笂锛堝悓涓�浠诲姟鍚屼竴棰樺嚭鐜颁袱涓笉鍚屽垎姣嶏級
+                    ServiceSubtaskDetailRatioExport existingExport = result.stream().filter(e -> Objects.equals(e.getTaskid(), export.getTaskid()) && e.getQuestiontext().equals(export.getQuestiontext()) && value.trim().equals(e.getTargetvalue())).findFirst().orElse(null);
+                    if (existingExport != null && existingExport.getRatio().equals("0") && existingExport.getCount().equals("0")) {
+                        //濡傛灉瀛樺湪锛屽垯灏� result 閲岀殑杩欎釜闂鎵�瀵瑰簲鐨� Targetvalue=value 杩欐潯鏁版嵁鏇存柊鎴愬綋鍓嶈繖鏉℃暟鎹�
+                        if (value.equals(export.getOptionresult())) {
+                            existingExport.setRatio(export.getRatio());
+                            existingExport.setScore(export.getScore());
+                            existingExport.setCount(export.getCount());
+                            existingExport.setOptionresult(export.getOptionresult());
+                        }
+                    } else if (existingExport != null && !existingExport.getRatio().equals("0")) {
+                        continue;
+                    } else {
+                        ServiceSubtaskDetailRatioExport newExport = DtoConversionUtils.sourceToTarget(export, ServiceSubtaskDetailRatioExport.class);
+                        newExport.setTargetvalue(value.trim()); // targetvalue 鏄剧ず鍗曚釜閫夐」
+                        if (!value.equals(export.getOptionresult())) {
+                            newExport.setOptionresult(null);
+                            newExport.setCount("0");
+                            newExport.setRatio("0");
+                        }
+                        result.add(newExport);
+                    }
+                }
+            } else {
+                // targetvalue 涓嶅寘鍚� &锛岀洿鎺ユ坊鍔犲埌缁撴灉
+                result.add(export);
+            }
+        }
+
+        return result;
+    }
+
+    public List<ServiceSubtaskDetailRatioExport> statMydQuestionOptionForExport(List<Long> subtaskIds, String questiontext) {
+        List<ServiceSubtaskDetailRatioExport> result = new ArrayList<>();
+        if (CollectionUtils.isEmpty(subtaskIds)) {
+            return result;
+        }
+        List<ServiceSubtaskOptionStatDTO> statList = satisfactionMapper.statMydQuestionOptionForExport(subtaskIds, questiontext);
+        if (CollectionUtils.isEmpty(statList)) {
+            return result;
+        }
+        return buildMydOptionRows(statList);
+    }
+
+    /**
+     * 鎶娿�屼换鍔�+棰樼洰+閫夐」銆嶇殑浣滅瓟缁熻鎷兼垚瀵煎嚭鐨勩�岄棶棰樿鎯呭崰姣斻�嶈
+     * <ul>
+     *     <li>閫夐」娓呭崟锛氶鐩厤缃繃鐨勯�夐」鍦� targetvalue锛堢敤 & 杩炴帴锛夐噷锛屽叏閮ㄥ垪鍑猴紝娌′汉閫夌殑鏄剧ず 0</li>
+     *     <li>鏁伴噺锛氭偅鑰呴�変腑鐨勯�夐」鍦� asrtext 閲岋紙澶氶�夐鏄�孉&B銆嶏級锛屾瘡涓�変腑椤瑰悇璁颁竴娆′汉鏁帮紝涓�涓偅鑰呭彧绠椾竴鏉℃槑缁�</li>
+     *     <li>姣斾緥锛氭暟閲� / 璇ラ浣滅瓟鎮h�呮暟锛屾墍浠ュ崟閫夐鍚勯�夐」鏁伴噺涔嬪拰 = 璇ラ浣滅瓟鎮h�呮暟</li>
+     *     <li>鍒嗘暟锛氳閫夐」鏄庣粏鐨� score 涔嬪拰锛涘閫夎鎸夐�変腑椤逛唤鏁板潎鍒嗭紝淇濊瘉鍚勯�夐」鍒嗘暟涔嬪拰 = 鏄庣粏鍒嗘暟涔嬪拰</li>
+     * </ul>
+     */
+    private static List<ServiceSubtaskDetailRatioExport> buildMydOptionRows(List<ServiceSubtaskOptionStatDTO> statList) {
+        List<ServiceSubtaskDetailRatioExport> result = new ArrayList<>();
+
+        // 鎸夈�屼换鍔� + 棰樼洰銆嶅垎缁勶紝淇濇寔 SQL 鐨勯『搴忥紙鍏堥鐩悗浠诲姟锛�
+        Map<String, List<ServiceSubtaskOptionStatDTO>> grouped = new LinkedHashMap<>();
+        for (ServiceSubtaskOptionStatDTO stat : statList) {
+            String key = stat.getTaskid() + "\u0001" + stat.getQuestiontext();
+            grouped.computeIfAbsent(key, k -> new ArrayList<>()).add(stat);
+        }
+
+        for (List<ServiceSubtaskOptionStatDTO> group : grouped.values()) {
+            ServiceSubtaskOptionStatDTO first = group.get(0);
+            // 閫夐」 -> 浜烘暟锛坙ong[1] 渚夸簬绱姞锛夛紝椤哄簭鍗冲嚭鐜伴『搴�
+            Map<String, long[]> countMap = new LinkedHashMap<>();
+            Map<String, BigDecimal> scoreMap = new LinkedHashMap<>();
+
+            for (ServiceSubtaskOptionStatDTO stat : group) {
+                // targetvalue = 鏈鎵�鏈夐�夐」鐨勬竻鍗曪紝娌′汉閫夌殑涔熻鍒楀嚭鏉�
+                List<String> options = splitOptions(stat.getTargetvalue());
+                // asrtext = 鎮h�呭疄闄呴�変腑鐨勯�夐」锛屽閫夐浼氭槸銆岄�変腑椤�1&閫変腑椤�2銆�
+                List<String> chosenOptions = splitOptions(stat.getOptionresult());
+                for (String option : options) {
+                    countMap.computeIfAbsent(option, k -> new long[1]);
+                    scoreMap.computeIfAbsent(option, k -> BigDecimal.ZERO);
+                }
+
+                long patientCount = stat.getPatientCount() == null ? 0L : stat.getPatientCount();
+                BigDecimal scoreSum = stat.getScoreSum() == null ? BigDecimal.ZERO : stat.getScoreSum();
+
+                if (!chosenOptions.isEmpty()) {
+                    BigDecimal[] scoreParts = splitScore(scoreSum, chosenOptions.size());
+                    for (int i = 0; i < chosenOptions.size(); i++) {
+                        String chosen = chosenOptions.get(i);
+                        // 閫変腑鐨勯�夐」鍙兘涓嶅湪閰嶇疆娓呭崟閲岋紙鍘嗗彶鏁版嵁锛夛紝琛ヤ竴琛�
+                        countMap.computeIfAbsent(chosen, k -> new long[1]);
+                        scoreMap.computeIfAbsent(chosen, k -> BigDecimal.ZERO);
+                        countMap.get(chosen)[0] += patientCount;
+                        scoreMap.put(chosen, scoreMap.get(chosen).add(scoreParts[i]));
+                    }
+                } else if (options.size() == 1) {
+                    // 娌℃湁 asrtext锛堟湭绛�/鏃犲簲绛旓級鏃讹紝鍙湁鍞竴閫夐」鎵嶈兘纭畾褰掑睘
+                    countMap.get(options.get(0))[0] += patientCount;
+                    scoreMap.put(options.get(0), scoreMap.get(options.get(0)).add(scoreSum));
+                }
+            }
+
+            long total = 0L;
+            for (long[] count : countMap.values()) total += count[0];
+
+            for (Map.Entry<String, long[]> entry : countMap.entrySet()) {
+                long count = entry.getValue()[0];
+                ServiceSubtaskDetailRatioExport row = new ServiceSubtaskDetailRatioExport();
+                row.setQuestiontext(first.getQuestiontext());
+                row.setOptionresult(entry.getKey());
+                row.setTargetvalue(entry.getKey());
+                row.setCount(String.valueOf(count));
+                row.setRatio(formatRatio(count, total));
+                row.setScore(formatScore(scoreMap.get(entry.getKey())));
+                row.setTaskname(first.getTaskName());
+                row.setTaskid(first.getTaskid());
+                row.setValueType(first.getValueType());
+                result.add(row);
+            }
+        }
+        return result;
+    }
+
+    /**
+     * 鎶婁竴鏉℃槑缁嗙殑鍒嗘暟鎸夐�変腑椤逛唤鏁板潎鍒嗭紝浣欐暟琛ョ粰绗竴涓紝淇濊瘉鍚勪唤涔嬪拰 = 鍘熷垎鏁�
+     */
+    private static BigDecimal[] splitScore(BigDecimal total, int parts) {
+        BigDecimal[] result = new BigDecimal[parts];
+        if (parts <= 0) {
+            return result;
+        }
+        BigDecimal each = total.divide(BigDecimal.valueOf(parts), 2, RoundingMode.HALF_UP);
+        for (int i = 0; i < parts; i++) {
+            result[i] = each;
+        }
+        result[0] = result[0].add(total.subtract(each.multiply(BigDecimal.valueOf(parts))));
+        return result;
+    }
+
+    /**
+     * 鎷嗗垎棰樼洰閰嶇疆鐨勯�夐」锛堝涓�夐」鐢� & 杩炴帴锛夛紝椤哄甫鍘绘帀鍏ㄨ/鍗婅绌虹櫧
+     */
+    private static List<String> splitOptions(String targetvalue) {
+        List<String> options = new ArrayList<>();
+        if (StringUtils.isEmpty(targetvalue)) {
+            return options;
+        }
+        for (String part : targetvalue.split("&")) {
+            String option = trimAll(part);
+            if (StringUtils.isNotEmpty(option) && !options.contains(option)) {
+                options.add(option);
+            }
+        }
+        return options;
+    }
+
+    /**
+     * 姣斾緥锛氭暟閲� / 璇ラ浣滅瓟鎮h�呮暟锛屼繚鐣欎袱浣嶅皬鏁�
+     */
+    private static String formatRatio(long count, long total) {
+        if (total <= 0) {
+            return "0.00%";
+        }
+        return BigDecimal.valueOf(count * 100.0 / total).setScale(2, RoundingMode.HALF_UP).toPlainString() + "%";
+    }
+
+    /**
+     * 鍒嗘暟涔嬪拰锛氬幓鎺夊浣欑殑灏忔暟浣嶏紙0.00 -> 0锛�
+     */
+    private static String formatScore(BigDecimal score) {
+        if (score == null) {
+            return "0";
+        }
+        BigDecimal value = score.stripTrailingZeros();
+        if (value.scale() < 0) {
+            value = value.setScale(0);
+        }
+        return value.toPlainString();
+    }
+
+    public Map<String, Map<String, List<QuestionCountKsOrBqDTO>>> statQuestionOptionByKsOrBq(QuestionCountKsOrBqVO vo) {
+
+        // 1. 鏌ユ槑缁�
+        List<QuestionCountKsOrBqDTO> sourceList = satisfactionMapper.statQuestionOptionByKsOrBq(vo);
+        if (CollectionUtils.isEmpty(sourceList)) {
+            return Collections.emptyMap();
+        }
+
+        // 2. 鍒ゆ柇鎸夌瀹よ繕鏄寜鐥呭尯
+        boolean byDept = vo.getLeaveldeptcodes() != null;
+        boolean byDistrict = !byDept && vo.getLeavehospitaldistrictcodes() != null;
+        if (!byDept && !byDistrict) {
+            return Collections.emptyMap();
+        }
+
+        // 3. key = 绉戝/鐥呭尯 + 棰樼洰 + 閫夐」
+        Map<String, QuestionCountKsOrBqDTO> map = new LinkedHashMap<>();
+
+        for (QuestionCountKsOrBqDTO dto : sourceList) {
+
+            if (StringUtils.isEmpty(dto.getTargetvalue())) {
+                continue;
+            }
+
+            // 鍒嗙粍鍚嶏細绉戝 鎴� 鐥呭尯
+            String groupName = byDept ? dto.getDeptname() : dto.getLeavehospitaldistrictname();
+            if (StringUtils.isEmpty(groupName)) {
+                continue;
+            }
+
+            String asrtext = trimAll(dto.getAsrtext());            // 鐢ㄦ埛瀹為檯閫変腑鐨勯�夐」
+            String questionText = trimAll(dto.getQuestionText());  // 棰樼洰
+            if (StringUtils.isEmpty(questionText)) {
+                continue;
+            }
+
+            // 鎷嗗垎 targetvalue锛屾瘡涓�夐」閮借鏈変竴鏉¤褰�
+            for (String raw : dto.getTargetvalue().split("&")) {
+
+                String option = trimAll(raw);
+                if (option.isEmpty()) {
+                    continue;
+                }
+
+                // key = 绉戝/鐥呭尯 + 棰樼洰 + 閫夐」
+                String key = buildKey(groupName, questionText, option);
+
+                QuestionCountKsOrBqDTO item = map.get(key);
+                if (item == null) {
+                    item = DtoConversionUtils.sourceToTarget(dto, QuestionCountKsOrBqDTO.class);
+                    item.setTargetvalue(option);
+                    item.setAsrtext(option);
+                    item.setQuestionText(dto.getQuestionText());
+                    item.setAnswerCount("0");
+                    item.setSingleScore("0");
+                    map.put(key, item);
+                }
+
+                // 鍙湁褰撳墠閫夐」 == 鐢ㄦ埛瀹為檯閫変腑鐨勯�夐」锛屾墠绱姞
+                if (option.equals(asrtext)) {
+                    int count = parseCount(item.getAnswerCount()) + 1;
+                    item.setAnswerCount(String.valueOf(count));
+
+                    BigDecimal totalScore = new BigDecimal(item.getSingleScore())
+                            .add(new BigDecimal(dto.getSingleScore() == null ? "0" : dto.getSingleScore()));
+                    item.setSingleScore(totalScore.toPlainString());
+                }
+            }
+        }
+
+        // 4. 璁$畻骞冲潎鍒�
+        for (QuestionCountKsOrBqDTO dto : map.values()) {
+            String answerCount = dto.getAnswerCount();
+            String singleScore = dto.getSingleScore();
+
+            BigDecimal avg;
+            if (StringUtils.isEmpty(answerCount)
+                    || BigDecimal.ZERO.compareTo(new BigDecimal(answerCount.trim())) == 0) {
+                avg = BigDecimal.ZERO;
+            } else {
+                avg = new BigDecimal(singleScore == null ? "0" : singleScore)
+                        .divide(new BigDecimal(answerCount.trim()), 2, RoundingMode.HALF_UP);
+            }
+            dto.setAvgScore(avg.toPlainString());
+        }
+
+        // 5. 杞垚 Map<String, Map<String, List<QuestionCountKsOrBqDTO>>>
+        //    绗竴灞� key锛氱瀹�/鐥呭尯
+        //    绗簩灞� key锛氶鐩�
+        //    绗簩灞� value锛氳棰樼洰涓嬬殑鎵�鏈夐�夐」鍒楄〃
+        Map<String, Map<String, List<QuestionCountKsOrBqDTO>>> result = new LinkedHashMap<>();
+
+        for (QuestionCountKsOrBqDTO dto : map.values()) {
+
+            // 鍒嗙粍鍚�
+            String groupName = byDept ? dto.getDeptname() : dto.getLeavehospitaldistrictname();
+            if (StringUtils.isEmpty(groupName)) {
+                continue;
+            }
+
+            // 棰樼洰鍚�
+            String questionText = dto.getQuestionText();
+            if (StringUtils.isEmpty(questionText)) {
+                continue;
+            }
+
+            // 绗竴灞傦細绉戝/鐥呭尯
+            Map<String, List<QuestionCountKsOrBqDTO>> questionMap =
+                    result.computeIfAbsent(groupName, k -> new LinkedHashMap<>());
+
+            // 绗簩灞傦細棰樼洰
+            List<QuestionCountKsOrBqDTO> optionList =
+                    questionMap.computeIfAbsent(questionText, k -> new ArrayList<>());
+
+            optionList.add(dto);
+        }
+
+        return result;
+    }
+
+    public List<ServiceSubtaskDetailRatioExport> statQuestionOptionBySubtaskIds(List<Long> subtaskIds, Date startOutHospTime, Date endOutHospTime, Date startFinishTime, Date endFinishTime, String scriptContent, Double scoreStart, Double scoreEnd, String valueType) {
+        //鍏堟竻鐞嗕竴涓嬮噸澶嶆暟鎹�
+        List<Long> repeatIds = satisfactionMapper.selectRepeatDetailIds();
+        if (!CollectionUtils.isEmpty(repeatIds)) {
+            satisfactionMapper.deleteRepeatDetailByIds(repeatIds);
+        }
+
+        List<ServiceSubtaskDetailRatioExport> serviceSubtaskDetailRatioExports = satisfactionMapper.statQuestionOptionBySubtaskIds(subtaskIds, startOutHospTime, endOutHospTime, startFinishTime, endFinishTime, scoreStart, scoreEnd, scriptContent, valueType);
+        // SQL 宸叉寜銆岄鐩� + 閫夐」銆嶆眹鎬绘暟閲忥紝杩欓噷鍐嶆寜棰樼洰鍚堝苟锛堜笉鍚屾ā鏉跨殑 targetvalue 閫夐」椤哄簭涓嶅悓锛屽悓涓�閫夐」浼氭媶鎴愬琛岋級锛�
+        // 骞舵妸娌′汉閫夌殑閫夐」琛ユ垚 0 琛屻�佹寜鍚勯�夐」鏁伴噺涔嬪拰閲嶇畻姣斾緥
+        return mergeMydOptionRows(serviceSubtaskDetailRatioExports);
+    }
+
+    /**
+     * 鎶� SQL 杩斿洖鐨勩�岄鐩� + 閫夐」銆嶆槑缁嗗悎骞舵垚銆屼竴琛屼竴涓�夐」銆�
+     * <ul>
+     *     <li>鍚屼竴棰樼洰涓嬬浉鍚� optionresult 鐨勬暟閲忕疮鍔狅細鍘熷疄鐜伴亣鍒板悓鍚嶉�夐」鐩存帴 continue锛�
+     *         姣忎釜閫夐」鍙細淇濈暀绗竴鏉℃槑缁嗙殑鏁伴噺锛屽浠诲姟/澶氭偅鑰呯殑椤规暟鍏ㄨ涓㈡帀锛屽墠绔�屾暟閲忋�嶆案杩滃彧鏈� 1</li>
+     *     <li>閫夐」娓呭崟鍙� targetvalue锛堢敤 &amp; 杩炴帴锛夛紝娌′汉閫夌殑閫夐」琛� 0 琛岋紝鍓嶇鍥哄畾鍏垪鎵嶈兘娓叉煋</li>
+     *     <li>姣斾緥 = 璇ラ�夐」鏁伴噺 / 璇ラ鍚勯�夐」鏁伴噺涔嬪拰锛屼繚璇佷竴閬撻鍚勯�夐」姣斾緥鍚堣 100%</li>
+     * </ul>
+     *
+     * @param rows SQL 鏄庣粏
+     */
+    private List<ServiceSubtaskDetailRatioExport> mergeMydOptionRows(List<ServiceSubtaskDetailRatioExport> rows) {
+        List<ServiceSubtaskDetailRatioExport> result = new ArrayList<>();
+        if (CollectionUtils.isEmpty(rows)) {
+            return result;
+        }
+
+        // key -> 閫夐」 -> 鏁伴噺锛岀敤 LinkedHashMap 淇濆簭锛氶�夐」椤哄簭娌跨敤闂嵎閰嶇疆 targetvalue 鐨勯『搴�
+        Map<String, Map<String, long[]>> countMap = new LinkedHashMap<>();
+        Map<String, Map<String, String>> scoreMap = new LinkedHashMap<>();
+        Map<String, ServiceSubtaskDetailRatioExport> templateMap = new LinkedHashMap<>();
+
+        for (ServiceSubtaskDetailRatioExport row : rows) {
+            if (row == null) {
+                continue;
+            }
+            String key = row.getQuestiontext();
+            if (key == null) {
+                continue;
+            }
+            Map<String, long[]> counts = countMap.computeIfAbsent(key, k -> new LinkedHashMap<>());
+            Map<String, String> scores = scoreMap.computeIfAbsent(key, k -> new LinkedHashMap<>());
+            templateMap.putIfAbsent(key, row);
+
+            // 棰樼洰閰嶇疆鐨勯�夐」娓呭崟锛氭病浜洪�夌殑閫夐」涔熻鍒楀嚭鏉ワ紙鏁伴噺涓� 0锛�
+            for (String option : splitOptions(row.getTargetvalue())) {
+                counts.computeIfAbsent(option, k -> new long[1]);
+            }
+
+            // 鎮h�呭疄闄呴�変腑鐨勯�夐」锛氬閫夐 asrtext 鏄�岄�夐」1&閫夐」2銆嶏紝鍗曢��/闂瓟棰樻槸鏁存鏂囨湰
+            List<String> chosenOptions = "2".equals(row.getValueType())
+                    ? splitOptions(row.getOptionresult())
+                    : singleOption(row.getOptionresult());
+            if (chosenOptions.isEmpty()) {
+                // 鏈綔绛旓細涓嶈繘浠讳綍閫夐」锛屼篃涓嶈繘鍒嗘瘝
+                continue;
+            }
+            long count = parseCount(row.getCount());
+            for (String chosen : chosenOptions) {
+                // 閫変腑鐨勯�夐」鍙兘涓嶅湪閰嶇疆娓呭崟閲岋紙鍘嗗彶鏁版嵁锛夛紝琛ヤ竴琛岋紝鏁伴噺涓嶈兘涓�
+                counts.computeIfAbsent(chosen, k -> new long[1])[0] += count;
+                if (StringUtils.isNotEmpty(row.getScore()) && StringUtils.isEmpty(scores.get(chosen))) {
+                    scores.put(chosen, row.getScore());
+                }
+            }
+        }
+
+        for (Map.Entry<String, Map<String, long[]>> entry : countMap.entrySet()) {
+            ServiceSubtaskDetailRatioExport template = templateMap.get(entry.getKey());
+            Map<String, String> scores = scoreMap.get(entry.getKey());
+            long total = 0L;
+            for (long[] count : entry.getValue().values()) {
+                total += count[0];
+            }
+            for (Map.Entry<String, long[]> optionEntry : entry.getValue().entrySet()) {
+                String option = optionEntry.getKey();
+                long count = optionEntry.getValue()[0];
+                ServiceSubtaskDetailRatioExport merged = DtoConversionUtils.sourceToTarget(template, ServiceSubtaskDetailRatioExport.class);
+                merged.setQuestiontext(template.getQuestiontext());
+                merged.setTargetvalue(option);
+                // 娌′汉閫夌殑閫夐」 optionresult 缃┖锛屽墠绔寜 targetvalue 鍏滃簳灞曠ず锛屼繚鎸佸師鏈夊睍绀哄彛寰�
+                merged.setOptionresult(count > 0 ? option : null);
+                merged.setCount(String.valueOf(count));
+                merged.setRatio(formatRatio(count, total));
+                merged.setScore(StringUtils.isNotEmpty(scores.get(option)) ? scores.get(option) : template.getScore());
+                result.add(merged);
+            }
+        }
+        return result;
+    }
+
+    /**
+     * 鍗曢��/闂瓟棰樼殑浣滅瓟鏂囨湰锛氫笉鎸� &amp; 鎷嗭紝閬垮厤鎶婇棶绛旈鐨勬枃鏈瓟妗堟媶鍧�
+     */
+    private static List<String> singleOption(String answer) {
+        List<String> options = new ArrayList<>();
+        String text = trimAll(answer);
+        if (StringUtils.isNotEmpty(text)) {
+            options.add(text);
+        }
+        return options;
+    }
+
+    /**
+     * 鏍囧噯鍖栭棶鍗风瓟妗堟枃鏈細鍘绘帀 A/B/C/D 鍗曞瓧姣嶅墠缂�鍜屽悗缁┖鏍�
+     * 渚嬪 "A 鏄�" -> "鏄�"锛�"B 鍚�" -> "鍚�"
+     */
+    private String normalizeOptionAnswer(String answer) {
+        if (StringUtils.isBlank(answer)) {
+            return "";
+        }
+        String trimmed = answer.trim();
+        if (trimmed.length() >= 3 && trimmed.charAt(1) == ' ') {
+            char firstChar = trimmed.charAt(0);
+            if (firstChar >= 'A' && firstChar <= 'Z') {
+                return trimmed.substring(2).trim();
+            }
+        }
+        return trimmed;
+    }
+
+    public List<MydTrendReqDTO> mydTrend(MydTrendReqVO mydTrendReqVO) {
+        List<MydTrendReqDTO> list = satisfactionMapper.mydTrend(mydTrendReqVO.getStartFinishTime(), mydTrendReqVO.getEndFinishTime(), mydTrendReqVO.getType());
+        if (mydTrendReqVO.getType().equals("week")) list = getWeekDate(mydTrendReqVO, list);
+        if (mydTrendReqVO.getType().equals("year")) list = getYearDate(mydTrendReqVO, list);
+        if (mydTrendReqVO.getType().equals("month")) list = getMonthDate(mydTrendReqVO, list);
+        if (mydTrendReqVO.getType().equals("day")) list = getDayDate(mydTrendReqVO, list);
+        return list;
+    }
+
+    public List<MydTrendReqDTO> sltdMydTotalByDimension(MydTrendReqVO mydTrendReqVO) {
+        List<MydTrendReqDTO> list = null;
+        if (mydTrendReqVO.getDimensionType().equals("1"))
+            list = satisfactionMapper.sltdMydTotalByDimension(mydTrendReqVO.getStartFinishTime(), mydTrendReqVO.getEndFinishTime(), mydTrendReqVO.getType());
+        else if (mydTrendReqVO.getDimensionType().equals("2"))
+            list = satisfactionMapper.sltdMydTotalByYQ(mydTrendReqVO.getStartFinishTime(), mydTrendReqVO.getEndFinishTime(), mydTrendReqVO.getType());
+
+        return list;
+    }
+
+    /**
+     * 闂缁撴灉鍗犳瘮
+     *
+     * @param sltdMydTotalVO
+     * @param taskIds      浠诲姟 id 鍒楄〃锛堥殢璁� serviceType=2 鍒嗘敮浣跨敤锛�
+     * @param subtaskIds   瀛愪换鍔� id 鍒楄〃锛堟弧鎰忓害 serviceType=6/14 鍒嗘敮浣跨敤锛�
+     * @return
+     */
+    public List<ServiceSubtaskDetailRatioExport> getDetailRatio(SltdMydTotalVO sltdMydTotalVO, List<SatisfactionSubtaskRef> subtaskRefs) {
+        List<ServiceSubtaskDetailRatioExport> serviceSubtaskDetailRatioExports = null;
+        List<String> serviceTypeList = sltdMydTotalVO.getServiceTypeList();
+        if (CollectionUtils.isEmpty(serviceTypeList)) {
+            log.warn("getDetailRatio 鏈紶 serviceTypeList锛屾棤娉曠粺璁¢棶棰樺崰姣�");
+            return serviceSubtaskDetailRatioExports;
+        }
+        // 鍒ゆ柇鍙e緞锛�2=闅忚锛堟寜 taskid 缁熻锛夛紝6/14=婊℃剰搴︼紙鎸� subtask id 缁熻锛�
+        boolean hasFollowUp = serviceTypeList.contains("2");
+        boolean hasSatisfaction = serviceTypeList.contains("6") || serviceTypeList.contains("14");
+        if (hasFollowUp && hasSatisfaction) {
+            //serviceType锛屽嵆鏈夐殢璁垮張鏈夋弧鎰忓害锛屾殏鏃舵病娉曠粺璁�
+            log.warn("getDetailRatio 鏆備笉鏀寔鍚屾椂缁熻闅忚(2)涓庢弧鎰忓害(6/14)锛宻erviceTypeList={}", serviceTypeList);
+            return serviceSubtaskDetailRatioExports;
+        }
+        if (hasFollowUp) {
+            //鑾峰彇serviceSubtaskList涓殑鎵�鏈塼askid
+            List<Long> taskIds = subtaskRefs.stream().map(SatisfactionSubtaskRef::getTaskid).filter(ObjectUtils::isNotEmpty).distinct().collect(Collectors.toList());
+            //鍚屾椂甯︿笂鏈瀵煎嚭鐨勫瓙浠诲姟 id锛氳銆岄棶棰樿鎯呭崰姣斻�嶅彧缁熻杩欐壒鎮h�咃紝涓庛�屾偅鑰呴殢璁夸俊鎭〃鍗曘�嶅彛寰勪竴鑷�
+            List<Long> subtaskIds = subtaskRefs.stream().map(SatisfactionSubtaskRef::getId).filter(ObjectUtils::isNotEmpty).distinct().collect(Collectors.toList());
+            serviceSubtaskDetailRatioExports = statQuestionOption(taskIds, sltdMydTotalVO.getStartFinishTime(), sltdMydTotalVO.getEndFinishTime(), sltdMydTotalVO.getStartOutHospTime(), sltdMydTotalVO.getEndOutHospTime(), sltdMydTotalVO.getQuestionContent(), sltdMydTotalVO.getStartScore(), sltdMydTotalVO.getEndScore(), sltdMydTotalVO.getValueType(), subtaskIds);
+        } else if (hasSatisfaction) {
+            //婊℃剰搴︾粺璁� 锛堥�氳繃 subtask 鐨� id 鍘荤粺璁★級
+            List<Long> subtaskIds = subtaskRefs.stream().map(SatisfactionSubtaskRef::getId).collect(Collectors.toList());
+            serviceSubtaskDetailRatioExports = statQuestionOptionBySubtaskIds(subtaskIds, sltdMydTotalVO.getStartOutHospTime(), sltdMydTotalVO.getEndOutHospTime(), sltdMydTotalVO.getStartFinishTime(), sltdMydTotalVO.getEndFinishTime(), sltdMydTotalVO.getQuestionContent(), sltdMydTotalVO.getStartScore(), sltdMydTotalVO.getEndScore(), sltdMydTotalVO.getValueType());
+        } else {
+            log.warn("getDetailRatio 鏆備笉鏀寔鐨� serviceTypeList={}锛堜粎鏀寔 2 鎴� 6/14锛�", serviceTypeList);
+        }
+        return serviceSubtaskDetailRatioExports;
+    }
+
+
+    private static String trimAll(String s) {
+        if (s == null) {
+            return "";
+        }
+        int start = 0;
+        int end = s.length();
+        while (start < end && isBlankChar(s.charAt(start))) {
+            start++;
+        }
+        while (end > start && isBlankChar(s.charAt(end - 1))) {
+            end--;
+        }
+        return s.substring(start, end);
+    }
+
+
+    private String buildKey(String groupName, String questionText, String option) {
+        return groupName + "\u0001" + questionText + "\u0001" + option;
+    }
+
+
+    private static int parseCount(String s) {
+        if (StringUtils.isEmpty(s)) {
+            return 0;
+        }
+        try {
+            return Integer.parseInt(s.trim());
+        } catch (NumberFormatException e) {
+            return 0;
+        }
+    }
+
+
+    private List<MydTrendReqDTO> getWeekDate(MydTrendReqVO mydTrendReqVO, List<MydTrendReqDTO> list) {
+        // 1. 璁$畻鎬诲懆鏁帮紙鍜� SQL 閲岀殑鍏紡淇濇寔涓�鑷达級
+        long days = (mydTrendReqVO.getEndFinishTime().getTime() - mydTrendReqVO.getStartFinishTime().getTime()) / (1000L * 60 * 60 * 24);
+        int totalWeeks = (int) (days / 7) + 1;
+
+        // 2. 宸叉湁鏁版嵁锛歱eriod -> totalScore
+        Map<Integer, String> exist = new HashMap<>();
+        for (MydTrendReqDTO row : list) {
+            // period 鏄暟瀛楋紝鍙兘鏄� Integer / Long / BigDecimal锛岀粺涓�杞� int
+            int p = Integer.parseInt(StringUtils.isNotEmpty(row.getPeriod()) ? row.getPeriod() : "0");
+            exist.put(p, row.getTotalScore() != null ? row.getTotalScore() : "0");
+            // 涓嬮潰杩欒鎸変綘鐨勫師閫昏緫淇濈暀锛堣櫧鐒剁湅璧锋潵鏈夌偣澶氫綑锛�
+            exist.put(p, String.valueOf(row.getTotalScore() != null ? row.getTotalScore() : 0));
+        }
+
+        // 3. 琛ュ叏 1..totalWeeks
+        List<MydTrendReqDTO> result = new ArrayList<>(totalWeeks);
+
+        // 鑾峰彇鏁翠綋鐨勫紑濮嬫椂闂�
+        long startTimeMillis = mydTrendReqVO.getStartFinishTime().getTime();
+        long endTimeMillis = mydTrendReqVO.getEndFinishTime().getTime();
+        long oneWeekMillis = 7L * 24 * 60 * 60 * 1000; // 涓�鍛ㄧ殑姣鏁�
+
+        for (int i = 1; i <= totalWeeks; i++) {
+            MydTrendReqDTO row = new MydTrendReqDTO();
+            row.setPeriod(String.valueOf(i));
+            row.setTotalScore(exist.getOrDefault(i, "0"));
+
+            // -------- 鏂板锛氳缃瘡鍛ㄧ殑 startDate 鍜� endDate --------
+            // 褰撳墠鍛ㄥ紑濮嬫椂闂� = 鏁翠綋寮�濮嬫椂闂� + (i - 1) * 7澶�
+            long currentStartMillis = startTimeMillis + (long) (i - 1) * oneWeekMillis;
+
+            // 褰撳墠鍛ㄧ粨鏉熸椂闂� = 褰撳墠鍛ㄥ紑濮嬫椂闂� + 7澶� - 1姣锛堜繚璇佹槸杩欎竴鍛ㄧ殑鏈�鍚庝竴鍒伙級
+            long currentEndMillis = currentStartMillis + oneWeekMillis - 1;
+
+            // 濡傛灉鏄渶鍚庝竴鍛紝缁撴潫鏃堕棿涓嶈兘瓒呰繃鏁翠綋鐨� endTime
+            if (i == totalWeeks) {
+                currentEndMillis = endTimeMillis;
+            }
+
+            row.setStartDate(new Date(currentStartMillis));
+            row.setEndDate(new Date(currentEndMillis));
+            // ----------------------------------------------------
+
+            result.add(row);
+        }
+
+        return result;
+    }
+
+
+    private List<MydTrendReqDTO> getYearDate(MydTrendReqVO mydTrendReqVO, List<MydTrendReqDTO> list) {
+        // 鑾峰彇鏁翠綋寮�濮嬪拰缁撴潫鏃堕棿
+        Date startTime = mydTrendReqVO.getStartFinishTime();
+        Date endTime = mydTrendReqVO.getEndFinishTime();
+
+        // 1. 璁$畻骞翠唤鑼冨洿
+        // 浣跨敤 Calendar 鑾峰彇璧峰骞翠唤鍜岀粨鏉熷勾浠�
+        Calendar cal = Calendar.getInstance();
+        cal.setTime(startTime);
+        int startYear = cal.get(Calendar.YEAR);
+
+        cal.setTime(endTime);
+        int endYear = cal.get(Calendar.YEAR);
+
+        // 鎬诲勾鏁� (鍖呭惈棣栧熬)
+        int totalYears = endYear - startYear + 1;
+
+        // 2. 宸叉湁鏁版嵁锛歱eriod -> totalScore (鍋囪 period 瀛樼殑鏄勾浠藉瓧绗︿覆锛屽 "2024")
+        Map<String, String> exist = new HashMap<>();
+        for (MydTrendReqDTO row : list) {
+            String period = row.getPeriod() != null ? row.getPeriod() : "";
+            exist.put(period, row.getTotalScore() != null ? row.getTotalScore() : "0");
+        }
+
+        // 3. 琛ュ叏鎵�鏈夊勾浠�
+        List<MydTrendReqDTO> result = new ArrayList<>(totalYears);
+
+        for (int i = 0; i < totalYears; i++) {
+            int currentYear = startYear + i;
+            String periodKey = String.valueOf(currentYear);
+
+            MydTrendReqDTO row = new MydTrendReqDTO();
+            row.setPeriod(periodKey);
+            // 濡傛灉娌℃湁璇ュ勾浠界殑鏁版嵁锛岄粯璁ょ粰 "0"
+            row.setTotalScore(exist.getOrDefault(periodKey, "0"));
+
+            // -------- 璁剧疆璇ュ勾浠界殑 startDate 鍜� endDate --------
+            Calendar yearCal = Calendar.getInstance();
+            // 璁剧疆涓鸿骞寸殑 1鏈�1鏃� 00:00:00
+            yearCal.set(currentYear, Calendar.JANUARY, 1, 0, 0, 0);
+            yearCal.set(Calendar.MILLISECOND, 0);
+            row.setStartDate(yearCal.getTime());
+
+            // 璁剧疆涓鸿骞寸殑 12鏈�31鏃� 23:59:59
+            yearCal.set(currentYear, Calendar.DECEMBER, 31, 23, 59, 59);
+            yearCal.set(Calendar.MILLISECOND, 999);
+            row.setEndDate(yearCal.getTime());
+            // ----------------------------------------------------
+
+            result.add(row);
+        }
+
+        return result;
+    }
+
+
+    private List<MydTrendReqDTO> getMonthDate(MydTrendReqVO mydTrendReqVO, List<MydTrendReqDTO> list) {
+        // 鑾峰彇鏁翠綋寮�濮嬪拰缁撴潫鏃堕棿
+        Date startTime = mydTrendReqVO.getStartFinishTime();
+        Date endTime = mydTrendReqVO.getEndFinishTime();
+
+        // 1. 璁$畻鏈堜唤鑼冨洿
+        Calendar cal = Calendar.getInstance();
+        cal.setTime(startTime);
+        int startYear = cal.get(Calendar.YEAR);
+        int startMonth = cal.get(Calendar.MONTH); // 娉ㄦ剰锛欳alendar.MONTH 浠� 0 寮�濮�
+
+        cal.setTime(endTime);
+        int endYear = cal.get(Calendar.YEAR);
+        int endMonth = cal.get(Calendar.MONTH);
+
+        // 鎬绘湀鏁� = (缁撴潫骞� - 寮�濮嬪勾) * 12 + (缁撴潫鏈� - 寮�濮嬫湀) + 1
+        int totalMonths = (endYear - startYear) * 12 + (endMonth - startMonth) + 1;
+
+        // 2. 宸叉湁鏁版嵁锛歱eriod -> totalScore (period 瀛樼殑鏄湀浠藉瓧绗︿覆锛屽 "2024-06")
+        Map<String, String> exist = new HashMap<>();
+        for (MydTrendReqDTO row : list) {
+            String period = row.getPeriod() != null ? row.getPeriod() : "";
+            exist.put(period, row.getTotalScore() != null ? row.getTotalScore() : "0");
+        }
+
+        // 3. 琛ュ叏鎵�鏈夋湀浠�
+        List<MydTrendReqDTO> result = new ArrayList<>(totalMonths);
+
+        Calendar monthCal = Calendar.getInstance();
+        monthCal.set(startYear, startMonth, 1, 0, 0, 0);
+        monthCal.set(Calendar.MILLISECOND, 0);
+
+        for (int i = 0; i < totalMonths; i++) {
+            int currentYear = monthCal.get(Calendar.YEAR);
+            int currentMonth = monthCal.get(Calendar.MONTH) + 1; // 杞垚 1-12 鏈�
+
+            // 鎷兼帴 period锛屾牸寮忥細yyyy-MM锛屾瘮濡� "2024-06"
+            // 濡傛灉浣犲笇鏈涙槸 "2024-6"锛屽彲浠ユ妸 String.format 鏀规垚 currentMonth
+            String periodKey = String.format("%04d-%02d", currentYear, currentMonth);
+
+            MydTrendReqDTO row = new MydTrendReqDTO();
+            row.setPeriod(periodKey);
+            row.setTotalScore(exist.getOrDefault(periodKey, "0"));
+
+            // -------- 璁剧疆璇ユ湀鐨� startDate 鍜� endDate --------
+            // 褰撴湀寮�濮嬫椂闂达細1鍙� 00:00:00.000
+            Calendar startCal = (Calendar) monthCal.clone();
+            row.setStartDate(startCal.getTime());
+
+            // 褰撴湀缁撴潫鏃堕棿锛氫笅涓湀1鍙� 00:00:00.000 鍑� 1 姣
+            Calendar endCal = (Calendar) monthCal.clone();
+            endCal.add(Calendar.MONTH, 1);
+            endCal.add(Calendar.MILLISECOND, -1);
+            row.setEndDate(endCal.getTime());
+            // ----------------------------------------------------
+
+            result.add(row);
+
+            // 杩涘叆涓嬩竴涓湀
+            monthCal.add(Calendar.MONTH, 1);
+        }
+
+        return result;
+    }
+
+
+    private List<MydTrendReqDTO> getDayDate(MydTrendReqVO mydTrendReqVO, List<MydTrendReqDTO> list) {
+        // 鑾峰彇鏁翠綋寮�濮嬪拰缁撴潫鏃堕棿
+        Date startTime = mydTrendReqVO.getStartFinishTime();
+        Date endTime = mydTrendReqVO.getEndFinishTime();
+
+        // 1. 璁$畻鎬诲ぉ鏁�
+        long oneDayMillis = 24L * 60 * 60 * 1000;
+        // 娉ㄦ剰锛氳繖閲屽彧绠楁棩鏈熷樊锛屼笉鑰冭檻鏃跺垎绉掞紝鍚﹀垯鍙兘宸竴澶�
+        Calendar startCal = Calendar.getInstance();
+        startCal.setTime(startTime);
+        startCal.set(Calendar.HOUR_OF_DAY, 0);
+        startCal.set(Calendar.MINUTE, 0);
+        startCal.set(Calendar.SECOND, 0);
+        startCal.set(Calendar.MILLISECOND, 0);
+
+        Calendar endCal = Calendar.getInstance();
+        endCal.setTime(endTime);
+        endCal.set(Calendar.HOUR_OF_DAY, 0);
+        endCal.set(Calendar.MINUTE, 0);
+        endCal.set(Calendar.SECOND, 0);
+        endCal.set(Calendar.MILLISECOND, 0);
+
+        long days = (endCal.getTimeInMillis() - startCal.getTimeInMillis()) / oneDayMillis;
+        int totalDays = (int) days + 1;
+
+        // 2. 宸叉湁鏁版嵁锛歱eriod -> totalScore (period 瀛樼殑鏄棩鏈熷瓧绗︿覆锛屽 "2024-06-01")
+        Map<String, String> exist = new HashMap<>();
+        for (MydTrendReqDTO row : list) {
+            String period = row.getPeriod() != null ? row.getPeriod() : "";
+            exist.put(period, row.getTotalScore() != null ? row.getTotalScore() : "0");
+        }
+
+        // 3. 琛ュ叏鎵�鏈夋棩鏈�
+        List<MydTrendReqDTO> result = new ArrayList<>(totalDays);
+
+        Calendar dayCal = (Calendar) startCal.clone();
+        for (int i = 0; i < totalDays; i++) {
+            // 鎷兼帴 period锛屾牸寮忥細yyyy-MM-dd
+            String periodKey = String.format("%04d-%02d-%02d", dayCal.get(Calendar.YEAR), dayCal.get(Calendar.MONTH) + 1, dayCal.get(Calendar.DAY_OF_MONTH));
+
+            MydTrendReqDTO row = new MydTrendReqDTO();
+            row.setPeriod(periodKey);
+            row.setTotalScore(exist.getOrDefault(periodKey, "0"));
+
+            // -------- 璁剧疆璇ュぉ鐨� startDate 鍜� endDate --------
+            Calendar startOfDay = (Calendar) dayCal.clone();
+            row.setStartDate(startOfDay.getTime());
+
+            Calendar endOfDay = (Calendar) dayCal.clone();
+            endOfDay.set(Calendar.HOUR_OF_DAY, 23);
+            endOfDay.set(Calendar.MINUTE, 59);
+            endOfDay.set(Calendar.SECOND, 59);
+            endOfDay.set(Calendar.MILLISECOND, 999);
+            row.setEndDate(endOfDay.getTime());
+            // ----------------------------------------------------
+
+            result.add(row);
+
+            // 杩涘叆涓嬩竴澶�
+            dayCal.add(Calendar.DAY_OF_MONTH, 1);
+        }
+
+        return result;
+    }
+
+    public List<MzMydScoreRankDTO> mzMydScoreRank(MzMydScoreRankVO mzMydScoreRankVO) {
+        if (StringUtils.isEmpty(mzMydScoreRankVO.getDimensionType())) {
+            mzMydScoreRankVO.setDimensionType("1");
+        }
+        if (CollectionUtils.isEmpty(mzMydScoreRankVO.getServiceTypes())) {
+          List<String> list=new ArrayList<>(Arrays.asList("14"));
+            mzMydScoreRankVO.setServiceTypes(list);
+        }
+        List<MzMydScoreRankDTO> list = satisfactionMapper.selectMzMydScoreRank(mzMydScoreRankVO);
+        if (CollectionUtils.isEmpty(list)) {
+            return new ArrayList<>();
+        }
+        // 鍚嶆锛歋QL 宸叉寜缁煎悎寰楀垎闄嶅簭杩斿洖锛岃繖閲岄『搴忕紪鍙�
+        for (int i = 0; i < list.size(); i++) {
+            list.get(i).setRank(i + 1);
+        }
+        return list;
+    }
+    /**
+     * 鏄惁绌虹櫧瀛楃锛堝惈鍏ㄨ绌烘牸锛夈��
+     * <p>
+     * 杩佺Щ璇存槑锛氬師涓� ServiceSubtaskServiceImpl 鐨勭鏈夐潤鎬佹柟娉曪紝婊℃剰搴︽柟娉曠敤鍒帮紙trimAll锛夛紝涓�骞舵惉鍏ャ��
+     */
+    private static Boolean isBlankChar(char c) {
+        return c == '\u3000'          // 鍏ㄨ绌烘牸
+                || c == ' '            // 鍗婅绌烘牸
+                || c == '\t' || c == '\n' || c == '\r' || c == '\f' || Character.isWhitespace(c);
+    }
+    /**
+     * 鎸夋満鏋�/闄㈠尯/鏈嶅姟绫诲瀷/鍑洪櫌鏃堕棿/绉戝鐥呭尯鍙栧瓙浠诲姟寮曠敤锛堝彧鍚� id銆乼askid锛夈��
+     * <p>
+     * 鍘熷疄鐜拌蛋 {@code serviceSubtaskService.getServiceSubtasks(ServiceSubtaskEntity)}锛�
+     * 璇ユ柟娉曚綅浜庨殢璁夸富骞� Service 涓斾緷璧� patArchiveService锛屼笉鑳芥惉锛�
+     * 杩欓噷鐢ㄦ弧鎰忓害涓撶敤鏌ヨ澶嶇幇鍏�<b>瀹為檯鐢ㄥ埌鐨勬潯浠跺彛寰�</b>锛堥�愭潯瑙� mapper XML 娉ㄩ噴锛夈��
+     */
+    @Override
+    public List<SatisfactionSubtaskRef> getMydSubtaskRefs(SatisfactionSubtaskQuery query) {
+        if (query == null) {
+            return new ArrayList<>();
+        }
+        List<SatisfactionSubtaskRef> refs = satisfactionMapper.selectMydSubtaskRefs(query);
+        return refs == null ? new ArrayList<>() : refs;
+    }
+    // ==================== 婊℃剰搴﹀垎绫伙紙渚涚櫥褰曢摼璺紝甯︾紦瀛橈級 ====================
+
+    /**
+     * 婊℃剰搴﹀垎绫荤紦瀛� key 鍓嶇紑锛堜笌杩佺Щ鍓嶄竴鑷达級
+     */
+    private static final String SATISFACTION_CACHE_KEY = "satisfaction:categories:";
+
+    /**
+     * 婊℃剰搴﹀垎绫荤紦瀛樻椂闀匡紙鍒嗛挓锛屼笌杩佺Щ鍓嶄竴鑷达級
+     */
+    private static final int SATISFACTION_CACHE_TTL = 30;
+
+    @Autowired
+    private RedisCache redisCache;
+
+    /**
+     * 鑾峰彇婊℃剰搴﹂棶棰樺垎绫伙紙璇煶 + 闂嵎锛夛紝甯� Redis 缂撳瓨銆�
+     * <p>
+     * 杩佺Щ璇存槑锛氬師涓� {@code smartor.UserExtInfoServiceImpl.getSatisfactionCategories}锛�
+     * 鍥犲睘浜庢弧鎰忓害閫昏緫锛屾寜妯″潡鍖栬姹傛惉鍏ユ湰妯″潡锛涚紦瀛� key 涓� TTL 淇濇寔鍘熷�笺��
+     */
+    @Override
+    @SuppressWarnings("unchecked")
+    public Map<String, Object> getSatisfactionCategories(String orgid) {
+        // 鍏堟煡缂撳瓨
+        String cacheKey = SATISFACTION_CACHE_KEY + orgid;
+        Map<String, Object> cached = redisCache.getCacheObject(cacheKey);
+        if (cached != null) {
+            return cached;
+        }
+
+        Map<String, Object> satisfactionCategories = new HashMap<>();
+
+        // 鑾峰彇璇煶闂鍒嗙被
+        List<SatisfactionCategoryRow> ivrLibaScriptAssorts =
+                satisfactionScriptMapper.selectIvrCategoryList(categoryQueryByOrgid(orgid));
+        List<Map<String, Object>> categoryList = new ArrayList<>();
+        if (!CollectionUtils.isEmpty(ivrLibaScriptAssorts)) {
+            for (SatisfactionCategoryRow assort : ivrLibaScriptAssorts) {
+                Map<String, Object> map = new HashMap<>();
+                map.put("categoryid", assort.getId());
+                map.put("categoryName", assort.getName());
+                categoryList.add(map);
+            }
+        }
+        satisfactionCategories.put("voiceCategories", categoryList);
+
+        // 鑾峰彇闂嵎闂鍒嗙被
+        List<SatisfactionCategoryRow> svyLibScriptCategories =
+                satisfactionScriptMapper.selectSvyCategoryList(categoryQueryByOrgid(orgid));
+        List<Map<String, Object>> questionnaireCategorys = new ArrayList<>();
+        if (!CollectionUtils.isEmpty(svyLibScriptCategories)) {
+            for (SatisfactionCategoryRow scriptCategory : svyLibScriptCategories) {
+                Map<String, Object> map = new HashMap<>();
+                map.put("categoryid", scriptCategory.getId());
+                map.put("categoryName", scriptCategory.getName());
+                questionnaireCategorys.add(map);
+            }
+        }
+        satisfactionCategorysPut(satisfactionCategories, questionnaireCategorys);
+
+        // 鍐欏叆缂撳瓨
+        redisCache.setCacheObject(cacheKey, satisfactionCategories, SATISFACTION_CACHE_TTL, TimeUnit.MINUTES);
+
+        return satisfactionCategories;
+    }
+
+    /**
+     * 涓庡師鏂囦竴鑷达細閿悕涓� {@code questionnaireCategorys}
+     */
+    private void satisfactionCategorysPut(Map<String, Object> target, List<Map<String, Object>> value) {
+        target.put("questionnaireCategorys", value);
+    }
+
+    /**
+     * 鎸夋満鏋勬瀯閫犳弧鎰忓害鍒嗙被鏌ヨ锛坥rgid + type=myd锛屼笌鍘熸枃涓�鑷达級
+     */
+    private SatisfactionScriptQuery categoryQueryByOrgid(String orgid) {
+        SatisfactionScriptQuery q = new SatisfactionScriptQuery();
+        q.setOrgid(orgid);
+        q.setType("myd");
+        return q;
+    }
+
+    // ==================== 婊℃剰搴﹀紓甯搁厤缃鐩� ====================
+
+    /**
+     * 鑾峰彇婊℃剰搴﹀紓甯搁厤缃鐩紙璇煶 + 闂嵎锛夈��
+     * <p>
+     * 杩佺Щ璇存槑锛氬師瀹炵幇浣嶄簬 {@code com.smartor.service.impl.ServiceSubtaskDetailTraceServiceImpl}锛�
+     * 鐜版暣娈垫惉鍏ユ湰妯″潡銆傚師鏉ョ殑璋冪敤閾炬槸锛�
+     * <pre>
+     * 璇煶鍒嗙被锛欼vrLibaScriptAssortServiceImpl.selectIvrLibaScriptAssortList  鈫� 鏈嶅姟鏂规硶锛屼細鎶婂垎绫汇�愭嫾鎴愭爲銆戯紝鍙繑鍥為《灞�
+     * 璇煶棰樼洰锛欼vrLibaTemplateScriptServiceImpl.selectIvrLibaTemplateScriptList(scriptAssortid=X)  鈫� 閫愪釜鍒嗙被璋冪敤
+     * 闂嵎鍒嗙被锛歋vyLibScriptCategoryMapper.selectSvyLibScriptCategoryList     鈫� 鐩存帴 Mapper锛岃繑鍥炪�愬叏閮ㄣ�戝垎绫�
+     * 闂嵎棰樼洰锛歋vyLibTemplateScriptServiceImpl.selectSvyLibTemplateScriptList(categoryid=Y)       鈫� 閫愪釜鍒嗙被璋冪敤
+     * </pre>
+     * 涓轰笉渚濊禆 smartor 鐨勬ā鏉�/鍒嗙被瀹炰綋锛岃繖閲屾敼鎴愭湰妯″潡鐨勫彧璇绘煡璇㈠鐜帮紝骞朵笖锛�
+     * <ul>
+     *   <li>璇煶鍒嗙被锛氫粛鐒舵寜椤跺眰杩囨护锛坽@code pid == null || pid == -1}锛夛紝涓庡師鏈嶅姟鏂规硶鐨勭粨鏋滈泦涓�鑷达紱</li>
+     *   <li>闂嵎鍒嗙被锛氫笉杩囨护椤跺眰锛屽彇鍏ㄩ儴锛屼笌鍘� Mapper 璋冪敤涓�鑷达紱</li>
+     *   <li>棰樼洰锛氬師鏂囨槸鎸夊垎绫婚�愪釜鏌ヨ鍚庣疮鍔狅紝涓ゆ潯 list statement 閮�<b>娌℃湁 order by</b>锛�
+     *       鎵�浠ュ悎骞舵垚涓�娆� {@code in (...)} 鏌ヨ鐨勭粨鏋滈泦涓庨�愭鏌ヨ鐨勫苟闆嗕竴鑷�
+     *       锛堥泦鍚堢浉鍚屻�侀『搴忔湰鏉ュ氨鏈畾涔夛級锛�</li>
+     *   <li>鍒嗙被 id 鍒楄〃涓虹┖鏃�<b>涓嶆煡</b>锛堝師鏂囧氨鏄湪 {@code if (isNotEmpty)} 閲屾墠鏌ワ級锛�
+     *       瀵瑰簲鏈ā鍧� SQL 閲�"ids 涓虹┖鍒欎笉鎷� in 鏉′欢"鐨勫啓娉曞繀椤婚潬杩欎釜鍒ゆ柇鍏滀綇锛�
+     *       鍚﹀垯浼氶��鍖栨垚"鍏ㄨ〃鏌ヨ"銆�</li>
+     * </ul>
+     */
+    @Override
+    public Map<String, Object> getSatisfactionExceptConfigQuestion(SatisfactionExceptConfigQuestionReqVO questionReqVO) {
+        // 鍒濆鍖栫粨鏋滈泦
+        Map<String, Object> result = new HashMap<>();
+
+        // ---------------------------------------------------------------- 璇煶
+        if (StringUtils.isEmpty(questionReqVO.getScriptType()) || questionReqVO.getScriptType().equals("1")) {
+            List<Map<String, Object>> voicetDataList = new ArrayList<>();
+
+            // 鑾峰彇婊℃剰搴︾殑鍒嗙被锛坱ype 榛樿 myd锛屽叆鍙傛湁鍊煎垯瑕嗙洊 鈥斺�� 涓庡師鏂囦竴鑷达級
+            List<Long> assortIdList = selectIvrTopCategoryIds(questionReqVO.getType());
+
+            // 鐢ㄦ潵璁板綍婊℃剰搴︾殑鎵�鏈夐棶棰�
+            if (CollectionUtils.isNotEmpty(assortIdList)) {
+                SatisfactionScriptQuery scriptQuery = new SatisfactionScriptQuery();
+                scriptQuery.setScriptAssortids(assortIdList);
+                List<SatisfactionIvrScriptRow> ivrScripts =
+                        satisfactionScriptMapper.queryIvrScriptByAssort(scriptQuery);
+                if (CollectionUtils.isNotEmpty(ivrScripts)) {
+                    for (SatisfactionIvrScriptRow ivrScript : ivrScripts) {
+                        Map<String, Object> voicetData = new HashMap<>();
+                        voicetData.put("questionId", ivrScript.getId());
+                        voicetData.put("libtemplateid", ivrScript.getTemplateid());
+                        voicetData.put("questionContent", ivrScript.getScriptContent());
+                        voicetData.put("questionTopic", ivrScript.getScriptTopic());
+                        voicetData.put("questionType", ivrScript.getScriptType());
+                        voicetData.put("categoryName", ivrScript.getScriptAssortname());
+                        voicetData.put("categoryId", ivrScript.getScriptAssortid());
+                        voicetDataList.add(voicetData);
+                    }
+                }
+            }
+            // 璇煶鏁版嵁杩斿洖闆嗗悎
+            result.put("voiceDataList", voicetDataList);
+        }
+
+        // ---------------------------------------------------------------- 闂嵎
+        if (StringUtils.isEmpty(questionReqVO.getScriptType()) || questionReqVO.getScriptType().equals("2")) {
+            List<Map<String, Object>> questionnaireDataList = new ArrayList<>();
+
+            // 鑾峰彇婊℃剰搴︾殑鍒嗙被锛堥棶鍗蜂晶鍘熸枃鐩存帴鏌� Mapper锛屽彇鍏ㄩ儴鍒嗙被锛屼笉鎷兼爲銆佷笉绛涢《灞傦級
+            List<Long> categoryIdList = selectSvyCategoryIds(questionReqVO.getType());
+
+            // 鐢ㄦ潵璁板綍婊℃剰搴︾殑鎵�鏈夐棶棰�
+            if (CollectionUtils.isNotEmpty(categoryIdList)) {
+                SatisfactionScriptQuery scriptQuery = new SatisfactionScriptQuery();
+                scriptQuery.setScriptCategoryids(categoryIdList);
+                List<SatisfactionSvyScriptRow> svyScripts =
+                        satisfactionScriptMapper.querySvyScriptByCategory(scriptQuery);
+                if (CollectionUtils.isNotEmpty(svyScripts)) {
+                    for (SatisfactionSvyScriptRow svyScript : svyScripts) {
+                        Map<String, Object> svyData = new HashMap<>();
+                        svyData.put("questionId", svyScript.getId());
+                        svyData.put("libtemplateid", svyScript.getSvyid());
+                        svyData.put("questionContent", svyScript.getScriptContent());
+                        svyData.put("questionTopic", svyScript.getScriptTopic());
+                        svyData.put("questionType", svyScript.getScriptType());
+                        svyData.put("categoryName", svyScript.getCategoryName());
+                        svyData.put("categoryId", svyScript.getCategoryid());
+                        questionnaireDataList.add(svyData);
+                    }
+                }
+            }
+            // 闂嵎鏁版嵁杩斿洖闆嗗悎
+            result.put("questionnaireDataList", questionnaireDataList);
+        }
+        return result;
+    }
+
+    /**
+     * 鍙栬闊充晶婊℃剰搴﹀垎绫� id锛�<b>鍙彇椤跺眰</b>锛屽鐜板師鏈嶅姟鏂规硶鎷兼爲鍚庣殑杩斿洖鍊硷級銆�
+     *
+     * @param type 婊℃剰搴︾被鍨嬶紱涓虹┖鏃剁敤 {@code "myd"}
+     */
+    private List<Long> selectIvrTopCategoryIds(String type) {
+        SatisfactionScriptQuery query = new SatisfactionScriptQuery();
+        query.setType(StringUtils.isEmpty(type) ? "myd" : type);
+        // 鍘熸枃娌℃湁浼� orgid锛岃繖閲屼繚鎸佷竴鑷达紙orgid 涓虹┖鏃� SQL 涓嶆嫾璇ユ潯浠讹級
+        List<SatisfactionCategoryRow> rows = satisfactionScriptMapper.selectIvrCategoryList(query);
+        List<Long> ids = new ArrayList<>();
+        if (CollectionUtils.isNotEmpty(rows)) {
+            for (SatisfactionCategoryRow row : rows) {
+                // 涓� IvrLibaScriptAssortServiceImpl 鎷兼爲鏃剁殑椤跺眰鍒ゆ柇閫愬瓧涓�鑷�
+                if (row.getPid() == null || row.getPid() == -1) {
+                    ids.add(row.getId());
+                }
+            }
+        }
+        return ids;
+    }
+
+    /**
+     * 鍙栭棶鍗蜂晶婊℃剰搴﹀垎绫� id锛�<b>鍙栧叏閮�</b>锛屽鐜板師 Mapper 璋冪敤鐨勮繑鍥炲�硷級銆�
+     *
+     * @param type 婊℃剰搴︾被鍨嬶紱涓虹┖鏃剁敤 {@code "myd"}
+     */
+    private List<Long> selectSvyCategoryIds(String type) {
+        SatisfactionScriptQuery query = new SatisfactionScriptQuery();
+        query.setType(StringUtils.isEmpty(type) ? "myd" : type);
+        List<SatisfactionCategoryRow> rows = satisfactionScriptMapper.selectSvyCategoryList(query);
+        List<Long> ids = new ArrayList<>();
+        if (CollectionUtils.isNotEmpty(rows)) {
+            for (SatisfactionCategoryRow row : rows) {
+                ids.add(row.getId());
+            }
+        }
+        return ids;
+    }
+
+    // ==================== 闅忚銆屾弧鎰忓害鎸囨暟銆� ====================
+
+    /**
+     * 銆屾弧鎰忓害棰樼洰鍙戦�侀噺銆嶇粺璁℃椂浣跨敤鐨勫瓙浠诲姟鍙戦�佺姸鎬侊紙涓庡師瀹炵幇涓�鑷达細3銆�5銆�6锛�
+     */
+    private static final List<Long> SATISFACTION_INDEX_SENDSTATES =
+            Collections.unmodifiableList(Arrays.asList(3L, 5L, 6L));
+
+    /**
+     * 闅忚缁熻椤电殑銆屾弧鎰忓害鎸囨暟銆嶏細婊℃剰搴﹂鐩�婚噺 / 濉姤閲� / 瀹屾垚姣斾緥銆�
+     * <p>
+     * 杩佺Щ璇存槑锛氬師涓� {@code com.smartor.service.impl.ServiceSubtaskServiceImpl#getSatisfactionIndex}
+     * 鐨勭鏈夋柟娉曪紝琚� {@code getSfStatisticsJoy} 璋冪敤銆傚畠鐨勫彇鏁板璞℃槸
+     * 銆屾弧鎰忓害棰樼洰銆嶏紙鎸� {@code ivrCategoryid} / {@code svyCategoryid} 璇嗗埆锛変笌
+     * 銆屾弧鎰忓害濉姤鏄庣粏銆嶏紝灞炰簬婊℃剰搴﹀彛寰勶紝鏁呮惉鍏ユ湰妯″潡銆�
+     * <p>
+     * <b>涓庡師瀹炵幇閫愭潯瀵瑰簲锛�</b>
+     * <ol>
+     *   <li>{@code type="1"} 鐢� {@code deptcode} 鍖归厤浠诲姟锛泏@code type="2"} 鐢�
+     *       {@code leavehospitaldistrictcode} 鍖归厤浠诲姟锛堜簰鏂ワ紝閮藉甫 orgid锛夛紱</li>
+     *   <li>璇煶浠诲姟锛氶鐩暟 = 璇ユā鏉夸笅 {@code script_assortid = ivrCategoryid} 鐨勯鐩暟锛�
+     *       鎬婚噺 += 棰樼洰鏁� 脳 鍙戦�侀噺锛坽@code sendstate in (3,5,6)}锛夛紱</li>
+     *   <li>闂嵎浠诲姟锛氬悓涓婏紝鐢� {@code categoryid = svyCategoryid}锛�</li>
+     *   <li>濉姤閲忥細鎸� {@code taskid + categoryid(鎸変换鍔$被鍨嬪彇璇煶/闂嵎) + orgid} 鏁版槑缁嗘潯鏁帮紱</li>
+     *   <li>瀹屾垚姣斾緥 = 濉姤閲� / 鎬婚噺锛堜繚鐣� 2 浣嶏紝HALF_UP锛夛紝鎬婚噺涓� 0 鏃朵负 {@code 0.0}銆�</li>
+     * </ol>
+     *
+     * <h3>鈿狅笍 鍘熷疄鐜板凡鐭ョ己闄凤紙鏈<b>鍘熸牱淇濈暀</b>锛屾湭淇锛�</h3>
+     * 鍘熶唬鐮佺敤鐨勬槸 {@code mydtmzlsum.add(new BigDecimal(n))} 鈥斺��
+     * {@link BigDecimal} 鏄笉鍙彉绫伙紝{@code add} 鐨勮繑鍥炲�兼病鏈夎璧嬪洖鍙橀噺锛�
+     * 鍥犳<b>鎵�鏈夌疮鍔犻兘鏄棤鏁堢殑</b>锛屾湰鏂规硶瀹為檯涓�<b>鎭掑畾杩斿洖</b>
+     * {@code {mydtmzl: 0, mydtbl: 0, wcbl: 0.0}}锛�
+     * 鍚屾椂鍥犱负绱姞璇彞浠嶅湪寰幆閲岋紝<b>姣忔璋冪敤渚濇棫浼氱湡瀹炴墽琛岄偅 5 鏉℃煡璇�</b>銆�
+     * <p>
+     * 杩佺Щ鏃舵寜"琛屼负涓嶅彉"鍘熷垯涓�瀛楁湭鏀癸紙杩炴棤鏁堢疮鍔犱篃淇濈暀锛夛細
+     * 鑻ョ洿鎺ユ妸鏌ヨ缁撴灉鏀规垚甯搁噺锛岃櫧鐒惰繑鍥炲�肩浉鍚岋紝浣嗕細鏀瑰彉鏁版嵁搴撳帇鍔涳紱
+     * 鑻ユ妸 {@code add} 鏀规垚 {@code xxx = xxx.add(...)}锛屽垯浼氭敼鍙橀殢璁跨粺璁¢〉鐨勬樉绀哄�笺��
+     * 涓ょ閮借秴鍑烘湰娆�"妯″潡鍖栥�佷笉鏀硅涓�"鐨勮寖鍥达紝鏁呯暀寰呭崟鐙‘璁ゃ��
+     * 璇﹁ {@code doc/婊℃剰搴︽ā鍧楀寲_鎵ц杩涘害.md} 鐨勯仐鐣欓」銆�
+     *
+     * @param type            "1"=鎸夌瀹わ紝"2"=鎸夌梾鍖猴紙涓庡師鏂囦竴鑷达級
+     * @param code            绉戝缂栫爜鎴栫梾鍖虹紪鐮�
+     * @param orgid           鏈烘瀯 id
+     * @param ivrCategoryid   璇煶婊℃剰搴﹀垎绫� id锛堝彲涓� null锛屼负 null 鏃惰鏉′欢涓嶇敓鏁堬級
+     * @param svyCategoryid   闂嵎婊℃剰搴﹀垎绫� id锛堝彲涓� null锛屼负 null 鏃惰鏉′欢涓嶇敓鏁堬級
+     * @return 鍚� {@code mydtmzl}(Integer) / {@code mydtbl}(Integer) / {@code wcbl}(Double) 鐨� Map
+     */
+    @Override
+    public Map<String, Object> getSatisfactionIndex(String type, String code, String orgid,
+                                                   Long ivrCategoryid, Long svyCategoryid) {
+        // 鍘熸枃鏄� type.equals("1")锛涜皟鐢ㄦ柟鍙紶瀛楅潰閲� "1"/"2"锛岃繖閲岀敤甯搁噺鍦ㄥ墠绛変环涓旀洿瀹夊叏
+        List<SatisfactionTaskRow> serviceTasks = null;
+        if ("1".equals(type)) {
+            serviceTasks = satisfactionMapper.selectSatisfactionServiceTasks(orgid, code, null);
+        } else if ("2".equals(type)) {
+            serviceTasks = satisfactionMapper.selectSatisfactionServiceTasks(orgid, null, code);
+        }
+
+        // 鑾峰彇婊℃剰搴﹂鐩�婚噺
+        BigDecimal mydtmzlsum = new BigDecimal(0);
+        // 鑾峰彇婊℃剰搴﹂鐩~鎶ラ噺
+        BigDecimal mydtblsum = new BigDecimal(0);
+        if (CollectionUtils.isNotEmpty(serviceTasks)) {
+            for (SatisfactionTaskRow serviceTask : serviceTasks) {
+                if (serviceTask.getType().equals("1")) {
+                    // 璇煶
+                    Long templateid = serviceTask.getTemplateid();
+                    int scriptCount = satisfactionMapper.countIvrTaskTemplateScripts(
+                            templateid, serviceTask.getOrgid(), ivrCategoryid);
+                    if (scriptCount > 0) {
+                        // 闇�瑕佸厛璁$畻鍑哄彂閫侀噺锛屽啀涔樹互鍗曚釜闂埜婊℃剰搴﹂鐩暟閲忥紝寰楀埌婊℃剰搴﹂鐩�婚噺锛堝寘鍚簡鏈仛鐨勶級
+                        int subtaskCount = satisfactionMapper.countSubtaskBySendstates(
+                                serviceTask.getTaskid(), SATISFACTION_INDEX_SENDSTATES);
+                        if (subtaskCount > 0) {
+                            int mydtmzlCount = scriptCount * subtaskCount;
+                            // 鈿狅笍 鍘熷疄鐜扮己闄凤細add 鐨勮繑鍥炲�艰涓㈠純锛圔igDecimal 涓嶅彲鍙橈級锛屾澶勫師鏍蜂繚鐣�
+                            mydtmzlsum.add(new BigDecimal(mydtmzlCount));
+                        }
+                    }
+                } else if (serviceTask.getType().equals("2")) {
+                    // 闂嵎
+                    int scriptCount = satisfactionMapper.countSvyTaskTemplateScripts(
+                            serviceTask.getTemplateid(), serviceTask.getOrgid(), svyCategoryid);
+                    if (scriptCount > 0) {
+                        int subtaskCount = satisfactionMapper.countSubtaskBySendstates(
+                                serviceTask.getTaskid(), SATISFACTION_INDEX_SENDSTATES);
+                        if (subtaskCount > 0) {
+                            int mydtmzlCount = scriptCount * subtaskCount;
+                            // 鈿狅笍 鍘熷疄鐜扮己闄凤細鍚屼笂锛屽師鏍蜂繚鐣�
+                            mydtmzlsum.add(new BigDecimal(mydtmzlCount));
+                        }
+                    }
+                }
+
+                // 鑾峰彇婊℃剰搴﹂鐩~鎶ラ噺
+                Long categoryid = null;
+                if (serviceTask.getType().equals("1")) categoryid = ivrCategoryid;
+                if (serviceTask.getType().equals("2")) categoryid = svyCategoryid;
+                int detailCount = satisfactionMapper.countSubtaskDetail(
+                        serviceTask.getTaskid(), categoryid, serviceTask.getOrgid());
+                if (detailCount > 0) {
+                    // 鈿狅笍 鍘熷疄鐜扮己闄凤細鍚屼笂锛屽師鏍蜂繚鐣�
+                    mydtblsum.add(new BigDecimal(detailCount));
+                }
+            }
+        }
+
+        Map<String, Object> map = new HashMap<>();
+        // 婊℃剰搴﹂鐩�婚噺
+        map.put("mydtmzl", mydtmzlsum.intValue());
+        map.put("mydtbl", mydtblsum.intValue());
+        map.put("wcbl", 0.0);
+        if (mydtmzlsum.intValue() != 0) {
+            map.put("wcbl", mydtblsum.divide(mydtmzlsum, 2, RoundingMode.HALF_UP).doubleValue());
+        }
+        return map;
+    }
+}
\ No newline at end of file
diff --git a/ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/service/strategy/CommonSatisfactionScoreStrategy.java b/ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/service/strategy/CommonSatisfactionScoreStrategy.java
new file mode 100644
index 0000000..3c237f0
--- /dev/null
+++ b/ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/service/strategy/CommonSatisfactionScoreStrategy.java
@@ -0,0 +1,82 @@
+package com.ruoyi.satisfaction.service.strategy;
+
+import com.ruoyi.satisfaction.domain.ServiceSubtaskDetailRatioExport;
+import org.apache.commons.collections4.CollectionUtils;
+import org.springframework.stereotype.Component;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.util.List;
+
+/**
+ * 閫氱敤鍙e緞锛堝崡鍗庝娇鐢級鐨勭患鍚堟弧鎰忓害寰楀垎绛栫暐銆�
+ * <p>
+ * <b>鍙e緞涓庡師瀹炵幇瀹屽叏涓�鑷�</b>锛堢瓑浜� {@code PatSatisfactionServiceImpl#averageScoreByStat}锛夛細
+ * <pre>
+ *   鍒嗗瓙 = 危(鍙В鏋愮殑 score 脳 璇ュ垎鍊肩殑鏄庣粏琛屾暟)
+ *   鍒嗘瘝 = 鍏ㄩ儴鏄庣粏琛屾暟锛�<b>鍖呭惈鍒嗗�间负绌虹殑琛�</b>锛�
+ *   缁撴灉 = 鍒嗗瓙 / 鍒嗘瘝锛屼繚鐣� 1 浣嶃�丠ALF_UP锛涙棤琛屾椂 0.0
+ * </pre>
+ * 鈿狅笍 涓や釜"涓嶈兘鏀�"鐨勭偣锛堝師娉ㄩ噴宸插己璋冿紝杩欓噷鍘熸牱淇濈暀锛夛細
+ * <ol>
+ *   <li>鍒嗘瘝鏄�<b>鏄庣粏琛屾暟</b>锛屼笉鏄�"鏈夊垎鍊肩殑琛屾暟" 鈥斺�� 鏀规垚鍚庤�呬細鏀瑰彉 {@code averageScore}锛�</li>
+ *   <li>鍒嗗�艰В鏋愮敤 {@code Double.parseDouble}锛岃剰鍊间細鎶� {@code NumberFormatException}
+ *       锛堝師瀹炵幇鍗冲姝わ紝鏈姞 try/catch锛屼繚鎸佷竴鑷达級銆�</li>
+ * </ol>
+ */
+@Component
+public class CommonSatisfactionScoreStrategy implements SatisfactionScoreStrategy {
+
+    /**
+     * 閫氱敤鍙e緞鐨勪繚鐣欎綅鏁帮紙1 浣嶏紝涓� {@code averageScoreByStat} 涓�鑷达級
+     */
+    private static final int SCALE = 1;
+
+    @Override
+    public String site() {
+        return SatisfactionSites.COMMON;
+    }
+
+    @Override
+    public String siteName() {
+        return "閫氱敤鍙e緞锛堝崡鍗庯級";
+    }
+
+    @Override
+    public BigDecimal comprehensiveScore(List<ServiceSubtaskDetailRatioExport> rows) {
+        if (CollectionUtils.isEmpty(rows)) {
+            return BigDecimal.ZERO.setScale(SCALE);
+        }
+
+        double sum = 0.0;
+        long totalRows = 0L;
+        for (ServiceSubtaskDetailRatioExport row : rows) {
+            long count = parseCount(row.getCount());
+            // 涓庡師鏂囦竴鑷达細闈炴鏁扮殑鑱氬悎琛岀洿鎺ヨ烦杩�
+            if (count <= 0L) continue;
+            totalRows += count;
+            String score = row.getScore();
+            if (score != null && !score.isEmpty()) {
+                sum += Double.parseDouble(score) * count;
+            }
+        }
+        if (totalRows == 0L) {
+            return BigDecimal.ZERO.setScale(SCALE);
+        }
+        return new BigDecimal(sum / totalRows).setScale(SCALE, RoundingMode.HALF_UP);
+    }
+
+    /**
+     * 鏄庣粏琛屾暟瑙f瀽锛氫笌 {@code ScoreCount.getCount()} 绛変环锛堝師濮嬫槸 long锛岃繖閲屼粠瀛楃涓插彇锛�
+     */
+    private long parseCount(String count) {
+        if (count == null || count.isEmpty()) {
+            return 0L;
+        }
+        try {
+            return Long.parseLong(count.trim());
+        } catch (NumberFormatException e) {
+            return 0L;
+        }
+    }
+}
diff --git a/ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/service/strategy/SatisfactionScoreStrategy.java b/ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/service/strategy/SatisfactionScoreStrategy.java
new file mode 100644
index 0000000..758c302
--- /dev/null
+++ b/ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/service/strategy/SatisfactionScoreStrategy.java
@@ -0,0 +1,44 @@
+package com.ruoyi.satisfaction.service.strategy;
+
+import com.ruoyi.satisfaction.domain.ServiceSubtaskDetailRatioExport;
+
+import java.math.BigDecimal;
+import java.util.List;
+
+/**
+ * 婊℃剰搴︺�岀珯鐐瑰彛寰勩�嶇瓥鐣ワ細<b>涓�涓珯鐐逛竴濂楃患鍚堟弧鎰忓害寰楀垎鍙e緞</b>銆�
+ * <p>
+ * 杩欐槸鏈ā鍧楃敤鏉ュ尯鍒嗐�屽崡鍗庛�嶄笌銆岀渷绔嬪悓寰枫�嶄袱濂楁弧鎰忓害鍙e緞鐨�<b>绛栫暐鎺ュ彛</b>锛�
+ * <ul>
+ *   <li>{@link CommonSatisfactionScoreStrategy}锛坽@link SatisfactionSites#COMMON}锛夆�斺�� 閫氱敤/鍗楀崕鍙e緞锛�</li>
+ *   <li>{@link SltdSatisfactionScoreStrategy}锛坽@link SatisfactionSites#SLTD}锛夆�斺�� 鐪佺珛鍚屽痉鍙e緞銆�</li>
+ * </ul>
+ * 鐢� {@link SatisfactionScoreStrategyResolver} 鎸夌珯鐐规爣璇嗛�夋嫨瀹炵幇锛涜皟鐢ㄦ柟锛圕ontroller / Service锛�
+ * 鍙緷璧栨湰鎺ュ彛 + 瑙f瀽鍣紝涓嶅啀鑷繁鍐欑珯鐐瑰垎鏀� 鈥斺�� 浠ュ悗鏂板绔欑偣鍙渶鏂板涓�涓疄鐜扮被銆�
+ * <p>
+ * <b>鍏ュ弬缁熶竴涓�</b>{@code List<ServiceSubtaskDetailRatioExport>}锛�"闂璇︽儏鍗犳瘮"琛岋細閫夐」 + 鏁伴噺 + 鍒嗗�硷級锛�
+ * 鍥犱负涓や釜绔欑偣鐨勭患璇勫叆鍙f嬁鍒扮殑閮芥槸杩欎釜缁撴瀯銆�
+ */
+public interface SatisfactionScoreStrategy {
+
+    /**
+     * 绔欑偣鏍囪瘑锛堣鑼冨�硷紝鍙栬嚜 {@link SatisfactionSites}锛�
+     */
+    String site();
+
+    /**
+     * 绔欑偣鍚嶇О锛堜腑鏂囷紝鐢ㄤ簬鏂囨。涓庢棩蹇楋級
+     */
+    String siteName();
+
+    /**
+     * 璁$畻銆岀患鍚堟弧鎰忓害寰楀垎銆嶃��
+     * <p>
+     * 杩斿洖鍊艰涔夌敱鍚勭珯鐐瑰彛寰勮嚜琛屽畾涔夛紙閫氱敤鍙e緞鏄�<b>骞冲潎鍒�</b>淇濈暀 1 浣嶏紱
+     * 鍚屽痉鍙e緞鏄�<b>鐧惧垎鍒跺姞鏉冨緱鍒�</b>淇濈暀 2 浣嶏級锛岃皟鐢ㄦ柟鎸夌珯鐐规棦鏈夌害瀹氬憟鐜般��
+     *
+     * @param rows 鍚屼竴閬撻锛堟垨鍚屼竴鎵癸級鐨勯�夐」鍗犳瘮琛�
+     * @return 寰楀垎锛�<b>鏃犳硶璁$畻鏃惰繑鍥� {@code null}</b>锛堣皟鐢ㄦ柟鎸夌珯鐐规棦鏈夌害瀹氬洖閫�锛屼緥濡傚悓寰峰洖閫�涓� "0.00"锛�
+     */
+    BigDecimal comprehensiveScore(List<ServiceSubtaskDetailRatioExport> rows);
+}
diff --git a/ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/service/strategy/SatisfactionScoreStrategyResolver.java b/ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/service/strategy/SatisfactionScoreStrategyResolver.java
new file mode 100644
index 0000000..ba63b20
--- /dev/null
+++ b/ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/service/strategy/SatisfactionScoreStrategyResolver.java
@@ -0,0 +1,71 @@
+package com.ruoyi.satisfaction.service.strategy;
+
+import com.ruoyi.common.exception.ServiceException;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+
+/**
+ * 婊℃剰搴︾珯鐐瑰彛寰勭瓥鐣ヨВ鏋愬櫒銆�
+ * <p>
+ * 鐢� Spring 娉ㄥ叆鍏ㄩ儴 {@link SatisfactionScoreStrategy} 瀹炵幇锛屾寜绔欑偣鏍囪瘑閫夋嫨锛�
+ * 绔欑偣涓虹┖鎴栨湭鐭ユ椂<b>鍥炶惤鍒伴�氱敤鍙e緞</b>骞舵墦 WARN锛堜繚璇佽皟鐢ㄦ柟姘歌繙涓嶄細鍥犱负
+ * "浼犱簡涓病瑙佽繃鐨勭珯鐐�"鑰屽け璐ワ級銆�
+ * <p>
+ * 鐢ㄦ硶锛�
+ * <pre>
+ * BigDecimal score = resolver.resolve(SatisfactionSites.SLTD).comprehensiveScore(rows);
+ * </pre>
+ * 浠ュ悗鏂板绔欑偣锛氬彧鍔犱竴涓� {@code SatisfactionScoreStrategy} 瀹炵幇绫诲嵆鍙紝鏈被鏃犻渶鏀瑰姩銆�
+ */
+@Slf4j
+@Component
+public class SatisfactionScoreStrategyResolver {
+
+    private final Map<String, SatisfactionScoreStrategy> strategies;
+
+    private final SatisfactionScoreStrategy fallback;
+
+    public SatisfactionScoreStrategyResolver(List<SatisfactionScoreStrategy> strategyList) {
+        Map<String, SatisfactionScoreStrategy> map = new LinkedHashMap<>();
+        if (strategyList != null) {
+            for (SatisfactionScoreStrategy strategy : strategyList) {
+                map.put(SatisfactionSites.normalize(strategy.site()), strategy);
+            }
+        }
+        this.strategies = Collections.unmodifiableMap(map);
+        this.fallback = map.get(SatisfactionSites.COMMON);
+        if (this.fallback == null) {
+            // 鍙彲鑳藉湪閰嶇疆閿欒鏃跺彂鐢燂紙閫氱敤鍙e緞瀹炵幇琚鍒狅級锛屽敖鏃╂毚闇�
+            throw new ServiceException("婊℃剰搴︽ā鍧楃己灏戦�氱敤鍙e緞绛栫暐瀹炵幇锛坰ite=" + SatisfactionSites.COMMON + "锛�");
+        }
+    }
+
+    /**
+     * 鎸夌珯鐐瑰彇绛栫暐锛涙湭鐭ョ珯鐐瑰洖钀藉埌閫氱敤鍙e緞骞舵墦 WARN
+     *
+     * @param site 绔欑偣鏍囪瘑锛坽@link SatisfactionSites} 涓殑甯搁噺锛屾垨鍏跺埆鍚嶏級
+     */
+    public SatisfactionScoreStrategy resolve(String site) {
+        String key = SatisfactionSites.normalize(site);
+        SatisfactionScoreStrategy strategy = strategies.get(key);
+        if (strategy == null) {
+            log.warn("銆愭弧鎰忓害銆戞湭鎵惧埌绔欑偣鍙e緞绛栫暐 site={}锛屽洖钀藉埌閫氱敤鍙e緞锛坽}锛�", site, SatisfactionSites.COMMON);
+            return fallback;
+        }
+        return strategy;
+    }
+
+    /**
+     * 宸叉敞鍐岀殑绔欑偣鏍囪瘑锛堜緵鏍¢獙/鏂囨。浣跨敤锛�
+     */
+    public Set<String> supportedSites() {
+        return Collections.unmodifiableSet(new TreeSet<>(strategies.keySet()));
+    }
+}
diff --git a/ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/service/strategy/SatisfactionSites.java b/ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/service/strategy/SatisfactionSites.java
new file mode 100644
index 0000000..7607798
--- /dev/null
+++ b/ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/service/strategy/SatisfactionSites.java
@@ -0,0 +1,73 @@
+package com.ruoyi.satisfaction.service.strategy;
+
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * 婊℃剰搴︺�岀珯鐐瑰彛寰勩�嶆爣璇嗐��
+ * <p>
+ * 鏈ā鍧楃敤<b>绛栫暐</b>鍖哄垎涓ゅ婊℃剰搴﹀彛寰勶紙瑙� {@link SatisfactionScoreStrategy}锛夛細
+ * <table border="1">
+ *   <tr><th>绔欑偣</th><th>鏍囪瘑</th><th>鍙e緞</th></tr>
+ *   <tr>
+ *     <td>閫氱敤锛堝崡鍗庝娇鐢紱鍚屽痉涔熷鐢ㄥ叾涓殑 4 涓�氱敤绔偣锛�</td>
+ *     <td>{@link #COMMON}锛坽@code "common"}锛屽埆鍚� {@code "nanhua"}锛�</td>
+ *     <td>{@link CommonSatisfactionScoreStrategy}锛氭寜鏄庣粏鐨� {@code score} 瀛楁姹傚钩鍧囷紙鍒嗘瘝鍚垎鍊间负绌虹殑琛岋級</td>
+ *   </tr>
+ *   <tr>
+ *     <td>鐪佺珛鍚屽痉</td>
+ *     <td>{@link #SLTD}锛坽@code "sltd"}锛�</td>
+ *     <td>{@link SltdSatisfactionScoreStrategy}锛氭寜閫夐」< b>鏂囨</b>鍒ゅ畾浜旀。骞跺姞鏉冿紙1/0.8/0.6/0.4/0.2锛夛紝鐧惧垎鍒�</td>
+ *   </tr>
+ * </table>
+ * <p>
+ * <b>涓轰粈涔堜袱濂楀彛寰勫繀椤诲垎寮�</b>锛氬悓寰风殑鍘嗗彶鏁版嵁閲� {@code score} 鏈� {@code 0}銆亄@code 0000000001}
+ * 杩欑被鑴忓�硷紝鎸� {@code score} 姹傚钩鍧囦細鎶婂緱鍒嗗浐瀹氭垚 0 鎴� 100锛屾墍浠ュ悓寰峰彧鑳芥寜閫夐」鏂囨鍒ゆ。锛�
+ * 鑰岄�氱敤鍙e緞鐨勫崡鍗庢暟鎹病鏈夎繖涓棶棰橈紝鏀圭敤鏂囨鍒ゆ。鍙嶈�屼細璁╃粨鏋滀笌杩佺Щ鍓嶄笉涓�鑷淬��
+ * 鍥犳杩欓噷涓嶅仛"缁熶竴绠楁硶"锛岃�屾槸鐢ㄧ瓥鐣ユ樉寮忓尯鍒嗐��
+ */
+public final class SatisfactionSites {
+
+    /**
+     * 閫氱敤鍙e緞锛堝崡鍗庝娇鐢紝鍚屽痉鐨� 4 涓�氱敤绔偣涔熻蛋瀹冿級
+     */
+    public static final String COMMON = "common";
+
+    /**
+     * 鐪佺珛鍚屽痉鍙e緞
+     */
+    public static final String SLTD = "sltd";
+
+    /**
+     * 绔欑偣鍒悕 鈫� 瑙勮寖绔欑偣鏍囪瘑锛堝崡鍗庢病鏈夌嫭鍗犲彛寰勶紝褰掑埌閫氱敤鍙e緞锛�
+     */
+    private static final Map<String, String> ALIASES;
+
+    static {
+        Map<String, String> aliases = new LinkedHashMap<>();
+        aliases.put("nanhua", COMMON);
+        aliases.put("閫氱敤", COMMON);
+        aliases.put("鍚屽痉", SLTD);
+        aliases.put("sld", SLTD);
+        ALIASES = Collections.unmodifiableMap(aliases);
+    }
+
+    private SatisfactionSites() {
+    }
+
+    /**
+     * 褰掍竴鍖栫珯鐐规爣璇嗭細鍘荤┖鐧姐�佽浆灏忓啓銆佹寜鍒悕鏄犲皠锛涚┖鍊艰繑鍥� {@link #COMMON}
+     */
+    public static String normalize(String site) {
+        if (site == null) {
+            return COMMON;
+        }
+        String key = site.trim().toLowerCase(java.util.Locale.ROOT);
+        if (key.isEmpty()) {
+            return COMMON;
+        }
+        String mapped = ALIASES.get(key);
+        return mapped == null ? key : mapped;
+    }
+}
diff --git a/ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/service/strategy/SltdSatisfactionScoreStrategy.java b/ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/service/strategy/SltdSatisfactionScoreStrategy.java
new file mode 100644
index 0000000..6a9bb4a
--- /dev/null
+++ b/ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/service/strategy/SltdSatisfactionScoreStrategy.java
@@ -0,0 +1,137 @@
+package com.ruoyi.satisfaction.service.strategy;
+
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.satisfaction.domain.ServiceSubtaskDetailRatioExport;
+import org.apache.commons.collections4.CollectionUtils;
+import org.springframework.stereotype.Component;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.util.List;
+
+/**
+ * 鐪佺珛鍚屽痉鍙e緞鐨勭患鍚堟弧鎰忓害寰楀垎绛栫暐锛堜簲妗e姞鏉冿紝鐧惧垎鍒讹級銆�
+ * <p>
+ * 绠楁硶浠庡師 {@code ServiceSubtaskDetailController#calcMydScore} 鍙婂叾 4 涓鏈夊姪鎵�
+ * <b>鍘熸牱鎼叆</b>锛堜竴琛屾湭鏀癸紝浠呮妸杩斿洖鍊肩敱 {@code String} 鏀逛负 {@link BigDecimal} 浠ヤ究缁熶竴绛栫暐绛惧悕锛�
+ * "鏃犳湁鏁堥」鏁�"杩欑被鎯呭喌鐢辫繑鍥� {@code null} 琛ㄨ揪锛岃皟鐢ㄦ柟浠嶅洖閫�涓� {@code "0.00"}锛屽澶栫粨鏋滀笉鍙橈級銆�
+ * <p>
+ * 鍙e緞锛�
+ * <pre>
+ *   缁煎悎婊℃剰搴� = (寰堟弧鎰忔暟脳1 + 婊℃剰鏁懊�0.8 + 涓�鑸暟脳0.6 + 涓嶆弧鎰忔暟脳0.4 + 寰堜笉婊℃剰鏁懊�0.2)
+ *              / (浜旀。椤规暟涔嬪拰) 脳 100%锛屼繚鐣� 2 浣�
+ * </pre>
+ * <b>涓轰粈涔堟寜閫夐」鏂囨鍒ゆ。銆佽�屼笉鏄敤鏄庣粏鐨� {@code score} 瀛楁</b>锛�
+ * 鍘嗗彶鏁版嵁閲� {@code score} 鏈� {@code 0}銆亄@code 0000000001} 杩欑被鑴忓�硷紝
+ * 鎸� {@code score} 绠椾細鎶婂緱鍒嗗浐瀹氭垚 0 鎴� 100銆�
+ * <p>
+ * 璺宠繃鐨勮锛氬閫夎锛坽@code valueType=2}锛夈�佷互鍙娿�屼笉閫傜敤 / 鏈瓟 / 鎷掔瓟銆嶏紙涓嶅湪浜旀。閲岋紝
+ * 鏃笉杩涘垎瀛愪篃涓嶈繘鍒嗘瘝锛夈��
+ */
+@Component
+public class SltdSatisfactionScoreStrategy implements SatisfactionScoreStrategy {
+
+    /**
+     * 澶氶�夐鐨勯棶棰樼被鍨�
+     */
+    private static final String VALUE_TYPE_MULTIPLE = "2";
+
+    /**
+     * 婊℃剰搴︿簲妗i�夐」锛堥『搴忎笌鏉冮噸椤哄簭涓�鑷达級
+     */
+    private static final String[] MYD_SCORE_OPTIONS = {"寰堟弧鎰�", "婊℃剰", "涓�鑸�", "涓嶆弧鎰�", "寰堜笉婊℃剰"};
+
+    /**
+     * 婊℃剰搴︿簲妗f潈閲嶏細寰堟弧鎰� 1銆佹弧鎰� 0.8銆佷竴鑸� 0.6銆佷笉婊℃剰 0.4銆佸緢涓嶆弧鎰� 0.2
+     */
+    private static final BigDecimal[] MYD_SCORE_WEIGHTS = {
+            new BigDecimal("1"), new BigDecimal("0.8"), new BigDecimal("0.6"), new BigDecimal("0.4"), new BigDecimal("0.2")};
+
+    @Override
+    public String site() {
+        return SatisfactionSites.SLTD;
+    }
+
+    @Override
+    public String siteName() {
+        return "鐪佺珛鍚屽痉鍙e緞";
+    }
+
+    /**
+     * 璁$畻涓�閬撻鐨勬弧鎰忓害缁煎悎寰楀垎锛堢櫨鍒嗗埗锛屼繚鐣欎袱浣嶅皬鏁帮級銆�
+     *
+     * @param exportList 鍚屼竴閬撻鐩殑閫夐」缁熻鏄庣粏
+     * @return 鐧惧垎鍒剁患鍚堝緱鍒嗭紱鏃犳湁鏁堥」鏁版椂杩斿洖 {@code null}锛堣皟鐢ㄦ柟鍛堢幇涓� "0.00"锛�
+     */
+    @Override
+    public BigDecimal comprehensiveScore(List<ServiceSubtaskDetailRatioExport> exportList) {
+        if (CollectionUtils.isEmpty(exportList)) return null;
+
+        BigDecimal scoreSum = BigDecimal.ZERO;   //鍒嗗瓙锛毼�(妗d綅鏉冮噸 脳 椤规暟)
+        BigDecimal countSum = BigDecimal.ZERO;   //鍒嗘瘝锛氫簲妗i」鏁颁箣鍜�
+
+        for (ServiceSubtaskDetailRatioExport detail : exportList) {
+            //澶氶�夐涓�琛屾槸澶氫釜閫夐」鐨勫垎鍊间箣鍜岋紝鍒嗘瘝鍙畻 1 椤癸紝浼氭妸寰楀垎绠楅珮锛屼笉璁″叆缁煎悎寰楀垎
+            if (VALUE_TYPE_MULTIPLE.equals(detail.getValueType())) continue;
+            //銆屼笉閫傜敤銆嶄笉鍦ㄥ叕寮忕殑浜旀。閲岋紝涓嶈鍏ョ患鍚堝緱鍒�
+            if (isNotApplicable(detail)) continue;
+
+            int optionIndex = mydScoreOptionIndex(detail.getOptionresult());
+            BigDecimal count = parseDecimal(detail.getCount());
+            if (optionIndex < 0 || count == null || count.signum() <= 0) continue;
+
+            scoreSum = scoreSum.add(MYD_SCORE_WEIGHTS[optionIndex].multiply(count));
+            countSum = countSum.add(count);
+        }
+
+        if (countSum.signum() <= 0) return null;
+
+        return scoreSum.divide(countSum, 6, RoundingMode.HALF_UP)
+                .multiply(BigDecimal.valueOf(100))
+                .setScale(2, RoundingMode.HALF_UP);
+    }
+
+    /**
+     * 閫夐」鏂囨 鈫� 婊℃剰搴︿簲妗d笅鏍囷紙寰堟弧鎰� 0銆佹弧鎰� 1銆佷竴鑸� 2銆佷笉婊℃剰 3銆佸緢涓嶆弧鎰� 4锛夛紝涓嶅湪浜旀。閲岃繑鍥� -1
+     */
+    private int mydScoreOptionIndex(String optionresult) {
+        if (StringUtils.isEmpty(optionresult)) return -1;
+        String text = optionresult.trim();
+        for (int i = 0; i < MYD_SCORE_OPTIONS.length; i++) {
+            if (MYD_SCORE_OPTIONS[i].equals(text)) return i;
+        }
+        //鍏煎銆孉 寰堟弧鎰忋�嶃�孊.婊℃剰銆嶈繖绫诲甫閫夐」搴忓彿鍓嶇紑鐨勫巻鍙叉暟鎹�
+        if (text.length() > 2 && (text.charAt(1) == ' ' || text.charAt(1) == '.' || text.charAt(1) == '銆�')) {
+            String stripped = text.substring(2).trim();
+            for (int i = 0; i < MYD_SCORE_OPTIONS.length; i++) {
+                if (MYD_SCORE_OPTIONS[i].equals(stripped)) return i;
+            }
+        }
+        return -1;
+    }
+
+    /**
+     * 鏄惁銆屼笉閫傜敤銆嶉�夐」
+     */
+    private boolean isNotApplicable(ServiceSubtaskDetailRatioExport detail) {
+        return containsNotApplicable(detail.getOptionresult()) || containsNotApplicable(detail.getTargetvalue());
+    }
+
+    private boolean containsNotApplicable(String optionText) {
+        if (StringUtils.isEmpty(optionText)) return false;
+        String text = optionText.trim();
+        return text.contains("涓嶉�傜敤") || text.contains("鏈瓟") || text.contains("鎷掔瓟");
+    }
+
+    /**
+     * 瀛楃涓茶浆 BigDecimal锛岀┖鍊�/闈炴硶鍊艰繑鍥� null
+     */
+    private BigDecimal parseDecimal(String value) {
+        if (StringUtils.isEmpty(value)) return null;
+        try {
+            return new BigDecimal(value.trim());
+        } catch (NumberFormatException e) {
+            return null;
+        }
+    }
+}
diff --git a/smartor/src/main/java/com/smartor/common/MydOptionRatioChartBuilder.java b/ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/support/MydOptionRatioChartBuilder.java
similarity index 89%
rename from smartor/src/main/java/com/smartor/common/MydOptionRatioChartBuilder.java
rename to ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/support/MydOptionRatioChartBuilder.java
index 3c0d163..0a6b850 100644
--- a/smartor/src/main/java/com/smartor/common/MydOptionRatioChartBuilder.java
+++ b/ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/support/MydOptionRatioChartBuilder.java
@@ -1,7 +1,7 @@
-package com.smartor.common;
+package com.ruoyi.satisfaction.support;
 
 import com.ruoyi.common.utils.poi.ExcelUtil;
-import com.smartor.domain.ServiceSubtaskDetailRatioExport;
+import com.ruoyi.satisfaction.domain.ServiceSubtaskDetailRatioExport;
 import org.apache.poi.ss.usermodel.Row;
 import org.apache.poi.ss.util.CellRangeAddress;
 import org.apache.poi.xddf.usermodel.chart.AxisCrosses;
@@ -30,9 +30,18 @@
 import java.util.Map;
 
 /**
- * 瀵煎嚭銆岄棶棰樿鎯呭崰姣斿浘琛ㄣ�峉heet 鐨勬瀯寤哄櫒
+ * 瀵煎嚭銆岄棶棰樿鎯呭崰姣斿浘琛ㄣ�峉heet 鐨勬瀯寤哄櫒銆�
  * <p>
  * 鎸変换鍔″悕绉板垎缁勶細姣忎釜浠诲姟鍐欎竴鍧楁暟鎹紙琛�=棰樼洰锛屽垪=閫夐」锛屽��=鏁伴噺锛夛紝骞跺湪鏁版嵁鍙充晶鐢讳竴寮犳煴鐘跺浘銆�
+ * <p>
+ * <b>杩佺Щ璇存槑</b>锛氬師绫讳负 {@code com.smartor.common.MydOptionRatioChartBuilder}銆�
+ * 瀹冨鐞嗙殑鏁版嵁鏄弧鎰忓害妯″潡鐨� {@link ServiceSubtaskDetailRatioExport}
+ * 锛堝嵆"闂璇︽儏鍗犳瘮"缁撴灉琛岋級锛屽睘浜庢弧鎰忓害瀵煎嚭閾捐矾鐨勪竴閮ㄥ垎锛�
+ * 鍥犳闅忔弧鎰忓害涓�璧锋惉鍏ユ湰妯″潡銆傛惉瀹跺悗<b>閫昏緫涓�琛屾湭鏀�</b>锛屽彧璋冩暣浜嗗寘鍚嶄笌 import銆�
+ * <p>
+ * 璋冪敤鏂癸紙闅忚瀵煎嚭鐨� {@code ServiceSubtaskController#patItemExport}锛夐�氳繃
+ * {@code SatisfactionFacade#getQuestionOptionRatio} 鎷垮埌鏁版嵁鍚庡啀浜ょ粰鏈被锛�
+ * 涓嶇洿鎺ユ帴瑙︽弧鎰忓害妯″潡鍐呴儴鐨� Service / Mapper銆�
  *
  * @author smartor
  */
diff --git a/smartor/src/main/resources/mapper/smartor/PatSatisfactionMapper.xml b/ruoyi-satisfaction/src/main/resources/mapper/satisfaction/PatSatisfactionMapper.xml
similarity index 95%
rename from smartor/src/main/resources/mapper/smartor/PatSatisfactionMapper.xml
rename to ruoyi-satisfaction/src/main/resources/mapper/satisfaction/PatSatisfactionMapper.xml
index 21125bd..f2c3cfb 100644
--- a/smartor/src/main/resources/mapper/smartor/PatSatisfactionMapper.xml
+++ b/ruoyi-satisfaction/src/main/resources/mapper/satisfaction/PatSatisfactionMapper.xml
@@ -2,9 +2,9 @@
 <!DOCTYPE mapper
         PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
-<mapper namespace="com.smartor.mapper.PatSatisfactionMapper">
+<mapper namespace="com.ruoyi.satisfaction.mapper.PatSatisfactionMapper">
 
-    <resultMap type="com.smartor.domain.PatSatisfaction" id="PatSatisfactionResult">
+    <resultMap type="com.ruoyi.satisfaction.domain.PatSatisfaction" id="PatSatisfactionResult">
         <result property="id" column="id"/>
         <result property="patName" column="pat_name"/>
         <result property="patCode" column="pat_code"/>
@@ -55,7 +55,7 @@
         from pat_satisfaction
     </sql>
 
-    <select id="selectPatSatisfactionList" parameterType="com.smartor.domain.PatSatisfaction"
+    <select id="selectPatSatisfactionList" parameterType="com.ruoyi.satisfaction.domain.PatSatisfaction"
             resultMap="PatSatisfactionResult">
         <include refid="selectPatSatisfactionVo"/>
         where 1=1
@@ -129,7 +129,7 @@
         where id = #{id}
     </select>
 
-    <insert id="insertPatSatisfaction" parameterType="com.smartor.domain.PatSatisfaction">
+    <insert id="insertPatSatisfaction" parameterType="com.ruoyi.satisfaction.domain.PatSatisfaction">
         insert into pat_satisfaction
         <trim prefix="(" suffix=")" suffixOverrides=",">
             <if test="id != null">id,
@@ -225,7 +225,7 @@
         </trim>
     </insert>
 
-    <update id="updatePatSatisfaction" parameterType="com.smartor.domain.PatSatisfaction">
+    <update id="updatePatSatisfaction" parameterType="com.ruoyi.satisfaction.domain.PatSatisfaction">
         update pat_satisfaction
         <trim prefix="SET" suffixOverrides=",">
             <if test="patName != null">pat_name =
diff --git a/ruoyi-satisfaction/src/main/resources/mapper/satisfaction/SatisfactionMapper.xml b/ruoyi-satisfaction/src/main/resources/mapper/satisfaction/SatisfactionMapper.xml
new file mode 100644
index 0000000..dcf5555
--- /dev/null
+++ b/ruoyi-satisfaction/src/main/resources/mapper/satisfaction/SatisfactionMapper.xml
@@ -0,0 +1,986 @@
+<?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.ruoyi.satisfaction.mapper.SatisfactionMapper">
+    <!--
+      婊℃剰搴︾粺璁℃煡璇紙鍗楀崕 + 鍚屽痉鍏辩敤鐨勬暟鎹闂眰锛夈��
+      鍘熸湰鏁h惤鍦� smartor 鐨� ServiceSubtaskMapper.xml 閲岋紝婊℃剰搴︽ā鍧楀寲鍚庨泦涓埌姝ゅ銆�
+
+      娉ㄦ剰锛�
+      1) 杩欎簺 SQL 鍙 service_subtask / service_subtask_detail / service_task锛屼笉鍋氫换浣曞啓鎿嶄綔锛�
+      2) 涓�寰嬩娇鐢ㄥ叏闄愬畾绫诲悕锛屼笉渚濊禆 typeAliasesPackage 鍒悕锛�
+      3) 鍘熸湰鐢ㄤ簬鎵挎帴 ServiceSubtask 瀹炰綋鐨� selectSatisfactionSubtaskList 涓� querySendCount 宸茬‘璁�
+         涓烘浠g爜锛堥浂璋冪敤鐐癸級骞跺垹闄わ紝鍥犳鏈枃浠朵笉渚濊禆浠讳綍鍏变韩 <sql> 鐗囨銆�
+    -->
+
+    <select id="selectSatisfactionSendCount" parameterType="com.ruoyi.satisfaction.domain.entity.SatisfactionStatQuery"
+            resultType="java.util.Map">
+        select libtemplateid as libtemplateid, count(distinct id) as cnt
+        from service_subtask
+        where del_flag = 0
+        <if test="orgid != null and orgid != ''">
+            and orgid = #{orgid}
+        </if>
+        <if test="campusid != null and campusid != ''">
+            and campusid = #{campusid}
+        </if>
+        <if test="type != null and type != ''">and type = #{type}</if>
+        <if test="startTime != null">
+            and visit_time &gt;= #{startTime}
+        </if>
+        <if test="endTime != null">
+            and visit_time &lt; date_add(#{endTime}, interval 1 day)
+        </if>
+        <if test="libTemplateIds != null and libTemplateIds.size() > 0">
+            and libtemplateid IN
+            <foreach collection="libTemplateIds" item="libTemplateId" open="(" separator="," close=")">
+                #{libTemplateId}
+            </foreach>
+        </if>
+        <if test="(hospitaldistrictcodes != null and hospitaldistrictcodes.size() > 0) or (deptcodes != null and deptcodes.size() > 0)">
+            AND (
+            <if test="hospitaldistrictcodes != null and hospitaldistrictcodes.size() > 0">
+                leavehospitaldistrictcode IN
+                <foreach collection="hospitaldistrictcodes" item="hospitaldistrictcode" open="("
+                         separator=","
+                         close=")">
+                    #{hospitaldistrictcode}
+                </foreach>
+            </if>
+            <if test="hospitaldistrictcodes != null and hospitaldistrictcodes.size() > 0 and deptcodes != null and deptcodes.size() > 0">
+                OR
+            </if>
+            <if test="deptcodes != null and deptcodes.size() > 0">
+                deptcode IN
+                <foreach collection="deptcodes" item="deptcode" open="(" separator="," close=")">
+                    #{deptcode}
+                </foreach>
+            </if>
+            )
+        </if>
+        group by libtemplateid
+    </select>
+
+    <select id="selectSatisfactionSubtaskTypes" parameterType="com.ruoyi.satisfaction.domain.vo.PatSatisfactionReqVO"
+            resultType="java.lang.String">
+        select distinct type
+        from service_subtask
+        where del_flag = 0
+        <if test="questionType != null">and type = #{questionType}</if>
+        <if test="hospitaldistrictcodes != null and hospitaldistrictcodes.size() > 0">
+            AND leavehospitaldistrictcode IN
+            <foreach collection="hospitaldistrictcodes" item="hospitaldistrictcode" open="(" separator=","
+                     close=")">
+                #{hospitaldistrictcode}
+            </foreach>
+        </if>
+        <if test="deptcodes != null and deptcodes.size() > 0">
+            AND deptcode IN
+            <foreach collection="deptcodes" item="deptcode" open="(" separator="," close=")">
+                #{deptcode}
+            </foreach>
+        </if>
+        <if test="serviceTypes != null and serviceTypes.size() > 0">
+            AND service_type IN
+            <foreach collection="serviceTypes" item="serviceType" open="(" separator="," close=")">
+                #{serviceType}
+            </foreach>
+        </if>
+        <if test="startTime != null and endTime != null">
+            AND visit_time &gt;= #{startTime}
+            AND visit_time &lt; date_add(#{endTime}, interval 1 day)
+        </if>
+    </select>
+
+    <select id="mydTrend" resultType="com.ruoyi.satisfaction.domain.dto.MydTrendReqDTO">
+        SELECT CASE #{type}
+                   WHEN 'year' THEN DATE_FORMAT(d.create_time, '%Y')
+                   WHEN 'quarter' THEN CONCAT(YEAR(d.create_time), '-Q', QUARTER(d.create_time))
+                   WHEN 'month' THEN DATE_FORMAT(d.create_time, '%Y-%m')
+                   WHEN 'week' THEN FLOOR(DATEDIFF(d.create_time, #{startFinishTime}) / 7) + 1
+                   ELSE DATE_FORMAT(d.create_time, '%Y-%m-%d')
+                   END                AS period,
+               ROUND(avg(d.score), 2) AS totalScore
+        FROM service_subtask_detail d
+                 JOIN service_task t ON d.taskid = t.taskid
+        WHERE t.service_type IN (14)
+          AND t.del_flag = 0
+          AND d.del_flag = 0
+          AND d.create_time >= #{startFinishTime}
+          AND d.create_time &lt; DATE_ADD(#{endFinishTime}, INTERVAL 1 DAY)
+        GROUP BY period
+        ORDER BY period;
+    </select>
+
+    <select id="sltdMydTotalByDimension" resultType="com.ruoyi.satisfaction.domain.dto.MydTrendReqDTO">
+        SELECT
+        p.period,
+        dim.dimension,
+        IFNULL(ROUND(SUM(d.score), 2), 0) AS totalScore
+        FROM (
+        <!-- 鐢熸垚鏌ヨ鑼冨洿鍐呭疄闄呭嚭鐜拌繃鐨勬椂闂存 -->
+        SELECT DISTINCT
+        CASE #{type}
+        WHEN 'year' THEN DATE_FORMAT(d.create_time, '%Y')
+        WHEN 'quarter' THEN CONCAT(YEAR(d.create_time), '-Q', QUARTER(d.create_time))
+        WHEN 'month' THEN DATE_FORMAT(d.create_time, '%Y-%m')
+        WHEN 'week' THEN FLOOR(DATEDIFF(d.create_time, #{startFinishTime}) / 7) + 1
+        ELSE DATE_FORMAT(d.create_time, '%Y-%m-%d')
+        END AS period
+        FROM service_subtask_detail d
+        WHERE d.del_flag = 0
+        AND d.create_time >= #{startFinishTime}
+        AND d.create_time &lt; #{endFinishTime}
+        ) p
+        CROSS JOIN (
+        <!-- 鎵�鏈夐渶瑕佸睍绀虹殑 dimension -->
+        SELECT DISTINCT dimension
+        FROM service_subtask_detail
+        WHERE del_flag = 0
+        AND dimension IS NOT NULL
+        ) dim
+        LEFT JOIN service_task t
+        ON t.service_type IN (6, 14)
+        AND t.del_flag = 0
+        LEFT JOIN service_subtask_detail d
+        ON d.taskid = t.taskid
+        AND d.del_flag = 0
+        AND d.dimension = dim.dimension
+        AND CASE #{type}
+        WHEN 'year' THEN DATE_FORMAT(d.create_time, '%Y')
+        WHEN 'quarter' THEN CONCAT(YEAR(d.create_time), '-Q', QUARTER(d.create_time))
+        WHEN 'month' THEN DATE_FORMAT(d.create_time, '%Y-%m')
+        WHEN 'week' THEN FLOOR(DATEDIFF(d.create_time, #{startFinishTime}) / 7) + 1
+        ELSE DATE_FORMAT(d.create_time, '%Y-%m-%d')
+        END = p.period
+        AND d.create_time >= #{startFinishTime}
+        AND d.create_time &lt; #{endFinishTime}
+        GROUP BY p.period, dim.dimension
+        ORDER BY p.period, dim.dimension
+    </select>
+
+    <select id="sltdMydTotalByYQ" resultType="com.ruoyi.satisfaction.domain.dto.MydTrendReqDTO">
+        SELECT p.period,
+               c.campusid,
+               IFNULL(ROUND(AVG(d.score), 2), 0) AS totalScore
+        FROM (
+                 -- 鐢熸垚鏌ヨ鑼冨洿鍐呭疄闄呭嚭鐜拌繃鐨勬椂闂存
+                 SELECT DISTINCT CASE #{type}
+                                     WHEN 'year' THEN DATE_FORMAT(d.create_time, '%Y')
+                                     WHEN 'quarter' THEN CONCAT(YEAR(d.create_time), '-Q', QUARTER(d.create_time))
+                                     WHEN 'month' THEN DATE_FORMAT(d.create_time, '%Y-%m')
+                                     WHEN 'week' THEN FLOOR(DATEDIFF(d.create_time, #{startFinishTime}) / 7) + 1
+                                     ELSE DATE_FORMAT(d.create_time, '%Y-%m-%d')
+                                     END AS period
+                 FROM service_subtask_detail d
+                 WHERE d.del_flag = 0
+                   AND d.create_time >= #{startFinishTime}
+                   AND d.create_time &lt; #{endFinishTime}) p
+                 CROSS JOIN (
+            -- 鎵�鏈夐渶瑕佸睍绀虹殑闄㈠尯
+            SELECT DISTINCT campusid
+            FROM service_task
+            WHERE service_type IN (6, 14)
+              AND del_flag = 0
+              AND campusid IS NOT NULL) c
+                 LEFT JOIN service_task t
+                           ON t.campusid = c.campusid
+                               AND t.service_type IN (6, 14)
+                               AND t.del_flag = 0
+                 LEFT JOIN service_subtask_detail d
+                           ON d.taskid = t.taskid
+                               AND d.del_flag = 0
+                               AND CASE #{type}
+                                       WHEN 'year' THEN DATE_FORMAT(d.create_time, '%Y')
+                                       WHEN 'quarter' THEN CONCAT(YEAR(d.create_time), '-Q', QUARTER(d.create_time))
+                                       WHEN 'month' THEN DATE_FORMAT(d.create_time, '%Y-%m')
+                                       WHEN 'week' THEN FLOOR(DATEDIFF(d.create_time, #{startFinishTime}) / 7) + 1
+                                       ELSE DATE_FORMAT(d.create_time, '%Y-%m-%d')
+                                       END = p.period
+                               AND d.create_time >= #{startFinishTime}
+                               AND d.create_time &lt; #{endFinishTime}
+        GROUP BY p.period, c.campusid
+        ORDER BY p.period, c.campusid;
+    </select>
+
+    <select id="statMydQuestionOptionForExport" resultType="com.ruoyi.satisfaction.domain.dto.ServiceSubtaskOptionStatDTO">
+        SELECT
+        t.taskid AS taskid,
+        t.task_name AS taskName,
+        d.questiontext AS questiontext,
+        d.asrtext AS optionresult,
+        d.targetvalue AS targetvalue,
+        d.value_type AS valueType,
+        COUNT(DISTINCT s.id) AS patientCount,
+        SUM(COALESCE(CAST(d.score AS DECIMAL(18,2)), 0)) AS scoreSum
+        FROM
+        service_task t
+        INNER JOIN service_subtask s ON s.taskid = t.taskid
+        INNER JOIN service_subtask_detail d ON d.sub_id = s.id
+        WHERE 1=1
+        AND s.del_flag = '0'
+        AND d.del_flag = 0
+        <if test="subtaskIds != null and subtaskIds.size() > 0">
+            AND s.id IN
+            <foreach collection="subtaskIds" item="subtaskId" open="(" separator="," close=")">
+                #{subtaskId}
+            </foreach>
+        </if>
+        <!-- 涓嶄紶瀛愪换鍔℃椂鐩存帴鏌ヤ笉鍒版暟鎹紝閬垮厤璇粺璁″叏閲� -->
+        <if test="subtaskIds == null or subtaskIds.size() == 0">
+            AND 1 = 0
+        </if>
+        <if test="questiontext != null and questiontext != ''">
+            AND d.questiontext LIKE CONCAT('%', #{questiontext}, '%')
+        </if>
+        <!-- 鍚屼竴鎮h�呭悓涓�棰樼洰鍙繚鐣欐渶鏂颁竴鏉★紝閲嶇瓟涓嶉噸澶嶈鍏ユ暟閲� -->
+        AND d.id = (
+        SELECT MAX(d2.id)
+        FROM service_subtask_detail d2
+        WHERE d2.sub_id = d.sub_id
+        AND d2.questiontext &lt;=&gt; d.questiontext
+        AND d2.del_flag = '0'
+        )
+        GROUP BY
+        t.taskid,
+        t.task_name,
+        d.questiontext,
+        d.asrtext,
+        d.targetvalue,
+        d.value_type
+        <!-- 鎸変换鍔″悕绉板垎缁勫睍绀猴細鍏堜换鍔″悕绉般�佸啀浠诲姟缂栧彿锛屽悓涓�浠诲姟鐨勯鐩�/閫夐」杩炵画鎺掑垪 -->
+        ORDER BY
+        t.task_name,
+        t.taskid,
+        d.questiontext
+    </select>
+
+    <select id="statQuestionOption" resultType="com.ruoyi.satisfaction.domain.ServiceSubtaskDetailRatioExport">
+        SELECT
+        d.questiontext AS questiontext,
+        t.taskid AS taskid,
+        t.task_name AS taskName,
+        d.asrtext AS optionresult,
+        d.score AS score,
+        d.targetvalue AS targetvalue,
+        d.value_type AS valueType,
+        COUNT(*) AS count,
+        CONCAT(ROUND(COUNT(*) * 100.0 / t2.total_count, 2), '%') AS ratio
+        FROM
+        service_task t
+        INNER JOIN service_subtask s ON s.taskid = t.taskid
+        INNER JOIN service_subtask_detail d ON d.sub_id = s.id
+        INNER JOIN (
+        SELECT
+        t.taskid,
+        d.questiontext,
+        COUNT(*) AS total_count
+        FROM
+        service_task t
+        INNER JOIN service_subtask s ON s.taskid = t.taskid
+        INNER JOIN service_subtask_detail d ON d.sub_id = s.id
+        WHERE 1=1
+        <if test="taskIds != null and taskIds.size() > 0">
+            AND t.taskid IN
+            <foreach collection="taskIds" item="taskId" open="(" separator="," close=")">
+                #{taskId}
+            </foreach>
+        </if>
+        <!-- 浼犱簡 subtaskIds 鍙粺璁¤繖鎵瑰瓙浠诲姟锛堟湰娆″鍑虹殑鎮h�咃級 -->
+        <if test="subtaskIds != null and subtaskIds.size() > 0">
+            AND s.id IN
+            <foreach collection="subtaskIds" item="subtaskId" open="(" separator="," close=")">
+                #{subtaskId}
+            </foreach>
+        </if>
+        <if test="valueType != null">AND d.value_type = #{valueType}</if>
+        <if test="scoreStart != null">AND d.score &gt;= #{scoreStart}</if>
+        <if test="scoreEnd != null">AND d.score &lt;= #{scoreEnd}</if>
+        <if test="questiontext != null  and questiontext != ''">and d.questiontext like concat('%', #{questiontext},
+            '%')
+        </if>
+        and s.sendstate=6
+        and d.del_flag=0
+        <!-- 鍒嗘瘝蹇呴』鍜屾槑缁嗙敤鍚屼竴濂楁椂闂存潯浠讹紝鍚﹀垯閫変簡鏃堕棿鑼冨洿鍚庢瘮渚嬩細鎸夊叏閲忓巻鍙茶绋�閲� -->
+        <if test="startOutHospTime != null">
+            AND date_format(s.endtime,'%y%m%d') &gt;= date_format(#{startOutHospTime},'%y%m%d')
+        </if>
+        <if test="endOutHospTime != null">
+            AND date_format(s.endtime,'%y%m%d') &lt;= date_format(#{endOutHospTime},'%y%m%d')
+        </if>
+        <if test="startFinishTime != null">
+            AND date_format(s.finishtime,'%y%m%d') &gt;= date_format(#{startFinishTime},'%y%m%d')
+        </if>
+        <if test="endFinishTime != null">
+            AND date_format(s.finishtime,'%y%m%d') &lt;= date_format(#{endFinishTime},'%y%m%d')
+        </if>
+        GROUP BY
+        t.taskid,
+        d.questiontext
+        ) t2 ON t2.taskid = t.taskid AND t2.questiontext = d.questiontext
+        WHERE 1=1
+        <if test="taskIds != null and taskIds.size() > 0">
+            AND t.taskid IN
+            <foreach collection="taskIds" item="taskId" open="(" separator="," close=")">
+                #{taskId}
+            </foreach>
+        </if>
+        <if test="subtaskIds != null and subtaskIds.size() > 0">
+            AND s.id IN
+            <foreach collection="subtaskIds" item="subtaskId" open="(" separator="," close=")">
+                #{subtaskId}
+            </foreach>
+        </if>
+        <if test="valueType != null">AND d.value_type = #{valueType}</if>
+        <if test="scoreStart != null">AND d.score &gt;= #{scoreStart}</if>
+        <if test="scoreEnd != null">AND d.score &lt;= #{scoreEnd}</if>
+        <if test="questiontext != null  and questiontext != ''">and d.questiontext like concat('%', #{questiontext},
+            '%')
+        </if>
+        AND s.sendstate = 6
+        and d.del_flag=0
+        <if test="startOutHospTime != null">
+            AND date_format(s.endtime,'%y%m%d') &gt;= date_format(#{startOutHospTime},'%y%m%d')
+        </if>
+        <if test="endOutHospTime != null">
+            AND date_format(s.endtime,'%y%m%d') &lt;= date_format(#{endOutHospTime},'%y%m%d')
+        </if>
+        <if test="startFinishTime != null">
+            AND date_format(s.finishtime,'%y%m%d') &gt;= date_format(#{startFinishTime},'%y%m%d')
+        </if>
+        <if test="endFinishTime != null">
+            AND date_format(s.finishtime,'%y%m%d') &lt;= date_format(#{endFinishTime},'%y%m%d')
+        </if>
+        GROUP BY
+        t.taskid,
+        d.questiontext,
+        d.score,
+        d.targetvalue,
+        d.asrtext,
+        d.value_type
+        ORDER BY
+        d.questiontext,
+        t.taskid
+    </select>
+
+    <select id="statQuestionOptionBySubtaskIds" resultType="com.ruoyi.satisfaction.domain.ServiceSubtaskDetailRatioExport">
+        SELECT
+        d.questiontext AS questiontext,
+        <!-- 鏄庣粏宸叉寜銆岄鐩� + 閫夐」銆嶆眹鎬伙紝浠诲姟/绉戝/鐥呭尯鍙槸闄勫甫淇℃伅锛屽繀椤荤敤鑱氬悎鍑芥暟鍙栧�硷紙MySQL ONLY_FULL_GROUP_BY锛� -->
+        MAX(t.taskid) AS taskid,
+        MAX(t.task_name) AS taskName,
+        d.asrtext AS optionresult,
+        d.score AS score,
+        MAX(s.deptcode) AS deptcode,
+        MAX(s.deptname) AS deptname,
+        MAX(s.leavehospitaldistrictcode) AS leavehospitaldistrictcode,
+        MAX(s.leavehospitaldistrictname) AS leavehospitaldistrictname,
+        d.targetvalue AS targetvalue,
+        d.value_type AS valueType,
+        COUNT(*) AS count,
+        CONCAT(ROUND(COUNT(*) * 100.0 / t2.total_count, 2), '%') AS ratio
+        FROM
+        service_task t
+        INNER JOIN service_subtask s ON s.taskid = t.taskid
+        INNER JOIN service_subtask_detail d ON d.sub_id = s.id
+        <!-- 鍘婚噸锛氬悓涓�瀛愪换鍔″悓涓�棰樼洰鍙繚鐣欐渶鏂颁竴鏉★紝閬垮厤閲嶇瓟/閲嶅鍥炶皟鎶婂悓涓�閬撻閲嶅璁″叆椤规暟锛堜笌 mzMydScoreRank 鍙e緞涓�鑷达級 -->
+        AND d.id = (
+        SELECT MAX(d2.id)
+        FROM service_subtask_detail d2
+        WHERE d2.sub_id = d.sub_id
+        AND d2.questiontext &lt;=&gt; d.questiontext
+        AND d2.del_flag = '0'
+        )
+        INNER JOIN (
+        <!-- 鍒嗘瘝 = 璇ラ鍚勯�夐」椤规暟涔嬪拰锛堟湭浣滅瓟鐨勭┖ asrtext 涓嶈繘鍒嗘瘝锛夛紝杩欐牱涓�閬撻鍚勯�夐」姣斾緥鍚堣鎵嶆槸 100% -->
+        SELECT
+        x.questiontext AS questiontext,
+        SUM(x.option_count) AS total_count
+        FROM (
+        SELECT
+        d.questiontext,
+        d.asrtext,
+        COUNT(*) AS option_count
+        FROM
+        service_task t
+        INNER JOIN service_subtask s ON s.taskid = t.taskid
+        INNER JOIN service_subtask_detail d ON d.sub_id = s.id
+        <!-- 鍘婚噸锛氬垎姣嶏紙璇ラ鏈夋晥绛斿嵎鎬绘暟锛変笌鏄庣粏鍙e緞淇濇寔涓�鑷达紝淇濊瘉鍚勯�夐」姣斾緥鍚堣 100% -->
+        AND d.id = (
+        SELECT MAX(d4.id)
+        FROM service_subtask_detail d4
+        WHERE d4.sub_id = d.sub_id
+        AND d4.questiontext &lt;=&gt; d.questiontext
+        AND d4.del_flag = '0'
+        )
+        WHERE 1=1
+        <if test="subtaskIds != null and subtaskIds.size() > 0">
+            AND s.id IN
+            <foreach collection="subtaskIds" item="subtaskId" open="(" separator="," close=")">
+                #{subtaskId}
+            </foreach>
+        </if>
+        <if test="valueType != null">AND d.value_type &gt;= #{valueType}</if>
+        <if test="scoreStart != null">AND d.score &gt;= #{scoreStart}</if>
+        <if test="scoreEnd != null">AND d.score &lt;= #{scoreEnd}</if>
+        <if test="questiontext != null  and questiontext != ''">and d.questiontext like concat('%', #{questiontext},
+            '%')
+        </if>
+        AND s.sendstate = 6
+        AND d.del_flag=0
+        <!-- 鍒嗘瘝涓庢槑缁嗙敤鍚屼竴濂楁椂闂存潯浠讹紝鍚﹀垯閫変簡鏃ユ湡鍖洪棿鍚庡悇閫夐」姣斾緥鍚堣涓嶅埌 100% -->
+        <if test="startOutHospTime != null">
+            AND date_format(s.endtime,'%y%m%d') &gt;= date_format(#{startOutHospTime},'%y%m%d')
+        </if>
+        <if test="endOutHospTime != null">
+            AND date_format(s.endtime,'%y%m%d') &lt;= date_format(#{endOutHospTime},'%y%m%d')
+        </if>
+        <if test="startFinishTime != null">
+            AND date_format(s.finishtime,'%y%m%d') &gt;= date_format(#{startFinishTime},'%y%m%d')
+        </if>
+        <if test="endFinishTime != null">
+            AND date_format(s.finishtime,'%y%m%d') &lt;= date_format(#{endFinishTime},'%y%m%d')
+        </if>
+        <!-- 鏈綔绛旓紙asrtext 涓虹┖锛夌殑鏄庣粏涓嶈鍏ュ垎姣� -->
+        AND d.asrtext IS NOT NULL AND TRIM(d.asrtext) &lt;&gt; ''
+        GROUP BY d.questiontext, d.asrtext
+        ) x
+        GROUP BY x.questiontext
+        ) t2 ON t2.questiontext = d.questiontext
+        WHERE 1=1
+        <!-- 鍘婚噸锛氭槑缁嗚鍚屼竴瀛愪换鍔″悓涓�棰樼洰鍙繚鐣欐渶鏂颁竴鏉★紝閲嶇瓟涓嶉噸澶嶈鍏ラ」鏁� -->
+        AND d.id = (
+        SELECT MAX(d3.id)
+        FROM service_subtask_detail d3
+        WHERE d3.sub_id = d.sub_id
+        AND d3.questiontext &lt;=&gt; d.questiontext
+        AND d3.del_flag = '0'
+        )
+        <if test="subtaskIds != null and subtaskIds.size() > 0">
+            AND s.id IN
+            <foreach collection="subtaskIds" item="subtaskId" open="(" separator="," close=")">
+                #{subtaskId}
+            </foreach>
+        </if>
+        AND s.sendstate = 6
+        AND d.del_flag=0
+        <!-- 鏈綔绛旓紙asrtext 涓虹┖锛変笉绠椾换浣曢�夐」锛屾槑缁嗛噷鐩存帴鎺掗櫎锛岄伩鍏嶅嚭鐜扮┖閫夐」琛� -->
+        AND d.asrtext IS NOT NULL AND TRIM(d.asrtext) &lt;&gt; ''
+        <if test="valueType != null">AND d.value_type &gt;= #{valueType}</if>
+        <if test="scoreStart != null">AND d.score &gt;= #{scoreStart}</if>
+        <if test="scoreEnd != null">AND d.score &lt;= #{scoreEnd}</if>
+        <if test="questiontext != null  and questiontext != ''">and d.questiontext like concat('%', #{questiontext},
+            '%')
+        </if>
+        <if test="startOutHospTime != null">
+            AND date_format(s.endtime,'%y%m%d') &gt;= date_format(#{startOutHospTime},'%y%m%d')
+        </if>
+        <if test="endOutHospTime != null">
+            AND date_format(s.endtime,'%y%m%d') &lt;= date_format(#{endOutHospTime},'%y%m%d')
+        </if>
+        <if test="startFinishTime != null">
+            AND date_format(s.finishtime,'%y%m%d') &gt;= date_format(#{startFinishTime},'%y%m%d')
+        </if>
+        <if test="endFinishTime != null">
+            AND date_format(s.finishtime,'%y%m%d') &lt;= date_format(#{endFinishTime},'%y%m%d')
+        </if>
+        <!-- 鍙寜銆岄鐩� + 閫夐」銆嶆眹鎬伙細鍘熸潵 GROUP BY 甯︿笂 taskid/绉戝/鐥呭尯锛屽悓涓�涓�夐」琚媶鎴愬琛屻�佹瘡琛屾暟閲忛兘鏄� 1锛�
+             鍓嶇/瀵煎嚭鎷垮埌鐨勩�屾暟閲忋�嶅氨姘歌繙鍋氫笉浜嗙疮鍔� -->
+        GROUP BY
+        d.questiontext,
+        d.asrtext,
+        d.score,
+        d.targetvalue,
+        d.value_type
+        ORDER BY
+        d.questiontext,
+        MIN(t.taskid)
+    </select>
+
+    <select id="statQuestionOptionByKsOrBq" resultType="com.ruoyi.satisfaction.domain.dto.QuestionCountKsOrBqDTO"
+            parameterType="com.ruoyi.satisfaction.domain.vo.QuestionCountKsOrBqVO">
+        SELECT
+        s.deptname AS deptname,
+        s.leavehospitaldistrictname AS leavehospitaldistrictname,
+        d.questiontext AS questionText,
+        d.targetvalue AS targetvalue, -- 鎵�鏈夐�夐」鎷兼帴
+        d.asrtext AS asrtext, -- 鐢ㄦ埛瀹為檯閫変腑鐨勯�夐」
+        d.score AS singleScore -- 鍗曟潯璁板綍鐨勫垎鏁�
+        FROM service_subtask_detail d
+        JOIN service_subtask s ON d.sub_id = s.id
+        WHERE s.del_flag = 0
+        AND d.del_flag = 0
+        AND s.service_type IN ('6', '14')
+        <if test="taskTemplateid != null">
+            AND s.templateid = #{taskTemplateid}
+        </if>
+        <if test="endFinishTime != null">
+            AND d.create_time &lt; #{endFinishTime}
+        </if>
+        <if test="startFinishTime != null">
+            AND d.create_time &gt;= #{startFinishTime}
+        </if>
+        <if test="deptOrDistrict ==1">
+            <if test="leavehospitaldistrictcodes != null and leavehospitaldistrictcodes.size() > 0">
+                AND leavehospitaldistrictcode IN
+                <foreach collection="leavehospitaldistrictcodes" item="leavehospitaldistrictcode" open="("
+                         separator=","
+                         close=")">
+                    #{leavehospitaldistrictcode}
+                </foreach>
+            </if>
+            <if test=" leaveldeptcodes != null and leaveldeptcodes.size()>0">
+                AND deptcode IN
+                <foreach collection="leaveldeptcodes" item="leaveldeptcode" open="(" separator=","
+                         close=")">
+                    #{leaveldeptcode}
+                </foreach>
+            </if>
+        </if>
+        <if test="deptOrDistrict ==2">
+            <if test="leavehospitaldistrictcodes != null and leavehospitaldistrictcodes.size() > 0">
+                AND leavehospitaldistrictcode IN
+                <foreach collection="leavehospitaldistrictcodes" item="leavehospitaldistrictcode" open="("
+                         separator=","
+                         close=")">
+                    #{leavehospitaldistrictcode}
+                </foreach>
+            </if>
+            <if test=" leaveldeptcodes != null and leaveldeptcodes.size()>0">
+                OR deptcode IN
+                <foreach collection="leaveldeptcodes" item="leaveldeptcode" open="(" separator=","
+                         close=")">
+                    #{leaveldeptcode}
+                </foreach>
+            </if>
+        </if>
+    </select>
+
+    <select id="selectMzMydScoreRank" parameterType="com.ruoyi.satisfaction.domain.vo.MzMydScoreRankVO"
+            resultType="com.ruoyi.satisfaction.domain.dto.MzMydScoreRankDTO">
+        SELECT
+        t.code                   AS code,
+        t.name                   AS name,
+        ROUND(AVG(t.w) * 100, 2) AS score,
+        COUNT(*)                 AS itemCount
+        FROM (
+        SELECT
+        <choose>
+            <when test='dimensionType == "2"'>
+                s.leavehospitaldistrictcode AS code,
+                s.leavehospitaldistrictname AS name,
+            </when>
+            <otherwise>
+                s.deptcode AS code,
+                s.deptname AS name,
+            </otherwise>
+        </choose>
+        CAST(d.score AS DECIMAL(10,4)) AS w
+        FROM service_subtask s
+        INNER JOIN service_subtask_detail d ON d.sub_id = s.id
+        WHERE s.del_flag = '0'
+        AND d.del_flag = '0'
+        AND s.sendstate = 6
+        <if test="serviceTypes != null and serviceTypes.size() > 0">
+            AND service_type IN
+            <foreach collection="serviceTypes" item="serviceType" open="(" separator=","
+                     close=")">
+                #{serviceType}
+            </foreach>
+        </if>
+<!--        &lt;!&ndash; 鍙彇鍏紡閲岀殑浜旀。鏉冮噸锛岀┖鍊�/0/鏈瓟绛夋棦涓嶈繘鍒嗗瓙涔熶笉杩涘垎姣� &ndash;&gt;-->
+<!--        AND CAST(d.score AS DECIMAL(10,4)) IN (1, 0.8, 0.6, 0.4, 0.2)-->
+        <!-- 鍚屼竴瀛愪换鍔″悓涓�棰樼洰鍙繚鐣欐渶鏂颁竴鏉★紝閬垮厤閲嶇瓟琚噸澶嶈鍏ラ」鏁� -->
+        AND d.id = (
+        SELECT MAX(d2.id)
+        FROM service_subtask_detail d2
+        WHERE d2.sub_id = d.sub_id
+        AND d2.questiontext &lt;=&gt; d.questiontext
+        AND d2.del_flag = '0'
+        )
+        <if test="orgid != null and orgid != ''">
+            AND s.orgid = #{orgid}
+        </if>
+        <if test="campusid != null and campusid != ''">
+            AND s.campusid = #{campusid}
+        </if>
+        <if test="startFinishTime != null">
+            AND s.finishtime &gt;= #{startFinishTime}
+        </if>
+        <if test="endFinishTime != null">
+            AND s.finishtime &lt; DATE_ADD(#{endFinishTime}, INTERVAL 1 DAY)
+        </if>
+        <if test="questionText != null  and questionText != ''">and d.questiontext like concat('%', #{questionText},
+            '%')
+        </if>
+        <choose>
+            <when test='dimensionType == "2"'>
+                AND s.leavehospitaldistrictcode IS NOT NULL AND s.leavehospitaldistrictcode &lt;&gt; ''
+                AND s.leavehospitaldistrictname IS NOT NULL AND s.leavehospitaldistrictname &lt;&gt; ''
+                <if test="districtCodes != null and districtCodes.size() > 0">
+                    AND s.leavehospitaldistrictcode IN
+                    <foreach collection="districtCodes" item="districtCode" open="(" separator="," close=")">
+                        #{districtCode}
+                    </foreach>
+                </if>
+            </when>
+            <otherwise>
+                AND s.deptcode IS NOT NULL AND s.deptcode &lt;&gt; ''
+                AND s.deptname IS NOT NULL AND s.deptname &lt;&gt; ''
+                <if test="deptCodes != null and deptCodes.size() > 0">
+                    AND s.deptcode IN
+                    <foreach collection="deptCodes" item="deptCode" open="(" separator="," close=")">
+                        #{deptCode}
+                    </foreach>
+                </if>
+            </otherwise>
+        </choose>
+        ) t
+        GROUP BY t.code, t.name
+        ORDER BY AVG(t.w) DESC, COUNT(*) DESC
+        <if test="limit != null and limit > 0">
+            LIMIT #{limit}
+        </if>
+    </select>
+
+    <select id="selectSatisfactionFillStat" parameterType="com.ruoyi.satisfaction.domain.entity.SatisfactionStatQuery"
+            resultType="java.util.Map">
+        select ss.libtemplateid as libtemplateid,
+               ssd.questiontext as questiontext,
+               ssd.score as score,
+               count(*) as cnt
+        from service_subtask_detail ssd
+        join service_subtask ss on ss.id = ssd.sub_id
+        where ssd.del_flag = 0
+        and ss.del_flag = 0
+        <if test="orgid != null and orgid != ''">
+            and ss.orgid = #{orgid}
+        </if>
+        <if test="campusid != null and campusid != ''">
+            and ss.campusid = #{campusid}
+        </if>
+        <if test="type != null and type != ''">and ss.type = #{type}</if>
+        <if test="startTime != null">
+            and ss.visit_time &gt;= #{startTime}
+        </if>
+        <if test="endTime != null">
+            and ss.visit_time &lt; date_add(#{endTime}, interval 1 day)
+        </if>
+        <if test="libTemplateIds != null and libTemplateIds.size() > 0">
+            and ss.libtemplateid IN
+            <foreach collection="libTemplateIds" item="libTemplateId" open="(" separator="," close=")">
+                #{libTemplateId}
+            </foreach>
+        </if>
+        <if test="(hospitaldistrictcodes != null and hospitaldistrictcodes.size() > 0) or (deptcodes != null and deptcodes.size() > 0)">
+            AND (
+            <if test="hospitaldistrictcodes != null and hospitaldistrictcodes.size() > 0">
+                ss.leavehospitaldistrictcode IN
+                <foreach collection="hospitaldistrictcodes" item="hospitaldistrictcode" open="("
+                         separator=","
+                         close=")">
+                    #{hospitaldistrictcode}
+                </foreach>
+            </if>
+            <if test="hospitaldistrictcodes != null and hospitaldistrictcodes.size() > 0 and deptcodes != null and deptcodes.size() > 0">
+                OR
+            </if>
+            <if test="deptcodes != null and deptcodes.size() > 0">
+                ss.deptcode IN
+                <foreach collection="deptcodes" item="deptcode" open="(" separator="," close=")">
+                    #{deptcode}
+                </foreach>
+            </if>
+            )
+        </if>
+        group by ss.libtemplateid, ssd.questiontext, ssd.score
+    </select>
+
+    <select id="selectSatisfactionFillStatByMatchedtext" parameterType="com.ruoyi.satisfaction.domain.entity.SatisfactionStatQuery"
+            resultType="java.util.Map">
+        select ss.libtemplateid as libtemplateid,
+               ssd.questiontext as questiontext,
+               ssd.matchedtext as matchedtext,
+               ssd.score as score,
+               count(*) as cnt,
+               min(ssd.targetvalue) as targetvalue
+        from service_subtask_detail ssd
+        join service_subtask ss on ss.id = ssd.sub_id
+        where ssd.del_flag = 0
+        and ss.del_flag = 0
+        <if test="orgid != null and orgid != ''">
+            and ss.orgid = #{orgid}
+        </if>
+        <if test="campusid != null and campusid != ''">
+            and ss.campusid = #{campusid}
+        </if>
+        <if test="type != null and type != ''">and ss.type = #{type}</if>
+        <if test="startTime != null">
+            and ss.visit_time &gt;= #{startTime}
+        </if>
+        <if test="endTime != null">
+            and ss.visit_time &lt; date_add(#{endTime}, interval 1 day)
+        </if>
+        <if test="libTemplateIds != null and libTemplateIds.size() > 0">
+            and ss.libtemplateid IN
+            <foreach collection="libTemplateIds" item="libTemplateId" open="(" separator="," close=")">
+                #{libTemplateId}
+            </foreach>
+        </if>
+        <if test="(hospitaldistrictcodes != null and hospitaldistrictcodes.size() > 0) or (deptcodes != null and deptcodes.size() > 0)">
+            AND (
+            <if test="hospitaldistrictcodes != null and hospitaldistrictcodes.size() > 0">
+                ss.leavehospitaldistrictcode IN
+                <foreach collection="hospitaldistrictcodes" item="hospitaldistrictcode" open="("
+                         separator=","
+                         close=")">
+                    #{hospitaldistrictcode}
+                </foreach>
+            </if>
+            <if test="hospitaldistrictcodes != null and hospitaldistrictcodes.size() > 0 and deptcodes != null and deptcodes.size() > 0">
+                OR
+            </if>
+            <if test="deptcodes != null and deptcodes.size() > 0">
+                ss.deptcode IN
+                <foreach collection="deptcodes" item="deptcode" open="(" separator="," close=")">
+                    #{deptcode}
+                </foreach>
+            </if>
+            )
+        </if>
+        group by ss.libtemplateid, ssd.questiontext, ssd.matchedtext, ssd.score
+    </select>
+
+    <select id="selectSatisfactionFillPersonCount" parameterType="com.ruoyi.satisfaction.domain.entity.SatisfactionStatQuery"
+            resultType="java.util.Map">
+        select ss.libtemplateid as libtemplateid,
+               count(distinct ssd.sub_id) as cnt
+        from service_subtask_detail ssd
+        join service_subtask ss on ss.id = ssd.sub_id
+        where ssd.del_flag = 0
+        and ss.del_flag = 0
+        <if test="orgid != null and orgid != ''">
+            and ss.orgid = #{orgid}
+        </if>
+        <if test="campusid != null and campusid != ''">
+            and ss.campusid = #{campusid}
+        </if>
+        <if test="type != null and type != ''">and ss.type = #{type}</if>
+        <if test="startTime != null">
+            and ss.visit_time &gt;= #{startTime}
+        </if>
+        <if test="endTime != null">
+            and ss.visit_time &lt; date_add(#{endTime}, interval 1 day)
+        </if>
+        <if test="libTemplateIds != null and libTemplateIds.size() > 0">
+            and ss.libtemplateid IN
+            <foreach collection="libTemplateIds" item="libTemplateId" open="(" separator="," close=")">
+                #{libTemplateId}
+            </foreach>
+        </if>
+        <if test="(hospitaldistrictcodes != null and hospitaldistrictcodes.size() > 0) or (deptcodes != null and deptcodes.size() > 0)">
+            AND (
+            <if test="hospitaldistrictcodes != null and hospitaldistrictcodes.size() > 0">
+                ss.leavehospitaldistrictcode IN
+                <foreach collection="hospitaldistrictcodes" item="hospitaldistrictcode" open="("
+                         separator=","
+                         close=")">
+                    #{hospitaldistrictcode}
+                </foreach>
+            </if>
+            <if test="hospitaldistrictcodes != null and hospitaldistrictcodes.size() > 0 and deptcodes != null and deptcodes.size() > 0">
+                OR
+            </if>
+            <if test="deptcodes != null and deptcodes.size() > 0">
+                ss.deptcode IN
+                <foreach collection="deptcodes" item="deptcode" open="(" separator="," close=")">
+                    #{deptcode}
+                </foreach>
+            </if>
+            )
+        </if>
+        group by ss.libtemplateid
+    </select>
+    <!-- ================================================ 閲嶅鏄庣粏娓呯悊锛堟弧鎰忓害缁熻鍓嶇疆锛�
+         鍘熷睘 ServiceSubtaskDetailMapper.xml锛屽彧鏈夋弧鎰忓害缁熻璋冪敤锛堝凡鏍稿疄锛夛紝鏁呬竴骞舵惉鍏ャ��
+         娉ㄦ剰 deleteRepeatDetailByIds 鏄�愬啓鎿嶄綔銆戯細鎸� id 閫昏緫鍒犻櫎閲嶅鏄庣粏锛坉el_flag=1锛夈�� -->
+
+    <select id="selectRepeatDetailIds" resultType="Long">
+        SELECT d1.id
+        FROM service_subtask_detail d1
+        INNER JOIN (
+            SELECT sub_id, questiontext,
+                   MAX(create_time) AS max_create_time,
+                   MAX(id) AS keep_id
+            FROM service_subtask_detail
+            WHERE del_flag = 0
+            GROUP BY sub_id, questiontext
+            HAVING COUNT(*) &gt; 1
+        ) d2 ON d1.sub_id = d2.sub_id
+            AND d1.questiontext = d2.questiontext
+            AND (
+                d1.create_time &lt; d2.max_create_time
+                OR (d1.create_time = d2.max_create_time AND d1.id &lt; d2.keep_id)
+            )
+        WHERE d1.del_flag = 0
+    </select>
+
+    <update id="deleteRepeatDetailByIds">
+        UPDATE service_subtask_detail
+        SET del_flag = 1
+        WHERE del_flag = 0
+        AND id IN
+        <foreach collection="ids" item="id" open="(" separator="," close=")">
+            #{id}
+        </foreach>
+    </update>
+    <!-- ================================================ 瀛愪换鍔★紙婊℃剰搴︾粺璁″彇鏁帮級
+         瀵瑰簲鍘� getServiceSubtasks 鈫� selectServiceSubtaskList 涓弧鎰忓害瀹為檯鐢ㄥ埌鐨勫彛寰勩��
+         鍘熸枃鏃犻粯璁ゆ帓搴�/鍒嗛〉锛岃繖閲屽悓鏍蜂笉鍔犮�備粎鍙� id銆乼askid 涓ゅ垪銆� -->
+    <select id="selectMydSubtaskRefs"
+            resultType="com.ruoyi.satisfaction.domain.row.SatisfactionSubtaskRef">
+        SELECT id, taskid
+        FROM service_subtask
+        WHERE del_flag = 0
+        <if test="q.orgid != null and q.orgid != ''">
+            AND orgid = #{q.orgid}
+        </if>
+        <if test="q.campusid != null and q.campusid != ''">
+            AND campusid = #{q.campusid}
+        </if>
+        <if test="q.serviceType != null">
+            AND service_type = #{q.serviceType}
+        </if>
+        <if test="q.startOutHospTime != null">
+            AND date_format(endtime,'%y%m%d') &gt;= date_format(#{q.startOutHospTime},'%y%m%d')
+        </if>
+        <if test="q.endOutHospTime != null">
+            AND date_format(endtime,'%y%m%d') &lt;= date_format(#{q.endOutHospTime},'%y%m%d')
+        </if>
+        <if test="q.deptOrDistrict==1">
+            <if test="q.leavehospitaldistrictcodes != null and q.leavehospitaldistrictcodes.size() > 0">
+                AND leavehospitaldistrictcode IN
+                <foreach collection="q.leavehospitaldistrictcodes" item="districtcode" open="(" separator="," close=")">
+                    #{districtcode}
+                </foreach>
+            </if>
+        </if>
+        <if test="q.leaveldeptcodes != null and q.leaveldeptcodes.size() > 0">
+            AND deptcode IN
+            <foreach collection="q.leaveldeptcodes" item="deptcode" open="(" separator="," close=")">
+                #{deptcode}
+            </foreach>
+        </if>
+    </select>
+
+    <!-- ================================================================
+         闅忚銆屾弧鎰忓害鎸囨暟銆嶅彇鏁帮紙鍘� ServiceSubtaskServiceImpl#getSatisfactionIndex 鐢ㄥ埌鐨� 5 鏉℃煡璇級
+
+         鍘熷疄鐜颁緷娆¤皟鐢ㄤ簡 5 涓笉鍚屾ā鍧楃殑 statement锛�
+           ServiceTaskMapper.selectServiceTaskList
+           IvrTaskTemplateScriptMapper.selectIvrTaskTemplateScriptList
+           SvyTaskTemplateScriptMapper.selectSvyTaskTemplateScriptList
+           ServiceSubtaskMapper.selectServiceSubtaskBySendstate
+           ServiceSubtaskDetailMapper.selectServiceSubtaskDetailList
+         璋冪敤鏂瑰彧鐢ㄥ埌瀹冧滑鐨� size()/鑻ュ共瀛楁锛屽洜姝よ繖閲屽悇鍐欎竴鏉°�愮瓑浠蜂笖绮剧畝銆戠殑鏌ヨ锛�
+           - 鍙繚鐣欒皟鐢ㄦ柟鐪熸璁剧疆杩囩殑鏉′欢锛堥�愭潯瀵圭収鍘� statement 鐨� <if>锛夛紱
+           - 璁℃暟鍦烘櫙鐩存帴鐢� count(*)锛堜笌 Java 渚� .size() 瀹屽叏绛変环锛夛紱
+           - 鐢ㄥ埌鐨� service_task / ivr_task_template_script / svy_task_template_script
+             鏄�岄殢璁夸换鍔′笌妯℃澘銆嶈〃锛屾湰妯″潡瀵瑰畠浠彧鍋氥�愬彧璇汇�戞煡璇紝涓嶅啓銆�
+         ================================================================ -->
+
+    <!-- 鍘燂細ServiceTaskMapper.selectServiceTaskList
+         璋冪敤鏂瑰彧璁句簡 (deptcode + orgid) 鎴� (leavehospitaldistrictcode + orgid)锛�
+         涓よ�呬簰鏂ワ紝鎵�浠ヤ竴鏉� SQL 瑕嗙洊鍘熸潵鐨勪袱涓垎鏀��
+         娉ㄦ剰 REGEXP 鐨勫啓娉曚笌鍘� statement 閫愬瓧涓�鑷达紙鍖归厤閫楀彿鍒嗛殧鐨勭紪鐮佷覆锛夈�� -->
+    <select id="selectSatisfactionServiceTasks"
+            resultType="com.ruoyi.satisfaction.domain.row.SatisfactionTaskRow">
+        select taskid,
+               templateid,
+               type,
+               orgid
+        from service_task
+        where 1=1
+        and del_flag = 0
+        <if test="deptcode != null and deptcode != ''">
+            and deptcode REGEXP CONCAT('(^|,)', #{deptcode}, '(,|$)')
+        </if>
+        <if test="leavehospitaldistrictcode != null and leavehospitaldistrictcode != ''">
+            and leavehospitaldistrictcode REGEXP CONCAT('(^|,)', #{leavehospitaldistrictcode}, '(,|$)')
+        </if>
+        <if test="orgid != null and orgid != ''">
+            and orgid = #{orgid}
+        </if>
+        order by update_time desc, taskid desc
+    </select>
+
+    <!-- 鍘燂細IvrTaskTemplateScriptMapper.selectIvrTaskTemplateScriptList锛堝彧鐢ㄥ埌 3 涓潯浠讹級 -->
+    <select id="countIvrTaskTemplateScripts" resultType="int">
+        select count(*)
+        from ivr_task_template_script
+        where 1=1
+        and del_flag = 0
+        <if test="templateID != null">
+            and templateID = #{templateID}
+        </if>
+        <if test="orgid != null and orgid != ''">
+            and orgid = #{orgid}
+        </if>
+        <if test="scriptAssortid != null">
+            and script_assortid = #{scriptAssortid}
+        </if>
+    </select>
+
+    <!-- 鍘燂細SvyTaskTemplateScriptMapper.selectSvyTaskTemplateScriptList锛堝彧鐢ㄥ埌 3 涓潯浠讹級 -->
+    <select id="countSvyTaskTemplateScripts" resultType="int">
+        select count(*)
+        from svy_task_template_script
+        where del_flag = 0
+        <if test="templateID != null">
+            and templateID = #{templateID}
+        </if>
+        <if test="orgid != null and orgid != ''">
+            and orgid = #{orgid}
+        </if>
+        <if test="categoryid != null">
+            and categoryid = #{categoryid}
+        </if>
+    </select>
+
+    <!-- 鍘燂細ServiceSubtaskMapper.selectServiceSubtaskBySendstate
+         璋冪敤鏂瑰彧璁句簡 taskid 涓� sendstates(3,5,6)锛沷rgid 鏈缃紝鏁呰繖閲屼笉鍚鏉′欢銆� -->
+    <select id="countSubtaskBySendstates" resultType="int">
+        select count(*)
+        from service_subtask
+        where 1=1
+        and del_flag = 0
+        <if test="taskid != null">
+            and taskid = #{taskid}
+        </if>
+        <if test="sendstates != null">
+            AND sendstate IN
+            <foreach collection="sendstates" item="sendstate" open="(" separator="," close=")">
+                #{sendstate}
+            </foreach>
+        </if>
+    </select>
+
+    <!-- 鍘燂細ServiceSubtaskDetailMapper.selectServiceSubtaskDetailList
+         鈿狅笍 鍘熸枃銆愭病鏈夈�� del_flag 鏉′欢锛岃繖閲屽悓鏍蜂笉鍔狅紝閬垮厤鍙e緞鍙樺寲銆�
+         categoryid 浠呭湪浠诲姟绫诲瀷涓鸿闊�/闂嵎鏃舵墠琚師瀹炵幇璁剧疆锛屾晠鐢� <if> 淇濅綇"涓嶈鍗充笉杩囨护"銆� -->
+    <select id="countSubtaskDetail" resultType="int">
+        select count(*)
+        from service_subtask_detail
+        where 1=1
+        <if test="taskid != null">
+            and taskid = #{taskid}
+        </if>
+        <if test="categoryid != null">
+            and categoryid = #{categoryid}
+        </if>
+        <if test="orgid != null and orgid != ''">
+            and orgid = #{orgid}
+        </if>
+    </select>
+
+</mapper>
\ No newline at end of file
diff --git a/ruoyi-satisfaction/src/main/resources/mapper/satisfaction/SatisfactionScriptMapper.xml b/ruoyi-satisfaction/src/main/resources/mapper/satisfaction/SatisfactionScriptMapper.xml
new file mode 100644
index 0000000..f4fab02
--- /dev/null
+++ b/ruoyi-satisfaction/src/main/resources/mapper/satisfaction/SatisfactionScriptMapper.xml
@@ -0,0 +1,200 @@
+<?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.ruoyi.satisfaction.mapper.SatisfactionScriptMapper">
+
+    <!--
+      婊℃剰搴︾粺璁″彇銆屾弧鎰忓害鍒嗙被 / 妯℃澘闂 / 閫夐」鍒嗗�笺�嶇殑鍙鏌ヨ銆�
+
+      涓� smartor 鍘熷疄鐜扮殑宸紓锛堝埢鎰忎负涔嬶紝涓斾笉鏀瑰彛寰勶級锛�
+      1) 鍘熸枃鐢� resultMap + 鍏变韩 <sql> 鐗囨锛坰electXxxVo锛夛紝鑰岀墖娈佃鍚屾枃浠堕噷鍏跺畠
+         闈炴弧鎰忓害鏌ヨ鍏辩敤銆傝繖閲屻�愪笉澶嶅埗鐗囨銆戯紝鏀逛负鏄惧紡鍒楀嚭婊℃剰搴︾湡姝g敤鍒扮殑鍒楋紝
+         閬垮厤鍚屼竴浠� SQL 涓ゅ缁存姢銆�
+      2) 瀹屽叏涓嶅紩鐢� smartor 鐨勭被鍨嬶紱涓�寰嬬敤鍏ㄩ檺瀹氱被鍚嶆垨 java.util.Map銆�
+      3) 鍏ㄩ儴鏄彧璇绘煡璇紝涓嶅 ivr_liba_* / svy_lib_* 鍋氫换浣曞啓鎿嶄綔銆�
+
+      鍙e緞瑕佺偣锛堝繀椤讳笌鍘熸枃閫愬瓧涓�鑷达級锛�
+      - queryIvrScriptByAssort / querySvyScriptByCategory锛歩ds 涓虹┖鏃朵笉鎷� IN 鏉′欢锛�
+      - selectIvrScriptByDimension锛歞imensionList 涓虹┖鏃舵嫾 `dimension IS NOT NULL`锛�
+      - selectSvyScriptByDimension锛歞imensionList 涓虹┖鏃躲�愪笉鎷笺�戣鏉′欢锛堜袱渚т笉涓�鑷存槸鍘熸枃琛屼负锛夛紱
+      - selectIvr/SvyTargetOptionScores锛歴criptIds 涓� null 鏃朵笉鎷艰繃婊わ紝绛変簬鍙栧叏閮ㄥ垎鍊笺��
+    -->
+
+    <!-- ================================================ 婊℃剰搴﹀垎绫伙紙璇煶渚э級
+         鍘燂細IvrLibaScriptAssortMapper.selectIvrLibaScriptAssortList
+         鍘熸枃 where 閲� del_flag=0 鍑虹幇涓ゆ锛堢墖娈靛悗涓�娆°�佹湯灏惧張涓�娆★級锛岀粨鏋滅瓑浠蜂簬涓�娆★紝杩欓噷淇濈暀涓�娆°��
+         娉ㄦ剰锛氳繖閲屽彇鐨勬槸銆愬師濮嬭銆戯紝鑰屽師鏈嶅姟鏂规硶 selectIvrLibaScriptAssortList 浼氭妸琛屾嫾鎴愭爲銆�
+         鍙繑鍥為《灞傦紙pid is null or pid = -1锛夈�傞渶瑕侀《灞傝涔夌殑璋冪敤鏂瑰繀椤昏嚜宸辨寜 pid 杩囨护銆� -->
+    <select id="selectIvrCategoryList"
+            resultType="com.ruoyi.satisfaction.domain.row.SatisfactionCategoryRow">
+        select id,
+               index_assort_name as name,
+               type,
+               pid
+        from ivr_liba_script_assort
+        where del_flag = 0
+        <if test="q.type != null and q.type != ''">
+            and type like concat('%', #{q.type}, '%')
+        </if>
+        <if test="q.orgid != null and q.orgid != ''">
+            and orgid = #{q.orgid}
+        </if>
+        order by seqno asc
+    </select>
+
+    <!-- ================================================ 婊℃剰搴﹀垎绫伙紙闂嵎渚э級
+         鍘燂細SvyLibScriptCategoryMapper.selectSvyLibScriptCategoryList
+             锛堣 statement 瀹為檯瀹氫箟鍦� SvyLibTopicCategoryMapper.xml锛宯amespace 涓庢枃浠跺悕涓嶄竴鑷达級
+         娉ㄦ剰锛氶棶鍗蜂晶鍘熸枃灏辨槸鐩存帴鍙栥�愬叏閮ㄨ銆戯紙涓嶆嫾鏍戙�佷笉绛涢《灞傦級锛屼笌璇煶渚т笉鍚屻�� -->
+    <select id="selectSvyCategoryList"
+            resultType="com.ruoyi.satisfaction.domain.row.SatisfactionCategoryRow">
+        select id,
+               name,
+               type,
+               pid
+        from svy_lib_script_category
+        where del_flag = 0
+        <if test="q.type != null and q.type != ''">
+            and type like concat('%', #{q.type}, '%')
+        </if>
+        <if test="q.orgid != null and q.orgid != ''">
+            and orgid = #{q.orgid}
+        </if>
+        order by seqno asc
+    </select>
+
+    <!-- ================================================ 鎸夎闊冲垎绫诲彇闂
+         鍘燂細IvrLibaTemplateScriptMapper.queryLibTemplateIds
+         鍙﹀锛屻�屾弧鎰忓害寮傚父閰嶇疆棰樼洰銆嶆帴鍙h蛋鐨勬槸 IvrLibaTemplateScriptMapper.selectIvrLibaTemplateScriptList
+         锛堝彧 set scriptAssortid锛夛紝鍏舵湁鏁堟潯浠跺悓鏍锋槸 del_flag=0 + script_assortid 杩囨护锛�
+         涓ゆ潯 statement 閮姐�愭病鏈� order by銆戯紝鍥犳鏈煡璇㈠彲涓�骞舵湇鍔′袱澶勶紱
+         涓轰簡璁╄鎺ュ彛鎷垮埌 questionId / questionTopic / questionType / categoryId锛�
+         杩欓噷棰濆澶氬彇 id銆乻cript_topic銆乻cript_type銆乻cript_assortid 鍥涘垪锛堢粺璁″彛寰勪笉浣跨敤瀹冧滑锛夈�� -->
+    <select id="queryIvrScriptByAssort"
+            resultType="com.ruoyi.satisfaction.domain.row.SatisfactionIvrScriptRow">
+        select id,
+               templateid,
+               scriptid,
+               script_content,
+               script_assortname,
+               script_assortid,
+               script_topic,
+               script_type,
+               dimension
+        from ivr_liba_template_script
+        where del_flag = 0
+        <if test="q.scriptAssortids != null and q.scriptAssortids.size() > 0">
+            and script_assortid in
+            <foreach item="scriptAssortid" collection="q.scriptAssortids" open="(" separator="," close=")">
+                #{scriptAssortid}
+            </foreach>
+        </if>
+    </select>
+
+    <!-- ================================================ 鎸夐棶鍗峰垎绫诲彇闂
+         鍘燂細SvyLibTemplateScriptMapper.querySvyLibTemplateIds
+         娉ㄦ剰锛氬悓鏃跺彇 svyid 涓� id 鈥斺�� 鑱氬悎缁熻鐢� svyid锛岄�夐」鍒嗗�煎叧鑱旂敤 id锛堜笌鍘熸枃涓�鑷达級
+         鍙﹀锛屻�屾弧鎰忓害寮傚父閰嶇疆棰樼洰銆嶆帴鍙h蛋鐨勬槸 SvyLibTemplateScriptMapper.selectSvyLibTemplateScriptList
+         锛堝彧 set categoryid锛夛紝鍏舵湁鏁堟潯浠舵槸 1=1 + del_flag=0 + categoryid 杩囨护锛�
+         涓ゆ潯 statement 閮姐�愭病鏈� order by銆戯紝鍥犳鏈煡璇㈠彲涓�骞舵湇鍔′袱澶勶紱
+         杩欓噷棰濆澶氬彇 script_topic銆乻cript_type銆乧ategoryid 涓夊垪锛堢粺璁″彛寰勪笉浣跨敤瀹冧滑锛夈�� -->
+    <select id="querySvyScriptByCategory"
+            resultType="com.ruoyi.satisfaction.domain.row.SatisfactionSvyScriptRow">
+        select id,
+               svyid,
+               script_content,
+               categoryName,
+               categoryid,
+               script_topic,
+               script_type,
+               dimension
+        from svy_lib_template_script
+        where del_flag = 0
+        <if test="q.scriptCategoryids != null and q.scriptCategoryids.size() > 0">
+            and categoryid in
+            <foreach item="scriptCategoryid" collection="q.scriptCategoryids" open="(" separator="," close=")">
+                #{scriptCategoryid}
+            </foreach>
+        </if>
+    </select>
+
+    <!-- ================================================ 鎸夌淮搴﹀彇闂锛堣闊充晶锛�
+         鍘燂細IvrLibaTemplateScriptMapper.selectILTSBydimension
+         鍙e緞瑕佺偣锛歞imensionList 涓虹┖鏃舵嫾 `dimension IS NOT NULL`锛堥棶鍗蜂晶涓嶆嫾锛屼袱渚т笉鍚屾槸鍘熸枃琛屼负锛� -->
+    <select id="selectIvrScriptByDimension"
+            resultType="com.ruoyi.satisfaction.domain.row.SatisfactionIvrScriptRow">
+        select templateid,
+               scriptid,
+               script_content,
+               script_assortname,
+               dimension
+        from ivr_liba_template_script
+        <where>
+            del_flag = 0
+            <if test="q.dimensionList != null and q.dimensionList.size() > 0">
+                and dimension in
+                <foreach item="dimension" collection="q.dimensionList" open="(" separator="," close=")">
+                    #{dimension}
+                </foreach>
+            </if>
+            <if test="q.dimensionList == null or q.dimensionList.size() == 0">
+                and dimension IS NOT NULL
+            </if>
+        </where>
+    </select>
+
+    <!-- ================================================ 鎸夌淮搴﹀彇闂锛堥棶鍗蜂晶锛�
+         鍘燂細SvyLibTemplateScriptMapper.selectSLTScriptListBydimension
+         鍙e緞瑕佺偣锛歞imensionList 涓虹┖鏃躲�愪笉鎷笺�慸imension IS NOT NULL锛堢瓑浜庡叏琛� del_flag=0锛夛紱
+                   鑱氬悎鐢ㄧ殑妯℃澘 id 涓庨�夐」鍏宠仈 id 閮芥槸 svyid -->
+    <select id="selectSvyScriptByDimension"
+            resultType="com.ruoyi.satisfaction.domain.row.SatisfactionSvyScriptRow">
+        select svyid,
+               svyid as id,
+               script_content,
+               categoryName,
+               dimension
+        from svy_lib_template_script
+        <where>
+            del_flag = 0
+            <if test="q.dimensionList != null and q.dimensionList.size() > 0">
+                and dimension in
+                <foreach item="dimension" collection="q.dimensionList" open="(" separator="," close=")">
+                    #{dimension}
+                </foreach>
+            </if>
+        </where>
+    </select>
+
+    <!-- ================================================ 閫夐」鍒嗗�硷紙璇煶渚э級
+         鍘燂細IvrLibaTemplateTargetoptionMapper.selectIvrLibaTargetoptionScores
+         鍙e緞瑕佺偣锛歴criptIds 涓� null 鏃朵笉鎷艰繃婊わ紙绛変簬鍙栬琛ㄥ叏閮ㄥ垎鍊硷級 -->
+    <select id="selectIvrTargetOptionScores"
+            resultType="com.ruoyi.satisfaction.domain.row.SatisfactionOptionScoreRow">
+        select scriptid as scriptid, score as score
+        from ivr_liba_template_targetoption
+        where del_flag = 0
+        <if test="scriptIds != null and scriptIds.size() > 0">
+            and scriptid IN
+            <foreach collection="scriptIds" item="scriptId" open="(" separator="," close=")">
+                #{scriptId}
+            </foreach>
+        </if>
+    </select>
+
+    <!-- ================================================ 閫夐」鍒嗗�硷紙闂嵎渚э級
+         鍘燂細SvyLibTemplateTargetoptionMapper.selectSvyLibTargetoptionScores -->
+    <select id="selectSvyTargetOptionScores"
+            resultType="com.ruoyi.satisfaction.domain.row.SatisfactionOptionScoreRow">
+        select scriptid as scriptid, score as score
+        from svy_lib_template_targetoption
+        where del_flag = 0
+        <if test="scriptIds != null and scriptIds.size() > 0">
+            and scriptid IN
+            <foreach collection="scriptIds" item="scriptId" open="(" separator="," close=")">
+                #{scriptId}
+            </foreach>
+        </if>
+    </select>
+
+</mapper>
diff --git a/ruoyi-satisfaction/src/test/java/com/ruoyi/satisfaction/api/SatisfactionContextCheck.java b/ruoyi-satisfaction/src/test/java/com/ruoyi/satisfaction/api/SatisfactionContextCheck.java
new file mode 100644
index 0000000..68ad347
--- /dev/null
+++ b/ruoyi-satisfaction/src/test/java/com/ruoyi/satisfaction/api/SatisfactionContextCheck.java
@@ -0,0 +1,269 @@
+package com.ruoyi.satisfaction.api;
+
+import com.ruoyi.common.core.service.IConfigService;
+import com.ruoyi.common.exception.ServiceException;
+import com.ruoyi.satisfaction.config.SatisfactionAvailabilityInterceptor;
+import com.ruoyi.satisfaction.config.SatisfactionProperties;
+import com.ruoyi.satisfaction.config.SatisfactionSwitch;
+import com.ruoyi.satisfaction.config.SatisfactionWebConfig;
+import com.ruoyi.satisfaction.service.strategy.CommonSatisfactionScoreStrategy;
+import com.ruoyi.satisfaction.service.strategy.SatisfactionScoreStrategyResolver;
+import com.ruoyi.satisfaction.service.strategy.SatisfactionSites;
+import com.ruoyi.satisfaction.service.strategy.SltdSatisfactionScoreStrategy;
+import org.springframework.context.annotation.AnnotationConfigApplicationContext;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 婊℃剰搴︽ā鍧楃殑 <b>鐪熷疄 Spring 瀹瑰櫒</b>瑁呴厤鏍¢獙锛堜笉渚濊禆 JUnit锛屼篃涓嶄緷璧栨暟鎹簱锛夈��
+ * <p>
+ * 涓轰粈涔堥渶瑕佽繖涓鏌ワ細鏈ā鍧楃殑鍑犱釜"闈欐�佹牎楠�"锛圡apper 涓�鑷存�с�侀�愯绛変环銆佺瓥鐣ョ瓑浠凤級閮藉彧鑳借瘉鏄�
+ * <b>浠g爜鍐呭</b>姝g‘锛岃瘉鏄庝笉浜� <b>Spring 鑳芥妸 Bean 瑁呰捣鏉�</b>銆傚疄闄呰俯杩囦袱涓潙锛�
+ * <ol>
+ *   <li>{@link SatisfactionFacade} 鏈変袱涓叕寮�鏋勯�犲櫒锛孲pring 鍥�"鏋勯�犲櫒涓嶅敮涓�"閫�鍥炴壘鏃犲弬鏋勯�犲櫒
+ *       鈫� 鍚姩鏈� {@code No default constructor found}锛�</li>
+ *   <li>{@code SatisfactionApi} 鐨勬寜绫诲瀷鏌ユ壘浼氭妸闂ㄩ潰<b>鑷繁</b>褰撳厹搴曞�欓��
+ *       鈫� 闂ㄩ潰鎶婅姹傝浆鍙戠粰鑷繁 鈫� {@code StackOverflowError}銆�</li>
+ * </ol>
+ * 鎵�浠ヨ繖閲岀洿鎺ヨ捣涓�涓湡瀹炵殑 {@link AnnotationConfigApplicationContext}锛屽彧娉ㄥ唽鏈ā鍧�
+ * <b>涓嶄緷璧� Mapper / 鏁版嵁搴�</b>鐨勯偅浜� Bean锛岄獙璇佽閰嶄笌寮�鍏宠涓恒��
+ * <p>
+ * 寮�鍏虫湰韬瓨鍦� {@code sys_config} 鍙傛暟琛ㄩ噷锛堥敭 {@code satisfaction.enabled}锛岃繍琛屾椂鐑锛夛紝
+ * 鍥犳杩欓噷鐢ㄤ竴涓亣鐨� {@link IConfigService} 妯℃嫙鍙傛暟琛紝楠岃瘉銆屽弬鏁拌〃浼樺厛銆亂ml 鍏滃簳銆嶃��
+ */
+public final class SatisfactionContextCheck {
+
+    private static int failed = 0;
+
+    private SatisfactionContextCheck() {
+    }
+
+    /**
+     * 鍋囩殑鍐呴儴瀹炵幇锛堢湡瀹炲満鏅噷鏄� DefaultSatisfactionApi锛屽畠闇�瑕� Mapper/Redis锛岃捣涓嶄簡杞婚噺瀹瑰櫒锛�
+     */
+    public static class StubApi implements SatisfactionApi {
+
+        /**
+         * 鍒嗙被鏌ヨ琚皟鐢ㄧ殑娆℃暟锛氱敤鏉ヨ瘉鏄�"鍙傛暟琛ㄥ叧闂椂闂ㄩ潰纭疄娌℃湁杞彂缁欏疄鐜�"
+         */
+        static final java.util.concurrent.atomic.AtomicInteger CALLS = new java.util.concurrent.atomic.AtomicInteger();
+
+        @Override
+        public boolean isEnabled() {
+            return true;
+        }
+
+        @Override
+        public Map<String, Object> getSatisfactionCategories(String orgid) {
+            CALLS.incrementAndGet();
+            return Collections.singletonMap("fromStub", orgid);
+        }
+
+        @Override
+        public SatisfactionExportBundle getQuestionOptionRatioExport(List<Long> subtaskIds, String questiontext) {
+            return SatisfactionExportBundle.empty();
+        }
+
+        @Override
+        public boolean hasSatisfactionCategory(String orgid) {
+            return true;
+        }
+
+        @Override
+        public Map<String, Object> getSatisfactionIndex(String type, String code, String orgid,
+                                                        Long ivrCategoryid, Long svyCategoryid) {
+            return SatisfactionFacade.emptySatisfactionIndex();
+        }
+    }
+
+    /**
+     * 鎺㈤拡锛氫互"鏃佽鑰�"韬唤娉ㄥ叆 {@code ObjectProvider<SatisfactionApi>}锛岀湅瀹瑰櫒鎸夌被鍨嬭兘鎵惧埌璋併��
+     * <p>
+     * 瀹冧笉鏄� {@code SatisfactionApi} 鐨勫疄鐜帮紝鎵�浠ヤ笉瀛樺湪"鑷紩鐢ㄨ鎺掗櫎"鐨勬儏鍐碉紝
+     * 鍥犳瀹冪湅鍒扮殑灏辨槸瀹屾暣鐨勫�欓�夐泦鍚� 鈥斺�� 鐢ㄦ潵璇佹槑"妯″潡鍏抽棴鏃跺鍣ㄤ細鎶婇棬闈㈣嚜宸卞綋鍊欓��"銆�
+     */
+    public static class ProviderProbe {
+
+        @org.springframework.beans.factory.annotation.Autowired
+        private org.springframework.beans.factory.ObjectProvider<SatisfactionApi> provider;
+
+        SatisfactionApi rawCandidate() {
+            return provider.getIfAvailable();
+        }
+    }
+
+    public static void main(String[] args) {
+        System.out.println("=== 鍦烘櫙 1锛氬弬鏁拌〃 satisfaction.enabled=true锛屼笖瀛樺湪鍐呴儴瀹炵幇锛圖efaultSatisfactionApi 鐨勮鑹诧級===");
+        try (AnnotationConfigApplicationContext ctx = context(true, true, ContextCheckConfig.SYS_ON)) {
+            SatisfactionFacade facade = ctx.getBean(SatisfactionFacade.class);
+            check("闂ㄩ潰鑳借 Spring 瀹炰緥鍖栵紙鏋勯�犲櫒涓嶅啀姝т箟锛�", "true", String.valueOf(facade != null));
+            check("鍚敤鏃� isEnabled()", "true", String.valueOf(facade.isEnabled()));
+            check("鍙栧埌鐨勬槸銆愬唴閮ㄥ疄鐜般�戣�屼笉鏄棬闈㈣嚜宸憋紙stub 鎶� orgid 鍘熸牱鍥炲~锛�", "ORG1",
+                    String.valueOf(facade.getSatisfactionCategories("ORG1").get("fromStub")));
+            check("闂ㄩ潰鍚屾椂鏄� ISatisfactionCategoryService",
+                    "true", String.valueOf(ctx.getBean(com.ruoyi.common.core.service.ISatisfactionCategoryService.class) == facade));
+            // 瀹瑰櫒閲屽簲鏈� 2 涓� SatisfactionApi锛氶棬闈㈣嚜宸� + 鍐呴儴瀹炵幇锛堥棬闈篃瀹炵幇璇ユ帴鍙o紝杩欐槸璁捐浣跨劧锛�
+            check("瀹瑰櫒鍐� SatisfactionApi 瀹炵幇鏁� = 闂ㄩ潰 1 + 鍐呴儴瀹炵幇 1", "2",
+                    String.valueOf(ctx.getBeansOfType(SatisfactionApi.class).size()));
+            check("寮�鍏宠鍙栫殑閿悕鏄� sys_config 鐨� satisfaction.enabled", SatisfactionSwitch.CONFIG_KEY,
+                    String.valueOf(SatisfactionSwitch.CONFIG_KEY));
+
+            SatisfactionScoreStrategyResolver resolver = ctx.getBean(SatisfactionScoreStrategyResolver.class);
+            check("绔欑偣绛栫暐瑙f瀽鍣ㄨ閰嶆垚鍔燂紝鏀寔绔欑偣 = [common, sltd]", "[common, sltd]",
+                    String.valueOf(resolver.supportedSites()));
+            check("鏈煡绔欑偣鍥炶惤鍒伴�氱敤鍙e緞", SatisfactionSites.COMMON,
+                    resolver.resolve("涓嶅瓨鍦ㄧ殑绔欑偣").site());
+        }
+
+        System.out.println();
+        System.out.println("=== 鍦烘櫙 2锛氬弬鏁拌〃 satisfaction.enabled=true锛屼絾銆愭病鏈夈�戝唴閮ㄥ疄鐜帮紙妯″潡娌¤鎵撹繘搴旂敤锛�===");
+        try (AnnotationConfigApplicationContext ctx = context(true, false, ContextCheckConfig.SYS_ON)) {
+            SatisfactionFacade facade = ctx.getBean(SatisfactionFacade.class);
+            check("鏃犲疄鐜版椂 isEnabled() 涓� false", "false", String.valueOf(facade.isEnabled()));
+            // 鍏抽敭锛氫笉鑳芥妸闂ㄩ潰鑷繁褰撳疄鐜帮紝鍚﹀垯杩欓噷浼� StackOverflowError
+            String result;
+            try {
+                result = String.valueOf(facade.getSatisfactionCategories("ORG1").isEmpty());
+            } catch (StackOverflowError e) {
+                result = "StackOverflowError";
+            }
+            check("鏃犲疄鐜版椂鍒嗙被鏌ヨ杩斿洖绌� Map锛堜笉浼氳嚜寮曠敤閫掑綊锛�", "true", result);
+            check("鏃犲疄鐜版椂鍗犳瘮瀵煎嚭鍖呬负绌�", "true",
+                    String.valueOf(facade.getQuestionOptionRatioExport(Collections.singletonList(1L), null).isEmpty()));
+            check("鏃犲疄鐜版椂婊℃剰搴︽寚鏁颁负鍏ㄩ浂 Map", "0",
+                    String.valueOf(facade.getSatisfactionIndex("1", "D001", "ORG1", null, null).get("mydtmzl")));
+
+            // 鍙嶈瘉锛氱洿鎺ラ棶瀹瑰櫒"鎸夌被鍨嬫湁鍝簺 SatisfactionApi 鍊欓��"锛岀湅瀹冩槸鍚︽妸闂ㄩ潰鑷繁绠楄繘鍘汇��
+            // 鑻ヨ繖閲岃繑鍥炵殑鏄棬闈㈣嚜宸憋紝灏辫鏄�"鑷紩鐢ㄥ厹搴�"纭疄瀛樺湪锛岄棬闈㈤噷鐨� self 杩囨护鏄繀瑕佺殑銆�
+            ProviderProbe probe = ctx.getBean(ProviderProbe.class);
+            check("鏃犲疄鐜版椂锛屽鍣ㄦ寜绫诲瀷鍙壘鍒伴棬闈㈣嚜宸憋紙璇佹槑鑷紩鐢ㄥ厹搴曞瓨鍦級", "true",
+                    String.valueOf(probe.rawCandidate() == facade));
+            check("浣嗛棬闈笉浼氭妸'鑷繁'褰撴垚瀹炵幇锛坰elf 杩囨护鐢熸晥锛�", "false",
+                    String.valueOf(facade.isEnabled()));
+        }
+
+        System.out.println();
+        System.out.println("=== 鍦烘櫙 3锛氬弬鏁拌〃 satisfaction.enabled=false锛堝疄鐜� Bean 渚濈劧瀛樺湪锛屼絾闂ㄩ潰蹇呴』涓嶈浆鍙戯級===");
+        StubApi.CALLS.set(0);
+        try (AnnotationConfigApplicationContext ctx = context(true, true, ContextCheckConfig.SYS_OFF)) {
+            SatisfactionFacade facade = ctx.getBean(SatisfactionFacade.class);
+            check("鍏抽棴鏃� isEnabled() 涓� false", "false", String.valueOf(facade.isEnabled()));
+            String result;
+            try {
+                result = String.valueOf(facade.getSatisfactionCategories("ORG1").isEmpty());
+            } catch (StackOverflowError e) {
+                result = "StackOverflowError";
+            }
+            check("鍏抽棴鏃跺垎绫绘煡璇㈣繑鍥炵┖ Map锛堜笉浼氳嚜閫掑綊锛�", "true", result);
+            check("鍏抽棴鏃朵笉鍐嶈皟鐢ㄥ唴閮ㄥ疄鐜帮紙stub 璋冪敤娆℃暟 = 0锛�", "0",
+                    String.valueOf(StubApi.CALLS.get()));
+
+            SatisfactionAvailabilityInterceptor interceptor = ctx.getBean(SatisfactionAvailabilityInterceptor.class);
+            String thrown = "(鏈姏寮傚父)";
+            try {
+                interceptor.preHandle(null, null, null);
+            } catch (Exception e) {
+                thrown = e.getClass().getSimpleName() + "|" + e.getMessage();
+            }
+            check("鍏抽棴鏃舵嫤鎴櫒鎶� ServiceException", "true", String.valueOf(thrown.startsWith("ServiceException|")));
+            check("鍏抽棴鏃堕敊璇彁绀烘槑纭�", "true",
+                    String.valueOf(thrown.contains("satisfaction.enabled=false") && thrown.contains("鎺ュ彛涓嶅彲鐢�")));
+
+            check("鍏抽棴鏃跺彈淇濇姢璺緞瑙勫垯 7 鏉�", "7",
+                    String.valueOf(SatisfactionWebConfig.guardedPathPatterns().size()));
+        }
+
+        System.out.println();
+        System.out.println("=== 鍦烘櫙 4锛氬弬鏁拌〃銆愭病鏈夈�憇atisfaction.enabled 杩欎竴琛屾椂锛屽洖钀� application.yml锛堥粯璁� true锛�===");
+        try (AnnotationConfigApplicationContext ctx = context(true, true, ContextCheckConfig.SYS_MISSING)) {
+            SatisfactionFacade facade = ctx.getBean(SatisfactionFacade.class);
+            check("鍙傛暟琛岀己澶便�亂ml 榛樿 true 鏃� isEnabled() 涓� true", "true", String.valueOf(facade.isEnabled()));
+        }
+        try (AnnotationConfigApplicationContext ctx = context(false, true, ContextCheckConfig.SYS_MISSING)) {
+            SatisfactionFacade facade = ctx.getBean(SatisfactionFacade.class);
+            check("鍙傛暟琛岀己澶便�亂ml=false 鏃� isEnabled() 涓� false", "false", String.valueOf(facade.isEnabled()));
+        }
+
+        System.out.println();
+        System.out.println("=== 鍦烘櫙 5锛氬鍣ㄩ噷銆愭病鏈夈�慖ConfigService 鏃讹紙鐙珛/娴嬭瘯鐜锛夊繀椤昏兘鍚姩涓旀寜 yml 鍏滃簳 ===");
+        try (AnnotationConfigApplicationContext ctx = context(true, true, null, false)) {
+            SatisfactionFacade facade = ctx.getBean(SatisfactionFacade.class);
+            check("鏃犻厤缃湇鍔℃椂闂ㄩ潰浠嶈兘瑁呴厤骞跺惎鍔�", "true", String.valueOf(facade != null));
+            check("鏃犻厤缃湇鍔℃椂鎸� yml 鍏滃簳锛坱rue锛�", "true", String.valueOf(facade.isEnabled()));
+        }
+
+        System.out.println();
+        if (failed == 0) {
+            System.out.println("===== 婊℃剰搴︽ā鍧� Spring 瑁呴厤鏍¢獙鍏ㄩ儴閫氳繃 =====");
+        } else {
+            System.out.println("===== 婊℃剰搴︽ā鍧� Spring 瑁呴厤鏍¢獙澶辫触 " + failed + " 椤� =====");
+            System.exit(1);
+        }
+    }
+
+    /**
+     * 鍙傛暟琛ㄧ殑妯℃嫙鍙栧��
+     */
+    static final class ContextCheckConfig {
+        /** 鍙傛暟琛屽瓨鍦ㄤ笖涓� true */
+        static final String SYS_ON = "true";
+        /** 鍙傛暟琛屽瓨鍦ㄤ笖涓� false */
+        static final String SYS_OFF = "false";
+        /** 鍙傛暟琛屼笉瀛樺湪 */
+        static final String SYS_MISSING = null;
+
+        private ContextCheckConfig() {
+        }
+    }
+
+    /**
+     * 璧蜂竴涓彧鍚湰妯″潡"鏃� Mapper 渚濊禆"Bean 鐨勭湡瀹炲鍣�
+     *
+     * @param ymlEnabled        application.yml 閲� satisfaction.enabled 鐨勫彇鍊硷紙鍏滃簳鐢級
+     * @param withDelegate      鏄惁娉ㄥ唽鍐呴儴瀹炵幇锛圖efaultSatisfactionApi 鐨勮鑹诧級
+     * @param sysConfigValue    妯℃嫙 sys_config 閲� satisfaction.enabled 鐨勯敭鍊硷紝null=娌℃湁杩欎竴琛�
+     */
+    private static AnnotationConfigApplicationContext context(boolean ymlEnabled, boolean withDelegate,
+                                                              final String sysConfigValue) {
+        return context(ymlEnabled, withDelegate, sysConfigValue, true);
+    }
+
+    /**
+     * @param withConfigService 瀹瑰櫒閲屾槸鍚︽敞鍐� {@link IConfigService}锛坒alse 妯℃嫙鐙珛/娴嬭瘯鐜锛�
+     */
+    private static AnnotationConfigApplicationContext context(boolean ymlEnabled, boolean withDelegate,
+                                                              final String sysConfigValue, boolean withConfigService) {
+        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
+        SatisfactionProperties properties = new SatisfactionProperties();
+        properties.setEnabled(ymlEnabled);
+        ctx.registerBean(SatisfactionProperties.class, () -> properties);
+        ctx.register(SatisfactionFacade.class,
+                SatisfactionSwitch.class,
+                SatisfactionAvailabilityInterceptor.class,
+                SatisfactionWebConfig.class,
+                SatisfactionScoreStrategyResolver.class,
+                CommonSatisfactionScoreStrategy.class,
+                SltdSatisfactionScoreStrategy.class,
+                ProviderProbe.class);
+        if (withConfigService) {
+            // 鍋囩殑鍙傛暟閰嶇疆鏈嶅姟锛氬彧璁� satisfaction.enabled 杩欎竴涓敭锛屽�肩敱 sysConfigValue 鍐冲畾
+            ctx.registerBean("stubConfigService", IConfigService.class, () -> (IConfigService) configKey ->
+                    SatisfactionSwitch.CONFIG_KEY.equals(configKey) ? sysConfigValue : "");
+        }
+        if (withDelegate) {
+            ctx.registerBean("stubSatisfactionApi", SatisfactionApi.class, StubApi::new);
+        }
+        ctx.refresh();
+        return ctx;
+    }
+
+    private static void check(String key, String expected, String actual) {
+        boolean ok = expected.equals(actual);
+        if (!ok) {
+            failed++;
+        }
+        System.out.println("[" + (ok ? "OK" : "FAIL") + "]   " + key + "  鏈熸湜=" + expected + "  瀹為檯=" + actual);
+    }
+}
diff --git a/ruoyi-satisfaction/src/test/java/com/ruoyi/satisfaction/api/SatisfactionSwitchCheck.java b/ruoyi-satisfaction/src/test/java/com/ruoyi/satisfaction/api/SatisfactionSwitchCheck.java
new file mode 100644
index 0000000..78ca3c4
--- /dev/null
+++ b/ruoyi-satisfaction/src/test/java/com/ruoyi/satisfaction/api/SatisfactionSwitchCheck.java
@@ -0,0 +1,236 @@
+package com.ruoyi.satisfaction.api;
+
+import com.ruoyi.common.core.service.ISatisfactionCategoryService;
+import com.ruoyi.satisfaction.config.SatisfactionAvailabilityInterceptor;
+import com.ruoyi.satisfaction.config.SatisfactionProperties;
+import com.ruoyi.satisfaction.config.SatisfactionSwitch;
+import com.ruoyi.satisfaction.config.SatisfactionWebConfig;
+import com.ruoyi.satisfaction.domain.ServiceSubtaskDetailRatioExport;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 婊℃剰搴﹀紑鍏充笌闂ㄩ潰琛屼负鏍¢獙锛堜笉渚濊禆 JUnit锛屽彲鐩存帴鐢ㄦā鍧� classpath 杩愯锛夈��
+ * <p>
+ * 寮�鍏冲凡浠� {@code application.yml} 鎸埌 {@code sys_config} 鍙傛暟琛ㄧ殑
+ * {@code satisfaction.enabled}锛堣繍琛屾椂鐑锛夛紝鍥犳杩欓噷楠岃瘉涓変欢浜嬶細
+ * <ol>
+ *   <li><b>鍙傛暟琛ㄤ紭鍏�</b>锛歿@code sys_config} 鏈夊�兼椂浠ュ畠涓哄噯锛屽悓涓�浠� yml 閰嶇疆涓嬪彲寮�鍙叧锛�</li>
+ *   <li><b>yml 鍏滃簳</b>锛歿@code sys_config} 娌℃湁杩欎竴琛屾椂鍥炶惤 {@code satisfaction.enabled}锛堥粯璁� true锛夛紱</li>
+ *   <li><b>鍏抽棴鏃朵笉鎶涘紓甯�</b>锛氶棬闈㈣繑鍥炲畨鍏ㄧ┖鍊硷紝涓旀弧鎰忓害绔偣鎷垮埌鐨勬槸銆屾槑纭敊璇�嶈�屼笉鏄� 404銆�</li>
+ * </ol>
+ */
+public final class SatisfactionSwitchCheck {
+
+    private static int failed = 0;
+
+    /** sys_config 閲岀殑銆屽凡寮�鍚�嶆ā鎷熷�� */
+    private static final String SYS_ON = "true";
+
+    /** sys_config 閲岀殑銆屽凡鍏抽棴銆嶆ā鎷熷�� */
+    private static final String SYS_OFF = "false";
+
+    /** sys_config 閲屻�屾病鏈夎繖涓�琛屻�� */
+    private static final String SYS_MISSING = null;
+
+    private SatisfactionSwitchCheck() {
+    }
+
+    /** 鍋囧疄鐜帮細鐢ㄤ簬楠岃瘉"鍚敤"璺緞 */
+    static class StubApi implements SatisfactionApi {
+        @Override
+        public boolean isEnabled() {
+            return true;
+        }
+
+        @Override
+        public Map<String, Object> getSatisfactionCategories(String orgid) {
+            return Collections.singletonMap("voiceCategories", Collections.singletonList(orgid));
+        }
+
+        @Override
+        public SatisfactionExportBundle getQuestionOptionRatioExport(List<Long> subtaskIds, String questiontext) {
+            return new SatisfactionExportBundle(
+                    Collections.singletonList(new ServiceSubtaskDetailRatioExport()),
+                    ServiceSubtaskDetailRatioExport.class, workbook -> { });
+        }
+
+        @Override
+        public boolean hasSatisfactionCategory(String orgid) {
+            return true;
+        }
+
+        @Override
+        public Map<String, Object> getSatisfactionIndex(String type, String code, String orgid,
+                                                        Long ivrCategoryid, Long svyCategoryid) {
+            return SatisfactionFacade.emptySatisfactionIndex();
+        }
+    }
+
+    public static void main(String[] args) throws Exception {
+        // ---------------- 鍚敤锛歽ml 鍏滃簳 ----------------
+        SatisfactionProperties on = new SatisfactionProperties();   // 榛樿 enabled=true
+        SatisfactionFacade onFacade = new SatisfactionFacade(on, new StubApi(), SYS_MISSING);
+
+        check("鍚敤鏃� isEnabled()", "true", String.valueOf(onFacade.isEnabled()));
+        check("鍚敤鏃跺垎绫绘煡璇㈡湁缁撴灉", "true",
+                String.valueOf(!onFacade.getSatisfactionCategories("ORG1").isEmpty()));
+        SatisfactionExportBundle onBundle = onFacade.getQuestionOptionRatioExport(Collections.singletonList(1L), null);
+        check("鍚敤鏃跺崰姣斿鍑哄寘鏈夎", "true", String.valueOf(!onBundle.isEmpty()));
+        check("鍚敤鏃跺崰姣斿鍑哄寘甯﹁绫诲瀷涓庡浘琛ㄦ瀯寤哄櫒", "true",
+                String.valueOf(onBundle.getRowClass() != null && onBundle.getChartBuilder() != null));
+        check("鍚敤鏃� hasSatisfactionCategory", "true", String.valueOf(onFacade.hasSatisfactionCategory("ORG1")));
+
+        // ---------------- 鍚敤锛歽ml 涓� false锛屼絾鍙傛暟琛ㄨ true锛堝弬鏁拌〃浼樺厛锛� ----------------
+        SatisfactionProperties ymlOff = new SatisfactionProperties();
+        ymlOff.setEnabled(false);
+        SatisfactionFacade sysOnFacade = new SatisfactionFacade(ymlOff, new StubApi(), SYS_ON);
+        check("鍙傛暟琛�=true 瑕嗙洊 yml=false 鏃� isEnabled()", "true", String.valueOf(sysOnFacade.isEnabled()));
+        check("鍙傛暟琛�=true 瑕嗙洊 yml=false 鏃跺垎绫绘煡璇㈡湁缁撴灉", "true",
+                String.valueOf(!sysOnFacade.getSatisfactionCategories("ORG1").isEmpty()));
+
+        // ---------------- 鍏抽棴锛氬弬鏁拌〃 false锛屽疄鐜� Bean 渚濈劧瀛樺湪 ----------------
+        SatisfactionFacade offFacade = new SatisfactionFacade(on, new StubApi(), SYS_OFF);
+
+        check("鍏抽棴鏃� isEnabled()", "false", String.valueOf(offFacade.isEnabled()));
+        check("鍏抽棴鏃跺垎绫绘煡璇㈣繑鍥炵┖ Map锛堝嵆浣垮疄鐜拌繕鍦ㄤ篃涓嶈浆鍙戯級", "true",
+                String.valueOf(offFacade.getSatisfactionCategories("ORG1").isEmpty()));
+        SatisfactionExportBundle offBundle = offFacade.getQuestionOptionRatioExport(Collections.singletonList(1L), null);
+        check("鍏抽棴鏃跺崰姣斿鍑哄寘涓虹┖", "true", String.valueOf(offBundle.isEmpty()));
+        check("鍏抽棴鏃跺崰姣斿鍑哄寘涓嶅甫鍥捐〃鏋勫缓鍣紙瀵煎嚭鐓у父瀹屾垚锛�", "true",
+                String.valueOf(offBundle.getChartBuilder() == null));
+        check("鍏抽棴鏃� hasSatisfactionCategory", "false", String.valueOf(offFacade.hasSatisfactionCategory("ORG1")));
+
+        // ---------------- 鍏抽棴锛氬弬鏁拌〃缂哄け + yml false锛堝厹搴曠敓鏁堬級 ----------------
+        SatisfactionFacade ymlOnlyOff = new SatisfactionFacade(ymlOff, new StubApi(), SYS_MISSING);
+        check("鍙傛暟琛ㄧ己澶便�亂ml=false 鏃� isEnabled()", "false", String.valueOf(ymlOnlyOff.isEnabled()));
+
+        // ---------------- 鍏抽棴锛氬弬鏁拌〃缂哄け + 鏃犲疄鐜� Bean锛堟ā鍧楁牴鏈笉鍦ㄤ緷璧栭噷锛� ----------------
+        SatisfactionFacade noImplFacade = new SatisfactionFacade(on, (SatisfactionApi) null, SYS_MISSING);
+        check("鍙傛暟琛ㄧ己澶便�佹棤瀹炵幇 Bean 鏃� isEnabled()", "false", String.valueOf(noImplFacade.isEnabled()));
+
+        // 鍏抽棴鏃跺弽澶嶈皟鐢ㄤ笉搴旀姏寮傚父
+        boolean noException = true;
+        try {
+            for (int i = 0; i < 3; i++) {
+                offFacade.getSatisfactionCategories(null);
+                offFacade.getQuestionOptionRatioExport(null, null);
+                offFacade.hasSatisfactionCategory(null);
+            }
+        } catch (Exception e) {
+            noException = false;
+            System.out.println("     寮傚父: " + e);
+        }
+        check("鍏抽棴鏃跺弽澶嶈皟鐢ㄤ笉鎶涘紓甯�", "true", String.valueOf(noException));
+
+        // ---------------- 鐧诲綍閾捐矾鐨勬帴鍙f毚闇诧紙绗� 9 姝ワ級 ----------------
+        // ruoyi-system 閫氳繃 ruoyi-common 鐨� ISatisfactionCategoryService 鎸夌被鍨嬫煡鎵惧疄鐜帮紝
+        // 鍥犳闂ㄩ潰蹇呴』瀹炵幇璇ユ帴鍙o紝鎵嶈兘"鍏抽棴鏃惰繑鍥炵┖ Map 鑰屼笉褰卞搷鐧诲綍"銆�
+        check("闂ㄩ潰瀹炵幇 ISatisfactionCategoryService锛堢櫥褰曢摼璺寜鎺ュ彛鏌ユ壘锛�", "true",
+                String.valueOf(onFacade instanceof ISatisfactionCategoryService));
+        check("鎸夋帴鍙h皟鐢紙鍚敤锛夎繑鍥為潪绌�", "true",
+                String.valueOf(!((ISatisfactionCategoryService) onFacade).getSatisfactionCategories("ORG1").isEmpty()));
+        check("鎸夋帴鍙h皟鐢紙鍏抽棴锛夎繑鍥炵┖ Map 涓斾笉鎶涘紓甯�", "true",
+                String.valueOf(((ISatisfactionCategoryService) offFacade).getSatisfactionCategories("ORG1").isEmpty()));
+
+        // ---------------- 婊℃剰搴︽寚鏁帮紙鍏抽棴鏃跺繀椤绘槸鍙敤鐨勫叏闆� Map锛屼笉鑳借璋冪敤鏂� NPE锛� ----------------
+        Map<String, Object> offIndex = offFacade.getSatisfactionIndex("1", "D001", "ORG1", null, null);
+        check("鍏抽棴鏃舵弧鎰忓害鎸囨暟杩斿洖鍏ㄩ浂 mydtmzl", "0", String.valueOf(offIndex.get("mydtmzl")));
+        check("鍏抽棴鏃舵弧鎰忓害鎸囨暟杩斿洖鍏ㄩ浂 mydtbl", "0", String.valueOf(offIndex.get("mydtbl")));
+        check("鍏抽棴鏃舵弧鎰忓害鎸囨暟杩斿洖鍏ㄩ浂 wcbl", "0.0", String.valueOf(offIndex.get("wcbl")));
+        check("鍏抽棴鏃舵弧鎰忓害鎸囨暟閿綈鍏紙涓嶄細璁╄皟鐢ㄦ柟 NPE锛�", "true",
+                String.valueOf(offIndex.containsKey("mydtmzl") && offIndex.containsKey("mydtbl")
+                        && offIndex.containsKey("wcbl")));
+
+        // ---------------- 寮�鍏崇儹鏇存柊锛氭敼 sys_config 鍚庤嚜鍔ㄧ敓鏁堬紙10 绉掔紦瀛樿繃鏈燂級 ----------------
+        SatisfactionProperties hot = new SatisfactionProperties();
+        final String[] configValue = { SYS_OFF };
+        SatisfactionSwitch hotSwitch = hotSwitch(hot, configValue);
+        check("鐑洿鏂板墠锛堝弬鏁拌〃=false锛�", "false", String.valueOf(hotSwitch.isEnabled()));
+        configValue[0] = SYS_ON;
+        check("缂撳瓨鏈繃鏈熸椂浠嶈繑鍥炴棫鍊硷紙杩涚▼鍐� 10 绉掔紦瀛橈級", "false", String.valueOf(hotSwitch.isEnabled()));
+        hotSwitch.invalidateCache();
+        check("缂撳瓨澶辨晥鍚庤鍒版柊鍊硷紙鏀规垚 true 绔嬪嵆鐢熸晥锛�", "true", String.valueOf(hotSwitch.isEnabled()));
+
+        // ---------------- 鍏抽棴鏃惰皟鐢ㄦ弧鎰忓害绔偣蹇呴』鎷垮埌銆屾槑纭敊璇�嶈�屼笉鏄� 404 ----------------
+        SatisfactionAvailabilityInterceptor offInterceptor =
+                new SatisfactionAvailabilityInterceptor(offFacade.getSatisfactionSwitch());
+        SatisfactionAvailabilityInterceptor onInterceptor =
+                new SatisfactionAvailabilityInterceptor(onFacade.getSatisfactionSwitch());
+
+        check("鍚敤鏃舵嫤鎴櫒鏀捐", "true", String.valueOf(onInterceptor.preHandle(null, null, null)));
+
+        String thrown = "(鏈姏寮傚父)";
+        try {
+            offInterceptor.preHandle(null, null, null);
+        } catch (Exception e) {
+            thrown = e.getClass().getName() + "|" + e.getMessage();
+        }
+        check("鍏抽棴鏃舵嫤鎴櫒鎶� ServiceException", "true",
+                String.valueOf(thrown.startsWith("com.ruoyi.common.exception.ServiceException|")));
+        check("鍏抽棴鏃堕敊璇彁绀烘槑纭紙鍚� satisfaction.enabled=false 涓庢帴鍙d笉鍙敤锛�", "true",
+                String.valueOf(thrown.contains("satisfaction.enabled=false") && thrown.contains("鎺ュ彛涓嶅彲鐢�")));
+
+        // 琚嫤鎴殑绔偣蹇呴』鏄湰妯″潡鐨� 17 涓紝涓斾笉鑳界鍒伴殢璁�/鍏跺畠涓氬姟鐨勮矾寰�
+        List<String> guarded = SatisfactionWebConfig.guardedPathPatterns();
+        check("鍙楀紑鍏充繚鎶ょ殑璺緞鏁伴噺 = 1 涓�氶厤 + 5 涓悓寰� + 1 涓紓甯搁厤缃 = 7 鏉¤鍒�", "7",
+                String.valueOf(guarded.size()));
+        check("鍙椾繚鎶よ矾寰勪笉鍚殢璁垮鍑虹瓑鍏跺畠涓氬姟绔偣", "false",
+                String.valueOf(guarded.contains("/smartor/serviceSubtask/patItemExport")
+                        || guarded.contains("/smartor/serviceSubtask/getSfStatisticsJoy")
+                        || guarded.contains("/smartor/subtaskAnswer/saveMYDQuestionAnswer")));
+
+        System.out.println();
+        if (failed == 0) {
+            System.out.println("===== 婊℃剰搴﹀紑鍏充笌闂ㄩ潰鏍¢獙鍏ㄩ儴閫氳繃 =====");
+        } else {
+            System.out.println("===== 婊℃剰搴﹀紑鍏充笌闂ㄩ潰鏍¢獙澶辫触 " + failed + " 椤� =====");
+            System.exit(1);
+        }
+    }
+
+    /**
+     * 閫犱竴涓�寋@code sys_config} 鐨勫�煎彲浠ヨ涓�旀敼鎺夈�嶇殑寮�鍏筹紝鐢ㄤ簬楠岃瘉缂撳瓨涓庣儹鏇存柊銆�
+     */
+    private static SatisfactionSwitch hotSwitch(SatisfactionProperties properties, final String[] configValue) {
+        return new SatisfactionSwitch(properties, new org.springframework.beans.factory.ObjectProvider<com.ruoyi.common.core.service.IConfigService>() {
+            private final com.ruoyi.common.core.service.IConfigService service =
+                    configKey -> configValue[0];
+
+            @Override
+            public com.ruoyi.common.core.service.IConfigService getObject(Object... args) {
+                return service;
+            }
+
+            @Override
+            public com.ruoyi.common.core.service.IConfigService getIfAvailable() {
+                return service;
+            }
+
+            @Override
+            public com.ruoyi.common.core.service.IConfigService getIfUnique() {
+                return service;
+            }
+
+            @Override
+            public com.ruoyi.common.core.service.IConfigService getObject() {
+                return service;
+            }
+
+            @Override
+            public java.util.stream.Stream<com.ruoyi.common.core.service.IConfigService> stream() {
+                return java.util.stream.Stream.of(service);
+            }
+        });
+    }
+
+    private static void check(String key, String expected, String actual) {
+        boolean ok = expected.equals(actual);
+        if (!ok) {
+            failed++;
+        }
+        System.out.println((ok ? "[OK]   " : "[FAIL] ") + key + "  鏈熸湜=" + expected + "  瀹為檯=" + actual);
+    }
+}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysUserServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysUserServiceImpl.java
index 8a64f61..20f6318 100644
--- a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysUserServiceImpl.java
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysUserServiceImpl.java
@@ -16,6 +16,7 @@
 import com.ruoyi.system.service.ISysConfigService;
 import com.ruoyi.system.service.ISysUserService;
 import com.ruoyi.common.core.service.IUserExtInfoService;
+import com.ruoyi.common.core.service.ISatisfactionCategoryService;
 import com.ruoyi.system.mapper.SysUserDeptMapper;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -314,12 +315,43 @@
             }
             sysUser.setBelongDepts(list);
             // 璁剧疆婊℃剰搴﹀垎绫伙紙璧扮紦瀛橈級
-            sysUser.setSatisfactionCategories(userExtInfoService.getSatisfactionCategories(sysUser.getOrgid()));
+            // 婊℃剰搴︽ā鍧楀寲鍚庯細涓嶅啀缁忕敱 smartor 鐨� IUserExtInfoService锛�
+            // 鏀逛负鎸� ruoyi-common 鐨� ISatisfactionCategoryService 鎺ュ彛鏌ユ壘瀹炵幇
+            // 锛堝疄鐜板湪 ruoyi-satisfaction锛岀敱甯搁┗闂ㄩ潰淇濊瘉"妯″潡鍏抽棴/缂哄け鏃惰繑鍥炵┖ Map"锛�
+            sysUser.setSatisfactionCategories(loadSatisfactionCategories(sysUser.getOrgid()));
         }
         return sysUser;
     }
 
     /**
+     * 瀹夊叏鍦拌幏鍙栨弧鎰忓害鍒嗙被銆�
+     * <p>
+     * 婊℃剰搴︽ā鍧楀彲浠ヨ閰嶇疆鍏抽棴锛坽@code sys_config} 鍙傛暟 {@code satisfaction.enabled=false}锛夛紝涔熷彲浠ユ暣浣撲笉鍦ㄤ緷璧栭噷锛�
+     * 杩欎袱绉嶆儏鍐典笅閮藉繀椤�<b>涓嶅奖鍝嶇櫥褰曚富娴佺▼</b>锛屽洜姝よ繖閲岋細
+     * <ul>
+     *   <li>瀹瑰櫒閲屾病鏈� {@code ISatisfactionCategoryService} 瀹炵幇鏃惰繑鍥炵┖ Map锛�</li>
+     *   <li>璋冪敤杩囩▼涓姏浠讳綍寮傚父涔熷彧璁版棩蹇楀苟杩斿洖绌� Map銆�</li>
+     * </ul>
+     *
+     * @param orgid 鏈烘瀯ID
+     * @return 婊℃剰搴﹀垎绫伙紱涓嶅彲鐢ㄦ椂杩斿洖绌� Map锛堜笉浼氳繑鍥� null锛�
+     */
+    private Map<String, Object> loadSatisfactionCategories(String orgid) {
+        try {
+            ISatisfactionCategoryService satisfactionCategoryService =
+                    SpringUtils.getBean(ISatisfactionCategoryService.class);
+            if (satisfactionCategoryService == null) {
+                return Collections.emptyMap();
+            }
+            Map<String, Object> categories = satisfactionCategoryService.getSatisfactionCategories(orgid);
+            return categories == null ? Collections.emptyMap() : categories;
+        } catch (Exception e) {
+            log.warn("鑾峰彇婊℃剰搴﹀垎绫诲け璐ワ紝宸查檷绾т负绌虹粨鏋滐紙涓嶅奖鍝嶇櫥褰曪級銆俹rgid={}", orgid, e);
+            return Collections.emptyMap();
+        }
+    }
+
+    /**
      * 鎸塪eptCode鍘婚噸
      */
     private List<SysUserDept> distinctByDeptCode(List<SysUserDept> list) {
diff --git a/smartor/pom.xml b/smartor/pom.xml
index 6bac502..e73057f 100644
--- a/smartor/pom.xml
+++ b/smartor/pom.xml
@@ -160,6 +160,15 @@
             <groupId>com.ruoyi</groupId>
             <artifactId>ruoyi-message</artifactId>
         </dependency>
+        <!--
+            婊℃剰搴︽ā鍧楋細smartor 鍙�氳繃瀹冪殑瀵瑰闂ㄩ潰 SatisfactionFacade 鍙栨弧鎰忓害鏁版嵁
+            锛堥殢璁垮鍑� patItemExport銆侀殢璁跨粺璁� getSfStatisticsJoy锛夛紝涓嶇洿鎺ヤ緷璧栧叾鍐呴儴 Service/Mapper銆�
+            娉ㄦ剰锛歳uoyi-satisfaction 鍙緷璧� ruoyi-common锛屽洜姝や笉浼氫笌鏈ā鍧楀舰鎴愬惊鐜緷璧栥��
+        -->
+        <dependency>
+            <groupId>com.ruoyi</groupId>
+            <artifactId>ruoyi-satisfaction</artifactId>
+        </dependency>
     </dependencies>
 
 
diff --git a/smartor/src/main/java/com/smartor/domain/PatMedOuthospError.java b/smartor/src/main/java/com/smartor/domain/PatMedOuthospError.java
new file mode 100644
index 0000000..e126d93
--- /dev/null
+++ b/smartor/src/main/java/com/smartor/domain/PatMedOuthospError.java
@@ -0,0 +1,83 @@
+package com.smartor.domain;
+
+import com.ruoyi.common.core.domain.BaseEntity;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.util.Date;
+
+/**
+ * 鐪佺珛鍚屽痉闂ㄦ�ヨ瘖閲囬泦澶辫触璁板綍瀵硅薄 pat_med_outhosp_error
+ * <p>
+ * 闂ㄦ�ヨ瘖鏁版嵁閲囬泦鏃讹紝鍗曟潯璁板綍杞崲澶辫触銆佹偅鑰呮。妗堝鐞嗗け璐ユ垨鍏ュ簱澶辫触锛屼笉鍐嶄腑鏂暣杞噰闆嗭紝
+ * 鑰屾槸鎶婂師濮嬫暟鎹拰寮傚父淇℃伅鍐欒繘杩欏紶琛ㄤ氦缁欎汉宸ュ鐞嗭紝淇濊瘉閲囬泦杩涘害涓嶈鍗曟潯鑴忔暟鎹樆鏂��
+ * 鍚屼竴鏉¤褰曞悓涓�绫婚敊璇彧淇濈暀涓�琛岋紙鍞竴绱㈠紩 uk_serialnum_type锛夛紝閲嶅澶辫触绱姞 fail_count銆�
+ *
+ * @author smartor
+ */
+@Data
+@ApiModel("闂ㄦ�ヨ瘖閲囬泦澶辫触璁板綍")
+public class PatMedOuthospError extends BaseEntity {
+    private static final long serialVersionUID = 1L;
+
+    /** 涓婚敭ID */
+    @ApiModelProperty("涓婚敭ID")
+    private Long id;
+
+    /** 璇婄枟璁板綍鍙凤紙healthcareRecordNo锛夛紝浜哄伐閲嶆帹鏃舵寜杩欎釜瀹氫綅 */
+    @ApiModelProperty("璇婄枟璁板綍鍙�")
+    private String serialnum;
+
+    /** 闂ㄨ瘖鍙� */
+    @ApiModelProperty("闂ㄨ瘖鍙�")
+    private String outhospno;
+
+    /** 鎮h�呭鍚� */
+    @ApiModelProperty("鎮h�呭鍚�")
+    private String patname;
+
+    /** 鐥呭巻鍙� */
+    @ApiModelProperty("鐥呭巻鍙�")
+    private String patno;
+
+    /** 闄㈠尯ID */
+    @ApiModelProperty("闄㈠尯ID")
+    private String campusid;
+
+    /** 鏈烘瀯ID */
+    @ApiModelProperty("鏈烘瀯ID")
+    private String orgid;
+
+    /** 灏辫瘖鏃堕棿 */
+    @ApiModelProperty("灏辫瘖鏃堕棿")
+    private Date admitdate;
+
+    /** 閲囬泦鏃ユ湡 yyyy-MM-dd */
+    @ApiModelProperty("閲囬泦鏃ユ湡")
+    private String gatherDate;
+
+    /** 閿欒绫诲瀷锛欳ONVERT-鏁版嵁杞崲澶辫触 ARCHIVE-鎮h�呮。妗堝鐞嗗け璐� INSERT-鍏ュ簱澶辫触 */
+    @ApiModelProperty("閿欒绫诲瀷")
+    private String errorType;
+
+    /** 寮傚父淇℃伅 */
+    @ApiModelProperty("寮傚父淇℃伅")
+    private String errorMsg;
+
+    /** 鎺ュ彛杩斿洖鐨勫師濮嬭褰旿SON锛屼汉宸ヤ慨澶嶅悗鍙嵁姝ら噸鎺� */
+    @ApiModelProperty("鍘熷璁板綍JSON")
+    private String rawData;
+
+    /** 绱澶辫触娆℃暟 */
+    @ApiModelProperty("绱澶辫触娆℃暟")
+    private Integer failCount;
+
+    /** 澶勭悊鐘舵�侊細0寰呭鐞� 1宸插鐞� 2宸插拷鐣� */
+    @ApiModelProperty("澶勭悊鐘舵��")
+    private String handleStatus;
+
+    /** 澶勭悊澶囨敞 */
+    @ApiModelProperty("澶勭悊澶囨敞")
+    private String handleRemark;
+}
diff --git a/smartor/src/main/java/com/smartor/mapper/IvrLibaTemplateScriptMapper.java b/smartor/src/main/java/com/smartor/mapper/IvrLibaTemplateScriptMapper.java
index 772d4c2..14f925b 100644
--- a/smartor/src/main/java/com/smartor/mapper/IvrLibaTemplateScriptMapper.java
+++ b/smartor/src/main/java/com/smartor/mapper/IvrLibaTemplateScriptMapper.java
@@ -1,7 +1,6 @@
 package com.smartor.mapper;
 
 import com.smartor.domain.IvrLibaTemplateScript;
-import com.smartor.domain.VO.PatSatisfactionReqVO;
 import org.apache.ibatis.annotations.Mapper;
 import org.apache.ibatis.annotations.Param;
 
diff --git a/smartor/src/main/java/com/smartor/mapper/PatMedOuthospErrorMapper.java b/smartor/src/main/java/com/smartor/mapper/PatMedOuthospErrorMapper.java
new file mode 100644
index 0000000..96a4533
--- /dev/null
+++ b/smartor/src/main/java/com/smartor/mapper/PatMedOuthospErrorMapper.java
@@ -0,0 +1,39 @@
+package com.smartor.mapper;
+
+import com.smartor.domain.PatMedOuthospError;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.List;
+
+/**
+ * 闂ㄦ�ヨ瘖閲囬泦澶辫触璁板綍Mapper鎺ュ彛
+ *
+ * @author smartor
+ */
+@Mapper
+public interface PatMedOuthospErrorMapper {
+
+    /**
+     * 鍐欏叆閲囬泦澶辫触璁板綍锛堝悓涓�鏉¤褰曞悓涓�绫婚敊璇彧淇濈暀涓�琛岋紝閲嶅澶辫触绱姞 fail_count锛�
+     *
+     * @param error 澶辫触璁板綍
+     * @return 缁撴灉
+     */
+    int insertPatMedOuthospError(PatMedOuthospError error);
+
+    /**
+     * 鏌ヨ閲囬泦澶辫触璁板綍鍒楄〃锛堜汉宸ュ鐞嗙敤锛�
+     *
+     * @param error 鏌ヨ鏉′欢锛坔andleStatus / errorType / serialnum / gatherDate锛�
+     * @return 澶辫触璁板綍闆嗗悎
+     */
+    List<PatMedOuthospError> selectPatMedOuthospErrorList(PatMedOuthospError error);
+
+    /**
+     * 浜哄伐澶勭悊锛氭洿鏂板鐞嗙姸鎬佸拰澶囨敞
+     *
+     * @param error 澶辫触璁板綍
+     * @return 缁撴灉
+     */
+    int updateHandleStatus(PatMedOuthospError error);
+}
diff --git a/smartor/src/main/java/com/smartor/mapper/PatSatisfactionMapper.java b/smartor/src/main/java/com/smartor/mapper/PatSatisfactionMapper.java
deleted file mode 100644
index f0bcdf9..0000000
--- a/smartor/src/main/java/com/smartor/mapper/PatSatisfactionMapper.java
+++ /dev/null
@@ -1,64 +0,0 @@
-package com.smartor.mapper;
-
-import com.smartor.domain.PatSatisfaction;
-import org.apache.ibatis.annotations.Mapper;
-
-import java.util.List;
-
-/**
- * 鎮h�呮弧鎰忓害Mapper鎺ュ彛
- *
- * @author lihu
- * @date 2025-06-30
- */
-@Mapper
-public interface PatSatisfactionMapper
-{
-    /**
-     * 鏌ヨ鎮h�呮弧鎰忓害
-     *
-     * @param id 鎮h�呮弧鎰忓害涓婚敭
-     * @return 鎮h�呮弧鎰忓害
-     */
-    public PatSatisfaction selectPatSatisfactionById(Long id);
-
-    /**
-     * 鏌ヨ鎮h�呮弧鎰忓害鍒楄〃
-     *
-     * @param patSatisfaction 鎮h�呮弧鎰忓害
-     * @return 鎮h�呮弧鎰忓害闆嗗悎
-     */
-    public List<PatSatisfaction> selectPatSatisfactionList(PatSatisfaction patSatisfaction);
-
-    /**
-     * 鏂板鎮h�呮弧鎰忓害
-     *
-     * @param patSatisfaction 鎮h�呮弧鎰忓害
-     * @return 缁撴灉
-     */
-    public int insertPatSatisfaction(PatSatisfaction patSatisfaction);
-
-    /**
-     * 淇敼鎮h�呮弧鎰忓害
-     *
-     * @param patSatisfaction 鎮h�呮弧鎰忓害
-     * @return 缁撴灉
-     */
-    public int updatePatSatisfaction(PatSatisfaction patSatisfaction);
-
-    /**
-     * 鍒犻櫎鎮h�呮弧鎰忓害
-     *
-     * @param id 鎮h�呮弧鎰忓害涓婚敭
-     * @return 缁撴灉
-     */
-    public int deletePatSatisfactionById(Long id);
-
-    /**
-     * 鎵归噺鍒犻櫎鎮h�呮弧鎰忓害
-     *
-     * @param ids 闇�瑕佸垹闄ょ殑鏁版嵁涓婚敭闆嗗悎
-     * @return 缁撴灉
-     */
-    public int deletePatSatisfactionByIds(Long[] ids);
-}
diff --git a/smartor/src/main/java/com/smartor/mapper/ServiceSubtaskDetailMapper.java b/smartor/src/main/java/com/smartor/mapper/ServiceSubtaskDetailMapper.java
index 7161470..dff42ec 100644
--- a/smartor/src/main/java/com/smartor/mapper/ServiceSubtaskDetailMapper.java
+++ b/smartor/src/main/java/com/smartor/mapper/ServiceSubtaskDetailMapper.java
@@ -4,7 +4,6 @@
 import com.smartor.domain.ServiceSubtaskDetail;
 import com.smartor.domain.ServiceSubtaskDetailTarget;
 import com.smartor.domain.ServiceSubtaskDetailVO;
-import com.smartor.domain.entity.SatisfactionStatQuery;
 import org.apache.ibatis.annotations.Mapper;
 import org.apache.ibatis.annotations.Param;
 
@@ -28,40 +27,6 @@
     public ServiceSubtaskDetail selectServiceSubtaskDetailByCalldetailid(String calldetailid);
 
     public List<ServiceSubtaskDetailDTO> queryFillCount(@Param("subids") List<Long> subids, @Param("questiontext") String questiontext);
-
-    /**
-     * 婊℃剰搴︽煴鐘跺浘锛氫竴娆℃�х粺璁℃湰娆¤姹傚叏閮ㄦā鏉跨殑濉姤閲�
-     * 锛堟寜 libtemplateid + questiontext + score 鍒嗙粍锛屽彧鍥炰紶鍒嗗�间笌鏉℃暟锛屼笉鍥炰紶鏄庣粏琛岋級
-     * <p>
-     * 鏇夸唬 queryFillCount 鐨� N+1 鍐欐硶锛岃 PatSatisfactionServiceImpl#satisfactionGraph
-     *
-     * @param query 鑱氬悎鏌ヨ鏉′欢
-     * @return 姣忚鍚� libtemplateid銆乹uestiontext銆乻core銆乧nt
-     */
-    public List<Map<String, Object>> selectSatisfactionFillStat(SatisfactionStatQuery query);
-
-    /**
-     * 婊℃剰搴︾粺璁★細涓�娆℃�х粺璁℃湰娆¤姹傚叏閮ㄩ棶棰樼殑濉姤閲忥紙鎸� 妯℃澘+闂+matchedtext+score 鍒嗙粍锛�
-     * <p>
-     * 鐢ㄤ簬 statistics / statisticsByDimension 鐨勩�屽悇閫夐」鏁伴噺鍙婂崰姣斻�嶏紝鏇夸唬閫愪釜闂鎵ц鐨�
-     * queryFillCount锛堥偅涓�姝ヤ細鍏堟煡鍑哄嚑涓囦釜 sub_id 鍐嶆嫾杩� IN(...)锛屽苟鎶婄害鍗佷竾琛屾槑缁嗕紶鍥� Java锛夈��
-     *
-     * @param query 鑱氬悎鏌ヨ鏉′欢
-     * @return 姣忚鍚� libtemplateid銆乹uestiontext銆乵atchedtext銆乻core銆乧nt銆乼argetvalue
-     */
-    public List<Map<String, Object>> selectSatisfactionFillStatByMatchedtext(SatisfactionStatQuery query);
-
-    /**
-     * 婊℃剰搴︽煴鐘跺浘锛氫竴娆℃�х粺璁℃湰娆¤姹傚叏閮ㄦā鏉跨殑銆屽洖鏀朵汉鏁般��
-     * 锛堟寜 libtemplateid 鍒嗙粍锛宑ount(distinct sub_id)锛氬悓涓�涓汉绛斾簡澶氶亾棰樺彧绠椾竴浜猴級
-     * <p>
-     * satisfactionGraph / satisfactionGraphDimension 鐨勫洖鏀朵汉鏁板繀椤绘槸浜烘暟鍙e緞锛�
-     * 涓嶈兘鍍� selectSatisfactionFillStat 閭f牱鎸夐鐩眹鎬荤瓟妗堣鏁帮紙閭f槸棰橀噺鍙e緞锛�
-     *
-     * @param query 鑱氬悎鏌ヨ鏉′欢
-     * @return 姣忚鍚� libtemplateid銆乧nt锛堣妯℃澘宸插洖鏀剁殑浠芥暟/浜烘暟锛�
-     */
-    public List<Map<String, Object>> selectSatisfactionFillPersonCount(SatisfactionStatQuery query);
 
     /**
      * 鎸� scriptid 鎵归噺缁熻濉啓璁板綍鏁帮紙涓�娆� SQL 鏇夸唬閫愪釜 scriptid 鏌ユ槑缁嗭級
@@ -123,7 +88,6 @@
      */
     public int deleteServiceSubtaskDetailByCalldetailids(String[] calldetailids);
 
-
     /**
      * 鎸囨爣缁熻
      *
@@ -131,19 +95,4 @@
      * @return 鎸囨爣缁熻
      */
     public List<ServiceSubtaskDetailTarget> countPatByTarget(Long targetid);
-
-    /**
-     * 鏌ヨ鍚屼釜subid閲嶅鐨刣etail鏁版嵁ID鍒楄〃
-     *
-     * @return 閲嶅璁板綍ID鍒楄〃
-     */
-    public List<Long> selectRepeatDetailIds();
-
-    /**
-     * 鏍规嵁ID鍒楄〃鎵归噺鏍囪閲嶅detail鏁版嵁涓哄垹闄�
-     *
-     * @param ids 閲嶅璁板綍ID鍒楄〃
-     * @return 鏇存柊琛屾暟
-     */
-    public Integer deleteRepeatDetailByIds(@Param("ids") List<Long> ids);
 }
diff --git a/smartor/src/main/java/com/smartor/mapper/ServiceSubtaskMapper.java b/smartor/src/main/java/com/smartor/mapper/ServiceSubtaskMapper.java
index 2f0e429..443f157 100644
--- a/smartor/src/main/java/com/smartor/mapper/ServiceSubtaskMapper.java
+++ b/smartor/src/main/java/com/smartor/mapper/ServiceSubtaskMapper.java
@@ -1,18 +1,9 @@
 package com.smartor.mapper;
 
 import com.smartor.domain.*;
-import com.smartor.domain.DTO.MydTrendReqDTO;
-import com.smartor.domain.DTO.MzMydScoreRankDTO;
-import com.smartor.domain.DTO.QuestionCountKsOrBqDTO;
-import com.smartor.domain.DTO.ServiceSubtaskOptionStatDTO;
-import com.smartor.domain.VO.PatSatisfactionReqVO;
 import com.smartor.domain.VO.HeLibraryCountVO;
-import com.smartor.domain.VO.MzMydScoreRankVO;
-import com.smartor.domain.VO.QuestionCountKsOrBqVO;
 import com.smartor.domain.VO.ServiceSubtaskCotinueCountVO;
 import com.smartor.domain.entity.ServiceSubtaskEntity;
-import com.smartor.domain.entity.ServiceSubtaskSatisfactionEntity;
-import com.smartor.domain.entity.SatisfactionStatQuery;
 import org.apache.ibatis.annotations.MapKey;
 import org.apache.ibatis.annotations.Mapper;
 import org.apache.ibatis.annotations.Param;
@@ -45,7 +36,7 @@
      */
     public List<ServiceSubtask> selectServiceSubtaskList(ServiceSubtaskEntity serviceSubtaskVO);
 
-    public List<ServiceSubtask> selectSatisfactionSubtaskList(PatSatisfactionReqVO serviceSubtaskVO);
+    
 
     /**
      * 鐢ㄤ簬琛ュ伩鏌ヨ
@@ -87,11 +78,11 @@
      */
     public int countActiveByScheduleIdAndLoop(@Param("scheduleId") Long scheduleId, @Param("loopCount") Long loopCount);
 
-    public  List<MydTrendReqDTO> mydTrend(@Param("startFinishTime") Date startFinishTime, @Param("endFinishTime") Date endFinishTime, @Param("type") String type);
+    
 
-    public  List<MydTrendReqDTO> sltdMydTotalByDimension(@Param("startFinishTime") Date startFinishTime, @Param("endFinishTime") Date endFinishTime, @Param("type") String type);
+    
 
-    public  List<MydTrendReqDTO> sltdMydTotalByYQ(@Param("startFinishTime") Date startFinishTime, @Param("endFinishTime") Date endFinishTime, @Param("type") String type);
+    
 
     /**
      * 浠诲姟缁勶紙appltype=5锛夛細鎷夊彇鏌愪竴杞鐨勫叏閮� subtask锛堢敤浜庡厠闅嗗埌涓嬩竴杞級銆�
@@ -238,29 +229,11 @@
      */
     PatMedRes selectVisitCount(PatMedReq patMedReq);
 
-    List<Long> querySendCount(ServiceSubtaskSatisfactionEntity serviceSubtaskSatisfactionEntity);
+    
 
-    /**
-     * 婊℃剰搴︽煴鐘跺浘锛氫竴娆℃�х粺璁℃湰娆¤姹傚叏閮ㄦā鏉跨殑鍙戦�侀噺锛堟寜 libtemplateid 鍒嗙粍锛�
-     * <p>
-     * 鏇夸唬閫愪釜妯℃澘璋冪敤 querySendCount 鐨� N+1 鍐欐硶锛岃 PatSatisfactionServiceImpl#satisfactionGraph
-     *
-     * @param query 鑱氬悎鏌ヨ鏉′欢
-     * @return 姣忚鍚� libtemplateid銆乧nt
-     */
-    List<Map<String, Object>> selectSatisfactionSendCount(SatisfactionStatQuery query);
+    
 
-    /**
-     * 婊℃剰搴︾粺璁★細鍙煡鏈鏉′欢涓嬪疄闄呭瓨鍦ㄥ摢浜� type锛�1 璇煶 / 2 闂嵎锛�
-     * <p>
-     * 鏇夸唬 selectSatisfactionSubtaskList 鈥斺�� 鍘熸柟娉曚細鎶婃帴杩戝叏琛ㄧ殑涓婄櫨涓瓧娈垫煡鍥炴潵锛�
-     * 璋冪敤鏂瑰嵈鍙敤瀹冨垽鏂�岃闊冲垎鏀�/闂嵎鍒嗘敮鏄惁涓虹┖銆嶏紝瑙�
-     * PatSatisfactionServiceImpl#statistics / #statisticsByDimension
-     *
-     * @param patSatisfactionReqVO 鏌ヨ鏉′欢锛堜笌鍘� selectSatisfactionSubtaskList 瀹屽叏涓�鑷达級
-     * @return 鍘婚噸鍚庣殑 type 鍒楄〃锛堝彲鑳藉惈 null锛�
-     */
-    List<String> selectSatisfactionSubtaskTypes(PatSatisfactionReqVO patSatisfactionReqVO);
+    
 
     /**
      * 鎸夌梾鍖哄垎缁勭粺璁″欢缁姢鐞嗘暟閲�
@@ -291,54 +264,14 @@
      */
     List<ServiceSubtask> gethelibraryCountHyperlink(HeLibraryCountVO heLibraryCountVO);
 
-    /**
-     * 缁熻闂鐨勯�夐」鐨勭櫨鍒嗘瘮
-     *
-     * @param taskIds          浠诲姟 ID 鍒楄〃 (鐢ㄤ簬鏈嶅姟绫诲瀷 2)
-     * @param subtaskIds       瀛愪换鍔� ID 鍒楄〃锛堝彲閫夛級銆備紶浜嗗氨鍙粺璁¤繖鎵瑰瓙浠诲姟锛堝嵆鏈瀵煎嚭鐨勬偅鑰咃級锛�
-     *                         涓嶄紶鍒欑粺璁¤繖浜涗换鍔′笅鐨勫叏閮ㄧ瓟鍗凤紝淇濇寔鍘熻涓�
-     * @param startOutHospTime
-     * @param endOutHospTime
-     * @return
-     */
-    List<ServiceSubtaskDetailRatioExport> statQuestionOption(@Param("taskIds") List<Long> taskIds, @Param("startOutHospTime") Date startOutHospTime, @Param("endOutHospTime") Date endOutHospTime, @Param("startFinishTime") Date startFinishTime, @Param("endFinishTime") Date endFinishTime, @Param("scoreStart") Double scoreStart, @Param("scoreEnd") Double scoreEnd, @Param("questiontext") String questiontext, @Param("valueType") String valueType, @Param("subtaskIds") List<Long> subtaskIds);
+    
 
-    /**
-     * 瀵煎嚭銆岄棶棰樿鎯呭崰姣斻�嶄笓鐢ㄧ粺璁★紙鎸夋湰娆″鍑虹殑瀛愪换鍔$粺璁★級
-     * <p>
-     * 鏁伴噺 = 閫夋嫨璇ラ�夐」鐨勬偅鑰呮暟锛堝悓涓�鎮h�呭悓涓�棰樺彧鍙栨渶鏂颁竴鏉℃槑缁嗭級锛屽垎鏁� = 璇ラ�夐」鏄庣粏鐨� score 涔嬪拰銆�
-     *
-     * @param subtaskIds   鏈瀵煎嚭鐨勫瓙浠诲姟 ID锛堝繀浼狅紝涓虹┖鍒欐煡涓嶅埌鏁版嵁锛�
-     * @param questiontext 闂鍐呭锛堝彲閫夛紝妯$硦鍖归厤锛�
-     */
-    List<ServiceSubtaskOptionStatDTO> statMydQuestionOptionForExport(@Param("subtaskIds") List<Long> subtaskIds, @Param("questiontext") String questiontext);
+    
 
-    /**
-     * 鏍规嵁瀛愪换鍔� ID 缁熻闂鐨勯�夐」鐨勭櫨鍒嗘瘮锛堢敤浜庢弧鎰忓害缁熻 serviceType=6锛�
-     *
-     * @param subtaskIds       瀛愪换鍔� ID 鍒楄〃 (鍙兘鍗曚釜 taskid 涓嬬殑鎵�鏈夊瓙浠诲姟 id)
-     * @param startOutHospTime
-     * @param endOutHospTime
-     * @return
-     */
-    List<ServiceSubtaskDetailRatioExport> statQuestionOptionBySubtaskIds(@Param("subtaskIds") List<Long> subtaskIds, @Param("startOutHospTime") Date startOutHospTime, @Param("endOutHospTime") Date endOutHospTime, @Param("startFinishTime") Date startFinishTime, @Param("endFinishTime") Date endFinishTime, @Param("scoreStart") Double scoreStart, @Param("scoreEnd") Double scoreEnd, @Param("questiontext") String questiontext, @Param("valueType") String valueType);
+    
 
-    /**
-     * 鏍规嵁绉戝銆佺梾鍖恒�侀鐩被鍨嬬粺璁$殑绉戝銆佺梾鍖鸿瘎鍒�
-     * @param questionCountKsOrBqVO
-     * @return
-     */
-    List<QuestionCountKsOrBqDTO> statQuestionOptionByKsOrBq(QuestionCountKsOrBqVO questionCountKsOrBqVO);
+    
 
-    /**
-     * 闂ㄨ瘖婊℃剰搴︾患鍚堝緱鍒嗘帓鍚嶏紙鎸夌瀹ゆ垨鐥呭尯锛�
-     * <p>
-     * 缁煎悎婊℃剰搴� = 危(璇勫垎鏄庣粏鏉冮噸) / 璇勫垎鏄庣粏鏉℃暟 脳 100%锛�
-     * 鏉冮噸鍙彇 1銆�0.8銆�0.6銆�0.4銆�0.2 浜旀。銆�
-     *
-     * @param mzMydScoreRankVO 鏌ヨ鏉′欢锛坉imensionType銆乻erviceType銆佹椂闂村尯闂淬�佺瀹�/鐥呭尯缂栧彿銆乷rgid/campusid锛�
-     * @return 姣忎釜绉戝/鐥呭尯鐨勭患鍚堝緱鍒嗕笌椤规暟锛屽凡鎸夌患鍚堝緱鍒嗛檷搴�
-     */
-    List<MzMydScoreRankDTO> selectMzMydScoreRank(MzMydScoreRankVO mzMydScoreRankVO);
+    
 
 }
diff --git a/smartor/src/main/java/com/smartor/message/LishuiMessageConfigProviderImpl.java b/smartor/src/main/java/com/smartor/message/LishuiMessageConfigProviderImpl.java
new file mode 100644
index 0000000..3fd82ff
--- /dev/null
+++ b/smartor/src/main/java/com/smartor/message/LishuiMessageConfigProviderImpl.java
@@ -0,0 +1,87 @@
+package com.smartor.message;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.message.sms.provider.ls.LishuiMessageConfig;
+import com.ruoyi.message.sms.provider.ls.LishuiMessageConfigProvider;
+import com.ruoyi.system.domain.SysConfig;
+import com.ruoyi.system.service.ISysConfigService;
+import com.smartor.common.LSHospTokenUtil;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.collections4.CollectionUtils;
+import org.springframework.stereotype.Component;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 涓芥按鐭俊鏈烘瀯绾ч厤缃疄鐜般��
+ * <p>
+ * 缁熶竴浜嗗師鍏堜笁濂楀苟瀛樼殑鏌ヨ鏂瑰紡锛�
+ * <ul>
+ *   <li>{@code RyTask.ls 鍒嗘敮}锛氭寜鏈烘瀯鏌� {@code ls.msg.info}锛�</li>
+ *   <li>{@code ServiceSubtaskServiceImpl.smsSubTask}锛氱敤 {@code MsgLSEnum}锛堝凡搴熷純锛夛紱</li>
+ *   <li>{@code SmsRecordsServiceImpl.sendSMS}锛氬張鏄涓夊锛堟湰杞凡鏀逛负濮旀墭鍙戦�佹柟锛夈��</li>
+ * </ul>
+ * 鐜板湪鍞竴鍏ュ彛灏辨槸鏈被锛氳 {@code sys_config} 琛ㄧ殑 {@code ls.msg.info}锛堟寜鏈烘瀯锛夛紝
+ * token 璧� {@code LSHospTokenUtil}銆�
+ */
+@Slf4j
+@Component
+public class LishuiMessageConfigProviderImpl implements LishuiMessageConfigProvider {
+
+    /**
+     * 涓芥按鏈烘瀯绾ч厤缃殑閰嶇疆椤� key锛堜笌鍘熷疄鐜颁竴鑷达級
+     */
+    private static final String CONFIG_KEY = "ls.msg.info";
+
+    private final ISysConfigService configService;
+
+    public LishuiMessageConfigProviderImpl(ISysConfigService configService) {
+        this.configService = configService;
+    }
+
+    @Override
+    public LishuiMessageConfig getConfig(String orgid) {
+        if (StringUtils.isEmpty(orgid)) {
+            return null;
+        }
+        SysConfig query = new SysConfig();
+        query.setConfigKey(CONFIG_KEY);
+        query.setOrgid(orgid);
+        List<SysConfig> sysConfigs = configService.selectConfigList(query);
+        if (CollectionUtils.isEmpty(sysConfigs)) {
+            log.warn("銆怢ishuiMessageConfigProviderImpl銆戞満鏋� {} 鏈厤缃� {}", orgid, CONFIG_KEY);
+            return null;
+        }
+
+        String configValue = sysConfigs.get(0).getConfigValue();
+        Map<String, String> msgInfo = new HashMap<>();
+        if (StringUtils.isNotEmpty(configValue)) {
+            try {
+                msgInfo = new ObjectMapper().readValue(configValue, Map.class);
+            } catch (Exception e) {
+                log.error("銆怢ishuiMessageConfigProviderImpl銆戣В鏋� {} 澶辫触锛宱rgid锛歿}锛寁alue锛歿}锛宔rr锛歿}",
+                        CONFIG_KEY, orgid, configValue, e.getMessage(), e);
+                return null;
+            }
+        }
+        log.info("-----------涓芥按鐭俊閰嶇疆 map 鐨勫�间负锛歿}", msgInfo);
+
+        LishuiMessageConfig config = new LishuiMessageConfig();
+        config.setOrgid(orgid);
+        config.setAddress(msgInfo.get("address"));
+        config.setZuHuId(msgInfo.get("ZuHuID"));
+        config.setZuHuMc(msgInfo.get("ZuHuMC"));
+        config.setSendPersonId(msgInfo.get("sendPersonId"));
+        config.setSendPersonName(msgInfo.get("sendPersonName"));
+        config.setTokenUrl(msgInfo.get("tokenUrl"));
+        return config;
+    }
+
+    @Override
+    public String getToken(String orgid, String tokenUrl) {
+        return LSHospTokenUtil.getToken(orgid, tokenUrl);
+    }
+}
diff --git a/smartor/src/main/java/com/smartor/message/WechatMpConfigProviderImpl.java b/smartor/src/main/java/com/smartor/message/WechatMpConfigProviderImpl.java
new file mode 100644
index 0000000..b90c92c
--- /dev/null
+++ b/smartor/src/main/java/com/smartor/message/WechatMpConfigProviderImpl.java
@@ -0,0 +1,89 @@
+package com.smartor.message;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.ruoyi.common.enums.WxGZHEnum;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.message.wechat.WechatMpConfig;
+import com.ruoyi.message.wechat.WechatMpConfigProvider;
+import com.ruoyi.system.service.ISysConfigService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+
+import java.util.Map;
+
+/**
+ * 寰俊鍏紬鍙锋満鏋勭骇閰嶇疆瀹炵幇锛氭敹鍙e師鍏堟暎钀藉湪涓変釜绫婚噷鐨� {@code WxGZHEnum.getDescByCode(orgid)}
+ * 璋冪敤锛坽@code RyTask} / {@code RedisMqReceiver} / {@code ServiceSubtaskServiceImpl}锛夈��
+ * <p>
+ * 閰嶇疆婧愪紭鍏堢骇锛�
+ * <ol>
+ *   <li>{@code sys_config} 琛� key = {@code wx.mp.config.<orgid>}锛屽�煎舰濡�
+ *       {@code {"appid":"xx","appSecret":"yy","address":"zz"}} 鈥斺�� 渚夸簬鏂版満鏋勪笉鏀逛唬鐮佹帴鍏ワ紱</li>
+ *   <li>浠g爜鍐� {@link WxGZHEnum} 鏋氫妇锛堜繚鎸佸師鏈夐厤缃簮涓嶅彉锛岃涓轰笌绾夸笂涓�鑷达級銆�</li>
+ * </ol>
+ * 鍚屾椂鎶娿�屾寜 List 涓嬫爣鍙� appid/appSecret/address銆嶈繖绉嶈剢寮卞啓娉曟敼鎴愪簡鏄惧紡瀛楁鍙栧�笺��
+ */
+@Slf4j
+@Component
+public class WechatMpConfigProviderImpl implements WechatMpConfigProvider {
+
+    /**
+     * 鏈烘瀯绾у叕浼楀彿閰嶇疆鐨勯厤缃」 key 鍓嶇紑
+     */
+    private static final String CONFIG_KEY_PREFIX = "wx.mp.config.";
+
+    private final ISysConfigService configService;
+
+    public WechatMpConfigProviderImpl(ISysConfigService configService) {
+        this.configService = configService;
+    }
+
+    @Override
+    public WechatMpConfig getConfig(String orgid) {
+        if (StringUtils.isEmpty(orgid)) {
+            return null;
+        }
+
+        //浼樺厛鍙栨暟鎹簱閰嶇疆锛堟敮鎸佹柊鏈烘瀯锛�
+        WechatMpConfig fromDb = readFromDb(orgid);
+        if (fromDb != null && fromDb.isComplete()) {
+            return fromDb;
+        }
+
+        WechatMpConfig config = new WechatMpConfig();
+        config.setOrgid(orgid);
+        for (WxGZHEnum item : WxGZHEnum.values()) {
+            if (orgid.equals(item.getOrgid())) {
+                config.setAppid(item.getAppid());
+                config.setAppSecret(item.getAppSecret());
+                config.setAddress(item.getAddress());
+                break;
+            }
+        }
+
+        if (!config.isComplete()) {
+            log.warn("銆怶echatMpConfigProviderImpl銆戞満鏋� {} 鐨勫叕浼楀彿閰嶇疆涓嶅畬鏁达細{}", orgid, config);
+        }
+        return config;
+    }
+
+    private WechatMpConfig readFromDb(String orgid) {
+        try {
+            String value = configService.selectConfigByKey(CONFIG_KEY_PREFIX + orgid, orgid);
+            if (StringUtils.isEmpty(value)) {
+                return null;
+            }
+            Map<String, String> map = new ObjectMapper().readValue(value, Map.class);
+            WechatMpConfig config = new WechatMpConfig();
+            config.setOrgid(orgid);
+            config.setAppid(map.get("appid"));
+            config.setAppSecret(map.get("appSecret"));
+            config.setAddress(map.get("address"));
+            return config;
+        } catch (Exception e) {
+            log.warn("銆怶echatMpConfigProviderImpl銆戣鍙� {} 閰嶇疆澶辫触锛宱rgid锛歿}锛宔rr锛歿}",
+                    CONFIG_KEY_PREFIX + orgid, orgid, e.getMessage());
+            return null;
+        }
+    }
+}
diff --git a/smartor/src/main/java/com/smartor/service/IPatSatisfactionService.java b/smartor/src/main/java/com/smartor/service/IPatSatisfactionService.java
deleted file mode 100644
index 9282b42..0000000
--- a/smartor/src/main/java/com/smartor/service/IPatSatisfactionService.java
+++ /dev/null
@@ -1,100 +0,0 @@
-package com.smartor.service;
-
-import java.util.List;
-import java.util.Map;
-
-import com.smartor.domain.PatSatisfaction;
-import com.smartor.domain.VO.PatSatisfactionReqVO;
-import com.smartor.domain.VO.PatSatisfactionResVO;
-
-/**
- * 鎮h�呮弧鎰忓害Service鎺ュ彛
- *
- * @author lihu
- * @date 2025-06-30
- */
-public interface IPatSatisfactionService
-{
-    /**
-     * 鏌ヨ鎮h�呮弧鎰忓害
-     *
-     * @param id 鎮h�呮弧鎰忓害涓婚敭
-     * @return 鎮h�呮弧鎰忓害
-     */
-    public PatSatisfaction selectPatSatisfactionById(Long id);
-
-    /**
-     * 鏌ヨ鎮h�呮弧鎰忓害鍒楄〃
-     *
-     * @param patSatisfaction 鎮h�呮弧鎰忓害
-     * @return 鎮h�呮弧鎰忓害闆嗗悎
-     */
-    public List<PatSatisfaction> selectPatSatisfactionList(PatSatisfaction patSatisfaction);
-
-    /**
-     * 鏂板鎮h�呮弧鎰忓害
-     *
-     * @param patSatisfaction 鎮h�呮弧鎰忓害
-     * @return 缁撴灉
-     */
-    public int insertPatSatisfaction(PatSatisfaction patSatisfaction);
-
-    /**
-     * 鎵归噺鏂板鎮h�呮弧鎰忓害
-     *
-     * @param patSatisfactions 鎮h�呮弧鎰忓害
-     * @return 缁撴灉
-     */
-    public int batchInsert(List<PatSatisfaction> patSatisfactions);
-
-    /**
-     * 淇敼鎮h�呮弧鎰忓害
-     *
-     * @param patSatisfaction 鎮h�呮弧鎰忓害
-     * @return 缁撴灉
-     */
-    public int updatePatSatisfaction(PatSatisfaction patSatisfaction);
-
-    /**
-     * 鎵归噺鍒犻櫎鎮h�呮弧鎰忓害
-     *
-     * @param ids 闇�瑕佸垹闄ょ殑鎮h�呮弧鎰忓害涓婚敭闆嗗悎
-     * @return 缁撴灉
-     */
-    public int deletePatSatisfactionByIds(Long[] ids);
-
-    /**
-     * 鍒犻櫎鎮h�呮弧鎰忓害淇℃伅
-     *
-     * @param id 鎮h�呮弧鎰忓害涓婚敭
-     * @return 缁撴灉
-     */
-    public int deletePatSatisfactionById(Long id);
-
-    /**
-     * 鎮h�呮弧鎰忓害缁熻
-     *
-     * @param patSatisfactionReqVO 鎮h�呮弧鎰忓害缁熻鏉′欢
-     * @return 鎮h�呮弧鎰忓害缁熻缁撴灉
-     */
-    public PatSatisfactionResVO statistics( PatSatisfactionReqVO patSatisfactionReqVO);
-
-    /**
-     * 鎮h�呮弧鎰忓害缁熻锛堢淮搴︼級
-     *
-     * @param patSatisfactionReqVO 鎮h�呮弧鎰忓害缁熻鏉′欢
-     * @return 鎮h�呮弧鎰忓害缁熻缁撴灉
-     */
-    public PatSatisfactionResVO statisticsByDimension( PatSatisfactionReqVO patSatisfactionReqVO);
-
-
-    /**
-     * 鎮h�呮弧鎰忓害鏌辩姸鍥句俊鎭�
-     *
-     * @param patSatisfactionReqVO 鎮h�呮弧鎰忓害鏌辩姸鍥句俊鎭�
-     * @return 鎮h�呮弧鎰忓害鏌辩姸鍥句俊鎭�
-     */
-    public Map<String, Object> satisfactionGraph(PatSatisfactionReqVO patSatisfactionReqVO);
-
-    public Map<String, Object> satisfactionGraphDimension(PatSatisfactionReqVO patSatisfactionReqVO);
-}
diff --git a/smartor/src/main/java/com/smartor/service/IServiceSubtaskDetailTraceService.java b/smartor/src/main/java/com/smartor/service/IServiceSubtaskDetailTraceService.java
index 3391fbd..08a7075 100644
--- a/smartor/src/main/java/com/smartor/service/IServiceSubtaskDetailTraceService.java
+++ b/smartor/src/main/java/com/smartor/service/IServiceSubtaskDetailTraceService.java
@@ -6,7 +6,6 @@
 import com.smartor.domain.DTO.DetailTraceDealDTO;
 import com.smartor.domain.ServiceSubtaskDetailTrace;
 import com.smartor.domain.VO.DetailTraceDealVO;
-import com.smartor.domain.VO.SatisfactionExceptConfigQuestionReqVO;
 import com.smartor.domain.VO.ServiceSubtaskDetailTraceVO;
 import org.springframework.web.bind.annotation.RequestBody;
 
@@ -73,10 +72,5 @@
      */
     public int deleteServiceSubtaskDetailTtraceById(Long id);
 
-    /**
-     * 鑾峰彇婊℃剰搴﹀紓甯搁厤缃鐩�
-     * @param questionReqVO
-     * @return
-     */
-    public Map<String, Object> getSatisfactionExceptConfigQuestion(SatisfactionExceptConfigQuestionReqVO questionReqVO);
+    
 }
diff --git a/smartor/src/main/java/com/smartor/service/IServiceSubtaskService.java b/smartor/src/main/java/com/smartor/service/IServiceSubtaskService.java
index 53b3c69..2759ec2 100644
--- a/smartor/src/main/java/com/smartor/service/IServiceSubtaskService.java
+++ b/smartor/src/main/java/com/smartor/service/IServiceSubtaskService.java
@@ -2,10 +2,6 @@
 
 import com.ruoyi.common.core.domain.AjaxResult;
 import com.smartor.domain.*;
-import com.smartor.domain.DTO.MydTrendReqDTO;
-import com.smartor.domain.DTO.MzMydScoreRankDTO;
-import com.smartor.domain.DTO.QuestionCountKsOrBqDTO;
-import com.smartor.domain.DTO.QuestionResultDTO;
 import com.smartor.domain.ServiceSubtask;
 import com.smartor.domain.VO.*;
 import com.smartor.domain.entity.ServiceSubtaskEntity;
@@ -140,14 +136,7 @@
 
     public PhoneCallBackSHVO processResust(PhoneCallResultYQVO phoneCallReqYQVO);
 
-    /**
-     * 闂缁撴灉鍗犳瘮
-     *
-     * @param QuestionResultVO
-     * @param serviceSubtaskRes
-     * @return
-     */
-    public List<ServiceSubtaskDetailRatioExport> getDetailRatio(SltdMydTotalVO sltdMydTotalVO, List<ServiceSubtaskRes> serviceSubtaskRes);
+    
 
 
     /**
@@ -222,57 +211,21 @@
 
     Map<String, Object> smsSubTask(Long subid);
 
-    /**
-     * 缁熻闂鐨勯�夐」鐨勭櫨鍒嗘瘮 (鐢ㄤ簬鏈嶅姟绫诲瀷 2)
-     *
-     * @param taskIds    浠诲姟 ID 鍒楄〃
-     * @param subtaskIds 瀛愪换鍔� ID 鍒楄〃锛堝彲閫夛級锛氫紶浜嗗氨鍙粺璁¤繖鎵瑰瓙浠诲姟锛堟湰娆″鍑虹殑鎮h�咃級锛屽垎姣嶄篃鐢ㄥ悓涓�鎵规暟鎹�
-     * @return
-     */
-    public List<ServiceSubtaskDetailRatioExport> statQuestionOption(List<Long> taskIds, Date startFinishTime, Date endFinishTime, Date startOutHospTime, Date endOutHospTime, String scriptContent, Double scoreStart, Double scoreEnd, String valueType, List<Long> subtaskIds);
+    
 
-    /**
-     * 瀵煎嚭銆岄棶棰樿鎯呭崰姣斻�嶄笓鐢ㄧ粺璁★紙鍙粺璁℃湰娆″鍑虹殑杩欐壒鎮h�咃級
-     * <p>
-     * 鏁伴噺 = 閫夋嫨璇ラ�夐」鐨勬偅鑰呮暟锛涙瘮渚� = 鏁伴噺 / 璇ラ浣滅瓟鎮h�呮暟锛涘垎鏁� = 璇ラ�夐」鏄庣粏 score 涔嬪拰锛�
-     * 浠诲姟鍚嶇О/浠诲姟缂栧彿 = service_task.task_name / taskid銆�
-     *
-     * @param subtaskIds   鏈瀵煎嚭鐨勫瓙浠诲姟 ID
-     * @param questiontext 闂鍐呭锛堝彲閫夛級
-     */
-    public List<ServiceSubtaskDetailRatioExport> statMydQuestionOptionForExport(List<Long> subtaskIds, String questiontext);
+    
 
-    /**
-     * 鏍规嵁瀛愪换鍔� ID 缁熻闂鐨勯�夐」鐨勭櫨鍒嗘瘮锛堢敤浜庢弧鎰忓害缁熻 serviceType=6锛�
-     *
-     * @param subtaskIds 瀛愪换鍔� ID 鍒楄〃
-     * @return
-     */
-    public List<ServiceSubtaskDetailRatioExport> statQuestionOptionBySubtaskIds(List<Long> subtaskIds, Date startOutHospTime, Date endOutHospTime, Date startFinishTime, Date endFinishTime, String scriptContent, Double scoreStart, Double scoreEnd, String valueType);
+    
 
-    public  Map<String, Map<String, List<QuestionCountKsOrBqDTO>>> statQuestionOptionByKsOrBq(QuestionCountKsOrBqVO questionCountKsOrBqVO);
+    
 
     public String scanGenerateSubtask(ServiceTask serviceTaskVo);
 
-    public List<MydTrendReqDTO> mydTrend(MydTrendReqVO mydTrendReqVO);
+    
 
-    /**
-     * 鏍规嵁鏃堕棿缁欏害杩涜瀵规瘮婊℃剰搴﹀垎鏁�
-     * @param mydTrendReqVO
-     * @return
-     */
-    public List<MydTrendReqDTO> sltdMydTotalByDimension(MydTrendReqVO mydTrendReqVO);
+    
 
     public Integer matchOptionIndex(String url, String questionText, String voiceText, List<IvrTaskTemplateTargetoption> options, String uuid);
 
-    /**
-     * 闂ㄨ瘖婊℃剰搴︾患鍚堝緱鍒嗘帓鍚嶏紙鎸夌瀹ゆ垨鐥呭尯锛�
-     * <p>
-     * 缁煎悎婊℃剰搴� = 危(璇勫垎鏄庣粏鏉冮噸) / 璇勫垎鏄庣粏鏉℃暟 脳 100%锛�
-     * 鏉冮噸鍙彇 1銆�0.8銆�0.6銆�0.4銆�0.2 浜旀。銆�
-     *
-     * @param mzMydScoreRankVO 鏌ヨ鏉′欢
-     * @return 鎺掑悕缁撴灉锛屽凡鎸夌患鍚堝緱鍒嗛檷搴忓苟濉厖鍚嶆锛堜粠 1 寮�濮嬶級
-     */
-    public List<MzMydScoreRankDTO> mzMydScoreRank(MzMydScoreRankVO mzMydScoreRankVO);
+    
 }
diff --git a/smartor/src/main/java/com/smartor/service/impl/ServiceSubtaskDetailTraceServiceImpl.java b/smartor/src/main/java/com/smartor/service/impl/ServiceSubtaskDetailTraceServiceImpl.java
index 985cbbb..f8dadef 100644
--- a/smartor/src/main/java/com/smartor/service/impl/ServiceSubtaskDetailTraceServiceImpl.java
+++ b/smartor/src/main/java/com/smartor/service/impl/ServiceSubtaskDetailTraceServiceImpl.java
@@ -10,7 +10,6 @@
 import com.smartor.domain.*;
 import com.smartor.domain.DTO.DetailTraceDealDTO;
 import com.smartor.domain.VO.DetailTraceDealVO;
-import com.smartor.domain.VO.SatisfactionExceptConfigQuestionReqVO;
 import com.smartor.domain.VO.ServiceSubtaskDetailTraceVO;
 import com.smartor.mapper.ServiceSubtaskDetailMapper;
 import com.smartor.mapper.ServiceSubtaskDetailTraceMapper;
@@ -273,79 +272,4 @@
     public int deleteServiceSubtaskDetailTtraceById(Long id) {
         return serviceSubtaskDetailTraceMapper.deleteServiceSubtaskDetailTtraceById(id);
     }
-
-    @Override
-    public Map<String, Object> getSatisfactionExceptConfigQuestion(SatisfactionExceptConfigQuestionReqVO questionReqVO) {
-        // 鍒濆鍖栫粨鏋滈泦
-        Map<String, Object> result = new HashMap<>();
-
-        if (StringUtils.isEmpty(questionReqVO.getScriptType()) || questionReqVO.getScriptType().equals("1")) {
-            List<Map<String, Object>> voicetDataList = new ArrayList<>();
-            //鑾峰彇婊℃剰搴︾殑鍒嗙被
-            IvrLibaScriptAssort ivrLibaScriptAssort = new IvrLibaScriptAssort();
-            ivrLibaScriptAssort.setType("myd");
-            if (!StringUtils.isEmpty(questionReqVO.getType())) ivrLibaScriptAssort.setType(questionReqVO.getType());
-            List<IvrLibaScriptAssortVO> ivrLibaScriptAssortVOS = iIvrLibaScriptAssortService.selectIvrLibaScriptAssortList(ivrLibaScriptAssort);
-            List<Long> assortIdList = Optional.ofNullable(ivrLibaScriptAssortVOS).filter(list -> !list.isEmpty()).map(list -> list.stream().map(IvrLibaScriptAssortVO::getId).collect(Collectors.toList())).orElse(Collections.emptyList());
-
-            //鐢ㄦ潵璁板綍婊℃剰搴︾殑鎵�鏈夐棶棰�
-            List<IvrLibaTemplateScript> ivrLibaTemplateScriptList = new ArrayList<>();
-            if (CollectionUtils.isNotEmpty(assortIdList)) {
-                for (Long assortId : assortIdList) {
-                    IvrLibaTemplateScript ivrLibaTemplateScript = new IvrLibaTemplateScript();
-                    ivrLibaTemplateScript.setScriptAssortid(assortId);
-                    List<IvrLibaTemplateScript> ivrLibaTemplateScripts = iIvrLibaTemplateScriptService.selectIvrLibaTemplateScriptList(ivrLibaTemplateScript);
-                    ivrLibaTemplateScriptList.addAll(ivrLibaTemplateScripts);
-                }
-                for (IvrLibaTemplateScript ivrLibaTemplateScript : ivrLibaTemplateScriptList) {
-                    Map<String, Object> voicetData = new HashMap<>();
-                    voicetData.put("questionId", ivrLibaTemplateScript.getId());
-                    voicetData.put("libtemplateid", ivrLibaTemplateScript.getTemplateid());
-                    voicetData.put("questionContent", ivrLibaTemplateScript.getScriptContent());
-                    voicetData.put("questionTopic", ivrLibaTemplateScript.getScriptTopic());
-                    voicetData.put("questionType", ivrLibaTemplateScript.getScriptType());
-                    voicetData.put("categoryName", ivrLibaTemplateScript.getScriptAssortname());
-                    voicetData.put("categoryId", ivrLibaTemplateScript.getScriptAssortid());
-                    voicetDataList.add(voicetData);
-                }
-            }
-            //璇煶鏁版嵁杩斿洖闆嗗悎
-            result.put("voiceDataList", voicetDataList);
-        }
-
-        if (StringUtils.isEmpty(questionReqVO.getScriptType()) || questionReqVO.getScriptType().equals("2")) {
-            List<Map<String, Object>> questionnaireDataList = new ArrayList<>();
-            //鑾峰彇婊℃剰搴︾殑鍒嗙被
-            SvyLibScriptCategory svyLibScriptCategory = new SvyLibScriptCategory();
-            svyLibScriptCategory.setType("myd");
-            if (!StringUtils.isEmpty(questionReqVO.getType())) svyLibScriptCategory.setType(questionReqVO.getType());
-            List<SvyLibScriptCategory> svyLibScriptCategoryVOS = svyLibScriptCategoryMapper.selectSvyLibScriptCategoryList(svyLibScriptCategory);
-            List<Long> assortIdList = Optional.ofNullable(svyLibScriptCategoryVOS).filter(list -> !list.isEmpty()).map(list -> list.stream().map(SvyLibScriptCategory::getId).collect(Collectors.toList())).orElse(Collections.emptyList());
-
-            //鐢ㄦ潵璁板綍婊℃剰搴︾殑鎵�鏈夐棶棰�
-            List<SvyLibTemplateScript> svyLibTemplateScriptList = new ArrayList<>();
-            if (CollectionUtils.isNotEmpty(assortIdList)) {
-                for (Long categoryid : assortIdList) {
-                    SvyLibTemplateScript svyLibTemplateScript = new SvyLibTemplateScript();
-                    svyLibTemplateScript.setCategoryid(categoryid);
-                    List<SvyLibTemplateScript> svyLibTemplateScripts = isvyLibTemplateScriptService.selectSvyLibTemplateScriptList(svyLibTemplateScript);
-                    svyLibTemplateScriptList.addAll(svyLibTemplateScripts);
-                }
-                for (SvyLibTemplateScript svyLibTemplateScript : svyLibTemplateScriptList) {
-                    Map<String, Object> voicetData = new HashMap<>();
-                    voicetData.put("questionId", svyLibTemplateScript.getId());
-                    voicetData.put("libtemplateid", svyLibTemplateScript.getSvyid());
-                    voicetData.put("questionContent", svyLibTemplateScript.getScriptContent());
-                    voicetData.put("questionTopic", svyLibTemplateScript.getScriptTopic());
-                    voicetData.put("questionType", svyLibTemplateScript.getScriptType());
-                    voicetData.put("categoryName", svyLibTemplateScript.getCategoryName());
-                    voicetData.put("categoryId", svyLibTemplateScript.getCategoryid());
-                    questionnaireDataList.add(voicetData);
-                }
-            }
-            //闂嵎鏁版嵁杩斿洖闆嗗悎
-            result.put("questionnaireDataList", questionnaireDataList);
-        }
-        return result;
-    }
-}
+}
\ No newline at end of file
diff --git a/smartor/src/main/java/com/smartor/service/impl/ServiceSubtaskServiceImpl.java b/smartor/src/main/java/com/smartor/service/impl/ServiceSubtaskServiceImpl.java
index 90c007a..b27d133 100644
--- a/smartor/src/main/java/com/smartor/service/impl/ServiceSubtaskServiceImpl.java
+++ b/smartor/src/main/java/com/smartor/service/impl/ServiceSubtaskServiceImpl.java
@@ -9,6 +9,7 @@
 import com.ruoyi.common.core.domain.entity.SysDept;
 import com.ruoyi.common.core.redis.RedisCache;
 import com.ruoyi.common.core.service.IConfigService;
+import com.ruoyi.satisfaction.api.SatisfactionFacade;
 import com.ruoyi.message.sms.provider.sltd.ws.MessageSend;
 import com.ruoyi.common.exception.base.BaseException;
 import com.ruoyi.common.utils.*;
@@ -72,6 +73,16 @@
 @Slf4j
 @Service
 public class ServiceSubtaskServiceImpl implements IServiceSubtaskService {
+
+    /**
+     * 婊℃剰搴︽ā鍧楀澶栭棬闈紙甯搁┗ Bean锛夈��
+     * <p>
+     * 闅忚缁熻閲岀殑銆屾弧鎰忓害鎸囨暟銆嶏紙{@code getSfStatisticsJoy}锛夊彧鑳介�氳繃瀹冨彇鏁帮紱
+     * 婊℃剰搴︽ā鍧楀叧闂椂闂ㄩ潰杩斿洖鍏ㄩ浂 Map锛屼笉褰卞搷闅忚缁熻鐨勫叾瀹冨瓧娈典笌鍚姩銆�
+     */
+    @Autowired
+    private SatisfactionFacade satisfactionFacade;
+
     @Autowired
     private ServiceSubtaskMapper serviceSubtaskMapper;
 
@@ -604,7 +615,6 @@
 //        return map;
 //    }
 
-
     /**
      * 缁熻浠诲姟鍚勭鐘舵�佺殑鏁伴噺锛堝凡浼樺寲锛�
      * 浼樺寲璇存槑锛�
@@ -851,7 +861,6 @@
             svyTaskTemplateService.deleteSvyTaskTemplateBySvyid(serviceSubtask.getTemplateid());
         } else if (serviceSubtask.getType().equals("1"))
             ivrTaskTemplateService.deleteIvrTaskTemplateByID(serviceSubtask.getTemplateid());
-
 
         String tempName = svyTaskTemplateService.selectSvyTaskTemplateBySvyid(Long.valueOf(taskTempid)).getSvyname();
         serviceSubtask.setTemplateid(taskTempid.longValue());
@@ -2471,7 +2480,6 @@
         return phoneCallBackYQVO;
     }
 
-
     private String getObject(ServiceSubtask serviceSubtask, String scriptContent) {
         Map<String, Map<String, String>> param = getParam(serviceSubtask.getTaskid());
         for (Map<String, String> map : param.values()) {
@@ -2488,7 +2496,6 @@
 
         return scriptContent;
     }
-
 
     /**
      * 鑾峰彇浠诲姟閲岀殑閫氶厤绗�
@@ -2538,7 +2545,6 @@
         }
         return map;
     }
-
 
     /**
      * 闆ㄧ划浠诲姟鎷夊彇
@@ -2608,7 +2614,6 @@
         log.info("-----------瀹為檯鎷夊彇鐨勭數璇濇暟鎹噺锛歿}", CollectionUtils.isNotEmpty(pullTaskVOList) ? pullTaskVOList.size() : null);
         return pullTaskVOList;
     }
-
 
     private List<PullTaskVO> getPullTaskList(List<String> subIds, String cacheName) {
         log.error("浠诲姟鎷夊彇subIds鐨勫�兼槸澶氬皯锛歿}", subIds);
@@ -2681,7 +2686,6 @@
 
         return pullTaskVOList;
     }
-
 
     @Override
     public Integer saveQuestionAnswerPhone(ServiceSubTaskDetailReq serviceSubTaskDetailReq) {
@@ -3080,7 +3084,6 @@
         return jsRates;
     }
 
-
     public List<ServiceSubtaskStatistic> getSfStatisticsExport(ServiceSubtaskCountReq serviceSubtaskCountReq) {
         List<ServiceSubtaskStatistic> sfStatistics = getSfStatistics(serviceSubtaskCountReq);
         // 娣诲姞鎬昏琛�
@@ -3167,13 +3170,15 @@
         if (CollectionUtils.isNotEmpty(serviceSubtaskStatistics)) {
             for (ServiceSubtaskStatistic serviceSubtaskStatistic : serviceSubtaskStatistics) {
                 //鎵�鏈夋弧鎰忓害鏁伴噺
+                //婊℃剰搴︽ā鍧楀寲鍚庯細婊℃剰搴︽寚鏁版嵁姝ゆ敼涓鸿皟鐢ㄦ弧鎰忓害妯″潡闂ㄩ潰锛�
+                //妯″潡鍏抽棴鏃堕棬闈㈣繑鍥炲叏闆� Map锛堜笌琚縼绉诲疄鐜扮殑瀹為檯杈撳嚭涓�鑷达級锛屼笉褰卞搷闅忚缁熻鍏跺畠瀛楁銆�
                 if (CollectionUtils.isNotEmpty(serviceSubtaskCountReq.getDeptcodes())) {
-                    Map<String, Object> satisfactionIndex = getSatisfactionIndex("1", serviceSubtaskStatistic.getDeptcode(), orgid, serviceSubtaskCountReq.getIvrCategoryid(), serviceSubtaskCountReq.getSvyCategoryid());
+                    Map<String, Object> satisfactionIndex = satisfactionFacade.getSatisfactionIndex("1", serviceSubtaskStatistic.getDeptcode(), orgid, serviceSubtaskCountReq.getIvrCategoryid(), serviceSubtaskCountReq.getSvyCategoryid());
                     serviceSubtaskStatistic.setJoyAllCount(satisfactionIndex.get("mydtmzl") != null ? (Integer) satisfactionIndex.get("mydtmzl") : 0);
                     serviceSubtaskStatistic.setJoyCount(satisfactionIndex.get("mydtbl") != null ? (Integer) satisfactionIndex.get("mydtbl") : 0);
                     serviceSubtaskStatistic.setJoyTotal((Double) satisfactionIndex.get("wcbl"));
                 } else if (CollectionUtils.isNotEmpty(serviceSubtaskCountReq.getLeavehospitaldistrictcodes())) {
-                    Map<String, Object> satisfactionIndex = getSatisfactionIndex("2", serviceSubtaskStatistic.getLeavehospitaldistrictcode(), orgid, serviceSubtaskCountReq.getIvrCategoryid(), serviceSubtaskCountReq.getSvyCategoryid());
+                    Map<String, Object> satisfactionIndex = satisfactionFacade.getSatisfactionIndex("2", serviceSubtaskStatistic.getLeavehospitaldistrictcode(), orgid, serviceSubtaskCountReq.getIvrCategoryid(), serviceSubtaskCountReq.getSvyCategoryid());
                     serviceSubtaskStatistic.setJoyAllCount(satisfactionIndex.get("mydtmzl") != null ? (Integer) satisfactionIndex.get("mydtmzl") : 0);
                     serviceSubtaskStatistic.setJoyCount(satisfactionIndex.get("mydtbl") != null ? (Integer) satisfactionIndex.get("mydtbl") : 0);
                     serviceSubtaskStatistic.setJoyTotal((Double) satisfactionIndex.get("wcbl"));
@@ -3183,103 +3188,6 @@
 
         return serviceSubtaskStatistics;
     }
-
-
-    /**
-     * 鑾峰彇婊℃剰搴﹂鐩�婚噺
-     * type : 1-绉戝   2鐥呭尯
-     * code: 绉戝鎴栫梾鍖篶ode
-     *
-     * @return
-     */
-    private Map<String, Object> getSatisfactionIndex(String type, String code, String orgid, Long ivrCategoryid, Long svyCategoryid) {
-        Map<String, Object> map = new HashMap<>();
-        List<ServiceTask> serviceTasks = null;
-        if (type.equals("1")) {
-            ServiceTask serviceTask = new ServiceTask();
-            serviceTask.setDeptcode(code);
-            serviceTask.setOrgid(orgid);
-            serviceTasks = serviceTaskService.selectServiceTaskList(serviceTask);
-        } else if (type.equals("2")) {
-            ServiceTask serviceTask = new ServiceTask();
-            serviceTask.setLeavehospitaldistrictcode(code);
-            serviceTask.setOrgid(orgid);
-            serviceTasks = serviceTaskService.selectServiceTaskList(serviceTask);
-        }
-
-        //鑾峰彇婊℃剰搴﹂鐩�婚噺
-        BigDecimal mydtmzlsum = new BigDecimal(0);
-        //鑾峰彇婊℃剰搴﹂鐩~鎶ラ噺
-        BigDecimal mydtblsum = new BigDecimal(0);
-        if (CollectionUtils.isNotEmpty(serviceTasks)) {
-            //鑾峰彇婊℃剰搴﹂鐩�婚噺
-            for (ServiceTask serviceTask : serviceTasks) {
-                if (serviceTask.getType().equals("1")) {
-                    //璇煶
-                    Long templateid = serviceTask.getTemplateid();
-                    IvrTaskTemplateScript ivrTaskTemplateScript = new IvrTaskTemplateScript();
-                    ivrTaskTemplateScript.setTemplateID(templateid);
-                    ivrTaskTemplateScript.setOrgid(serviceTask.getOrgid());
-                    //鍏堥粯璁や负1鍚э紝assortid=1  浠h〃鏄弧鎰忓害
-                    ivrTaskTemplateScript.setScriptAssortid(ivrCategoryid);
-                    List<IvrTaskTemplateScript> ivrTaskTemplateScripts = iIvrTaskTemplateScriptService.selectIvrTaskTemplateScriptList(ivrTaskTemplateScript);
-                    if (CollectionUtils.isNotEmpty(ivrTaskTemplateScripts)) {
-                        //闇�瑕佸厛璁$畻鍑哄彂閫侀噺锛屽啀涔樹互鍗曚釜闂埜婊℃剰搴﹂鐩暟閲忥紝寰楀埌婊℃剰搴﹂鐩�婚噺锛堝寘鍚簡鏈仛鐨勶級
-                        ServiceSubtaskEntity serviceSubtaskVO = new ServiceSubtaskEntity();
-                        serviceSubtaskVO.setTaskid(serviceTask.getTaskid());
-                        List<Long> sendstates = new ArrayList<>(Arrays.asList(3L, 5L, 6L));
-                        serviceSubtaskVO.setSendstates(sendstates);
-                        List<ServiceSubtask> serviceSubtaskList = serviceSubtaskMapper.selectServiceSubtaskBySendstate(serviceSubtaskVO);
-                        if (CollectionUtils.isNotEmpty(serviceSubtaskList)) {
-                            int mydtmzlCount = ivrTaskTemplateScripts.size() * serviceSubtaskList.size();
-                            mydtmzlsum.add(new BigDecimal(mydtmzlCount));
-                        }
-
-                    }
-                } else if (serviceTask.getType().equals("2")) {
-                    //闂嵎
-                    SvyTaskTemplateScript svyTaskTemplateScript = new SvyTaskTemplateScript();
-                    svyTaskTemplateScript.setTemplateID(serviceTask.getTemplateid());
-                    svyTaskTemplateScript.setOrgid(serviceTask.getOrgid());
-                    svyTaskTemplateScript.setCategoryid(svyCategoryid);
-                    List<SvyTaskTemplateScript> svyTaskTemplateScriptList = svyTaskTemplateScriptService.selectSvyTaskTemplateScriptList(svyTaskTemplateScript);
-                    if (CollectionUtils.isNotEmpty(svyTaskTemplateScriptList)) {
-                        ServiceSubtaskEntity serviceSubtaskEntity = new ServiceSubtaskEntity();
-                        serviceSubtaskEntity.setTaskid(serviceTask.getTaskid());
-                        List<Long> sendstates = new ArrayList<>(Arrays.asList(3L, 5L, 6L));
-                        serviceSubtaskEntity.setSendstates(sendstates);
-                        List<ServiceSubtask> serviceSubtaskList = serviceSubtaskMapper.selectServiceSubtaskBySendstate(serviceSubtaskEntity);
-                        if (CollectionUtils.isNotEmpty(serviceSubtaskList)) {
-                            int mydtmzlCount = svyTaskTemplateScriptList.size() * serviceSubtaskList.size();
-                            mydtmzlsum.add(new BigDecimal(mydtmzlCount));
-                        }
-                    }
-                }
-
-                //鑾峰彇婊℃剰搴﹂鐩~鎶ラ噺
-                ServiceSubtaskDetail serviceSubtaskDetail = new ServiceSubtaskDetail();
-                serviceSubtaskDetail.setTaskid(serviceTask.getTaskid());
-                if (serviceTask.getType().equals("1")) serviceSubtaskDetail.setCategoryid(ivrCategoryid);
-                if (serviceTask.getType().equals("2")) serviceSubtaskDetail.setCategoryid(svyCategoryid);
-                serviceSubtaskDetail.setOrgid(serviceTask.getOrgid());
-                List<ServiceSubtaskDetail> serviceSubtaskDetails = serviceSubtaskDetailMapper.selectServiceSubtaskDetailList(serviceSubtaskDetail);
-                if (CollectionUtils.isNotEmpty(serviceSubtaskDetails))
-                    mydtblsum.add(new BigDecimal(serviceSubtaskDetails.size()));
-            }
-
-
-        }
-        //婊℃剰搴﹂鐩�婚噺
-        map.put("mydtmzl", mydtmzlsum.intValue());
-        map.put("mydtbl", mydtblsum.intValue());
-        map.put("wcbl", 0.0);
-        if (mydtmzlsum.intValue() != 0) {
-            map.put("wcbl", mydtblsum.divide(mydtmzlsum, 2, RoundingMode.HALF_UP).doubleValue());
-        }
-
-        return map;
-    }
-
 
     private ServiceSubtaskStatistic getStatistic(List<ServiceSubtask> serviceSubtaskList, String followUpCountStyle, Integer visitCount) {
         ServiceSubtaskStatistic serviceSubtaskStatistic = new ServiceSubtaskStatistic();
@@ -3911,7 +3819,6 @@
         return serviceSubtaskStatistic;
     }
 
-
     private ServiceSubtaskStatistic getStatisticJoy(List<ServiceSubtask> serviceSubtaskList, List<ServiceSubtaskCount> joySubCount) {
         ServiceSubtaskStatistic serviceSubtaskStatistic = new ServiceSubtaskStatistic();
         //鏍煎紡鍖栦负涓や綅灏忔暟鐨勭櫨鍒嗘瘮
@@ -4034,7 +3941,6 @@
         if (subCounts == null) {
             subCounts = new ArrayList<>();
         }
-
 
         for (ServiceSubtask serviceSubtask : serviceSubtaskList) {
             List<ServiceSubtaskCount> subCountTemp = subCounts.stream().filter(r -> r.getSubTaskId().equals(serviceSubtask.getId())).collect(Collectors.toList());
@@ -4512,7 +4418,6 @@
         }
     }
 
-
     /**
      * 鑾峰彇闅忚缁熻姣斾緥
      *
@@ -4734,7 +4639,6 @@
         return result;
     }
 
-
     /**
      * @param serviceSubtask
      * @param preachform
@@ -4765,7 +4669,6 @@
             serviceSubtaskPreachform1.setSendstate(failSendstate);
             serviceSubtaskPreachform1.setRemark(remark);
             serviceSubtaskPreachformMapper.updateServiceSubtaskPreachform(serviceSubtaskPreachform1);
-
 
             //濡傛灉褰撳墠鐨刾reachform宸茬粡鏄渶鍚庝竴涓簡锛岄偅鐩存帴灏唖erviceSubtask鐨剆endstate鐘舵�佹敼鎴�5灏辫浜�(鍏ㄥけ璐ヤ簡)
             if (serviceSubtaskPreachform1.getSort() == spSize.size() && failSendstate.equals("4")) {
@@ -4812,7 +4715,6 @@
         }
         return visitTime;
     }
-
 
     /**
      * 璁$畻鎬昏琛�
@@ -5229,7 +5131,6 @@
             serviceOutPath.setUrl(url);
             iServiceOutPathService.updateServiceOutPath(serviceOutPath);
 
-
             //his鎮h�卛d鏍¢獙锛堜笌鍘熷疄鐜颁竴鑷达細鍏堟牎楠岃繖涓�椤癸級
             if (StringUtils.isEmpty(serviceSubtask.getPatidHis())) {
                 resultMap.put("code", -1);
@@ -5269,450 +5170,6 @@
             return resultMap;
         }
         return resultMap;
-    }
-    @Override
-    public List<ServiceSubtaskDetailRatioExport> statQuestionOption(List<Long> taskIds, Date startFinishTime, Date endFinishTime, Date startOutHospTime, Date endOutHospTime, String scriptContent, Double scoreStart, Double scoreEnd, String valueType, List<Long> subtaskIds) {
-        //鍏堟竻鐞嗕竴涓嬮噸澶嶆暟鎹�
-        List<Long> repeatIds = serviceSubtaskDetailMapper.selectRepeatDetailIds();
-        if (!CollectionUtils.isEmpty(repeatIds)) {
-            serviceSubtaskDetailMapper.deleteRepeatDetailByIds(repeatIds);
-        }
-        List<ServiceSubtaskDetailRatioExport> serviceSubtaskDetailRatioExports = serviceSubtaskMapper.statQuestionOption(taskIds, startOutHospTime, endOutHospTime, startFinishTime, endFinishTime, scoreStart, scoreEnd, scriptContent, valueType, subtaskIds);
-
-        // 澶勭悊 targetvalue锛氭寜 & 鍒嗗壊鎴愬鏉¤褰�
-        List<ServiceSubtaskDetailRatioExport> result = new ArrayList<>();
-        for (ServiceSubtaskDetailRatioExport export : serviceSubtaskDetailRatioExports) {
-            if (StringUtils.isNotEmpty(export.getTargetvalue()) && export.getTargetvalue().contains("&")) {
-                String[] values = export.getTargetvalue().split("&");
-                // 濡傛灉鏈夊鏉$洰鏍囧�硷紝鎷嗗垎鎴愬琛�
-                for (String value : values) {
-                    //鍏堝幓 result 涓煡涓�涓嬶紝鏈夋病鏈夎繖涓� questiontext 鍜屽搴旂殑 targetvalue
-                    //娉ㄦ剰蹇呴』甯︿笂 taskid 涓�璧峰尮閰嶏細鍚﹀垯浼氭妸鍒殑浠诲姟鐨勬暟鎹~鍒版湰浠诲姟鐨勮涓婏紝
-                    //瀵艰嚧瀵煎嚭鐨勩�屾暟閲�/姣斾緥銆嶅拰璇ヨ鐨勩�屼换鍔″悕绉�/浠诲姟缂栧彿銆嶅涓嶄笂锛堝悓涓�浠诲姟鍚屼竴棰樺嚭鐜颁袱涓笉鍚屽垎姣嶏級
-                    ServiceSubtaskDetailRatioExport existingExport = result.stream().filter(e -> Objects.equals(e.getTaskid(), export.getTaskid()) && e.getQuestiontext().equals(export.getQuestiontext()) && value.trim().equals(e.getTargetvalue())).findFirst().orElse(null);
-                    if (existingExport != null && existingExport.getRatio().equals("0") && existingExport.getCount().equals("0")) {
-                        //濡傛灉瀛樺湪锛屽垯灏� result 閲岀殑杩欎釜闂鎵�瀵瑰簲鐨� Targetvalue=value 杩欐潯鏁版嵁鏇存柊鎴愬綋鍓嶈繖鏉℃暟鎹�
-                        if (value.equals(export.getOptionresult())) {
-                            existingExport.setRatio(export.getRatio());
-                            existingExport.setScore(export.getScore());
-                            existingExport.setCount(export.getCount());
-                            existingExport.setOptionresult(export.getOptionresult());
-                        }
-                    } else if (existingExport != null && !existingExport.getRatio().equals("0")) {
-                        continue;
-                    } else {
-                        ServiceSubtaskDetailRatioExport newExport = DtoConversionUtils.sourceToTarget(export, ServiceSubtaskDetailRatioExport.class);
-                        newExport.setTargetvalue(value.trim()); // targetvalue 鏄剧ず鍗曚釜閫夐」
-                        if (!value.equals(export.getOptionresult())) {
-                            newExport.setOptionresult(null);
-                            newExport.setCount("0");
-                            newExport.setRatio("0");
-                        }
-                        result.add(newExport);
-                    }
-                }
-            } else {
-                // targetvalue 涓嶅寘鍚� &锛岀洿鎺ユ坊鍔犲埌缁撴灉
-                result.add(export);
-            }
-        }
-
-        return result;
-    }
-
-    @Override
-    public List<ServiceSubtaskDetailRatioExport> statMydQuestionOptionForExport(List<Long> subtaskIds, String questiontext) {
-        List<ServiceSubtaskDetailRatioExport> result = new ArrayList<>();
-        if (CollectionUtils.isEmpty(subtaskIds)) {
-            return result;
-        }
-        List<ServiceSubtaskOptionStatDTO> statList = serviceSubtaskMapper.statMydQuestionOptionForExport(subtaskIds, questiontext);
-        if (CollectionUtils.isEmpty(statList)) {
-            return result;
-        }
-        return buildMydOptionRows(statList);
-    }
-
-    /**
-     * 鎶娿�屼换鍔�+棰樼洰+閫夐」銆嶇殑浣滅瓟缁熻鎷兼垚瀵煎嚭鐨勩�岄棶棰樿鎯呭崰姣斻�嶈
-     * <ul>
-     *     <li>閫夐」娓呭崟锛氶鐩厤缃繃鐨勯�夐」鍦� targetvalue锛堢敤 & 杩炴帴锛夐噷锛屽叏閮ㄥ垪鍑猴紝娌′汉閫夌殑鏄剧ず 0</li>
-     *     <li>鏁伴噺锛氭偅鑰呴�変腑鐨勯�夐」鍦� asrtext 閲岋紙澶氶�夐鏄�孉&B銆嶏級锛屾瘡涓�変腑椤瑰悇璁颁竴娆′汉鏁帮紝涓�涓偅鑰呭彧绠椾竴鏉℃槑缁�</li>
-     *     <li>姣斾緥锛氭暟閲� / 璇ラ浣滅瓟鎮h�呮暟锛屾墍浠ュ崟閫夐鍚勯�夐」鏁伴噺涔嬪拰 = 璇ラ浣滅瓟鎮h�呮暟</li>
-     *     <li>鍒嗘暟锛氳閫夐」鏄庣粏鐨� score 涔嬪拰锛涘閫夎鎸夐�変腑椤逛唤鏁板潎鍒嗭紝淇濊瘉鍚勯�夐」鍒嗘暟涔嬪拰 = 鏄庣粏鍒嗘暟涔嬪拰</li>
-     * </ul>
-     */
-    private static List<ServiceSubtaskDetailRatioExport> buildMydOptionRows(List<ServiceSubtaskOptionStatDTO> statList) {
-        List<ServiceSubtaskDetailRatioExport> result = new ArrayList<>();
-
-        // 鎸夈�屼换鍔� + 棰樼洰銆嶅垎缁勶紝淇濇寔 SQL 鐨勯『搴忥紙鍏堥鐩悗浠诲姟锛�
-        Map<String, List<ServiceSubtaskOptionStatDTO>> grouped = new LinkedHashMap<>();
-        for (ServiceSubtaskOptionStatDTO stat : statList) {
-            String key = stat.getTaskid() + "\u0001" + stat.getQuestiontext();
-            grouped.computeIfAbsent(key, k -> new ArrayList<>()).add(stat);
-        }
-
-        for (List<ServiceSubtaskOptionStatDTO> group : grouped.values()) {
-            ServiceSubtaskOptionStatDTO first = group.get(0);
-            // 閫夐」 -> 浜烘暟锛坙ong[1] 渚夸簬绱姞锛夛紝椤哄簭鍗冲嚭鐜伴『搴�
-            Map<String, long[]> countMap = new LinkedHashMap<>();
-            Map<String, BigDecimal> scoreMap = new LinkedHashMap<>();
-
-            for (ServiceSubtaskOptionStatDTO stat : group) {
-                // targetvalue = 鏈鎵�鏈夐�夐」鐨勬竻鍗曪紝娌′汉閫夌殑涔熻鍒楀嚭鏉�
-                List<String> options = splitOptions(stat.getTargetvalue());
-                // asrtext = 鎮h�呭疄闄呴�変腑鐨勯�夐」锛屽閫夐浼氭槸銆岄�変腑椤�1&閫変腑椤�2銆�
-                List<String> chosenOptions = splitOptions(stat.getOptionresult());
-                for (String option : options) {
-                    countMap.computeIfAbsent(option, k -> new long[1]);
-                    scoreMap.computeIfAbsent(option, k -> BigDecimal.ZERO);
-                }
-
-                long patientCount = stat.getPatientCount() == null ? 0L : stat.getPatientCount();
-                BigDecimal scoreSum = stat.getScoreSum() == null ? BigDecimal.ZERO : stat.getScoreSum();
-
-                if (!chosenOptions.isEmpty()) {
-                    BigDecimal[] scoreParts = splitScore(scoreSum, chosenOptions.size());
-                    for (int i = 0; i < chosenOptions.size(); i++) {
-                        String chosen = chosenOptions.get(i);
-                        // 閫変腑鐨勯�夐」鍙兘涓嶅湪閰嶇疆娓呭崟閲岋紙鍘嗗彶鏁版嵁锛夛紝琛ヤ竴琛�
-                        countMap.computeIfAbsent(chosen, k -> new long[1]);
-                        scoreMap.computeIfAbsent(chosen, k -> BigDecimal.ZERO);
-                        countMap.get(chosen)[0] += patientCount;
-                        scoreMap.put(chosen, scoreMap.get(chosen).add(scoreParts[i]));
-                    }
-                } else if (options.size() == 1) {
-                    // 娌℃湁 asrtext锛堟湭绛�/鏃犲簲绛旓級鏃讹紝鍙湁鍞竴閫夐」鎵嶈兘纭畾褰掑睘
-                    countMap.get(options.get(0))[0] += patientCount;
-                    scoreMap.put(options.get(0), scoreMap.get(options.get(0)).add(scoreSum));
-                }
-            }
-
-            long total = 0L;
-            for (long[] count : countMap.values()) total += count[0];
-
-            for (Map.Entry<String, long[]> entry : countMap.entrySet()) {
-                long count = entry.getValue()[0];
-                ServiceSubtaskDetailRatioExport row = new ServiceSubtaskDetailRatioExport();
-                row.setQuestiontext(first.getQuestiontext());
-                row.setOptionresult(entry.getKey());
-                row.setTargetvalue(entry.getKey());
-                row.setCount(String.valueOf(count));
-                row.setRatio(formatRatio(count, total));
-                row.setScore(formatScore(scoreMap.get(entry.getKey())));
-                row.setTaskname(first.getTaskName());
-                row.setTaskid(first.getTaskid());
-                row.setValueType(first.getValueType());
-                result.add(row);
-            }
-        }
-        return result;
-    }
-
-    /**
-     * 鎶婁竴鏉℃槑缁嗙殑鍒嗘暟鎸夐�変腑椤逛唤鏁板潎鍒嗭紝浣欐暟琛ョ粰绗竴涓紝淇濊瘉鍚勪唤涔嬪拰 = 鍘熷垎鏁�
-     */
-    private static BigDecimal[] splitScore(BigDecimal total, int parts) {
-        BigDecimal[] result = new BigDecimal[parts];
-        if (parts <= 0) {
-            return result;
-        }
-        BigDecimal each = total.divide(BigDecimal.valueOf(parts), 2, RoundingMode.HALF_UP);
-        for (int i = 0; i < parts; i++) {
-            result[i] = each;
-        }
-        result[0] = result[0].add(total.subtract(each.multiply(BigDecimal.valueOf(parts))));
-        return result;
-    }
-
-    /**
-     * 鎷嗗垎棰樼洰閰嶇疆鐨勯�夐」锛堝涓�夐」鐢� & 杩炴帴锛夛紝椤哄甫鍘绘帀鍏ㄨ/鍗婅绌虹櫧
-     */
-    private static List<String> splitOptions(String targetvalue) {
-        List<String> options = new ArrayList<>();
-        if (StringUtils.isEmpty(targetvalue)) {
-            return options;
-        }
-        for (String part : targetvalue.split("&")) {
-            String option = trimAll(part);
-            if (StringUtils.isNotEmpty(option) && !options.contains(option)) {
-                options.add(option);
-            }
-        }
-        return options;
-    }
-
-    /**
-     * 姣斾緥锛氭暟閲� / 璇ラ浣滅瓟鎮h�呮暟锛屼繚鐣欎袱浣嶅皬鏁�
-     */
-    private static String formatRatio(long count, long total) {
-        if (total <= 0) {
-            return "0.00%";
-        }
-        return BigDecimal.valueOf(count * 100.0 / total).setScale(2, RoundingMode.HALF_UP).toPlainString() + "%";
-    }
-
-    /**
-     * 鍒嗘暟涔嬪拰锛氬幓鎺夊浣欑殑灏忔暟浣嶏紙0.00 -> 0锛�
-     */
-    private static String formatScore(BigDecimal score) {
-        if (score == null) {
-            return "0";
-        }
-        BigDecimal value = score.stripTrailingZeros();
-        if (value.scale() < 0) {
-            value = value.setScale(0);
-        }
-        return value.toPlainString();
-    }
-
-    /*
-     * 鏍规嵁绉戝鎴栫梾鍖虹粺璁¢棶鍗烽�夐」(绉戝銆侀鐩煡璇㈢殑绉戝璇勫垎琛�)
-     */
-    @Override
-    public Map<String, Map<String, List<QuestionCountKsOrBqDTO>>> statQuestionOptionByKsOrBq(QuestionCountKsOrBqVO vo) {
-
-        // 1. 鏌ユ槑缁�
-        List<QuestionCountKsOrBqDTO> sourceList = serviceSubtaskMapper.statQuestionOptionByKsOrBq(vo);
-        if (CollectionUtils.isEmpty(sourceList)) {
-            return Collections.emptyMap();
-        }
-
-        // 2. 鍒ゆ柇鎸夌瀹よ繕鏄寜鐥呭尯
-        boolean byDept = vo.getLeaveldeptcodes() != null;
-        boolean byDistrict = !byDept && vo.getLeavehospitaldistrictcodes() != null;
-        if (!byDept && !byDistrict) {
-            return Collections.emptyMap();
-        }
-
-        // 3. key = 绉戝/鐥呭尯 + 棰樼洰 + 閫夐」
-        Map<String, QuestionCountKsOrBqDTO> map = new LinkedHashMap<>();
-
-        for (QuestionCountKsOrBqDTO dto : sourceList) {
-
-            if (StringUtils.isEmpty(dto.getTargetvalue())) {
-                continue;
-            }
-
-            // 鍒嗙粍鍚嶏細绉戝 鎴� 鐥呭尯
-            String groupName = byDept ? dto.getDeptname() : dto.getLeavehospitaldistrictname();
-            if (StringUtils.isEmpty(groupName)) {
-                continue;
-            }
-
-            String asrtext = trimAll(dto.getAsrtext());            // 鐢ㄦ埛瀹為檯閫変腑鐨勯�夐」
-            String questionText = trimAll(dto.getQuestionText());  // 棰樼洰
-            if (StringUtils.isEmpty(questionText)) {
-                continue;
-            }
-
-            // 鎷嗗垎 targetvalue锛屾瘡涓�夐」閮借鏈変竴鏉¤褰�
-            for (String raw : dto.getTargetvalue().split("&")) {
-
-                String option = trimAll(raw);
-                if (option.isEmpty()) {
-                    continue;
-                }
-
-                // key = 绉戝/鐥呭尯 + 棰樼洰 + 閫夐」
-                String key = buildKey(groupName, questionText, option);
-
-                QuestionCountKsOrBqDTO item = map.get(key);
-                if (item == null) {
-                    item = DtoConversionUtils.sourceToTarget(dto, QuestionCountKsOrBqDTO.class);
-                    item.setTargetvalue(option);
-                    item.setAsrtext(option);
-                    item.setQuestionText(dto.getQuestionText());
-                    item.setAnswerCount("0");
-                    item.setSingleScore("0");
-                    map.put(key, item);
-                }
-
-                // 鍙湁褰撳墠閫夐」 == 鐢ㄦ埛瀹為檯閫変腑鐨勯�夐」锛屾墠绱姞
-                if (option.equals(asrtext)) {
-                    int count = parseCount(item.getAnswerCount()) + 1;
-                    item.setAnswerCount(String.valueOf(count));
-
-                    BigDecimal totalScore = new BigDecimal(item.getSingleScore())
-                            .add(new BigDecimal(dto.getSingleScore() == null ? "0" : dto.getSingleScore()));
-                    item.setSingleScore(totalScore.toPlainString());
-                }
-            }
-        }
-
-        // 4. 璁$畻骞冲潎鍒�
-        for (QuestionCountKsOrBqDTO dto : map.values()) {
-            String answerCount = dto.getAnswerCount();
-            String singleScore = dto.getSingleScore();
-
-            BigDecimal avg;
-            if (StringUtils.isEmpty(answerCount)
-                    || BigDecimal.ZERO.compareTo(new BigDecimal(answerCount.trim())) == 0) {
-                avg = BigDecimal.ZERO;
-            } else {
-                avg = new BigDecimal(singleScore == null ? "0" : singleScore)
-                        .divide(new BigDecimal(answerCount.trim()), 2, RoundingMode.HALF_UP);
-            }
-            dto.setAvgScore(avg.toPlainString());
-        }
-
-        // 5. 杞垚 Map<String, Map<String, List<QuestionCountKsOrBqDTO>>>
-        //    绗竴灞� key锛氱瀹�/鐥呭尯
-        //    绗簩灞� key锛氶鐩�
-        //    绗簩灞� value锛氳棰樼洰涓嬬殑鎵�鏈夐�夐」鍒楄〃
-        Map<String, Map<String, List<QuestionCountKsOrBqDTO>>> result = new LinkedHashMap<>();
-
-        for (QuestionCountKsOrBqDTO dto : map.values()) {
-
-            // 鍒嗙粍鍚�
-            String groupName = byDept ? dto.getDeptname() : dto.getLeavehospitaldistrictname();
-            if (StringUtils.isEmpty(groupName)) {
-                continue;
-            }
-
-            // 棰樼洰鍚�
-            String questionText = dto.getQuestionText();
-            if (StringUtils.isEmpty(questionText)) {
-                continue;
-            }
-
-            // 绗竴灞傦細绉戝/鐥呭尯
-            Map<String, List<QuestionCountKsOrBqDTO>> questionMap =
-                    result.computeIfAbsent(groupName, k -> new LinkedHashMap<>());
-
-            // 绗簩灞傦細棰樼洰
-            List<QuestionCountKsOrBqDTO> optionList =
-                    questionMap.computeIfAbsent(questionText, k -> new ArrayList<>());
-
-            optionList.add(dto);
-        }
-
-        return result;
-    }
-
-    @Override
-    public List<ServiceSubtaskDetailRatioExport> statQuestionOptionBySubtaskIds(List<Long> subtaskIds, Date startOutHospTime, Date endOutHospTime, Date startFinishTime, Date endFinishTime, String scriptContent, Double scoreStart, Double scoreEnd, String valueType) {
-        //鍏堟竻鐞嗕竴涓嬮噸澶嶆暟鎹�
-        List<Long> repeatIds = serviceSubtaskDetailMapper.selectRepeatDetailIds();
-        if (!CollectionUtils.isEmpty(repeatIds)) {
-            serviceSubtaskDetailMapper.deleteRepeatDetailByIds(repeatIds);
-        }
-
-        List<ServiceSubtaskDetailRatioExport> serviceSubtaskDetailRatioExports = serviceSubtaskMapper.statQuestionOptionBySubtaskIds(subtaskIds, startOutHospTime, endOutHospTime, startFinishTime, endFinishTime, scoreStart, scoreEnd, scriptContent, valueType);
-        // SQL 宸叉寜銆岄鐩� + 閫夐」銆嶆眹鎬绘暟閲忥紝杩欓噷鍐嶆寜棰樼洰鍚堝苟锛堜笉鍚屾ā鏉跨殑 targetvalue 閫夐」椤哄簭涓嶅悓锛屽悓涓�閫夐」浼氭媶鎴愬琛岋級锛�
-        // 骞舵妸娌′汉閫夌殑閫夐」琛ユ垚 0 琛屻�佹寜鍚勯�夐」鏁伴噺涔嬪拰閲嶇畻姣斾緥
-        return mergeMydOptionRows(serviceSubtaskDetailRatioExports);
-    }
-
-    /**
-     * 鎶� SQL 杩斿洖鐨勩�岄鐩� + 閫夐」銆嶆槑缁嗗悎骞舵垚銆屼竴琛屼竴涓�夐」銆�
-     * <ul>
-     *     <li>鍚屼竴棰樼洰涓嬬浉鍚� optionresult 鐨勬暟閲忕疮鍔狅細鍘熷疄鐜伴亣鍒板悓鍚嶉�夐」鐩存帴 continue锛�
-     *         姣忎釜閫夐」鍙細淇濈暀绗竴鏉℃槑缁嗙殑鏁伴噺锛屽浠诲姟/澶氭偅鑰呯殑椤规暟鍏ㄨ涓㈡帀锛屽墠绔�屾暟閲忋�嶆案杩滃彧鏈� 1</li>
-     *     <li>閫夐」娓呭崟鍙� targetvalue锛堢敤 &amp; 杩炴帴锛夛紝娌′汉閫夌殑閫夐」琛� 0 琛岋紝鍓嶇鍥哄畾鍏垪鎵嶈兘娓叉煋</li>
-     *     <li>姣斾緥 = 璇ラ�夐」鏁伴噺 / 璇ラ鍚勯�夐」鏁伴噺涔嬪拰锛屼繚璇佷竴閬撻鍚勯�夐」姣斾緥鍚堣 100%</li>
-     * </ul>
-     *
-     * @param rows SQL 鏄庣粏
-     */
-    private List<ServiceSubtaskDetailRatioExport> mergeMydOptionRows(List<ServiceSubtaskDetailRatioExport> rows) {
-        List<ServiceSubtaskDetailRatioExport> result = new ArrayList<>();
-        if (CollectionUtils.isEmpty(rows)) {
-            return result;
-        }
-
-        // key -> 閫夐」 -> 鏁伴噺锛岀敤 LinkedHashMap 淇濆簭锛氶�夐」椤哄簭娌跨敤闂嵎閰嶇疆 targetvalue 鐨勯『搴�
-        Map<String, Map<String, long[]>> countMap = new LinkedHashMap<>();
-        Map<String, Map<String, String>> scoreMap = new LinkedHashMap<>();
-        Map<String, ServiceSubtaskDetailRatioExport> templateMap = new LinkedHashMap<>();
-
-        for (ServiceSubtaskDetailRatioExport row : rows) {
-            if (row == null) {
-                continue;
-            }
-            String key = row.getQuestiontext();
-            if (key == null) {
-                continue;
-            }
-            Map<String, long[]> counts = countMap.computeIfAbsent(key, k -> new LinkedHashMap<>());
-            Map<String, String> scores = scoreMap.computeIfAbsent(key, k -> new LinkedHashMap<>());
-            templateMap.putIfAbsent(key, row);
-
-            // 棰樼洰閰嶇疆鐨勯�夐」娓呭崟锛氭病浜洪�夌殑閫夐」涔熻鍒楀嚭鏉ワ紙鏁伴噺涓� 0锛�
-            for (String option : splitOptions(row.getTargetvalue())) {
-                counts.computeIfAbsent(option, k -> new long[1]);
-            }
-
-            // 鎮h�呭疄闄呴�変腑鐨勯�夐」锛氬閫夐 asrtext 鏄�岄�夐」1&閫夐」2銆嶏紝鍗曢��/闂瓟棰樻槸鏁存鏂囨湰
-            List<String> chosenOptions = "2".equals(row.getValueType())
-                    ? splitOptions(row.getOptionresult())
-                    : singleOption(row.getOptionresult());
-            if (chosenOptions.isEmpty()) {
-                // 鏈綔绛旓細涓嶈繘浠讳綍閫夐」锛屼篃涓嶈繘鍒嗘瘝
-                continue;
-            }
-            long count = parseCount(row.getCount());
-            for (String chosen : chosenOptions) {
-                // 閫変腑鐨勯�夐」鍙兘涓嶅湪閰嶇疆娓呭崟閲岋紙鍘嗗彶鏁版嵁锛夛紝琛ヤ竴琛岋紝鏁伴噺涓嶈兘涓�
-                counts.computeIfAbsent(chosen, k -> new long[1])[0] += count;
-                if (StringUtils.isNotEmpty(row.getScore()) && StringUtils.isEmpty(scores.get(chosen))) {
-                    scores.put(chosen, row.getScore());
-                }
-            }
-        }
-
-        for (Map.Entry<String, Map<String, long[]>> entry : countMap.entrySet()) {
-            ServiceSubtaskDetailRatioExport template = templateMap.get(entry.getKey());
-            Map<String, String> scores = scoreMap.get(entry.getKey());
-            long total = 0L;
-            for (long[] count : entry.getValue().values()) {
-                total += count[0];
-            }
-            for (Map.Entry<String, long[]> optionEntry : entry.getValue().entrySet()) {
-                String option = optionEntry.getKey();
-                long count = optionEntry.getValue()[0];
-                ServiceSubtaskDetailRatioExport merged = DtoConversionUtils.sourceToTarget(template, ServiceSubtaskDetailRatioExport.class);
-                merged.setQuestiontext(template.getQuestiontext());
-                merged.setTargetvalue(option);
-                // 娌′汉閫夌殑閫夐」 optionresult 缃┖锛屽墠绔寜 targetvalue 鍏滃簳灞曠ず锛屼繚鎸佸師鏈夊睍绀哄彛寰�
-                merged.setOptionresult(count > 0 ? option : null);
-                merged.setCount(String.valueOf(count));
-                merged.setRatio(formatRatio(count, total));
-                merged.setScore(StringUtils.isNotEmpty(scores.get(option)) ? scores.get(option) : template.getScore());
-                result.add(merged);
-            }
-        }
-        return result;
-    }
-
-    /**
-     * 鍗曢��/闂瓟棰樼殑浣滅瓟鏂囨湰锛氫笉鎸� &amp; 鎷嗭紝閬垮厤鎶婇棶绛旈鐨勬枃鏈瓟妗堟媶鍧�
-     */
-    private static List<String> singleOption(String answer) {
-        List<String> options = new ArrayList<>();
-        String text = trimAll(answer);
-        if (StringUtils.isNotEmpty(text)) {
-            options.add(text);
-        }
-        return options;
-    }
-
-    /**
-     * 鏍囧噯鍖栭棶鍗风瓟妗堟枃鏈細鍘绘帀 A/B/C/D 鍗曞瓧姣嶅墠缂�鍜屽悗缁┖鏍�
-     * 渚嬪 "A 鏄�" -> "鏄�"锛�"B 鍚�" -> "鍚�"
-     */
-    private String normalizeOptionAnswer(String answer) {
-        if (StringUtils.isBlank(answer)) {
-            return "";
-        }
-        String trimmed = answer.trim();
-        if (trimmed.length() >= 3 && trimmed.charAt(1) == ' ') {
-            char firstChar = trimmed.charAt(0);
-            if (firstChar >= 'A' && firstChar <= 'Z') {
-                return trimmed.substring(2).trim();
-            }
-        }
-        return trimmed;
     }
 
     private Boolean saveServiceSubtaskDetail(ServiceSubtask serviceSubtask) {
@@ -5918,34 +5375,6 @@
         return content;
     }
 
-    /**
-     * 婊℃剰搴﹁秼鍔挎煡璇�
-     *
-     * @param mydTrendReqVO
-     * @return
-     */
-    @Override
-    public List<MydTrendReqDTO> mydTrend(MydTrendReqVO mydTrendReqVO) {
-        List<MydTrendReqDTO> list = serviceSubtaskMapper.mydTrend(mydTrendReqVO.getStartFinishTime(), mydTrendReqVO.getEndFinishTime(), mydTrendReqVO.getType());
-        if (mydTrendReqVO.getType().equals("week")) list = getWeekDate(mydTrendReqVO, list);
-        if (mydTrendReqVO.getType().equals("year")) list = getYearDate(mydTrendReqVO, list);
-        if (mydTrendReqVO.getType().equals("month")) list = getMonthDate(mydTrendReqVO, list);
-        if (mydTrendReqVO.getType().equals("day")) list = getDayDate(mydTrendReqVO, list);
-        return list;
-    }
-
-    @Override
-    public List<MydTrendReqDTO> sltdMydTotalByDimension(MydTrendReqVO mydTrendReqVO) {
-        List<MydTrendReqDTO> list = null;
-        if (mydTrendReqVO.getDimensionType().equals("1"))
-            list = serviceSubtaskMapper.sltdMydTotalByDimension(mydTrendReqVO.getStartFinishTime(), mydTrendReqVO.getEndFinishTime(), mydTrendReqVO.getType());
-        else if (mydTrendReqVO.getDimensionType().equals("2"))
-            list = serviceSubtaskMapper.sltdMydTotalByYQ(mydTrendReqVO.getStartFinishTime(), mydTrendReqVO.getEndFinishTime(), mydTrendReqVO.getType());
-
-        return list;
-    }
-
-
     public ServiceSubtask boxedServiceSubtask(ServiceTask serviceTask, ServiceTaskdept serviceTaskdept, PatArchive patArchive) {
         ServiceSubtask serviceSubtask = DtoConversionUtils.sourceToTarget(serviceTask, ServiceSubtask.class);
         serviceSubtask.setTaskid(serviceTask.getTaskid());
@@ -6059,7 +5488,6 @@
         }
     }
 
-
     public PhoneCallBackSHVO getFirstQues(PhoneCallReqYQVO phoneCallReqYQVO) {
         //鐢ㄤ簬杩斿洖
         PhoneCallBackSHVO phoneCallBackSHVO = new PhoneCallBackSHVO();
@@ -6097,7 +5525,6 @@
         }
         return phoneCallBackSHVO;
     }
-
 
     /**
      * 涓夋眹ASR缁撴灉澶勭悊
@@ -6171,7 +5598,6 @@
             Object cacheObject = redisCache.getCacheObject(phoneCallResultYQVO.getSubId() + "-" + phoneCallResultYQVO.getScriptId());
             if (cacheObject == null || (Integer) cacheObject < ivrTaskTemplate.getMateNum()) {
 
-
                 // 鑾峰彇閫夐」闆嗗悎涓殑 optiondesc 鐢ㄤ簬寮曞鎮h��
                 List<IvrTaskTemplateTargetoption> optionList = ivrTaskTemplateScriptVO.getIvrTaskScriptTargetoptionList();
                 String optionDescStr = "";
@@ -6214,7 +5640,6 @@
         }
         return phoneCallBackSHVO;
     }
-
 
     public PhoneCallBackYQVO AiMatch(String url, PhoneCallReqYQVO phoneCallReqYQVO, PhoneCallBackYQVO phoneCallBackYQVO, IvrTaskTemplateScriptVO ivrTaskTemplateScriptVO, ServiceSubtask serviceSubtask, IvrTaskTemplate ivrTaskTemplate, List<IvrTaskTemplateScript> ivrTaskTemplateScripts, String scriptId, Integer flag, String uuid) {
         PhoneCallBackYQVO back = null;
@@ -6517,45 +5942,6 @@
         return back;
     }
 
-
-    /**
-     * 闂缁撴灉鍗犳瘮
-     *
-     * @param sltdMydTotalVO
-     * @param serviceSubtaskRes
-     * @return
-     */
-    public List<ServiceSubtaskDetailRatioExport> getDetailRatio(SltdMydTotalVO sltdMydTotalVO, List<ServiceSubtaskRes> serviceSubtaskRes) {
-        List<ServiceSubtaskDetailRatioExport> serviceSubtaskDetailRatioExports = null;
-        List<String> serviceTypeList = sltdMydTotalVO.getServiceTypeList();
-        if (CollectionUtils.isEmpty(serviceTypeList)) {
-            log.warn("getDetailRatio 鏈紶 serviceTypeList锛屾棤娉曠粺璁¢棶棰樺崰姣�");
-            return serviceSubtaskDetailRatioExports;
-        }
-        // 鍒ゆ柇鍙e緞锛�2=闅忚锛堟寜 taskid 缁熻锛夛紝6/14=婊℃剰搴︼紙鎸� subtask id 缁熻锛�
-        boolean hasFollowUp = serviceTypeList.contains("2");
-        boolean hasSatisfaction = serviceTypeList.contains("6") || serviceTypeList.contains("14");
-        if (hasFollowUp && hasSatisfaction) {
-            //serviceType锛屽嵆鏈夐殢璁垮張鏈夋弧鎰忓害锛屾殏鏃舵病娉曠粺璁�
-            log.warn("getDetailRatio 鏆備笉鏀寔鍚屾椂缁熻闅忚(2)涓庢弧鎰忓害(6/14)锛宻erviceTypeList={}", serviceTypeList);
-            return serviceSubtaskDetailRatioExports;
-        }
-        if (hasFollowUp) {
-            //鑾峰彇serviceSubtaskList涓殑鎵�鏈塼askid
-            List<Long> taskIds = serviceSubtaskRes.stream().map(ServiceSubtaskRes::getTaskid).filter(ObjectUtils::isNotEmpty).distinct().collect(Collectors.toList());
-            //鍚屾椂甯︿笂鏈瀵煎嚭鐨勫瓙浠诲姟 id锛氳銆岄棶棰樿鎯呭崰姣斻�嶅彧缁熻杩欐壒鎮h�咃紝涓庛�屾偅鑰呴殢璁夸俊鎭〃鍗曘�嶅彛寰勪竴鑷�
-            List<Long> subtaskIds = serviceSubtaskRes.stream().map(ServiceSubtaskRes::getId).filter(ObjectUtils::isNotEmpty).distinct().collect(Collectors.toList());
-            serviceSubtaskDetailRatioExports = statQuestionOption(taskIds, sltdMydTotalVO.getStartFinishTime(), sltdMydTotalVO.getEndFinishTime(), sltdMydTotalVO.getStartOutHospTime(), sltdMydTotalVO.getEndOutHospTime(), sltdMydTotalVO.getQuestionContent(), sltdMydTotalVO.getStartScore(), sltdMydTotalVO.getEndScore(), sltdMydTotalVO.getValueType(), subtaskIds);
-        } else if (hasSatisfaction) {
-            //婊℃剰搴︾粺璁� 锛堥�氳繃 subtask 鐨� id 鍘荤粺璁★級
-            List<Long> subtaskIds = serviceSubtaskRes.stream().map(ServiceSubtaskRes::getId).collect(Collectors.toList());
-            serviceSubtaskDetailRatioExports = statQuestionOptionBySubtaskIds(subtaskIds, sltdMydTotalVO.getStartOutHospTime(), sltdMydTotalVO.getEndOutHospTime(), sltdMydTotalVO.getStartFinishTime(), sltdMydTotalVO.getEndFinishTime(), sltdMydTotalVO.getQuestionContent(), sltdMydTotalVO.getStartScore(), sltdMydTotalVO.getEndScore(), sltdMydTotalVO.getValueType());
-        } else {
-            log.warn("getDetailRatio 鏆備笉鏀寔鐨� serviceTypeList={}锛堜粎鏀寔 2 鎴� 6/14锛�", serviceTypeList);
-        }
-        return serviceSubtaskDetailRatioExports;
-    }
-
     //鑾峰彇浠诲姟缁勪俊鎭�
     private void findServiceTaskScheduleVO(ServiceTaskVO serviceTaskVO) {
         if (StringUtils.isNotEmpty(serviceTaskVO.getAppltype()) && serviceTaskVO.getAppltype().equals("5")) {
@@ -6594,307 +5980,6 @@
             }
         }
         log.info("ai鍖归厤鎴愬姛锛屽尮閰嶇粨鏋滄槸:" + matchedText);
-    }
-
-    private static String trimAll(String s) {
-        if (s == null) {
-            return "";
-        }
-        int start = 0;
-        int end = s.length();
-        while (start < end && isBlankChar(s.charAt(start))) {
-            start++;
-        }
-        while (end > start && isBlankChar(s.charAt(end - 1))) {
-            end--;
-        }
-        return s.substring(start, end);
-    }
-
-    private static Boolean isBlankChar(char c) {
-        return c == '\u3000'          // 鍏ㄨ绌烘牸
-                || c == ' '            // 鍗婅绌烘牸
-                || c == '\t' || c == '\n' || c == '\r' || c == '\f' || Character.isWhitespace(c);
-    }
-
-
-    private String buildKey(String groupName, String questionText, String option) {
-        return groupName + "\u0001" + questionText + "\u0001" + option;
-    }
-
-    private static int parseCount(String s) {
-        if (StringUtils.isEmpty(s)) {
-            return 0;
-        }
-        try {
-            return Integer.parseInt(s.trim());
-        } catch (NumberFormatException e) {
-            return 0;
-        }
-    }
-
-    private List<MydTrendReqDTO> getWeekDate(MydTrendReqVO mydTrendReqVO, List<MydTrendReqDTO> list) {
-        // 1. 璁$畻鎬诲懆鏁帮紙鍜� SQL 閲岀殑鍏紡淇濇寔涓�鑷达級
-        long days = (mydTrendReqVO.getEndFinishTime().getTime() - mydTrendReqVO.getStartFinishTime().getTime()) / (1000L * 60 * 60 * 24);
-        int totalWeeks = (int) (days / 7) + 1;
-
-        // 2. 宸叉湁鏁版嵁锛歱eriod -> totalScore
-        Map<Integer, String> exist = new HashMap<>();
-        for (MydTrendReqDTO row : list) {
-            // period 鏄暟瀛楋紝鍙兘鏄� Integer / Long / BigDecimal锛岀粺涓�杞� int
-            int p = Integer.parseInt(StringUtils.isNotEmpty(row.getPeriod()) ? row.getPeriod() : "0");
-            exist.put(p, row.getTotalScore() != null ? row.getTotalScore() : "0");
-            // 涓嬮潰杩欒鎸変綘鐨勫師閫昏緫淇濈暀锛堣櫧鐒剁湅璧锋潵鏈夌偣澶氫綑锛�
-            exist.put(p, String.valueOf(row.getTotalScore() != null ? row.getTotalScore() : 0));
-        }
-
-        // 3. 琛ュ叏 1..totalWeeks
-        List<MydTrendReqDTO> result = new ArrayList<>(totalWeeks);
-
-        // 鑾峰彇鏁翠綋鐨勫紑濮嬫椂闂�
-        long startTimeMillis = mydTrendReqVO.getStartFinishTime().getTime();
-        long endTimeMillis = mydTrendReqVO.getEndFinishTime().getTime();
-        long oneWeekMillis = 7L * 24 * 60 * 60 * 1000; // 涓�鍛ㄧ殑姣鏁�
-
-        for (int i = 1; i <= totalWeeks; i++) {
-            MydTrendReqDTO row = new MydTrendReqDTO();
-            row.setPeriod(String.valueOf(i));
-            row.setTotalScore(exist.getOrDefault(i, "0"));
-
-            // -------- 鏂板锛氳缃瘡鍛ㄧ殑 startDate 鍜� endDate --------
-            // 褰撳墠鍛ㄥ紑濮嬫椂闂� = 鏁翠綋寮�濮嬫椂闂� + (i - 1) * 7澶�
-            long currentStartMillis = startTimeMillis + (long) (i - 1) * oneWeekMillis;
-
-            // 褰撳墠鍛ㄧ粨鏉熸椂闂� = 褰撳墠鍛ㄥ紑濮嬫椂闂� + 7澶� - 1姣锛堜繚璇佹槸杩欎竴鍛ㄧ殑鏈�鍚庝竴鍒伙級
-            long currentEndMillis = currentStartMillis + oneWeekMillis - 1;
-
-            // 濡傛灉鏄渶鍚庝竴鍛紝缁撴潫鏃堕棿涓嶈兘瓒呰繃鏁翠綋鐨� endTime
-            if (i == totalWeeks) {
-                currentEndMillis = endTimeMillis;
-            }
-
-            row.setStartDate(new Date(currentStartMillis));
-            row.setEndDate(new Date(currentEndMillis));
-            // ----------------------------------------------------
-
-            result.add(row);
-        }
-
-        return result;
-    }
-
-    private List<MydTrendReqDTO> getYearDate(MydTrendReqVO mydTrendReqVO, List<MydTrendReqDTO> list) {
-        // 鑾峰彇鏁翠綋寮�濮嬪拰缁撴潫鏃堕棿
-        Date startTime = mydTrendReqVO.getStartFinishTime();
-        Date endTime = mydTrendReqVO.getEndFinishTime();
-
-        // 1. 璁$畻骞翠唤鑼冨洿
-        // 浣跨敤 Calendar 鑾峰彇璧峰骞翠唤鍜岀粨鏉熷勾浠�
-        Calendar cal = Calendar.getInstance();
-        cal.setTime(startTime);
-        int startYear = cal.get(Calendar.YEAR);
-
-        cal.setTime(endTime);
-        int endYear = cal.get(Calendar.YEAR);
-
-        // 鎬诲勾鏁� (鍖呭惈棣栧熬)
-        int totalYears = endYear - startYear + 1;
-
-        // 2. 宸叉湁鏁版嵁锛歱eriod -> totalScore (鍋囪 period 瀛樼殑鏄勾浠藉瓧绗︿覆锛屽 "2024")
-        Map<String, String> exist = new HashMap<>();
-        for (MydTrendReqDTO row : list) {
-            String period = row.getPeriod() != null ? row.getPeriod() : "";
-            exist.put(period, row.getTotalScore() != null ? row.getTotalScore() : "0");
-        }
-
-        // 3. 琛ュ叏鎵�鏈夊勾浠�
-        List<MydTrendReqDTO> result = new ArrayList<>(totalYears);
-
-        for (int i = 0; i < totalYears; i++) {
-            int currentYear = startYear + i;
-            String periodKey = String.valueOf(currentYear);
-
-            MydTrendReqDTO row = new MydTrendReqDTO();
-            row.setPeriod(periodKey);
-            // 濡傛灉娌℃湁璇ュ勾浠界殑鏁版嵁锛岄粯璁ょ粰 "0"
-            row.setTotalScore(exist.getOrDefault(periodKey, "0"));
-
-            // -------- 璁剧疆璇ュ勾浠界殑 startDate 鍜� endDate --------
-            Calendar yearCal = Calendar.getInstance();
-            // 璁剧疆涓鸿骞寸殑 1鏈�1鏃� 00:00:00
-            yearCal.set(currentYear, Calendar.JANUARY, 1, 0, 0, 0);
-            yearCal.set(Calendar.MILLISECOND, 0);
-            row.setStartDate(yearCal.getTime());
-
-            // 璁剧疆涓鸿骞寸殑 12鏈�31鏃� 23:59:59
-            yearCal.set(currentYear, Calendar.DECEMBER, 31, 23, 59, 59);
-            yearCal.set(Calendar.MILLISECOND, 999);
-            row.setEndDate(yearCal.getTime());
-            // ----------------------------------------------------
-
-            result.add(row);
-        }
-
-        return result;
-    }
-
-    private List<MydTrendReqDTO> getMonthDate(MydTrendReqVO mydTrendReqVO, List<MydTrendReqDTO> list) {
-        // 鑾峰彇鏁翠綋寮�濮嬪拰缁撴潫鏃堕棿
-        Date startTime = mydTrendReqVO.getStartFinishTime();
-        Date endTime = mydTrendReqVO.getEndFinishTime();
-
-        // 1. 璁$畻鏈堜唤鑼冨洿
-        Calendar cal = Calendar.getInstance();
-        cal.setTime(startTime);
-        int startYear = cal.get(Calendar.YEAR);
-        int startMonth = cal.get(Calendar.MONTH); // 娉ㄦ剰锛欳alendar.MONTH 浠� 0 寮�濮�
-
-        cal.setTime(endTime);
-        int endYear = cal.get(Calendar.YEAR);
-        int endMonth = cal.get(Calendar.MONTH);
-
-        // 鎬绘湀鏁� = (缁撴潫骞� - 寮�濮嬪勾) * 12 + (缁撴潫鏈� - 寮�濮嬫湀) + 1
-        int totalMonths = (endYear - startYear) * 12 + (endMonth - startMonth) + 1;
-
-        // 2. 宸叉湁鏁版嵁锛歱eriod -> totalScore (period 瀛樼殑鏄湀浠藉瓧绗︿覆锛屽 "2024-06")
-        Map<String, String> exist = new HashMap<>();
-        for (MydTrendReqDTO row : list) {
-            String period = row.getPeriod() != null ? row.getPeriod() : "";
-            exist.put(period, row.getTotalScore() != null ? row.getTotalScore() : "0");
-        }
-
-        // 3. 琛ュ叏鎵�鏈夋湀浠�
-        List<MydTrendReqDTO> result = new ArrayList<>(totalMonths);
-
-        Calendar monthCal = Calendar.getInstance();
-        monthCal.set(startYear, startMonth, 1, 0, 0, 0);
-        monthCal.set(Calendar.MILLISECOND, 0);
-
-        for (int i = 0; i < totalMonths; i++) {
-            int currentYear = monthCal.get(Calendar.YEAR);
-            int currentMonth = monthCal.get(Calendar.MONTH) + 1; // 杞垚 1-12 鏈�
-
-            // 鎷兼帴 period锛屾牸寮忥細yyyy-MM锛屾瘮濡� "2024-06"
-            // 濡傛灉浣犲笇鏈涙槸 "2024-6"锛屽彲浠ユ妸 String.format 鏀规垚 currentMonth
-            String periodKey = String.format("%04d-%02d", currentYear, currentMonth);
-
-            MydTrendReqDTO row = new MydTrendReqDTO();
-            row.setPeriod(periodKey);
-            row.setTotalScore(exist.getOrDefault(periodKey, "0"));
-
-            // -------- 璁剧疆璇ユ湀鐨� startDate 鍜� endDate --------
-            // 褰撴湀寮�濮嬫椂闂达細1鍙� 00:00:00.000
-            Calendar startCal = (Calendar) monthCal.clone();
-            row.setStartDate(startCal.getTime());
-
-            // 褰撴湀缁撴潫鏃堕棿锛氫笅涓湀1鍙� 00:00:00.000 鍑� 1 姣
-            Calendar endCal = (Calendar) monthCal.clone();
-            endCal.add(Calendar.MONTH, 1);
-            endCal.add(Calendar.MILLISECOND, -1);
-            row.setEndDate(endCal.getTime());
-            // ----------------------------------------------------
-
-            result.add(row);
-
-            // 杩涘叆涓嬩竴涓湀
-            monthCal.add(Calendar.MONTH, 1);
-        }
-
-        return result;
-    }
-
-    private List<MydTrendReqDTO> getDayDate(MydTrendReqVO mydTrendReqVO, List<MydTrendReqDTO> list) {
-        // 鑾峰彇鏁翠綋寮�濮嬪拰缁撴潫鏃堕棿
-        Date startTime = mydTrendReqVO.getStartFinishTime();
-        Date endTime = mydTrendReqVO.getEndFinishTime();
-
-        // 1. 璁$畻鎬诲ぉ鏁�
-        long oneDayMillis = 24L * 60 * 60 * 1000;
-        // 娉ㄦ剰锛氳繖閲屽彧绠楁棩鏈熷樊锛屼笉鑰冭檻鏃跺垎绉掞紝鍚﹀垯鍙兘宸竴澶�
-        Calendar startCal = Calendar.getInstance();
-        startCal.setTime(startTime);
-        startCal.set(Calendar.HOUR_OF_DAY, 0);
-        startCal.set(Calendar.MINUTE, 0);
-        startCal.set(Calendar.SECOND, 0);
-        startCal.set(Calendar.MILLISECOND, 0);
-
-        Calendar endCal = Calendar.getInstance();
-        endCal.setTime(endTime);
-        endCal.set(Calendar.HOUR_OF_DAY, 0);
-        endCal.set(Calendar.MINUTE, 0);
-        endCal.set(Calendar.SECOND, 0);
-        endCal.set(Calendar.MILLISECOND, 0);
-
-        long days = (endCal.getTimeInMillis() - startCal.getTimeInMillis()) / oneDayMillis;
-        int totalDays = (int) days + 1;
-
-        // 2. 宸叉湁鏁版嵁锛歱eriod -> totalScore (period 瀛樼殑鏄棩鏈熷瓧绗︿覆锛屽 "2024-06-01")
-        Map<String, String> exist = new HashMap<>();
-        for (MydTrendReqDTO row : list) {
-            String period = row.getPeriod() != null ? row.getPeriod() : "";
-            exist.put(period, row.getTotalScore() != null ? row.getTotalScore() : "0");
-        }
-
-        // 3. 琛ュ叏鎵�鏈夋棩鏈�
-        List<MydTrendReqDTO> result = new ArrayList<>(totalDays);
-
-        Calendar dayCal = (Calendar) startCal.clone();
-        for (int i = 0; i < totalDays; i++) {
-            // 鎷兼帴 period锛屾牸寮忥細yyyy-MM-dd
-            String periodKey = String.format("%04d-%02d-%02d", dayCal.get(Calendar.YEAR), dayCal.get(Calendar.MONTH) + 1, dayCal.get(Calendar.DAY_OF_MONTH));
-
-            MydTrendReqDTO row = new MydTrendReqDTO();
-            row.setPeriod(periodKey);
-            row.setTotalScore(exist.getOrDefault(periodKey, "0"));
-
-            // -------- 璁剧疆璇ュぉ鐨� startDate 鍜� endDate --------
-            Calendar startOfDay = (Calendar) dayCal.clone();
-            row.setStartDate(startOfDay.getTime());
-
-            Calendar endOfDay = (Calendar) dayCal.clone();
-            endOfDay.set(Calendar.HOUR_OF_DAY, 23);
-            endOfDay.set(Calendar.MINUTE, 59);
-            endOfDay.set(Calendar.SECOND, 59);
-            endOfDay.set(Calendar.MILLISECOND, 999);
-            row.setEndDate(endOfDay.getTime());
-            // ----------------------------------------------------
-
-            result.add(row);
-
-            // 杩涘叆涓嬩竴澶�
-            dayCal.add(Calendar.DAY_OF_MONTH, 1);
-        }
-
-        return result;
-    }
-
-    /**
-     * 闂ㄨ瘖婊℃剰搴︾患鍚堝緱鍒嗘帓鍚嶏紙鎸夌瀹ゆ垨鐥呭尯锛�
-     * <p>
-     * 缁煎悎婊℃剰搴� = 危(璇勫垎鏄庣粏鏉冮噸) / 璇勫垎鏄庣粏鏉℃暟 脳 100%锛�
-     * 鏉冮噸鍙彇 1銆�0.8銆�0.6銆�0.4銆�0.2 浜旀。锛堝緢婊℃剰銆佹弧鎰忋�佷竴鑸�佷笉婊℃剰銆佸緢涓嶆弧鎰忥級銆�
-     *
-     * @param mzMydScoreRankVO 鏌ヨ鏉′欢
-     * @return 鎺掑悕缁撴灉锛屽悕娆′粠 1 寮�濮嬮『搴忕紪鍙�
-     */
-    @Override
-    public List<MzMydScoreRankDTO> mzMydScoreRank(MzMydScoreRankVO mzMydScoreRankVO) {
-        if (StringUtils.isEmpty(mzMydScoreRankVO.getDimensionType())) {
-            mzMydScoreRankVO.setDimensionType("1");
-        }
-        if (CollectionUtils.isEmpty(mzMydScoreRankVO.getServiceTypes())) {
-          List<String> list=new ArrayList<>(Arrays.asList("14"));
-            mzMydScoreRankVO.setServiceTypes(list);
-        }
-        List<MzMydScoreRankDTO> list = serviceSubtaskMapper.selectMzMydScoreRank(mzMydScoreRankVO);
-        if (CollectionUtils.isEmpty(list)) {
-            return new ArrayList<>();
-        }
-        // 鍚嶆锛歋QL 宸叉寜缁煎悎寰楀垎闄嶅簭杩斿洖锛岃繖閲岄『搴忕紪鍙�
-        for (int i = 0; i < list.size(); i++) {
-            list.get(i).setRank(i + 1);
-        }
-        return list;
     }
 
     // ---------------------------------------------------------------------
diff --git a/smartor/src/main/java/com/smartor/service/impl/UserExtInfoServiceImpl.java b/smartor/src/main/java/com/smartor/service/impl/UserExtInfoServiceImpl.java
index 4e4fdbf..76da7e3 100644
--- a/smartor/src/main/java/com/smartor/service/impl/UserExtInfoServiceImpl.java
+++ b/smartor/src/main/java/com/smartor/service/impl/UserExtInfoServiceImpl.java
@@ -1,45 +1,32 @@
 package com.smartor.service.impl;
 
 import com.ruoyi.common.core.service.IUserExtInfoService;
-import com.ruoyi.common.core.redis.RedisCache;
 import com.ruoyi.common.utils.StringUtils;
-import com.smartor.domain.IvrLibaScriptAssort;
-import com.smartor.domain.SvyLibScriptCategory;
 import com.smartor.domain.SysUserOrg;
-import com.smartor.mapper.IvrLibaScriptAssortMapper;
-import com.smartor.mapper.SvyLibScriptCategoryMapper;
 import com.smartor.mapper.SysUserOrgMapper;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import org.springframework.util.CollectionUtils;
 
-import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.concurrent.TimeUnit;
 
 /**
  * 鐢ㄦ埛鎵╁睍淇℃伅鏈嶅姟瀹炵幇锛坰martor 妯″潡锛�
  * 渚� ruoyi-system 閫氳繃 SpringUtils 璋冪敤
+ * <p>
+ * 杩佺Щ璇存槑锛氬師 {@code getSatisfactionCategories}锛堝惈 Redis 缂撳瓨銆佽闊�/闂嵎鍒嗙被鏌ヨ锛�
+ * 灞炰簬婊℃剰搴﹂�昏緫锛屽凡鏁存鎼叆 ruoyi-satisfaction 妯″潡锛岀敱
+ * {@code com.ruoyi.satisfaction.api.SatisfactionFacade} 瀹炵幇
+ * {@code com.ruoyi.common.core.service.ISatisfactionCategoryService}銆�
+ * 鏈被鍥犳涓嶅啀渚濊禆 {@code IvrLibaScriptAssortMapper} / {@code SvyLibScriptCategoryMapper} / {@code RedisCache}銆�
  */
 @Service
 public class UserExtInfoServiceImpl implements IUserExtInfoService {
 
-    private static final String SATISFACTION_CACHE_KEY = "satisfaction:categories:";
-    private static final int SATISFACTION_CACHE_TTL = 30;
-
     @Autowired
     private SysUserOrgMapper sysUserOrgMapper;
-
-    @Autowired
-    private IvrLibaScriptAssortMapper ivrLibaScriptAssortMapper;
-
-    @Autowired
-    private SvyLibScriptCategoryMapper svyLibScriptCategoryMapper;
-
-    @Autowired
-    private RedisCache redisCache;
 
     /**
      * 鑾峰彇鐢ㄦ埛鏈烘瀯/闄㈠尯淇℃伅
@@ -57,58 +44,5 @@
             result.put("campusid", sysUserOrgs.get(0).getCampusid());
         }
         return result;
-    }
-
-    /**
-     * 鑾峰彇婊℃剰搴﹂棶棰樺垎绫伙紙璇煶 + 闂嵎锛夛紝甯edis缂撳瓨
-     */
-    @Override
-    @SuppressWarnings("unchecked")
-    public Map<String, Object> getSatisfactionCategories(String orgid) {
-        // 鍏堟煡缂撳瓨
-        String cacheKey = SATISFACTION_CACHE_KEY + orgid;
-        Map<String, Object> cached = redisCache.getCacheObject(cacheKey);
-        if (cached != null) {
-            return cached;
-        }
-
-        Map<String, Object> satisfactionCategories = new HashMap<>();
-
-        // 鑾峰彇璇煶闂鍒嗙被
-        IvrLibaScriptAssort ivrLibaScriptAssort = new IvrLibaScriptAssort();
-        ivrLibaScriptAssort.setOrgid(orgid);
-        ivrLibaScriptAssort.setType("myd");
-        List<IvrLibaScriptAssort> ivrLibaScriptAssorts = ivrLibaScriptAssortMapper.selectIvrLibaScriptAssortList(ivrLibaScriptAssort);
-        List<Map<String, Object>> categoryList = new ArrayList<>();
-        if (!CollectionUtils.isEmpty(ivrLibaScriptAssorts)) {
-            for (IvrLibaScriptAssort assort : ivrLibaScriptAssorts) {
-                Map<String, Object> map = new HashMap<>();
-                map.put("categoryid", assort.getId());
-                map.put("categoryName", assort.getIndexAssortName());
-                categoryList.add(map);
-            }
-        }
-        satisfactionCategories.put("voiceCategories", categoryList);
-
-        // 鑾峰彇闂嵎闂鍒嗙被
-        SvyLibScriptCategory svyLibScriptCategory = new SvyLibScriptCategory();
-        svyLibScriptCategory.setOrgid(orgid);
-        svyLibScriptCategory.setType("myd");
-        List<SvyLibScriptCategory> svyLibScriptCategories = svyLibScriptCategoryMapper.selectSvyLibScriptCategoryList(svyLibScriptCategory);
-        List<Map<String, Object>> questionnaireCategorys = new ArrayList<>();
-        if (!CollectionUtils.isEmpty(svyLibScriptCategories)) {
-            for (SvyLibScriptCategory scriptCategory : svyLibScriptCategories) {
-                Map<String, Object> map = new HashMap<>();
-                map.put("categoryid", scriptCategory.getId());
-                map.put("categoryName", scriptCategory.getName());
-                questionnaireCategorys.add(map);
-            }
-        }
-        satisfactionCategories.put("questionnaireCategorys", questionnaireCategorys);
-
-        // 鍐欏叆缂撳瓨
-        redisCache.setCacheObject(cacheKey, satisfactionCategories, SATISFACTION_CACHE_TTL, TimeUnit.MINUTES);
-
-        return satisfactionCategories;
     }
 }
diff --git a/smartor/src/main/java/com/smartor/statemachine/README.java b/smartor/src/main/java/com/smartor/statemachine/README.java
new file mode 100644
index 0000000..43ed78c
--- /dev/null
+++ b/smartor/src/main/java/com/smartor/statemachine/README.java
@@ -0,0 +1,258 @@
+/**
+ * ============================================================================
+ * 浣忛櫌鐘舵�佹満闆嗘垚鎸囧崡
+ * ============================================================================
+ * 
+ * 涓�銆佺姸鎬佹満鏂囦欢娓呭崟
+ * ============================================================================
+ * 
+ * 1. 鏍稿績鏋氫妇
+ *    鈹斺攢 com.smartor.common.enums.InhospStateEnum
+ *       瀹氫箟鐘舵�佸強杞崲瑙勫垯
+ * 
+ * 2. 寮傚父绫�
+ *    鈹斺攢 com.smartor.common.exception.StateTransitionException
+ *       澶勭悊闈炴硶鐘舵�佽浆鎹�
+ * 
+ * 3. 澶勭悊鍣ㄦ帴鍙�
+ *    鈹斺攢 com.smartor.statemachine.handler.InhospStateHandler
+ *       鐘舵�佸鐞嗗櫒鎺ュ彛
+ * 
+ * 4. 澶勭悊鍣ㄥ疄鐜�
+ *    鈹溾攢 com.smartor.statemachine.handler.impl.PreAdmissionHandler
+ *    鈹�  澶勭悊棰勫叆闄�(state=3)
+ *    鈹溾攢 com.smartor.statemachine.handler.impl.InHospitalHandler
+ *    鈹�  澶勭悊鍏ラ櫌(state=0)
+ *    鈹斺攢 com.smartor.statemachine.handler.impl.DischargeHandler
+ *       澶勭悊鍑洪櫌(state=1)
+ * 
+ * 5. 鐘舵�佹満绠$悊鍣�
+ *    鈹斺攢 com.smartor.statemachine.InhospStateMachine
+ *       鍗忚皟鐘舵�佽浆鎹€�佺鐞嗗垎甯冨紡閿�
+ * 
+ * 6. 闂ㄩ潰鏈嶅姟
+ *    鈹斺攢 com.smartor.statemachine.InhospStateMachineService
+ *       瀵瑰API鎺ュ彛
+ * 
+ * 7. 浣跨敤绀轰緥
+ *    鈹斺攢 com.smartor.statemachine.InhospStateMachineExample
+ *       闆嗘垚绀轰緥浠g爜
+ * 
+ * 
+ * 浜屻�佸浣曞湪鐜版湁浠g爜涓泦鎴�
+ * ============================================================================
+ * 
+ * 鍘熶唬鐮佷綅缃細
+ * ServiceSLTDHealthcareRecordServiceImpl.processPatientInhospInfo (L561-677)
+ * 
+ * 銆愭柟妗�1銆戝畬鍏ㄦ浛鎹紙鎺ㄨ崘鐢ㄤ簬鏂板姛鑳斤級
+ * -----------------------------------------
+ * 
+ * @Autowired
+ * private InhospStateMachineService stateMachineService;
+ * 
+ * private void processPatientInhospInfo(ServiceSLTDInhospResDTO dto, PatArchive patArchive, String cry) {
+ *     PatMedInhosp patMedInhosp = buildPatientInhospInfo(dto, patArchive, cry);
+ *     stateMachineService.processStateChange(patMedInhosp, cry);
+ * }
+ * 
+ * 
+ * 銆愭柟妗�2銆戝弻鍐欐ā寮忥紙鎺ㄨ崘鐢ㄤ簬鐢熶骇鐜楠岃瘉锛�
+ * -----------------------------------------
+ * 
+ * @Autowired
+ * private InhospStateMachineService stateMachineService;
+ * 
+ * private void processPatientInhospInfo(ServiceSLTDInhospResDTO dto, PatArchive patArchive, String cry) {
+ *     PatMedInhosp patMedInhosp = buildPatientInhospInfo(dto, patArchive, cry);
+ *     
+ *     // 鏂瑰紡A锛氫繚鐣欏師鏈夐�昏緫
+ *     // processPatientInhospInfoOld(dto, patArchive, cry);
+ *     
+ *     // 鏂瑰紡B锛氫娇鐢ㄧ姸鎬佹満
+ *     try {
+ *         boolean success = stateMachineService.processStateChange(patMedInhosp, cry);
+ *         log.info("銆愮姸鎬佹満銆戝鐞嗙粨鏋滐細{}, serialnum={}, cry={}", success, patMedInhosp.getSerialnum(), cry);
+ *     } catch (Exception e) {
+ *         log.error("銆愮姸鎬佹満銆戝鐞嗗紓甯革細serialnum={}, cry={}, 閿欒锛歿}", patMedInhosp.getSerialnum(), cry, e.getMessage());
+ *     }
+ * }
+ * 
+ * 
+ * 銆愭柟妗�3銆戝紑鍏虫帶鍒讹紙鎺ㄨ崘鐢ㄤ簬鐏板害鍙戝竷锛�
+ * -----------------------------------------
+ * 
+ * @Value("${inhosp.statemachine.enabled:false}")
+ * private boolean stateMachineEnabled;
+ * 
+ * @Autowired
+ * private InhospStateMachineService stateMachineService;
+ * 
+ * private void processPatientInhospInfo(ServiceSLTDInhospResDTO dto, PatArchive patArchive, String cry) {
+ *     PatMedInhosp patMedInhosp = buildPatientInhospInfo(dto, patArchive, cry);
+ *     
+ *     if (stateMachineEnabled) {
+ *         // 浣跨敤鐘舵�佹満
+ *         stateMachineService.processStateChange(patMedInhosp, cry);
+ *     } else {
+ *         // 浣跨敤鍘熸湁閫昏緫
+ *         processPatientInhospInfoOld(dto, patArchive, cry);
+ *     }
+ * }
+ * 
+ * 閰嶇疆鏂囦欢 application.yml锛�
+ * inhosp:
+ *   statemachine:
+ *     enabled: true  # 寮�鍚姸鎬佹満
+ * 
+ * 
+ * 涓夈�佹暟鎹簱绱㈠紩寤鸿
+ * ============================================================================
+ * 
+ * 銆愰�夐」1銆戝厑璁稿鐘舵�佽褰曪紙鎺ㄨ崘锛屼笌鐜版湁閫昏緫鍏煎锛�
+ * -----------------------------------------
+ * CREATE UNIQUE INDEX uk_serialnum_orgid_state 
+ * ON pat_med_inhosp(serialnum, orgid, inhospstate);
+ * 
+ * 浼樼偣锛�
+ * - 涓庣幇鏈夋暟鎹吋瀹�
+ * - 鍙嫤鎴浉鍚岀姸鎬佺殑閲嶅鎻掑叆
+ * - 鐘舵�佹満澶勭悊鍣ㄥ彲浠ュ喅瀹氭槸鏂板杩樻槸鏇存柊
+ * 
+ * 
+ * 銆愰�夐」2銆戝己鍒跺崟璁板綍锛堥渶鏁版嵁杩佺Щ锛�
+ * -----------------------------------------
+ * CREATE UNIQUE INDEX uk_serialnum_orgid 
+ * ON pat_med_inhosp(serialnum, orgid);
+ * 
+ * 浼樼偣锛�
+ * - 鏁版嵁涓�鑷存�ф洿寮�
+ * - 閬垮厤涓�鎮e璁板綍闂
+ * 
+ * 缂虹偣锛�
+ * - 闇�瑕佹竻鐞嗗巻鍙叉暟鎹�
+ * - 鎵�鏈夌姸鎬佸彉鏇村繀椤昏蛋鏇存柊
+ * 
+ * 鏁版嵁娓呯悊鑴氭湰锛�
+ * -- 淇濈暀姣忎釜鎮h�呮渶鏂扮姸鎬佺殑璁板綍锛屽垹闄ゅ叾浠栬褰�
+ * DELETE t1 FROM pat_med_inhosp t1
+ * INNER JOIN (
+ *     SELECT serialnum, orgid, MAX(inhospid) as max_id
+ *     FROM pat_med_inhosp
+ *     GROUP BY serialnum, orgid
+ *     HAVING COUNT(*) > 1
+ * ) t2 ON t1.serialnum = t2.serialnum 
+ *     AND t1.orgid = t2.orgid 
+ *     AND t1.inhospid < t2.max_id;
+ * 
+ * 
+ * 鍥涖�佺姸鎬佹満鏍稿績浼樺娍
+ * ============================================================================
+ * 
+ * 1. 骞跺彂瀹夊叏
+ *    鉁� 鑷姩鍔犲垎甯冨紡閿侊紙Redis锛�
+ *    鉁� 閿佺矑搴︼細serialnum + orgid
+ *    鉁� 瓒呮椂鏃堕棿锛�10绉掞紙鍙厤缃級
+ *    鉁� Redis涓嶅彲鐢ㄦ椂鑷姩闄嶇骇
+ * 
+ * 2. 鐘舵�佹牎楠�
+ *    鉁� 寮哄埗鍗曞悜娴佽浆锛�3(棰勫叆闄�) 鈫� 0(鍏ラ櫌) 鈫� 1(鍑洪櫌)
+ *    鉁� 闃绘闈炴硶杞崲锛堝锛氶鍏ラ櫌鈫掑嚭闄級
+ *    鉁� 闃绘閲嶅鎿嶄綔锛堝锛氶噸澶嶅叆闄級
+ * 
+ * 3. 浠g爜绠�鍖�
+ *    鉁� 100+琛宨f-else 鈫� 10琛岀姸鎬佹満璋冪敤
+ *    鉁� 绛栫暐妯″紡锛屾槗鎵╁睍
+ *    鉁� 闆嗕腑寮忕姸鎬佺鐞�
+ * 
+ * 4. 鏃ュ織瀹屽杽
+ *    鉁� 姣忎釜鐘舵�佽浆鎹㈤兘鏈夋棩蹇�
+ *    鉁� 寮傚父淇℃伅璇︾粏
+ *    鉁� 渚夸簬闂鎺掓煡
+ * 
+ * 
+ * 浜斻�佹祴璇曢獙璇佸缓璁�
+ * ============================================================================
+ * 
+ * 1. 鍗曞厓娴嬭瘯
+ *    - 娴嬭瘯鐘舵�佹灇涓剧殑杞崲瑙勫垯
+ *    - 娴嬭瘯姣忎釜Handler鐨勯�昏緫
+ *    - 娴嬭瘯鐘舵�佹満鐨勯攣鏈哄埗
+ * 
+ * 2. 闆嗘垚娴嬭瘯
+ *    - 姝e父娴佺▼锛氶鍏ラ櫌鈫掑叆闄⑩啋鍑洪櫌
+ *    - 璺宠繃棰勫叆闄細鐩存帴鍏ラ櫌鈫掑嚭闄�
+ *    - 寮傚父娴佺▼锛氶噸澶嶆搷浣溿�侀潪娉曡浆鎹�
+ * 
+ * 3. 骞跺彂娴嬭瘯
+ *    - 浣跨敤JMeter妯℃嫙骞跺彂璇锋眰
+ *    - 楠岃瘉鍚屼竴鎮h��100骞跺彂涓嶄細閲嶅鎻掑叆
+ *    - 楠岃瘉Redis閿佺殑鏈夋晥鎬�
+ * 
+ * 4. 鐢熶骇楠岃瘉
+ *    - 鍏堝紑鍚弻鍐欐ā寮�
+ *    - 瀵规瘮鍘熼�昏緫涓庣姸鎬佹満缁撴灉
+ *    - 楠岃瘉1-2鍛ㄥ悗瀹屽叏鍒囨崲
+ * 
+ * 
+ * 鍏�侀厤缃弬鏁帮紙鍙�夛級
+ * ============================================================================
+ * 
+ * application.yml 鍙坊鍔犱互涓嬮厤缃細
+ * 
+ * inhosp:
+ *   statemachine:
+ *     enabled: true                  # 鏄惁鍚敤鐘舵�佹満
+ *     lock-timeout: 10               # 鍒嗗竷寮忛攣瓒呮椂鏃堕棿锛堢锛�
+ *     allow-direct-discharge: false  # 鏄惁鍏佽鏃犲叆闄㈣褰曠洿鎺ュ嚭闄�
+ * 
+ * 
+ * 涓冦�佸父瑙侀棶棰�
+ * ============================================================================
+ * 
+ * Q1: 鐘舵�佹満鏄惁浼氬奖鍝嶆�ц兘锛�
+ * A: 涓嶄細銆傜姸鎬佹満鍐呴儴閫昏緫涓庡師浠g爜鐩镐技锛屽彧鏄粨鏋勬洿娓呮櫚銆俁edis閿佺殑鑾峰彇鑰楁椂鍦ㄦ绉掔骇銆�
+ * 
+ * Q2: Redis涓嶅彲鐢ㄦ椂浼氭�庢牱锛�
+ * A: 鐘舵�佹満浼氳嚜鍔ㄩ檷绾т负鏃犻攣妯″紡锛屼緷鐒跺彲浠ユ墽琛岋紝浣嗗苟鍙戝畨鍏ㄤ緷璧栨暟鎹簱鍞竴绱㈠紩銆�
+ * 
+ * Q3: 濡備綍鎵╁睍鏂扮姸鎬侊紵
+ * A: 
+ *    1. 鍦↖nhospStateEnum涓坊鍔犳柊鐘舵��
+ *    2. 鍒涘缓瀵瑰簲鐨凥andler瀹炵幇
+ *    3. 鐘舵�佹満浼氳嚜鍔ㄦ敞鍐屽苟璇嗗埆
+ * 
+ * Q4: 鐘舵�佹満鏄惁鏀寔浜嬪姟锛�
+ * A: 鏀寔銆傜姸鎬佹満璋冪敤鏈韩鍙互鍦ˊTransactional鏂规硶涓娇鐢ㄣ��
+ * 
+ * Q5: 鍑洪櫌鍚庤兘鍚﹀啀鍏ラ櫌锛�
+ * A: 鐩墠閫昏緫涓嶅厑璁搞�傚鏋滈渶瑕佹敮鎸侊紝寤鸿鍦ㄦ暟鎹ā鍨嬩腑澧炲姞"灏辫瘖鎵规"瀛楁銆�
+ * 
+ * 
+ * 鍏�佸悗缁紭鍖栨柟鍚�
+ * ============================================================================
+ * 
+ * 1. 鐘舵�佸彉鏇村巻鍙茶〃
+ *    - 璁板綍姣忔鐘舵�佽浆鎹�
+ *    - 鏀寔鐘舵�佸洖婧�
+ *    - 渚夸簬瀹¤
+ * 
+ * 2. 鐘舵�佹満鍙鍖�
+ *    - 鎻愪緵鐘舵�佹祦杞浘
+ *    - 瀹炴椂鐩戞帶鐘舵�佸垎甯�
+ * 
+ * 3. 琛ュ伩鏈哄埗
+ *    - 鏀寔鐘舵�佸洖婊�
+ *    - 寮傚父鐘舵�佷慨澶�
+ * 
+ * 4. 閰嶇疆鍖栫姸鎬佽鍒�
+ *    - 浠庢暟鎹簱璇诲彇鐘舵�佽浆鎹㈣鍒�
+ *    - 鍔ㄦ�佽皟鏁翠笉闇�瑕侀噸鍚�
+ * 
+ * 
+ * ============================================================================
+ * 鑱旂郴浜猴細smartor寮�鍙戝洟闃�
+ * 鍒涘缓鏃ユ湡锛�2026-01-29
+ * ============================================================================
+ */
+package com.smartor.statemachine;
diff --git a/smartor/src/main/resources/mapper/smartor/PatMedOuthospErrorMapper.xml b/smartor/src/main/resources/mapper/smartor/PatMedOuthospErrorMapper.xml
new file mode 100644
index 0000000..5e3d5f5
--- /dev/null
+++ b/smartor/src/main/resources/mapper/smartor/PatMedOuthospErrorMapper.xml
@@ -0,0 +1,79 @@
+<?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.smartor.mapper.PatMedOuthospErrorMapper">
+
+    <resultMap type="com.smartor.domain.PatMedOuthospError" id="PatMedOuthospErrorResult">
+        <result property="id" column="id"/>
+        <result property="serialnum" column="serialnum"/>
+        <result property="outhospno" column="outhospno"/>
+        <result property="patname" column="patname"/>
+        <result property="patno" column="patno"/>
+        <result property="campusid" column="campusid"/>
+        <result property="orgid" column="orgid"/>
+        <result property="admitdate" column="admitdate"/>
+        <result property="gatherDate" column="gather_date"/>
+        <result property="errorType" column="error_type"/>
+        <result property="errorMsg" column="error_msg"/>
+        <result property="rawData" column="raw_data"/>
+        <result property="failCount" column="fail_count"/>
+        <result property="handleStatus" column="handle_status"/>
+        <result property="handleRemark" column="handle_remark"/>
+        <result property="createTime" column="create_time"/>
+        <result property="updateTime" column="update_time"/>
+    </resultMap>
+
+    <sql id="selectPatMedOuthospErrorVo">
+        select id, serialnum, outhospno, patname, patno, campusid, orgid, admitdate, gather_date,
+               error_type, error_msg, raw_data, fail_count, handle_status, handle_remark, create_time, update_time
+        from pat_med_outhosp_error
+    </sql>
+
+    <!--
+      閲囬泦澶辫触钀借〃锛歴erialnum 涓嶄负绌烘椂闈犲敮涓�绱㈠紩 uk_serialnum_type(serialnum, error_type) 鍋� upsert锛�
+      閬垮厤姣忓崐灏忔椂涓�杞妸鍚屼竴鏉¤剰鏁版嵁閲嶅鍐欒繘鏉ワ紱fail_count 绱姞鍙互鐪嬪埌瀹冨埌搴曞け璐ヤ簡澶氬皯杞��
+      宸茶浜哄伐鏍囪"宸插鐞�"鐨勮褰曞鏋滃啀娆″け璐ワ紝鐘舵�侀噸鏂板洖鍒�"寰呭鐞�"锛�"宸插拷鐣�"鐨勪繚鎸佸拷鐣ヤ笉鍐嶆墦鎵般��
+    -->
+    <insert id="insertPatMedOuthospError" parameterType="com.smartor.domain.PatMedOuthospError"
+            useGeneratedKeys="true" keyProperty="id">
+        insert into pat_med_outhosp_error
+        (serialnum, outhospno, patname, patno, campusid, orgid, admitdate, gather_date,
+         error_type, error_msg, raw_data, fail_count, handle_status, handle_remark, create_time, update_time)
+        values
+        (#{serialnum}, #{outhospno}, #{patname}, #{patno}, #{campusid}, #{orgid}, #{admitdate}, #{gatherDate},
+         #{errorType}, #{errorMsg}, #{rawData}, 1, #{handleStatus}, #{handleRemark}, now(), now())
+        <if test="serialnum != null and serialnum != ''">
+            on duplicate key update
+            error_msg = values(error_msg),
+            raw_data = values(raw_data),
+            admitdate = values(admitdate),
+            fail_count = fail_count + 1,
+            handle_status = case when handle_status = '1' then '0' else handle_status end,
+            update_time = now()
+        </if>
+    </insert>
+
+    <select id="selectPatMedOuthospErrorList" parameterType="com.smartor.domain.PatMedOuthospError"
+            resultMap="PatMedOuthospErrorResult">
+        <include refid="selectPatMedOuthospErrorVo"/>
+        <where>
+            <if test="serialnum != null and serialnum != ''">and serialnum = #{serialnum}</if>
+            <if test="errorType != null and errorType != ''">and error_type = #{errorType}</if>
+            <if test="gatherDate != null and gatherDate != ''">and gather_date = #{gatherDate}</if>
+            <if test="handleStatus != null and handleStatus != ''">and handle_status = #{handleStatus}</if>
+        </where>
+        order by id desc
+    </select>
+
+    <update id="updateHandleStatus" parameterType="com.smartor.domain.PatMedOuthospError">
+        update pat_med_outhosp_error
+        <trim prefix="SET" suffixOverrides=",">
+            <if test="handleStatus != null and handleStatus != ''">handle_status = #{handleStatus},</if>
+            <if test="handleRemark != null">handle_remark = #{handleRemark},</if>
+            update_time = now(),
+        </trim>
+        where id = #{id}
+    </update>
+
+</mapper>
diff --git a/smartor/src/main/resources/mapper/smartor/ServiceSubtaskDetailMapper.xml b/smartor/src/main/resources/mapper/smartor/ServiceSubtaskDetailMapper.xml
index 945af85..6c48fbf 100644
--- a/smartor/src/main/resources/mapper/smartor/ServiceSubtaskDetailMapper.xml
+++ b/smartor/src/main/resources/mapper/smartor/ServiceSubtaskDetailMapper.xml
@@ -283,185 +283,7 @@
         <if test="questiontext != null">and questiontext = #{questiontext}</if>
     </select>
 
-    <!--
-        婊℃剰搴︽煴鐘跺浘锛氫竴娆℃�х粺璁℃湰娆¤姹傚叏閮ㄦā鏉跨殑濉姤閲忋��
-        绛変环浜庡姣忎釜妯℃澘鎵ц queryFillCount(subids, questiontext)锛屽尯鍒槸锛�
-          1) 鐢� join service_subtask 浠f浛鍏堟妸鍏ㄩ儴 sub_id 鏌ュ洖 Java 鍐嶆嫾 IN(...)锛�
-          2) 鎸� libtemplateid + questiontext + score 鍒嗙粍锛屽彧鍥炰紶鍒嗗�间笌鏉℃暟锛�
-          3) visit_time 鏀规垚鍙蛋绱㈠紩鐨勮寖鍥存潯浠躲��
-        娉ㄦ剰锛氳繖閲屽埢鎰忎笉鍦� SQL 閲岃繃婊� questiontext锛岀敱 Java 鎸夊師 queryFillCount 鐨�
-        銆宷uestiontext 涓� null 鏃朵笉鎷艰鏉′欢銆嶈涔夊仛鍖归厤锛屼繚璇佺粨鏋滃彛寰勫畬鍏ㄤ竴鑷淬��
-    -->
-    <select id="selectSatisfactionFillStat" parameterType="com.smartor.domain.entity.SatisfactionStatQuery"
-            resultType="java.util.Map">
-        select ss.libtemplateid as libtemplateid,
-               ssd.questiontext as questiontext,
-               ssd.score as score,
-               count(*) as cnt
-        from service_subtask_detail ssd
-        join service_subtask ss on ss.id = ssd.sub_id
-        where ssd.del_flag = 0
-        and ss.del_flag = 0
-        <if test="orgid != null and orgid != ''">
-            and ss.orgid = #{orgid}
-        </if>
-        <if test="campusid != null and campusid != ''">
-            and ss.campusid = #{campusid}
-        </if>
-        <if test="type != null and type != ''">and ss.type = #{type}</if>
-        <if test="startTime != null">
-            and ss.visit_time &gt;= #{startTime}
-        </if>
-        <if test="endTime != null">
-            and ss.visit_time &lt; date_add(#{endTime}, interval 1 day)
-        </if>
-        <if test="libTemplateIds != null and libTemplateIds.size() > 0">
-            and ss.libtemplateid IN
-            <foreach collection="libTemplateIds" item="libTemplateId" open="(" separator="," close=")">
-                #{libTemplateId}
-            </foreach>
-        </if>
-        <if test="(hospitaldistrictcodes != null and hospitaldistrictcodes.size() > 0) or (deptcodes != null and deptcodes.size() > 0)">
-            AND (
-            <if test="hospitaldistrictcodes != null and hospitaldistrictcodes.size() > 0">
-                ss.leavehospitaldistrictcode IN
-                <foreach collection="hospitaldistrictcodes" item="hospitaldistrictcode" open="("
-                         separator=","
-                         close=")">
-                    #{hospitaldistrictcode}
-                </foreach>
-            </if>
-            <if test="hospitaldistrictcodes != null and hospitaldistrictcodes.size() > 0 and deptcodes != null and deptcodes.size() > 0">
-                OR
-            </if>
-            <if test="deptcodes != null and deptcodes.size() > 0">
-                ss.deptcode IN
-                <foreach collection="deptcodes" item="deptcode" open="(" separator="," close=")">
-                    #{deptcode}
-                </foreach>
-            </if>
-            )
-        </if>
-        group by ss.libtemplateid, ssd.questiontext, ssd.score
-    </select>
-
-    <select id="selectSatisfactionFillPersonCount" parameterType="com.smartor.domain.entity.SatisfactionStatQuery"
-            resultType="java.util.Map">
-        select ss.libtemplateid as libtemplateid,
-               count(distinct ssd.sub_id) as cnt
-        from service_subtask_detail ssd
-        join service_subtask ss on ss.id = ssd.sub_id
-        where ssd.del_flag = 0
-        and ss.del_flag = 0
-        <if test="orgid != null and orgid != ''">
-            and ss.orgid = #{orgid}
-        </if>
-        <if test="campusid != null and campusid != ''">
-            and ss.campusid = #{campusid}
-        </if>
-        <if test="type != null and type != ''">and ss.type = #{type}</if>
-        <if test="startTime != null">
-            and ss.visit_time &gt;= #{startTime}
-        </if>
-        <if test="endTime != null">
-            and ss.visit_time &lt; date_add(#{endTime}, interval 1 day)
-        </if>
-        <if test="libTemplateIds != null and libTemplateIds.size() > 0">
-            and ss.libtemplateid IN
-            <foreach collection="libTemplateIds" item="libTemplateId" open="(" separator="," close=")">
-                #{libTemplateId}
-            </foreach>
-        </if>
-        <if test="(hospitaldistrictcodes != null and hospitaldistrictcodes.size() > 0) or (deptcodes != null and deptcodes.size() > 0)">
-            AND (
-            <if test="hospitaldistrictcodes != null and hospitaldistrictcodes.size() > 0">
-                ss.leavehospitaldistrictcode IN
-                <foreach collection="hospitaldistrictcodes" item="hospitaldistrictcode" open="("
-                         separator=","
-                         close=")">
-                    #{hospitaldistrictcode}
-                </foreach>
-            </if>
-            <if test="hospitaldistrictcodes != null and hospitaldistrictcodes.size() > 0 and deptcodes != null and deptcodes.size() > 0">
-                OR
-            </if>
-            <if test="deptcodes != null and deptcodes.size() > 0">
-                ss.deptcode IN
-                <foreach collection="deptcodes" item="deptcode" open="(" separator="," close=")">
-                    #{deptcode}
-                </foreach>
-            </if>
-            )
-        </if>
-        group by ss.libtemplateid
-    </select>
-
-    <!--
-        婊℃剰搴︾粺璁★紙statistics / statisticsByDimension锛夛細涓�娆℃�х粺璁℃湰娆¤姹傚叏閮ㄩ棶棰樼殑濉姤閲忋��
-        绛変环浜庡姣忎釜闂鎵ц queryFillCount(subids, questiontext)锛屽尯鍒槸锛�
-          1) 鐢� join service_subtask 浠f浛鍏堟妸鍏ㄩ儴 sub_id 鏌ュ洖 Java 鍐嶆嫾 IN(...)锛�
-          2) 鎸� 妯℃澘+闂+matchedtext+score 鍒嗙粍锛屽彧鍥炰紶閫夐」/鍒嗗�间笌鏉℃暟锛�
-          3) targetvalue 鐢� min() 鍙栬闂鐨勪竴涓潪绌洪�夐」涓诧紙鍚屼竴涓棶棰樿鍊间竴鑷达級锛�
-          4) visit_time 鏀规垚鍙蛋绱㈠紩鐨勮寖鍥存潯浠躲��
-        娉ㄦ剰锛氳繖閲屽埢鎰忎笉鍦� SQL 閲岃繃婊� questiontext锛岀敱 Java 鎸夊師 queryFillCount 鐨�
-        銆宷uestiontext 涓� null 鏃朵笉鎷艰鏉′欢銆嶈涔夊仛鍖归厤锛屼繚璇佺粨鏋滃彛寰勫畬鍏ㄤ竴鑷淬��
-    -->
-    <select id="selectSatisfactionFillStatByMatchedtext" parameterType="com.smartor.domain.entity.SatisfactionStatQuery"
-            resultType="java.util.Map">
-        select ss.libtemplateid as libtemplateid,
-               ssd.questiontext as questiontext,
-               ssd.matchedtext as matchedtext,
-               ssd.score as score,
-               count(*) as cnt,
-               min(ssd.targetvalue) as targetvalue
-        from service_subtask_detail ssd
-        join service_subtask ss on ss.id = ssd.sub_id
-        where ssd.del_flag = 0
-        and ss.del_flag = 0
-        <if test="orgid != null and orgid != ''">
-            and ss.orgid = #{orgid}
-        </if>
-        <if test="campusid != null and campusid != ''">
-            and ss.campusid = #{campusid}
-        </if>
-        <if test="type != null and type != ''">and ss.type = #{type}</if>
-        <if test="startTime != null">
-            and ss.visit_time &gt;= #{startTime}
-        </if>
-        <if test="endTime != null">
-            and ss.visit_time &lt; date_add(#{endTime}, interval 1 day)
-        </if>
-        <if test="libTemplateIds != null and libTemplateIds.size() > 0">
-            and ss.libtemplateid IN
-            <foreach collection="libTemplateIds" item="libTemplateId" open="(" separator="," close=")">
-                #{libTemplateId}
-            </foreach>
-        </if>
-        <if test="(hospitaldistrictcodes != null and hospitaldistrictcodes.size() > 0) or (deptcodes != null and deptcodes.size() > 0)">
-            AND (
-            <if test="hospitaldistrictcodes != null and hospitaldistrictcodes.size() > 0">
-                ss.leavehospitaldistrictcode IN
-                <foreach collection="hospitaldistrictcodes" item="hospitaldistrictcode" open="("
-                         separator=","
-                         close=")">
-                    #{hospitaldistrictcode}
-                </foreach>
-            </if>
-            <if test="hospitaldistrictcodes != null and hospitaldistrictcodes.size() > 0 and deptcodes != null and deptcodes.size() > 0">
-                OR
-            </if>
-            <if test="deptcodes != null and deptcodes.size() > 0">
-                ss.deptcode IN
-                <foreach collection="deptcodes" item="deptcode" open="(" separator="," close=")">
-                    #{deptcode}
-                </foreach>
-            </if>
-            )
-        </if>
-        group by ss.libtemplateid, ssd.questiontext, ssd.matchedtext, ssd.score
-    </select>
-
-    <select id="isExistServiceSubtaskDetail" resultType="com.smartor.domain.DTO.ServiceSubtaskDetailDTO">
+                <select id="isExistServiceSubtaskDetail" resultType="com.smartor.domain.DTO.ServiceSubtaskDetailDTO">
         select id, questiontext, matchedtext, score
         from service_subtask_detail
         where del_flag = 0
@@ -650,7 +472,6 @@
         where id = #{id}
     </update>
 
-
     <update id="updateSSDByCondition" parameterType="com.smartor.domain.ServiceSubtaskDetail">
         update service_subtask_detail
         <trim prefix="SET" suffixOverrides=",">
@@ -743,33 +564,4 @@
             service_subtask_detail.matchedtext
     </select>
 
-    <select id="selectRepeatDetailIds" resultType="Long">
-        SELECT d1.id
-        FROM service_subtask_detail d1
-        INNER JOIN (
-            SELECT sub_id, questiontext,
-                   MAX(create_time) AS max_create_time,
-                   MAX(id) AS keep_id
-            FROM service_subtask_detail
-            WHERE del_flag = 0
-            GROUP BY sub_id, questiontext
-            HAVING COUNT(*) &gt; 1
-        ) d2 ON d1.sub_id = d2.sub_id
-            AND d1.questiontext = d2.questiontext
-            AND (
-                d1.create_time &lt; d2.max_create_time
-                OR (d1.create_time = d2.max_create_time AND d1.id &lt; d2.keep_id)
-            )
-        WHERE d1.del_flag = 0
-    </select>
-
-    <update id="deleteRepeatDetailByIds">
-        UPDATE service_subtask_detail
-        SET del_flag = 1
-        WHERE del_flag = 0
-        AND id IN
-        <foreach collection="ids" item="id" open="(" separator="," close=")">
-            #{id}
-        </foreach>
-    </update>
-</mapper>
+        </mapper>
diff --git a/smartor/src/main/resources/mapper/smartor/ServiceSubtaskMapper.xml b/smartor/src/main/resources/mapper/smartor/ServiceSubtaskMapper.xml
index 8724894..a065af3 100644
--- a/smartor/src/main/resources/mapper/smartor/ServiceSubtaskMapper.xml
+++ b/smartor/src/main/resources/mapper/smartor/ServiceSubtaskMapper.xml
@@ -291,7 +291,6 @@
         <if test="continueTimeNext != null ">and continue_time_next = #{continueTimeNext}</if>
     </select>
 
-
     <select id="selectServiceSubtaskList" parameterType="com.smartor.domain.entity.ServiceSubtaskEntity"
             resultMap="ServiceSubtaskResult">
         <include refid="selectServiceSubtaskVo"/>
@@ -537,78 +536,7 @@
         <if test="pageSize != null  and pageNum != null">limit ${pageSize} OFFSET ${pageNum}</if>
     </select>
 
-    <select id="selectSatisfactionSubtaskList" parameterType="com.smartor.domain.VO.PatSatisfactionReqVO"
-            resultMap="ServiceSubtaskResult">
-        <include refid="selectServiceSubtaskVo"/>
-        where 1=1
-        and del_flag = 0
-        <if test="questionType != null">and type = #{questionType}</if>
-
-        <if test="hospitaldistrictcodes != null and hospitaldistrictcodes.size() > 0">
-            AND leavehospitaldistrictcode IN
-            <foreach collection="hospitaldistrictcodes" item="hospitaldistrictcode" open="(" separator=","
-                     close=")">
-                #{hospitaldistrictcode}
-            </foreach>
-        </if>
-        <if test="deptcodes != null and deptcodes.size() > 0">
-            AND deptcode IN
-            <foreach collection="deptcodes" item="deptcode" open="(" separator=","
-                     close=")">
-                #{deptcode}
-            </foreach>
-        </if>
-        <if test="serviceTypes != null and serviceTypes.size() > 0">
-            AND service_type IN
-            <foreach collection="serviceTypes" item="serviceType" open="(" separator=","
-                     close=")">
-                #{serviceType}
-            </foreach>
-        </if>
-
-        <if test="startTime != null and endTime != null">
-            AND date_format(visit_time,'%y%m%d') >= date_format(#{startTime},'%y%m%d')
-            AND date_format(visit_time,'%y%m%d') &lt;= date_format(#{endTime},'%y%m%d')
-        </if>
-    </select>
-    <!--
-        婊℃剰搴︾粺璁★細鍙煡鏈鏉′欢涓嬪疄闄呭瓨鍦ㄥ摢浜� type銆�
-        杩囨护鏉′欢涓� selectSatisfactionSubtaskList 瀹屽叏涓�鑷达紝鍙槸锛�
-          1) 鍙� select distinct type锛堝師鏉ユ槸鎶婁笂鐧句釜瀛楁鍏ㄦ煡鍥炴潵锛夛紱
-          2) visit_time 鏀规垚鍙蛋绱㈠紩鐨勮寖鍥存潯浠讹紙鍘熸潵瀵瑰垪濂� date_format锛岀储寮曠敤涓嶄笂锛夈��
-    -->
-    <select id="selectSatisfactionSubtaskTypes" parameterType="com.smartor.domain.VO.PatSatisfactionReqVO"
-            resultType="java.lang.String">
-        select distinct type
-        from service_subtask
-        where del_flag = 0
-        <if test="questionType != null">and type = #{questionType}</if>
-        <if test="hospitaldistrictcodes != null and hospitaldistrictcodes.size() > 0">
-            AND leavehospitaldistrictcode IN
-            <foreach collection="hospitaldistrictcodes" item="hospitaldistrictcode" open="(" separator=","
-                     close=")">
-                #{hospitaldistrictcode}
-            </foreach>
-        </if>
-        <if test="deptcodes != null and deptcodes.size() > 0">
-            AND deptcode IN
-            <foreach collection="deptcodes" item="deptcode" open="(" separator="," close=")">
-                #{deptcode}
-            </foreach>
-        </if>
-        <if test="serviceTypes != null and serviceTypes.size() > 0">
-            AND service_type IN
-            <foreach collection="serviceTypes" item="serviceType" open="(" separator="," close=")">
-                #{serviceType}
-            </foreach>
-        </if>
-        <if test="startTime != null and endTime != null">
-            AND visit_time &gt;= #{startTime}
-            AND visit_time &lt; date_add(#{endTime}, interval 1 day)
-        </if>
-    </select>
-
-    <select id="getCompensateServiceSubtaskList" parameterType="com.smartor.domain.entity.ServiceSubtaskEntity"
+            <select id="getCompensateServiceSubtaskList" parameterType="com.smartor.domain.entity.ServiceSubtaskEntity"
             resultMap="ServiceSubtaskResult">
         <include refid="selectServiceSubtaskVo"/>
         where del_flag = 0
@@ -669,7 +597,6 @@
         <if test="continueTimeNext != null ">and continue_time_next = #{continueTimeNext}</if>
 
     </select>
-
 
     <select id="selectServiceSubtaskListAgain" parameterType="com.smartor.domain.entity.ServiceSubtaskEntity"
             resultMap="ServiceSubtaskResult">
@@ -862,7 +789,6 @@
             GROUP BY ss.${groupKey}
         </if>
     </select>
-
 
     <select id="selectTimelyRateDetail" parameterType="com.smartor.domain.ServiceSubtask"
             resultMap="ServiceSubtaskResult">
@@ -1547,7 +1473,6 @@
         </trim>
         where task_guid = #{taskGuid} and task_name = #{taskName}
     </update>
-
 
     <update id="deleteServiceSubtaskById" parameterType="Long">
         update service_subtask
@@ -2830,7 +2755,6 @@
         </if>
     </select>
 
-
     <select id="selectVisitCount" parameterType="com.smartor.domain.PatMedReq"
             resultType="com.smartor.domain.PatMedRes">
         select
@@ -2897,109 +2821,7 @@
         </if>
     </select>
 
-    <select id="querySendCount" parameterType="com.smartor.domain.entity.ServiceSubtaskSatisfactionEntity"
-            resultType="Long">
-        select distinct id from service_subtask
-        where del_flag = 0
-        <if test="orgid != null and orgid != ''">
-            and orgid = #{orgid}
-        </if>
-        <if test="campusid != null and campusid != ''">
-            and campusid = #{campusid}
-        </if>
-        <if test="type != null ">and type = #{type}</if>
-        <if test="startTime != null">
-            AND date_format(visit_time,'%y%m%d') >= date_format(#{startTime},'%y%m%d')
-        </if>
-        <if test="endTime != null">
-            AND date_format(visit_time,'%y%m%d') &lt;= date_format(#{endTime},'%y%m%d')
-        </if>
-        <if test=" libTemplateIds != null and libTemplateIds.size()>0">
-            AND libtemplateid IN
-            <foreach collection="libTemplateIds" item="libTemplateId" open="(" separator=","
-                     close=")">
-                #{libTemplateId}
-            </foreach>
-        </if>
-        <if test="(hospitaldistrictcodes != null and hospitaldistrictcodes.size() > 0) or (deptcodes != null and deptcodes.size() > 0)">
-            AND (
-            <if test="hospitaldistrictcodes != null and hospitaldistrictcodes.size() > 0">
-                leavehospitaldistrictcode IN
-                <foreach collection="hospitaldistrictcodes" item="hospitaldistrictcode" open="("
-                         separator=","
-                         close=")">
-                    #{hospitaldistrictcode}
-                </foreach>
-            </if>
-            <if test="hospitaldistrictcodes != null and hospitaldistrictcodes.size() > 0 and deptcodes != null and deptcodes.size() > 0">
-                OR
-            </if>
-            <if test="deptcodes != null and deptcodes.size() > 0">
-                deptcode IN
-                <foreach collection="deptcodes" item="deptcode" open="(" separator=","
-                         close=")">
-                    #{deptcode}
-                </foreach>
-            </if>
-            )
-        </if>
-    </select>
-
-    <!--
-        婊℃剰搴︽煴鐘跺浘锛氫竴娆℃�х粺璁℃湰娆¤姹傚叏閮ㄦā鏉跨殑鍙戦�侀噺銆�
-        涓� querySendCount 鐨勮繃婊ゆ潯浠跺畬鍏ㄤ竴鑷达紝鍖哄埆鏄細
-          1) 鎸� libtemplateid 鍒嗙粍锛屼竴娆¤繑鍥炴墍鏈夋ā鏉匡紙鏇夸唬閫愪釜妯℃澘鏌ヨ锛夛紱
-          2) visit_time 鏀规垚鍙蛋绱㈠紩鐨勮寖鍥存潯浠讹紙鍘熸潵瀵瑰垪濂� date_format锛岀储寮曠敤涓嶄笂锛夈��
-    -->
-    <select id="selectSatisfactionSendCount" parameterType="com.smartor.domain.entity.SatisfactionStatQuery"
-            resultType="java.util.Map">
-        select libtemplateid as libtemplateid, count(distinct id) as cnt
-        from service_subtask
-        where del_flag = 0
-        <if test="orgid != null and orgid != ''">
-            and orgid = #{orgid}
-        </if>
-        <if test="campusid != null and campusid != ''">
-            and campusid = #{campusid}
-        </if>
-        <if test="type != null and type != ''">and type = #{type}</if>
-        <if test="startTime != null">
-            and visit_time &gt;= #{startTime}
-        </if>
-        <if test="endTime != null">
-            and visit_time &lt; date_add(#{endTime}, interval 1 day)
-        </if>
-        <if test="libTemplateIds != null and libTemplateIds.size() > 0">
-            and libtemplateid IN
-            <foreach collection="libTemplateIds" item="libTemplateId" open="(" separator="," close=")">
-                #{libTemplateId}
-            </foreach>
-        </if>
-        <if test="(hospitaldistrictcodes != null and hospitaldistrictcodes.size() > 0) or (deptcodes != null and deptcodes.size() > 0)">
-            AND (
-            <if test="hospitaldistrictcodes != null and hospitaldistrictcodes.size() > 0">
-                leavehospitaldistrictcode IN
-                <foreach collection="hospitaldistrictcodes" item="hospitaldistrictcode" open="("
-                         separator=","
-                         close=")">
-                    #{hospitaldistrictcode}
-                </foreach>
-            </if>
-            <if test="hospitaldistrictcodes != null and hospitaldistrictcodes.size() > 0 and deptcodes != null and deptcodes.size() > 0">
-                OR
-            </if>
-            <if test="deptcodes != null and deptcodes.size() > 0">
-                deptcode IN
-                <foreach collection="deptcodes" item="deptcode" open="(" separator="," close=")">
-                    #{deptcode}
-                </foreach>
-            </if>
-            )
-        </if>
-        group by libtemplateid
-    </select>
-
-    <!--
+            <!--
         鎸夌梾鍖烘垨绉戝鍒嗙粍缁熻 continue_flag 鏁伴噺
         浼犱簡 leavehospitaldistrictcodes 鈫� 鎸夌梾鍖虹淮搴�
         浼犱簡 deptcodes              鈫� 鎸夌瀹ょ淮搴�
@@ -3131,118 +2953,7 @@
           and sendstate in (1, 2, 3)
     </select>
 
-    <select id="mydTrend" resultType="com.smartor.domain.DTO.MydTrendReqDTO">
-        SELECT CASE #{type}
-                   WHEN 'year' THEN DATE_FORMAT(d.create_time, '%Y')
-                   WHEN 'quarter' THEN CONCAT(YEAR(d.create_time), '-Q', QUARTER(d.create_time))
-                   WHEN 'month' THEN DATE_FORMAT(d.create_time, '%Y-%m')
-                   WHEN 'week' THEN FLOOR(DATEDIFF(d.create_time, #{startFinishTime}) / 7) + 1
-                   ELSE DATE_FORMAT(d.create_time, '%Y-%m-%d')
-                   END                AS period,
-               ROUND(avg(d.score), 2) AS totalScore
-        FROM service_subtask_detail d
-                 JOIN service_task t ON d.taskid = t.taskid
-        WHERE t.service_type IN (14)
-          AND t.del_flag = 0
-          AND d.del_flag = 0
-          AND d.create_time >= #{startFinishTime}
-          AND d.create_time &lt; DATE_ADD(#{endFinishTime}, INTERVAL 1 DAY)
-        GROUP BY period
-        ORDER BY period;
-    </select>
-
-    <select id="sltdMydTotalByDimension" resultType="com.smartor.domain.DTO.MydTrendReqDTO">
-        SELECT
-        p.period,
-        dim.dimension,
-        IFNULL(ROUND(SUM(d.score), 2), 0) AS totalScore
-        FROM (
-        <!-- 鐢熸垚鏌ヨ鑼冨洿鍐呭疄闄呭嚭鐜拌繃鐨勬椂闂存 -->
-        SELECT DISTINCT
-        CASE #{type}
-        WHEN 'year' THEN DATE_FORMAT(d.create_time, '%Y')
-        WHEN 'quarter' THEN CONCAT(YEAR(d.create_time), '-Q', QUARTER(d.create_time))
-        WHEN 'month' THEN DATE_FORMAT(d.create_time, '%Y-%m')
-        WHEN 'week' THEN FLOOR(DATEDIFF(d.create_time, #{startFinishTime}) / 7) + 1
-        ELSE DATE_FORMAT(d.create_time, '%Y-%m-%d')
-        END AS period
-        FROM service_subtask_detail d
-        WHERE d.del_flag = 0
-        AND d.create_time >= #{startFinishTime}
-        AND d.create_time &lt; #{endFinishTime}
-        ) p
-        CROSS JOIN (
-        <!-- 鎵�鏈夐渶瑕佸睍绀虹殑 dimension -->
-        SELECT DISTINCT dimension
-        FROM service_subtask_detail
-        WHERE del_flag = 0
-        AND dimension IS NOT NULL
-        ) dim
-        LEFT JOIN service_task t
-        ON t.service_type IN (6, 14)
-        AND t.del_flag = 0
-        LEFT JOIN service_subtask_detail d
-        ON d.taskid = t.taskid
-        AND d.del_flag = 0
-        AND d.dimension = dim.dimension
-        AND CASE #{type}
-        WHEN 'year' THEN DATE_FORMAT(d.create_time, '%Y')
-        WHEN 'quarter' THEN CONCAT(YEAR(d.create_time), '-Q', QUARTER(d.create_time))
-        WHEN 'month' THEN DATE_FORMAT(d.create_time, '%Y-%m')
-        WHEN 'week' THEN FLOOR(DATEDIFF(d.create_time, #{startFinishTime}) / 7) + 1
-        ELSE DATE_FORMAT(d.create_time, '%Y-%m-%d')
-        END = p.period
-        AND d.create_time >= #{startFinishTime}
-        AND d.create_time &lt; #{endFinishTime}
-        GROUP BY p.period, dim.dimension
-        ORDER BY p.period, dim.dimension
-    </select>
-
-    <select id="sltdMydTotalByYQ" resultType="com.smartor.domain.DTO.MydTrendReqDTO">
-        SELECT p.period,
-               c.campusid,
-               IFNULL(ROUND(AVG(d.score), 2), 0) AS totalScore
-        FROM (
-                 -- 鐢熸垚鏌ヨ鑼冨洿鍐呭疄闄呭嚭鐜拌繃鐨勬椂闂存
-                 SELECT DISTINCT CASE #{type}
-                                     WHEN 'year' THEN DATE_FORMAT(d.create_time, '%Y')
-                                     WHEN 'quarter' THEN CONCAT(YEAR(d.create_time), '-Q', QUARTER(d.create_time))
-                                     WHEN 'month' THEN DATE_FORMAT(d.create_time, '%Y-%m')
-                                     WHEN 'week' THEN FLOOR(DATEDIFF(d.create_time, #{startFinishTime}) / 7) + 1
-                                     ELSE DATE_FORMAT(d.create_time, '%Y-%m-%d')
-                                     END AS period
-                 FROM service_subtask_detail d
-                 WHERE d.del_flag = 0
-                   AND d.create_time >= #{startFinishTime}
-                   AND d.create_time &lt; #{endFinishTime}) p
-                 CROSS JOIN (
-            -- 鎵�鏈夐渶瑕佸睍绀虹殑闄㈠尯
-            SELECT DISTINCT campusid
-            FROM service_task
-            WHERE service_type IN (6, 14)
-              AND del_flag = 0
-              AND campusid IS NOT NULL) c
-                 LEFT JOIN service_task t
-                           ON t.campusid = c.campusid
-                               AND t.service_type IN (6, 14)
-                               AND t.del_flag = 0
-                 LEFT JOIN service_subtask_detail d
-                           ON d.taskid = t.taskid
-                               AND d.del_flag = 0
-                               AND CASE #{type}
-                                       WHEN 'year' THEN DATE_FORMAT(d.create_time, '%Y')
-                                       WHEN 'quarter' THEN CONCAT(YEAR(d.create_time), '-Q', QUARTER(d.create_time))
-                                       WHEN 'month' THEN DATE_FORMAT(d.create_time, '%Y-%m')
-                                       WHEN 'week' THEN FLOOR(DATEDIFF(d.create_time, #{startFinishTime}) / 7) + 1
-                                       ELSE DATE_FORMAT(d.create_time, '%Y-%m-%d')
-                                       END = p.period
-                               AND d.create_time >= #{startFinishTime}
-                               AND d.create_time &lt; #{endFinishTime}
-        GROUP BY p.period, c.campusid
-        ORDER BY p.period, c.campusid;
-    </select>
-
-    <!-- 浠诲姟缁勶紙appltype=5锛夋粴鍔ㄨ皟搴︾敤锛氭媺鏈疆鍏ㄩ噺 subtask锛屼綔涓轰笅涓�杞殑鍏嬮殕妯℃澘 -->
+                <!-- 浠诲姟缁勶紙appltype=5锛夋粴鍔ㄨ皟搴︾敤锛氭媺鏈疆鍏ㄩ噺 subtask锛屼綔涓轰笅涓�杞殑鍏嬮殕妯℃澘 -->
     <select id="selectByScheduleIdAndLoop" resultMap="ServiceSubtaskResult">
         <include refid="selectServiceSubtaskVo"/>
         where schedule_id = #{scheduleId}
@@ -3333,367 +3044,7 @@
         </if>
     </select>
 
-    <select id="statQuestionOption" resultType="com.smartor.domain.ServiceSubtaskDetailRatioExport">
-        SELECT
-        d.questiontext AS questiontext,
-        t.taskid AS taskid,
-        t.task_name AS taskName,
-        d.asrtext AS optionresult,
-        d.score AS score,
-        d.targetvalue AS targetvalue,
-        d.value_type AS valueType,
-        COUNT(*) AS count,
-        CONCAT(ROUND(COUNT(*) * 100.0 / t2.total_count, 2), '%') AS ratio
-        FROM
-        service_task t
-        INNER JOIN service_subtask s ON s.taskid = t.taskid
-        INNER JOIN service_subtask_detail d ON d.sub_id = s.id
-        INNER JOIN (
-        SELECT
-        t.taskid,
-        d.questiontext,
-        COUNT(*) AS total_count
-        FROM
-        service_task t
-        INNER JOIN service_subtask s ON s.taskid = t.taskid
-        INNER JOIN service_subtask_detail d ON d.sub_id = s.id
-        WHERE 1=1
-        <if test="taskIds != null and taskIds.size() > 0">
-            AND t.taskid IN
-            <foreach collection="taskIds" item="taskId" open="(" separator="," close=")">
-                #{taskId}
-            </foreach>
-        </if>
-        <!-- 浼犱簡 subtaskIds 鍙粺璁¤繖鎵瑰瓙浠诲姟锛堟湰娆″鍑虹殑鎮h�咃級 -->
-        <if test="subtaskIds != null and subtaskIds.size() > 0">
-            AND s.id IN
-            <foreach collection="subtaskIds" item="subtaskId" open="(" separator="," close=")">
-                #{subtaskId}
-            </foreach>
-        </if>
-        <if test="valueType != null">AND d.value_type = #{valueType}</if>
-        <if test="scoreStart != null">AND d.score &gt;= #{scoreStart}</if>
-        <if test="scoreEnd != null">AND d.score &lt;= #{scoreEnd}</if>
-        <if test="questiontext != null  and questiontext != ''">and d.questiontext like concat('%', #{questiontext},
-            '%')
-        </if>
-        and s.sendstate=6
-        and d.del_flag=0
-        <!-- 鍒嗘瘝蹇呴』鍜屾槑缁嗙敤鍚屼竴濂楁椂闂存潯浠讹紝鍚﹀垯閫変簡鏃堕棿鑼冨洿鍚庢瘮渚嬩細鎸夊叏閲忓巻鍙茶绋�閲� -->
-        <if test="startOutHospTime != null">
-            AND date_format(s.endtime,'%y%m%d') &gt;= date_format(#{startOutHospTime},'%y%m%d')
-        </if>
-        <if test="endOutHospTime != null">
-            AND date_format(s.endtime,'%y%m%d') &lt;= date_format(#{endOutHospTime},'%y%m%d')
-        </if>
-        <if test="startFinishTime != null">
-            AND date_format(s.finishtime,'%y%m%d') &gt;= date_format(#{startFinishTime},'%y%m%d')
-        </if>
-        <if test="endFinishTime != null">
-            AND date_format(s.finishtime,'%y%m%d') &lt;= date_format(#{endFinishTime},'%y%m%d')
-        </if>
-        GROUP BY
-        t.taskid,
-        d.questiontext
-        ) t2 ON t2.taskid = t.taskid AND t2.questiontext = d.questiontext
-        WHERE 1=1
-        <if test="taskIds != null and taskIds.size() > 0">
-            AND t.taskid IN
-            <foreach collection="taskIds" item="taskId" open="(" separator="," close=")">
-                #{taskId}
-            </foreach>
-        </if>
-        <if test="subtaskIds != null and subtaskIds.size() > 0">
-            AND s.id IN
-            <foreach collection="subtaskIds" item="subtaskId" open="(" separator="," close=")">
-                #{subtaskId}
-            </foreach>
-        </if>
-        <if test="valueType != null">AND d.value_type = #{valueType}</if>
-        <if test="scoreStart != null">AND d.score &gt;= #{scoreStart}</if>
-        <if test="scoreEnd != null">AND d.score &lt;= #{scoreEnd}</if>
-        <if test="questiontext != null  and questiontext != ''">and d.questiontext like concat('%', #{questiontext},
-            '%')
-        </if>
-        AND s.sendstate = 6
-        and d.del_flag=0
-        <if test="startOutHospTime != null">
-            AND date_format(s.endtime,'%y%m%d') &gt;= date_format(#{startOutHospTime},'%y%m%d')
-        </if>
-        <if test="endOutHospTime != null">
-            AND date_format(s.endtime,'%y%m%d') &lt;= date_format(#{endOutHospTime},'%y%m%d')
-        </if>
-        <if test="startFinishTime != null">
-            AND date_format(s.finishtime,'%y%m%d') &gt;= date_format(#{startFinishTime},'%y%m%d')
-        </if>
-        <if test="endFinishTime != null">
-            AND date_format(s.finishtime,'%y%m%d') &lt;= date_format(#{endFinishTime},'%y%m%d')
-        </if>
-        GROUP BY
-        t.taskid,
-        d.questiontext,
-        d.score,
-        d.targetvalue,
-        d.asrtext,
-        d.value_type
-        ORDER BY
-        d.questiontext,
-        t.taskid
-    </select>
-
-    <!--
-      瀵煎嚭銆岄棶棰樿鎯呭崰姣斻�嶄笓鐢ㄧ粺璁★紙鍙粺璁℃湰娆″鍑虹殑杩欐壒鎮h�咃級
-      鏁伴噺 = 閫夋嫨璇ラ�夐」鐨勬偅鑰呮暟锛圕OUNT(DISTINCT s.id)锛屽苟鐢� MAX(id) 淇濊瘉鍚屼竴鎮h�呭悓涓�棰樺彧绠楁渶鏂颁竴鏉★級
-      鍒嗘暟 = 璇ラ�夐」鏄庣粏鐨� score 涔嬪拰
-      姣斾緥 = 鏁伴噺 / 璇ラ浣滅瓟鎮h�呮暟锛屽湪 Java 渚х粺涓�璁$畻锛屽洜姝ゃ�屽崟棰樺悇閫夐」鏁伴噺涔嬪拰 = 璇ラ浣滅瓟鎮h�呮暟銆�
-    -->
-    <select id="statMydQuestionOptionForExport" resultType="com.smartor.domain.DTO.ServiceSubtaskOptionStatDTO">
-        SELECT
-        t.taskid AS taskid,
-        t.task_name AS taskName,
-        d.questiontext AS questiontext,
-        d.asrtext AS optionresult,
-        d.targetvalue AS targetvalue,
-        d.value_type AS valueType,
-        COUNT(DISTINCT s.id) AS patientCount,
-        SUM(COALESCE(CAST(d.score AS DECIMAL(18,2)), 0)) AS scoreSum
-        FROM
-        service_task t
-        INNER JOIN service_subtask s ON s.taskid = t.taskid
-        INNER JOIN service_subtask_detail d ON d.sub_id = s.id
-        WHERE 1=1
-        AND s.del_flag = '0'
-        AND d.del_flag = 0
-        <if test="subtaskIds != null and subtaskIds.size() > 0">
-            AND s.id IN
-            <foreach collection="subtaskIds" item="subtaskId" open="(" separator="," close=")">
-                #{subtaskId}
-            </foreach>
-        </if>
-        <!-- 涓嶄紶瀛愪换鍔℃椂鐩存帴鏌ヤ笉鍒版暟鎹紝閬垮厤璇粺璁″叏閲� -->
-        <if test="subtaskIds == null or subtaskIds.size() == 0">
-            AND 1 = 0
-        </if>
-        <if test="questiontext != null and questiontext != ''">
-            AND d.questiontext LIKE CONCAT('%', #{questiontext}, '%')
-        </if>
-        <!-- 鍚屼竴鎮h�呭悓涓�棰樼洰鍙繚鐣欐渶鏂颁竴鏉★紝閲嶇瓟涓嶉噸澶嶈鍏ユ暟閲� -->
-        AND d.id = (
-        SELECT MAX(d2.id)
-        FROM service_subtask_detail d2
-        WHERE d2.sub_id = d.sub_id
-        AND d2.questiontext &lt;=&gt; d.questiontext
-        AND d2.del_flag = '0'
-        )
-        GROUP BY
-        t.taskid,
-        t.task_name,
-        d.questiontext,
-        d.asrtext,
-        d.targetvalue,
-        d.value_type
-        <!-- 鎸変换鍔″悕绉板垎缁勫睍绀猴細鍏堜换鍔″悕绉般�佸啀浠诲姟缂栧彿锛屽悓涓�浠诲姟鐨勯鐩�/閫夐」杩炵画鎺掑垪 -->
-        ORDER BY
-        t.task_name,
-        t.taskid,
-        d.questiontext
-    </select>
-
-    <!-- 鏂版柟娉曪細鏍规嵁瀛愪换鍔� ID 缁熻闂閫夐」鐧惧垎姣� (鐢ㄤ簬婊℃剰搴︾粺璁� serviceType=6) -->
-    <select id="statQuestionOptionBySubtaskIds" resultType="com.smartor.domain.ServiceSubtaskDetailRatioExport">
-        SELECT
-        d.questiontext AS questiontext,
-        <!-- 鏄庣粏宸叉寜銆岄鐩� + 閫夐」銆嶆眹鎬伙紝浠诲姟/绉戝/鐥呭尯鍙槸闄勫甫淇℃伅锛屽繀椤荤敤鑱氬悎鍑芥暟鍙栧�硷紙MySQL ONLY_FULL_GROUP_BY锛� -->
-        MAX(t.taskid) AS taskid,
-        MAX(t.task_name) AS taskName,
-        d.asrtext AS optionresult,
-        d.score AS score,
-        MAX(s.deptcode) AS deptcode,
-        MAX(s.deptname) AS deptname,
-        MAX(s.leavehospitaldistrictcode) AS leavehospitaldistrictcode,
-        MAX(s.leavehospitaldistrictname) AS leavehospitaldistrictname,
-        d.targetvalue AS targetvalue,
-        d.value_type AS valueType,
-        COUNT(*) AS count,
-        CONCAT(ROUND(COUNT(*) * 100.0 / t2.total_count, 2), '%') AS ratio
-        FROM
-        service_task t
-        INNER JOIN service_subtask s ON s.taskid = t.taskid
-        INNER JOIN service_subtask_detail d ON d.sub_id = s.id
-        <!-- 鍘婚噸锛氬悓涓�瀛愪换鍔″悓涓�棰樼洰鍙繚鐣欐渶鏂颁竴鏉★紝閬垮厤閲嶇瓟/閲嶅鍥炶皟鎶婂悓涓�閬撻閲嶅璁″叆椤规暟锛堜笌 mzMydScoreRank 鍙e緞涓�鑷达級 -->
-        AND d.id = (
-        SELECT MAX(d2.id)
-        FROM service_subtask_detail d2
-        WHERE d2.sub_id = d.sub_id
-        AND d2.questiontext &lt;=&gt; d.questiontext
-        AND d2.del_flag = '0'
-        )
-        INNER JOIN (
-        <!-- 鍒嗘瘝 = 璇ラ鍚勯�夐」椤规暟涔嬪拰锛堟湭浣滅瓟鐨勭┖ asrtext 涓嶈繘鍒嗘瘝锛夛紝杩欐牱涓�閬撻鍚勯�夐」姣斾緥鍚堣鎵嶆槸 100% -->
-        SELECT
-        x.questiontext AS questiontext,
-        SUM(x.option_count) AS total_count
-        FROM (
-        SELECT
-        d.questiontext,
-        d.asrtext,
-        COUNT(*) AS option_count
-        FROM
-        service_task t
-        INNER JOIN service_subtask s ON s.taskid = t.taskid
-        INNER JOIN service_subtask_detail d ON d.sub_id = s.id
-        <!-- 鍘婚噸锛氬垎姣嶏紙璇ラ鏈夋晥绛斿嵎鎬绘暟锛変笌鏄庣粏鍙e緞淇濇寔涓�鑷达紝淇濊瘉鍚勯�夐」姣斾緥鍚堣 100% -->
-        AND d.id = (
-        SELECT MAX(d4.id)
-        FROM service_subtask_detail d4
-        WHERE d4.sub_id = d.sub_id
-        AND d4.questiontext &lt;=&gt; d.questiontext
-        AND d4.del_flag = '0'
-        )
-        WHERE 1=1
-        <if test="subtaskIds != null and subtaskIds.size() > 0">
-            AND s.id IN
-            <foreach collection="subtaskIds" item="subtaskId" open="(" separator="," close=")">
-                #{subtaskId}
-            </foreach>
-        </if>
-        <if test="valueType != null">AND d.value_type &gt;= #{valueType}</if>
-        <if test="scoreStart != null">AND d.score &gt;= #{scoreStart}</if>
-        <if test="scoreEnd != null">AND d.score &lt;= #{scoreEnd}</if>
-        <if test="questiontext != null  and questiontext != ''">and d.questiontext like concat('%', #{questiontext},
-            '%')
-        </if>
-        AND s.sendstate = 6
-        AND d.del_flag=0
-        <!-- 鍒嗘瘝涓庢槑缁嗙敤鍚屼竴濂楁椂闂存潯浠讹紝鍚﹀垯閫変簡鏃ユ湡鍖洪棿鍚庡悇閫夐」姣斾緥鍚堣涓嶅埌 100% -->
-        <if test="startOutHospTime != null">
-            AND date_format(s.endtime,'%y%m%d') &gt;= date_format(#{startOutHospTime},'%y%m%d')
-        </if>
-        <if test="endOutHospTime != null">
-            AND date_format(s.endtime,'%y%m%d') &lt;= date_format(#{endOutHospTime},'%y%m%d')
-        </if>
-        <if test="startFinishTime != null">
-            AND date_format(s.finishtime,'%y%m%d') &gt;= date_format(#{startFinishTime},'%y%m%d')
-        </if>
-        <if test="endFinishTime != null">
-            AND date_format(s.finishtime,'%y%m%d') &lt;= date_format(#{endFinishTime},'%y%m%d')
-        </if>
-        <!-- 鏈綔绛旓紙asrtext 涓虹┖锛夌殑鏄庣粏涓嶈鍏ュ垎姣� -->
-        AND d.asrtext IS NOT NULL AND TRIM(d.asrtext) &lt;&gt; ''
-        GROUP BY d.questiontext, d.asrtext
-        ) x
-        GROUP BY x.questiontext
-        ) t2 ON t2.questiontext = d.questiontext
-        WHERE 1=1
-        <!-- 鍘婚噸锛氭槑缁嗚鍚屼竴瀛愪换鍔″悓涓�棰樼洰鍙繚鐣欐渶鏂颁竴鏉★紝閲嶇瓟涓嶉噸澶嶈鍏ラ」鏁� -->
-        AND d.id = (
-        SELECT MAX(d3.id)
-        FROM service_subtask_detail d3
-        WHERE d3.sub_id = d.sub_id
-        AND d3.questiontext &lt;=&gt; d.questiontext
-        AND d3.del_flag = '0'
-        )
-        <if test="subtaskIds != null and subtaskIds.size() > 0">
-            AND s.id IN
-            <foreach collection="subtaskIds" item="subtaskId" open="(" separator="," close=")">
-                #{subtaskId}
-            </foreach>
-        </if>
-        AND s.sendstate = 6
-        AND d.del_flag=0
-        <!-- 鏈綔绛旓紙asrtext 涓虹┖锛変笉绠椾换浣曢�夐」锛屾槑缁嗛噷鐩存帴鎺掗櫎锛岄伩鍏嶅嚭鐜扮┖閫夐」琛� -->
-        AND d.asrtext IS NOT NULL AND TRIM(d.asrtext) &lt;&gt; ''
-        <if test="valueType != null">AND d.value_type &gt;= #{valueType}</if>
-        <if test="scoreStart != null">AND d.score &gt;= #{scoreStart}</if>
-        <if test="scoreEnd != null">AND d.score &lt;= #{scoreEnd}</if>
-        <if test="questiontext != null  and questiontext != ''">and d.questiontext like concat('%', #{questiontext},
-            '%')
-        </if>
-        <if test="startOutHospTime != null">
-            AND date_format(s.endtime,'%y%m%d') &gt;= date_format(#{startOutHospTime},'%y%m%d')
-        </if>
-        <if test="endOutHospTime != null">
-            AND date_format(s.endtime,'%y%m%d') &lt;= date_format(#{endOutHospTime},'%y%m%d')
-        </if>
-        <if test="startFinishTime != null">
-            AND date_format(s.finishtime,'%y%m%d') &gt;= date_format(#{startFinishTime},'%y%m%d')
-        </if>
-        <if test="endFinishTime != null">
-            AND date_format(s.finishtime,'%y%m%d') &lt;= date_format(#{endFinishTime},'%y%m%d')
-        </if>
-        <!-- 鍙寜銆岄鐩� + 閫夐」銆嶆眹鎬伙細鍘熸潵 GROUP BY 甯︿笂 taskid/绉戝/鐥呭尯锛屽悓涓�涓�夐」琚媶鎴愬琛屻�佹瘡琛屾暟閲忛兘鏄� 1锛�
-             鍓嶇/瀵煎嚭鎷垮埌鐨勩�屾暟閲忋�嶅氨姘歌繙鍋氫笉浜嗙疮鍔� -->
-        GROUP BY
-        d.questiontext,
-        d.asrtext,
-        d.score,
-        d.targetvalue,
-        d.value_type
-        ORDER BY
-        d.questiontext,
-        MIN(t.taskid)
-    </select>
-
-    <select id="statQuestionOptionByKsOrBq" resultType="com.smartor.domain.DTO.QuestionCountKsOrBqDTO"
-            parameterType="com.smartor.domain.VO.QuestionCountKsOrBqVO">
-        SELECT
-        s.deptname AS deptname,
-        s.leavehospitaldistrictname AS leavehospitaldistrictname,
-        d.questiontext AS questionText,
-        d.targetvalue AS targetvalue, -- 鎵�鏈夐�夐」鎷兼帴
-        d.asrtext AS asrtext, -- 鐢ㄦ埛瀹為檯閫変腑鐨勯�夐」
-        d.score AS singleScore -- 鍗曟潯璁板綍鐨勫垎鏁�
-        FROM service_subtask_detail d
-        JOIN service_subtask s ON d.sub_id = s.id
-        WHERE s.del_flag = 0
-        AND d.del_flag = 0
-        AND s.service_type IN ('6', '14')
-        <if test="taskTemplateid != null">
-            AND s.templateid = #{taskTemplateid}
-        </if>
-        <if test="endFinishTime != null">
-            AND d.create_time &lt; #{endFinishTime}
-        </if>
-        <if test="startFinishTime != null">
-            AND d.create_time &gt;= #{startFinishTime}
-        </if>
-        <if test="deptOrDistrict ==1">
-            <if test="leavehospitaldistrictcodes != null and leavehospitaldistrictcodes.size() > 0">
-                AND leavehospitaldistrictcode IN
-                <foreach collection="leavehospitaldistrictcodes" item="leavehospitaldistrictcode" open="("
-                         separator=","
-                         close=")">
-                    #{leavehospitaldistrictcode}
-                </foreach>
-            </if>
-            <if test=" leaveldeptcodes != null and leaveldeptcodes.size()>0">
-                AND deptcode IN
-                <foreach collection="leaveldeptcodes" item="leaveldeptcode" open="(" separator=","
-                         close=")">
-                    #{leaveldeptcode}
-                </foreach>
-            </if>
-        </if>
-        <if test="deptOrDistrict ==2">
-            <if test="leavehospitaldistrictcodes != null and leavehospitaldistrictcodes.size() > 0">
-                AND leavehospitaldistrictcode IN
-                <foreach collection="leavehospitaldistrictcodes" item="leavehospitaldistrictcode" open="("
-                         separator=","
-                         close=")">
-                    #{leavehospitaldistrictcode}
-                </foreach>
-            </if>
-            <if test=" leaveldeptcodes != null and leaveldeptcodes.size()>0">
-                OR deptcode IN
-                <foreach collection="leaveldeptcodes" item="leaveldeptcode" open="(" separator=","
-                         close=")">
-                    #{leaveldeptcode}
-                </foreach>
-            </if>
-        </if>
-    </select>
-
-    <select id="selectServiceSubtaskListDeadLine" parameterType="com.smartor.domain.entity.ServiceSubtaskEntity"
+                    <select id="selectServiceSubtaskListDeadLine" parameterType="com.smartor.domain.entity.ServiceSubtaskEntity"
             resultMap="ServiceSubtaskResult">
         <include refid="selectServiceSubtaskVo"/>
         where 1=1
@@ -3703,95 +3054,5 @@
     </select>
 
     <!-- 闂ㄨ瘖婊℃剰搴︾患鍚堝緱鍒嗘帓鍚嶏紙鎸夌瀹ゆ垨鐥呭尯锛� -->
-    <!-- 缁煎悎婊℃剰搴� = 危(璇勫垎鏄庣粏鏉冮噸) / 璇勫垎鏄庣粏鏉℃暟 脳 100%锛屾潈閲嶅彧鍙� 1銆�0.8銆�0.6銆�0.4銆�0.2 浜旀。 -->
-    <select id="selectMzMydScoreRank" parameterType="com.smartor.domain.VO.MzMydScoreRankVO"
-            resultType="com.smartor.domain.DTO.MzMydScoreRankDTO">
-        SELECT
-        t.code                   AS code,
-        t.name                   AS name,
-        ROUND(AVG(t.w) * 100, 2) AS score,
-        COUNT(*)                 AS itemCount
-        FROM (
-        SELECT
-        <choose>
-            <when test='dimensionType == "2"'>
-                s.leavehospitaldistrictcode AS code,
-                s.leavehospitaldistrictname AS name,
-            </when>
-            <otherwise>
-                s.deptcode AS code,
-                s.deptname AS name,
-            </otherwise>
-        </choose>
-        CAST(d.score AS DECIMAL(10,4)) AS w
-        FROM service_subtask s
-        INNER JOIN service_subtask_detail d ON d.sub_id = s.id
-        WHERE s.del_flag = '0'
-        AND d.del_flag = '0'
-        AND s.sendstate = 6
-        <if test="serviceTypes != null and serviceTypes.size() > 0">
-            AND service_type IN
-            <foreach collection="serviceTypes" item="serviceType" open="(" separator=","
-                     close=")">
-                #{serviceType}
-            </foreach>
-        </if>
-<!--        &lt;!&ndash; 鍙彇鍏紡閲岀殑浜旀。鏉冮噸锛岀┖鍊�/0/鏈瓟绛夋棦涓嶈繘鍒嗗瓙涔熶笉杩涘垎姣� &ndash;&gt;-->
-<!--        AND CAST(d.score AS DECIMAL(10,4)) IN (1, 0.8, 0.6, 0.4, 0.2)-->
-        <!-- 鍚屼竴瀛愪换鍔″悓涓�棰樼洰鍙繚鐣欐渶鏂颁竴鏉★紝閬垮厤閲嶇瓟琚噸澶嶈鍏ラ」鏁� -->
-        AND d.id = (
-        SELECT MAX(d2.id)
-        FROM service_subtask_detail d2
-        WHERE d2.sub_id = d.sub_id
-        AND d2.questiontext &lt;=&gt; d.questiontext
-        AND d2.del_flag = '0'
-        )
-        <if test="orgid != null and orgid != ''">
-            AND s.orgid = #{orgid}
-        </if>
-        <if test="campusid != null and campusid != ''">
-            AND s.campusid = #{campusid}
-        </if>
-        <if test="startFinishTime != null">
-            AND s.finishtime &gt;= #{startFinishTime}
-        </if>
-        <if test="endFinishTime != null">
-            AND s.finishtime &lt; DATE_ADD(#{endFinishTime}, INTERVAL 1 DAY)
-        </if>
-        <if test="questionText != null  and questionText != ''">and d.questiontext like concat('%', #{questionText},
-            '%')
-        </if>
-        <choose>
-            <when test='dimensionType == "2"'>
-                AND s.leavehospitaldistrictcode IS NOT NULL AND s.leavehospitaldistrictcode &lt;&gt; ''
-                AND s.leavehospitaldistrictname IS NOT NULL AND s.leavehospitaldistrictname &lt;&gt; ''
-                <if test="districtCodes != null and districtCodes.size() > 0">
-                    AND s.leavehospitaldistrictcode IN
-                    <foreach collection="districtCodes" item="districtCode" open="(" separator="," close=")">
-                        #{districtCode}
-                    </foreach>
-                </if>
-            </when>
-            <otherwise>
-                AND s.deptcode IS NOT NULL AND s.deptcode &lt;&gt; ''
-                AND s.deptname IS NOT NULL AND s.deptname &lt;&gt; ''
-                <if test="deptCodes != null and deptCodes.size() > 0">
-                    AND s.deptcode IN
-                    <foreach collection="deptCodes" item="deptCode" open="(" separator="," close=")">
-                        #{deptCode}
-                    </foreach>
-                </if>
-            </otherwise>
-        </choose>
-        ) t
-        GROUP BY t.code, t.name
-        ORDER BY AVG(t.w) DESC, COUNT(*) DESC
-        <if test="limit != null and limit > 0">
-            LIMIT #{limit}
-        </if>
-    </select>
-
-
-
-</mapper>
+    </mapper>
 

--
Gitblit v1.9.3