| | |
| | | package com.smartor.service.impl; |
| | | |
| | | import java.util.List; |
| | | import java.math.BigDecimal; |
| | | import java.math.RoundingMode; |
| | | import java.util.*; |
| | | import java.util.stream.Collectors; |
| | | |
| | | import com.ruoyi.common.core.domain.entity.SysUser; |
| | | import com.ruoyi.common.core.domain.entity.SysUserDept; |
| | | import com.ruoyi.common.core.domain.model.LoginUser; |
| | | import com.ruoyi.common.utils.Arith; |
| | | import com.ruoyi.common.utils.ArrayListUtils; |
| | | import com.ruoyi.common.utils.DateUtils; |
| | | import com.ruoyi.common.utils.DtoConversionUtils; |
| | | import com.smartor.domain.*; |
| | | 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 org.apache.commons.collections4.CollectionUtils; |
| | | import org.springframework.beans.factory.annotation.Autowired; |
| | | import org.springframework.stereotype.Service; |
| | | import com.smartor.mapper.PatSatisfactionMapper; |
| | | import com.smartor.domain.PatSatisfaction; |
| | | import com.smartor.service.IPatSatisfactionService; |
| | | |
| | | import static com.ruoyi.common.utils.SecurityUtils.getLoginUser; |
| | | |
| | | /** |
| | | * 患者满意度Service业务层处理 |
| | |
| | | public class PatSatisfactionServiceImpl implements IPatSatisfactionService { |
| | | @Autowired |
| | | private PatSatisfactionMapper patSatisfactionMapper; |
| | | |
| | | @Autowired |
| | | private ServiceSubtaskMapper serviceSubtaskMapper; |
| | | |
| | | @Autowired |
| | | private ServiceSubtaskDetailMapper serviceSubtaskDetailMapper; |
| | | |
| | | @Autowired |
| | | private IvrLibaTemplateScriptMapper ivrLibaTemplateScriptMapper; |
| | | |
| | | @Autowired |
| | | private IvrLibaScriptAssortMapper ivrLibaScriptAssortMapper; |
| | | |
| | | @Autowired |
| | | private SvyLibScriptCategoryMapper svyLibScriptCategoryMapper; |
| | | |
| | | @Autowired |
| | | private IvrLibaTemplateTargetoptionMapper ivrLibaTemplateTargetoptionMapper; |
| | | |
| | | @Autowired |
| | | private SvyLibTemplateTargetoptionMapper svyLibTemplateTargetoptionMapper; |
| | | |
| | | @Autowired |
| | | private SvyLibTemplateScriptMapper svyLibTemplateScriptMapper; |
| | | |
| | | /** |
| | | * 查询患者满意度 |
| | |
| | | } |
| | | |
| | | /** |
| | | * 新增患者满意度 |
| | | * |
| | | * @param patSatisfactions 患者满意度 |
| | | * @return 结果 |
| | | */ |
| | | @Override |
| | | public int batchInsert(List<PatSatisfaction> patSatisfactions) { |
| | | if(CollectionUtils.isEmpty(patSatisfactions)){ |
| | | return 0; |
| | | } |
| | | List<Long> ids = patSatisfactions.stream().map(PatSatisfaction::getId). |
| | | distinct().collect(Collectors.toList()); |
| | | List<PatSatisfaction> patSatisfactionsOld = new ArrayList<>(); |
| | | PatSatisfaction patSatisfactionVo = new PatSatisfaction(); |
| | | patSatisfactionVo.setIds(ids); |
| | | patSatisfactionsOld = patSatisfactionMapper.selectPatSatisfactionList(patSatisfactionVo); |
| | | Map<String, List<PatSatisfaction>> maps = ArrayListUtils.splitListsById(patSatisfactions, |
| | | patSatisfactionsOld, PatSatisfaction::getId); |
| | | //只在patSatisfactions的数据,执行新增操作 |
| | | List<PatSatisfaction> addItems = maps.get("onlyIn1"); |
| | | //只在patSatisfactionsOld的数据,执行删除操作 |
| | | // List<PatSatisfaction> delItems = maps.get("onlyIn2"); |
| | | //重叠的数据,执行更新操作 |
| | | List<PatSatisfaction> editItems = maps.get("common"); |
| | | Integer count = 0; |
| | | LoginUser loginUser = getLoginUser(); |
| | | SysUser user = loginUser.getUser(); |
| | | for (PatSatisfaction patSatisfaction0 : editItems) { |
| | | patSatisfaction0.setUpdateBy(user.getNickName()); |
| | | patSatisfaction0.setUpdateTime(DateUtils.getNowDate()); |
| | | count = count + patSatisfactionMapper.updatePatSatisfaction(patSatisfaction0); |
| | | } |
| | | |
| | | for (PatSatisfaction patSatisfaction1 : addItems) { |
| | | patSatisfaction1.setCreateBy(user.getNickName()); |
| | | patSatisfaction1.setOrgid(user.getOrgid()); |
| | | patSatisfaction1.setCreateTime(DateUtils.getNowDate()); |
| | | patSatisfaction1.setUpdateTime(DateUtils.getNowDate()); |
| | | count = count + patSatisfactionMapper.insertPatSatisfaction(patSatisfaction1); |
| | | } |
| | | return count; |
| | | } |
| | | |
| | | /** |
| | | * 修改患者满意度 |
| | | * |
| | | * @param patSatisfaction 患者满意度 |
| | |
| | | public int deletePatSatisfactionById(Long id) { |
| | | return patSatisfactionMapper.deletePatSatisfactionById(id); |
| | | } |
| | | |
| | | /** |
| | | * 患者满意度统计 |
| | | * |
| | | * @param patSatisfactionReqVO 患者满意度统计条件 |
| | | * @return 患者满意度统计结果 |
| | | */ |
| | | @Override |
| | | public PatSatisfactionResVO statistics(PatSatisfactionReqVO patSatisfactionReqVO) { |
| | | //定义返参 |
| | | PatSatisfactionResVO result = new PatSatisfactionResVO(); |
| | | //患者满意度统详情 |
| | | List<PatSatisfactionDetailEntity> patSatisfactionDetailEntities = new ArrayList<>(); |
| | | // [0]=答题总人数(已经回答问题的) [1]=总人数(包含未回答的) |
| | | int[] personTotals = new int[2]; |
| | | |
| | | // 只查本次条件下实际存在哪些 type(原来是把接近全表的上百个字段查回来,仅用于判断下面两个分支是否为空) |
| | | List<String> types = serviceSubtaskMapper.selectSatisfactionSubtaskTypes(patSatisfactionReqVO); |
| | | boolean hasVoice = types != null && types.contains("1"); |
| | | boolean hasQuestionnaire = types != null && types.contains("2"); |
| | | |
| | | if (hasVoice) { |
| | | //1. 先获取ivr_liba_script_assort的满意度分类ID |
| | | IvrLibaScriptAssort ivrLibaScriptAssort = new IvrLibaScriptAssort(); |
| | | ivrLibaScriptAssort.setOrgid(patSatisfactionReqVO.getOrgid()); |
| | | List<IvrLibaScriptAssort> ivrLibaScriptAssorts = ivrLibaScriptAssortMapper.selectIvrLibaScriptAssortList(ivrLibaScriptAssort); |
| | | List<Long> mydIdList = ivrLibaScriptAssorts.stream().filter(item -> item.getType() != null && item.getType().contains("myd")).map(IvrLibaScriptAssort::getId).collect(Collectors.toList()); |
| | | |
| | | //2 再获取该问题满意度分类下的IvrLibaTemplateScript的集合 |
| | | List<IvrLibaTemplateScript> ivrLibaTemplateScripts = ivrLibaTemplateScriptMapper.queryLibTemplateIds(mydIdList); |
| | | |
| | | //3 一次统计全部问题的发送量/填报量/选项分值(原来是每个问题各查 3 次) |
| | | List<SatScriptRef> scriptRefs = new ArrayList<>(); |
| | | for (IvrLibaTemplateScript ivrLibaTemplateScript : ivrLibaTemplateScripts) { |
| | | scriptRefs.add(new SatScriptRef(ivrLibaTemplateScript.getTemplateid(), ivrLibaTemplateScript.getScriptid(), ivrLibaTemplateScript.getScriptContent())); |
| | | } |
| | | buildSatisfactionDetailEntities(patSatisfactionReqVO, "1", true, scriptRefs, patSatisfactionDetailEntities, personTotals); |
| | | } |
| | | |
| | | if (hasQuestionnaire) { |
| | | //1. 先获取svy_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()); |
| | | |
| | | //2 再获取该问题满意度分类下的IvrLibaTemplateScript的集合 |
| | | List<SvyLibTemplateScript> svyLibTemplateScripts = svyLibTemplateScriptMapper.querySvyLibTemplateIds(mydIdList); |
| | | |
| | | //3 一次统计全部问题的发送量/填报量/选项分值(原来是每个问题各查 3 次) |
| | | List<SatScriptRef> scriptRefs = new ArrayList<>(); |
| | | for (SvyLibTemplateScript svyLibTemplateScript : svyLibTemplateScripts) { |
| | | // 问卷的选项表按 svy_lib_template_script 的主键关联 |
| | | scriptRefs.add(new SatScriptRef(svyLibTemplateScript.getSvyid(), svyLibTemplateScript.getId(), svyLibTemplateScript.getScriptContent())); |
| | | } |
| | | buildSatisfactionDetailEntities(patSatisfactionReqVO, "2", false, scriptRefs, patSatisfactionDetailEntities, personTotals); |
| | | } |
| | | result.setTotalAnswerRate(Arith.div(personTotals[0], personTotals[1])); |
| | | result.setPatSatisfactionDetailEntities(patSatisfactionDetailEntities); |
| | | result.setTotalPerson(personTotals[0]); |
| | | |
| | | return result; |
| | | } |
| | | |
| | | /** |
| | | * 患者满意度统计(维度) |
| | | * |
| | | * @param patSatisfactionReqVO 患者满意度统计条件 |
| | | * @return 患者满意度统计结果 |
| | | */ |
| | | @Override |
| | | public PatSatisfactionResVO statisticsByDimension(PatSatisfactionReqVO patSatisfactionReqVO) { |
| | | //定义返参 |
| | | PatSatisfactionResVO result = new PatSatisfactionResVO(); |
| | | //患者满意度统详情 |
| | | List<PatSatisfactionDetailEntity> patSatisfactionDetailEntities = new ArrayList<>(); |
| | | // [0]=答题总人数(已经回答问题的) [1]=总人数(包含未回答的) |
| | | int[] personTotals = new int[2]; |
| | | |
| | | // 只查本次条件下实际存在哪些 type(原来是把接近全表的上百个字段查回来,仅用于判断下面两个分支是否为空) |
| | | List<String> types = serviceSubtaskMapper.selectSatisfactionSubtaskTypes(patSatisfactionReqVO); |
| | | boolean hasVoice = types != null && types.contains("1"); |
| | | boolean hasQuestionnaire = types != null && types.contains("2"); |
| | | |
| | | if (hasVoice) { |
| | | //1. 先获取入参中的维护集合 |
| | | IvrLibaTemplateScript iltscript = new IvrLibaTemplateScript(); |
| | | iltscript.setDimensionList(patSatisfactionReqVO.getDimensionList()); |
| | | |
| | | //2 再获取该问题满意度分类下的IvrLibaTemplateScript的集合 |
| | | List<IvrLibaTemplateScript> ivrLibaTemplateScripts = ivrLibaTemplateScriptMapper.selectILTSBydimension(iltscript); |
| | | if (CollectionUtils.isNotEmpty(ivrLibaTemplateScripts)) { |
| | | //3 一次统计全部问题的发送量/填报量/选项分值(原来是每个问题各查 3 次) |
| | | List<SatScriptRef> scriptRefs = new ArrayList<>(); |
| | | for (IvrLibaTemplateScript ivrLibaTemplateScript : ivrLibaTemplateScripts) { |
| | | scriptRefs.add(new SatScriptRef(ivrLibaTemplateScript.getTemplateid(), ivrLibaTemplateScript.getScriptid(), ivrLibaTemplateScript.getScriptContent())); |
| | | } |
| | | buildSatisfactionDetailEntities(patSatisfactionReqVO, "1", true, scriptRefs, patSatisfactionDetailEntities, personTotals); |
| | | } |
| | | } |
| | | |
| | | if (hasQuestionnaire) { |
| | | //1. 先获取入参中的满意度维度 |
| | | SvyLibTemplateScriptVO svyLibTemplateScriptVO = new SvyLibTemplateScriptVO(); |
| | | svyLibTemplateScriptVO.setDimensionList(patSatisfactionReqVO.getDimensionList()); |
| | | //2 再获取该问题满意度分类下的IvrLibaTemplateScript的集合 |
| | | List<SvyLibTemplateScript> svyLibTemplateScripts = svyLibTemplateScriptMapper.selectSLTScriptListBydimension(svyLibTemplateScriptVO); |
| | | if (CollectionUtils.isNotEmpty(svyLibTemplateScripts)) { |
| | | //3 一次统计全部问题的发送量/填报量/选项分值(原来是每个问题各查 3 次) |
| | | List<SatScriptRef> scriptRefs = new ArrayList<>(); |
| | | for (SvyLibTemplateScript svyLibTemplateScript : svyLibTemplateScripts) { |
| | | // 问卷的选项表按 svy_lib_template_script 的主键关联 |
| | | scriptRefs.add(new SatScriptRef(svyLibTemplateScript.getSvyid(), svyLibTemplateScript.getId(), svyLibTemplateScript.getScriptContent())); |
| | | } |
| | | buildSatisfactionDetailEntities(patSatisfactionReqVO, "2", false, scriptRefs, patSatisfactionDetailEntities, personTotals); |
| | | } |
| | | } |
| | | result.setTotalAnswerRate(Arith.div(personTotals[0], personTotals[1])); |
| | | result.setPatSatisfactionDetailEntities(patSatisfactionDetailEntities); |
| | | result.setTotalPerson(personTotals[0]); |
| | | |
| | | return result; |
| | | } |
| | | |
| | | @Override |
| | | public Map<String, Object> satisfactionGraph(PatSatisfactionReqVO patSatisfactionReqVO) { |
| | | //用来记录柱状图信息(key=满意度分类名称,value=满意度分类下的总人数和填报人数) |
| | | Map<String, Object> zzt = new HashMap<>(); |
| | | |
| | | // 记录一下每种分类的填报情况(只记分值与条数,不再把明细行全部查回 Java) |
| | | Map<String, List<ScoreCount>> assortNameFillCountMap = new HashMap<>(); |
| | | |
| | | if (patSatisfactionReqVO.getType() == null || patSatisfactionReqVO.getType() == 1) { |
| | | //1. 先获取ivr_liba_script_assort的满意度分类ID |
| | | IvrLibaScriptAssort ivrLibaScriptAssort = new IvrLibaScriptAssort(); |
| | | ivrLibaScriptAssort.setOrgid(patSatisfactionReqVO.getOrgid()); |
| | | List<IvrLibaScriptAssort> ivrLibaScriptAssorts = ivrLibaScriptAssortMapper.selectIvrLibaScriptAssortList(ivrLibaScriptAssort); |
| | | List<Long> mydIdList = ivrLibaScriptAssorts.stream().filter(item -> item.getType() != null && item.getType().contains("myd")).map(IvrLibaScriptAssort::getId).collect(Collectors.toList()); |
| | | |
| | | //2 再获取该问题满意度分类下的IvrLibaTemplateScript的集合 |
| | | List<IvrLibaTemplateScript> ivrLibaTemplateScripts = ivrLibaTemplateScriptMapper.queryLibTemplateIds(mydIdList); |
| | | |
| | | //2.1 一次性把本次请求全部模板的发送量、填报量查回来(原来是每个模板问题各查两次) |
| | | SatisfactionStatQuery statQuery = buildSatisfactionStatQuery(patSatisfactionReqVO, "1", |
| | | ivrLibaTemplateScripts.stream().map(IvrLibaTemplateScript::getTemplateid).collect(Collectors.toList())); |
| | | Map<Long, Long> sendCountMap = mapSatisfactionSendCount(statQuery); |
| | | SatisfactionFillStat fillStat = querySatisfactionFillStat(statQuery); |
| | | |
| | | //3 按scriptAssortname分组,并获取每个集合进行遍历,计算每个分类的总人数和填报人数 |
| | | Map<String, List<IvrLibaTemplateScript>> scriptGroupByAssortName = ivrLibaTemplateScripts.stream().filter(item -> item.getScriptAssortname() != null).collect(Collectors.groupingBy(IvrLibaTemplateScript::getScriptAssortname)); |
| | | if (scriptGroupByAssortName != null && !scriptGroupByAssortName.isEmpty()) { |
| | | for (String assortNameKey : scriptGroupByAssortName.keySet()) { |
| | | //记录总人数 |
| | | Double subidAll = 0.0; |
| | | //记录总的填报人数 |
| | | Double fillCountAll = 0.0; |
| | | //获取该分类的填报总数量 |
| | | List<ScoreCount> allFillCountList = new ArrayList<>(); |
| | | |
| | | List<IvrLibaTemplateScript> ivrLibaTemplateScriptList = scriptGroupByAssortName.get(assortNameKey); |
| | | for (IvrLibaTemplateScript ivrLibaTemplateScript : ivrLibaTemplateScriptList) { |
| | | //通过模板id取问题发送总量(原来是把全部 sub_id 查回 Java 再取 size) |
| | | subidAll += sendCountMap.getOrDefault(ivrLibaTemplateScript.getTemplateid(), 0L); |
| | | //获取填报数量(原来是把明细行全部查回 Java 再取 size) |
| | | List<ScoreCount> fillCount = fillStat.get(ivrLibaTemplateScript.getTemplateid(), ivrLibaTemplateScript.getScriptContent()); |
| | | if (CollectionUtils.isNotEmpty(fillCount)) allFillCountList.addAll(fillCount); |
| | | fillCountAll += countFillRows(fillCount); |
| | | } |
| | | |
| | | // 这里先记录一下每个分类的总人数和填报人数 |
| | | Map<String, Object> personCount = new HashMap<>(); |
| | | personCount.put("fillCountAll", fillCountAll); |
| | | personCount.put("subidAll", subidAll); |
| | | personCount.put("receiveRate", Arith.div(fillCountAll, subidAll)); |
| | | personCount.put("averageScore", averageScoreByStat(allFillCountList)); |
| | | zzt.put(assortNameKey, personCount); |
| | | assortNameFillCountMap.put(assortNameKey, allFillCountList); |
| | | } |
| | | } |
| | | } |
| | | |
| | | if (patSatisfactionReqVO.getType() == null || patSatisfactionReqVO.getType() == 2) { |
| | | //1. 先获取svy_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()); |
| | | |
| | | //2 再获取该问题满意度分类下的IvrLibaTemplateScript的集合 |
| | | List<SvyLibTemplateScript> svyLibTemplateScripts = svyLibTemplateScriptMapper.querySvyLibTemplateIds(mydIdList); |
| | | |
| | | //2.1 一次性把本次请求全部模板的发送量、填报量查回来(原来是每个模板问题各查两次) |
| | | SatisfactionStatQuery statQuery = buildSatisfactionStatQuery(patSatisfactionReqVO, "2", |
| | | svyLibTemplateScripts.stream().map(SvyLibTemplateScript::getSvyid).collect(Collectors.toList())); |
| | | Map<Long, Long> sendCountMap = mapSatisfactionSendCount(statQuery); |
| | | SatisfactionFillStat fillStat = querySatisfactionFillStat(statQuery); |
| | | |
| | | //3 按scriptAssortname分组,并获取每个集合进行遍历,计算每个分类的总人数和填报人数 |
| | | Map<String, List<SvyLibTemplateScript>> scriptGroupByatCegoryName = svyLibTemplateScripts.stream().filter(item -> item.getCategoryName() != null).collect(Collectors.groupingBy(SvyLibTemplateScript::getCategoryName)); |
| | | if (scriptGroupByatCegoryName != null && !scriptGroupByatCegoryName.isEmpty()) { |
| | | for (String categoryNameKey : scriptGroupByatCegoryName.keySet()) { |
| | | //记录总人数 |
| | | Double subidAll = 0.0; |
| | | //记录总的填报人数 |
| | | Double fillCountAll = 0.0; |
| | | //获取该分类的填报总数量 |
| | | List<ScoreCount> allFillCountList = new ArrayList<>(); |
| | | |
| | | List<SvyLibTemplateScript> svyLibTemplateScriptList = scriptGroupByatCegoryName.get(categoryNameKey); |
| | | for (SvyLibTemplateScript svyLibTemplateScript : svyLibTemplateScriptList) { |
| | | //通过模板id取问题发送总量(原来是把全部 sub_id 查回 Java 再取 size) |
| | | subidAll += sendCountMap.getOrDefault(svyLibTemplateScript.getSvyid(), 0L); |
| | | //获取填报数量(原来是把明细行全部查回 Java 再取 size) |
| | | List<ScoreCount> fillCount = fillStat.get(svyLibTemplateScript.getSvyid(), svyLibTemplateScript.getScriptContent()); |
| | | if (CollectionUtils.isNotEmpty(fillCount)) allFillCountList.addAll(fillCount); |
| | | fillCountAll += countFillRows(fillCount); |
| | | } |
| | | |
| | | |
| | | // 这里先记录一下每个分类的总人数和填报人数 |
| | | Map<String, Object> personCount = (Map<String, Object>) zzt.get(categoryNameKey); |
| | | if (!Objects.isNull(personCount)) { |
| | | Double fillCountAll2 = (Double) personCount.get("fillCountAll"); |
| | | Double subidAll2 = (Double) personCount.get("subidAll"); |
| | | personCount.put("fillCountAll", fillCountAll2 + fillCountAll); |
| | | personCount.put("subidAll", subidAll2 + subidAll); |
| | | personCount.put("receiveRate", Arith.div((Double) personCount.get("fillCountAll"), (Double) personCount.get("subidAll"))); |
| | | |
| | | //看一下之前有没有该分类的填报量 |
| | | List<ScoreCount> serviceSubtaskDetailDTOS = assortNameFillCountMap.get(categoryNameKey); |
| | | if (CollectionUtils.isNotEmpty(serviceSubtaskDetailDTOS)) |
| | | allFillCountList.addAll(serviceSubtaskDetailDTOS); |
| | | personCount.put("averageScore", averageScoreByStat(allFillCountList)); |
| | | } else { |
| | | personCount = new HashMap<>(); |
| | | personCount.put("fillCountAll", fillCountAll); |
| | | personCount.put("subidAll", subidAll); |
| | | personCount.put("receiveRate", Arith.div(fillCountAll, subidAll)); |
| | | personCount.put("averageScore", averageScoreByStat(allFillCountList)); |
| | | zzt.put(categoryNameKey, personCount); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | return zzt; |
| | | } |
| | | |
| | | |
| | | @Override |
| | | public Map<String, Object> satisfactionGraphDimension(PatSatisfactionReqVO patSatisfactionReqVO) { |
| | | //用来记录柱状图信息(key=满意度维度名称,value=该维度下的总人数和填报人数) |
| | | Map<String, Object> zzt = new HashMap<>(); |
| | | |
| | | // 记录一下每个维度的填报情况(只记分值与条数,不再把明细行全部查回 Java) |
| | | Map<String, List<ScoreCount>> assortNameFillCountMap = new HashMap<>(); |
| | | |
| | | if (patSatisfactionReqVO.getType() == null || patSatisfactionReqVO.getType() == 1) { |
| | | //1. 从入参中获取满意度维维度 |
| | | IvrLibaTemplateScript ivrLibaTemplateScript2 = new IvrLibaTemplateScript(); |
| | | ivrLibaTemplateScript2.setDimensionList(patSatisfactionReqVO.getDimensionList()); |
| | | |
| | | //2 再获取该问题满意度分类下的IvrLibaTemplateScript的集合 |
| | | List<IvrLibaTemplateScript> ivrLibaTemplateScripts = ivrLibaTemplateScriptMapper.selectILTSBydimension(ivrLibaTemplateScript2); |
| | | if (ivrLibaTemplateScripts != null && !ivrLibaTemplateScripts.isEmpty()) { |
| | | //3 按dimension维度分组,并获取每个集合进行遍历,计算每个分类的总人数和填报人数 |
| | | List<IvrLibaTemplateScript> dimensionScripts = ivrLibaTemplateScripts.stream().filter(item -> item.getDimension() != null).collect(Collectors.toList()); |
| | | Map<String, List<IvrLibaTemplateScript>> groupByDimension = dimensionScripts.stream().collect(Collectors.groupingBy(IvrLibaTemplateScript::getDimension)); |
| | | |
| | | if (groupByDimension != null && !groupByDimension.isEmpty()) { |
| | | //3.1 一次性把本次请求全部模板的发送量、填报量查回来(原来是每个模板问题各查两次) |
| | | SatisfactionStatQuery statQuery = buildSatisfactionStatQuery(patSatisfactionReqVO, "1", |
| | | dimensionScripts.stream().map(IvrLibaTemplateScript::getTemplateid).collect(Collectors.toList())); |
| | | Map<Long, Long> sendCountMap = mapSatisfactionSendCount(statQuery); |
| | | SatisfactionFillStat fillStat = querySatisfactionFillStat(statQuery); |
| | | |
| | | for (String dimension : groupByDimension.keySet()) { |
| | | //记录总人数 |
| | | Double subidAll = 0.0; |
| | | //记录总的填报人数 |
| | | Double fillCountAll = 0.0; |
| | | //获取该维度的填报总数量 |
| | | List<ScoreCount> allFillCountList = new ArrayList<>(); |
| | | |
| | | List<IvrLibaTemplateScript> ivrLibaTemplateScriptList = groupByDimension.get(dimension); |
| | | for (IvrLibaTemplateScript ivrLibaTemplateScript : ivrLibaTemplateScriptList) { |
| | | //通过模板id取问题发送总量(原来是把全部 sub_id 查回 Java 再取 size) |
| | | subidAll += sendCountMap.getOrDefault(ivrLibaTemplateScript.getTemplateid(), 0L); |
| | | //获取填报数量(原来是把明细行全部查回 Java 再取 size) |
| | | List<ScoreCount> fillCount = fillStat.get(ivrLibaTemplateScript.getTemplateid(), ivrLibaTemplateScript.getScriptContent()); |
| | | if (CollectionUtils.isNotEmpty(fillCount)) allFillCountList.addAll(fillCount); |
| | | fillCountAll += countFillRows(fillCount); |
| | | } |
| | | |
| | | // 这里先记录一下每个分类的总人数和填报人数 |
| | | Map<String, Object> personCount = new HashMap<>(); |
| | | personCount.put("fillCountAll", fillCountAll); |
| | | personCount.put("subidAll", subidAll); |
| | | personCount.put("receiveRate", Arith.div(fillCountAll, subidAll)); |
| | | personCount.put("averageScore", averageScoreByStat(allFillCountList)); |
| | | zzt.put(dimension, personCount); |
| | | assortNameFillCountMap.put(dimension, allFillCountList); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | if (patSatisfactionReqVO.getType() == null || patSatisfactionReqVO.getType() == 2) { |
| | | //1. 从入参中获取满意度维维度 |
| | | SvyLibTemplateScriptVO svyLibTemplateScriptVO = new SvyLibTemplateScriptVO(); |
| | | svyLibTemplateScriptVO.setDimensionList(patSatisfactionReqVO.getDimensionList()); |
| | | |
| | | //2 再获取该问题满意度分类下的IvrLibaTemplateScript的集合 |
| | | List<SvyLibTemplateScript> svyLibTemplateScripts = svyLibTemplateScriptMapper.selectSLTScriptListBydimension(svyLibTemplateScriptVO); |
| | | if (svyLibTemplateScripts != null && !svyLibTemplateScripts.isEmpty()) { |
| | | //3 按dimension维度分组,并获取每个集合进行遍历,计算每个分类的总人数和填报人数 |
| | | List<SvyLibTemplateScript> dimensionScripts = svyLibTemplateScripts.stream().filter(item -> item.getDimension() != null).collect(Collectors.toList()); |
| | | Map<String, List<SvyLibTemplateScript>> groupByDimension = dimensionScripts.stream().collect(Collectors.groupingBy(SvyLibTemplateScript::getDimension)); |
| | | if (groupByDimension != null && !groupByDimension.isEmpty()) { |
| | | //3.1 一次性把本次请求全部模板的发送量、填报量查回来(原来是每个模板问题各查两次) |
| | | SatisfactionStatQuery statQuery = buildSatisfactionStatQuery(patSatisfactionReqVO, "2", |
| | | dimensionScripts.stream().map(SvyLibTemplateScript::getSvyid).collect(Collectors.toList())); |
| | | Map<Long, Long> sendCountMap = mapSatisfactionSendCount(statQuery); |
| | | SatisfactionFillStat fillStat = querySatisfactionFillStat(statQuery); |
| | | |
| | | for (String dimension : groupByDimension.keySet()) { |
| | | //记录总人数 |
| | | Double subidAll = 0.0; |
| | | //记录总的填报人数 |
| | | Double fillCountAll = 0.0; |
| | | //获取该分类的填报总数量 |
| | | List<ScoreCount> allFillCountList = new ArrayList<>(); |
| | | |
| | | List<SvyLibTemplateScript> svyLibTemplateScriptList = groupByDimension.get(dimension); |
| | | for (SvyLibTemplateScript svyLibTemplateScript : svyLibTemplateScriptList) { |
| | | //通过模板id取问题发送总量(原来是把全部 sub_id 查回 Java 再取 size) |
| | | subidAll += sendCountMap.getOrDefault(svyLibTemplateScript.getSvyid(), 0L); |
| | | //获取填报数量(原来是把明细行全部查回 Java 再取 size) |
| | | List<ScoreCount> fillCount = fillStat.get(svyLibTemplateScript.getSvyid(), svyLibTemplateScript.getScriptContent()); |
| | | if (CollectionUtils.isNotEmpty(fillCount)) allFillCountList.addAll(fillCount); |
| | | fillCountAll += countFillRows(fillCount); |
| | | } |
| | | |
| | | |
| | | // 这里先记录一下每个分类的总人数和填报人数 |
| | | Map<String, Object> personCount = (Map<String, Object>) zzt.get(dimension); |
| | | if (!Objects.isNull(personCount)) { |
| | | Double fillCountAll2 = (Double) personCount.get("fillCountAll"); |
| | | Double subidAll2 = (Double) personCount.get("subidAll"); |
| | | personCount.put("fillCountAll", fillCountAll2 + fillCountAll); |
| | | personCount.put("subidAll", subidAll2 + subidAll); |
| | | personCount.put("receiveRate", Arith.div((Double) personCount.get("fillCountAll"), (Double) personCount.get("subidAll"))); |
| | | |
| | | //看一下之前有没有该分类的填报量 |
| | | List<ScoreCount> serviceSubtaskDetailDTOS = assortNameFillCountMap.get(dimension); |
| | | if (CollectionUtils.isNotEmpty(serviceSubtaskDetailDTOS)) |
| | | allFillCountList.addAll(serviceSubtaskDetailDTOS); |
| | | personCount.put("averageScore", averageScoreByStat(allFillCountList)); |
| | | } else { |
| | | personCount = new HashMap<>(); |
| | | personCount.put("fillCountAll", fillCountAll); |
| | | personCount.put("subidAll", subidAll); |
| | | personCount.put("receiveRate", Arith.div(fillCountAll, subidAll)); |
| | | personCount.put("averageScore", averageScoreByStat(allFillCountList)); |
| | | zzt.put(dimension, personCount); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | 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 聚合统计(替代逐个模板查库) ==================== |
| | | |
| | | /** |
| | | * 构建聚合统计入参(取值来源与原 DtoConversionUtils.sourceToTarget(patSatisfactionReqVO, ServiceSubtaskSatisfactionEntity.class) 一致) |
| | | * |
| | | * @param reqVO 原始请求 |
| | | * @param type service_subtask.type(1 语音、2 问卷),用字符串比较避免隐式转换 |
| | | * @param templateIds 本次请求涉及的模板id(ivr 用 templateid,问卷用 svyid) |
| | | */ |
| | | private SatisfactionStatQuery buildSatisfactionStatQuery(PatSatisfactionReqVO reqVO, String type, List<Long> templateIds) { |
| | | SatisfactionStatQuery statQuery = new SatisfactionStatQuery(); |
| | | statQuery.setOrgid(reqVO.getOrgid()); |
| | | statQuery.setCampusid(reqVO.getCampusid()); |
| | | statQuery.setType(type); |
| | | statQuery.setStartTime(reqVO.getStartTime()); |
| | | statQuery.setEndTime(reqVO.getEndTime()); |
| | | statQuery.setDeptcodes(reqVO.getDeptcodes()); |
| | | statQuery.setHospitaldistrictcodes(reqVO.getHospitaldistrictcodes()); |
| | | statQuery.setLibTemplateIds(CollectionUtils.isEmpty(templateIds) ? templateIds |
| | | : templateIds.stream().filter(Objects::nonNull).distinct().collect(Collectors.toList())); |
| | | return statQuery; |
| | | } |
| | | |
| | | /** |
| | | * 一次查出全部模板的发送量 |
| | | * |
| | | * @return key=模板id,value=发送量(等价于原 querySendCount 结果的 size) |
| | | */ |
| | | private Map<Long, Long> mapSatisfactionSendCount(SatisfactionStatQuery statQuery) { |
| | | Map<Long, Long> sendCountMap = new HashMap<>(); |
| | | if (statQuery == null || CollectionUtils.isEmpty(statQuery.getLibTemplateIds())) return sendCountMap; |
| | | List<Map<String, Object>> rows = serviceSubtaskMapper.selectSatisfactionSendCount(statQuery); |
| | | if (CollectionUtils.isEmpty(rows)) return sendCountMap; |
| | | for (Map<String, Object> row : rows) { |
| | | Long templateId = toLong(row.get("libtemplateid")); |
| | | if (templateId == null) continue; |
| | | sendCountMap.put(templateId, toLong(row.get("cnt"), 0L)); |
| | | } |
| | | return sendCountMap; |
| | | } |
| | | |
| | | /** |
| | | * 一次查出全部模板的填报量(按 模板+问题+分值 分组) |
| | | */ |
| | | private SatisfactionFillStat querySatisfactionFillStat(SatisfactionStatQuery statQuery) { |
| | | SatisfactionFillStat fillStat = new SatisfactionFillStat(); |
| | | if (statQuery == null || CollectionUtils.isEmpty(statQuery.getLibTemplateIds())) return fillStat; |
| | | List<Map<String, Object>> rows = serviceSubtaskDetailMapper.selectSatisfactionFillStat(statQuery); |
| | | if (CollectionUtils.isEmpty(rows)) return fillStat; |
| | | for (Map<String, Object> row : rows) { |
| | | Long templateId = toLong(row.get("libtemplateid")); |
| | | if (templateId == null) continue; |
| | | long cnt = toLong(row.get("cnt"), 0L); |
| | | if (cnt <= 0L) continue; |
| | | Object scoreValue = row.get("score"); |
| | | Object questiontextValue = row.get("questiontext"); |
| | | ScoreCount scoreCount = new ScoreCount(scoreValue == null ? null : String.valueOf(scoreValue), cnt); |
| | | fillStat.add(templateId, questiontextValue == null ? null : String.valueOf(questiontextValue), scoreCount); |
| | | } |
| | | return fillStat; |
| | | } |
| | | |
| | | /** |
| | | * 取填报明细行数(原 fillCount.size()) |
| | | */ |
| | | private long countFillRows(List<ScoreCount> fillCount) { |
| | | if (CollectionUtils.isEmpty(fillCount)) return 0L; |
| | | long rows = 0L; |
| | | for (ScoreCount scoreCount : fillCount) { |
| | | rows += scoreCount.getCount(); |
| | | } |
| | | return rows; |
| | | } |
| | | |
| | | /** |
| | | * 获取平均分(聚合版:分值与条数) |
| | | * <p> |
| | | * 口径与 {@link #averageScore(List)} 完全一致:分子为可解析分值之和,分母为明细行数(包含分值为空的行)。 |
| | | * 注意不能改成 sql 的 avg() 或「有分值的行数」做分母,否则返回的 averageScore 会变。 |
| | | */ |
| | | private Double averageScoreByStat(List<ScoreCount> fillCount) { |
| | | if (CollectionUtils.isEmpty(fillCount)) return 0.0; |
| | | double sum = 0.0; |
| | | long rows = 0L; |
| | | for (ScoreCount scoreCount : fillCount) { |
| | | if (scoreCount.getCount() <= 0L) continue; |
| | | rows += scoreCount.getCount(); |
| | | if (scoreCount.getScore() != null && !scoreCount.getScore().isEmpty()) { |
| | | sum += Double.parseDouble(scoreCount.getScore()) * scoreCount.getCount(); |
| | | } |
| | | } |
| | | if (rows == 0L) return 0.0; |
| | | return new BigDecimal(sum / rows).setScale(1, RoundingMode.HALF_UP).doubleValue(); |
| | | } |
| | | |
| | | /** |
| | | * 分值分组键:模板id + 分隔符 + 规范化后的问题内容 |
| | | * <p> |
| | | * MySQL 里 questiontext = #{questiontext} 是「忽略大小写、忽略尾部空格」的比较, |
| | | * 这里用 trim + 小写做等价匹配。 |
| | | */ |
| | | private static String statKey(Long templateId, String questiontext) { |
| | | return templateId + "\u0001" + (questiontext == null ? "" : questiontext.trim().toLowerCase(Locale.ROOT)); |
| | | } |
| | | |
| | | private static Long toLong(Object value) { |
| | | if (value == null) return null; |
| | | if (value instanceof Number) return ((Number) value).longValue(); |
| | | String text = String.valueOf(value).trim(); |
| | | return text.isEmpty() ? null : Long.valueOf(text); |
| | | } |
| | | |
| | | private static long toLong(Object value, long defaultValue) { |
| | | Long result = toLong(value); |
| | | return result == null ? defaultValue : result; |
| | | } |
| | | |
| | | /** |
| | | * 满意度分值聚合结果:分值 + 该分值对应的明细行数 |
| | | */ |
| | | private static class ScoreCount { |
| | | private final String score; |
| | | private final long count; |
| | | |
| | | ScoreCount(String score, long count) { |
| | | this.score = score; |
| | | this.count = count; |
| | | } |
| | | |
| | | String getScore() { |
| | | return score; |
| | | } |
| | | |
| | | long getCount() { |
| | | return count; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 满意度填报量聚合结果 |
| | | * <p> |
| | | * byTemplateQuestion:按 模板+问题 分组; |
| | | * byTemplate:按模板分组(该模板全部问题),用于兼容原 queryFillCount 在 |
| | | * script_content 为 null 时不拼 questiontext 条件的行为。 |
| | | */ |
| | | private static class SatisfactionFillStat { |
| | | private final Map<String, List<ScoreCount>> byTemplateQuestion = new HashMap<>(); |
| | | private final Map<Long, List<ScoreCount>> byTemplate = new HashMap<>(); |
| | | |
| | | void add(Long templateId, String questiontext, ScoreCount scoreCount) { |
| | | byTemplate.computeIfAbsent(templateId, key -> new ArrayList<>()).add(scoreCount); |
| | | if (questiontext != null) { |
| | | byTemplateQuestion.computeIfAbsent(statKey(templateId, questiontext), key -> new ArrayList<>()).add(scoreCount); |
| | | } |
| | | } |
| | | |
| | | List<ScoreCount> get(Long templateId, String scriptContent) { |
| | | if (templateId == null) return null; |
| | | if (scriptContent == null) return byTemplate.get(templateId); |
| | | return byTemplateQuestion.get(statKey(templateId, scriptContent)); |
| | | } |
| | | } |
| | | |
| | | // ==================== statistics / statisticsByDimension 批量统计(替代逐个问题查库) ==================== |
| | | |
| | | /** |
| | | * 满意度明细统计(statistics、statisticsByDimension 共用) |
| | | * <p> |
| | | * 把原来「每个问题 3 条 SQL」改成「整个请求 3 条查询 + 内存组装」: |
| | | * ① 发送量按 libtemplateid 分组;② 填报量按 模板+问题+matchedtext+score 分组;③ 选项分值按 scriptid 分组。 |
| | | * |
| | | * @param reqVO 原始请求 |
| | | * @param type service_subtask.type(1 语音、2 问卷) |
| | | * @param voice true=语音问题(选项分值取 ivr_liba_template_targetoption) |
| | | * @param scriptRefs 本次要统计的问题(顺序即返回顺序) |
| | | * @param out 明细结果 |
| | | * @param totals [0]=totalPerson, [1]=allPerson |
| | | */ |
| | | private void buildSatisfactionDetailEntities(PatSatisfactionReqVO reqVO, String type, boolean voice, |
| | | List<SatScriptRef> scriptRefs, |
| | | List<PatSatisfactionDetailEntity> out, int[] totals) { |
| | | if (CollectionUtils.isEmpty(scriptRefs)) return; |
| | | |
| | | List<Long> templateIds = scriptRefs.stream().map(SatScriptRef::getTemplateId) |
| | | .filter(Objects::nonNull).distinct().collect(Collectors.toList()); |
| | | SatisfactionStatQuery statQuery = buildSatisfactionStatQuery(reqVO, type, templateIds); |
| | | Map<Long, Long> sendCountMap = mapSatisfactionSendCount(statQuery); |
| | | SatisfactionFillStatByMatchedtext fillStat = querySatisfactionFillStatByMatchedtext(statQuery); |
| | | Map<Long, List<String>> optionScores = loadTargetOptionScores(voice, scriptRefs); |
| | | |
| | | for (SatScriptRef scriptRef : scriptRefs) { |
| | | Long templateId = scriptRef.getTemplateId(); |
| | | //发送总量(原来是把全部 sub_id 查回 Java 再取 size) |
| | | long sendCount = templateId == null ? 0L : sendCountMap.getOrDefault(templateId, 0L); |
| | | //填报数量(原来是把明细行全部查回 Java 再统计) |
| | | List<MatchedtextCount> fillRows = fillStat.get(templateId, scriptRef.getScriptContent()); |
| | | long fillCount = countFillRowsByMatchedtext(fillRows); |
| | | |
| | | PatSatisfactionDetailEntity detail = new PatSatisfactionDetailEntity(); |
| | | detail.setScriptContent(scriptRef.getScriptContent()); |
| | | detail.setAnswerPerson((int) fillCount); |
| | | detail.setNoAnswerPerson(fillCount > 0L && sendCount > 0L ? (int) (sendCount - fillCount) : sendCount > 0L ? (int) sendCount : 0); |
| | | detail.setAverageScore(String.valueOf(averageScoreByMatchedtext(fillRows))); |
| | | List<String> scores = optionScores.get(scriptRef.getOptionScriptId()); |
| | | detail.setMaxScore(String.valueOf(maxScoreOf(scores))); |
| | | detail.setMinScore(String.valueOf(minScoreOf(scores))); |
| | | detail.setAnswerRate(sendCount == 0L ? 0 : fillCount == 0L ? 0 : fillCount * 1.0 / sendCount); |
| | | detail.setMatchedtextStats(buildMatchedtextStats(fillRows, fillCount)); |
| | | out.add(detail); |
| | | |
| | | totals[0] += (int) fillCount; |
| | | totals[1] += (int) sendCount; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 一次查出全部问题的填报量(按 模板+问题+matchedtext+score 分组,含 targetvalue) |
| | | */ |
| | | private SatisfactionFillStatByMatchedtext querySatisfactionFillStatByMatchedtext(SatisfactionStatQuery statQuery) { |
| | | SatisfactionFillStatByMatchedtext fillStat = new SatisfactionFillStatByMatchedtext(); |
| | | if (statQuery == null || CollectionUtils.isEmpty(statQuery.getLibTemplateIds())) return fillStat; |
| | | List<Map<String, Object>> rows = serviceSubtaskDetailMapper.selectSatisfactionFillStatByMatchedtext(statQuery); |
| | | if (CollectionUtils.isEmpty(rows)) return fillStat; |
| | | for (Map<String, Object> row : rows) { |
| | | Long templateId = toLong(row.get("libtemplateid")); |
| | | if (templateId == null) continue; |
| | | long cnt = toLong(row.get("cnt"), 0L); |
| | | if (cnt <= 0L) continue; |
| | | Object questiontext = row.get("questiontext"); |
| | | Object matchedtext = row.get("matchedtext"); |
| | | Object score = row.get("score"); |
| | | Object targetvalue = row.get("targetvalue"); |
| | | fillStat.add(templateId, questiontext == null ? null : String.valueOf(questiontext), |
| | | new MatchedtextCount(matchedtext == null ? null : String.valueOf(matchedtext), |
| | | score == null ? null : String.valueOf(score), |
| | | targetvalue == null ? null : String.valueOf(targetvalue), cnt)); |
| | | } |
| | | return fillStat; |
| | | } |
| | | |
| | | /** |
| | | * 一次查出全部问题的选项分值(只取 scriptid、score 两列) |
| | | * <p> |
| | | * 原实现是每个问题查一次整行,而调用方只用到 score 算最高/最低分; |
| | | * 问题 id 为 null 时原实现不拼 scriptid 条件(等于取该表全部分值),这里用 null 作为 key 存这一份。 |
| | | */ |
| | | private Map<Long, List<String>> loadTargetOptionScores(boolean voice, List<SatScriptRef> scriptRefs) { |
| | | Map<Long, List<String>> optionScores = new HashMap<>(); |
| | | List<Long> scriptIds = scriptRefs.stream().map(SatScriptRef::getOptionScriptId) |
| | | .filter(Objects::nonNull).distinct().collect(Collectors.toList()); |
| | | if (CollectionUtils.isNotEmpty(scriptIds)) { |
| | | addTargetOptionScores(optionScores, voice |
| | | ? ivrLibaTemplateTargetoptionMapper.selectIvrLibaTargetoptionScores(scriptIds) |
| | | : svyLibTemplateTargetoptionMapper.selectSvyLibTargetoptionScores(scriptIds)); |
| | | } |
| | | if (scriptRefs.stream().anyMatch(ref -> ref.getOptionScriptId() == null)) { |
| | | optionScores.put(null, collectTargetOptionScores(voice |
| | | ? ivrLibaTemplateTargetoptionMapper.selectIvrLibaTargetoptionScores(null) |
| | | : svyLibTemplateTargetoptionMapper.selectSvyLibTargetoptionScores(null))); |
| | | } |
| | | return optionScores; |
| | | } |
| | | |
| | | private void addTargetOptionScores(Map<Long, List<String>> optionScores, List<Map<String, Object>> rows) { |
| | | if (CollectionUtils.isEmpty(rows)) return; |
| | | for (Map<String, Object> row : rows) { |
| | | Long scriptId = toLong(row.get("scriptid")); |
| | | if (scriptId == null) continue; |
| | | Object score = row.get("score"); |
| | | optionScores.computeIfAbsent(scriptId, key -> new ArrayList<>()) |
| | | .add(score == null ? null : String.valueOf(score)); |
| | | } |
| | | } |
| | | |
| | | private List<String> collectTargetOptionScores(List<Map<String, Object>> rows) { |
| | | List<String> scores = new ArrayList<>(); |
| | | if (CollectionUtils.isEmpty(rows)) return scores; |
| | | for (Map<String, Object> row : rows) { |
| | | Object score = row.get("score"); |
| | | scores.add(score == null ? null : String.valueOf(score)); |
| | | } |
| | | return scores; |
| | | } |
| | | |
| | | /** |
| | | * 各选项(matchedtext)数量及占比 |
| | | * <p> |
| | | * 口径与原实现一致:分母是全部明细行数(含 matchedtext 为 null 的行); |
| | | * targetvalue 用于补全未出现的选项(count=0、ratio=0)。 |
| | | */ |
| | | private Map<String, Map<String, Object>> buildMatchedtextStats(List<MatchedtextCount> fillRows, long totalFill) { |
| | | Map<String, Map<String, Object>> matchedtextStats = new HashMap<>(); |
| | | if (CollectionUtils.isEmpty(fillRows) || totalFill <= 0L) return matchedtextStats; |
| | | |
| | | Map<String, Long> matchedtextCounts = new LinkedHashMap<>(); |
| | | String targetvalue = null; |
| | | for (MatchedtextCount row : fillRows) { |
| | | if (row.getMatchedtext() != null) { |
| | | matchedtextCounts.merge(row.getMatchedtext(), row.getCount(), Long::sum); |
| | | } |
| | | if (targetvalue == null && row.getTargetvalue() != null) { |
| | | targetvalue = row.getTargetvalue(); |
| | | } |
| | | } |
| | | for (Map.Entry<String, Long> entry : matchedtextCounts.entrySet()) { |
| | | Map<String, Object> stat = new HashMap<>(); |
| | | stat.put("count", entry.getValue()); |
| | | stat.put("ratio", new BigDecimal((double) entry.getValue() / totalFill * 100).setScale(1, RoundingMode.HALF_UP).doubleValue()); |
| | | matchedtextStats.put(entry.getKey(), stat); |
| | | } |
| | | // 从明细里取targetvalue(&分隔的当前问题的所有选项),补全未出现的选项,count和ratio均为0 |
| | | if (targetvalue != null) { |
| | | for (String option : targetvalue.split("&")) { |
| | | String optionTrim = option.trim(); |
| | | if (!matchedtextStats.containsKey(optionTrim)) { |
| | | Map<String, Object> emptyStat = new HashMap<>(); |
| | | emptyStat.put("count", 0L); |
| | | emptyStat.put("ratio", 0.0); |
| | | matchedtextStats.put(optionTrim, emptyStat); |
| | | } |
| | | } |
| | | } |
| | | return matchedtextStats; |
| | | } |
| | | |
| | | /** |
| | | * 取填报明细行数(原 fillCount.size()) |
| | | */ |
| | | private long countFillRowsByMatchedtext(List<MatchedtextCount> fillRows) { |
| | | if (CollectionUtils.isEmpty(fillRows)) return 0L; |
| | | long rows = 0L; |
| | | for (MatchedtextCount row : fillRows) { |
| | | rows += row.getCount(); |
| | | } |
| | | return rows; |
| | | } |
| | | |
| | | /** |
| | | * 获取平均分(聚合版:分值与条数) |
| | | * <p> |
| | | * 口径与 {@link #averageScore(List)} 一致:分子为可解析分值之和,分母为明细行数(含分值为空的行)。 |
| | | */ |
| | | private Double averageScoreByMatchedtext(List<MatchedtextCount> fillRows) { |
| | | if (CollectionUtils.isEmpty(fillRows)) return 0.0; |
| | | double sum = 0.0; |
| | | long rows = 0L; |
| | | for (MatchedtextCount row : fillRows) { |
| | | if (row.getCount() <= 0L) continue; |
| | | rows += row.getCount(); |
| | | if (row.getScore() != null && !row.getScore().isEmpty()) { |
| | | sum += Double.parseDouble(row.getScore()) * row.getCount(); |
| | | } |
| | | } |
| | | if (rows == 0L) return 0.0; |
| | | return new BigDecimal(sum / rows).setScale(1, RoundingMode.HALF_UP).doubleValue(); |
| | | } |
| | | |
| | | /** |
| | | * 获取最高分(聚合版,口径同 {@link #maxScore(List)}:空列表返回 0.0) |
| | | */ |
| | | private Double maxScoreOf(List<String> scores) { |
| | | if (CollectionUtils.isEmpty(scores)) return 0.0; |
| | | return scores.stream().filter(scoreStr -> scoreStr != null && !scoreStr.isEmpty()) |
| | | .mapToDouble(Double::parseDouble).max().orElse(0.0); |
| | | } |
| | | |
| | | /** |
| | | * 获取最低分(聚合版,口径同 {@link #minScore(List)}:空列表返回 0.0) |
| | | */ |
| | | private Double minScoreOf(List<String> scores) { |
| | | if (CollectionUtils.isEmpty(scores)) return 0.0; |
| | | return scores.stream().filter(scoreStr -> scoreStr != null && !scoreStr.isEmpty()) |
| | | .mapToDouble(Double::parseDouble).min().orElse(0.0); |
| | | } |
| | | |
| | | /** |
| | | * 满意度统计用的问题引用:模板id、选项表关联的问题id(可能为 null)、问题内容 |
| | | */ |
| | | private static class SatScriptRef { |
| | | private final Long templateId; |
| | | private final Long optionScriptId; |
| | | private final String scriptContent; |
| | | |
| | | SatScriptRef(Long templateId, Long optionScriptId, String scriptContent) { |
| | | this.templateId = templateId; |
| | | this.optionScriptId = optionScriptId; |
| | | this.scriptContent = scriptContent; |
| | | } |
| | | |
| | | Long getTemplateId() { |
| | | return templateId; |
| | | } |
| | | |
| | | Long getOptionScriptId() { |
| | | return optionScriptId; |
| | | } |
| | | |
| | | String getScriptContent() { |
| | | return scriptContent; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 满意度填报聚合结果:matchedtext + score + targetvalue + 条数 |
| | | */ |
| | | private static class MatchedtextCount { |
| | | private final String matchedtext; |
| | | private final String score; |
| | | private final String targetvalue; |
| | | private final long count; |
| | | |
| | | MatchedtextCount(String matchedtext, String score, String targetvalue, long count) { |
| | | this.matchedtext = matchedtext; |
| | | this.score = score; |
| | | this.targetvalue = targetvalue; |
| | | this.count = count; |
| | | } |
| | | |
| | | String getMatchedtext() { |
| | | return matchedtext; |
| | | } |
| | | |
| | | String getScore() { |
| | | return score; |
| | | } |
| | | |
| | | String getTargetvalue() { |
| | | return targetvalue; |
| | | } |
| | | |
| | | long getCount() { |
| | | return count; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 满意度填报聚合结果(按 matchedtext 版) |
| | | * <p> |
| | | * byTemplateQuestion:按 模板+问题 分组; |
| | | * byTemplate:按模板分组(该模板全部问题),用于兼容原 queryFillCount 在 |
| | | * script_content 为 null 时不拼 questiontext 条件的行为。 |
| | | */ |
| | | private static class SatisfactionFillStatByMatchedtext { |
| | | private final Map<String, List<MatchedtextCount>> byTemplateQuestion = new HashMap<>(); |
| | | private final Map<Long, List<MatchedtextCount>> byTemplate = new HashMap<>(); |
| | | |
| | | void add(Long templateId, String questiontext, MatchedtextCount row) { |
| | | byTemplate.computeIfAbsent(templateId, key -> new ArrayList<>()).add(row); |
| | | if (questiontext != null) { |
| | | byTemplateQuestion.computeIfAbsent(statKey(templateId, questiontext), key -> new ArrayList<>()).add(row); |
| | | } |
| | | } |
| | | |
| | | List<MatchedtextCount> get(Long templateId, String scriptContent) { |
| | | if (templateId == null) return null; |
| | | if (scriptContent == null) return byTemplate.get(templateId); |
| | | return byTemplateQuestion.get(statKey(templateId, scriptContent)); |
| | | } |
| | | } |
| | | } |