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<String, Object> 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<String, Object> gatherQcData(Long qcId, Integer qcType) {
|
Map<String, Object> 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<MesQcIndicatorResultDO> results = indicatorResultMapper.selectListByQcIdAndType(qcId, qcType);
|
if (CollUtil.isNotEmpty(results)) {
|
List<Map<String, Object>> indicatorData = new ArrayList<>();
|
for (MesQcIndicatorResultDO result : results) {
|
List<MesQcIndicatorResultDetailDO> details =
|
indicatorResultDetailMapper.selectListByResultId(result.getId());
|
if (CollUtil.isEmpty(details)) continue;
|
|
Map<String, Object> sample = new LinkedHashMap<>();
|
sample.put("样品编号", result.getCode());
|
sample.put("SN", result.getSn() != null ? result.getSn() : "");
|
List<Map<String, String>> values = new ArrayList<>();
|
for (MesQcIndicatorResultDetailDO detail : details) {
|
Map<String, String> 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<MesQcDefectRecordDO> defects = defectRecordMapper.selectListByQcTypeAndQcId(qcType, qcId);
|
if (CollUtil.isNotEmpty(defects)) {
|
List<Map<String, Object>> defectData = defects.stream().map(d -> {
|
Map<String, Object> 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<String, Object> 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<String, Object> 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<String, Object> 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<String, Object> 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<String> keyPoints = (List<String>) parsed.get("keyPoints");
|
respVO.setKeyPoints(keyPoints);
|
} catch (Exception e) {
|
log.warn("解析 AI 响应失败,返回原始内容: {}", aiResponse, e);
|
respVO.setReasoning(aiResponse);
|
}
|
return respVO;
|
}
|
|
}
|