3 天以前 83e1b4d0e661f11a407fd6ea86e906b9b87b7180
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
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;
    }
 
}