| | |
| | | /** |
| | | * 省立同德满意度数据统计(按题目统计选项数量/占比,并给出该题的综合得分) |
| | | * <p> |
| | | * 综合满意度 = Σ(选项分值 × 项数) / Σ项数 / 本题最高分值 × 100%, |
| | | * 分值取 1、0.8、0.6、0.4、0.2 五档(很满意、满意、一般、不满意、很不满意); |
| | | * 「不适用」、0 分、空值等既不进分子也不进分母。 |
| | | * 综合满意度 = (很满意项数×1 + 满意项数×0.8 + 一般项数×0.6 + 不满意项数×0.4 + 很不满意项数×0.2) |
| | | * / (很满意项数 + 满意项数 + 一般项数 + 不满意项数 + 很不满意项数) × 100%; |
| | | * 「未经历」「不适用」等不在五档里的选项既不进分子也不进分母。 |
| | | */ |
| | | @ApiOperation("省立同德满意度数据统计") |
| | | @AddOrgId(field = "orgid", paramIndex = 0, campusField = "campusid") |
| | |
| | | QuestionResultDTO questionResultDTO = new QuestionResultDTO(); |
| | | questionResultDTO.setQeustionText(questionText); |
| | | questionResultDTO.setSubtaskDetailRatioExportList(exportList); |
| | | //综合得分:Σ(分值×项数) / Σ项数 / 本题最高分值 × 100%,不适用/0分/空值不进分子也不进分母 |
| | | //综合得分:(很满意×1 + 满意×0.8 + 一般×0.6 + 不满意×0.4 + 很不满意×0.2) / 五档项数之和 × 100% |
| | | questionResultDTO.setAvgScore(calcMydScore(exportList)); |
| | | result.add(questionResultDTO); |
| | | }); |
| | |
| | | private static final String VALUE_TYPE_MULTIPLE = "2"; |
| | | |
| | | /** |
| | | * 满意度五档选项(顺序与权重顺序一致) |
| | | */ |
| | | private static final String[] MYD_SCORE_OPTIONS = {"很满意", "满意", "一般", "不满意", "很不满意"}; |
| | | |
| | | /** |
| | | * 满意度五档权重:很满意 1、满意 0.8、一般 0.6、不满意 0.4、很不满意 0.2 |
| | | */ |
| | | private static final BigDecimal[] MYD_SCORE_WEIGHTS = { |
| | | new BigDecimal("1"), new BigDecimal("0.8"), new BigDecimal("0.6"), new BigDecimal("0.4"), new BigDecimal("0.2")}; |
| | | |
| | | /** |
| | | * 计算一道题的满意度综合得分(百分制,保留两位小数) |
| | | * <p> |
| | | * 综合满意度 = Σ(选项分值 × 项数) / Σ项数 / 本题最高分值 × 100% |
| | | * 等价于 Σ(很满意×1 + 满意×0.8 + 一般×0.6 + 不满意×0.4 + 很不满意×0.2) / Σ(五档项数) × 100%。 |
| | | * 用本题最高分值归一化,兼容明细分值存 0-1 权重(1、0.8…)和存 5 分制(5、4…)两种口径。 |
| | | * 综合满意度 = (很满意项数×1 + 满意项数×0.8 + 一般项数×0.6 + 不满意项数×0.4 + 很不满意项数×0.2) |
| | | * / (很满意项数 + 满意项数 + 一般项数 + 不满意项数 + 很不满意项数) × 100% |
| | | * <p> |
| | | * 权重按选项文案判定(见 {@link #mydScoreOptionIndex(String)}),不再依赖明细的 score 字段: |
| | | * 历史数据里 score 有 0、「0000000001」这类脏值,按 score 算会把得分固定成 0 或 100。 |
| | | * 「未经历」「不适用」等不在五档里的选项既不进分子也不进分母。 |
| | | * |
| | | * @param exportList 同一道题目的选项统计明细 |
| | | * @return 百分制综合得分,无有效项数时返回 "0.00" |
| | |
| | | private String calcMydScore(List<ServiceSubtaskDetailRatioExport> exportList) { |
| | | if (CollectionUtils.isEmpty(exportList)) return "0.00"; |
| | | |
| | | BigDecimal scoreSum = BigDecimal.ZERO; //分子:Σ(选项分值 × 项数) |
| | | BigDecimal countSum = BigDecimal.ZERO; //分母:Σ项数 |
| | | BigDecimal maxScore = null; //本题最高分值,用于归一化 |
| | | BigDecimal scoreSum = BigDecimal.ZERO; //分子:Σ(档位权重 × 项数) |
| | | BigDecimal countSum = BigDecimal.ZERO; //分母:五档项数之和 |
| | | |
| | | for (ServiceSubtaskDetailRatioExport detail : exportList) { |
| | | //多选题一行是多个选项的分值之和,分母只算 1 项,会把得分算高,不计入综合得分 |
| | |
| | | //「不适用」不在公式的五档里,不计入综合得分 |
| | | if (isNotApplicable(detail)) continue; |
| | | |
| | | BigDecimal score = parseDecimal(detail.getScore()); |
| | | int optionIndex = mydScoreOptionIndex(detail.getOptionresult()); |
| | | BigDecimal count = parseDecimal(detail.getCount()); |
| | | if (score == null || count == null) continue; |
| | | //0 分(未答/空答)、空数量:既不进分子也不进分母 |
| | | if (score.signum() <= 0 || count.signum() <= 0) continue; |
| | | if (optionIndex < 0 || count == null || count.signum() <= 0) continue; |
| | | |
| | | if (maxScore == null || score.compareTo(maxScore) > 0) maxScore = score; |
| | | scoreSum = scoreSum.add(score.multiply(count)); |
| | | scoreSum = scoreSum.add(MYD_SCORE_WEIGHTS[optionIndex].multiply(count)); |
| | | countSum = countSum.add(count); |
| | | } |
| | | |
| | | if (maxScore == null || countSum.signum() <= 0) return "0.00"; |
| | | if (countSum.signum() <= 0) return "0.00"; |
| | | |
| | | return scoreSum.divide(countSum, 6, RoundingMode.HALF_UP) |
| | | .divide(maxScore, 6, RoundingMode.HALF_UP) |
| | | .multiply(BigDecimal.valueOf(100)) |
| | | .setScale(2, RoundingMode.HALF_UP) |
| | | .toPlainString(); |
| | | } |
| | | |
| | | /** |
| | | * 选项文案 → 满意度五档下标(很满意 0、满意 1、一般 2、不满意 3、很不满意 4),不在五档里返回 -1 |
| | | */ |
| | | private int mydScoreOptionIndex(String optionresult) { |
| | | if (StringUtils.isEmpty(optionresult)) return -1; |
| | | String text = optionresult.trim(); |
| | | for (int i = 0; i < MYD_SCORE_OPTIONS.length; i++) { |
| | | if (MYD_SCORE_OPTIONS[i].equals(text)) return i; |
| | | } |
| | | //兼容「A 很满意」「B.满意」这类带选项序号前缀的历史数据 |
| | | if (text.length() > 2 && (text.charAt(1) == ' ' || text.charAt(1) == '.' || text.charAt(1) == '、')) { |
| | | String stripped = text.substring(2).trim(); |
| | | for (int i = 0; i < MYD_SCORE_OPTIONS.length; i++) { |
| | | if (MYD_SCORE_OPTIONS[i].equals(stripped)) return i; |
| | | } |
| | | } |
| | | return -1; |
| | | } |
| | | |
| | | /** |
| | | * 是否「不适用」选项 |
| | | */ |
| | | private boolean isNotApplicable(ServiceSubtaskDetailRatioExport detail) { |