From 176d7d8283e66ccf63878c9ab823e900df94b748 Mon Sep 17 00:00:00 2001
From: feiyu02 <risaku@163.com>
Date: 星期二, 05 八月 2025 17:20:58 +0800
Subject: [PATCH] 2025.8.5 1. 动态溯源模块添加延迟数据周期异常合并功能

---
 src/main/kotlin/com/flightfeather/uav/biz/dataanalysis/exceptiontype/ExceptionValueMutation.kt        |    5 
 src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/model/RemainException.kt                        |   62 ++++++
 src/main/kotlin/com/flightfeather/uav/common/exception/BizException.kt                                |    6 
 src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/model/PollutedData.kt                           |    8 
 src/main/kotlin/com/flightfeather/uav/common/net/AMapService.kt                                       |    2 
 src/main/kotlin/com/flightfeather/uav/biz/dataanalysis/BaseExceptionContinuous.kt                     |  183 ++++++++++++++++---
 src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/exceptiontype/BaseRTExcChangeRate.kt            |   24 -
 src/main/kotlin/com/flightfeather/uav/biz/dataanalysis/model/ExceptionTag.kt                          |   26 ++
 src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/config/RTExcWindLevelConfig.kt                  |    3 
 src/main/kotlin/com/flightfeather/uav/biz/dataanalysis/exceptiontype/ExceptionDataExceed.kt           |    4 
 src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/exceptiontype/RealTimeExceptionValueMutation.kt |    5 
 src/main/kotlin/com/flightfeather/uav/biz/dataanalysis/BaseAnalysisConfig.kt                          |    6 
 src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/model/PollutedClue.kt                           |    8 
 src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/exceptiontype/BaseRTExcWindLevel.kt             |   29 +--
 src/main/kotlin/com/flightfeather/uav/domain/entity/BaseRealTimeData.kt                               |    7 
 src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/model/PollutedArea.kt                           |    2 
 src/test/kotlin/com/flightfeather/uav/biz/dataanalysis/BaseExceptionContinuousTest.kt                 |  156 +++++++++++++++++
 17 files changed, 450 insertions(+), 86 deletions(-)

diff --git a/src/main/kotlin/com/flightfeather/uav/biz/dataanalysis/BaseAnalysisConfig.kt b/src/main/kotlin/com/flightfeather/uav/biz/dataanalysis/BaseAnalysisConfig.kt
index f1ee8b6..8c2d780 100644
--- a/src/main/kotlin/com/flightfeather/uav/biz/dataanalysis/BaseAnalysisConfig.kt
+++ b/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,
-)
\ No newline at end of file
+){
+    // 鏁版嵁寮傚父鍚堝苟鏃讹紝鍏佽寤惰繜鏈�澶ф暟鎹懆鏈�
+    var maxDelayPeriod: Int = 1
+}
\ No newline at end of file
diff --git a/src/main/kotlin/com/flightfeather/uav/biz/dataanalysis/BaseExceptionContinuous.kt b/src/main/kotlin/com/flightfeather/uav/biz/dataanalysis/BaseExceptionContinuous.kt
index 36fdfa1..3866509 100644
--- a/src/main/kotlin/com/flightfeather/uav/biz/dataanalysis/BaseExceptionContinuous.kt
+++ b/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,54 +283,163 @@
     }
 
     /**
+     * 灏嗕笉鍦ㄥ叧鑱斿叧绯讳腑鐨勭洃娴嬪洜瀛愬紓甯稿瓨鍌紝骞跺墧闄�
+     */
+    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 res = mutableListOf<Pair<FactorFilter.SelectedFactor, T>>()
-            var exist = true
-            // 鏌ョ湅缁勫悎鍐呯殑鎵�鏈夊洜瀛愭槸鍚﹂兘鍚屾椂鍑虹幇寮傚父
-            c.forEach { f ->
-                val r = latestExceptions.find { e ->
-                    e.first.main == f
-                }
-                if (r != null) {
-                    res.add(r)
-                } else {
-                    exist = false
-                }
-            }
+            val combRes = matchCombFactor(c, latestExceptions)
+            val res = combRes.second
+            val exist = combRes.first
             // 濡傛灉缁勫悎鍐呯殑鎵�鏈夊洜瀛愰兘瀛樺湪寮傚父锛屽垯瀛樺偍涓哄悎骞跺紓甯�
             if (exist) {
-                // 灏嗗悎骞跺紓甯镐粠鍗曚釜寮傚父闆嗗悎涓幓闄�
-                res.forEach { r ->
-                    latestExceptions.removeIf { e -> e.first.main == r.first.main }
-                }
                 // 灏嗗悎骞跺紓甯稿瓨鍌�
-                latestCombinedExc.add(res)
+                combinedExc.add(res)
+            }
+            // 鍚﹀垯灏嗗紓甯哥殑娣辨嫹璐濈増鏈瓨鍏ュ緟鍚堝苟寮傚父闆嗗悎
+            // TODO 2025.8.4: 鍚庣画娣诲姞褰撳叧鑱旂殑鐩戞祴鍥犲瓙绱寮傚父璁℃暟鎺ヨ繎闃堝�兼椂锛屾墠瀛樺叆闆嗗悎鐨勯�昏緫
+            else {
+                remainingExceptions.add(RemainException(res, c))
             }
         }
