feiyu02
2025-08-05 176d7d8283e66ccf63878c9ab823e900df94b748
2025.8.5
1. 动态溯源模块添加延迟数据周期异常合并功能
已修改15个文件
已添加2个文件
526 ■■■■ 文件已修改
src/main/kotlin/com/flightfeather/uav/biz/dataanalysis/BaseAnalysisConfig.kt 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/kotlin/com/flightfeather/uav/biz/dataanalysis/BaseExceptionContinuous.kt 175 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/kotlin/com/flightfeather/uav/biz/dataanalysis/exceptiontype/ExceptionDataExceed.kt 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/kotlin/com/flightfeather/uav/biz/dataanalysis/exceptiontype/ExceptionValueMutation.kt 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/kotlin/com/flightfeather/uav/biz/dataanalysis/model/ExceptionTag.kt 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/config/RTExcWindLevelConfig.kt 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/exceptiontype/BaseRTExcChangeRate.kt 24 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/exceptiontype/BaseRTExcWindLevel.kt 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/exceptiontype/RealTimeExceptionValueMutation.kt 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/model/PollutedArea.kt 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/model/PollutedClue.kt 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/model/PollutedData.kt 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/model/RemainException.kt 62 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/kotlin/com/flightfeather/uav/common/exception/BizException.kt 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/kotlin/com/flightfeather/uav/common/net/AMapService.kt 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/kotlin/com/flightfeather/uav/domain/entity/BaseRealTimeData.kt 7 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/test/kotlin/com/flightfeather/uav/biz/dataanalysis/BaseExceptionContinuousTest.kt 156 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/kotlin/com/flightfeather/uav/biz/dataanalysis/BaseAnalysisConfig.kt
@@ -1,7 +1,6 @@
package com.flightfeather.uav.biz.dataanalysis
import com.flightfeather.uav.biz.FactorFilter
import com.flightfeather.uav.socket.eunm.FactorType
/**
 * æ•°æ®åˆ†æžé…ç½®å‚数基类
@@ -11,4 +10,7 @@
abstract class BaseAnalysisConfig(
    // å› å­ç­›é€‰
    val factorFilter: FactorFilter,
)
){
    // æ•°æ®å¼‚常合并时,允许延迟最大数据周期
    var maxDelayPeriod: Int = 1
}
src/main/kotlin/com/flightfeather/uav/biz/dataanalysis/BaseExceptionContinuous.kt
@@ -2,10 +2,10 @@
import com.flightfeather.uav.biz.FactorFilter
import com.flightfeather.uav.biz.dataanalysis.model.ExceptionTag
import com.flightfeather.uav.biz.sourcetrace.model.RemainException
import com.flightfeather.uav.domain.entity.BaseRealTimeData
import com.flightfeather.uav.lightshare.eunm.ExceptionStatusType
import com.flightfeather.uav.socket.eunm.FactorType
import org.springframework.beans.BeanUtils
import java.time.Duration
/**
@@ -34,17 +34,20 @@
    protected var lastData: BaseRealTimeData? = null
    // æœ€æ–°çš„一组异常,记录单因子异常
    protected val latestExceptions = mutableListOf<Pair<FactorFilter.SelectedFactor, T>>()
    val latestExceptions = mutableListOf<Pair<FactorFilter.SelectedFactor, T>>()
    /**
     * æœ€æ–°çš„一组合并异常,根据配置参数从[latestExceptions]单因子异常中,合并异常
     */
    protected val latestCombinedExc = mutableListOf<List<Pair<FactorFilter.SelectedFactor, T>>>()
    // è®°å½•需要延迟数据周期进行合并的异常
    val remainingExceptions = mutableListOf<RemainException<T>>()
    /**
     * å¼‚常结果
     */
    protected val result = mutableListOf<Y>()
    val result = mutableListOf<Y>()
    /**
     * ä¸é€‚用于此异常类型的监测因子
@@ -150,7 +153,10 @@
        }
        lastData = data
        mergeExceptionResult()
        removeSingleFactor(data)
        checkDelayedExceptions(data)
        mergeExceptionResult(data)
        onNewResult(result)
        clearExceptions(data)
    }
@@ -277,16 +283,141 @@
    }
    /**
     * å°†ä¸åœ¨å…³è”关系中的监测因子异常存储,并剔除
     */
    fun removeSingleFactor(data: BaseRealTimeData) {
        // æŸ¥æ‰¾ä¸åœ¨å› å­å…³è”组合中的异常因子
        val sfList = latestExceptions.filter {
            config.factorFilter.combination.find { c -> c.find { f -> f == it.first.main } != null } == null
        }
        // ç”Ÿæˆå¯¹åº”的异常结果,并初始化该异常
        sfList.forEach {
            result.add(newResult(listOf(it)))
            it.second.refreshWithNextException(data)
        }
        // å‰”除
        latestExceptions.removeAll(sfList)
    }
    /**
     * æ£€æŸ¥å»¶è¿Ÿçš„待合并异常与当前异常是否能匹配
     * 1. å°†é—留的超过等待数据周期的异常存储
     * 2. å°†åŒ¹é…æˆåŠŸçš„åˆå¹¶å¼‚å¸¸å­˜å‚¨
     * 3. ä¿ç•™ä¾æ—§æœªåˆå¹¶æˆåŠŸå¹¶ä¸”å¯ç»§ç»­ç­‰å¾…çš„å¼‚å¸¸
     * @return è¢«åŒ¹é…æˆåŠŸçš„å…³è”å…³ç³»
     */
    fun checkDelayedExceptions(data: BaseRealTimeData): List<List<FactorType>> {
        // è¢«åŒ¹é…æˆåŠŸçš„ç›‘æµ‹å› å­å…³è”å…³ç³»
        val fittedComb = mutableListOf<List<FactorType>>()
        // é—留的进入下一个数据周期做判断的待合并异常集合
        val leftExc = mutableListOf<RemainException<T>>()
        // æˆåŠŸåŒ¹é…çš„åˆå¹¶å¼‚å¸¸é›†åˆ
        val combinedExc = mutableListOf<List<Pair<FactorFilter.SelectedFactor, T>>>()
        // æœ¬æ¬¡æ•°æ®å‘¨æœŸä¸­ï¼Œè¢«åŒ¹é…æˆåŠŸçš„å¼‚å¸¸é›†åˆ
        val exceps = mutableListOf<Pair<FactorFilter.SelectedFactor, T>>()
        remainingExceptions.forEach {
            // æ£€æŸ¥å½“前新异常中,是否包含因子关联关系中的异常
            val combRes = matchCombFactor(it.combination, latestExceptions)
            val res = combRes.second
            // åˆ¤æ–­æœ¬æ¬¡æ•°æ®å‘¨æœŸä¸­æ‰¾åˆ°çš„因子和已有的因子是否满足关联关系
            val findFactors = mutableListOf<FactorType>()
            res.forEach {r -> findFactors.add(r.first.main) }
            it.exceptions.forEach {r -> findFactors.add(r.first.main) }
            // åˆ¤æ–­æ˜¯å¦è¿˜æœ‰ç¼ºå¤±å¼‚常
            val isFitAll = findFactors.distinct() == it.combination
            // å¦‚果已经没有缺失的异常因子,则可合并为组合异常
            if (isFitAll) {
                fittedComb.add(it.combination)
                // å°†æŸ¥æ‰¾ç»“果添加至已有异常集合中
                it.addExceptions(res)
//                // è®°å½•被匹配成功的异常
//                res.forEach { r->
//                    if (exceps.find { e -> e.second == r.second } == null) {
//                        exceps.add(r)
//                    }
//                }
                // å°†åˆå¹¶å¼‚常存储
                combinedExc.add(it.exceptions)
            }
            // å¦åˆ™ç•™ä½œä¸‹æ¬¡æ•°æ®å‘¨æœŸå†åˆ¤å®šå­˜å…¥å¾…合并异常集合
            else {
                it.period++
                // å½“待合并的异常等待数据周期大于设定值时,不再等待,直接输出异常
                if (it.period > config.maxDelayPeriod) {
                    result.add(newResult(it.exceptions))
                    return@forEach
                } else {
                    fittedComb.add(it.combination)
                    // å°†æŸ¥æ‰¾ç»“果添加至已有异常集合中
                    it.addExceptions(res)
//                    // è®°å½•被匹配成功的异常
//                    res.forEach { r->
//                        if (exceps.find { e -> e.second == r.second } == null) {
//                            exceps.add(r)
//                        }
//                    }
                    leftExc.add(it)
                }
            }
        }
        // å­˜å‚¨åˆå¹¶å¼‚常
        combinedExc.forEach {
            result.add(newResult(it))
        }
//        // å°†è¢«åŒ¹é…æˆåŠŸçš„å¼‚å¸¸åˆ·æ–°ï¼Œå¹¶ä»Žæœ¬æ¬¡æ•°æ®å‘¨æœŸçš„å¼‚å¸¸é›†åˆä¸­ç§»é™¤
//        exceps.forEach { r-> r.second.refreshWithNextException(data) }
//        latestExceptions.removeAll(exceps)
        // ä¿ç•™æœªåŒ¹é…çš„组合
        remainingExceptions.clear()
        remainingExceptions.addAll(leftExc)
        return fittedComb
    }
    /**
     * åˆå¹¶å¼‚常
     */
    open fun mergeExceptionResult() {
    open fun mergeExceptionResult(data: BaseRealTimeData) {
        val combinedExc = mutableListOf<List<Pair<FactorFilter.SelectedFactor, T>>>()
        // éåŽ†æ‰€æœ‰çš„å› å­ç»„åˆ
        config.factorFilter.combination.forEach { c ->
            val combRes = matchCombFactor(c, latestExceptions)
            val res = combRes.second
            val exist = combRes.first
            // å¦‚果组合内的所有因子都存在异常,则存储为合并异常
            if (exist) {
                // å°†åˆå¹¶å¼‚常存储
                combinedExc.add(res)
            }
            // å¦åˆ™å°†å¼‚常的深拷贝版本存入待合并异常集合
            // TODO 2025.8.4: åŽç»­æ·»åŠ å½“å…³è”çš„ç›‘æµ‹å› å­ç´¯è®¡å¼‚å¸¸è®¡æ•°æŽ¥è¿‘é˜ˆå€¼æ—¶ï¼Œæ‰å­˜å…¥é›†åˆçš„é€»è¾‘
            else {
                remainingExceptions.add(RemainException(res, c))
            }
        }
        // å­˜å‚¨åˆå¹¶å¼‚常
        combinedExc.forEach {
            result.add(newResult(it))
        }
    }
    /**
     * åŒ¹é…å…³è”异常因子
     * @param comb å…³è”因子关系
     * @param exceptions å„监测因子异常集合
     * @return exist表示是否找到关联关系[comb]中所有的因子,res表示找到的结果
     */
    private fun matchCombFactor(
        comb: List<FactorType>,
        exceptions: List<Pair<FactorFilter.SelectedFactor, T>>,
    ): Pair<Boolean, MutableList<Pair<FactorFilter.SelectedFactor, T>>> {
            val res = mutableListOf<Pair<FactorFilter.SelectedFactor, T>>()
            var exist = true
            // æŸ¥çœ‹ç»„合内的所有因子是否都同时出现异常
            c.forEach { f ->
                val r = latestExceptions.find { e ->
        comb.forEach { f ->
            val r = exceptions.find { e ->
                    e.first.main == f
                }
                if (r != null) {
@@ -295,36 +426,20 @@
                    exist = false
                }
            }
            // å¦‚果组合内的所有因子都存在异常,则存储为合并异常
            if (exist) {
                // å°†åˆå¹¶å¼‚常从单个异常集合中去除
                res.forEach { r ->
                    latestExceptions.removeIf { e -> e.first.main == r.first.main }
                }
                // å°†åˆå¹¶å¼‚常存储
                latestCombinedExc.add(res)
            }
        }
        // å­˜å‚¨å¼‚常结果
        latestExceptions.forEach {
            result.add(newResult(listOf(it)))
        }
        latestCombinedExc.forEach {
            result.add(newResult(it))
        }
        return exist to res
    }
    abstract fun onNewResult(result: List<Y>)
    /**
     * åœ¨å¼‚常生成结果后,进行初始化
     */
    private fun clearExceptions(data: BaseRealTimeData) {
        // æ­¤æ—¶latestExceptions中应该包含的依旧是本次数据周期内的所有异常
        latestExceptions.forEach {
            it.second.refreshWithNextException(data)
        }
        latestExceptions.clear()
        latestCombinedExc.forEach {
            it.forEach { e ->
                e.second.refreshWithNextException(data)
            }
        }
        latestCombinedExc.clear()
        result.clear()
    }
src/main/kotlin/com/flightfeather/uav/biz/dataanalysis/exceptiontype/ExceptionDataExceed.kt
@@ -48,4 +48,8 @@
    override fun newResult(exceptions: List<Pair<FactorFilter.SelectedFactor, ExceptionTag>>): ExceptionResult {
        return ExceptionResult()
    }
    override fun onNewResult(result: List<ExceptionResult>) {
        TODO("Not yet implemented")
    }
}
src/main/kotlin/com/flightfeather/uav/biz/dataanalysis/exceptiontype/ExceptionValueMutation.kt
@@ -1,6 +1,7 @@
package com.flightfeather.uav.biz.dataanalysis.exceptiontype
import com.flightfeather.uav.biz.dataanalysis.model.DataAnalysisConfig
import com.flightfeather.uav.biz.dataanalysis.model.ExceptionResult
import com.flightfeather.uav.biz.dataanalysis.model.ExceptionTag
import com.flightfeather.uav.biz.dataanalysis.model.ExceptionType
import com.flightfeather.uav.domain.entity.BaseRealTimeData
@@ -53,4 +54,8 @@
//        println("sIndex: $sIndex --- eIndex: $eIndex --- special: $special")
        return b1 || b2
    }
    override fun onNewResult(result: List<ExceptionResult>) {
        TODO("Not yet implemented")
    }
}
src/main/kotlin/com/flightfeather/uav/biz/dataanalysis/model/ExceptionTag.kt
@@ -2,16 +2,20 @@
import com.flightfeather.uav.biz.dataanalysis.BaseExceptionResult
import com.flightfeather.uav.domain.entity.BaseRealTimeData
import org.apache.commons.lang3.SerializationUtils
import org.springframework.beans.BeanUtils
import java.io.Serializable
/**
 * å¼‚常数据标签
 * @date 2025/5/13
 * @author feiyu02
 */
