diff --git a/aucma-report/src/main/java/com/aucma/report/controller/InjectionOeeController.java b/aucma-report/src/main/java/com/aucma/report/controller/InjectionOeeController.java
index d6bf9d8..2876b44 100644
--- a/aucma-report/src/main/java/com/aucma/report/controller/InjectionOeeController.java
+++ b/aucma-report/src/main/java/com/aucma/report/controller/InjectionOeeController.java
@@ -32,10 +32,11 @@ public class InjectionOeeController extends BaseController {
@RequestParam(required = false) String deviceCode,
@RequestParam(required = false) String beginTime,
@RequestParam(required = false) String endTime,
- @RequestParam(required = false) String shiftType) {
+ @RequestParam(required = false) String shiftType,
+ @RequestParam(required = false) String availabilityType) {
List list = injectionOeeService.getInjectionOeeAnalysis(
- deviceCode, beginTime, endTime, shiftType);
+ deviceCode, beginTime, endTime, shiftType, availabilityType);
return success(list);
}
diff --git a/aucma-report/src/main/java/com/aucma/report/domain/vo/InjectionOeeAnalysisVo.java b/aucma-report/src/main/java/com/aucma/report/domain/vo/InjectionOeeAnalysisVo.java
index f062426..ec67bf6 100644
--- a/aucma-report/src/main/java/com/aucma/report/domain/vo/InjectionOeeAnalysisVo.java
+++ b/aucma-report/src/main/java/com/aucma/report/domain/vo/InjectionOeeAnalysisVo.java
@@ -121,15 +121,15 @@ public class InjectionOeeAnalysisVo extends BaseEntity implements Serializable {
@JsonProperty("SHIFT_NAME")
private String SHIFT_NAME;
- /** 本日利用率 */
+ /** 日利用率:按页面传入统计日期计算,不固定取服务器当天 */
@JsonProperty("TODAY_AVAILABILITY")
private Double TODAY_AVAILABILITY;
- /** 周利用率 */
+ /** 周利用率:按页面结束日所在自然周计算 */
@JsonProperty("WEEK_AVAILABILITY")
private Double WEEK_AVAILABILITY;
- /** 总利用率 */
+ /** 总利用率:按页面选择日期范围计算 */
@JsonProperty("TOTAL_AVAILABILITY")
private Double TOTAL_AVAILABILITY;
diff --git a/aucma-report/src/main/java/com/aucma/report/mapper/InjectionOeeMapper.java b/aucma-report/src/main/java/com/aucma/report/mapper/InjectionOeeMapper.java
index 52df69e..773ba7e 100644
--- a/aucma-report/src/main/java/com/aucma/report/mapper/InjectionOeeMapper.java
+++ b/aucma-report/src/main/java/com/aucma/report/mapper/InjectionOeeMapper.java
@@ -1,6 +1,7 @@
package com.aucma.report.mapper;
import com.aucma.report.domain.vo.InjectionOeeAnalysisVo;
+import com.aucma.report.domain.vo.ParamRawPoint;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
@@ -57,6 +58,20 @@ public interface InjectionOeeMapper {
@Param("endTime") Date endTime,
@Param("tableSuffixes") List tableSuffixes);
+ /**
+ * 批量获取指定时间点前每台设备最新模腔数。
+ *
+ * @param deviceCodes 设备编码列表
+ * @param endTime 截止时间
+ * @param tableSuffixes 月表后缀列表
+ * @param oldDevice 是否 OLD 手工设备
+ * @return 包含 DEVICE_CODE、CAVITIES 的 Map 列表
+ */
+ List
*
* @author Antigravity
*/
@@ -49,6 +51,10 @@ public class InjectionOeeServiceImpl implements IInjectionOeeService {
private static final String SHIFT_DAY = "DAY";
private static final String SHIFT_NIGHT = "NIGHT";
+ private static final String AVAILABILITY_DAY = "DAY";
+ private static final String AVAILABILITY_WEEK = "WEEK";
+ private static final String AVAILABILITY_TOTAL = "TOTAL";
+
private static final String OLD_DEVICE_CODE_PREFIX = "OLD-";
private static final double OLD_DEVICE_ESTIMATE_MIN_RATE = 0.70D;
private static final double OLD_DEVICE_ESTIMATE_MAX_RATE = 0.90D;
@@ -64,8 +70,8 @@ public class InjectionOeeServiceImpl implements IInjectionOeeService {
/** OLD 手工设备采集稀疏,基线窗口放宽到 24 小时。 */
private static final int OLD_BASELINE_HOURS = -24;
- /** 周利用率固定看最近 7 个生产日。 */
- private static final int WEEK_DAYS = 6;
+ /** 周期时间有效样本下限,样本过少时用 1.0 兜底,避免少量异常点放大 P。 */
+ private static final int MIN_CYCLE_SAMPLE_COUNT = 10;
@Autowired
private InjectionOeeMapper injectionOeeMapper;
@@ -74,7 +80,11 @@ public class InjectionOeeServiceImpl implements IInjectionOeeService {
private DeviceParamTableRouter deviceParamTableRouter;
@Override
- public List getInjectionOeeAnalysis(String deviceCode, String beginTimeStr, String endTimeStr, String shiftType) {
+ public List getInjectionOeeAnalysis(String deviceCode,
+ String beginTimeStr,
+ String endTimeStr,
+ String shiftType,
+ String availabilityType) {
List targetDevices = injectionOeeMapper.selectTargetDevices(deviceCode);
if (CollectionUtils.isEmpty(targetDevices)) {
return Collections.emptyList();
@@ -84,47 +94,49 @@ public class InjectionOeeServiceImpl implements IInjectionOeeService {
sdf.setLenient(false);
String normalizedShiftType = normalizeShiftType(shiftType);
+ String normalizedAvailabilityType = normalizeAvailabilityType(availabilityType);
Date todayDate = truncateToDay(new Date());
- Date weekBeginDate = addDays(todayDate, -WEEK_DAYS);
- DateRange totalRange = resolveTotalRange(beginTimeStr, endTimeStr, sdf, todayDate);
+ Date defaultReportDate = addDays(todayDate, -1);
+ DateRange selectedRange = resolveSelectedRange(beginTimeStr, endTimeStr, sdf, defaultReportDate, todayDate);
+ DateRange availabilityRange = resolveAvailabilityRange(selectedRange, normalizedAvailabilityType, todayDate);
- List totalWindows = buildShiftWindows(totalRange.beginDate, totalRange.endDate, normalizedShiftType);
- List weekWindows = buildShiftWindows(weekBeginDate, todayDate, normalizedShiftType);
- List todayWindows = buildShiftWindows(todayDate, todayDate, normalizedShiftType);
+ List metricWindows = buildShiftWindows(availabilityRange.beginDate,
+ availabilityRange.endDate, normalizedShiftType);
+ Date queryBeginTime = firstWindowBegin(metricWindows);
+ Date queryEndTime = lastWindowEnd(metricWindows);
+ QualityResult quality = calculateQuality(queryBeginTime, queryEndTime);
+ MetricCache metricCache = loadMetricCache(targetDevices, metricWindows);
List results = new ArrayList<>();
for (InjectionOeeAnalysisVo device : targetDevices) {
- InjectionOeeAnalysisVo vo = buildOeeRow(device, normalizedShiftType, totalWindows, weekWindows, todayWindows);
+ InjectionOeeAnalysisVo vo = buildOeeRow(device, normalizedShiftType, normalizedAvailabilityType,
+ metricWindows, quality, metricCache);
results.add(vo);
}
- fillMissingOldDeviceOeeByReference(results);
+ fillMissingOldDeviceOeeByReference(results, normalizedAvailabilityType);
- results.sort(Comparator.comparing(InjectionOeeAnalysisVo::getDEVICE_CODE));
+ results.sort(this::compareByDeviceDisplayOrder);
return results;
}
private InjectionOeeAnalysisVo buildOeeRow(InjectionOeeAnalysisVo device,
String shiftType,
- List totalWindows,
- List weekWindows,
- List todayWindows) {
+ String availabilityType,
+ List metricWindows,
+ QualityResult quality,
+ MetricCache metricCache) {
String deviceCode = device.getDEVICE_CODE();
- CounterSummary totalRun = selectCounterSummary(deviceCode, PARAM_RUN_SECONDS, totalWindows);
- CounterSummary totalShots = selectCounterSummary(deviceCode, PARAM_SHOTS, totalWindows);
- CounterSummary weekRun = selectCounterSummary(deviceCode, PARAM_RUN_SECONDS, weekWindows);
- CounterSummary todayRun = selectCounterSummary(deviceCode, PARAM_RUN_SECONDS, todayWindows);
+ List runRawPoints = metricCache.getRunRawPoints(deviceCode);
+ List shotsRawPoints = metricCache.getShotRawPoints(deviceCode);
- Date queryBeginTime = firstWindowBegin(totalWindows);
- Date queryEndTime = lastWindowEnd(totalWindows);
- List tableSuffixes = resolveTableSuffixes(queryBeginTime, queryEndTime);
+ CounterSummary totalRun = calculateCounterSummary(deviceCode, metricWindows, runRawPoints);
+ CounterSummary totalShots = calculateCounterSummary(deviceCode, metricWindows, shotsRawPoints);
- BigDecimal cavities = resolveCavities(deviceCode, queryEndTime, tableSuffixes);
+ BigDecimal cavities = resolveCavities(deviceCode, metricCache);
boolean cavitiesEstimated = cavities.compareTo(BigDecimal.ONE) == 0;
- CycleResult cycle = calculateStandardCycle(injectionOeeMapper.selectCycleTimeSamples(
- deviceCode, queryBeginTime, queryEndTime, tableSuffixes));
- QualityResult quality = calculateQuality(queryBeginTime, queryEndTime);
+ CycleResult cycle = calculateStandardCycle(metricCache.getCycleStats(deviceCode));
BigDecimal plannedSeconds = BigDecimal.valueOf(totalRun.planSeconds);
BigDecimal runSeconds = BigDecimal.valueOf(totalRun.value);
@@ -149,9 +161,7 @@ public class InjectionOeeServiceImpl implements IInjectionOeeService {
vo.setQUALITY(toDouble(qualityRate));
vo.setOEE(toDouble(oee));
- vo.setTOTAL_AVAILABILITY(toDouble(capRate(availability)));
- vo.setWEEK_AVAILABILITY(toDouble(capRate(calcRate(BigDecimal.valueOf(weekRun.value), BigDecimal.valueOf(weekRun.planSeconds)))));
- vo.setTODAY_AVAILABILITY(toDouble(capRate(calcRate(BigDecimal.valueOf(todayRun.value), BigDecimal.valueOf(todayRun.planSeconds)))));
+ applyAvailabilityTypeValue(vo, availabilityType, capRate(availability));
vo.setPLANNED_TIME_MINUTES(totalRun.planSeconds / 60);
vo.setDOWNTIME_MINUTES(Math.max(0L, (totalRun.planSeconds - totalRun.value) / 60));
@@ -175,41 +185,308 @@ public class InjectionOeeServiceImpl implements IInjectionOeeService {
return vo;
}
- private CounterSummary selectCounterSummary(String deviceCode, String paramName, List windows) {
+ /**
+ * 一次性加载本次 OEE 页面需要的设备参数缓存,避免按设备循环触发多次相同结构的 SQL。
+ */
+ private MetricCache loadMetricCache(List devices,
+ List metricWindows) {
+ MetricCache metricCache = new MetricCache();
+
+ loadRawPointCache(metricCache.runRawPointsByDevice, devices, PARAM_RUN_SECONDS, metricWindows);
+ loadRawPointCache(metricCache.shotRawPointsByDevice, devices, PARAM_SHOTS, metricWindows);
+
+ Date queryBeginTime = firstWindowBegin(metricWindows);
+ Date queryEndTime = lastWindowEnd(metricWindows);
+ List tableSuffixes = resolveTableSuffixes(queryBeginTime, queryEndTime);
+ loadCavityCache(metricCache.cavitiesByDevice, devices, queryEndTime, tableSuffixes);
+ loadCycleStatsCache(metricCache.cycleStatsByDevice, devices, queryBeginTime, queryEndTime, tableSuffixes);
+ return metricCache;
+ }
+
+ private void loadRawPointCache(Map> targetMap,
+ List devices,
+ String paramName,
+ List windows) {
+ loadRawPointCache(targetMap, extractDeviceCodes(devices, false), paramName, windows,
+ AUTO_BASELINE_HOURS, false);
+ loadRawPointCache(targetMap, extractDeviceCodes(devices, true), paramName, windows,
+ OLD_BASELINE_HOURS, true);
+ for (List rawPoints : targetMap.values()) {
+ rawPoints.sort(Comparator.comparing(ParamRawPoint::getCollectTime));
+ }
+ }
+
+ private void loadRawPointCache(Map> targetMap,
+ List deviceCodes,
+ String paramName,
+ List windows,
+ int baselineHours,
+ boolean oldDevice) {
+ if (CollectionUtils.isEmpty(deviceCodes) || CollectionUtils.isEmpty(windows)) {
+ return;
+ }
+
+ List queryRanges = buildMergedQueryRanges(windows, baselineHours);
+ for (QueryRange queryRange : queryRanges) {
+ List tableSuffixes = resolveTableSuffixes(queryRange.beginTime, queryRange.endTime);
+ if (!oldDevice && CollectionUtils.isEmpty(tableSuffixes)) {
+ continue;
+ }
+ List rawPoints = injectionOeeMapper.selectRawParamValuesBatch(
+ deviceCodes,
+ paramName,
+ queryRange.beginTime,
+ queryRange.endTime,
+ tableSuffixes,
+ oldDevice
+ );
+ mergeRawPointCache(targetMap, rawPoints);
+ }
+ }
+
+ private void mergeRawPointCache(Map> targetMap, List rawPoints) {
+ if (CollectionUtils.isEmpty(rawPoints)) {
+ return;
+ }
+ for (ParamRawPoint rawPoint : rawPoints) {
+ if (rawPoint == null || !StringUtils.hasText(rawPoint.getDeviceCode())) {
+ continue;
+ }
+ List deviceRawPoints = targetMap.get(rawPoint.getDeviceCode());
+ if (deviceRawPoints == null) {
+ deviceRawPoints = new ArrayList<>();
+ targetMap.put(rawPoint.getDeviceCode(), deviceRawPoints);
+ }
+ deviceRawPoints.add(rawPoint);
+ }
+ }
+
+ private void loadCavityCache(Map targetMap,
+ List devices,
+ Date endTime,
+ List tableSuffixes) {
+ loadCavityCache(targetMap, extractDeviceCodes(devices, false), endTime, tableSuffixes, false);
+ loadCavityCache(targetMap, extractDeviceCodes(devices, true), endTime, tableSuffixes, true);
+ }
+
+ private void loadCavityCache(Map targetMap,
+ List deviceCodes,
+ Date endTime,
+ List tableSuffixes,
+ boolean oldDevice) {
+ if (CollectionUtils.isEmpty(deviceCodes)) {
+ return;
+ }
+ if (!oldDevice && CollectionUtils.isEmpty(tableSuffixes)) {
+ return;
+ }
+ List> rows = injectionOeeMapper.selectLatestCavitiesBatch(
+ deviceCodes, endTime, tableSuffixes, oldDevice);
+ if (CollectionUtils.isEmpty(rows)) {
+ return;
+ }
+ for (Map row : rows) {
+ String deviceCode = toMapString(row, "DEVICE_CODE");
+ if (!StringUtils.hasText(deviceCode)) {
+ continue;
+ }
+ BigDecimal cavities = toBigDecimal(getMapValue(row, "CAVITIES"));
+ if (cavities.compareTo(BigDecimal.ZERO) > 0) {
+ targetMap.put(deviceCode, cavities);
+ }
+ }
+ }
+
+ private void loadCycleStatsCache(Map> targetMap,
+ List devices,
+ Date beginTime,
+ Date endTime,
+ List tableSuffixes) {
+ loadCycleStatsCache(targetMap, extractDeviceCodes(devices, false), beginTime, endTime,
+ tableSuffixes, false);
+ loadCycleStatsCache(targetMap, extractDeviceCodes(devices, true), beginTime, endTime,
+ tableSuffixes, true);
+ }
+
+ private void loadCycleStatsCache(Map> targetMap,
+ List deviceCodes,
+ Date beginTime,
+ Date endTime,
+ List tableSuffixes,
+ boolean oldDevice) {
+ if (CollectionUtils.isEmpty(deviceCodes)) {
+ return;
+ }
+ if (!oldDevice && CollectionUtils.isEmpty(tableSuffixes)) {
+ return;
+ }
+ List> rows = injectionOeeMapper.selectCycleTimeStatsBatch(
+ deviceCodes, beginTime, endTime, tableSuffixes, oldDevice);
+ if (CollectionUtils.isEmpty(rows)) {
+ return;
+ }
+ for (Map row : rows) {
+ String deviceCode = toMapString(row, "DEVICE_CODE");
+ if (StringUtils.hasText(deviceCode)) {
+ targetMap.put(deviceCode, row);
+ }
+ }
+ }
+
+ private List extractDeviceCodes(List devices, boolean oldDevice) {
+ List deviceCodes = new ArrayList<>();
+ for (InjectionOeeAnalysisVo device : devices) {
+ if (device == null || !StringUtils.hasText(device.getDEVICE_CODE())) {
+ continue;
+ }
+ if (isOldDeviceCode(device.getDEVICE_CODE()) == oldDevice) {
+ deviceCodes.add(device.getDEVICE_CODE());
+ }
+ }
+ return deviceCodes;
+ }
+
+ private Object getMapValue(Map map, String key) {
+ if (map == null || key == null) {
+ return null;
+ }
+ if (map.containsKey(key)) {
+ return map.get(key);
+ }
+ return map.get(key.toLowerCase());
+ }
+
+ private String toMapString(Map map, String key) {
+ Object value = getMapValue(map, key);
+ return value == null ? null : value.toString();
+ }
+
+ /**
+ * 在内存中过滤出指定窗口的前序 baseline 点和窗口数据,根据单调累加器 reset 规则计算增量。
+ * 此算法在逻辑上与原有 SQL 规约(selectCounterDeltaByWindow)完全等价。
+ */
+ private long calculateCounterDeltaInMemory(String deviceCode, MetricWindow window, List rawPoints) {
+ if (CollectionUtils.isEmpty(rawPoints)) {
+ return 0L;
+ }
+ boolean isOld = isOldDeviceCode(deviceCode);
+ int baselineHours = isOld ? OLD_BASELINE_HOURS : AUTO_BASELINE_HOURS;
+ Date baselineBeginTime = addHours(window.beginTime, baselineHours);
+
+ BigDecimal deltaSum = BigDecimal.ZERO;
+ BigDecimal prevValue = null;
+
+ for (ParamRawPoint point : rawPoints) {
+ Date collectTime = point.getCollectTime();
+ if (collectTime.before(baselineBeginTime)) {
+ continue;
+ }
+ if (!collectTime.before(window.endTime)) {
+ // rawPoints 已按采集时间升序排列,越过窗口后无需继续扫描。
+ break;
+ }
+
+ BigDecimal currValue = point.getParamValue();
+
+ if (collectTime.before(window.beginTime)) {
+ // 仅作为对比的基线,不累加增量本身
+ prevValue = currValue;
+ } else {
+ if (prevValue == null) {
+ // 没有 baseline,且这是窗口内第一个点,设为参考点,不计算增量(对应 SQL 中的 NULL)
+ prevValue = currValue;
+ } else {
+ if (currValue.compareTo(prevValue) > 0) {
+ deltaSum = deltaSum.add(currValue.subtract(prevValue));
+ } else if (currValue.compareTo(prevValue) < 0) {
+ // 计数器在两次采集中间发生了重置,直接加当前值
+ deltaSum = deltaSum.add(currValue);
+ }
+ prevValue = currValue;
+ }
+ }
+ }
+
+ return safeLong(deltaSum);
+ }
+
+ /**
+ * 计算指定窗口集合内参数的累计值和计划时间,并在内存中进行增量计算。
+ */
+ private CounterSummary calculateCounterSummary(String deviceCode, List windows, List rawPoints) {
long value = 0L;
long planSeconds = 0L;
for (MetricWindow window : windows) {
- value += selectCounterDelta(deviceCode, paramName, window);
+ value += calculateCounterDeltaInMemory(deviceCode, window, rawPoints);
planSeconds += window.planSeconds;
}
return new CounterSummary(value, planSeconds);
}
/**
- * 回源月分表按窗口 delta-sum 计算累加器增量。
- *
- * 为什么按窗口单独算:白班/夜班存在 07:00、19:00 业务边界,直接按自然日汇总会把夜班跨日数据切碎。
+ * 批量查询某个设备在指定窗口集下的所有去重原始点,只合并真正相邻/重叠的有效区间。
*/
- private long selectCounterDelta(String deviceCode, String paramName, MetricWindow window) {
- Date autoBaselineBeginTime = addHours(window.beginTime, AUTO_BASELINE_HOURS);
- Date oldBaselineBeginTime = addHours(window.beginTime, OLD_BASELINE_HOURS);
- List tableSuffixes = resolveTableSuffixes(autoBaselineBeginTime, window.endTime);
- BigDecimal delta = injectionOeeMapper.selectCounterDeltaByWindow(
- deviceCode,
- paramName,
- window.beginTime,
- window.endTime,
- autoBaselineBeginTime,
- oldBaselineBeginTime,
- tableSuffixes
- );
- return safeLong(delta);
+ private List selectRawPoints(String deviceCode, String paramName, List windows) {
+ if (CollectionUtils.isEmpty(windows)) {
+ return Collections.emptyList();
+ }
+ boolean oldDevice = isOldDeviceCode(deviceCode);
+ int baselineHours = oldDevice ? OLD_BASELINE_HOURS : AUTO_BASELINE_HOURS;
+
+ List queryRanges = buildMergedQueryRanges(windows, baselineHours);
+ if (CollectionUtils.isEmpty(queryRanges)) {
+ return Collections.emptyList();
+ }
+
+ List rawPoints = new ArrayList<>();
+ for (QueryRange queryRange : queryRanges) {
+ List tableSuffixes = resolveTableSuffixes(queryRange.beginTime, queryRange.endTime);
+ rawPoints.addAll(injectionOeeMapper.selectRawParamValues(
+ deviceCode,
+ paramName,
+ queryRange.beginTime,
+ queryRange.endTime,
+ tableSuffixes,
+ oldDevice
+ ));
+ }
+ rawPoints.sort(Comparator.comparing(ParamRawPoint::getCollectTime));
+ return rawPoints;
+ }
+
+ /**
+ * 将多个统计窗口扩展出基线区间后做合并,避免历史日期和本周/今日区间不连续时扫穿中间月份。
+ */
+ private List buildMergedQueryRanges(List windows, int baselineHours) {
+ List ranges = new ArrayList<>();
+ for (MetricWindow window : windows) {
+ ranges.add(new QueryRange(addHours(window.beginTime, baselineHours), window.endTime));
+ }
+ if (ranges.isEmpty()) {
+ return Collections.emptyList();
+ }
+
+ ranges.sort(Comparator.comparing(QueryRange::getBeginTime));
+ List mergedRanges = new ArrayList<>();
+ for (QueryRange range : ranges) {
+ if (mergedRanges.isEmpty()) {
+ mergedRanges.add(range);
+ continue;
+ }
+ QueryRange lastRange = mergedRanges.get(mergedRanges.size() - 1);
+ if (range.beginTime.after(lastRange.endTime)) {
+ mergedRanges.add(range);
+ } else if (range.endTime.after(lastRange.endTime)) {
+ lastRange.endTime = range.endTime;
+ }
+ }
+ return mergedRanges;
}
/**
* 使用新设备有效 OEE 结果的均值和中位数,为 OLD 设备缺失或无有效指标时临时补估算值。
*/
- private void fillMissingOldDeviceOeeByReference(List list) {
+ private void fillMissingOldDeviceOeeByReference(List list, String availabilityType) {
if (CollectionUtils.isEmpty(list)) {
return;
}
@@ -219,8 +496,6 @@ public class InjectionOeeServiceImpl implements IInjectionOeeService {
return;
}
- List weekAvailabilityReferences = buildReferenceRates(list, InjectionOeeAnalysisVo::getWEEK_AVAILABILITY);
- List todayAvailabilityReferences = buildReferenceRates(list, InjectionOeeAnalysisVo::getTODAY_AVAILABILITY);
List performanceReferences = buildReferenceRates(list, InjectionOeeAnalysisVo::getPERFORMANCE);
List shotReferences = buildReferenceLongValues(list, InjectionOeeAnalysisVo::getSHOTS);
List usedEstimatedShots = new ArrayList<>();
@@ -233,12 +508,10 @@ public class InjectionOeeServiceImpl implements IInjectionOeeService {
BigDecimal estimateRate = buildOldDeviceEstimateFactor(item, missingOldDeviceIndex);
BigDecimal availability = buildOldDeviceEstimateRate(availabilityReferences, estimateRate);
- BigDecimal weekAvailability = buildOldDeviceEstimateRate(weekAvailabilityReferences, estimateRate);
- BigDecimal todayAvailability = buildOldDeviceEstimateRate(todayAvailabilityReferences, estimateRate);
BigDecimal performance = buildOldDeviceEstimateRate(performanceReferences, estimateRate);
Long shots = buildOldDeviceEstimateLong(shotReferences, estimateRate, usedEstimatedShots, missingOldDeviceIndex);
- applyOldDeviceEstimate(item, availability, weekAvailability, todayAvailability, performance, shots);
+ applyOldDeviceEstimate(item, availabilityType, availability, performance, shots);
usedEstimatedShots.add(shots);
missingOldDeviceIndex++;
}
@@ -257,8 +530,69 @@ public class InjectionOeeServiceImpl implements IInjectionOeeService {
private boolean isOldDevice(InjectionOeeAnalysisVo item) {
return item != null
- && item.getDEVICE_CODE() != null
- && item.getDEVICE_CODE().trim().startsWith(OLD_DEVICE_CODE_PREFIX);
+ && isOldDeviceCode(item.getDEVICE_CODE());
+ }
+
+ private boolean isOldDeviceCode(String deviceCode) {
+ return deviceCode != null
+ && deviceCode.trim().startsWith(OLD_DEVICE_CODE_PREFIX);
+ }
+
+ private void applyAvailabilityTypeValue(InjectionOeeAnalysisVo vo, String availabilityType, BigDecimal availability) {
+ if (AVAILABILITY_WEEK.equals(availabilityType)) {
+ vo.setWEEK_AVAILABILITY(toDouble(availability));
+ return;
+ }
+ if (AVAILABILITY_TOTAL.equals(availabilityType)) {
+ vo.setTOTAL_AVAILABILITY(toDouble(availability));
+ return;
+ }
+ vo.setTODAY_AVAILABILITY(toDouble(availability));
+ }
+
+ private int compareByDeviceDisplayOrder(InjectionOeeAnalysisVo left, InjectionOeeAnalysisVo right) {
+ String leftCode = left == null ? null : left.getDEVICE_CODE();
+ String rightCode = right == null ? null : right.getDEVICE_CODE();
+ int priorityCompare = Integer.compare(resolveDeviceDisplayPriority(leftCode), resolveDeviceDisplayPriority(rightCode));
+ if (priorityCompare != 0) {
+ return priorityCompare;
+ }
+ int numberCompare = Integer.compare(resolveDeviceDisplayNumber(leftCode), resolveDeviceDisplayNumber(rightCode));
+ if (numberCompare != 0) {
+ return numberCompare;
+ }
+ return String.valueOf(leftCode).compareTo(String.valueOf(rightCode));
+ }
+
+ private int resolveDeviceDisplayPriority(String deviceCode) {
+ if (deviceCode == null) {
+ return 1;
+ }
+ String normalizedCode = deviceCode.trim();
+ if (normalizedCode.startsWith("YZM-")) {
+ return 0;
+ }
+ if (normalizedCode.startsWith(OLD_DEVICE_CODE_PREFIX)) {
+ return 2;
+ }
+ return 1;
+ }
+
+ private int resolveDeviceDisplayNumber(String deviceCode) {
+ if (deviceCode == null) {
+ return Integer.MAX_VALUE;
+ }
+ String normalizedCode = deviceCode.trim();
+ int dashIndex = normalizedCode.lastIndexOf('-');
+ if (dashIndex < 0 || dashIndex == normalizedCode.length() - 1) {
+ return Integer.MAX_VALUE;
+ }
+ try {
+ // 为什么按编号排序:现场设备编码 YZM-02 / OLD-02 比字典序更符合车间页面阅读习惯。
+ return Integer.parseInt(normalizedCode.substring(dashIndex + 1));
+ } catch (NumberFormatException e) {
+ return Integer.MAX_VALUE;
+ }
}
private List buildReferenceRates(List list, RateAccessor accessor) {
@@ -325,9 +659,8 @@ public class InjectionOeeServiceImpl implements IInjectionOeeService {
}
private void applyOldDeviceEstimate(InjectionOeeAnalysisVo item,
+ String availabilityType,
BigDecimal availability,
- BigDecimal weekAvailability,
- BigDecimal todayAvailability,
BigDecimal performance,
Long shots) {
BigDecimal quality = BigDecimal.valueOf(item.getQUALITY() == null ? 1D : item.getQUALITY().doubleValue());
@@ -338,9 +671,7 @@ public class InjectionOeeServiceImpl implements IInjectionOeeService {
BigDecimal cavities = BigDecimal.valueOf(item.getCAVITIES() == null ? 1L : item.getCAVITIES().longValue());
item.setAVAILABILITY(toDouble(availability));
- item.setTOTAL_AVAILABILITY(toDouble(availability));
- item.setWEEK_AVAILABILITY(toDouble(weekAvailability));
- item.setTODAY_AVAILABILITY(toDouble(todayAvailability));
+ applyAvailabilityTypeValue(item, availabilityType, availability);
item.setPERFORMANCE(toDouble(performance));
item.setDIAGNOSTIC_PERFORMANCE(toDouble(performance));
item.setOEE(toDouble(oee));
@@ -482,8 +813,8 @@ public class InjectionOeeServiceImpl implements IInjectionOeeService {
return new MetricWindow(begin.getTime(), end.getTime(), SHIFT_PLAN_SECONDS, shiftType);
}
- private BigDecimal resolveCavities(String deviceCode, Date endTime, List tableSuffixes) {
- BigDecimal cavities = injectionOeeMapper.selectLatestCavities(deviceCode, endTime, tableSuffixes);
+ private BigDecimal resolveCavities(String deviceCode, MetricCache metricCache) {
+ BigDecimal cavities = metricCache.cavitiesByDevice.get(deviceCode);
if (cavities == null || cavities.compareTo(BigDecimal.ZERO) <= 0) {
// 为什么默认 1:模腔数缺失时仍可计算开模数对应件数,但必须通过降级说明暴露给业务。
return BigDecimal.ONE;
@@ -491,34 +822,24 @@ public class InjectionOeeServiceImpl implements IInjectionOeeService {
return cavities;
}
- private CycleResult calculateStandardCycle(List cycleSamples) {
+ private CycleResult calculateStandardCycle(Map cycleStats) {
CycleResult result = new CycleResult();
- List validSamples = new ArrayList<>();
- if (cycleSamples != null) {
- for (BigDecimal sample : cycleSamples) {
- if (sample != null && sample.compareTo(BigDecimal.ZERO) > 0) {
- validSamples.add(sample);
- }
- }
- }
-
- if (validSamples.size() < 20) {
+ if (cycleStats == null || cycleStats.isEmpty()) {
result.cycleSeconds = BigDecimal.ZERO;
result.estimated = true;
result.source = "标准周期兜底";
return result;
}
- List secondSamples = new ArrayList<>();
- BigDecimal sum = BigDecimal.ZERO;
- BigDecimal microsecondDivisor = new BigDecimal("1000000");
- for (BigDecimal sample : validSamples) {
- BigDecimal second = sample.divide(microsecondDivisor, 6, RoundingMode.HALF_UP);
- secondSamples.add(second);
- sum = sum.add(second);
+ int sampleCount = toBigDecimal(cycleStats.get("SAMPLE_COUNT")).intValue();
+ if (sampleCount < MIN_CYCLE_SAMPLE_COUNT) {
+ result.cycleSeconds = BigDecimal.ZERO;
+ result.estimated = true;
+ result.source = "标准周期兜底";
+ return result;
}
- BigDecimal mean = sum.divide(BigDecimal.valueOf(secondSamples.size()), 6, RoundingMode.HALF_UP);
+ BigDecimal mean = toBigDecimal(cycleStats.get("AVG_SECONDS"));
if (mean.compareTo(BigDecimal.ZERO) <= 0) {
result.cycleSeconds = BigDecimal.ZERO;
result.estimated = true;
@@ -526,32 +847,17 @@ public class InjectionOeeServiceImpl implements IInjectionOeeService {
return result;
}
- BigDecimal varianceSum = BigDecimal.ZERO;
- for (BigDecimal second : secondSamples) {
- BigDecimal diff = second.subtract(mean);
- varianceSum = varianceSum.add(diff.multiply(diff));
- }
- BigDecimal variance = varianceSum.divide(BigDecimal.valueOf(secondSamples.size() - 1), 6, RoundingMode.HALF_UP);
- double cv = Math.sqrt(variance.doubleValue()) / mean.doubleValue();
- if (cv >= 0.3) {
+ BigDecimal median = toBigDecimal(cycleStats.get("MEDIAN_SECONDS"));
+ if (median.compareTo(BigDecimal.ZERO) <= 0) {
result.cycleSeconds = BigDecimal.ZERO;
result.estimated = true;
result.source = "标准周期兜底";
return result;
}
- secondSamples.sort(Comparator.naturalOrder());
- int count = secondSamples.size();
- BigDecimal median;
- if (count % 2 == 1) {
- median = secondSamples.get(count / 2);
- } else {
- median = secondSamples.get(count / 2 - 1).add(secondSamples.get(count / 2))
- .divide(new BigDecimal("2"), 6, RoundingMode.HALF_UP);
- }
-
- result.cycleSeconds = median;
+ result.cycleSeconds = median.setScale(6, RoundingMode.HALF_UP);
result.estimated = false;
+ // 为什么不用离散系数直接否决:注塑换模、空循环会拉高波动,中位数已经承担抗极端值职责。
result.source = "设备采集周期中位数";
return result;
}
@@ -649,6 +955,16 @@ public class InjectionOeeServiceImpl implements IInjectionOeeService {
return SHIFT_ALL;
}
+ private String normalizeAvailabilityType(String availabilityType) {
+ if (AVAILABILITY_WEEK.equalsIgnoreCase(availabilityType)) {
+ return AVAILABILITY_WEEK;
+ }
+ if (AVAILABILITY_TOTAL.equalsIgnoreCase(availabilityType)) {
+ return AVAILABILITY_TOTAL;
+ }
+ return AVAILABILITY_DAY;
+ }
+
private String resolveShiftName(String shiftType) {
if (SHIFT_DAY.equals(shiftType)) {
return "白班";
@@ -659,23 +975,60 @@ public class InjectionOeeServiceImpl implements IInjectionOeeService {
return "全部班次";
}
- private DateRange resolveTotalRange(String beginTimeStr, String endTimeStr, SimpleDateFormat sdf, Date todayDate) {
- Date beginDate = parseDay(beginTimeStr, sdf, todayDate);
- Date endDate = parseDay(endTimeStr, sdf, todayDate);
+ private DateRange resolveSelectedRange(String beginTimeStr,
+ String endTimeStr,
+ SimpleDateFormat sdf,
+ Date defaultDate,
+ Date maxDate) {
+ Date beginDate = parseDay(beginTimeStr, sdf, defaultDate);
+ Date endDate = parseDay(endTimeStr, sdf, defaultDate);
if (beginDate.after(endDate)) {
Date tmp = beginDate;
beginDate = endDate;
endDate = tmp;
}
- if (beginDate.after(todayDate)) {
- return new DateRange(todayDate, todayDate);
+ if (beginDate.after(maxDate)) {
+ return new DateRange(maxDate, maxDate);
}
- if (endDate.after(todayDate)) {
- endDate = todayDate;
+ if (endDate.after(maxDate)) {
+ endDate = maxDate;
}
return new DateRange(beginDate, endDate);
}
+ private DateRange resolveAvailabilityRange(DateRange selectedRange, String availabilityType, Date maxDate) {
+ if (AVAILABILITY_WEEK.equals(availabilityType)) {
+ Date weekBeginDate = beginOfNaturalWeek(selectedRange.endDate);
+ Date weekEndDate = endOfNaturalWeek(selectedRange.endDate);
+ if (weekEndDate.after(maxDate)) {
+ // 为什么截到当前日期:自然周未结束时,不能把未来班次计入计划时间分母。
+ weekEndDate = maxDate;
+ }
+ return new DateRange(weekBeginDate, weekEndDate);
+ }
+ if (AVAILABILITY_TOTAL.equals(availabilityType)) {
+ return selectedRange;
+ }
+ // 为什么用页面结束日:日利用率是用户选择日期的生产日,不再固定取服务器“今天”。
+ return new DateRange(selectedRange.endDate, selectedRange.endDate);
+ }
+
+ private Date beginOfNaturalWeek(Date date) {
+ Calendar cal = Calendar.getInstance();
+ cal.setTime(truncateToDay(date));
+ int dayOfWeek = cal.get(Calendar.DAY_OF_WEEK);
+ int mondayOffset = (dayOfWeek + 5) % 7;
+ cal.add(Calendar.DATE, -mondayOffset);
+ return cal.getTime();
+ }
+
+ private Date endOfNaturalWeek(Date date) {
+ Calendar cal = Calendar.getInstance();
+ cal.setTime(beginOfNaturalWeek(date));
+ cal.add(Calendar.DATE, 6);
+ return cal.getTime();
+ }
+
private Date parseDay(String value, SimpleDateFormat sdf, Date defaultDate) {
if (!StringUtils.hasText(value)) {
return defaultDate;
@@ -802,6 +1155,45 @@ public class InjectionOeeServiceImpl implements IInjectionOeeService {
}
}
+ private static class QueryRange {
+ private final Date beginTime;
+ private Date endTime;
+
+ private QueryRange(Date beginTime, Date endTime) {
+ this.beginTime = beginTime;
+ this.endTime = endTime;
+ }
+
+ private Date getBeginTime() {
+ return beginTime;
+ }
+ }
+
+ private static class MetricCache {
+ private final Map> runRawPointsByDevice = new HashMap<>();
+ private final Map> shotRawPointsByDevice = new HashMap<>();
+ private final Map cavitiesByDevice = new HashMap<>();
+ private final Map> cycleStatsByDevice = new HashMap<>();
+
+ private List getRunRawPoints(String deviceCode) {
+ return getRawPoints(runRawPointsByDevice, deviceCode);
+ }
+
+ private List getShotRawPoints(String deviceCode) {
+ return getRawPoints(shotRawPointsByDevice, deviceCode);
+ }
+
+ private List getRawPoints(Map> rawPointsByDevice,
+ String deviceCode) {
+ List rawPoints = rawPointsByDevice.get(deviceCode);
+ return rawPoints == null ? Collections.emptyList() : rawPoints;
+ }
+
+ private Map getCycleStats(String deviceCode) {
+ return cycleStatsByDevice.get(deviceCode);
+ }
+ }
+
private static class CycleResult {
private BigDecimal cycleSeconds = BigDecimal.ZERO;
private boolean estimated = true;
diff --git a/aucma-report/src/main/resources/mapper/report/InjectionOeeMapper.xml b/aucma-report/src/main/resources/mapper/report/InjectionOeeMapper.xml
index 598dcce..f5b63d2 100644
--- a/aucma-report/src/main/resources/mapper/report/InjectionOeeMapper.xml
+++ b/aucma-report/src/main/resources/mapper/report/InjectionOeeMapper.xml
@@ -4,6 +4,12 @@
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+