From d649f734c44541641158aec2d6b10d630f5a0827 Mon Sep 17 00:00:00 2001
From: feiyu02 <risaku@163.com>
Date: 星期四, 25 十二月 2025 17:25:16 +0800
Subject: [PATCH] 2025.12.19 1. 动态溯源相关分析逻辑调整; 2. 走航报告接口参数调整;

---
 src/main/kotlin/com/flightfeather/uav/lightshare/service/impl/DataAnalysisServiceImpl.kt     |  145 +++++++++++++++++-------
 src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/model/PollutedData.kt                  |   31 ++--
 src/test/kotlin/com/flightfeather/uav/biz/sourcetrace/SourceTraceControllerTest.kt           |   12 +
 src/main/kotlin/com/flightfeather/uav/biz/report/MissionRiskArea.kt                          |    3 
 src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/model/PollutedSource.kt                |    8 
 src/test/kotlin/com/flightfeather/uav/lightshare/service/impl/MissionServiceImplTest.kt      |    2 
 src/main/kotlin/com/flightfeather/uav/domain/repository/SourceTraceRep.kt                    |   50 ++++++--
 src/test/kotlin/com/flightfeather/uav/lightshare/service/impl/DataAnalysisServiceImplTest.kt |    2 
 src/main/kotlin/com/flightfeather/uav/lightshare/bean/AnalysisOption.kt                      |    4 
 src/main/kotlin/com/flightfeather/uav/lightshare/service/DataAnalysisService.kt              |   46 +++++-
 src/test/kotlin/com/flightfeather/uav/Test.kt                                                |    5 
 src/main/kotlin/com/flightfeather/uav/lightshare/web/DataAnalysisController.kt               |   39 ++++--
 src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/model/PollutedArea.kt                  |    6 
 13 files changed, 238 insertions(+), 115 deletions(-)

diff --git a/src/main/kotlin/com/flightfeather/uav/biz/report/MissionRiskArea.kt b/src/main/kotlin/com/flightfeather/uav/biz/report/MissionRiskArea.kt
index e019c7c..0f0d49c 100644
--- a/src/main/kotlin/com/flightfeather/uav/biz/report/MissionRiskArea.kt
+++ b/src/main/kotlin/com/flightfeather/uav/biz/report/MissionRiskArea.kt
@@ -3,7 +3,6 @@
 import com.flightfeather.uav.biz.sourcetrace.model.PollutedClue
 import com.flightfeather.uav.common.utils.MapUtil
 import com.flightfeather.uav.domain.entity.SceneInfo
-import com.flightfeather.uav.domain.entity.avg
 import com.flightfeather.uav.socket.eunm.AggregatedFactorType
 import com.flightfeather.uav.socket.eunm.FactorType
 