-        // 瀛樺偍寮傚父缁撴灉
-        latestExceptions.forEach {
-            result.add(newResult(listOf(it)))
-        }
-        latestCombinedExc.forEach {
+
+        // 瀛樺偍鍚堝苟寮傚父
+        combinedExc.forEach {
             result.add(newResult(it))
         }
     }
 
+    /**
+     * 鍖归厤鍏宠仈寮傚父鍥犲瓙
+     * @param comb 鍏宠仈鍥犲瓙鍏崇郴
+     * @param exceptions 鍚勭洃娴嬪洜瀛愬紓甯搁泦鍚�
+     * @return exist琛ㄧず鏄惁鎵惧埌鍏宠仈鍏崇郴[comb]涓墍鏈夌殑鍥犲瓙锛宺es琛ㄧず鎵惧埌鐨勭粨鏋�
+     */
+    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
+        // 鏌ョ湅缁勫悎鍐呯殑鎵�鏈夊洜瀛愭槸鍚﹂兘鍚屾椂鍑虹幇寮傚父
+        comb.forEach { f ->
+            val r = exceptions.find { e ->
+                e.first.main == f
+            }
+            if (r != null) {
+                res.add(r)
+            } else {
+                exist = false
+            }
+        }
+        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()
     }
 
diff --git a/src/main/kotlin/com/flightfeather/uav/biz/dataanalysis/exceptiontype/ExceptionDataExceed.kt b/src/main/kotlin/com/flightfeather/uav/biz/dataanalysis/exceptiontype/ExceptionDataExceed.kt
index 2e382b4..babc0c4 100644
--- a/src/main/kotlin/com/flightfeather/uav/biz/dataanalysis/exceptiontype/ExceptionDataExceed.kt
+++ b/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")
+    }
 }
\ No newline at end of file
diff --git a/src/main/kotlin/com/flightfeather/uav/biz/dataanalysis/exceptiontype/ExceptionValueMutation.kt b/src/main/kotlin/com/flightfeather/uav/biz/dataanalysis/exceptiontype/ExceptionValueMutation.kt
index 5633645..7b09f49 100644
--- a/src/main/kotlin/com/flightfeather/uav/biz/dataanalysis/exceptiontype/ExceptionValueMutation.kt
+++ b/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")
+    }
 }
\ No newline at end of file
diff --git a/src/main/kotlin/com/flightfeather/uav/biz/dataanalysis/model/ExceptionTag.kt b/src/main/kotlin/com/flightfeather/uav/biz/dataanalysis/model/ExceptionTag.kt
index 4d7cb93..65507b3 100644
--- a/src/main/kotlin/com/flightfeather/uav/biz/dataanalysis/model/ExceptionTag.kt
+++ b/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
 
@@ -53,7 +57,7 @@
         }
     }
 
