package com.smartor.service.impl;
|
|
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.service.IPatSatisfactionService;
|
|
import static com.ruoyi.common.utils.SecurityUtils.getLoginUser;
|
|
/**
|
* 患者满意度Service业务层处理
|
*
|
* @author lihu
|
* @date 2025-06-30
|
*/
|
@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 id 患者满意度主键
|
* @return 患者满意度
|
*/
|
@Override
|
public PatSatisfaction selectPatSatisfactionById(Long id) {
|
PatSatisfaction ps = patSatisfactionMapper.selectPatSatisfactionById(id);
|
double total = ps.getDoctorSatisfaction() + ps.getAuthenticity() + ps.getWeekFinish() + ps.getStandard() + ps.getTimeliness() + ps.getLibrary() + ps.getEnvironment() + ps.getNurseSatisfaction();
|
ps.setTotal(total);
|
return ps;
|
}
|
|
/**
|
* 查询患者满意度列表
|
*
|
* @param patSatisfaction 患者满意度
|
* @return 患者满意度
|
*/
|
@Override
|
public List<PatSatisfaction> selectPatSatisfactionList(PatSatisfaction patSatisfaction) {
|
List<PatSatisfaction> patSatisfactions = patSatisfactionMapper.selectPatSatisfactionList(patSatisfaction);
|
if (CollectionUtils.isNotEmpty(patSatisfactions)) {
|
patSatisfactions.forEach(ps -> {
|
double total = ps.getDoctorSatisfaction() + ps.getAuthenticity() + ps.getWeekFinish() + ps.getStandard() + ps.getTimeliness() + ps.getLibrary() + ps.getEnvironment() + ps.getNurseSatisfaction();
|
ps.setTotal(total);
|
});
|
}
|
return patSatisfactions;
|
}
|
|
/**
|
* 新增患者满意度
|
*
|
* @param patSatisfaction 患者满意度
|
* @return 结果
|
*/
|
@Override
|
public int insertPatSatisfaction(PatSatisfaction patSatisfaction) {
|
patSatisfaction.setCreateTime(DateUtils.getNowDate());
|
patSatisfaction.setUpdateTime(DateUtils.getNowDate());
|
return patSatisfactionMapper.insertPatSatisfaction(patSatisfaction);
|
}
|
|
/**
|
* 新增患者满意度
|
*
|
* @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 患者满意度
|
* @return 结果
|
*/
|
@Override
|
public int updatePatSatisfaction(PatSatisfaction patSatisfaction) {
|
patSatisfaction.setUpdateTime(DateUtils.getNowDate());
|
return patSatisfactionMapper.updatePatSatisfaction(patSatisfaction);
|
}
|
|
/**
|
* 批量删除患者满意度
|
*
|
* @param ids 需要删除的患者满意度主键
|
* @return 结果
|
*/
|
@Override
|
public int deletePatSatisfactionByIds(Long[] ids) {
|
return patSatisfactionMapper.deletePatSatisfactionByIds(ids);
|
}
|
|
/**
|
* 删除患者满意度信息
|
*
|
* @param id 患者满意度主键
|
* @return 结果
|
*/
|
@Override
|
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));
|
}
|
}
|
}
|