package com.ruoyi.report.service.impl;
|
|
import cn.hutool.core.collection.CollectionUtil;
|
import cn.hutool.core.date.DateUtil;
|
import com.alibaba.excel.EasyExcel;
|
import com.ruoyi.framework.exception.ErrorException;
|
import com.ruoyi.report.dto.SpcChartDto;
|
import com.ruoyi.report.mapper.SpcChartMapper;
|
import com.ruoyi.report.service.SpcChartService;
|
import com.ruoyi.report.vo.SpcResultVo;
|
import lombok.AllArgsConstructor;
|
import org.springframework.stereotype.Service;
|
|
import javax.servlet.http.HttpServletResponse;
|
import java.io.IOException;
|
import java.math.BigDecimal;
|
import java.math.MathContext;
|
import java.math.RoundingMode;
|
import java.util.*;
|
import java.util.stream.Collectors;
|
|
/**
|
* SPC控制图服务实现
|
*/
|
@Service
|
@AllArgsConstructor
|
public class SpcChartServiceImpl implements SpcChartService {
|
|
private SpcChartMapper spcChartMapper;
|
|
@Override
|
public SpcResultVo analyze(SpcChartDto dto) {
|
// 查询数据
|
List<Map<String, Object>> itemData = spcChartMapper.getItemData(dto);
|
|
if (CollectionUtil.isEmpty(itemData)) {
|
return null;
|
}
|
|
// 获取数值列表
|
List<BigDecimal> values = itemData.stream()
|
.map(m -> new BigDecimal((String) m.get("lastValue")))
|
.collect(Collectors.toList());
|
|
// 子组大小,默认为5
|
int subgroupSize = dto.getSubgroupSize() != null ? dto.getSubgroupSize() : 5;
|
|
// 分组
|
List<List<BigDecimal>> subgroups = new ArrayList<>();
|
for (int i = 0; i < values.size(); i += subgroupSize) {
|
int end = Math.min(i + subgroupSize, values.size());
|
if (end - i >= 2) { // 至少需要2个数据点
|
subgroups.add(values.subList(i, end));
|
}
|
}
|
|
if (subgroups.isEmpty()) {
|
throw new ErrorException("数据不足以进行SPC分析");
|
}
|
|
// 计算X-bar和R
|
List<BigDecimal> xBarData = new ArrayList<>();
|
List<BigDecimal> rData = new ArrayList<>();
|
List<String> sampleLabels = new ArrayList<>();
|
|
for (int i = 0; i < subgroups.size(); i++) {
|
List<BigDecimal> subgroup = subgroups.get(i);
|
BigDecimal sum = subgroup.stream().reduce(BigDecimal.ZERO, BigDecimal::add);
|
BigDecimal mean = sum.divide(new BigDecimal(subgroup.size()), 10, RoundingMode.HALF_UP);
|
BigDecimal max = subgroup.stream().max(BigDecimal::compareTo).orElse(BigDecimal.ZERO);
|
BigDecimal min = subgroup.stream().min(BigDecimal::compareTo).orElse(BigDecimal.ZERO);
|
BigDecimal range = max.subtract(min);
|
|
xBarData.add(mean.setScale(4, RoundingMode.HALF_UP));
|
rData.add(range.setScale(4, RoundingMode.HALF_UP));
|
sampleLabels.add("组" + (i + 1));
|
}
|
|
// 计算控制限
|
BigDecimal xBarMean = xBarData.stream().reduce(BigDecimal.ZERO, BigDecimal::add)
|
.divide(new BigDecimal(xBarData.size()), 10, RoundingMode.HALF_UP);
|
BigDecimal rMean = rData.stream().reduce(BigDecimal.ZERO, BigDecimal::add)
|
.divide(new BigDecimal(rData.size()), 10, RoundingMode.HALF_UP);
|
|
// A2, D3, D4 常数 (针对子组大小)
|
BigDecimal A2 = getA2(subgroupSize);
|
BigDecimal D3 = getD3(subgroupSize);
|
BigDecimal D4 = getD4(subgroupSize);
|
|
// X-bar 控制限
|
BigDecimal xBarUcl = dto.getUcl() != null ? dto.getUcl() :
|
xBarMean.add(A2.multiply(rMean)).setScale(4, RoundingMode.HALF_UP);
|
BigDecimal xBarLcl = dto.getLcl() != null ? dto.getLcl() :
|
xBarMean.subtract(A2.multiply(rMean)).setScale(4, RoundingMode.HALF_UP);
|
|
// R 控制限
|
BigDecimal rUcl = D4.multiply(rMean).setScale(4, RoundingMode.HALF_UP);
|
BigDecimal rLcl = D3.multiply(rMean).setScale(4, RoundingMode.HALF_UP);
|
|
// 构建结果
|
SpcResultVo result = new SpcResultVo();
|
|
SpcResultVo.ChartData xBarChart = new SpcResultVo.ChartData();
|
xBarChart.setData(xBarData);
|
xBarChart.setUcl(xBarUcl);
|
xBarChart.setLcl(xBarLcl);
|
xBarChart.setCl(xBarMean.setScale(4, RoundingMode.HALF_UP));
|
xBarChart.setSampleLabels(sampleLabels);
|
result.setXBar(xBarChart);
|
|
SpcResultVo.ChartData rChart = new SpcResultVo.ChartData();
|
rChart.setData(rData);
|
rChart.setUcl(rUcl);
|
rChart.setLcl(rLcl);
|
rChart.setCl(rMean.setScale(4, RoundingMode.HALF_UP));
|
rChart.setSampleLabels(sampleLabels);
|
result.setRChart(rChart);
|
|
// 制程能力
|
SpcResultVo.Capability capability = calculateCapability(values, dto.getUcl(), dto.getLcl());
|
result.setCapability(capability);
|
|
return result;
|
}
|
|
@Override
|
public SpcResultVo.Capability getCapability(SpcChartDto dto) {
|
List<Map<String, Object>> itemData = spcChartMapper.getItemData(dto);
|
|
if (CollectionUtil.isEmpty(itemData)) {
|
return null;
|
}
|
|
List<BigDecimal> values = itemData.stream()
|
.map(m -> new BigDecimal((String) m.get("lastValue")))
|
.collect(Collectors.toList());
|
|
return calculateCapability(values, dto.getUcl(), dto.getLcl());
|
}
|
|
@Override
|
public void export(SpcChartDto dto, HttpServletResponse response) {
|
SpcResultVo result = analyze(dto);
|
|
if (result == null) {
|
return;
|
}
|
|
try {
|
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
|
response.setCharacterEncoding("utf-8");
|
String fileName = "SPC分析数据_" + DateUtil.format(new Date(), "yyyyMMddHHmmss");
|
response.setHeader("Content-disposition", "attachment;filename=" + fileName + ".xlsx");
|
|
// 构建导出数据
|
List<Map<String, Object>> exportData = new ArrayList<>();
|
List<String> labels = result.getXBar().getSampleLabels();
|
List<BigDecimal> xBarData = result.getXBar().getData();
|
List<BigDecimal> rData = result.getRChart().getData();
|
|
for (int i = 0; i < labels.size(); i++) {
|
Map<String, Object> row = new HashMap<>();
|
row.put("sampleLabel", labels.get(i));
|
row.put("xBar", xBarData.get(i));
|
row.put("r", rData.get(i));
|
exportData.add(row);
|
}
|
|
EasyExcel.write(response.getOutputStream())
|
.sheet("SPC分析数据")
|
.doWrite(exportData);
|
} catch (IOException e) {
|
e.printStackTrace();
|
}
|
}
|
|
@Override
|
public List<String> getItemNames(SpcChartDto dto) {
|
return spcChartMapper.getItemNames(dto);
|
}
|
|
@Override
|
public List<String> getSampleNames(SpcChartDto dto) {
|
return spcChartMapper.getSampleNames(dto);
|
}
|
|
/**
|
* 计算制程能力
|
*/
|
private SpcResultVo.Capability calculateCapability(List<BigDecimal> values, BigDecimal ucl, BigDecimal lcl) {
|
if (ucl == null || lcl == null) {
|
return null;
|
}
|
|
SpcResultVo.Capability capability = new SpcResultVo.Capability();
|
|
// 计算均值和标准差
|
BigDecimal sum = values.stream().reduce(BigDecimal.ZERO, BigDecimal::add);
|
BigDecimal mean = sum.divide(new BigDecimal(values.size()), 10, RoundingMode.HALF_UP);
|
|
BigDecimal variance = BigDecimal.ZERO;
|
for (BigDecimal v : values) {
|
BigDecimal diff = v.subtract(mean);
|
variance = variance.add(diff.multiply(diff));
|
}
|
variance = variance.divide(new BigDecimal(values.size() - 1), 10, RoundingMode.HALF_UP);
|
BigDecimal stdDev = sqrt(variance);
|
|
// 计算Cp, Cpk
|
BigDecimal USL = ucl;
|
BigDecimal LSL = lcl;
|
BigDecimal tolerance = USL.subtract(LSL);
|
|
BigDecimal cp = tolerance.divide(stdDev.multiply(new BigDecimal(6)), 4, RoundingMode.HALF_UP);
|
BigDecimal cpu = USL.subtract(mean).divide(stdDev.multiply(new BigDecimal(3)), 4, RoundingMode.HALF_UP);
|
BigDecimal cpl = mean.subtract(LSL).divide(stdDev.multiply(new BigDecimal(3)), 4, RoundingMode.HALF_UP);
|
BigDecimal cpk = cpu.min(cpl);
|
|
capability.setCp(cp);
|
capability.setCpk(cpk);
|
capability.setPp(cp); // 简化处理
|
capability.setPpk(cpk);
|
|
return capability;
|
}
|
|
/**
|
* 获取A2常数
|
*/
|
private BigDecimal getA2(int n) {
|
double[] a2Values = {0, 1.880, 1.023, 0.729, 0.577, 0.483, 0.419, 0.373, 0.337, 0.308};
|
return new BigDecimal(a2Values[Math.min(n, 9)]);
|
}
|
|
/**
|
* 获取D3常数
|
*/
|
private BigDecimal getD3(int n) {
|
double[] d3Values = {0, 0, 0, 0, 0, 0, 0.076, 0.136, 0.184, 0.223};
|
return new BigDecimal(d3Values[Math.min(n, 9)]);
|
}
|
|
/**
|
* 获取D4常数
|
*/
|
private BigDecimal getD4(int n) {
|
double[] d4Values = {0, 3.267, 2.574, 2.282, 2.114, 2.004, 1.924, 1.864, 1.816, 1.777};
|
return new BigDecimal(d4Values[Math.min(n, 9)]);
|
}
|
|
/**
|
* 平方根计算
|
*/
|
private BigDecimal sqrt(BigDecimal value) {
|
BigDecimal x = value;
|
BigDecimal tolerance = new BigDecimal("1E-10");
|
BigDecimal guess = value.divide(BigDecimal.valueOf(2), MathContext.DECIMAL128);
|
|
while (x.subtract(guess).abs().compareTo(tolerance) > 0) {
|
x = guess;
|
guess = x.add(value.divide(x, MathContext.DECIMAL128)).divide(new BigDecimal("2"), MathContext.DECIMAL128);
|
}
|
|
return guess;
|
}
|
|
}
|