package cn.iocoder.yudao.module.qcreport.engine.rule;
import cn.iocoder.yudao.module.qcreport.engine.JsValues;
import cn.iocoder.yudao.module.qcreport.engine.Paths;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
/**
* 规则求值。
*
* 只认 {@link RuleNode} 与白名单函数,规则文本里的任何内容都不会变成可执行代码。
* 与前端 {@code engine/rule-engine.ts} 的求值段一一对应,两边结果必须一致。
*/
public final class RuleEvaluator {
/** 判定结果类取值统一成 PASS / FAIL:兼容中文、布尔与数字 1/0 */
private static final Set PASS_TOKENS = Set.of("PASS", "TRUE", "合格", "OK", "1", "是");
private static final Set FAIL_TOKENS = Set.of("FAIL", "FALSE", "不合格", "NG", "0", "否");
private RuleEvaluator() {
}
/* ------------------------------ 基础判定 ------------------------------ */
/**
* JS {@code String(value)} 的等价实现。
*
* 规则里的文本比较与拼接都要走这里,否则两端会算出不同结果。
*/
public static String asText(Object value) {
return JsValues.asText(value);
}
/** 空值:null 或空串(空数组不算空) */
public static boolean isBlank(Object value) {
return value == null || "".equals(value);
}
/** 真值判定,与 JS 版 truthy 同语义 */
public static boolean truthy(Object value) {
if (value instanceof List> list) {
return !list.isEmpty();
}
if (isBlank(value)) {
return false;
}
if (value instanceof Boolean bool) {
return bool;
}
double numeric = Paths.toNumber(value);
if (Double.isFinite(numeric)) {
return numeric != 0;
}
return true;
}
/** 两侧都能当数字时按数值比较,否则按字符串比较 */
public static int compare(Object left, Object right) {
double leftNumber = Paths.toNumber(left);
double rightNumber = Paths.toNumber(right);
if (Double.isFinite(leftNumber) && Double.isFinite(rightNumber)) {
return Double.compare(leftNumber, rightNumber);
}
return asText(left).compareTo(asText(right));
}
/** 判定结果类取值统一成 PASS / FAIL,识别不出返回空串 */
public static String normalizeResultToken(Object value) {
if (value instanceof Boolean bool) {
return bool ? "PASS" : "FAIL";
}
String text = asText(value).trim().toUpperCase(Locale.ROOT);
if (PASS_TOKENS.contains(text)) {
return "PASS";
}
return FAIL_TOKENS.contains(text) ? "FAIL" : "";
}
/* ------------------------------ 白名单函数 ------------------------------ */
/** 白名单函数表,规则文本只能调用这里面的函数 */
private static final Map FUNCTIONS = buildFunctions();
private static Map buildFunctions() {
Map functions = new LinkedHashMap<>();
functions.put("ABS", args -> Math.abs(requireNumber("ABS", arg(args, 0))));
functions.put("AVG", args -> mean(requireNumbers("AVG", arg(args, 0))));
functions.put("COUNT", args -> arg(args, 0) instanceof List> list
? list.size()
: Paths.toNumberArray(arg(args, 0)).size());
// 过程能力指数 CP=(USL-LSL)/(6σ)
functions.put("CP", args -> {
List values = requireNumbers("CP", arg(args, 0));
double deviation = standardDeviation(values);
if (!Double.isFinite(deviation) || deviation == 0) {
return Double.NaN;
}
return (requireNumber("CP", arg(args, 1)) - requireNumber("CP", arg(args, 2))) / (6 * deviation);
});
// 过程能力指数 CPK=min(USL-μ, μ-LSL)/(3σ)
functions.put("CPK", args -> {
List values = requireNumbers("CPK", arg(args, 0));
double deviation = standardDeviation(values);
if (!Double.isFinite(deviation) || deviation == 0) {
return Double.NaN;
}
double upper = requireNumber("CPK", arg(args, 1));
double lower = requireNumber("CPK", arg(args, 2));
double average = mean(values);
return Math.min(upper - average, average - lower) / (3 * deviation);
});
// 不合格率(百分比),口径与 PASS_RATE 一致
functions.put("FAIL_RATE", args -> rate(arg(args, 0), "FAIL"));
functions.put("MAX", args -> extreme(requireNumbers("MAX", arg(args, 0)), true));
functions.put("MIN", args -> extreme(requireNumbers("MIN", arg(args, 0)), false));
// 合格率(百分比,保留两位)
functions.put("PASS_RATE", args -> rate(arg(args, 0), "PASS"));
functions.put("ROUND", args -> {
double digits = args.size() > 1 ? requireNumber("ROUND", arg(args, 1)) : 0;
double factor = Math.pow(10, digits);
return Math.round(requireNumber("ROUND", arg(args, 0)) * factor) / factor;
});
functions.put("STDDEV", args -> standardDeviation(requireNumbers("STDDEV", arg(args, 0))));
functions.put("SUM", args -> {
double sum = 0;
for (Double value : requireNumbers("SUM", arg(args, 0))) {
sum += value;
}
return sum;
});
// 不能换成 Map.copyOf:它不保证遍历顺序,报错里列出的可用函数会随机排
return Collections.unmodifiableMap(functions);
}
/**
* 取第 index 个实参,缺参返回 null。
*
* 少了这个兜底,{@code SUM()} 这种写错的规则会直接抛 IndexOutOfBoundsException 穿透到接口层,
* 用户看到的是「系统异常」而不是「这条规则写错了」。
*/
private static Object arg(List