From dac47617b37ccfb834cd73ce0ee725e1101de214 Mon Sep 17 00:00:00 2001
From: feiyu02 <risaku@163.com>
Date: 星期四, 14 八月 2025 17:25:51 +0800
Subject: [PATCH] 2025.8.14 1. 动态溯源模块添加滑动平均异常计算(调试中)

---
 src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/exceptiontype/RTExcSlideAverage.kt |  194 ++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 194 insertions(+), 0 deletions(-)

diff --git a/src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/exceptiontype/RTExcSlideAverage.kt b/src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/exceptiontype/RTExcSlideAverage.kt
new file mode 100644
index 0000000..fb74b06
--- /dev/null
+++ b/src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/exceptiontype/RTExcSlideAverage.kt
@@ -0,0 +1,194 @@
+package com.flightfeather.uav.biz.sourcetrace.exceptiontype
+
+import com.flightfeather.uav.biz.FactorFilter
+import com.flightfeather.uav.biz.dataanalysis.BaseExceptionAnalysis
+import com.flightfeather.uav.biz.dataanalysis.exceptiontype.ExceptionSlideAverage.Tag
+import com.flightfeather.uav.biz.dataanalysis.model.ExceptionResult
+import com.flightfeather.uav.biz.dataanalysis.model.ExceptionSlideAverageTag
+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.config.RTExcWindLevelConfig
+import com.flightfeather.uav.biz.sourcetrace.model.PollutedClue
+import com.flightfeather.uav.biz.sourcetrace.model.RealTimeExceptionResult
+import com.flightfeather.uav.domain.entity.BaseRealTimeData
+import com.flightfeather.uav.lightshare.eunm.ExceptionStatusType
+import com.flightfeather.uav.socket.eunm.FactorType
+import kotlin.math.abs
+
+/**
+ * 婊戝姩骞冲潎鍊肩獊鍙樺紓甯�
+ * @date 2025/5/13
+ * @author feiyu02
+ */
+class RTExcSlideAverage : BaseExceptionAnalysis<RTExcWindLevelConfig, PollutedClue> {
+
+    constructor(config: RTExcWindLevelConfig) : super(config)
+
+    constructor(config: RTExcWindLevelConfig, callback: NewPolluteClueCallback) : super(config){
+        this.callback = callback
+    }
+
+    private var callback: NewPolluteClueCallback? = null
+
+    private val historyDataList = mutableListOf<BaseRealTimeData>()
+    private val tempDataList = mutableListOf<BaseRealTimeData>()
+    private var lastData: BaseRealTimeData? = null
+
+    protected val tagMap = mutableMapOf<FactorType, ExceptionSlideAverageTag>()
+
+    override fun init() {
+        super.init()
+        historyDataList.clear()
+        tempDataList.clear()
+        lastData = null
+
+        tagMap.clear()
+        config.factorFilter.mainList().forEach { f ->
+            tagMap[f] = ExceptionSlideAverageTag()
+        }
+    }
+
+    override fun getExceptionType(): ExceptionType {
+        return ExceptionType.TYPE7
+    }
+
+    override fun onNextData(data: BaseRealTimeData) {
+        historyDataList.add(data)
+        // 鏁版嵁鍔犲叆涓存椂鏁扮粍
+        tempDataList.add(data)
+        // 鏁版嵁閲忚秴鍑鸿缃暟閲忔椂锛屽幓闄ゅ綋鍓嶆暟鎹粍棣栦釜鏁版嵁
+        if (tempDataList.size > config.changeTrendGroup) {
+            tempDataList.removeAt(0)
+        }
+        config.factorFilter.selectedList.forEach { s ->
+            val f = s.main
+            tagMap[f]?.let {
+                it.eIndex++
+                it.endData = lastData
+                if (it.startData == null) {
+                    it.startData = data
+                }
+                // 鏁版嵁閲忕瓑浜庤缃暟閲忔椂锛岃绠楀綋鍓嶆暟鎹粍鍧囧��
+                if (tempDataList.size == config.changeTrendGroup) {
+                    calAvg(f, tempDataList)
+                    if (checkSlideAvg(f)) {
+                        it.addExceptionData(data)
+                        checkResult(s)
+                    } else {
+                        recordException(s, it, data)
+                    }
+                }
+            }
+        }
+        lastData = data
+    }
+
+    override fun onDone() {
+        checkResult(exceptionStatus = ExceptionStatusType.Ended)
+    }
+
+    /**
+     * 寮傚父缁撴潫锛岃褰曞紓甯�
+     */
+    fun recordException(factor: FactorFilter.SelectedFactor, tag: ExceptionSlideAverageTag, data: BaseRealTimeData) {
+        checkResult(factor, ExceptionStatusType.Ended)
+        tag.refreshAfterCheckResult(historyDataList, config.changeTrendGroup)
+    }
+
+    /**
+     * 褰撳墠鏁版嵁鏈嚭鐜板紓甯告椂锛屾垨鏁版嵁寰幆缁撴潫鏃讹紝鍒ゆ柇鍚庣画姝ラ
+     */
+    private fun checkResult(
+        factor: FactorFilter.SelectedFactor? = null,
+        exceptionStatus: ExceptionStatusType = ExceptionStatusType.InProgress
+    ) {
+        val tag = tagMap[factor?.main]
+        if (factor != null && tag != null) {
+            if (tag.exceptionExisted) {
+                onNewException(tag, factor, exceptionStatus)
+            }
+        } else {
+            config.factorFilter.selectedList.forEach { f ->
+                val tag1 = tagMap[f.main] ?: return@forEach
+                if (tag1.exceptionExisted) {
+                    onNewException(tag1, f, exceptionStatus)
+                }
+            }
+        }
+
+    }
+
+    /**
+     * 鏂板鎴栨洿鏂颁竴鏉″紓甯�
+     */
+    open fun onNewException(tag: ExceptionSlideAverageTag, factor: FactorFilter.SelectedFactor, exceptionStatus: ExceptionStatusType) {
+        if (tag.startData == null) return
+        val ex = newResult(listOf(factor to tag))
+
+        callback?.invoke(ex)
+    }
+
+    fun newResult(exceptions: List<Pair<FactorFilter.SelectedFactor, ExceptionTag>>): PollutedClue {
+        return PollutedClue(exceptions, getExceptionType(), config, null)
+    }
+
+    /**
+     * 璁$畻涓�缁勬暟鎹殑鍧囧��
+     */
+    private fun calAvg(type: FactorType, list: List<BaseRealTimeData>) {
+        var total = .0
+        var valid = true
+        val count = list.size
+        if (count == 0) return
+        list.forEach {
+            val v = it.getByFactorType(type)
+            if (v == null) {
+                valid = false
+            } else {
+                total += v
+            }
+        }
+        val avg = total / count
+        tagMap[type]?.avgListReverse?.add(0, Pair(avg, valid))
+    }
+
+    /**
+     * 璁$畻鏁版嵁缁勪箣闂寸殑鍧囧�煎樊寮傛槸鍚﹁繛缁秴杩囬檺瀹氭瘮鐜�
+     */
+    private fun checkSlideAvg(type: FactorType): Boolean {
+        val tag = tagMap[type] ?: return false
+        // 璁$畻婊戝姩鍧囧�兼渶浣庤姹備釜鏁�
+        val minSize = config.changeTrendTimes + config.changeTrendInterval
+        if (tag.avgListReverse.size < minSize) {
+            return false
+        } else {
+            // 婊戝姩鍧囧�兼弧瓒虫暟閲忔椂锛岃绠楀潎鍊间箣闂存槸鍚﹁繛缁秴杩囬檺瀹氭瘮鐜�
+            val rateList = mutableListOf<Pair<Double, Boolean>>()
+            for (i in tag.avgListReverse.indices) {
+                if (i >= config.changeTrendTimes) break
+                val r = calAvgChangeRate(tag.avgListReverse[i], tag.avgListReverse[i + config.changeTrendInterval])
+                rateList.add(r)
+            }
+            for (y in rateList) {
+                if (!y.second || y.first < config.changeTrendRate) {
+                    return false
+                }
+            }
+            return true
+        }
+    }
+
+    /**
+     * 璁$畻婊戝姩鍧囧�煎彉鍖栫巼
+     * 姹俛1鐩稿浜巃2鐨勫彉鍖栫巼
+     */
+    private fun calAvgChangeRate(a1: Pair<Double, Boolean>, a2: Pair<Double, Boolean>): Pair<Double, Boolean> {
+        val valid = a1.second && a2.second
+        return if (a2.first == .0) {
+            Pair(1.0, valid)
+        } else {
+            Pair(abs(a1.first - a2.first) / a2.first, valid)
+        }
+    }
+}
\ No newline at end of file

--
Gitblit v1.9.3