| | |
| | | } |
| | | |
| | | List<ServiceSubtaskDetailRatioExport> serviceSubtaskDetailRatioExports = serviceSubtaskMapper.statQuestionOptionBySubtaskIds(subtaskIds, startOutHospTime, endOutHospTime, startFinishTime, endFinishTime, scoreStart, scoreEnd, scriptContent, valueType); |
| | | // 处理 targetvalue:按 & 分割成多条记录 |
| | | // SQL 已按「题目 + 选项」汇总数量,这里再按题目合并(不同模板的 targetvalue 选项顺序不同,同一选项会拆成多行), |
| | | // 并把没人选的选项补成 0 行、按各选项数量之和重算比例 |
| | | return mergeMydOptionRows(serviceSubtaskDetailRatioExports); |
| | | } |
| | | |
| | | /** |
| | | * 把 SQL 返回的「题目 + 选项」明细合并成「一行一个选项」 |
| | | * <ul> |
| | | * <li>同一题目下相同 optionresult 的数量累加:原实现遇到同名选项直接 continue, |
| | | * 每个选项只会保留第一条明细的数量,多任务/多患者的项数全被丢掉,前端「数量」永远只有 1</li> |
| | | * <li>选项清单取 targetvalue(用 & 连接),没人选的选项补 0 行,前端固定六列才能渲染</li> |
| | | * <li>比例 = 该选项数量 / 该题各选项数量之和,保证一道题各选项比例合计 100%</li> |
| | | * </ul> |
| | | * |
| | | * @param rows SQL 明细 |
| | | */ |
| | | private List<ServiceSubtaskDetailRatioExport> mergeMydOptionRows(List<ServiceSubtaskDetailRatioExport> rows) { |
| | | 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 |
| | | ServiceSubtaskDetailRatioExport existingExport = result.stream().filter(e -> { |
| | | if (e == null || export == null) { |
| | | return false; |
| | | if (CollectionUtils.isEmpty(rows)) { |
| | | return result; |
| | | } |
| | | String q1 = e.getQuestiontext() != null ? e.getQuestiontext() : ""; |
| | | String q2 = export.getQuestiontext() != null ? export.getQuestiontext() : ""; |
| | | String t1 = value.trim(); |
| | | String t2 = e.getTargetvalue() != null ? e.getTargetvalue() : ""; |
| | | return q1.equals(q2) && t1.equals(t2); |
| | | }).findFirst().orElse(null); |
| | | if (existingExport != null && "0".equals(existingExport.getRatio()) && "0".equals(existingExport.getCount())) { |
| | | //如果存在,则将 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 && !"0".equals(existingExport.getRatio())) { |
| | | |
| | | // 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; |
| | | } 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); |
| | | 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]); |
| | | } |
| | | } else { |
| | | // targetvalue 不包含 &,直接添加到结果 |
| | | result.add(export); |
| | | |
| | | // 患者实际选中的选项:多选题 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; |
| | | } |
| | | |
| | | /** |
| | | * 单选/问答题的作答文本:不按 & 拆,避免把问答题的文本答案拆坏 |
| | | */ |
| | | 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 否" -> "否" |
| | | */ |