open class ExceptionTag {
open class ExceptionTag : Serializable {
    companion object {
        const val MAX_HISTORY = 10
    }
    // èµ·å§‹æ•°æ®ä¸‹æ ‡
    var sIndex = 0
@@ -69,4 +73,22 @@
        exceptionExisted = false
        exceptionCreated = false
    }
    fun clone(): ExceptionTag {
        val exceptionTag = SerializationUtils.clone(this)
//        val exceptionTag = ExceptionTag()
//        BeanUtils.copyProperties(this, exceptionTag)
//        exceptionTag.apply {
//            this.sIndex = this@ExceptionTag.sIndex
//            this.startData = this@ExceptionTag.startData
//            this.eIndex = this@ExceptionTag.eIndex
//            this.endData = this@ExceptionTag.endData
//            this.exceptionData = this@ExceptionTag.exceptionData
//            this.historyData = this@ExceptionTag.historyData
//            this.exceptionExisted = this@ExceptionTag.exceptionExisted
//            this.exceptionCreated = this@ExceptionTag.exceptionCreated
//            this.exceptionResult = this@ExceptionTag.exceptionResult
//        }
        return exceptionTag
    }
}
src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/config/RTExcWindLevelConfig.kt
@@ -21,12 +21,15 @@
}
    // æ˜¯å¦è”网查找对应路段地址信息
    var isSearchAddress = true
    // é™å®šè·ç¦»å†…(单位:米)
    var distanceLimit = 3000
    // é™å®šæ—¶é—´å†…(单位:分钟)
    var timeLimit = 3
    // æº¯æºæ‰©æ•£åç§»è§’度(单位:度)
    var sourceTraceDegOffset = 120.0
    // å®šæ—¶çº¿ç´¢åˆ†æžæ—¶é—´é—´éš”(单位:分钟)
    var analysisPeriod = 5
    // å®šæ—¶åˆ†æžé—´éš”中,立即进行线索分析的最小线索量(单位:个)
