@@ -1,25 +1,34 @@
package com.klp.pocket.acid.service.impl ;
import com.baomidou.dynamic.datasource.annotation.DS ;
import com.klp.common.utils.StringUtils ;
import com.klp.pocket.acid.domain.vo.AcidOeeCoilInfoByDateVo ;
import com.klp.pocket.acid.domain.vo.AcidOeeDailySummaryVo ;
import com.klp.pocket.acid.domain.vo.AcidOeeIdealCycleVo ;
import com.klp.pocket.acid.domain.vo.AcidOeeLoss7Vo ;
import com.klp.pocket.acid.domain.vo.Klptcm1ProStoppageVo ;
import com.klp.pocket.acid.domain.bo.Klptcm1ProStoppageBo ;
import com.klp.pocket.acid.mapper.AcidOeeMapper ;
import com.klp.pocket.acid.mapper.AcidOeeMasterMa pper ;
import com.klp.pocket.acid.service.IAcidOeeService ;
import com.klp.pocket.acid.service.IKlptcm1ProStoppageService ;
import com.klp.pocket.common.service.ICoilQualityJudgeService ;
import lombok.RequiredArgsConstructor ;
import lombok.extern.slf4j.Slf4j ;
import org.springframework.beans.factory.annotation.Value ;
import org.springframework.stereotype.Service ;
import java.math.BigDecimal ;
import java.math.RoundingMode ;
import java.text.SimpleDateFormat ;
import java.util.* ;
import java.util.ArrayList ;
import java.util.Arrays ;
import java.util.Calendar ;
import java.util.Collections ;
import java.util.Comparator ;
import java.util.Date ;
import java.util.HashMap ;
import java.util.HashSet ;
import java.util.List ;
import java.util.Map ;
import java.util.Set ;
/**
* 酸轧线OEE Service实现类
@@ -29,53 +38,65 @@ import java.util.Calendar;
*/
@Slf4j
@RequiredArgsConstructor
@DS ( " acid " )
@Service
public class AcidOeeServiceImpl implements IAcidOeeService {
/** 酸轧成品库库区ID */
private static final Long ACID_FINISHED_WAREHOUSE_ID = 1988150099140866050L ;
/** 固定理论节拍( min/吨) */
private static final BigDecimal FIXED_IDEAL_CYCLE = BigDecimal . valueOf ( 0 . 47 ) ;
private final AcidOeeMapper acidOeeMapper ;
/** 次品判级集合:命中这些 quality_status 判为次品 */
private static final Set < String > SCRAP_QUALITY_STATUS = new HashSet < > (
Arrays . asList ( " C+ " , " C " , " C- " , " D+ " , " D " , " D- " )
) ;
private final AcidOeeMasterMapper acidOeeMasterMapper ;
private final IKlptcm1ProStoppageService stoppageService ;
private final ICoilQualityJudgeService coilQualityJudgeService ;
@Value ( " ${oee.acid.coil-create-by} " )
private String acidCreateName ;
@Override
public List < AcidOeeDailySummaryVo > getDailySummary ( String startDate , String endDate ) {
// 1. 查询基础日汇总(产量、停机时间等)
List < AcidOeeDailySummaryVo > summaries = acidOeeMapper . selectDailySummary ( startDate , endDate ) ;
List < AcidOeeDailySummaryVo > summaries = acidOeeMasterMa pper . selectDailySummary (
startDate ,
endDate ,
acidCreateName
) ;
if ( summaries = = null | | summaries . isEmpty ( ) ) {
return Collections . emptyList ( ) ;
}
// 2. 查询停机事件,按日期聚合停机时间
Map < String , Long > downtimeByDate = aggregateDowntimeByDate ( startDate , endDate ) ;
// 3. 查询产量明细,用于良品/次品判定
Map < String , List < CoilInfo > > coilInfoByDate = getCoilNosByDate ( startDate , endDate ) ;
// 4. 理论节拍: 使用固定值0.47
BigDecimal ideal CycleTon = FIXED_IDEAL_CYCLE ;
// 5. 填充每个日汇总的完整数据
// 4. 先按天计算 dailyCycle = runTime/totalOutputTon, 再取中位数作为 理论节拍
List < BigDecimal> daily Cycles = new ArrayList < > ( ) ;
for ( AcidOeeDailySummaryVo summary : summaries ) {
String statDate = summary . getStatDate ( ) ;
summary . setLineId ( " SY " ) ;
summary . setLineName ( " 酸轧线 " ) ;
// 填充停机时间
Long downtime = downtimeByDate . getOrDefault ( statDate , 0L ) ;
summary . setDowntimeMin ( downtime ) ;
// 计算运转时间
Long loadingTime = summary . getLoadingTimeMin ( ) ! = null ? summary . getLoadingTimeMin ( ) : 0L ;
Long runTime = Math . max ( 0 , loadingTime - downtime ) ;
summary . setRunTimeMin ( runTime ) ;
// 理论节拍:若尚未填充,则统一使用“优良日统计”得到的节拍
if ( summary . getIdealCycleTimeMinPerTon ( ) = = null & & idealCycleTon ! = null ) {
BigDecimal totalOutputTon = summary . getTotalOutputTon ( ) ;
if ( runTime > 0 & & totalOutputTon ! = null & & totalOutputTon . compareTo ( BigDecimal . ZERO ) > 0 ) {
BigDecimal dailyCycle = BigDecimal . valueOf ( runTime )
. divide ( totalOutputTon , 6 , RoundingMode . HALF_UP ) ;
dailyCycles . add ( dailyCycle ) ;
}
}
dailyCycles . sort ( BigDecimal : : compareTo ) ;
BigDecimal idealCycleTon = applyEightyPercent ( median ( dailyCycles ) ) ;
// 5. 回填理论节拍、良品次品并计算派生指标
for ( AcidOeeDailySummaryVo summary : summaries ) {
String statDate = summary . getStatDate ( ) ;
if ( idealCycleTon ! = null ) {
summary . setIdealCycleTimeMinPerTon ( idealCycleTon ) ;
}
@@ -91,11 +112,6 @@ public class AcidOeeServiceImpl implements IAcidOeeService {
summary . setDefectOutputCoil ( 0L ) ;
}
// 填充理论节拍(从回归数据或缓存获取,这里暂时留空,由调用方填充)
// summary.setIdealCycleTimeMinPerTon(...);
// summary.setIdealCycleTimeMinPerCoil(...);
// 计算派生指标
calculateDerivedMetrics ( summary ) ;
}
@@ -127,7 +143,11 @@ public class AcidOeeServiceImpl implements IAcidOeeService {
@Override
public AcidOeeIdealCycleVo getIdealCycle ( String startDate , String endDate ) {
// 1) 取基础日汇总(产量、负荷时间等)
List < AcidOeeDailySummaryVo > daily = acidOeeMapper . selectDailySummary ( startDate , endDate ) ;
List < AcidOeeDailySummaryVo > daily = acidOeeMasterMa pper . selectDailySummary (
startDate ,
endDate ,
acidCreateName
) ;
AcidOeeIdealCycleVo rsp = new AcidOeeIdealCycleVo ( ) ;
rsp . setLineId ( " SY " ) ;
rsp . setLineName ( " 酸轧线 " ) ;
@@ -151,23 +171,31 @@ public class AcidOeeServiceImpl implements IAcidOeeService {
d . setRunTimeMin ( Math . max ( 0 , loading - downtime ) ) ;
}
// 3) 卷级节拍 = (END_DATE - START_DATE)/出口重量,计算 中位数( 用于展示, 不用于OEE计算)
List < BigDecimal > co ilCycles = acidOeeMapper . selectCoilCycleMinPerTon ( startDate , endDate ) ;
coilCycles . removeIf ( c - > c = = null | | c . compareTo ( BigDecimal . ZERO ) < = 0 ) ;
coilCycles . sort ( BigDecimal : : compareTo ) ;
BigDecimal medianCycle = median ( coilCycles ) ;
// 理论节拍使用固定值0.47( 用于OEE计算)
rsp . setIdealCycleTimeMinPerTon ( FIXED_IDEAL_CYCLE ) ;
// 中位数理论节拍(用于展示)
// 3) 理论节拍按“天维度”:每天(运转时间/总吨),再按日样本统计 中位数用于展示
List < BigDecimal > da ily Cycles = new ArrayList < > ( ) ;
for ( AcidOeeDailySummaryVo d : daily ) {
Long run = d . getRunTimeMin ( ) ;
BigDecimal ton = d . getTotalOutputTon ( ) ;
if ( run = = null | | run < = 0 | | ton = = null | | ton . compareTo ( BigDecimal . ZERO ) < = 0 ) {
continue ;
}
dailyCycles . add ( BigDecimal . valueOf ( run ) . divide ( ton , 6 , RoundingMode . HALF_UP ) ) ;
}
dailyCycles . sort ( BigDecimal : : compareTo ) ;
BigDecimal medianCycle = median ( dailyCycles ) ;
// 理论节拍:按“当天运转时间/当天总吨”逐日计算, 接口返回前乘以80%
BigDecimal idealCycle = applyEightyPercent ( medianCycle ) ;
rsp . setIdealCycleTimeMinPerTon ( idealCycle ) ;
// 展示字段保持为中位数
rsp . setMedianCycleTimeMinPerTon ( medianCycle ) ;
// 样本天数:当前查询区间内有产量的自然日数量(与传入的日期范围一一对应)
rsp . setSampleDays ( daily . size ( ) ) ;
// 4) 日粒度对比数据:理论耗时 vs 实际运转时间(用于前端展示"有效性")
// 使用固定值0.47 计算理论耗时
// 使用“中间50%样本平均理论节拍” 计算理论耗时
List < AcidOeeIdealCycleVo . DailyComparePointVo > compare = new ArrayList < > ( ) ;
if ( FIXED_IDEAL_CYCLE ! = null ) {
if ( idealCycle ! = null ) {
for ( AcidOeeDailySummaryVo d : daily ) {
BigDecimal ton = d . getTotalOutputTon ( ) ;
Long run = d . getRunTimeMin ( ) ;
@@ -175,7 +203,7 @@ public class AcidOeeServiceImpl implements IAcidOeeService {
AcidOeeIdealCycleVo . DailyComparePointVo p = new AcidOeeIdealCycleVo . DailyComparePointVo ( ) ;
p . setStatDate ( d . getStatDate ( ) ) ;
p . setActualRunTimeMin ( run ) ;
p . setTheoreticalTimeMin ( FIXED_IDEAL_CYCLE . multiply ( ton ) ) ;
p . setTheoreticalTimeMin ( idealCycle . multiply ( ton ) ) ;
compare . add ( p ) ;
}
}
@@ -270,25 +298,25 @@ public class AcidOeeServiceImpl implements IAcidOeeService {
SimpleDateFormat dateFormat = new SimpleDateFormat ( " yyyy-MM-dd " ) ;
Calendar cal = Calendar . getInstance ( ) ;
for ( Klptcm1ProStoppageVo event : events ) {
if ( event . getStartDate ( ) = = null | | event . getDuration ( ) = = null | | event . getDuration ( ) < = 0 ) {
continue ;
}
Date eventStart = event . getStartDate ( ) ;
long durationSec = event . getDuration ( ) ;
long durationMin = ( durationSec + 59 ) / 60 ; // 向上取整,避免丢失秒数
// 计算停机结束时间
cal . setTime ( eventStart ) ;
cal . add ( Calendar . SECOND , ( int ) durationSec ) ;
Date eventEnd = cal . getTime ( ) ;
// 如果停机事件在同一天,直接累加
String startDateStr = dateFormat . format ( eventStart ) ;
String endDateStr = dateFormat . format ( eventEnd ) ;
if ( startDateStr . equals ( endDateStr ) ) {
// 同一天,直接累加
downtimeMap . merge ( startDateStr , durationMin , Long : : sum ) ;
@@ -300,23 +328,23 @@ public class AcidOeeServiceImpl implements IAcidOeeService {
cal . set ( Calendar . SECOND , 0 ) ;
cal . set ( Calendar . MILLISECOND , 0 ) ;
Date dayStart = cal . getTime ( ) ;
Date currentDayStart = dayStart ;
while ( currentDayStart . before ( eventEnd ) ) {
cal . setTime ( currentDayStart ) ;
cal . add ( Calendar . DAY_OF_MONTH , 1 ) ;
Date nextDayStart = cal . getTime ( ) ;
// 计算当前天的停机分钟数
Date dayEnd = nextDayStart . before ( eventEnd ) ? nextDayStart : eventEnd ;
long dayMinutes = Math . max ( 0 , ( dayEnd . getTime ( ) - Math . max ( currentDayStart . getTime ( ) , eventStart . getTime ( ) ) ) / ( 1000 * 60 ) ) ;
if ( dayMinutes > 0 ) {
String dateKey = dateFormat . format ( currentDayStart ) ;
downtimeMap . merge ( dateKey , dayMinutes , Long : : sum ) ;
}
currentDayStart = nextDayStart ;
}
}
@@ -329,13 +357,18 @@ public class AcidOeeServiceImpl implements IAcidOeeService {
* 获取每日的钢卷号和重量(用于良品/次品判定)
*/
private Map < String , List < CoilInfo > > getCoilNosByDate ( String startDate , String endDate ) {
List < AcidOeeMapper . CoilInfoByDate > coilInfoList = acidOeeMapper . selectCoilInfoByDate ( startDate , endDate ) ;
List < AcidOeeCoilInfoByDateVo > coilInfoList = acidOeeMasterMapper . selectCoilInfoByDate (
startDate ,
endDate ,
acidCreateName
) ;
Map < String , List < CoilInfo > > result = new HashMap < > ( ) ;
for ( AcidOeeMapper . CoilInfoByDate info : coilInfoList ) {
for ( AcidOeeCoilInfoByDateVo info : coilInfoList ) {
String date = info . getStatDate ( ) ;
result . computeIfAbsent ( date , k - > new ArrayList < > ( ) )
. add ( new CoilInfo ( info . getCoilNo ( ) , info . getWeight ( ) ) ) ;
. add ( new CoilInfo ( info . getCoilNo ( ) , info . getWeight ( ) , info . getQualityStatus ( ) )) ;
}
return result ;
@@ -347,10 +380,12 @@ public class AcidOeeServiceImpl implements IAcidOeeService {
private static class CoilInfo {
final String coilNo ;
final BigDecimal weight ;
final String qualityStatus ;
CoilInfo ( String coilNo , BigDecimal weight ) {
CoilInfo ( String coilNo , BigDecimal weight , String qualityStatus ) {
this . coilNo = coilNo ;
this . weight = weight ;
this . qualityStatus = qualityStatus ;
}
}
@@ -362,19 +397,18 @@ public class AcidOeeServiceImpl implements IAcidOeeService {
long goodCoil = 0L ;
BigDecimal defectTon = BigDecimal . ZERO ;
long defectCoil = 0L ;
for ( CoilInfo coilInfo : coilInfos ) {
String coilNo = coilInfo . coilNo ;
BigDecimal coilWeight = coilInfo . weight ! = null ? coilInfo . weight : BigDecimal . ZERO ;
String qualityStatus = StringUtils . trim ( coilInfo . qualityStatus ) ;
// 通过WMS判定良品/次品
Boolean isScrap = coilQualityJudgeService . isScrap ( ACID_FINISHED_WAREHOUSE_ID , coilNo ) ;
if ( isScrap = = null ) {
// 匹配不到,忽略不计
// 没有判级时按良品处理(避免再次跨库匹配导致错配)
if ( StringUtils . isBlank ( qualityStatus ) ) {
goodTon = goodTon . add ( coilWeight ) ;
goodCoil + + ;
continue ;
}
if ( Boolean . TRUE . equals ( isScrap ) ) {
if ( SCRAP_QUALITY_STATUS . contains ( qualityStatus ) ) {
// 次品
defectTon = defectTon . add ( coilWeight ) ;
defectCoil + + ;
@@ -406,6 +440,7 @@ public class AcidOeeServiceImpl implements IAcidOeeService {
}
// 性能稼动率(吨维度)
// 口径:理论节拍单位为 min/吨 时,性能稼动率 = (理论节拍 × 实际产量) / 实际运转时间 × 100
Long runTime = summary . getRunTimeMin ( ) ! = null ? summary . getRunTimeMin ( ) : 0L ;
BigDecimal idealCycleTon = summary . getIdealCycleTimeMinPerTon ( ) ;
BigDecimal totalOutputTon = summary . getTotalOutputTon ( ) ;
@@ -415,12 +450,14 @@ public class AcidOeeServiceImpl implements IAcidOeeService {
& & totalOutputTon ! = null
& & totalOutputTon . compareTo ( BigDecimal . ZERO ) > 0 ) {
BigDecimal idealTime = idealCycleTon . multiply ( totalOutputTon ) ;
BigDecimal performanceTon = idealTime . divide ( BigDecimal . valueOf ( runTime ) , 4 , RoundingMode . HALF_UP )
BigDecimal performanceTon = idealTime
. divide ( BigDecimal . valueOf ( runTime ) , 4 , RoundingMode . HALF_UP )
. multiply ( BigDecimal . valueOf ( 100 ) ) ;
summary . setPerformanceTon ( performanceTon ) ;
}
// 性能稼动率(卷维度)
// 口径:理论节拍单位为 min/卷 时,性能稼动率 = (理论节拍 × 实际产量) / 实际运转时间 × 100
Long totalOutputCoil = summary . getTotalOutputCoil ( ) ! = null ? summary . getTotalOutputCoil ( ) : 0L ;
BigDecimal idealCycleCoil = summary . getIdealCycleTimeMinPerCoil ( ) ;
if ( runTime > 0
@@ -428,7 +465,8 @@ public class AcidOeeServiceImpl implements IAcidOeeService {
& & idealCycleCoil . compareTo ( BigDecimal . ZERO ) > 0
& & totalOutputCoil > 0 ) {
BigDecimal idealTime = idealCycleCoil . multiply ( BigDecimal . valueOf ( totalOutputCoil ) ) ;
BigDecimal performanceCoil = idealTime . divide ( BigDecimal . valueOf ( runTime ) , 4 , RoundingMode . HALF_UP )
BigDecimal performanceCoil = idealTime
. divide ( BigDecimal . valueOf ( runTime ) , 4 , RoundingMode . HALF_UP )
. multiply ( BigDecimal . valueOf ( 100 ) ) ;
summary . setPerformanceCoil ( performanceCoil ) ;
}
@@ -463,6 +501,15 @@ public class AcidOeeServiceImpl implements IAcidOeeService {
return a . add ( b ) . divide ( BigDecimal . valueOf ( 2 ) , 6 , RoundingMode . HALF_UP ) ;
}
/** 理论节拍返回前按业务口径乘以70% */
private BigDecimal applyEightyPercent ( BigDecimal cycle ) {
if ( cycle = = null ) {
return null ;
}
return cycle . multiply ( BigDecimal . valueOf ( 0 . 7 ) ) . setScale ( 6 , RoundingMode . HALF_UP ) ;
}
/**
* 解析日期字符串为Date对象
*/