|
|
|
|
@ -13,6 +13,7 @@ import java.math.BigDecimal;
|
|
|
|
|
import java.math.MathContext;
|
|
|
|
|
import java.math.RoundingMode;
|
|
|
|
|
import java.util.ArrayList;
|
|
|
|
|
import java.util.HashMap;
|
|
|
|
|
import java.util.List;
|
|
|
|
|
import java.util.Map;
|
|
|
|
|
import java.util.function.Function;
|
|
|
|
|
@ -63,24 +64,30 @@ public class ProdMixTraceReportServiceImpl implements IProdMixTraceReportService
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public TableDataInfo<MixTraceListVo> queryTraceList(Map<String, Object> params, PageQuery pageQuery) {
|
|
|
|
|
Page<MixTraceListVo> page = mixTraceReportMapper.selectTraceList(params, pageQuery.build());
|
|
|
|
|
// 统一把 null 查询参数转为空 Map,避免 Mapper XML 中 OGNL 访问空指针。
|
|
|
|
|
Map<String, Object> queryParams = safeParams(params);
|
|
|
|
|
// 由 PageQuery 生成分页对象,确保前后端分页语义一致(pageNum/pageSize)。
|
|
|
|
|
Page<MixTraceListVo> page = mixTraceReportMapper.selectTraceList(queryParams, pageQuery.build());
|
|
|
|
|
// 统一封装成 TableDataInfo,前端可直接读取 rows/total。
|
|
|
|
|
return TableDataInfo.build(page);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public List<MixTraceListVo> queryTraceList(Map<String, Object> params) {
|
|
|
|
|
return mixTraceReportMapper.selectTraceList(params);
|
|
|
|
|
return mixTraceReportMapper.selectTraceList(safeParams(params));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ==================== 追溯详情(图9) ====================
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public MixTraceDetailVo queryTraceDetail(Long recipeId) {
|
|
|
|
|
// 先查主信息;不存在时直接返回 null,前端按“无详情”处理。
|
|
|
|
|
MixTraceListVo recipeInfo = mixTraceReportMapper.selectTraceRecipeInfo(recipeId);
|
|
|
|
|
if (recipeInfo == null) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
MixTraceDetailVo detail = new MixTraceDetailVo();
|
|
|
|
|
// 主表信息(配方基础信息)。
|
|
|
|
|
detail.setRecipeInfo(recipeInfo);
|
|
|
|
|
// 称量明细按 weight_seq 排序
|
|
|
|
|
detail.setWeightList(mixTraceReportMapper.selectWeightListByRecipeId(recipeId));
|
|
|
|
|
@ -93,7 +100,9 @@ public class ProdMixTraceReportServiceImpl implements IProdMixTraceReportService
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public TableDataInfo<MixTraceSpcSampleVo> querySpcSamples(Map<String, Object> params, PageQuery pageQuery) {
|
|
|
|
|
Page<MixTraceSpcSampleVo> page = mixTraceReportMapper.selectSpcSamples(params, pageQuery.build());
|
|
|
|
|
// SPC 样本分页查询:入参做空安全处理,避免条件拼接异常。
|
|
|
|
|
Map<String, Object> queryParams = safeParams(params);
|
|
|
|
|
Page<MixTraceSpcSampleVo> page = mixTraceReportMapper.selectSpcSamples(queryParams, pageQuery.build());
|
|
|
|
|
return TableDataInfo.build(page);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@ -101,29 +110,34 @@ public class ProdMixTraceReportServiceImpl implements IProdMixTraceReportService
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public MixTraceSpcResultVo calculateSpcCapability(Map<String, Object> params) {
|
|
|
|
|
// 1) 参数归一化:保证后续读取参数时不会出现 null Map。
|
|
|
|
|
Map<String, Object> queryParams = safeParams(params);
|
|
|
|
|
// 获取分析参数名称,默认分析混炼温度
|
|
|
|
|
String paramName = getParamName(params);
|
|
|
|
|
String paramName = getParamName(queryParams);
|
|
|
|
|
|
|
|
|
|
// 查询全量样本
|
|
|
|
|
List<MixTraceSpcSampleVo> samples = mixTraceReportMapper.selectSpcSamples(params);
|
|
|
|
|
// 2) 查询参与统计的全量样本(能力分析必须用全样本,不用分页样本)。
|
|
|
|
|
List<MixTraceSpcSampleVo> samples = mixTraceReportMapper.selectSpcSamples(queryParams);
|
|
|
|
|
if (samples == null || samples.isEmpty()) {
|
|
|
|
|
// 无样本直接返回空结构,前端用 sampleCount=0 判断无图表数据。
|
|
|
|
|
return buildEmptyResult(paramName);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 提取指定参数的实测值和设定值
|
|
|
|
|
// 3) 根据参数名选择“实测值/设定值”的提取器,避免写 if-else 大分支。
|
|
|
|
|
Function<MixTraceSpcSampleVo, BigDecimal> actualExtractor = getActualExtractor(paramName);
|
|
|
|
|
Function<MixTraceSpcSampleVo, BigDecimal> setExtractor = getSetExtractor(paramName);
|
|
|
|
|
|
|
|
|
|
// 4) 仅保留非空实测值参与统计,屏蔽脏数据对均值/方差计算的影响。
|
|
|
|
|
List<BigDecimal> values = samples.stream()
|
|
|
|
|
.map(actualExtractor)
|
|
|
|
|
.filter(v -> v != null)
|
|
|
|
|
.collect(Collectors.toList());
|
|
|
|
|
|
|
|
|
|
if (values.size() < 2) {
|
|
|
|
|
// 标准差至少需要 2 个样本点,少于 2 个无法做能力计算。
|
|
|
|
|
return buildEmptyResult(paramName);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 基本统计
|
|
|
|
|
// 5) 计算基础统计量(均值、标准差、极值)。
|
|
|
|
|
MixTraceSpcResultVo result = new MixTraceSpcResultVo();
|
|
|
|
|
result.setParamName(paramName);
|
|
|
|
|
result.setParamLabel(getParamLabel(paramName));
|
|
|
|
|
@ -139,7 +153,9 @@ public class ProdMixTraceReportServiceImpl implements IProdMixTraceReportService
|
|
|
|
|
result.setMinValue(min);
|
|
|
|
|
result.setMaxValue(max);
|
|
|
|
|
|
|
|
|
|
// 规格限:从设定值中取平均作为Target,USL/LSL可按 Target ± 容差 或取最大/最小设定值
|
|
|
|
|
// 6) 计算规格限:
|
|
|
|
|
// - 优先使用设定值的最小/最大作为 LSL/USL(最贴近工艺设定)。
|
|
|
|
|
// - 若设定值没有波动(全相同),退化为 target ± 10% 的经验容差。
|
|
|
|
|
List<BigDecimal> setValues = samples.stream()
|
|
|
|
|
.map(setExtractor)
|
|
|
|
|
.filter(v -> v != null)
|
|
|
|
|
@ -161,13 +177,14 @@ public class ProdMixTraceReportServiceImpl implements IProdMixTraceReportService
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 能力指数
|
|
|
|
|
// 7) 计算能力指数(前提:σ>0 且已得到上下规格限)。
|
|
|
|
|
if (sigma.compareTo(BigDecimal.ZERO) > 0 && result.getUsl() != null && result.getLsl() != null) {
|
|
|
|
|
BigDecimal sixSigma = sigma.multiply(new BigDecimal("6"));
|
|
|
|
|
BigDecimal threeSigma = sigma.multiply(new BigDecimal("3"));
|
|
|
|
|
BigDecimal usl = result.getUsl();
|
|
|
|
|
BigDecimal lsl = result.getLsl();
|
|
|
|
|
|
|
|
|
|
// Cp 反映潜在能力;Cpu/Cpl 反映均值偏移;Cpk 取二者较小值。
|
|
|
|
|
result.setCp(usl.subtract(lsl).divide(sixSigma, SCALE, RM));
|
|
|
|
|
result.setCpu(usl.subtract(mean).divide(threeSigma, SCALE, RM));
|
|
|
|
|
result.setCpl(mean.subtract(lsl).divide(threeSigma, SCALE, RM));
|
|
|
|
|
@ -178,10 +195,11 @@ public class ProdMixTraceReportServiceImpl implements IProdMixTraceReportService
|
|
|
|
|
result.setPpk(result.getCpk());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 直方图数据
|
|
|
|
|
// 8) 构造直方图分箱数据,供前端能力图直接渲染。
|
|
|
|
|
buildHistogram(result, values);
|
|
|
|
|
|
|
|
|
|
// 运行图数据
|
|
|
|
|
// 9) 构造运行图序列数据(值 + 标签)。
|
|
|
|
|
// 这里标签优先用配方编码,若缺失则回退为样本序号,保证前端 x 轴稳定。
|
|
|
|
|
result.setSampleValues(values);
|
|
|
|
|
List<String> labels = new ArrayList<>();
|
|
|
|
|
for (int i = 0; i < samples.size(); i++) {
|
|
|
|
|
@ -200,23 +218,33 @@ public class ProdMixTraceReportServiceImpl implements IProdMixTraceReportService
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public MixTraceSpcResultVo calculateSpcRunChart(Map<String, Object> params) {
|
|
|
|
|
// 运行图与能力分析共用数据,直接复用
|
|
|
|
|
return calculateSpcCapability(params);
|
|
|
|
|
// 运行图复用样本统计结果,但仅返回运行图所需数据,避免语义混用。
|
|
|
|
|
MixTraceSpcResultVo result = calculateSpcCapability(safeParams(params));
|
|
|
|
|
if (result != null) {
|
|
|
|
|
// 显式清空直方图字段,避免前端误把 runChart 结果当 capability 结果使用。
|
|
|
|
|
result.setHistogramBins(null);
|
|
|
|
|
result.setHistogramCounts(null);
|
|
|
|
|
}
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ==================== SPC Xbar-R图(图10) ====================
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public MixTraceSpcResultVo calculateSpcXbarR(Map<String, Object> params) {
|
|
|
|
|
String paramName = getParamName(params);
|
|
|
|
|
// 1) 参数归一化并确定分析参数。
|
|
|
|
|
Map<String, Object> queryParams = safeParams(params);
|
|
|
|
|
String paramName = getParamName(queryParams);
|
|
|
|
|
|
|
|
|
|
List<MixTraceSpcSampleVo> samples = mixTraceReportMapper.selectSpcSamples(params);
|
|
|
|
|
// 2) 取全量样本,Xbar-R 需要连续样本进行分组统计。
|
|
|
|
|
List<MixTraceSpcSampleVo> samples = mixTraceReportMapper.selectSpcSamples(queryParams);
|
|
|
|
|
if (samples == null || samples.isEmpty()) {
|
|
|
|
|
return buildEmptyResult(paramName);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Function<MixTraceSpcSampleVo, BigDecimal> actualExtractor = getActualExtractor(paramName);
|
|
|
|
|
|
|
|
|
|
// 仅保留有效实测值,保证子组计算准确。
|
|
|
|
|
List<BigDecimal> values = samples.stream()
|
|
|
|
|
.map(actualExtractor)
|
|
|
|
|
.filter(v -> v != null)
|
|
|
|
|
@ -224,9 +252,9 @@ public class ProdMixTraceReportServiceImpl implements IProdMixTraceReportService
|
|
|
|
|
|
|
|
|
|
// 子组大小,默认5,可通过参数配置
|
|
|
|
|
int subgroupSize = 5;
|
|
|
|
|
if (params.containsKey("subgroupSize")) {
|
|
|
|
|
if (queryParams.containsKey("subgroupSize")) {
|
|
|
|
|
try {
|
|
|
|
|
subgroupSize = Integer.parseInt(String.valueOf(params.get("subgroupSize")));
|
|
|
|
|
subgroupSize = Integer.parseInt(String.valueOf(queryParams.get("subgroupSize")));
|
|
|
|
|
} catch (NumberFormatException ignored) {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
@ -234,13 +262,13 @@ public class ProdMixTraceReportServiceImpl implements IProdMixTraceReportService
|
|
|
|
|
subgroupSize = Math.max(2, Math.min(10, subgroupSize));
|
|
|
|
|
|
|
|
|
|
if (values.size() < subgroupSize * 2) {
|
|
|
|
|
// 样本不足,至少需要2个子组
|
|
|
|
|
// 业务约束:至少需要 2 个完整子组,否则控制图没有统计意义。
|
|
|
|
|
MixTraceSpcResultVo result = buildEmptyResult(paramName);
|
|
|
|
|
result.setSampleCount(values.size());
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 按子组分组
|
|
|
|
|
// 3) 顺序分组:按样本顺序切分为固定大小子组(忽略最后不完整子组)。
|
|
|
|
|
int groupCount = values.size() / subgroupSize;
|
|
|
|
|
List<BigDecimal> xbarValues = new ArrayList<>();
|
|
|
|
|
List<BigDecimal> rValues = new ArrayList<>();
|
|
|
|
|
@ -254,6 +282,7 @@ public class ProdMixTraceReportServiceImpl implements IProdMixTraceReportService
|
|
|
|
|
BigDecimal groupMin = group.stream().min(BigDecimal::compareTo).orElse(BigDecimal.ZERO);
|
|
|
|
|
BigDecimal groupRange = groupMax.subtract(groupMin);
|
|
|
|
|
|
|
|
|
|
// 每个子组提取两个关键统计量:均值(Xbar) 和 极差(R)。
|
|
|
|
|
xbarValues.add(groupMean);
|
|
|
|
|
rValues.add(groupRange);
|
|
|
|
|
subgroupLabels.add("G" + (i + 1));
|
|
|
|
|
@ -263,7 +292,7 @@ public class ProdMixTraceReportServiceImpl implements IProdMixTraceReportService
|
|
|
|
|
BigDecimal xbarbar = calcMean(xbarValues);
|
|
|
|
|
BigDecimal rbar = calcMean(rValues);
|
|
|
|
|
|
|
|
|
|
// 查找常量 A2, D3, D4
|
|
|
|
|
// 4) 按子组大小查控制图常量 A2/D3/D4(行业标准常量表)。
|
|
|
|
|
int constIndex = subgroupSize - 2; // 数组从 n=2 开始
|
|
|
|
|
double a2 = XBAR_R_CONSTANTS[constIndex][0];
|
|
|
|
|
double d3 = XBAR_R_CONSTANTS[constIndex][1];
|
|
|
|
|
@ -288,12 +317,18 @@ public class ProdMixTraceReportServiceImpl implements IProdMixTraceReportService
|
|
|
|
|
result.setXbarbar(xbarbar);
|
|
|
|
|
result.setRbar(rbar);
|
|
|
|
|
|
|
|
|
|
// Xbar 控制限
|
|
|
|
|
// 5) Xbar 图控制限:
|
|
|
|
|
// UCLx = Xbarbar + A2 * Rbar
|
|
|
|
|
// CLx = Xbarbar
|
|
|
|
|
// LCLx = Xbarbar - A2 * Rbar
|
|
|
|
|
result.setUclX(xbarbar.add(A2.multiply(rbar)).setScale(SCALE, RM));
|
|
|
|
|
result.setClX(xbarbar.setScale(SCALE, RM));
|
|
|
|
|
result.setLclX(xbarbar.subtract(A2.multiply(rbar)).setScale(SCALE, RM));
|
|
|
|
|
|
|
|
|
|
// R 控制限
|
|
|
|
|
// 6) R 图控制限:
|
|
|
|
|
// UCLr = D4 * Rbar
|
|
|
|
|
// CLr = Rbar
|
|
|
|
|
// LCLr = D3 * Rbar
|
|
|
|
|
result.setUclR(D4.multiply(rbar).setScale(SCALE, RM));
|
|
|
|
|
result.setClR(rbar.setScale(SCALE, RM));
|
|
|
|
|
result.setLclR(D3.multiply(rbar).setScale(SCALE, RM));
|
|
|
|
|
@ -307,30 +342,33 @@ public class ProdMixTraceReportServiceImpl implements IProdMixTraceReportService
|
|
|
|
|
* 获取分析参数名称,默认 mixingTemp
|
|
|
|
|
*/
|
|
|
|
|
private String getParamName(Map<String, Object> params) {
|
|
|
|
|
if (params == null || params.isEmpty()) {
|
|
|
|
|
// 未传分析参数时默认按混炼温度分析,保持接口可直接调用。
|
|
|
|
|
return "mixingTemp";
|
|
|
|
|
}
|
|
|
|
|
Object paramNameObj = params.get("paramName");
|
|
|
|
|
// 兼容前端传空字符串场景,统一回退默认参数。
|
|
|
|
|
return (paramNameObj != null && !String.valueOf(paramNameObj).isEmpty())
|
|
|
|
|
? String.valueOf(paramNameObj) : "mixingTemp";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private Map<String, Object> safeParams(Map<String, Object> params) {
|
|
|
|
|
// Mapper 层统一假设 map 非空,服务层在入口处兜底。
|
|
|
|
|
return params == null ? new HashMap<>() : params;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 参数名称 -> 中文标签映射
|
|
|
|
|
*/
|
|
|
|
|
private String getParamLabel(String paramName) {
|
|
|
|
|
switch (paramName) {
|
|
|
|
|
case "mixingTemp": return "混炼温度";
|
|
|
|
|
case "mixingTime": return "混炼时间";
|
|
|
|
|
case "mixingEnergy": return "混炼能量";
|
|
|
|
|
case "mixingPower": return "混炼功率";
|
|
|
|
|
case "mixingPress": return "混炼压力";
|
|
|
|
|
case "mixingSpeed": return "混炼转速";
|
|
|
|
|
default: return paramName;
|
|
|
|
|
}
|
|
|
|
|
return paramName;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 根据参数名获取实测值提取器
|
|
|
|
|
*/
|
|
|
|
|
private Function<MixTraceSpcSampleVo, BigDecimal> getActualExtractor(String paramName) {
|
|
|
|
|
// 参数名与“实测字段”一一映射,时间/转速等整型字段转 BigDecimal 统一参与计算。
|
|
|
|
|
switch (paramName) {
|
|
|
|
|
case "mixingTemp": return MixTraceSpcSampleVo::getMixingTemp;
|
|
|
|
|
case "mixingTime": return s -> s.getMixingTime() != null ? BigDecimal.valueOf(s.getMixingTime()) : null;
|
|
|
|
|
@ -346,6 +384,7 @@ public class ProdMixTraceReportServiceImpl implements IProdMixTraceReportService
|
|
|
|
|
* 根据参数名获取设定值提取器
|
|
|
|
|
*/
|
|
|
|
|
private Function<MixTraceSpcSampleVo, BigDecimal> getSetExtractor(String paramName) {
|
|
|
|
|
// 参数名与“设定字段”一一映射,口径与实测字段保持一致。
|
|
|
|
|
switch (paramName) {
|
|
|
|
|
case "mixingTemp": return s -> s.getSetTemp() != null ? BigDecimal.valueOf(s.getSetTemp()) : null;
|
|
|
|
|
case "mixingTime": return s -> s.getSetTime() != null ? BigDecimal.valueOf(s.getSetTime()) : null;
|
|
|
|
|
@ -364,6 +403,7 @@ public class ProdMixTraceReportServiceImpl implements IProdMixTraceReportService
|
|
|
|
|
if (values == null || values.isEmpty()) {
|
|
|
|
|
return BigDecimal.ZERO;
|
|
|
|
|
}
|
|
|
|
|
// BigDecimal 全程计算,避免 double 误差影响 SPC 指标。
|
|
|
|
|
BigDecimal sum = values.stream().reduce(BigDecimal.ZERO, BigDecimal::add);
|
|
|
|
|
return sum.divide(BigDecimal.valueOf(values.size()), SCALE, RM);
|
|
|
|
|
}
|
|
|
|
|
@ -375,6 +415,7 @@ public class ProdMixTraceReportServiceImpl implements IProdMixTraceReportService
|
|
|
|
|
if (values == null || values.size() < 2) {
|
|
|
|
|
return BigDecimal.ZERO;
|
|
|
|
|
}
|
|
|
|
|
// 先算离差平方和,再按 n-1 求样本方差。
|
|
|
|
|
BigDecimal sumSq = values.stream()
|
|
|
|
|
.map(v -> v.subtract(mean).pow(2))
|
|
|
|
|
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
|
|
|
|
@ -387,6 +428,7 @@ public class ProdMixTraceReportServiceImpl implements IProdMixTraceReportService
|
|
|
|
|
* 构建空结果
|
|
|
|
|
*/
|
|
|
|
|
private MixTraceSpcResultVo buildEmptyResult(String paramName) {
|
|
|
|
|
// 空结果也回传参数信息,前端可继续显示“当前分析参数”。
|
|
|
|
|
MixTraceSpcResultVo result = new MixTraceSpcResultVo();
|
|
|
|
|
result.setParamName(paramName);
|
|
|
|
|
result.setParamLabel(getParamLabel(paramName));
|
|
|
|
|
@ -399,8 +441,10 @@ public class ProdMixTraceReportServiceImpl implements IProdMixTraceReportService
|
|
|
|
|
*/
|
|
|
|
|
private void buildHistogram(MixTraceSpcResultVo result, List<BigDecimal> values) {
|
|
|
|
|
if (values == null || values.size() < 2) {
|
|
|
|
|
// 样本过少时不生成分箱,前端直方图区域自然不显示。
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
// 采用固定 10 箱的等宽分箱策略,优先保证可解释性与稳定渲染。
|
|
|
|
|
int binCount = 10;
|
|
|
|
|
BigDecimal min = result.getMinValue();
|
|
|
|
|
BigDecimal max = result.getMaxValue();
|
|
|
|
|
@ -427,9 +471,11 @@ public class ProdMixTraceReportServiceImpl implements IProdMixTraceReportService
|
|
|
|
|
int finalI = i;
|
|
|
|
|
long count = values.stream()
|
|
|
|
|
.filter(v -> {
|
|
|
|
|
// 最后一个分箱右闭区间,确保最大值不会因边界比较丢失。
|
|
|
|
|
if (finalI == binCount - 1) {
|
|
|
|
|
return v.compareTo(lower) >= 0 && v.compareTo(upper) <= 0;
|
|
|
|
|
}
|
|
|
|
|
// 其余分箱采用左闭右开,避免边界值重复计数。
|
|
|
|
|
return v.compareTo(lower) >= 0 && v.compareTo(upper) < 0;
|
|
|
|
|
})
|
|
|
|
|
.count();
|
|
|
|
|
|