src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/exceptiontype/BaseRTExcChangeRate.kt
@@ -122,30 +122,20 @@
            PollutedClue(exceptions, getExceptionType(), config, changeRate[exceptions[0].first.main])
    }
    override fun onNewException(
        tag: ExceptionTag,
        factor: FactorFilter.SelectedFactor,
        exceptionStatus: ExceptionStatusType,
    ) {
        super.onNewException(tag, factor, exceptionStatus)
//    override fun mergeExceptionResult() {
//        super.mergeExceptionResult()
//        callback?.let { func ->
//            val exc = tag.exceptionResult.last()
//            func.invoke(exc as PollutedClue)
//            result.forEach {
//                func.invoke(it)
//        }
    }
//        }
//    }
    override fun mergeExceptionResult() {
        super.mergeExceptionResult()
    override fun onNewResult(result: List<PollutedClue>) {
        callback?.let { func ->
            result.forEach {
                func.invoke(it)
            }
//            latestExceptions.forEach {
//                func.invoke(listOf(it as PollutedClue))
//            }
//            latestCombinedExc.forEach {
//                func.invoke(it as List<PollutedClue>)
//            }
        }
    }
}
src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/exceptiontype/BaseRTExcWindLevel.kt
@@ -139,30 +139,21 @@
//        return PollutedClue(start, end, factor, exceptionData, getExceptionType(), config, windLevelCondition)
//    }
    override fun onNewException(
        tag: ExceptionTag,
        factor: FactorFilter.SelectedFactor,
        exceptionStatus: ExceptionStatusType,
    ) {
        super.onNewException(tag, factor, exceptionStatus)
//        callback?.let { func ->
//            val exc = tag.exceptionResult.last()
//            func.invoke(exc as PollutedClue)
//        }
    }
    override fun mergeExceptionResult() {
        super.mergeExceptionResult()
//    override fun mergeExceptionResult() {
//        super.mergeExceptionResult()
//        callback?.let { func ->
//            result.forEach {
//                func.invoke(it)
//            }
//        }
//    }
    override fun onNewResult(result: List<PollutedClue>) {
        callback?.let { func ->
            result.forEach {
                func.invoke(it)
            }
//            latestExceptions.forEach {
//                func.invoke(listOf(it as PollutedClue))
//            }
//            latestCombinedExc.forEach {
//                func.invoke(it as List<PollutedClue>)
//            }
        }
    }
}
src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/exceptiontype/RealTimeExceptionValueMutation.kt
@@ -3,6 +3,7 @@
import com.flightfeather.uav.biz.dataanalysis.model.ExceptionTag
import com.flightfeather.uav.biz.dataanalysis.model.ExceptionType
import com.flightfeather.uav.biz.sourcetrace.RealTimeAnalysisConfig
import com.flightfeather.uav.biz.sourcetrace.model.RealTimeExceptionResult
import com.flightfeather.uav.common.utils.MapUtil
import com.flightfeather.uav.domain.entity.BaseRealTimeData
import com.flightfeather.uav.socket.eunm.FactorType
@@ -90,4 +91,8 @@
        return b1 || b2
    }
    override fun onNewResult(result: List<RealTimeExceptionResult>) {
        TODO("Not yet implemented")
    }
}
src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/model/PollutedArea.kt
@@ -63,7 +63,7 @@
        val pair = avgData.longitude!!.toDouble() to avgData.latitude!!.toDouble()
        polygon = calSector(
            avgData.windDirection!!.toDouble(),
            avgData.windDirection?.toDouble() ?: .0,
            pair,
            windLevelCondition.distanceType.disRange,
            config.sourceTraceDegOffset
src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/model/PollutedClue.kt
@@ -45,6 +45,7 @@
        var exceptionData = mutableListOf<BaseRealTimeData>()
        var historyData = mutableListOf<BaseRealTimeData>()
        exceptions.forEach { e ->
            // å°†é‡‡æ ·æ—¶é—´æœ€æ—©çš„作为开始数据
            if (startData == null) {
                startData = e.second.startData
            } else {
@@ -53,6 +54,7 @@
                }
            }
            // å°†é‡‡æ ·æ—¶é—´æœ€æ™šçš„作为结束数据
            if (endData == null) {
                endData = e.second.endData
            } else {
@@ -61,6 +63,7 @@
                }
            }
            // å°†æ‰€æœ‰å¼‚常数据去重合并
            if (exceptionData.isEmpty()) {
                exceptionData = e.second.exceptionData
            } else {
@@ -71,6 +74,7 @@
                }
            }
            // å°†æ‰€æœ‰åŽ†å²æ•°æ®åŽ»é‡åˆå¹¶
            if (historyData.isEmpty()) {
                historyData = e.second.historyData
            } else {
@@ -81,10 +85,12 @@
                }
            }
        }
        // æŒ‰ç…§é‡‡æ ·æ—¶é—´å‡åºæŽ’列
        exceptionData.sortBy { it.dataTime }
        historyData.sortBy { it.dataTime }
        val factorList = exceptions.map { it.first }
        // èŽ·å–åŽ»é‡åŽçš„ç›‘æµ‹å› å­ç±»åž‹
        val factorList = exceptions.map { it.first }.distinct()
        pollutedData = PollutedData(
            startData!!, endData, factorList, exceptionData, historyData, eType, windLevelCondition
        )