@@ -57,7 +56,7 @@
         pollutedClues.forEach { pollutedClue ->
             if (pollutedClue == null) return@forEach
             // 璁$畻鍗曚釜PollutedClue鐨勫潎鍊肩粡绾害
-            val wgs84Center = pollutedClue.pollutedData?.getExceptionCenter() ?: return@forEach
+            val wgs84Center = pollutedClue.pollutedData?.exceptionCenter() ?: return@forEach
 
             // 鍧愭爣杞崲
             val gcj02Point = MapUtil.wgs84ToGcj02(wgs84Center)
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 127622c..df8bd4d 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
@@ -1,12 +1,8 @@
 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.biz.sourcetrace.config.RTExcWindLevelConfig
 import com.flightfeather.uav.common.net.AMapService
 import com.flightfeather.uav.common.utils.MapUtil
-import com.flightfeather.uav.domain.entity.BaseRealTimeData
-import com.flightfeather.uav.domain.entity.avg
 import kotlin.math.PI
 
 /**
@@ -70,7 +66,7 @@
         windLevelCondition: RTExcWindLevelConfig.WindLevelCondition,
     ) {
 
-        val avgData = pollutedData.getExceptionAvgData()
+        val avgData = pollutedData.exceptionAvgData()
         val pair = avgData.longitude!!.toDouble() to avgData.latitude!!.toDouble()
         polygon = calSector(
             avgData.windDirection?.toDouble() ?: .0,
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 0483664..073926b 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
@@ -3,7 +3,6 @@
 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.common.utils.DateUtil
 import com.flightfeather.uav.domain.entity.BaseRealTimeData
 import com.flightfeather.uav.domain.entity.avg
@@ -31,8 +30,8 @@
         constructor(dataIndexList: List<Int>, factorType: FactorType){
             this.dataIndexList = dataIndexList
             this.factorType = factorType
-            val first = getFirstDataValue()?.toDouble()
-            val last = getLastDataValue()?.toDouble()
+            val first = firstDataValue()?.toDouble()
+            val last = lastDataValue()?.toDouble()
             if (first != null && last != null) {
                 per = round((last - first) / first * 100) / 100
                 rate = round((last - first) / DEFAULT_PERIOD * 100) / 100
@@ -52,26 +51,26 @@
          * 鑾峰彇寮傚父鏁版嵁鐨勭涓�涓暟鎹�
          * !!!!绗竴涓暟鎹叾瀹炴槸棣栦釜寮傚父鏁版嵁鐨勫墠涓�涓暟鎹��!!!!
          */
-        fun getFirstData(): BaseRealTimeData? {
+        fun firstData(): BaseRealTimeData? {
             return dataIndexList?.firstOrNull()?.let {
                 val i = if (it > 0) it - 1 else it
                 historyDataList[i].toBaseRealTimeData(BaseRealTimeData::class.java)
             }
         }
-        fun getFirstDataValue(): Float? {
-            return getFirstData()?.getByFactorType(factorType)
+        fun firstDataValue(): Float? {
+            return firstData()?.getByFactorType(factorType)
         }
 
         /**
          * 鑾峰彇寮傚父鏁版嵁鐨勬渶鍚庝竴涓暟鎹�
          */
-        fun getLastData(): BaseRealTimeData? {
+        fun lastData(): BaseRealTimeData? {
             return dataIndexList?.lastOrNull()?.let {
                 historyDataList[it].toBaseRealTimeData(BaseRealTimeData::class.java)
             }
         }
-        fun getLastDataValue(): Float? {
-            return getLastData()?.getByFactorType(factorType)
+        fun lastDataValue(): Float? {
+            return lastData()?.getByFactorType(factorType)
         }
     }
 
@@ -105,7 +104,7 @@
         /**
          * 鑾峰彇寮傚父鏁版嵁
          */
-        fun getExceptionData(): List<BaseRealTimeData>? {
+        fun exceptionData(): List<BaseRealTimeData>? {
             return dataIndexList?.map { historyDataList[it].toBaseRealTimeData(BaseRealTimeData::class.java) }
         }
 
@@ -113,7 +112,7 @@
          * 鑾峰彇寮傚父鏁版嵁鍒嗘鎯呭喌
          * 灏嗚繛缁殑寮傚父鏁版嵁鍒嗕负涓�缁�
          */
-        fun getExceptionDataGroup(): List<List<Int>> {
+        fun exceptionDataGroup(): List<List<Int>> {
             val res = mutableListOf<MutableList<Int>>()
             var curGroup = mutableListOf<Int>()
             var lastIndex = -2
@@ -202,7 +201,7 @@
                 min = s.second
                 max = s.third
 
-                excGroup = getExceptionDataGroup().map { ExcGroup(it, e.first.main) }
+                excGroup = exceptionDataGroup().map { ExcGroup(it, e.first.main) }
                 avgPer = excGroup?.mapNotNull { it.per }?.average()
                 avgRate = excGroup?.mapNotNull { it.rate }?.average()
             }
@@ -232,16 +231,16 @@
         return factors
     }
 
-    fun getExceptionAvgData(): BaseRealTimeData {
-        val exceptionDataList = statisticMap.flatMap { it.value.getExceptionData() ?: emptyList() }
+    fun exceptionAvgData(): BaseRealTimeData {
+        val exceptionDataList = statisticMap.flatMap { it.value.exceptionData() ?: emptyList() }
         val avgData = exceptionDataList.avg()
         return avgData
     }
     /**
      * 鑾峰彇寮傚父鏁版嵁涓績鍧愭爣锛堝紓甯告暟鎹腑缁忓害绾害鐨勫钩鍧囧�硷級
      */
-    fun getExceptionCenter(): Pair<Double, Double>? {
-        val avgData = getExceptionAvgData()
+    fun exceptionCenter(): Pair<Double, Double>? {
+        val avgData = exceptionAvgData()
         val wgs84Lng = avgData.longitude?.toDouble()
         val wgs84Lat = avgData.latitude?.toDouble()
         return if (wgs84Lng == null || wgs84Lat == null) null else Pair(wgs84Lng, wgs84Lat)
diff --git a/src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/model/PollutedSource.kt b/src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/model/PollutedSource.kt
index a6e9311..52b226c 100644
--- a/src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/model/PollutedSource.kt
+++ b/src/main/kotlin/com/flightfeather/uav/biz/sourcetrace/model/PollutedSource.kt
@@ -3,7 +3,6 @@
 import com.flightfeather.uav.biz.dataanalysis.model.ExceptionType
 import com.flightfeather.uav.common.utils.DateUtil
 import com.flightfeather.uav.common.utils.MapUtil
-import com.flightfeather.uav.domain.entity.BaseRealTimeData
 import com.flightfeather.uav.domain.entity.SceneInfo
 import com.flightfeather.uav.domain.repository.SceneInfoRep
 import com.flightfeather.uav.lightshare.bean.AreaVo
@@ -12,6 +11,7 @@
 import com.flightfeather.uav.socket.eunm.FactorType
 import org.springframework.beans.BeanUtils
 import org.springframework.web.context.ContextLoader
+import kotlin.math.round
 
 /**
  * 姹℃煋鏉ユ簮
@@ -186,9 +186,9 @@
         pollutedData.statisticMap.entries.forEach { s ->
             val txtArr = mutableListOf<String>()
             s.value.excGroup?.forEach exception@{ p ->
-                val preValue = p.getFirstDataValue()
-                val curValue = p.getLastDataValue()
-                val per = p.per?.times(100)
+                val preValue = p.firstDataValue()
+                val curValue = p.lastDataValue()
+                val per = round(p.per?.times(100) ?: .0)
                 val rate = p.rate
                 if (preValue == null || curValue == null || per == null) return@exception
                 when (pollutedData.exceptionType) {
diff --git a/src/main/kotlin/com/flightfeather/uav/domain/repository/SourceTraceRep.kt b/src/main/kotlin/com/flightfeather/uav/domain/repository/SourceTraceRep.kt
index f726760..3d8d7e7 100644
--- a/src/main/kotlin/com/flightfeather/uav/domain/repository/SourceTraceRep.kt
+++ b/src/main/kotlin/com/flightfeather/uav/domain/repository/SourceTraceRep.kt
@@ -99,30 +99,52 @@
         return if (res.isEmpty()) null else res[0]
     }
 
+    val stMsgCache = mutableMapOf<String, List<BaseExceptionResult?>>()
     fun fetchList(
         deviceCode: String,
         startTime: Date,
         endTime: Date,
         msgType: MsgType? = null,
+        minPer: Double? = 0.5,
     ): List<BaseExceptionResult?> {
-        var res = sourceTraceMsgBlobMapper.selectWithBlob(deviceCode, startTime, endTime)
-        if (msgType !== null) {
-            res = res.filter { it?.msgType == msgType.value }
+        var stMsgList = listOf<BaseExceptionResult?>()
+        val key = "${deviceCode}_${startTime.time}_${endTime.time}_${msgType?.value}"
+        if (stMsgCache.containsKey(key)) {
+            stMsgList = stMsgCache[key]!!
         }
+        if (stMsgList.isEmpty()) {
+            var res = sourceTraceMsgBlobMapper.selectWithBlob(deviceCode, startTime, endTime)
+            if (msgType !== null) {
+                res = res.filter { it?.msgType == msgType.value }
+            }
 
-        return res.map { stm ->
-            when (stm?.msgType) {
-                MsgType.PolClue.value,
-                MsgType.DataChange.value,
-                    -> {
-                    GsonUtils.gson.fromJson(stm.blobContent, PollutedClue::class.java)
+            stMsgList = res.map { stm ->
+                when (stm?.msgType) {
+                    MsgType.PolClue.value,
+                    MsgType.DataChange.value,
+                        -> {
+                        GsonUtils.gson.fromJson(stm.blobContent, PollutedClue::class.java)
+                    }
+
+                    MsgType.AnaResult.value -> {
+                        GsonUtils.gson.fromJson(stm.blobContent, AnalysisResult::class.java)
+                    }
+
+                    else -> null
                 }
-
-                MsgType.AnaResult.value -> {
-                    GsonUtils.gson.fromJson(stm.blobContent, AnalysisResult::class.java)
+            }
+            stMsgCache[key] = stMsgList
+        }
+        // 绛涢�夊嚭寮傚父鏁版嵁PollutedClue涓紓甯哥櫨鍒嗘瘮澶т簬minPer鐨�
+        return stMsgList.filter {
+            if (it is PollutedClue) {
+                var valid = false
+                (it as PollutedClue?)?.pollutedData?.statisticMap?.entries?.forEach {sta->
+                    if (!valid) valid = (sta.value.avgPer?:.0) > minPer!!
                 }
-
-                else -> null
+                valid
+            } else {
+                true
             }
         }
     }
diff --git a/src/main/kotlin/com/flightfeather/uav/lightshare/bean/AnalysisOption.kt b/src/main/kotlin/com/flightfeather/uav/lightshare/bean/AnalysisOption.kt
index 3f6e965..25935f3 100644
--- a/src/main/kotlin/com/flightfeather/uav/lightshare/bean/AnalysisOption.kt
+++ b/src/main/kotlin/com/flightfeather/uav/lightshare/bean/AnalysisOption.kt
@@ -44,4 +44,8 @@
 
     @ApiParam("闇�瑕佺粺璁$殑鐩戞祴鍥犲瓙", example = "NO2, CO")
     var factorTypes: List<String>? = null
+
+    /** 鏈�灏忔薄鏌撶櫨鍒嗘瘮锛岀敤浜庣瓫閫夊紓甯告暟鎹偣锛堝彲閫夛級 */
+    @ApiModelProperty(value = "鏈�灏忔薄鏌撶櫨鍒嗘瘮锛岀敤浜庣瓫閫夊紓甯告暟鎹偣锛堝彲閫夛級")
+    var minPer: Double? = 0.5
 }
\ No newline at end of file
diff --git a/src/main/kotlin/com/flightfeather/uav/lightshare/service/DataAnalysisService.kt b/src/main/kotlin/com/flightfeather/uav/lightshare/service/DataAnalysisService.kt
index b2a14af..0587c85 100644
--- a/src/main/kotlin/com/flightfeather/uav/lightshare/service/DataAnalysisService.kt
+++ b/src/main/kotlin/com/flightfeather/uav/lightshare/service/DataAnalysisService.kt
@@ -39,9 +39,10 @@
      * 鑾峰彇鍘嗗彶姹℃煋婧簮缁撴灉
      * 鏍规嵁浠诲姟缂栧彿鏌ヨ骞惰繑鍥炲巻鍙叉薄鏌撴函婧愮粨鏋�
      * @param missionCode 璧拌埅浠诲姟缂栧彿
+     * @param minPer 鏈�灏忔薄鏌撶櫨鍒嗘瘮锛岀敤浜庣瓫閫夊紓甯告暟鎹偣锛堝彲閫夛級
      * @return 鍘嗗彶姹℃煋婧簮缁撴灉鐨勫瓧绗︿覆琛ㄧず锛堝叿浣撴牸寮忛渶鍙傝�冨疄鐜扮被锛�
      */
-    fun fetchHistory(missionCode: String): String
+    fun fetchHistory(missionCode: String, minPer: Double?): String
 
     /**
      * 鐢熸垚璧拌埅浠诲姟姹囨�荤粺璁�
@@ -51,9 +52,14 @@
      * @param areaVo 鍖哄煙鍙傛暟锛屽寘鍚粡绾害鑼冨洿绛夊湴鐞嗕俊鎭�
      * @return 姹囨�荤粺璁″璞★紝鍖呭惈浠诲姟鎬绘暟銆佸紓甯哥巼銆佸钩鍧囨暟鎹瓑鎸囨爣
      */
-    fun generateMissionSummary(startTime: Date, endTime: Date, areaVo: AreaVo): MissionSummary.Summary
+    fun generateMissionSummary(
+        startTime: Date, endTime: Date, areaVo: AreaVo, removeOtherDistrict: Boolean = false,
+        removeNoPollutedSource: Boolean = false, minPer: Double? = 0.5,
+    ): MissionSummary.Summary
 
-    fun generateMissionSummary(missionCode: String): MissionSummary.Summary
+    fun generateMissionSummary(
+        missionCode: String, minPer: Double? = 0.5,
+    ): MissionSummary.Summary
 
     /**
      * 鐢熸垚璧拌埅浠诲姟娓呭崟锛堟寜鏃堕棿鍜屽尯鍩熺瓫閫夛級
@@ -63,7 +69,10 @@
      * @param areaVo 鍖哄煙鍙傛暟
      * @return 璧拌埅浠诲姟淇℃伅鍒楄〃锛屾瘡涓厓绱犲寘鍚换鍔″熀鏈俊鎭拰缁熻鏁版嵁
      */
-    fun generateMissionList(startTime: Date, endTime: Date, areaVo: AreaVo): List<MissionInventory.MissionInfo>
+    fun generateMissionList(
+        startTime: Date, endTime: Date, areaVo: AreaVo, removeOtherDistrict: Boolean = false,
+        removeNoPollutedSource: Boolean = false, minPer: Double? = 0.5,
+    ): List<MissionInventory.MissionInfo>
 
     /**
      * 鐢熸垚璧拌埅浠诲姟娓呭崟锛堢洿鎺ュ鐞嗕换鍔$嚎绱級
@@ -86,7 +95,12 @@
      * @param areaVo 鍖哄煙鍙傛暟
      * @return 浠诲姟璇︽儏鍒楄〃锛屾瘡涓厓绱犲寘鍚换鍔″畬鏁翠俊鎭�佸満鏅暟鎹拰缁熻缁撴灉
      */
-    fun generateMissionDetail(startTime: Date, endTime: Date, areaVo: AreaVo,granularity: String?): List<MissionDetail>
+    fun generateMissionDetail(
+        startTime: Date, endTime: Date, areaVo: AreaVo, granularity: String?,
+        removeOtherDistrict: Boolean = false,
+        removeNoPollutedSource: Boolean = false,
+        minPer: Double? = 0.5,
+    ): List<MissionDetail>
 
     /**
      * 鐢熸垚璧拌埅浠诲姟璇︽儏锛堟寜浠诲姟缂栧彿绛涢�夛級
@@ -95,7 +109,9 @@
      * @param granularity 鏁版嵁棰楃矑搴︼紝鍙�夊�间负SECOND, MINUTE, HOUR, 榛樿MINUTE
      * @return 浠诲姟璇︽儏瀵硅薄锛屽寘鍚换鍔″畬鏁翠俊鎭�佸満鏅暟鎹拰缁熻缁撴灉
      */
-    fun generateMissionDetail(missionCode: String, granularity: String?): MissionDetail
+    fun generateMissionDetail(
+        missionCode: String, granularity: String?, minPer: Double? = 0.5,
+    ): MissionDetail
 
     /**
      * 鑾峰彇璧拌埅浠诲姟璇︽儏锛堢洿鎺ュ鐞嗕换鍔℃暟鎹級
@@ -115,16 +131,24 @@
         startTime: Date,
         endTime: Date,
         areaVo: AreaVo,
-        removeOtherDistrict: Boolean,
-        removeNoPollutedSource: Boolean,
+        removeOtherDistrict: Boolean = false,
+        removeNoPollutedSource: Boolean = false,
+        minPer: Double? = 0.5,
     ): List<MissionRiskArea.ClassifyClue>
 
-    fun generateClueByRiskArea(missionCode: String): List<MissionRiskArea.ClueByArea>
+    fun generateClueByRiskArea(missionCode: String, minPer: Double? = 0.5): List<MissionRiskArea.ClueByArea>
 
     fun generateClueByRiskArea(keyScenes: List<SceneInfo?>, pollutedClues: List<PollutedClue?>): List<MissionRiskArea.ClueByArea>
 
-    fun generateGridFusion(factorTypes: List<FactorType>, startTime: Date, endTime: Date, areaVo: AreaVo):
-            List<MissionGridFusion.GridFusionByAQI>
+    fun generateGridFusion(
+        factorTypes: List<FactorType>,
+        startTime: Date,
+        endTime: Date,
+        areaVo: AreaVo,
+        removeOtherDistrict: Boolean = false,
+        removeNoPollutedSource: Boolean = false,
+        minPer: Double? = 0.5,
+    ): List<MissionGridFusion.GridFusionByAQI>
 
     fun generateGridFusion(
         factorTypes: List<FactorType>,
diff --git a/src/main/kotlin/com/flightfeather/uav/lightshare/service/impl/DataAnalysisServiceImpl.kt b/src/main/kotlin/com/flightfeather/uav/lightshare/service/impl/DataAnalysisServiceImpl.kt
index efb1451..ad2cc21 100644
--- a/src/main/kotlin/com/flightfeather/uav/lightshare/service/impl/DataAnalysisServiceImpl.kt
+++ b/src/main/kotlin/com/flightfeather/uav/lightshare/service/impl/DataAnalysisServiceImpl.kt
@@ -77,13 +77,14 @@
      * 鑾峰彇鍘嗗彶姹℃煋婧簮缁撴灉
      * 鏌ヨ鎸囧畾浠诲姟鐨勫巻鍙叉薄鏌撴函婧愮粨鏋滃苟搴忓垪鍖栦负JSON瀛楃涓�
      * @param missionCode 璧拌埅浠诲姟缂栫爜
+     * @param minPer 鏈�灏忔薄鏌撶櫨鍒嗘瘮锛岀敤浜庣瓫閫夊紓甯告暟鎹偣锛堝彲閫夛級
      * @return 鍘嗗彶姹℃煋婧簮缁撴灉鐨凧SON瀛楃涓诧紝鍏蜂綋鏍煎紡鐢眘ourceTraceRep瀹炵幇鍐冲畾
      * @throws BizException 褰撹蛋鑸换鍔′笉瀛樺湪鏃舵姏鍑�
      */
-    override fun fetchHistory(missionCode: String): String {
+    override fun fetchHistory(missionCode: String, minPer: Double?): String {
         val mission = missionRep.findOne(missionCode) ?: throw BizException("璧拌埅浠诲姟涓嶅瓨鍦�")
 
-        val res = sourceTraceRep.fetchList(mission.deviceCode, mission.startTime, mission.endTime)
+        val res = sourceTraceRep.fetchList(mission.deviceCode, mission.startTime, mission.endTime, minPer = minPer ?: 0.5)
         return GsonUtils.gson.toJson(res)
     }
 
@@ -94,26 +95,33 @@
      * @param endTime 缁熻缁撴潫鏃堕棿锛堝寘鍚級
      * @param areaVo 鍖哄煙鍙傛暟锛屽寘鍚渷銆佸競銆佸尯涓夌骇琛屾斂鍖哄垝缂栫爜
      * @return 姹囨�荤粺璁″璞★紝鍖呭惈浠诲姟鎬绘暟銆佸紓甯哥偣鏁伴噺銆佸钩鍧囬噷绋嬬瓑鏍稿績鎸囨爣
-     * @see MissionSummary 姹囨�荤粺璁″鐞嗗櫒锛屽皝瑁呭叿浣撶殑缁熻閫昏緫瀹炵幇
+     * @see MissionSummary 姹囨�荤粺璁″鐞嗗櫒锛屽皝瑁呭叿浣撶粺璁¢�昏緫鐨勫疄鐜�
      */
-    override fun generateMissionSummary(startTime: Date, endTime: Date, areaVo: AreaVo): MissionSummary.Summary {
+    override fun generateMissionSummary(
+        startTime: Date, endTime: Date, areaVo: AreaVo, removeOtherDistrict: Boolean,
+        removeNoPollutedSource: Boolean, minPer: Double?,
+    ): MissionSummary.Summary {
         val clues = mutableListOf<PollutedClue?>()
         val missions = missionRep.findByAreaAndTime(areaVo, startTime, endTime).onEach {
             it ?: return@onEach
-            val clue = sourceTraceRep.fetchList(it.deviceCode, it.startTime, it.endTime, MsgType.PolClue) as List<PollutedClue?>
+            val clue = sourceTraceRep.fetchList(it.deviceCode, it.startTime, it.endTime, MsgType.PolClue, minPer ?: 0.5) as List<PollutedClue?>
             clues.addAll(clue)
         }
+        filterClue(areaVo, clues, removeOtherDistrict, removeNoPollutedSource)
         val summary = MissionSummary().execute(startTime, endTime, missions, clues)
         return summary
     }
 
-    override fun generateMissionSummary(missionCode: String): MissionSummary.Summary {
+    override fun generateMissionSummary(
+        missionCode: String, minPer: Double?,
+    ): MissionSummary.Summary {
         val mission = missionRep.findOne(missionCode) ?: throw BizException("璧拌埅浠诲姟涓嶅瓨鍦�")
         val clues = sourceTraceRep.fetchList(
             mission.deviceCode,
             mission.startTime,
             mission.endTime,
-            MsgType.PolClue
+            MsgType.PolClue,
+            minPer ?: 0.5,
         ) as List<PollutedClue?>
         val summary = MissionSummary().execute(mission.startTime, mission.endTime, listOf(mission), clues)
         return summary
@@ -129,17 +137,20 @@
      * @see MissionRep.findByAreaAndTime 鍖哄煙鏃堕棿绛涢�夋暟鎹簮
      * @see generateMissionList 閲嶈浇鏂规硶锛屽鐞嗗凡鍏宠仈鐨勬暟鎹
      */
-    override fun generateMissionList(startTime: Date, endTime: Date, areaVo: AreaVo): List<MissionInventory.MissionInfo> {
-//        val missionClues = missionRep.findByAreaAndTime(areaVo, startTime, endTime).filterNotNull().map {
-//            it to sourceTraceRep.fetchList(it.deviceCode, it.startTime, it.endTime, MsgType.PolClue) as List<PollutedClue?>
-//        }
-//        return generateMissionList(missionClues)
+    override fun generateMissionList(
+        startTime: Date, endTime: Date, areaVo: AreaVo, removeOtherDistrict: Boolean,
+        removeNoPollutedSource: Boolean, minPer: Double?,
+    ): List<MissionInventory.MissionInfo> {
         val missionCluesData = missionRep.findByAreaAndTime(areaVo, startTime, endTime).filterNotNull().map {
-            Triple(
-                it,
-                sourceTraceRep.fetchList(it.deviceCode, it.startTime, it.endTime, MsgType.PolClue) as List<PollutedClue?>,
-                realTimeDataRep.fetchData(it)
-            )
+            val clues = sourceTraceRep.fetchList(
+                it.deviceCode,
+                it.startTime,
+                it.endTime,
+                MsgType.PolClue,
+                minPer ?: 0.5
+            ) as List<PollutedClue?>
+            filterClue(areaVo, clues.toMutableList(), removeOtherDistrict, removeNoPollutedSource)
+            Triple(it, clues, realTimeDataRep.fetchData(it))
         }
         val keyScenes = sceneInfoRep.findBySceneTypes(
             listOf(
@@ -187,13 +198,20 @@
         endTime: Date,
         areaVo: AreaVo,
         granularity: String?,
+        removeOtherDistrict: Boolean,
+        removeNoPollutedSource: Boolean,
+        minPer: Double?,
     ): List<MissionInventory.MissionDetail> {
         val missionCluesData = missionRep.findByAreaAndTime(areaVo, startTime, endTime).filterNotNull().map {
-            Triple(
-                it,
-                sourceTraceRep.fetchList(it.deviceCode, it.startTime, it.endTime, MsgType.PolClue) as List<PollutedClue?>,
-                realTimeDataRep.fetchData(it)
-            )
+            val clues = sourceTraceRep.fetchList(
+                it.deviceCode,
+                it.startTime,
+                it.endTime,
+                MsgType.PolClue,
+                minPer ?: 0.5
+            ) as List<PollutedClue?>
+            filterClue(areaVo, clues.toMutableList(), removeOtherDistrict, removeNoPollutedSource)
+            Triple(it, clues, realTimeDataRep.fetchData(it))
         }
         val keyScenes = sceneInfoRep.findBySceneTypes(
             listOf(
@@ -205,13 +223,18 @@
         return generateMissionDetail(keyScenes, missionCluesData, granularity ?: "MINUTE")
     }
 
-    override fun generateMissionDetail(missionCode: String, granularity: String?): MissionInventory.MissionDetail {
+    override fun generateMissionDetail(
+        missionCode: String,
+        granularity: String?,
+        minPer: Double?,
+    ): MissionInventory.MissionDetail {
         val mission = missionRep.findOne(missionCode) ?: throw BizException("浠诲姟涓嶅瓨鍦�")
         val missionClues = sourceTraceRep.fetchList(
             mission.deviceCode,
             mission.startTime,
             mission.endTime,
-            MsgType.PolClue
+            MsgType.PolClue,
+            minPer ?: 0.5
         ) as List<PollutedClue?>
         val realTimeData = realTimeDataRep.fetchData(mission)
         val keyScenes = sceneInfoRep.findBySceneTypes(
@@ -248,33 +271,42 @@
         areaVo: AreaVo,
         removeOtherDistrict: Boolean,
         removeNoPollutedSource: Boolean,
+        minPer: Double?,
     ): List<MissionRiskArea.ClassifyClue> {
         val clues = mutableListOf<PollutedClue?>()
         missionRep.findByAreaAndTime(areaVo, startTime, endTime).onEach {
             it ?: return@onEach
-            val clue = sourceTraceRep.fetchList(it.deviceCode, it.startTime, it.endTime, MsgType.PolClue) as List<PollutedClue?>
+            val clue = sourceTraceRep.fetchList(
+                it.deviceCode,
+                it.startTime,
+                it.endTime,
+                MsgType.PolClue,
+                minPer ?: 0.5
+            ) as List<PollutedClue?>
             clues.addAll(clue)
         }
-        if (removeOtherDistrict) {
-            clues.removeIf {
-                !areaVo.districtName.isNullOrBlank() &&
-                        (it?.pollutedArea?.address.isNullOrBlank()
-                                || !it!!.pollutedArea!!.address!!.contains(areaVo.districtName!!))
-            }
-        }
-        if (removeNoPollutedSource) {
-            clues.removeIf { it?.pollutedSource?.sceneList.isNullOrEmpty() }
-        }
+//        if (removeOtherDistrict) {
+//            clues.removeIf {
+//                !areaVo.districtName.isNullOrBlank() &&
+//                        (it?.pollutedArea?.address.isNullOrBlank()
+//                                || !it!!.pollutedArea!!.address!!.contains(areaVo.districtName!!))
+//            }
+//        }
+//        if (removeNoPollutedSource) {
+//            clues.removeIf { it?.pollutedSource?.sceneList.isNullOrEmpty() }
+//        }
+        filterClue(areaVo, clues, removeOtherDistrict, removeNoPollutedSource)
         return MissionRiskArea().generateClueByRiskArea(clues)
     }
 
-    override fun generateClueByRiskArea(missionCode: String): List<MissionRiskArea.ClueByArea> {
+    override fun generateClueByRiskArea(missionCode: String, minPer: Double?): List<MissionRiskArea.ClueByArea> {
         val mission = missionRep.findOne(missionCode) ?: throw BizException("浠诲姟涓嶅瓨鍦�")
         val pollutedClues = sourceTraceRep.fetchList(
             mission.deviceCode,
             mission.startTime,
             mission.endTime,
-            MsgType.PolClue
+            MsgType.PolClue,
+            minPer ?: 0.5
         ) as List<PollutedClue?>
         val keyScenes = sceneInfoRep.findBySceneTypes(
             listOf(
@@ -298,6 +330,9 @@
         startTime: Date,
         endTime: Date,
         areaVo: AreaVo,
+        removeOtherDistrict: Boolean,
+        removeNoPollutedSource: Boolean,
+        minPer: Double?,
     ): List<MissionGridFusion.GridFusionByAQI> {
         val gridLen = 100
         // 鏌ヨ100绫崇綉鏍肩殑鍏蜂綋缃戞牸鏁版嵁
@@ -328,11 +363,15 @@
             val gridDataDetailMixVos = satelliteDataCalculateService.mixUnderwayGridData(gridGroup.id, gridDataIds)
             // 缁熻姣忎釜璧拌埅浠诲姟鐨勮蛋鑸鎯呬俊鎭�
             val missionCluesData = validMissions.filterNotNull().map {
-                Triple(
-                    it,
-                    sourceTraceRep.fetchList(it.deviceCode, it.startTime, it.endTime, MsgType.PolClue) as List<PollutedClue?>,
-                    realTimeDataRep.fetchData(it)
-                )
+                val clues = sourceTraceRep.fetchList(
+                    it.deviceCode,
+                    it.startTime,
+                    it.endTime,
+                    MsgType.PolClue,
+                    minPer ?: 0.5
+                ) as List<PollutedClue?>
+                filterClue(areaVo, clues.toMutableList(), removeOtherDistrict, removeNoPollutedSource)
+                Triple(it, clues, realTimeDataRep.fetchData(it))
             }
             val keyScenes = sceneInfoRep.findBySceneTypes(
                 listOf(
@@ -357,4 +396,26 @@
     ): List<MissionGridFusion.GridFusionByAQI> {
         return MissionGridFusion(sceneInfoRep).generateGridFusion(factorTypes, gridLen, gridCells, dataList)
     }
+
+    private fun filterClue(
+        areaVo: AreaVo, clues: MutableList<PollutedClue?>, removeOtherDistrict: Boolean,
+        removeNoPollutedSource: Boolean,
+    ) {
+        if (removeOtherDistrict) {
+            clues.removeIf {
+                !areaVo.districtName.isNullOrBlank() &&
+                        (it?.pollutedArea?.address.isNullOrBlank()
+                                || !it!!.pollutedArea!!.address!!.contains(areaVo.districtName!!))
+            }
+            clues.forEach {
+                it?.pollutedSource?.sceneList = it?.pollutedSource?.sceneList?.filter { s->
+                    s.districtCode == areaVo.districtCode
+                }
+            }
+        }
+        if (removeNoPollutedSource) {
+            clues.removeIf { it?.pollutedSource?.sceneList.isNullOrEmpty() }
+        }
+
+    }
 }
\ No newline at end of file
diff --git a/src/main/kotlin/com/flightfeather/uav/lightshare/web/DataAnalysisController.kt b/src/main/kotlin/com/flightfeather/uav/lightshare/web/DataAnalysisController.kt
index 8fd3212..f06e332 100644
--- a/src/main/kotlin/com/flightfeather/uav/lightshare/web/DataAnalysisController.kt
+++ b/src/main/kotlin/com/flightfeather/uav/lightshare/web/DataAnalysisController.kt
@@ -1,17 +1,13 @@
 package com.flightfeather.uav.lightshare.web
 
-import com.fasterxml.jackson.annotation.JsonFormat
 import com.flightfeather.uav.common.exception.BizException
 import com.flightfeather.uav.lightshare.bean.AnalysisOption
-import com.flightfeather.uav.lightshare.bean.AreaVo
 import com.flightfeather.uav.lightshare.service.DataAnalysisService
 import com.flightfeather.uav.socket.eunm.FactorType
 import io.swagger.annotations.Api
 import io.swagger.annotations.ApiOperation
 import io.swagger.annotations.ApiParam
-import org.springframework.format.annotation.DateTimeFormat
 import org.springframework.web.bind.annotation.*
-import java.time.LocalDateTime
 import java.time.ZoneId
 import java.util.*
 
@@ -36,7 +32,8 @@
     @GetMapping("/pollution/trace/history")
     fun fetchHistory(
         @ApiParam("璧拌埅浠诲姟缂栧彿") @RequestParam missionCode: String,
-    ) = resPack { dataAnalysisService.fetchHistory(missionCode) }
+        @ApiParam("鏈�灏忔薄鏌撶櫨鍒嗘瘮锛岀敤浜庣瓫閫夊紓甯告暟鎹偣锛堝彲閫夛級") @RequestParam(required = false) minPer: Double? = null,
+    ) = resPack { dataAnalysisService.fetchHistory(missionCode, minPer) }
 
     @ApiOperation(value = "鐢熸垚璧拌埅浠诲姟姹囨�荤粺璁�")
     @PostMapping("/report/missionSummary")
@@ -52,7 +49,10 @@
         dataAnalysisService.generateMissionSummary(
             Date.from(startTime.atZone(ZoneId.systemDefault()).toInstant()),
             Date.from(endTime.atZone(ZoneId.systemDefault()).toInstant()),
-            areaVo
+            areaVo,
+            analysisOption.removeOtherDistrict,
+            analysisOption.removeNoPollutedSource,
+            analysisOption.minPer,
         )
     }
 
@@ -60,7 +60,8 @@
     @GetMapping("/report/missionSummary/one")
     fun generateOneMissionSummary(
         @ApiParam("浠诲姟缂栧彿") @RequestParam missionCode: String,
-    ) = resPack { dataAnalysisService.generateMissionSummary(missionCode) }
+        @ApiParam("鏈�灏忔薄鏌撶櫨鍒嗘瘮锛岀敤浜庣瓫閫夊紓甯告暟鎹偣锛堝彲閫夛級") @RequestParam(required = false) minPer: Double? = null,
+    ) = resPack { dataAnalysisService.generateMissionSummary(missionCode, minPer) }
 
     @ApiOperation(value = "鐢熸垚璧拌埅浠诲姟娓呭崟")
     @PostMapping("/report/missionList")
@@ -76,7 +77,10 @@
         dataAnalysisService.generateMissionList(
             Date.from(startTime.atZone(ZoneId.systemDefault()).toInstant()),
             Date.from(endTime.atZone(ZoneId.systemDefault()).toInstant()),
-            areaVo
+            areaVo,
+            analysisOption.removeOtherDistrict,
+            analysisOption.removeNoPollutedSource,
+            analysisOption.minPer,
         )
     }
 
@@ -95,7 +99,10 @@
         dataAnalysisService.generateMissionDetail(
             Date.from(startTime.atZone(ZoneId.systemDefault()).toInstant()),
             Date.from(endTime.atZone(ZoneId.systemDefault()).toInstant()),
-            areaVo,granularity
+            areaVo,granularity,
+            analysisOption.removeOtherDistrict,
+            analysisOption.removeNoPollutedSource,
+            analysisOption.minPer,
         )
     }
 
@@ -105,7 +112,8 @@
         @ApiParam("浠诲姟缂栧彿") @RequestParam missionCode: String,
         @ApiParam("鏁版嵁棰楃矑搴�", allowableValues = "SECOND, MINUTE, HOUR") @RequestParam(required = false)
         granularity: String?,
-    ) = resPack { dataAnalysisService.generateMissionDetail(missionCode, granularity) }
+        @ApiParam("鏈�灏忔薄鏌撶櫨鍒嗘瘮锛岀敤浜庣瓫閫夊紓甯告暟鎹偣锛堝彲閫夛級") @RequestParam(required = false) minPer: Double? = null,
+    ) = resPack { dataAnalysisService.generateMissionDetail(missionCode, granularity, minPer) }
 
     @ApiOperation(value = "璧拌埅鍏稿瀷闅愭偅鍖哄煙缁熻")
     @PostMapping("/report/clueByRiskArea")
@@ -122,7 +130,8 @@
             Date.from(endTime.atZone(ZoneId.systemDefault()).toInstant()),
             areaVo,
             analysisOption.removeOtherDistrict,
-            analysisOption.removeNoPollutedSource
+            analysisOption.removeNoPollutedSource,
+            analysisOption.minPer,
         )
     }
 
@@ -130,7 +139,8 @@
     @GetMapping("/report/clueByRiskArea/one")
     fun generateOneClueByRiskArea(
         @ApiParam("浠诲姟缂栧彿") @RequestParam missionCode: String,
-    ) = resPack { dataAnalysisService.generateClueByRiskArea(missionCode) }
+        @ApiParam("鏈�灏忔薄鏌撶櫨鍒嗘瘮锛岀敤浜庣瓫閫夊紓甯告暟鎹偣锛堝彲閫夛級") @RequestParam(required = false) minPer: Double? = null,
+    ) = resPack { dataAnalysisService.generateClueByRiskArea(missionCode, minPer) }
 
     @ApiOperation(value = "鍙犲姞铻嶅悎鍒嗘瀽")
     @PostMapping("/report/gridFusion")
@@ -148,7 +158,10 @@
             factorTypes,
             Date.from(startTime.atZone(ZoneId.systemDefault()).toInstant()),
             Date.from(endTime.atZone(ZoneId.systemDefault()).toInstant()),
-            areaVo
+            areaVo,
+            analysisOption.removeOtherDistrict,
+            analysisOption.removeNoPollutedSource,
+            analysisOption.minPer,
         )
     }
 }
\ No newline at end of file
diff --git a/src/test/kotlin/com/flightfeather/uav/Test.kt b/src/test/kotlin/com/flightfeather/uav/Test.kt
index 2ca32f5..9da42dc 100644
--- a/src/test/kotlin/com/flightfeather/uav/Test.kt
+++ b/src/test/kotlin/com/flightfeather/uav/Test.kt
@@ -16,6 +16,7 @@
 import java.io.OutputStreamWriter
 import java.text.SimpleDateFormat
 import java.util.*
+import kotlin.math.round
 
 /**
  * @author riku
@@ -202,8 +203,8 @@
 
     @Test
     fun reGeo() {
-        val a = AMapService.reGeo(MapUtil.wgs84ToGcj02(121.45017 to 31.274426))
-        println(a)
+        val a = 0.7000000000000001
+        println(round(a * 100))
     }
 
     @Test
diff --git a/src/test/kotlin/com/flightfeather/uav/biz/sourcetrace/SourceTraceControllerTest.kt b/src/test/kotlin/com/flightfeather/uav/biz/sourcetrace/SourceTraceControllerTest.kt
index 7b99c9c..2a48760 100644
--- a/src/test/kotlin/com/flightfeather/uav/biz/sourcetrace/SourceTraceControllerTest.kt
+++ b/src/test/kotlin/com/flightfeather/uav/biz/sourcetrace/SourceTraceControllerTest.kt
@@ -39,8 +39,10 @@
     @Test
     fun autoSourceTrace() {
         val sourceTraceController = SourceTraceController(sceneInfoRep, sourceTraceRep, true)
-        val startTime = LocalDateTime.of(2025, 12, 11, 0, 0, 0).atZone(ZoneId.systemDefault()).toInstant()
-        val endTime = LocalDateTime.of(2025, 12, 11, 23, 59, 59).atZone(ZoneId.systemDefault()).toInstant()
+        val startTime = LocalDateTime.of(2025, 11, 19, 0, 0, 0).atZone(ZoneId.systemDefault()).toInstant()
+//        val endTime = LocalDateTime.of(2025, 11, 19, 23, 59, 59).atZone(ZoneId.systemDefault()).toInstant()
+//        val startTime = LocalDateTime.of(2025, 11, 1, 0, 0, 0).atZone(ZoneId.systemDefault()).toInstant()
+        val endTime = LocalDateTime.of(2025, 12, 31, 23, 59, 59).atZone(ZoneId.systemDefault()).toInstant()
         val missions = missionMapper.selectByExample(Example(Mission::class.java).apply {
             createCriteria().andBetween("startTime", startTime, endTime)
         })
@@ -65,8 +67,10 @@
 
     @Test
     fun deleteSourceTrace() {
-        val startTime = LocalDateTime.of(2025, 12, 11, 0, 0, 0).atZone(ZoneId.systemDefault()).toInstant()
-        val endTime = LocalDateTime.of(2025, 12, 11, 23, 59, 59).atZone(ZoneId.systemDefault()).toInstant()
+        val startTime = LocalDateTime.of(2025, 11, 19, 0, 0, 0).atZone(ZoneId.systemDefault()).toInstant()
+//        val endTime = LocalDateTime.of(2025, 11, 19, 23, 59, 59).atZone(ZoneId.systemDefault()).toInstant()
+//        val startTime = LocalDateTime.of(2025, 11, 1, 0, 0, 0).atZone(ZoneId.systemDefault()).toInstant()
+        val endTime = LocalDateTime.of(2025, 12, 31, 23, 59, 59).atZone(ZoneId.systemDefault()).toInstant()
         val missions = missionMapper.selectByExample(Example(Mission::class.java).apply {
             createCriteria().andBetween("startTime", startTime, endTime)
         })
diff --git a/src/test/kotlin/com/flightfeather/uav/lightshare/service/impl/DataAnalysisServiceImplTest.kt b/src/test/kotlin/com/flightfeather/uav/lightshare/service/impl/DataAnalysisServiceImplTest.kt
index aec4b68..fe09759 100644
--- a/src/test/kotlin/com/flightfeather/uav/lightshare/service/impl/DataAnalysisServiceImplTest.kt
+++ b/src/test/kotlin/com/flightfeather/uav/lightshare/service/impl/DataAnalysisServiceImplTest.kt
@@ -34,7 +34,7 @@
 
     @Test
     fun fetchHistory() {
-        dataAnalysisService.fetchHistory("SH-CN-20250723(01)")
+        dataAnalysisService.fetchHistory("SH-CN-20250723(01)", .0)
     }
 
     @Test
diff --git a/src/test/kotlin/com/flightfeather/uav/lightshare/service/impl/MissionServiceImplTest.kt b/src/test/kotlin/com/flightfeather/uav/lightshare/service/impl/MissionServiceImplTest.kt
index f5f17d3..bc62623 100644
--- a/src/test/kotlin/com/flightfeather/uav/lightshare/service/impl/MissionServiceImplTest.kt
+++ b/src/test/kotlin/com/flightfeather/uav/lightshare/service/impl/MissionServiceImplTest.kt
@@ -34,7 +34,7 @@
     @Test
     fun calMissionInfo() {
         missionMapper.selectByExample(Example(Mission::class.java).apply {
-            createCriteria().andBetween("startTime", "2025-12-05 00:00:00", "2025-12-31 23:59:59")
+            createCriteria().andBetween("startTime", "2025-12-11 00:00:00", "2025-12-11 23:59:59")
         }).forEach {mission ->
             mission?.let { missionService.calMissionInfo(it.missionCode) }
             Thread.sleep(1000)

--
Gitblit v1.9.3