package com.ruoyi.satisfaction.service.strategy;
|
|
import com.ruoyi.common.utils.StringUtils;
|
import com.ruoyi.satisfaction.domain.ServiceSubtaskDetailRatioExport;
|
import org.apache.commons.collections4.CollectionUtils;
|
import org.springframework.stereotype.Component;
|
|
import java.math.BigDecimal;
|
import java.math.RoundingMode;
|
import java.util.List;
|
|
/**
|
* 省立同德口径的综合满意度得分策略(五档加权,百分制)。
|
* <p>
|
* 算法从原 {@code ServiceSubtaskDetailController#calcMydScore} 及其 4 个私有助手
|
* <b>原样搬入</b>(一行未改,仅把返回值由 {@code String} 改为 {@link BigDecimal} 以便统一策略签名;
|
* "无有效项数"这类情况由返回 {@code null} 表达,调用方仍回退为 {@code "0.00"},对外结果不变)。
|
* <p>
|
* 口径:
|
* <pre>
|
* 综合满意度 = (很满意数×1 + 满意数×0.8 + 一般数×0.6 + 不满意数×0.4 + 很不满意数×0.2)
|
* / (五档项数之和) × 100%,保留 2 位
|
* </pre>
|
* <b>为什么按选项文案判档、而不是用明细的 {@code score} 字段</b>:
|
* 历史数据里 {@code score} 有 {@code 0}、{@code 0000000001} 这类脏值,
|
* 按 {@code score} 算会把得分固定成 0 或 100。
|
* <p>
|
* 跳过的行:多选行({@code valueType=2})、以及「不适用 / 未答 / 拒答」(不在五档里,
|
* 既不进分子也不进分母)。
|
*/
|
@Component
|
public class SltdSatisfactionScoreStrategy implements SatisfactionScoreStrategy {
|
|
/**
|
* 多选题的问题类型
|
*/
|
private static final String VALUE_TYPE_MULTIPLE = "2";
|
|
/**
|
* 满意度五档选项(顺序与权重顺序一致)
|
*/
|
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")};
|
|
@Override
|
public String site() {
|
return SatisfactionSites.SLTD;
|
}
|
|
@Override
|
public String siteName() {
|
return "省立同德口径";
|
}
|
|
/**
|
* 计算一道题的满意度综合得分(百分制,保留两位小数)。
|
*
|
* @param exportList 同一道题目的选项统计明细
|
* @return 百分制综合得分;无有效项数时返回 {@code null}(调用方呈现为 "0.00")
|
*/
|
@Override
|
public BigDecimal comprehensiveScore(List<ServiceSubtaskDetailRatioExport> exportList) {
|
if (CollectionUtils.isEmpty(exportList)) return null;
|
|
BigDecimal scoreSum = BigDecimal.ZERO; //分子:Σ(档位权重 × 项数)
|
BigDecimal countSum = BigDecimal.ZERO; //分母:五档项数之和
|
|
for (ServiceSubtaskDetailRatioExport detail : exportList) {
|
//多选题一行是多个选项的分值之和,分母只算 1 项,会把得分算高,不计入综合得分
|
if (VALUE_TYPE_MULTIPLE.equals(detail.getValueType())) continue;
|
//「不适用」不在公式的五档里,不计入综合得分
|
if (isNotApplicable(detail)) continue;
|
|
int optionIndex = mydScoreOptionIndex(detail.getOptionresult());
|
BigDecimal count = parseDecimal(detail.getCount());
|
if (optionIndex < 0 || count == null || count.signum() <= 0) continue;
|
|
scoreSum = scoreSum.add(MYD_SCORE_WEIGHTS[optionIndex].multiply(count));
|
countSum = countSum.add(count);
|
}
|
|
if (countSum.signum() <= 0) return null;
|
|
return scoreSum.divide(countSum, 6, RoundingMode.HALF_UP)
|
.multiply(BigDecimal.valueOf(100))
|
.setScale(2, RoundingMode.HALF_UP);
|
}
|
|
/**
|
* 选项文案 → 满意度五档下标(很满意 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) {
|
return containsNotApplicable(detail.getOptionresult()) || containsNotApplicable(detail.getTargetvalue());
|
}
|
|
private boolean containsNotApplicable(String optionText) {
|
if (StringUtils.isEmpty(optionText)) return false;
|
String text = optionText.trim();
|
return text.contains("不适用") || text.contains("未答") || text.contains("拒答");
|
}
|
|
/**
|
* 字符串转 BigDecimal,空值/非法值返回 null
|
*/
|
private BigDecimal parseDecimal(String value) {
|
if (StringUtils.isEmpty(value)) return null;
|
try {
|
return new BigDecimal(value.trim());
|
} catch (NumberFormatException e) {
|
return null;
|
}
|
}
|
}
|