src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/model/PollutedData.kt
@@ -127,8 +127,8 @@
        var total = .0
        for (i in 0 until list.size - 1) {
            val p = list[i].getByFactorType(factorType)!!
            val n = list[i + 1].getByFactorType(factorType)!!
            val p = list[i].getByFactorType(factorType) ?: .0f
            val n = list[i + 1].getByFactorType(factorType) ?: .0f
            total += (n - p) / p
        }
        return total / (list.size - 1)
@@ -140,8 +140,8 @@
        var total = .0
        for (i in 0 until list.size - 1) {
            val p = list[i].getByFactorType(factorType)!!
            val n = list[i + 1].getByFactorType(factorType)!!
            val p = list[i].getByFactorType(factorType) ?: .0f
            val n = list[i + 1].getByFactorType(factorType) ?: .0f
            total += (n - p) / 4
        }
        return total / (list.size - 1)
src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/model/RemainException.kt
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,62 @@
package com.flightfeather.uav.biz.sourcetrace.model
import com.flightfeather.uav.biz.FactorFilter
import com.flightfeather.uav.biz.dataanalysis.model.ExceptionTag
import com.flightfeather.uav.socket.eunm.FactorType
/**
 * å¾…合并异常
 * å¼‚常出现后,若相关的其他因子累计异常次数接近阈值,则该异常可以等待若干个数据周期后再合并。
 * @date 2025/8/1
 * @author feiyu02
 */
