feiyu02
2025-09-12 61871594dfa0a5ac2c4d895d9ec4034feba57094
src/main/kotlin/com/flightfeather/uav/biz/report/MissionSummary.kt
@@ -1,16 +1,31 @@
package com.flightfeather.uav.biz.report
import org.springframework.stereotype.Component
import com.flightfeather.uav.biz.sourcetrace.model.PollutedClue
import com.flightfeather.uav.domain.entity.Mission
import com.flightfeather.uav.lightshare.bean.AreaVo
import com.flightfeather.uav.socket.eunm.FactorType
import com.flightfeather.uav.socket.sender.MsgType
import java.util.*
import kotlin.math.round
/**
 * 走航任务汇总
 * @date 2025/8/22
 * @author feiyu02
 */
@Component
class MissionSummary {
class MissionSummary() {
    inner class Summary(
    companion object {
        private const val FOCUS_AREA_COUNT = 2
    }
    data class Summary(
        // 汇总周期开始时间
        val startTime: Date,
        // 汇总周期结束时间
        val endTime: Date,
        // 走航区域信息
        val area: AreaVo,
        // 走航次数
        val count: Int,
        // 总里程数(公里)
@@ -20,14 +35,143 @@
        // 各等级空气质量背景走航次数,<空气质量等级,次数,占比>
        val countByDegree: List<Triple<String, Int, Double>>,
        // 问题总数
        val probCount:Int,
        val probCount: Int,
        // 高风险场景总数
        val highRiskSceneCount:Int,
        val highRiskSceneCount: Int,
        // 问题按监测因子类型分布情况, <因子类型,次数,占比>
        val probByFactor:List<Triple<String, Int, Double>>
        val probByFactor: List<Triple<String, Int, Double>>,
        // 聚焦区域或场景
        val focusRegion: List<String>,
    )
    fun execute() {
    /**
     * 根据时间范围查询走航任务并生成统计结果
     * @param startTime 统计开始时间
     * @param endTime 统计结束时间
     * @param missions 走航任务列表(外部传入,避免重复查询)
     * @param clues 污染线索列表(用于问题统计)
     * @return 走航任务统计结果Summary
     */
    fun execute(startTime: Date, endTime: Date, missions: List<Mission?>, clues: List<PollutedClue?>): Summary {
        // 1. 查询指定时间范围内的走航任务
        if (missions.isEmpty()) {
            return Summary(
                startTime = startTime,
                endTime = endTime,
                area = AreaVo(), // 空任务时返回默认区域信息
                count = 0,
                kilometres = 0.0,
                regionList = emptyList(),
                countByDegree = emptyList(),
                probCount = 0,
                highRiskSceneCount = 0,
                probByFactor = emptyList(),
                focusRegion = emptyList()
            )
        }
        // 2. 基础统计:总任务数、总里程
        val totalCount = missions.size
        val totalKilometres = missions.sumOf { it?.kilometres?.toDouble() ?: 0.0 }
        // 3. 区域信息:取首个任务的行政区划信息(如存在多区域可扩展为合并逻辑)
        val firstMission = missions.first()
        val area = AreaVo().apply {
            provinceCode = firstMission?.provinceCode
            provinceName = firstMission?.provinceName
            cityCode = firstMission?.cityCode
            cityName = firstMission?.cityName
            districtCode = firstMission?.districtCode
            districtName = firstMission?.districtName
            townCode = firstMission?.townCode
            townName = firstMission?.townName
        }
        // 4. 涉及区域列表:去重收集所有任务的region字段
        val regionList = missions.mapNotNull { it?.region }.distinct()
        // 5. 空气质量等级分布:按pollutionDegree分组统计次数及占比
        val degreeGroups = missions
            .filter { it?.pollutionDegree != null } // 过滤无效等级
            .groupBy { it?.pollutionDegree!! }
            .mapValues { it.value.size }
        val countByDegree = degreeGroups.map { (degree, count) ->
            Triple(degree, count, count.toDouble() / totalCount)
        }
        // 6. 问题相关统计
        val clueRes = calClue(clues)
        val probCount = clueRes.first // 需关联问题表统计
        val highRiskSceneCount = clueRes.second // 需关联场景表统计
        val probByFactor = clueRes.third
        // 7. 从异常所在地区和溯源的场景中统计聚焦区域
        val focusRegion = calFocusRegion(clues)
        // 8. 构建并返回统计结果
        return Summary(
            startTime = startTime,
            endTime = endTime,
            area = area,
            count = totalCount,
            kilometres = totalKilometres,
            regionList = regionList,
            countByDegree = countByDegree,
            probCount = probCount,
            highRiskSceneCount = highRiskSceneCount,
            probByFactor = probByFactor,
            focusRegion = focusRegion
        )
    }
    private fun calClue(clues: List<PollutedClue?>): Triple<Int, Int, List<Triple<String, Int, Double>>> {
        var probCount = 0
        var highRiskSceneCount = 0
        val probByFactorMap = mutableMapOf<FactorType, Int>()
        clues.forEach { c ->
            if (c?.msgType == MsgType.PolClue.value) {
                c.pollutedSource?.sceneList?.size?.let { s -> highRiskSceneCount += s }
                c.pollutedData?.statisticMap?.keys?.forEach { k ->
                    probCount++
                    if (!probByFactorMap.containsKey(k)) {
                        probByFactorMap[k] = 0
                    }
                    probByFactorMap[k] = probByFactorMap[k]!! + 1
                }
            }
        }
        val probByFactor = probByFactorMap.entries.map {
            val per = if(probCount == 0) .0 else round(it.value.toDouble() / probCount * 100) / 100
            Triple(it.key.des, it.value, per)
        }
        return Triple(probCount, highRiskSceneCount, probByFactor)
    }
    private fun calFocusRegion(clues: List<PollutedClue?>): List<String> {
        // 统计每个区域或场景出现的次数
        val focusArea = mutableMapOf<String, Int>()
        val focusScene = mutableMapOf<String, Int>()
        clues.forEach { c->
            if (c?.msgType == MsgType.PolClue.value) {
                if (!c.pollutedArea?.address.isNullOrBlank()) {
                    if (focusArea.containsKey(c.pollutedArea?.address)) {
                        focusArea[c.pollutedArea?.address!!] = focusArea[c.pollutedArea?.address]!! + 1
                    } else {
                        focusArea[c.pollutedArea?.address!!] = 1
                    }
                }
                c.pollutedSource?.sceneList?.forEach { s->
                    if (s.name != null) {
                        if (focusScene.containsKey(s.name!!)) {
                            focusScene[s.name!!] = focusScene[s.name!!]!! + 1
                        } else {
                            focusScene[s.name!!] = 1
                        }
                    }
                }
            }
        }
        return focusArea.entries.sortedByDescending { it.value }.map { it.key }.take(FOCUS_AREA_COUNT) +
                focusScene.entries.sortedByDescending { it.value }.map { it.key }.take(FOCUS_AREA_COUNT)
    }
}