| | |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.pro.workorder.MesProWorkOrderBomDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.pro.workorder.MesProWorkOrderDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.pro.workorder.MesProWorkOrderProcessDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.qc.ipqc.MesQcIpqcDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.wm.materialstock.MesWmMaterialStockDO; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.dv.machinery.MesDvMachineryMapper; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.md.workstation.MesMdWorkstationMachineMapper; |
| | |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.pro.task.MesProTaskIssueMapper; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.pro.task.MesProTaskMapper; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.pro.workorder.MesProWorkOrderBomMapper; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.qc.ipqc.MesQcIpqcMapper; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.wm.materialstock.MesWmMaterialStockMapper; |
| | | import cn.iocoder.yudao.module.mes.service.pro.mps.MesProMpsService; |
| | | import cn.iocoder.yudao.module.mes.service.pro.process.MesProProcessService; |
| | | import cn.iocoder.yudao.module.mes.service.pro.workorder.MesProWorkOrderProcessService; |
| | | import cn.iocoder.yudao.module.mes.service.pro.workorder.MesProWorkOrderService; |
| | |
| | | 你需要综合分析以下信息,给出专业的风险评估: |
| | | |
| | | ## 分析维度 |
| | | 1. 质量风险:过程检验(IPQC)的缺陷率数据(致命/严重/轻微缺陷率) |
| | | 2. 设备风险:设备状态、上次保养时间、上次点检时间、生产效率 |
| | | 3. 物料风险:物料库存可用量与需求量的对比 |
| | | 4. 工艺瓶颈:关键工序的设备产能、任务排队情况 |
| | | 1. 设备风险:设备状态、上次保养时间、上次点检时间、生产效率 |
| | | 2. 物料风险:物料库存可用量与需求量的对比 |
| | | 3. 工艺瓶颈:关键工序的设备产能、任务排队情况 |
| | | |
| | | ## 风险等级定义 |
| | | - 高:存在致命缺陷率>5% 或 设备故障 或 关键物料短缺>50% 或 关键工序严重积压 |
| | | - 中:存在严重缺陷率>10% 或 设备长期未保养 或 物料紧张 |
| | | - 高:存在设备故障 或 关键物料短缺>50% 或 关键工序严重积压 |
| | | - 中:存在设备长期未保养 或 物料紧张 |
| | | - 低:各项指标正常 |
| | | |
| | | ## 输出格式 |
| | |
| | | overallRiskLevel 取值:1=低风险 2=中风险 3=高风险 |
| | | riskLevel 取值:充足/紧张/短缺 |
| | | severity 取值:高/中/低 |
| | | category 取值:质量/设备/物料/工艺瓶颈 |
| | | category 取值:设备/物料/工艺瓶颈 |
| | | """; |
| | | |
| | | private static final String SYSTEM_PROMPT_DELIVERY = """ |
| | |
| | | @Resource |
| | | private MesProFeedbackMapper feedbackMapper; |
| | | @Resource |
| | | private MesQcIpqcMapper ipqcMapper; |
| | | @Resource |
| | | private MesWmMaterialStockMapper materialStockMapper; |
| | | @Resource |
| | | private MesMdWorkstationMachineMapper workstationMachineMapper; |
| | |
| | | private MesDvMachineryMapper machineryMapper; |
| | | @Resource |
| | | private MesProMpsMapper mpsMapper; |
| | | @Resource |
| | | private MesProMpsService mpsService; |
| | | @Resource |
| | | private MesProProcessService processService; |
| | | @Resource |
| | |
| | | @Override |
| | | public MesProAiMaterialPredictRespVO predictMaterial(MesProAiMaterialPredictReqVO reqVO) { |
| | | try { |
| | | Map<String, Object> data = gatherMaterialData(reqVO.getWorkOrderId()); |
| | | Map<String, Object> data; |
| | | String logPrefix; |
| | | if (reqVO.getMpsId() != null) { |
| | | data = gatherMaterialDataFromMps(reqVO.getMpsId()); |
| | | logPrefix = "mpsId=" + reqVO.getMpsId(); |
| | | } else { |
| | | data = gatherMaterialData(reqVO.getWorkOrderId()); |
| | | logPrefix = "workOrderId=" + reqVO.getWorkOrderId(); |
| | | } |
| | | String aiResponse = aiChatApi.chat(SYSTEM_PROMPT_MATERIAL, JSONUtil.toJsonPrettyStr(data)); |
| | | log.info("AI 物料预测: workOrderId={}, response={}", reqVO.getWorkOrderId(), aiResponse); |
| | | log.info("AI 物料预测: {}, response={}", logPrefix, aiResponse); |
| | | return parseMaterialResponse(aiResponse); |
| | | } catch (Exception e) { |
| | | log.warn("AI 物料预测失败,workOrderId={}", reqVO.getWorkOrderId(), e); |
| | | log.warn("AI 物料预测失败,workOrderId={}, mpsId={}", reqVO.getWorkOrderId(), reqVO.getMpsId(), e); |
| | | MesProAiMaterialPredictRespVO fallback = new MesProAiMaterialPredictRespVO(); |
| | | fallback.setReasoning("AI 分析暂时不可用,请稍后重试"); |
| | | return fallback; |
| | |
| | | return data; |
| | | } |
| | | |
| | | private Map<String, Object> gatherMaterialDataFromMps(Long mpsId) { |
| | | Map<String, Object> data = new LinkedHashMap<>(); |
| | | MesProMpsDO mps = mpsService.getMps(mpsId); |
| | | if (mps == null) throw exception(PRO_WORK_ORDER_NOT_EXISTS); |
| | | |
| | | data.put("MPS 编号", mps.getCode()); |
| | | data.put("来源", "MPS 未下发,基于产品信息预测"); |
| | | data.put("计划数量", mps.getQuantity()); |
| | | data.put("已生产数量", mps.getQuantityProduced() != null ? mps.getQuantityProduced() : BigDecimal.ZERO); |
| | | fillItemInfo(data, mps.getProductId()); |
| | | |
| | | // 查询产品在库物料库存(通用,非工单特定 BOM) |
| | | List<MesWmMaterialStockDO> stocks = materialStockMapper.selectList( |
| | | new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<MesWmMaterialStockDO>() |
| | | .ne(MesWmMaterialStockDO::getQuantity, BigDecimal.ZERO)); |
| | | if (CollUtil.isNotEmpty(stocks)) { |
| | | List<Map<String, Object>> materials = new ArrayList<>(); |
| | | for (MesWmMaterialStockDO stock : stocks) { |
| | | Map<String, Object> item = new LinkedHashMap<>(); |
| | | item.put("物料ID", stock.getItemId()); |
| | | item.put("物料名称", resolveItemName(stock.getItemId())); |
| | | item.put("库存可用量", stock.getQuantity()); |
| | | materials.add(item); |
| | | } |
| | | data.put("当前库存物料", materials); |
| | | } else { |
| | | data.put("提示", "暂无库存数据,请参考产品编码评估物料需求"); |
| | | } |
| | | |
| | | return data; |
| | | } |
| | | |
| | | private MesProAiMaterialPredictRespVO parseMaterialResponse(String aiResponse) { |
| | | MesProAiMaterialPredictRespVO respVO = new MesProAiMaterialPredictRespVO(); |
| | | try { |
| | |
| | | @Override |
| | | public MesProAiDurationPredictRespVO predictDuration(MesProAiDurationPredictReqVO reqVO) { |
| | | try { |
| | | Map<String, Object> data = gatherDurationData(reqVO.getWorkOrderId()); |
| | | Map<String, Object> data; |
| | | String logPrefix; |
| | | if (reqVO.getMpsId() != null) { |
| | | data = gatherDurationDataFromMps(reqVO.getMpsId()); |
| | | logPrefix = "mpsId=" + reqVO.getMpsId(); |
| | | } else { |
| | | data = gatherDurationData(reqVO.getWorkOrderId()); |
| | | logPrefix = "workOrderId=" + reqVO.getWorkOrderId(); |
| | | } |
| | | String aiResponse = aiChatApi.chat(SYSTEM_PROMPT_DURATION, JSONUtil.toJsonPrettyStr(data)); |
| | | log.info("AI 时长预测: workOrderId={}, response={}", reqVO.getWorkOrderId(), aiResponse); |
| | | log.info("AI 时长预测: {}, response={}", logPrefix, aiResponse); |
| | | return parseDurationResponse(aiResponse); |
| | | } catch (Exception e) { |
| | | log.warn("AI 时长预测失败,workOrderId={}", reqVO.getWorkOrderId(), e); |
| | | log.warn("AI 时长预测失败,workOrderId={}, mpsId={}", reqVO.getWorkOrderId(), reqVO.getMpsId(), e); |
| | | MesProAiDurationPredictRespVO fallback = new MesProAiDurationPredictRespVO(); |
| | | fallback.setReasoning("AI 分析暂时不可用,请稍后重试"); |
| | | return fallback; |
| | |
| | | return totalEfficiency; |
| | | } |
| | | |
| | | private Map<String, Object> gatherDurationDataFromMps(Long mpsId) { |
| | | Map<String, Object> data = new LinkedHashMap<>(); |
| | | MesProMpsDO mps = mpsService.getMps(mpsId); |
| | | if (mps == null) throw exception(PRO_WORK_ORDER_NOT_EXISTS); |
| | | |
| | | data.put("MPS 编号", mps.getCode()); |
| | | data.put("来源", "MPS 未下发,基于产品信息估算"); |
| | | data.put("计划数量", mps.getQuantity()); |
| | | data.put("已生产数量", mps.getQuantityProduced() != null ? mps.getQuantityProduced() : BigDecimal.ZERO); |
| | | data.put("需求日期", mps.getRequestDate() != null ? mps.getRequestDate().toString() : "未设置"); |
| | | fillItemInfo(data, mps.getProductId()); |
| | | data.put("提示", "MPS 未关联工单工序,请基于产品编码和经验估算生产时长"); |
| | | |
| | | return data; |
| | | } |
| | | |
| | | private MesProAiDurationPredictRespVO parseDurationResponse(String aiResponse) { |
| | | MesProAiDurationPredictRespVO respVO = new MesProAiDurationPredictRespVO(); |
| | | try { |
| | |
| | | @Override |
| | | public MesProAiRiskPredictRespVO predictRisk(MesProAiRiskPredictReqVO reqVO) { |
| | | try { |
| | | Map<String, Object> data = gatherRiskData(reqVO.getWorkOrderId()); |
| | | Map<String, Object> data; |
| | | String logPrefix; |
| | | if (reqVO.getMpsId() != null) { |
| | | data = gatherRiskDataFromMps(reqVO.getMpsId()); |
| | | logPrefix = "mpsId=" + reqVO.getMpsId(); |
| | | } else { |
| | | data = gatherRiskData(reqVO.getWorkOrderId()); |
| | | logPrefix = "workOrderId=" + reqVO.getWorkOrderId(); |
| | | } |
| | | String aiResponse = aiChatApi.chat(SYSTEM_PROMPT_RISK, JSONUtil.toJsonPrettyStr(data)); |
| | | log.info("AI 风险预测: workOrderId={}, response={}", reqVO.getWorkOrderId(), aiResponse); |
| | | log.info("AI 风险预测: {}, response={}", logPrefix, aiResponse); |
| | | return parseRiskResponse(aiResponse); |
| | | } catch (Exception e) { |
| | | log.warn("AI 风险预测失败,workOrderId={}", reqVO.getWorkOrderId(), e); |
| | | log.warn("AI 风险预测失败,workOrderId={}, mpsId={}", reqVO.getWorkOrderId(), reqVO.getMpsId(), e); |
| | | MesProAiRiskPredictRespVO fallback = new MesProAiRiskPredictRespVO(); |
| | | fallback.setReasoning("AI 分析暂时不可用,请稍后重试"); |
| | | return fallback; |
| | |
| | | data.put("计划数量", workOrder.getQuantity()); |
| | | data.put("已生产数量", workOrder.getQuantityProduced()); |
| | | fillItemInfo(data, workOrder.getProductId()); |
| | | |
| | | // 质量风险 - IPQC |
| | | List<MesQcIpqcDO> ipqcList = ipqcMapper.selectList( |
| | | new LambdaQueryWrapper<MesQcIpqcDO>() |
| | | .eq(MesQcIpqcDO::getWorkOrderId, workOrderId)); |
| | | if (CollUtil.isNotEmpty(ipqcList)) { |
| | | List<Map<String, Object>> qcData = ipqcList.stream().map(ipqc -> { |
| | | Map<String, Object> item = new LinkedHashMap<>(); |
| | | item.put("检验单编号", ipqc.getCode()); |
| | | item.put("致命缺陷率", ipqc.getCriticalRate() != null ? ipqc.getCriticalRate() + "%" : "0%"); |
| | | item.put("严重缺陷率", ipqc.getMajorRate() != null ? ipqc.getMajorRate() + "%" : "0%"); |
| | | item.put("轻微缺陷率", ipqc.getMinorRate() != null ? ipqc.getMinorRate() + "%" : "0%"); |
| | | item.put("致命缺陷数", ipqc.getCriticalQuantity() != null ? ipqc.getCriticalQuantity() : 0); |
| | | item.put("严重缺陷数", ipqc.getMajorQuantity() != null ? ipqc.getMajorQuantity() : 0); |
| | | item.put("轻微缺陷数", ipqc.getMinorQuantity() != null ? ipqc.getMinorQuantity() : 0); |
| | | item.put("检验结果", ipqc.getCheckResult()); |
| | | return item; |
| | | }).collect(Collectors.toList()); |
| | | data.put("过程检验(IPQC)记录", qcData); |
| | | } |
| | | |
| | | // 设备风险 |
| | | List<MesProTaskDO> tasks = taskMapper.selectListByWorkOrderId(workOrderId); |
| | |
| | | return data; |
| | | } |
| | | |
| | | private Map<String, Object> gatherRiskDataFromMps(Long mpsId) { |
| | | Map<String, Object> data = new LinkedHashMap<>(); |
| | | MesProMpsDO mps = mpsService.getMps(mpsId); |
| | | if (mps == null) throw exception(PRO_WORK_ORDER_NOT_EXISTS); |
| | | |
| | | data.put("MPS 编号", mps.getCode()); |
| | | data.put("来源", "MPS 未下发,风险评估基于产品信息"); |
| | | data.put("计划数量", mps.getQuantity()); |
| | | data.put("已生产数量", mps.getQuantityProduced() != null ? mps.getQuantityProduced() : BigDecimal.ZERO); |
| | | data.put("需求日期", mps.getRequestDate() != null ? mps.getRequestDate().toString() : "未设置"); |
| | | data.put("下发日期", mps.getIssueDate() != null ? mps.getIssueDate().toString() : "未下发"); |
| | | fillItemInfo(data, mps.getProductId()); |
| | | |
| | | // 产品库存情况 |
| | | List<MesWmMaterialStockDO> stocks = materialStockMapper.selectList( |
| | | new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<MesWmMaterialStockDO>() |
| | | .ne(MesWmMaterialStockDO::getQuantity, BigDecimal.ZERO)); |
| | | if (CollUtil.isNotEmpty(stocks)) { |
| | | List<Map<String, Object>> stockList = new ArrayList<>(); |
| | | for (MesWmMaterialStockDO stock : stocks) { |
| | | Map<String, Object> item = new LinkedHashMap<>(); |
| | | item.put("物料ID", stock.getItemId()); |
| | | item.put("物料名称", resolveItemName(stock.getItemId())); |
| | | item.put("可用量", stock.getQuantity()); |
| | | stockList.add(item); |
| | | } |
| | | data.put("库存物料", stockList); |
| | | } |
| | | |
| | | data.put("提示", "MPS 未下发无工单数据,请结合产品特性和库存自行判断风险"); |
| | | |
| | | return data; |
| | | } |
| | | |
| | | private MesProAiRiskPredictRespVO parseRiskResponse(String aiResponse) { |
| | | MesProAiRiskPredictRespVO respVO = new MesProAiRiskPredictRespVO(); |
| | | try { |
| | |
| | | @Override |
| | | public MesProAiDeliveryPredictRespVO predictDelivery(MesProAiDeliveryPredictReqVO reqVO) { |
| | | try { |
| | | Map<String, Object> data = gatherDeliveryData(reqVO.getWorkOrderId()); |
| | | Map<String, Object> data; |
| | | String logPrefix; |
| | | if (reqVO.getMpsId() != null) { |
| | | data = gatherDeliveryDataFromMps(reqVO.getMpsId()); |
| | | logPrefix = "mpsId=" + reqVO.getMpsId(); |
| | | } else { |
| | | data = gatherDeliveryData(reqVO.getWorkOrderId()); |
| | | logPrefix = "workOrderId=" + reqVO.getWorkOrderId(); |
| | | } |
| | | String aiResponse = aiChatApi.chat(SYSTEM_PROMPT_DELIVERY, JSONUtil.toJsonPrettyStr(data)); |
| | | log.info("AI 交付预测: workOrderId={}, response={}", reqVO.getWorkOrderId(), aiResponse); |
| | | log.info("AI 交付预测: {}, response={}", logPrefix, aiResponse); |
| | | return parseDeliveryResponse(aiResponse); |
| | | } catch (Exception e) { |
| | | log.warn("AI 交付预测失败,workOrderId={}", reqVO.getWorkOrderId(), e); |
| | | log.warn("AI 交付预测失败,workOrderId={}, mpsId={}", reqVO.getWorkOrderId(), reqVO.getMpsId(), e); |
| | | MesProAiDeliveryPredictRespVO fallback = new MesProAiDeliveryPredictRespVO(); |
| | | fallback.setReasoning("AI 分析暂时不可用,请稍后重试"); |
| | | return fallback; |
| | |
| | | return data; |
| | | } |
| | | |
| | | private Map<String, Object> gatherDeliveryDataFromMps(Long mpsId) { |
| | | Map<String, Object> data = new LinkedHashMap<>(); |
| | | MesProMpsDO mps = mpsService.getMps(mpsId); |
| | | if (mps == null) throw exception(PRO_WORK_ORDER_NOT_EXISTS); |
| | | |
| | | data.put("MPS 编号", mps.getCode()); |
| | | data.put("来源", "MPS 未下发,基于产品信息评估"); |
| | | data.put("计划数量", mps.getQuantity()); |
| | | data.put("已生产数量", mps.getQuantityProduced() != null ? mps.getQuantityProduced() : BigDecimal.ZERO); |
| | | data.put("MPS 状态", mps.getStatus()); |
| | | data.put("销售订单号", mps.getSaleOrderNo()); |
| | | data.put("需求日期", mps.getRequestDate() != null ? mps.getRequestDate().toString() : "未设置"); |
| | | data.put("下发日期", mps.getIssueDate() != null ? mps.getIssueDate().toString() : "未下发"); |
| | | fillItemInfo(data, mps.getProductId()); |
| | | fillClientInfo(data, mps.getClientId()); |
| | | |
| | | // 进度计算 |
| | | BigDecimal progressPercent = BigDecimal.ZERO; |
| | | if (mps.getQuantity() != null && mps.getQuantity().compareTo(BigDecimal.ZERO) > 0 |
| | | && mps.getQuantityProduced() != null) { |
| | | progressPercent = mps.getQuantityProduced() |
| | | .divide(mps.getQuantity(), 4, RoundingMode.HALF_UP) |
| | | .multiply(BigDecimal.valueOf(100)); |
| | | } |
| | | data.put("生产进度(%)", progressPercent.setScale(1, RoundingMode.HALF_UP)); |
| | | |
| | | // 剩余天数 |
| | | int remainingDays = 0; |
| | | if (mps.getRequestDate() != null) { |
| | | remainingDays = (int) ChronoUnit.DAYS.between(LocalDate.now(), mps.getRequestDate().toLocalDate()); |
| | | } |
| | | data.put("距离需求日期剩余天数", Math.max(0, remainingDays)); |
| | | |
| | | return data; |
| | | } |
| | | |
| | | private MesProAiDeliveryPredictRespVO parseDeliveryResponse(String aiResponse) { |
| | | MesProAiDeliveryPredictRespVO respVO = new MesProAiDeliveryPredictRespVO(); |
| | | try { |