class RemainException<T : ExceptionTag>(
    exceptions: List<Pair<FactorFilter.SelectedFactor, T>>,
    combination: List<FactorType>,
) {
    // å·²æœ‰çš„异常
    var exceptions = mutableListOf<Pair<FactorFilter.SelectedFactor, T>>()
    // éœ€è¦å»¶è¿Ÿæ£€æµ‹çš„因子关联关系
    val combination = mutableListOf<FactorType>()
    // ç¼ºå¤±çš„监测因子
    val lackFactors = mutableListOf<FactorType>()
    // å·²ç»è¿‡çš„æ•°æ®å‘¨æœŸ
    var period: Int = 1
    init {
        // å­˜å‚¨ç›‘测因子异常对象的克隆版本,
        this.exceptions.addAll(exceptions.map {
            it.first to (it.second.clone() as T)
        })
        this.combination.addAll(combination)
        calLackFactors()
    }
    /**
     * æ·»åŠ æ–°çš„å¼‚å¸¸é›†åˆ
     */
    fun addExceptions(exceptions: List<Pair<FactorFilter.SelectedFactor, T>>) {
        // å­˜å‚¨ç›‘测因子异常对象的克隆版本,
        this.exceptions.addAll(exceptions.map {
            it.first to (it.second.clone() as T)
        })
        calLackFactors()
    }
    /**
     * è®¡ç®—缺失的监测因子
     */
    private fun calLackFactors() {
        lackFactors.clear()
        combination.forEach { c ->
            val e = exceptions.find { it.first.main == c }
            if (e == null) {
                lackFactors.add(c)
            }
        }
    }
}
src/main/kotlin/com/flightfeather/uav/common/exception/BizException.kt
@@ -6,8 +6,8 @@
class BizException : Exception {
    constructor():super()
    constructor(message: String) : super(message)
    constructor(message: String, cause: Throwable) : super(message, cause)
    constructor(cause: Throwable) : super(cause)
    constructor(message: String, cause: Throwable, enableSuppression: Boolean, writableStackTrace: Boolean)
    constructor(message: String, cause: Throwable?) : super(message, cause)
    constructor(cause: Throwable?) : super(cause)
    constructor(message: String, cause: Throwable?, enableSuppression: Boolean, writableStackTrace: Boolean)
            : super(message, cause, enableSuppression, writableStackTrace)
}
src/main/kotlin/com/flightfeather/uav/common/net/AMapService.kt
@@ -105,7 +105,7 @@
                a["streetNumber"].asJsonObject["street"].asString,
            )
        } catch (e: Exception) {
            throw BizException("高德API坐标转换错误,${e.message}")
            throw BizException("高德API坐标转换错误,${e.message}", e.cause)
        }
    }