-    fun addExceptionData(data: BaseRealTimeData){
+    fun addExceptionData(data: BaseRealTimeData) {
         exceptionExisted = true
         exceptionData.add(data)
     }
@@ -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
+    }
 }
\ No newline at end of file
diff --git a/src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/config/RTExcWindLevelConfig.kt b/src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/config/RTExcWindLevelConfig.kt
index 042bcf2..b56fb48 100644
--- a/src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/config/RTExcWindLevelConfig.kt
+++ b/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
     // 瀹氭椂鍒嗘瀽闂撮殧涓紝绔嬪嵆杩涜绾跨储鍒嗘瀽鐨勬渶灏忕嚎绱㈤噺(鍗曚綅锛氫釜)
diff --git a/src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/exceptiontype/BaseRTExcChangeRate.kt b/src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/exceptiontype/BaseRTExcChangeRate.kt
index 28d1591..81cb8a6 100644
--- a/src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/exceptiontype/BaseRTExcChangeRate.kt
+++ b/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>)
-//            }
         }
     }
 }
\ No newline at end of file
diff --git a/src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/exceptiontype/BaseRTExcWindLevel.kt b/src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/exceptiontype/BaseRTExcWindLevel.kt
index 24fa6d7..13b0f3d 100644
--- a/src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/exceptiontype/BaseRTExcWindLevel.kt
+++ b/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>)
-//            }
         }
     }
 }
\ No newline at end of file
diff --git a/src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/exceptiontype/RealTimeExceptionValueMutation.kt b/src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/exceptiontype/RealTimeExceptionValueMutation.kt
index 3bccabf..2a3d11c 100644
--- a/src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/exceptiontype/RealTimeExceptionValueMutation.kt
+++ b/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")
+    }
 }
\ No newline at end of file
diff --git a/src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/model/PollutedArea.kt b/src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/model/PollutedArea.kt
index e9092d6..9993a86 100644
--- a/src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/model/PollutedArea.kt
+++ b/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
diff --git a/src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/model/PollutedClue.kt b/src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/model/PollutedClue.kt
index 19eedc2..6035ec5 100644
--- a/src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/model/PollutedClue.kt
+++ b/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
         )
diff --git a/src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/model/PollutedData.kt b/src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/model/PollutedData.kt
index 3dc2e05..266ed2a 100644
--- a/src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/model/PollutedData.kt
+++ b/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)
diff --git a/src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/model/RemainException.kt b/src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/model/RemainException.kt
new file mode 100644
index 0000000..cff1723
--- /dev/null
+++ b/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)
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/com/flightfeather/uav/common/exception/BizException.kt b/src/main/kotlin/com/flightfeather/uav/common/exception/BizException.kt
index a0198cd..7a7746b 100644
--- a/src/main/kotlin/com/flightfeather/uav/common/exception/BizException.kt
+++ b/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)
 }
\ No newline at end of file
diff --git a/src/main/kotlin/com/flightfeather/uav/common/net/AMapService.kt b/src/main/kotlin/com/flightfeather/uav/common/net/AMapService.kt
index 52e1940..9b25bff 100644
--- a/src/main/kotlin/com/flightfeather/uav/common/net/AMapService.kt
+++ b/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)
         }
     }
 
diff --git a/src/main/kotlin/com/flightfeather/uav/domain/entity/BaseRealTimeData.kt b/src/main/kotlin/com/flightfeather/uav/domain/entity/BaseRealTimeData.kt
index 741a06a..dccef45 100644
--- a/src/main/kotlin/com/flightfeather/uav/domain/entity/BaseRealTimeData.kt
+++ b/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
diff --git a/src/test/kotlin/com/flightfeather/uav/biz/dataanalysis/BaseExceptionContinuousTest.kt b/src/test/kotlin/com/flightfeather/uav/biz/dataanalysis/BaseExceptionContinuousTest.kt
new file mode 100644
index 0000000..450871f
--- /dev/null
+++ b/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() {
+
+    }
+
+
+}
\ No newline at end of file

--
Gitblit v1.9.3