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> itemData = spcChartMapper.getItemData(dto); if (CollectionUtil.isEmpty(itemData)) { return null; } // 获取数值列表 List values = itemData.stream() .map(m -> new BigDecimal((String) m.get("lastValue"))) .collect(Collectors.toList()); // 子组大小,默认为5 int subgroupSize = dto.getSubgroupSize() != null ? dto.getSubgroupSize() : 5; // 分组 List> 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 xBarData = new ArrayList<>(); List rData = new ArrayList<>(); List sampleLabels = new ArrayList<>(); for (int i = 0; i < subgroups.size(); i++) { List 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> itemData = spcChartMapper.getItemData(dto); if (CollectionUtil.isEmpty(itemData)) { return null; } List 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> exportData = new ArrayList<>(); List labels = result.getXBar().getSampleLabels(); List xBarData = result.getXBar().getData(); List rData = result.getRChart().getData(); for (int i = 0; i < labels.size(); i++) { Map 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 getItemNames(SpcChartDto dto) { return spcChartMapper.getItemNames(dto); } @Override public List getSampleNames(SpcChartDto dto) { return spcChartMapper.getSampleNames(dto); } /** * 计算制程能力 */ private SpcResultVo.Capability calculateCapability(List 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; } }