src/main/kotlin/com/flightfeather/uav/domain/entity/BaseRealTimeData.kt
@@ -5,6 +5,7 @@
import com.flightfeather.uav.lightshare.bean.DataVo
import com.flightfeather.uav.socket.bean.AirData
import com.flightfeather.uav.socket.eunm.FactorType
import java.io.Serializable
import java.math.BigDecimal
import java.time.LocalDateTime
import java.time.ZoneId
@@ -21,7 +22,7 @@
/**
 * å®žæ—¶ç›‘测数据基类
 */
open class BaseRealTimeData {
open class BaseRealTimeData : Serializable {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    var id: Int? = null
@@ -277,7 +278,9 @@
    }
    return RealTimeDataGridMin().apply {
        val time = LocalDateTime.ofInstant(get(0).dataTime?.toInstant(), ZoneId.systemDefault()).withSecond(0)
        val time = LocalDateTime
            .ofInstant(get(0).dataTime?.toInstant() ?: Date().toInstant(), ZoneId.systemDefault())
            .withSecond(0)
        deviceCode = get(0).deviceCode
        dataTime = Date.from(time.atZone(ZoneId.systemDefault()).toInstant())
        createTime = dataTime
src/test/kotlin/com/flightfeather/uav/biz/dataanalysis/BaseExceptionContinuousTest.kt
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,156 @@
package com.flightfeather.uav.biz.dataanalysis
import com.flightfeather.uav.biz.FactorFilter
import com.flightfeather.uav.biz.dataanalysis.model.ExceptionTag
import com.flightfeather.uav.biz.dataanalysis.model.ExceptionType
import com.flightfeather.uav.biz.sourcetrace.config.RTExcWindLevelConfig
import com.flightfeather.uav.biz.sourcetrace.exceptiontype.*
import com.flightfeather.uav.biz.sourcetrace.model.PollutedClue
import com.flightfeather.uav.biz.sourcetrace.model.RemainException
import com.flightfeather.uav.domain.entity.BaseRealTimeData
import com.flightfeather.uav.socket.eunm.FactorType
import org.junit.Test
import kotlin.test.assertContentEquals
class BaseExceptionContinuousTest {
    private val exceptionTag = ExceptionTag().apply {
        sIndex = 1
        startData = BaseRealTimeData().apply { id = 1 }
        eIndex = 5
        endData = BaseRealTimeData().apply { id = 4 }
        exceptionData = mutableListOf(
            startData!!,
            BaseRealTimeData().apply { id = 2 },
            BaseRealTimeData().apply { id = 3 },
            endData!!
        )
        historyData = mutableListOf()
        exceptionExisted = true
        exceptionCreated = false
//        exceptionResult =
    }
    private val combination = listOf(
        listOf(FactorType.PM25, FactorType.PM10),
        listOf(FactorType.VOC, FactorType.CO),
        listOf(FactorType.VOC, FactorType.PM25),
        listOf(FactorType.VOC, FactorType.PM25, FactorType.CO),
    )
    private val config = RTExcWindLevelConfig(
        FactorFilter.builder()
            .withMain(FactorType.NO2)
            .withMain(FactorType.CO)
//                    .withMain(FactorType.H2S)
//                    .withMain(FactorType.SO2)
            .withMain(FactorType.O3)
            .withMain(FactorType.PM25)
            .withMain(FactorType.PM10)
            .withMain(FactorType.VOC)
            .withCombination(combination)
            .create()
    ).apply { isSearchAddress = false }
    private fun taskList() = mutableListOf(
        RTExcWindLevel1(config).also { it.init() },
//        RTExcWindLevel1_1(config).also { it.init() },
//        RTExcWindLevel4(config).also { it.init() },
//        RTExcWindLevel6(config).also { it.init() },
        RTExcChangeRate1(config).also { it.init() },
//        RTExcChangeRate4(config).also { it.init() },
//        RTExcChangeRate6(config).also { it.init() },
//        RTWarnChangeRate(config).also { it.init() },
//        RTWarnChangeRate2(config).also { it.init() }
    )
    // æœ€æ–°çš„一组异常,记录单因子异常
    private fun exceptions() = mutableListOf(
        FactorFilter.SelectedFactor(FactorType.NO2) to exceptionTag.clone(),
        FactorFilter.SelectedFactor(FactorType.CO) to exceptionTag.clone(),
        FactorFilter.SelectedFactor(FactorType.O3) to exceptionTag.clone(),
        FactorFilter.SelectedFactor(FactorType.PM25) to exceptionTag.clone(),
        FactorFilter.SelectedFactor(FactorType.PM10) to exceptionTag.clone(),
        FactorFilter.SelectedFactor(FactorType.VOC) to exceptionTag.clone(),
    )
    @Test
    fun removeSingleFactor() {
        taskList().forEach { exc ->
            exc.latestExceptions.clear()
            exc.latestExceptions.addAll(exceptions())
            exc.removeSingleFactor(BaseRealTimeData())
            val resList = exc.result.map {
                it.pollutedData?.statisticMap?.entries?.map { e -> e.key }
            }
            assertContentEquals(
                listOf(listOf(FactorType.NO2), listOf(FactorType.O3)),
                resList,
                "异常结果应该都是不在组合中的异常"
            )
            val resList2 = exc.latestExceptions.map { it.first.main }
            assertContentEquals(
                listOf(FactorType.CO, FactorType.PM25, FactorType.PM10, FactorType.VOC),
                resList2,
                "剩余的应该是不在组合中的异常"
            )
        }
    }
    @Test
    fun checkDelayedExceptions() {
        taskList().forEach { exc ->
            val e = exceptions()
            exc.remainingExceptions.add(RemainException(listOf(e[3], e[5]), listOf(FactorType.VOC, FactorType.PM25, FactorType.CO)))
            exc.remainingExceptions.add(RemainException(listOf(e[1]), listOf(FactorType.VOC, FactorType.CO)))
            exc.remainingExceptions.add(RemainException(listOf(e[3]), listOf(FactorType.PM10, FactorType.PM25)))
            exc.latestExceptions.clear()
            exc.latestExceptions.addAll(exceptions())
            exc.removeSingleFactor(BaseRealTimeData())
            val resList = exc.result.map {
                it.pollutedData?.statisticMap?.entries?.map { e -> e.key }
            }
            assertContentEquals(
                listOf(listOf(FactorType.NO2), listOf(FactorType.O3)),
                resList,
                "异常结果应该都是不在组合中的异常"
            )
            val resList2 = exc.latestExceptions.map { it.first.main }
            assertContentEquals(
                listOf(FactorType.CO, FactorType.PM25, FactorType.PM10, FactorType.VOC),
                resList2,
                "剩余的应该是不在组合中的异常"
            )
        }
    }
    @Test
    fun mergeExceptionResult() {
        val factorList = listOf(
            FactorFilter.SelectedFactor(FactorType.PM10),
            FactorFilter.SelectedFactor(FactorType.PM25),
            FactorFilter.SelectedFactor(FactorType.CO),
        )
        val factorList2 = listOf(
            FactorFilter.SelectedFactor(FactorType.CO),
            FactorFilter.SelectedFactor(FactorType.PM25),
            FactorFilter.SelectedFactor(FactorType.PM10),
        )
        println(factorList == factorList2)
    }
    @Test
    fun clearExceptions() {
    }
}