package cn.iocoder.yudao.module.mes.service.qc.ai; import cn.hutool.core.collection.CollUtil; import cn.hutool.json.JSONUtil; import cn.iocoder.yudao.module.ai.api.chat.AiChatApi; import cn.iocoder.yudao.module.mes.controller.admin.qc.ai.vo.MesQcAiSuggestReqVO; import cn.iocoder.yudao.module.mes.controller.admin.qc.ai.vo.MesQcAiSuggestRespVO; import cn.iocoder.yudao.module.mes.dal.dataobject.qc.defectrecord.MesQcDefectRecordDO; import cn.iocoder.yudao.module.mes.dal.dataobject.qc.indicatorresult.MesQcIndicatorResultDO; import cn.iocoder.yudao.module.mes.dal.dataobject.qc.indicatorresult.MesQcIndicatorResultDetailDO; import cn.iocoder.yudao.module.mes.dal.dataobject.qc.iqc.MesQcIqcDO; import cn.iocoder.yudao.module.mes.dal.dataobject.qc.ipqc.MesQcIpqcDO; import cn.iocoder.yudao.module.mes.dal.dataobject.qc.oqc.MesQcOqcDO; import cn.iocoder.yudao.module.mes.dal.dataobject.qc.rqc.MesQcRqcDO; import cn.iocoder.yudao.module.mes.dal.mysql.qc.indicatorresult.MesQcIndicatorResultMapper; import cn.iocoder.yudao.module.mes.dal.mysql.qc.indicatorresult.MesQcIndicatorResultDetailMapper; import cn.iocoder.yudao.module.mes.dal.mysql.qc.defectrecord.MesQcDefectRecordMapper; import cn.iocoder.yudao.module.mes.enums.MesBizTypeConstants; import cn.iocoder.yudao.module.mes.enums.qc.MesQcCheckResultEnum; import cn.iocoder.yudao.module.mes.service.qc.indicator.MesQcIndicatorService; import cn.iocoder.yudao.module.mes.service.qc.iqc.MesQcIqcService; import cn.iocoder.yudao.module.mes.service.qc.ipqc.MesQcIpqcService; import cn.iocoder.yudao.module.mes.service.qc.oqc.MesQcOqcService; import cn.iocoder.yudao.module.mes.service.qc.rqc.MesQcRqcService; import cn.iocoder.yudao.module.mdm.api.item.MdmItemApi; import cn.iocoder.yudao.module.mdm.api.item.dto.MdmItemRespDTO; import cn.iocoder.yudao.module.srm.api.supplier.SrmSupplierApi; import cn.iocoder.yudao.module.srm.api.supplier.dto.SrmSupplierRespDTO; import cn.iocoder.yudao.module.crm.api.customer.CrmCustomerApi; import cn.iocoder.yudao.module.crm.api.customer.dto.CrmCustomerRespDTO; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import java.util.*; import java.util.stream.Collectors; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.*; @Service @Slf4j public class MesQcAiServiceImpl implements MesQcAiService { private static final String SYSTEM_PROMPT = """ 你是一位资深的质检专家,擅长根据检验数据判定产品是否合格。 你需要综合分析以下信息,给出专业的判定建议: ## 判定规则 1. 合格(编码1):所有指标均在阈值范围内,无致命/严重缺陷 2. 特采(编码2):个别指标轻微超差,可让步接收 3. 不合格退货(编码3):多项指标超差或存在严重缺陷,不可接收 4. 不合格报废(编码4):存在致命缺陷或多项严重超差,无法使用 ## 输出格式 请按以下JSON格式返回(不要包含其他内容): {"suggestedResult": 1, "reasoning": "判定理由...", "keyPoints": ["关注点1", "关注点2"]} suggestedResult 取值:1合格 2特采 3不合格退货 4不合格报废 reasoning:简明扼要的判定理由 keyPoints:需要重点关注的指标或缺陷清单"""; @Resource private MesQcIqcService iqcService; @Resource private MesQcIpqcService ipqcService; @Resource private MesQcOqcService oqcService; @Resource private MesQcRqcService rqcService; @Resource private MesQcIndicatorResultMapper indicatorResultMapper; @Resource private MesQcIndicatorResultDetailMapper indicatorResultDetailMapper; @Resource private MesQcDefectRecordMapper defectRecordMapper; @Resource private MesQcIndicatorService indicatorService; @Resource private MdmItemApi mdmItemApi; @Resource private SrmSupplierApi srmSupplierApi; @Resource private CrmCustomerApi crmCustomerApi; @Resource private AiChatApi aiChatApi; @Override public MesQcAiSuggestRespVO suggestCheckResult(MesQcAiSuggestReqVO reqVO) { try { // 1. 收集质检数据 Map qcData = gatherQcData(reqVO.getQcId(), reqVO.getQcType()); // 2. 构建 Prompt 并调用 AI String userMessage = JSONUtil.toJsonPrettyStr(qcData); String aiResponse = aiChatApi.chat(SYSTEM_PROMPT, userMessage); log.info("AI 判定结果: qcId={}, qcType={}, response={}", reqVO.getQcId(), reqVO.getQcType(), aiResponse); // 3. 解析 AI 返回 return parseAiResponse(aiResponse); } catch (Exception e) { log.warn("AI 辅助判定失败,qcId={}, qcType={}", reqVO.getQcId(), reqVO.getQcType(), e); MesQcAiSuggestRespVO fallback = new MesQcAiSuggestRespVO(); fallback.setReasoning("AI 判定暂时不可用,请人工判定"); return fallback; } } private Map gatherQcData(Long qcId, Integer qcType) { Map data = new LinkedHashMap<>(); // 质检单头信息 if (MesBizTypeConstants.QC_IQC == qcType) { data.put("质检类型", "来料检验"); MesQcIqcDO iqc = iqcService.getIqc(qcId); if (iqc == null) throw exception(QC_IQC_NOT_EXISTS); data.put("检验单编号", iqc.getCode()); data.put("接收数量", iqc.getReceivedQuantity()); data.put("检测数量", iqc.getCheckQuantity()); data.put("合格数量", iqc.getQualifiedQuantity()); data.put("不合格数量", iqc.getUnqualifiedQuantity()); data.put("缺陷统计", Map.of( "致命缺陷率", iqc.getCriticalRate() != null ? iqc.getCriticalRate() + "%" : "0%", "严重缺陷率", iqc.getMajorRate() != null ? iqc.getMajorRate() + "%" : "0%", "轻微缺陷率", iqc.getMinorRate() != null ? iqc.getMinorRate() + "%" : "0%", "致命缺陷数", iqc.getCriticalQuantity() != null ? iqc.getCriticalQuantity() : 0, "严重缺陷数", iqc.getMajorQuantity() != null ? iqc.getMajorQuantity() : 0, "轻微缺陷数", iqc.getMinorQuantity() != null ? iqc.getMinorQuantity() : 0 )); fillItemInfo(data, iqc.getItemId()); fillVendorInfo(data, iqc.getVendorId()); } else if (MesBizTypeConstants.QC_IPQC == qcType) { data.put("质检类型", "过程检验"); MesQcIpqcDO ipqc = ipqcService.getIpqc(qcId); if (ipqc == null) throw exception(QC_IPQC_NOT_EXISTS); data.put("检验单编号", ipqc.getCode()); data.put("检测数量", ipqc.getCheckQuantity()); data.put("合格数量", ipqc.getQualifiedQuantity()); data.put("不合格数量", ipqc.getUnqualifiedQuantity()); data.put("工单ID", ipqc.getWorkOrderId()); data.put("工序ID", ipqc.getProcessId()); fillItemInfo(data, ipqc.getItemId()); } else if (MesBizTypeConstants.QC_OQC == qcType) { data.put("质检类型", "出货检验"); MesQcOqcDO oqc = oqcService.getOqc(qcId); if (oqc == null) throw exception(QC_OQC_NOT_EXISTS); data.put("检验单编号", oqc.getCode()); data.put("出货数量", oqc.getOutQuantity()); data.put("检测数量", oqc.getCheckQuantity()); data.put("合格数量", oqc.getQualifiedQuantity()); data.put("不合格数量", oqc.getUnqualifiedQuantity()); data.put("最小检测数量", oqc.getMinCheckQuantity()); data.put("最大不合格数量", oqc.getMaxUnqualifiedQuantity()); fillItemInfo(data, oqc.getItemId()); fillClientInfo(data, oqc.getClientId()); } else if (MesBizTypeConstants.QC_RQC == qcType) { data.put("质检类型", "退货检验"); MesQcRqcDO rqc = rqcService.getRqc(qcId); if (rqc == null) throw exception(QC_RQC_NOT_EXISTS); data.put("检验单编号", rqc.getCode()); data.put("检测数量", rqc.getCheckQuantity()); data.put("合格数量", rqc.getQualifiedQuantity()); data.put("不合格数量", rqc.getUnqualifiedQuantity()); fillItemInfo(data, rqc.getItemId()); } // 指标检验结果 List results = indicatorResultMapper.selectListByQcIdAndType(qcId, qcType); if (CollUtil.isNotEmpty(results)) { List> indicatorData = new ArrayList<>(); for (MesQcIndicatorResultDO result : results) { List details = indicatorResultDetailMapper.selectListByResultId(result.getId()); if (CollUtil.isEmpty(details)) continue; Map sample = new LinkedHashMap<>(); sample.put("样品编号", result.getCode()); sample.put("SN", result.getSn() != null ? result.getSn() : ""); List> values = new ArrayList<>(); for (MesQcIndicatorResultDetailDO detail : details) { Map item = new LinkedHashMap<>(); String indicatorName = indicatorService.getIndicator(detail.getIndicatorId()).getName(); item.put("指标", indicatorName); item.put("实测值", detail.getValue()); values.add(item); } sample.put("检测值", values); indicatorData.add(sample); } data.put("检验指标结果", indicatorData); } // 缺陷记录 List defects = defectRecordMapper.selectListByQcTypeAndQcId(qcType, qcId); if (CollUtil.isNotEmpty(defects)) { List> defectData = defects.stream().map(d -> { Map item = new LinkedHashMap<>(); item.put("缺陷名称", d.getName()); item.put("缺陷等级", d.getLevel()); item.put("缺陷数量", d.getQuantity()); item.put("备注", d.getRemark() != null ? d.getRemark() : ""); return item; }).collect(Collectors.toList()); data.put("缺陷记录", defectData); } return data; } private void fillItemInfo(Map data, Long itemId) { if (itemId == null) return; try { MdmItemRespDTO item = mdmItemApi.getItem(itemId).getCheckedData(); if (item != null) { data.put("物料编码", item.getCode() != null ? item.getCode() : itemId); data.put("物料名称", item.getName() != null ? item.getName() : ""); } } catch (Exception e) { data.put("物料ID", itemId); } } private void fillVendorInfo(Map data, Long vendorId) { if (vendorId == null) return; try { SrmSupplierRespDTO vendor = srmSupplierApi.getSupplier(vendorId).getCheckedData(); if (vendor != null) { data.put("供应商名称", vendor.getName() != null ? vendor.getName() : ""); } } catch (Exception e) { data.put("供应商ID", vendorId); } } private void fillClientInfo(Map data, Long clientId) { if (clientId == null) return; try { CrmCustomerRespDTO customer = crmCustomerApi.getCustomer(clientId); if (customer != null) { data.put("客户名称", customer.getName() != null ? customer.getName() : ""); } } catch (Exception e) { data.put("客户ID", clientId); } } private MesQcAiSuggestRespVO parseAiResponse(String aiResponse) { MesQcAiSuggestRespVO respVO = new MesQcAiSuggestRespVO(); try { // 提取 JSON 部分(AI 可能包裹在 markdown 代码块中) String json = aiResponse; int start = json.indexOf("{"); int end = json.lastIndexOf("}"); if (start >= 0 && end > start) { json = json.substring(start, end + 1); } Map parsed = JSONUtil.toBean(json, Map.class); Object result = parsed.get("suggestedResult"); if (result instanceof Number num) { respVO.setSuggestedResult(num.intValue()); // 映射结果名称 for (MesQcCheckResultEnum e : MesQcCheckResultEnum.values()) { if (e.getType().equals(num.intValue())) { respVO.setSuggestedResultName(e.getName()); break; } } } respVO.setReasoning((String) parsed.get("reasoning")); @SuppressWarnings("unchecked") List keyPoints = (List) parsed.get("keyPoints"); respVO.setKeyPoints(keyPoints); } catch (Exception e) { log.warn("解析 AI 响应失败,返回原始内容: {}", aiResponse, e); respVO.setReasoning(aiResponse); } return respVO; } }